feat: i18n web components with locale-changed listener
Replace hardcoded German strings in modal.js and oikos-install-prompt.js with t() calls; wire locale-changed event listener for live re-render on locale switch. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,12 +4,15 @@
|
|||||||
* Focus-Restore, Scroll-Lock und aria-modal.
|
* Focus-Restore, Scroll-Lock und aria-modal.
|
||||||
* Auf Mobile: Bottom Sheet mit Swipe-to-Close und Slide-out-Animation.
|
* Auf Mobile: Bottom Sheet mit Swipe-to-Close und Slide-out-Animation.
|
||||||
* Abhängigkeiten: CSS-Klassen aus layout.css (.modal-overlay, .modal-panel, etc.)
|
* Abhängigkeiten: CSS-Klassen aus layout.css (.modal-overlay, .modal-panel, etc.)
|
||||||
|
* i18n.js (t)
|
||||||
*
|
*
|
||||||
* API:
|
* API:
|
||||||
* openModal({ title, content, onSave, onDelete, size }) → void
|
* openModal({ title, content, onSave, onDelete, size }) → void
|
||||||
* closeModal() → void
|
* closeModal() → void
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { t } from '/i18n.js';
|
||||||
|
|
||||||
let activeOverlay = null;
|
let activeOverlay = null;
|
||||||
let previouslyFocused = null;
|
let previouslyFocused = null;
|
||||||
let focusTrapHandler = null;
|
let focusTrapHandler = null;
|
||||||
@@ -191,7 +194,7 @@ export function openModal({ title, content, onSave, onDelete, size = 'md' } = {}
|
|||||||
aria-labelledby="shared-modal-title">
|
aria-labelledby="shared-modal-title">
|
||||||
<div class="modal-panel__header">
|
<div class="modal-panel__header">
|
||||||
<h2 class="modal-panel__title" id="shared-modal-title">${title}</h2>
|
<h2 class="modal-panel__title" id="shared-modal-title">${title}</h2>
|
||||||
<button class="modal-panel__close" data-action="close-modal" aria-label="Schließen">
|
<button class="modal-panel__close" data-action="close-modal" aria-label="${t('modal.closeLabel')}">
|
||||||
<i data-lucide="x" style="width:18px;height:18px" aria-hidden="true"></i>
|
<i data-lucide="x" style="width:18px;height:18px" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Modul: Install-Prompt Web Component
|
* Modul: Install-Prompt Web Component
|
||||||
* Zweck: Dezentes Banner für PWA-Installation (Chrome/Android) und iOS-Anleitung
|
* 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:
|
* Verhalten:
|
||||||
* - Chrome/Android: Fängt beforeinstallprompt ab, zeigt Install-Banner
|
* - Chrome/Android: Fängt beforeinstallprompt ab, zeigt Install-Banner
|
||||||
@@ -11,6 +11,8 @@
|
|||||||
* - Timing: Banner erst nach 2 Nutzer-Interaktionen anzeigen
|
* - Timing: Banner erst nach 2 Nutzer-Interaktionen anzeigen
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { t } from '/i18n.js';
|
||||||
|
|
||||||
const DISMISS_KEY = 'oikos-install-dismissed';
|
const DISMISS_KEY = 'oikos-install-dismissed';
|
||||||
const DISMISS_DURATION_MS = 7 * 24 * 60 * 60 * 1000; // 7 Tage
|
const DISMISS_DURATION_MS = 7 * 24 * 60 * 60 * 1000; // 7 Tage
|
||||||
|
|
||||||
@@ -39,6 +41,14 @@ class OikosInstallPrompt extends HTMLElement {
|
|||||||
return;
|
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
|
// Noch nicht genug Interaktionen
|
||||||
const interactions = Number(localStorage.getItem(INTERACTION_KEY) || '0');
|
const interactions = Number(localStorage.getItem(INTERACTION_KEY) || '0');
|
||||||
if (interactions < INTERACTION_THRESHOLD) {
|
if (interactions < INTERACTION_THRESHOLD) {
|
||||||
@@ -56,6 +66,9 @@ class OikosInstallPrompt extends HTMLElement {
|
|||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
window.removeEventListener('beforeinstallprompt', this._onBeforeInstall);
|
window.removeEventListener('beforeinstallprompt', this._onBeforeInstall);
|
||||||
if (this._offInteraction) this._offInteraction();
|
if (this._offInteraction) this._offInteraction();
|
||||||
|
if (this._onLocaleChanged) {
|
||||||
|
window.removeEventListener('locale-changed', this._onLocaleChanged);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_waitForInteractions() {
|
_waitForInteractions() {
|
||||||
@@ -97,6 +110,7 @@ class OikosInstallPrompt extends HTMLElement {
|
|||||||
|
|
||||||
/** Banner rendern */
|
/** Banner rendern */
|
||||||
_showBanner(isIOS) {
|
_showBanner(isIOS) {
|
||||||
|
this._currentIsIOS = isIOS;
|
||||||
this._shadow.innerHTML = '';
|
this._shadow.innerHTML = '';
|
||||||
|
|
||||||
const style = document.createElement('style');
|
const style = document.createElement('style');
|
||||||
@@ -242,7 +256,7 @@ class OikosInstallPrompt extends HTMLElement {
|
|||||||
|
|
||||||
const title = document.createElement('div');
|
const title = document.createElement('div');
|
||||||
title.className = 'title';
|
title.className = 'title';
|
||||||
title.textContent = 'Oikos installieren';
|
title.textContent = t('install.title');
|
||||||
|
|
||||||
const subtitle = document.createElement('div');
|
const subtitle = document.createElement('div');
|
||||||
subtitle.className = 'subtitle';
|
subtitle.className = 'subtitle';
|
||||||
@@ -251,12 +265,12 @@ class OikosInstallPrompt extends HTMLElement {
|
|||||||
// iOS: Teilen-Icon als SVG inline
|
// iOS: Teilen-Icon als SVG inline
|
||||||
subtitle.innerHTML = '';
|
subtitle.innerHTML = '';
|
||||||
subtitle.append(
|
subtitle.append(
|
||||||
document.createTextNode('Tippe auf '),
|
document.createTextNode(t('install.iosTip1')),
|
||||||
this._createShareIcon(),
|
this._createShareIcon(),
|
||||||
document.createTextNode(' → „Zum Home-Bildschirm"')
|
document.createTextNode(t('install.iosTip2'))
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
subtitle.textContent = 'Zur App hinzufügen';
|
subtitle.textContent = t('install.subtitle');
|
||||||
}
|
}
|
||||||
|
|
||||||
text.appendChild(title);
|
text.appendChild(title);
|
||||||
@@ -267,7 +281,7 @@ class OikosInstallPrompt extends HTMLElement {
|
|||||||
if (!isIOS) {
|
if (!isIOS) {
|
||||||
const btn = document.createElement('button');
|
const btn = document.createElement('button');
|
||||||
btn.className = 'btn-install';
|
btn.className = 'btn-install';
|
||||||
btn.textContent = 'Installieren';
|
btn.textContent = t('install.installButton');
|
||||||
btn.addEventListener('click', () => this._onInstallClick());
|
btn.addEventListener('click', () => this._onInstallClick());
|
||||||
banner.appendChild(btn);
|
banner.appendChild(btn);
|
||||||
}
|
}
|
||||||
@@ -275,7 +289,7 @@ class OikosInstallPrompt extends HTMLElement {
|
|||||||
// Dismiss-Button
|
// Dismiss-Button
|
||||||
const dismiss = document.createElement('button');
|
const dismiss = document.createElement('button');
|
||||||
dismiss.className = 'btn-dismiss';
|
dismiss.className = 'btn-dismiss';
|
||||||
dismiss.setAttribute('aria-label', 'Schließen');
|
dismiss.setAttribute('aria-label', t('install.dismissLabel'));
|
||||||
dismiss.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
|
dismiss.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
|
||||||
dismiss.addEventListener('click', () => this._dismiss());
|
dismiss.addEventListener('click', () => this._dismiss());
|
||||||
banner.appendChild(dismiss);
|
banner.appendChild(dismiss);
|
||||||
|
|||||||
Reference in New Issue
Block a user