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.
|
||||
* 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">
|
||||
<div class="modal-panel__header">
|
||||
<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>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -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 = `<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());
|
||||
banner.appendChild(dismiss);
|
||||
|
||||
Reference in New Issue
Block a user