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:
Ulas
2026-03-31 23:15:51 +02:00
parent a21fe6afdd
commit fe70cbf979
2 changed files with 25 additions and 8 deletions
+4 -1
View File
@@ -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>
+21 -7
View File
@@ -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);