106 lines
3.3 KiB
TypeScript
106 lines
3.3 KiB
TypeScript
/**
|
||
* Task H4 – i18n-Grundlage: zentrale Strings-Maps de/en.
|
||
*
|
||
* t(key) liefert den Text der aktuellen Sprache; unbekannte Keys
|
||
* fallen auf den Key zurück (im Code sichtbar, lint-bar über Tests).
|
||
* Sprache persistiert in localStorage ('webcad.language', Default de).
|
||
* Komponenten nutzen useT() für Live-Updates bei Sprachwechsel.
|
||
*
|
||
* Vollständige mechanische Ersetzung ALLER Komponenten-Texte ist
|
||
* teilautomatisierbares Follow-up; hier liegt die Infrastruktur und
|
||
* die Kern-UI-Texte (LayoutBar, Modus).
|
||
*/
|
||
import { useEffect, useState } from 'react';
|
||
|
||
export const LANGUAGES = ['de', 'en'] as const;
|
||
export type Language = (typeof LANGUAGES)[number];
|
||
|
||
type StringEntry = string | ((arg?: string) => string);
|
||
|
||
const STORAGE_KEY = 'webcad.language';
|
||
|
||
/** Zentrale Text-Maps (Key-Pfad → Text oder parametrisierte Funktion). */
|
||
const DE: Record<string, StringEntry> = {
|
||
'layouts.title': 'Layouts',
|
||
'layouts.add': 'Layout hinzufügen',
|
||
'layouts.empty': 'Keine Layouts',
|
||
'layouts.delete': (n?: string) => 'Layout ' + (n ?? '') + ' löschen',
|
||
'layouts.print': 'Layout drucken (A4 landscape)',
|
||
'layouts.titleblock': (n?: string) => 'Titelblock für ' + (n ?? '') + ' einfügen/aktualisieren',
|
||
'layouts.zoomto': (n?: string) => 'Zoom zu ' + (n ?? ''),
|
||
'mode.toSimple': 'Zum einfachen Modus wechseln',
|
||
'mode.toPro': 'Zum Profi-Modus wechseln',
|
||
};
|
||
|
||
const EN: Record<string, StringEntry> = {
|
||
'layouts.title': 'Layouts',
|
||
'layouts.add': 'Add layout',
|
||
'layouts.empty': 'No layouts',
|
||
'layouts.delete': (n?: string) => 'Delete layout ' + (n ?? ''),
|
||
'layouts.print': 'Print layout (A4 landscape)',
|
||
'layouts.titleblock': (n?: string) => 'Insert/update title block for ' + (n ?? ''),
|
||
'layouts.zoomto': (n?: string) => 'Zoom to ' + (n ?? ''),
|
||
'mode.toSimple': 'Switch to simple mode',
|
||
'mode.toPro': 'Switch to pro mode',
|
||
};
|
||
|
||
function mapFor(lang: Language): Record<string, StringEntry> {
|
||
return lang === 'en' ? EN : DE;
|
||
}
|
||
|
||
function readStored(): Language {
|
||
try {
|
||
const raw = localStorage.getItem(STORAGE_KEY);
|
||
if (raw === 'de' || raw === 'en') return raw;
|
||
} catch {
|
||
// localStorage nicht verfügbar
|
||
}
|
||
return 'de';
|
||
}
|
||
|
||
let current: Language = readStored();
|
||
const listeners = new Set<(lang: Language) => void>();
|
||
|
||
/** Aktuelle Sprache. */
|
||
export function getLanguage(): Language {
|
||
return current;
|
||
}
|
||
|
||
/** Sprache wechseln und persistieren. */
|
||
export function setLanguage(lang: Language): void {
|
||
current = lang;
|
||
try {
|
||
localStorage.setItem(STORAGE_KEY, lang);
|
||
} catch {
|
||
// Persistenz optional
|
||
}
|
||
for (const cb of listeners) cb(lang);
|
||
}
|
||
|
||
/** Subscribe auf Sprachwechsel; gibt Unsubscribe zurück. */
|
||
export function subscribeLanguage(cb: (lang: Language) => void): () => void {
|
||
listeners.add(cb);
|
||
return () => { listeners.delete(cb); };
|
||
}
|
||
|
||
/**
|
||
* Übersetzung: t('key') bzw. t('key', arg) für parametrisierte Texte.
|
||
* Unbekannter Key → Key als Fallback (offensichtlich sichtbar).
|
||
*/
|
||
export function t(key: string, arg?: string): string {
|
||
const entry = mapFor(current)[key];
|
||
if (typeof entry === 'function') return entry(arg);
|
||
if (typeof entry === 'string') return entry;
|
||
return key;
|
||
}
|
||
|
||
/** React-Hook: erzwingt Re-Render bei Sprachwechsel. */
|
||
export function useT(): typeof t {
|
||
const [, force] = useState(0);
|
||
useEffect(() => {
|
||
const off = subscribeLanguage(() => force((n) => n + 1));
|
||
return off;
|
||
}, []);
|
||
return t;
|
||
}
|