diff --git a/public/components/modal.js b/public/components/modal.js index 890d290..83e4ebd 100644 --- a/public/components/modal.js +++ b/public/components/modal.js @@ -4,12 +4,15 @@ * Focus-Restore, Scroll-Lock und aria-modal. * Auf Mobile: Bottom Sheet mit Swipe-to-Close und Slide-out-Animation. * Abhängigkeiten: CSS-Klassen aus layout.css (.modal-overlay, .modal-panel, etc.) + * i18n.js (t) * * API: * openModal({ title, content, onSave, onDelete, size }) → void * closeModal() → void */ +import { t } from '/i18n.js'; + let activeOverlay = null; let previouslyFocused = null; let focusTrapHandler = null; @@ -191,7 +194,7 @@ export function openModal({ title, content, onSave, onDelete, size = 'md' } = {} aria-labelledby="shared-modal-title"> diff --git a/public/components/oikos-install-prompt.js b/public/components/oikos-install-prompt.js index 2a0d407..5b5a3ff 100644 --- a/public/components/oikos-install-prompt.js +++ b/public/components/oikos-install-prompt.js @@ -1,7 +1,7 @@ /** * Modul: Install-Prompt Web Component * Zweck: Dezentes Banner für PWA-Installation (Chrome/Android) und iOS-Anleitung - * Abhängigkeiten: Design Tokens aus tokens.css (via CSS custom properties) + * Abhängigkeiten: Design Tokens aus tokens.css (via CSS custom properties), i18n.js (t) * * Verhalten: * - Chrome/Android: Fängt beforeinstallprompt ab, zeigt Install-Banner @@ -11,6 +11,8 @@ * - Timing: Banner erst nach 2 Nutzer-Interaktionen anzeigen */ +import { t } from '/i18n.js'; + const DISMISS_KEY = 'oikos-install-dismissed'; const DISMISS_DURATION_MS = 7 * 24 * 60 * 60 * 1000; // 7 Tage @@ -39,6 +41,14 @@ class OikosInstallPrompt extends HTMLElement { return; } + // locale-changed: Banner neu rendern wenn Sprache wechselt + this._onLocaleChanged = () => { + if (this._currentIsIOS !== undefined) { + this._showBanner(this._currentIsIOS); + } + }; + window.addEventListener('locale-changed', this._onLocaleChanged); + // Noch nicht genug Interaktionen const interactions = Number(localStorage.getItem(INTERACTION_KEY) || '0'); if (interactions < INTERACTION_THRESHOLD) { @@ -56,6 +66,9 @@ class OikosInstallPrompt extends HTMLElement { disconnectedCallback() { window.removeEventListener('beforeinstallprompt', this._onBeforeInstall); if (this._offInteraction) this._offInteraction(); + if (this._onLocaleChanged) { + window.removeEventListener('locale-changed', this._onLocaleChanged); + } } _waitForInteractions() { @@ -97,6 +110,7 @@ class OikosInstallPrompt extends HTMLElement { /** Banner rendern */ _showBanner(isIOS) { + this._currentIsIOS = isIOS; this._shadow.innerHTML = ''; const style = document.createElement('style'); @@ -242,7 +256,7 @@ class OikosInstallPrompt extends HTMLElement { const title = document.createElement('div'); title.className = 'title'; - title.textContent = 'Oikos installieren'; + title.textContent = t('install.title'); const subtitle = document.createElement('div'); subtitle.className = 'subtitle'; @@ -251,12 +265,12 @@ class OikosInstallPrompt extends HTMLElement { // iOS: Teilen-Icon als SVG inline subtitle.innerHTML = ''; subtitle.append( - document.createTextNode('Tippe auf '), + document.createTextNode(t('install.iosTip1')), this._createShareIcon(), - document.createTextNode(' → „Zum Home-Bildschirm"') + document.createTextNode(t('install.iosTip2')) ); } else { - subtitle.textContent = 'Zur App hinzufügen'; + subtitle.textContent = t('install.subtitle'); } text.appendChild(title); @@ -267,7 +281,7 @@ class OikosInstallPrompt extends HTMLElement { if (!isIOS) { const btn = document.createElement('button'); btn.className = 'btn-install'; - btn.textContent = 'Installieren'; + btn.textContent = t('install.installButton'); btn.addEventListener('click', () => this._onInstallClick()); banner.appendChild(btn); } @@ -275,7 +289,7 @@ class OikosInstallPrompt extends HTMLElement { // Dismiss-Button const dismiss = document.createElement('button'); dismiss.className = 'btn-dismiss'; - dismiss.setAttribute('aria-label', 'Schließen'); + dismiss.setAttribute('aria-label', t('install.dismissLabel')); dismiss.innerHTML = ``; dismiss.addEventListener('click', () => this._dismiss()); banner.appendChild(dismiss);