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>
This commit is contained in:
@@ -1,16 +1,23 @@
|
||||
"use client";
|
||||
|
||||
// Stellt Sprache + Wörterbuch für Client-Komponenten bereit. Wird einmal
|
||||
// im Root-Layout mit dem serverseitig aus dem Cookie gelesenen Locale
|
||||
// instanziiert; Client-Komponenten lesen darüber via useDict()/useLocale().
|
||||
// Stellt Sprache + Wörterbuch + Liste der aktiven Sprachen für Client-
|
||||
// Komponenten bereit. Wird einmal im Root-Layout mit den serverseitig
|
||||
// ermittelten Werten instanziiert; Client-Komponenten lesen darüber
|
||||
// via useDict()/useLocale()/useLanguages().
|
||||
|
||||
import { createContext, useContext, useMemo } from "react";
|
||||
import { formatMessage } from "@/lib/i18n/shared";
|
||||
import type { Dict, Locale } from "@/lib/i18n/shared";
|
||||
|
||||
export interface LanguageInfo {
|
||||
code: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface LocaleContextValue {
|
||||
locale: Locale;
|
||||
dict: Dict;
|
||||
languages: LanguageInfo[];
|
||||
}
|
||||
|
||||
const LocaleContext = createContext<LocaleContextValue | null>(null);
|
||||
@@ -18,9 +25,13 @@ const LocaleContext = createContext<LocaleContextValue | null>(null);
|
||||
export function LocaleProvider({
|
||||
locale,
|
||||
dict,
|
||||
languages,
|
||||
children,
|
||||
}: LocaleContextValue & { children: React.ReactNode }) {
|
||||
const value = useMemo(() => ({ locale, dict }), [locale, dict]);
|
||||
const value = useMemo(
|
||||
() => ({ locale, dict, languages }),
|
||||
[locale, dict, languages]
|
||||
);
|
||||
return (
|
||||
<LocaleContext.Provider value={value}>{children}</LocaleContext.Provider>
|
||||
);
|
||||
@@ -40,6 +51,11 @@ export function useLocale(): Locale {
|
||||
return useLocaleContext().locale;
|
||||
}
|
||||
|
||||
/** Alle aktiven Sprachen (Code + Anzeigename), z. B. für den Sprachumschalter. */
|
||||
export function useLanguages(): LanguageInfo[] {
|
||||
return useLocaleContext().languages;
|
||||
}
|
||||
|
||||
/** t("Text mit {var}", { var: "Wert" }) für Platzhalter in Übersetzungen. */
|
||||
export function useFormatMessage() {
|
||||
return formatMessage;
|
||||
|
||||
Reference in New Issue
Block a user