// Sprache der Seite (Deutsch/Englisch) im gettext-Stil: Der deutsche Text ist zugleich der Schlüssel. // `t('Deutscher Text', { name })` schlägt ihn bei Englisch in EN (en.ts) nach und setzt die Platzhalter `{name}` ein; // fehlt der Eintrag, bleibt der deutsche Text. Übersetzt wird beim Aufruf, ein Sprachwechsel baut die Oberfläche neu auf (main.ts). import { EN } from './en'; export type Lang = 'de' | 'en'; export const LANG_KEY = 'aircard.lang'; /** * Startsprache: die gespeicherte Wahl (`de` oder `en`), sonst Deutsch, wenn die erste Browsersprache mit `de` beginnt, * andernfalls Englisch. Dieselbe Regel steht im Skript in index.html, das vor dem ersten Paint setzt. */ export function initialLang(stored: string | null, browserLanguages: readonly string[]): Lang { if (stored === 'de' || stored === 'en') return stored; return (browserLanguages[0] ?? '').toLowerCase().startsWith('de') ? 'de' : 'en'; } function readStored(): string | null { try { return localStorage.getItem(LANG_KEY); } catch { return null; // Speicher gesperrt (z. B. privates Fenster): es gilt die Browsersprache } } /** Die bevorzugten Browsersprachen: `navigator.languages`, ersatzweise `[navigator.language]`. */ function browserLanguages(): readonly string[] { if (navigator.languages?.length) return navigator.languages; return navigator.language ? [navigator.language] : []; } // Die Sprache der Seite; null, bis sie zum ersten Mal gebraucht wird. let lang: Lang | null = null; const listeners = new Set<(lang: Lang) => void>(); /** Die Sprache der Seite; beim ersten Aufruf aus der gespeicherten Wahl bzw. der Browsersprache bestimmt. */ export function currentLang(): Lang { lang ??= initialLang(readStored(), browserLanguages()); return lang; } /** * Stellt die Sprache um: setzt , merkt sie sich (außer mit `persist: false`) und meldet einen echten Wechsel an * die Hörer. Das Neuzeichnen von Kopfzeile und Seite ist Sache der Hörer. */ export function setLang(next: Lang, opts: { persist?: boolean } = {}): void { const changed = next !== currentLang(); lang = next; document.documentElement.lang = next; if (opts.persist ?? true) { try { localStorage.setItem(LANG_KEY, next); } catch { // gilt dann nur für diese Sitzung } } // Kopie, damit sich ein Hörer beim Melden abmelden darf. if (changed) for (const fn of [...listeners]) fn(next); } /** Meldet jeden Sprachwechsel (nicht den bloßen Aufruf mit der aktuellen Sprache). Gibt die Abmeldung zurück. */ export function onLangChange(fn: (lang: Lang) => void): () => void { listeners.add(fn); return () => { listeners.delete(fn); }; } /** * Übersetzt einen deutschen Text in die Sprache der Seite und setzt die Platzhalter `{name}` aus `params` ein; * ein Platzhalter ohne Wert bleibt stehen. Ohne Eintrag in EN bleibt der deutsche Text. */ export function t(text: string, params?: Readonly>): string { const base = currentLang() === 'en' && Object.hasOwn(EN, text) ? EN[text] : text; if (!params) return base; return base.replace(/\{(\w+)\}/g, (placeholder, name: string) => (Object.hasOwn(params, name) ? String(params[name]) : placeholder)); } /** Markiert einen deutschen Text, der vorab in einer Konstante steht; übersetzt wird erst beim Anzeigen mit `t()`. */ export function msg(text: T): T { return text; }