Files
DOSSIER-STANDALONE/src/i18n/index.ts
T
karim 359ce35737 Browser-BIM (cad): semantisches Modell, abgeleitete 2D/3D-Sichten, Zeichenwerkzeuge
Standalone-Browser-Port von DOSSIER. Enthaelt das semantische Modell mit
Plan-/3D-Ableitung, Zeichen- und Editierwerkzeuge, Rhino-artiges Befehlssystem,
dockbares Panel-System, Resource-Manager, DXF/.lin/.pat-Import, i18n (de/en)
sowie Projektdokumentation und Probe-Harness.
2026-06-30 20:52:27 +02:00

95 lines
3.6 KiB
TypeScript

// i18n-Schicht — eine einzige Übersetzungsfunktion `t(key, params?)` plus ein
// optionaler React-Hook `useT`. Default-Sprache ist Deutsch; das deutsche
// Wörterbuch (de.ts) hält die EXAKTEN bisherigen UI-Texte, sodass die Umstellung
// optisch nichts ändert. en.ts beweist die Übersetzbarkeit.
//
// Bewusst minimal gehalten: ein modulweiter „current language" + Wörterbuch-
// Lookup mit {param}-Interpolation. Kein Provider-Zwang — t(...) ist überall
// direkt aufrufbar; useT() liefert dieselbe Funktion und re-rendert Komponenten
// beim Sprachwechsel (subscribe), falls je ein Umschalter gebaut wird.
//
// Identifier/Keys bleiben englisch (DOSSIER-Namespacing); nur die Wörterbuch-
// Werte sind übersetzbarer Text.
import { useSyncExternalStore } from "react";
import { de } from "./de";
import { en } from "./en";
import type { TranslationKey } from "./de";
export type { TranslationKey } from "./de";
/** Unterstützte Sprachen. Default ist „de". */
export type Language = "de" | "en";
/** Wörterbücher je Sprache. de ist die Quelle der Keys (Default-Sprache). */
const DICTS: Record<Language, Record<TranslationKey, string>> = { de, en };
/** Werte für die Platzhalter-Interpolation (`{name}` → params.name). */
export type TParams = Record<string, string | number>;
// ── Modulweiter Sprachzustand + Abonnenten (für useT) ──────────────────────
let currentLanguage: Language = "de";
const listeners = new Set<() => void>();
/** Aktuell gewählte Sprache. */
export function getLanguage(): Language {
return currentLanguage;
}
/**
* Sprache umschalten. Benachrichtigt abonnierte Komponenten (useT), damit sie
* mit den neuen Texten neu rendern. (Noch kein UI-Umschalter — die Naht ist
* aber vorhanden und verdrahtet.)
*/
export function setLanguage(lang: Language): void {
if (lang === currentLanguage) return;
currentLanguage = lang;
for (const l of listeners) l();
}
// ── Kern: Lookup + Interpolation ───────────────────────────────────────────
/** Ersetzt `{name}`-Platzhalter im Wert durch die übergebenen Parameter. */
function interpolate(value: string, params?: TParams): string {
if (!params) return value;
return value.replace(/\{(\w+)\}/g, (match, name: string) =>
name in params ? String(params[name]) : match,
);
}
/**
* Übersetzt einen Key in der aktuellen Sprache mit optionaler {param}-
* Interpolation. Fehlt der Key in der gewählten Sprache, greift Deutsch als
* Fallback; fehlt er auch dort, wird der Key selbst zurückgegeben (sichtbar,
* damit fehlende Texte auffallen statt leer zu bleiben).
*/
export function t(key: TranslationKey | string, params?: TParams): string {
const dict = DICTS[currentLanguage];
const value =
(dict as Record<string, string>)[key] ??
(de as Record<string, string>)[key] ??
key;
return interpolate(value, params);
}
// ── React-Hook ─────────────────────────────────────────────────────────────
/**
* Hook-Variante: liefert dieselbe `t`-Funktion, abonniert aber den
* Sprachzustand, sodass die Komponente beim Sprachwechsel neu rendert. In den
* meisten Komponenten reicht der direkte `t`-Import; useT() ist für den Fall
* gedacht, dass ein Live-Sprachumschalter existiert.
*/
export function useT(): typeof t {
useSyncExternalStore(
(cb) => {
listeners.add(cb);
return () => listeners.delete(cb);
},
getLanguage,
getLanguage,
);
return t;
}