Files
Speisekarte/lib/i18n/index.ts
T
florian.wildeandClaude Sonnet 5 236f8c9490 feat: Dynamisches Sprachpaket-System für die Bedienoberfläche
Teil 1 des "mehrsprachig variabel"-Umbaus (Speiseplan-Inhalte folgen
in einer späteren Version). Sprachen sind jetzt zur Laufzeit über eine
neue Admin-Seite "Sprachen" verwaltbar statt fest im Code zu stehen:

- Neue Tabellen languages/ui_translations (Migration 0004), bestehende
  Englisch-/Polnisch-Texte per generiertem Seed automatisch übernommen
  (Migration 0005) - keine manuelle Nacharbeit, nichts geht verloren.
- lib/i18n/flatten.ts: verschachtelte Dict-Objekte (inkl. Arrays wie
  weekdays.long) <-> flache Schlüssel-Wert-Paare, Basis für Vorlage-
  Export/-Import.
- lib/i18n/template.ts: Vollständigkeits-/Veraltet-Berechnung (Hash
  über die DE-Schlüsselliste) und Import-Validierung (Platzhalter wie
  {message} müssen erhalten bleiben, unbekannte Schlüssel werden
  ignoriert statt Fehler zu werfen).
- getDictionary()/getLocale() (lib/i18n/index.ts) jetzt asynchron und
  DB-gestützt für Nicht-Basis-Sprachen, mit Fallback auf Deutsch für
  fehlende Schlüssel. Locale-Typ bewusst von festem TypeScript-Tupel
  auf string generalisiert (Kompromiss: Compiler prüft Vollständigkeit
  nicht mehr automatisch, das übernimmt jetzt die Veraltet-Erkennung
  zur Laufzeit).
- LanguageSwitcher/LocaleProvider lesen aktive Sprachen jetzt aus dem
  Context statt aus einer festen Konstante; blendet sich bei nur einer
  aktiven Sprache automatisch aus.
- Neue Admin-Routen app/api/admin/languages/* (Liste inkl.
  Vollständigkeit, Anlegen, Aktivieren/Deaktivieren mit
  "mindestens eine muss aktiv bleiben"-Schutz, Löschen außer
  Basis-Sprache, Vorlage-Download, Datei-Import) + LanguageManager.tsx.

Vollständig gegen echten DB-Zustand getestet: Vorlage-Export/-Import
inkl. Platzhalter-Validierung, neue Sprache anlegen, auf eine Sprache
reduzieren (Umschalter verschwindet, "letzte Sprache"-Schutz greift),
Veraltet-Erkennung nach simuliertem neuen Schlüssel, Cascade-Delete.

Version 1.10.0.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 10:34:51 +02:00

38 lines
1.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Nur für Server-Komponenten/Routen importieren (next/headers, DB)!
// Client-Komponenten müssen stattdessen "@/lib/i18n/shared" verwenden,
// sonst landet next/headers/die DB-Anbindung im Client-Bundle.
import "server-only";
import { cookies } from "next/headers";
import { listActiveLanguageCodes, listTranslations } from "@/lib/db/languages";
import { dictionaries, type Dict } from "./dictionaries";
import { flattenDict, unflattenDict } from "./flatten";
import { DEFAULT_LOCALE, isLocale, type Locale } from "./locales";
import { LOCALE_COOKIE } from "./shared";
export * from "./shared";
/** Aktuelle Sprache aus dem Cookie lesen, validiert gegen die aktiven
* Sprachen (Server-Komponenten). Ungültig oder nicht mehr aktiv →
* Basis-Sprache. */
export async function getLocale(): Promise<Locale> {
const store = await cookies();
const value = store.get(LOCALE_COOKIE)?.value;
const active = await listActiveLanguageCodes();
return isLocale(value, active) ? value : DEFAULT_LOCALE;
}
/** Wörterbuch für eine Sprache. Nicht-deutsche Sprachen werden aus der
* Datenbank geladen und mit der deutschen Vorlage aufgefüllt – fehlt
* eine Übersetzung (z. B. nach einem Update mit neuen Texten), zeigt
* die App automatisch den deutschen Text statt leer zu bleiben. */
export async function getDictionary(locale: Locale): Promise<Dict> {
if (locale === DEFAULT_LOCALE) return dictionaries.de;
const rows = await listTranslations(locale);
const flat = flattenDict(dictionaries.de);
for (const row of rows) {
if (row.key in flat) flat[row.key] = row.value;
}
return unflattenDict(flat) as Dict;
}