# Gateway i18n: Einheitliches Sprachsystem ## Beschreibung und Kontext Das Frontend nutzt bereits ein DB-basiertes Sprachsystem (`t()` -> `UiLanguageSet` -> AI-Uebersetzung). Das Gateway hat aktuell **kein** einheitliches System -- UI-sichtbare Texte sind hardcoded (deutsch/englisch), Model-Labels werden pro Datei mit `registerModelLabels()` in allen Sprachen manuell gepflegt, und `TextMultilingual`-Felder haben einen starren 4-Sprachen-Renderer. **Business-Treiber:** Mehrsprachigkeit ist Pflicht fuer SaaS. Aktuell muss jede neue Sprache manuell in ~115 `registerModelLabels()`-Aufrufen und ~150 `HTTPException`-Texten nachgepflegt werden. Das skaliert nicht. Mit dem `@i18nModel`-Decorator entfallen die manuellen Aufrufe komplett. **Risiko bei Nicht-Umsetzung:** Neue Sprachen erfordern Code-Aenderungen in 29+ Dateien. Inkonsistente Uebersetzungen. Kein AI-Uebersetzungsworkflow fuer Backend-Texte. ## Fokus und kritische Details - **Caching:** `@i18nModel` Decorator wird bei Import-Time ausgefuehrt (~29 Klassen). Die `t()`-Funktion darf NICHT pro Aufruf die DB abfragen. Loesung: In-Memory-Cache beim Boot laden, danach O(1)-Lookup. - **Boot-Reihenfolge:** Der Gateway-Boot-Scan muss NACH DB-Init laufen, aber VOR dem ersten Request. - **Abwaertskompatibilitaet:** `getModelLabels()` und `getModelLabel()` muessen weiterhin funktionieren (werden von `routeAttributes.py`, `routeSystem.py` etc. aufgerufen). Die neue Implementierung ersetzt die interne Logik, nicht die API. - **Context-Namensraum:** Gateway-Texte bekommen `context = "api.*"`, Model-Labels `context = "table..*"`. Kein Konflikt mit Frontend `context = "ui"`. - **AI-Kontext (value):** Wie im Frontend wird der `value` im xx-Set als Kontext-Beschreibung fuer die AI genutzt. Fuer Model-Labels wird der Kontext automatisch aus Pydantic-Metadaten (Class-Docstring, Field-Description) extrahiert. Fuer HTTPExceptions wird er manuell mitgegeben oder aus Route-Name + HTTP-Status abgeleitet. ## Ziel und Nicht-Ziele - **Ziel:** Alle UI-sichtbaren Gateway-Texte laufen ueber das bestehende `UiLanguageSet`-System und werden per AI uebersetzt wie Frontend-Texte. - **Ziel:** `registerModelLabels()` wird komplett durch `@i18nModel` Decorator ersetzt -- Labels und AI-Kontext werden automatisch aus Pydantic-Metadaten extrahiert. - **Ziel:** `TextMultilingual`-Felder im UI dynamisch fuer alle verfuegbaren Sprachen rendern (nicht nur 4 hardcoded). - **Explizit NICHT:** Log-Eintraege und AI-Prompts werden NICHT mehrsprachig. Interne Fehlermeldungen die nie im UI erscheinen bleiben englisch. ## Betroffene Module - **Gateway:** - `modules/shared/i18nRegistry.py` -- NEUES Modul: `t()`, `@i18nModel`, Boot-Scan, Cache, DB-Sync - `modules/shared/attributeUtils.py` -- `getModelLabels()`/`getModelLabel()` auf neues System umstellen, `registerModelLabels()` entfernen - `modules/routes/routeI18n.py` -- Erweiterung: Gateway-Keys in sync-xx integrieren - `modules/datamodels/*.py` (~29 Dateien) -- `@i18nModel` Decorator + `json_schema_extra["label"]`, `registerModelLabels()` entfernen - `modules/routes/*.py` (~21 Dateien) -- `HTTPException(detail=...)` auf `t()` umstellen - `app.py` -- Boot-Hook fuer Gateway-Key-Scan - **Frontend:** - `FormGeneratorForm.tsx` -- `TextMultilingual`-Renderer dynamisch machen - `FormGeneratorTable.tsx` -- `formatTextMultilingual` dynamisch machen - `AdminLanguagesPage.tsx` -- Gateway-Keys in sync-xx anzeigen (optional) - **DB-Migration:** Nein (nutzt bestehendes `UiLanguageSet`-Schema) - **Andere Komponenten:** Keine ## Entscheidungen | Datum | Entscheidung | Begruendung | |-------|-------------|------------| | 2026-04-09 | Gateway-`t()` mit In-Memory-Cache, kein DB-Call pro Aufruf | Performance: ~115 registerModelLabels + ~150 HTTPException = ~265+ Aufrufe pro Request-Zyklus | | 2026-04-09 | Boot-Scan beim App-Start, automatisch in DB schreiben | Konsistenz: Jeder Gateway-Start hat ein vollstaendiges Key-Set in der DB | | 2026-04-09 | Context-Prefix `api` fuer Gateway-UI-Texte, `table.` fuer Model-Labels | Klare Trennung von Frontend (`ui`) und Backend-Texten | | 2026-04-09 | `TextMultilingual` dynamisch aus `availableLanguages` rendern | Skaliert mit neuen Sprachen ohne Code-Aenderung | | 2026-04-09 | `@i18nModel` Decorator ersetzt `registerModelLabels()` komplett | Eliminiert Duplikation: Labels + AI-Kontext kommen aus Pydantic-Metadaten. Entwickler schreibt weniger Code, neue Felder werden automatisch registriert | ## Architektur ### Datenfluss ``` Gateway Boot: 1. DB init (PostgreSQL) 2. Module laden -> @i18nModel Decorator + t() Aufrufe -> Keys + Kontext in _REGISTRY sammeln 3. Boot-Hook: _REGISTRY -> UiLanguageSet(xx) sync - Nur context="api"/"table.*" Keys mergen (UI-Keys context="ui" nicht anfassen) - Neue Keys hinzufuegen, verwaiste entfernen, Kontext (value) aktualisieren 4. Sprach-Cache laden: alle UiLanguageSets -> In-Memory Dict Request: User (language=fr) -> Middleware setzt _CURRENT_LANGUAGE="fr" -> Gateway -> t("Zugriff verweigert") -> Cache["fr"]["Zugriff verweigert"] -> "Accès refusé" AI-Uebersetzung (Admin sync): xx-Entry: {context: "api.routeSecurityLocal", key: "Zugriff verweigert", value: "Fehlermeldung bei fehlendem Zugriff auf eine Ressource"} -> AI bekommt: {"key": "Zugriff verweigert", "context": "Fehlermeldung bei fehlendem Zugriff auf eine Ressource"} -> AI uebersetzt kontextbewusst: "Access denied" (en), "Accès refusé" (fr) ``` ### Kontext-Strategie fuer die AI-Uebersetzung Das Frontend speichert im `xx`-Set den `value` als **Kontext-Beschreibung fuer die AI**: - Entry: `{context: "ui", key: "Abbrechen", value: "pages/admin/AdminLanguagesPage.tsx > AdminLanguagesPage"}` - Die AI bekommt: `{"key": "Abbrechen", "context": "Button in Admin-Sprachverwaltung"}` und weiss damit, wie der Text zu uebersetzen ist. Fuer das Backend bauen wir denselben Mechanismus. Der Kontext wird automatisch aus der Code-Struktur abgeleitet: | Kategorie | context | value (AI-Kontext) | Beispiel | |-----------|---------|-------------------|----------| | **Model-Label** (Tabelle) | `table.Feature` | Pydantic-Class-Docstring oder `"Datenmodell: Feature"` | `key="Feature", value="Datenmodell fuer installierbare Funktionsmodule"` | | **Model-Attribut** | `table.Feature.label` | Field-Description aus Pydantic `Field(description=...)` | `key="Bezeichnung", value="Anzeigename des Features (mehrsprachig)"` | | **API-Fehlermeldung** | `api.route.` | Automatisch aus Route-Funktion + HTTP-Status | `key="Zugriff verweigert", value="HTTP 403 in routeSecurityLocal.login"` | | **API-UI-Text** | `api.` | Manuell uebergeben oder aus Funktionsname | `key="Datei hochgeladen", value="Erfolgsmeldung nach Datei-Upload"` | ### Neues Modul: `i18nRegistry.py` ```python # platform-core/modules/shared/i18nRegistry.py _REGISTRY: Dict[str, I18nRegistryEntry] = {} # key -> {context, value} _CACHE: Dict[str, Dict[str, str]] = {} # lang -> {key -> translation} _CURRENT_LANGUAGE: ContextVar[str] = ContextVar("i18n_lang", default="de") @dataclass class I18nRegistryEntry: context: str # z.B. "table.Feature.label", "api.routeSecurityLocal" value: str # AI-Kontext: Beschreibung wo/wie der Text verwendet wird def t(key: str, context: str = "api", value: str = "") -> str: """Tag und uebersetze einen UI-sichtbaren Text. Beim Import: registriert den Key mit Kontext und AI-Beschreibung. Zur Laufzeit: liefert die Uebersetzung aus dem Cache. Args: key: Deutscher Basis-Text (identisch mit Frontend-t()) context: Herkunft des Texts (table.*, api.*) value: AI-Kontext-Beschreibung fuer die Uebersetzung """ if key not in _REGISTRY: _REGISTRY[key] = I18nRegistryEntry(context=context, value=value) lang = _CURRENT_LANGUAGE.get() return _CACHE.get(lang, {}).get(key, key) def _setLanguage(lang: str): """Setzt die Sprache fuer den aktuellen Request (Middleware).""" _CURRENT_LANGUAGE.set(lang) async def _syncRegistryToDb(): """Boot-Hook: Schreibt alle gesammelten Keys in UiLanguageSet(xx). Merged mit bestehenden UI-Keys (context=ui), ueberschreibt nur api/table-Keys. """ async def _loadCache(): """Boot-Hook: Laedt alle UiLanguageSets in den In-Memory-Cache.""" # Fuer jede Sprache: {key: value} aus entries[] # _CACHE["fr"] = {"Zugriff verweigert": "Accès refusé", ...} ``` ### `registerModelLabels()` entfaellt -- ersetzt durch `@i18nModel` Decorator **Analyse:** `registerModelLabels()` dupliziert Information die bereits in der Pydantic-Klasse steckt: - **Model-Label** = deutscher Name -> kann aus Class-Docstring oder explizitem `_label` kommen - **Attribut-Labels** = deutsche Feldnamen -> koennen als `json_schema_extra["label"]` am Field definiert werden - **AI-Kontext** = Beschreibung -> kommt aus `Field(description=...)` und Class-Docstring Ein Decorator kann all das automatisch extrahieren. `registerModelLabels()` wird komplett entfernt. **Vorher** (~115 manuelle Aufrufe in 29 Dateien, pro Sprache gepflegt): ```python class Feature(PowerOnModel): """Feature-Definition (global, z.B. 'trustee', 'chatbot').""" code: str = Field(description="Unique feature code") label: TextMultilingual = Field(description="Feature label in multiple languages") icon: str = Field(default="", description="Icon identifier for the feature") registerModelLabels( "Feature", {"en": "Feature", "de": "Feature", "fr": "Fonctionnalité"}, {"code": {"en": "Code", "de": "Code", "fr": "Code"}, "label": {"en": "Label", "de": "Bezeichnung", "fr": "Libellé"}, "icon": {"en": "Icon", "de": "Symbol", "fr": "Icône"}}, ) ``` **Nachher** (Decorator, kein separater Aufruf, keine manuellen Uebersetzungen): ```python @i18nModel("Feature", "Feature-Definition (global, z.B. 'trustee', 'chatbot')") class Feature(PowerOnModel): """Feature-Definition (global, z.B. 'trustee', 'chatbot').""" code: str = Field( description="Unique feature code", json_schema_extra={"label": "Code", ...} ) label: TextMultilingual = Field( description="Feature label in multiple languages", json_schema_extra={"label": "Bezeichnung", ...} ) icon: str = Field( default="", description="Icon identifier for the feature", json_schema_extra={"label": "Symbol", ...} ) ``` **Was der Decorator macht:** ```python def i18nModel(modelLabel: str, aiContext: str = ""): """Class-Decorator: registriert Model- und Feld-Labels fuer i18n. 1. Registriert t(modelLabel, "table.", aiContext or docstring) 2. Fuer jedes Field mit json_schema_extra["label"]: Registriert t(label, "table..", field.description) 3. Speichert alles in MODEL_LABELS fuer getModelLabels()/getModelLabel() """ def _decorator(cls): className = cls.__name__ # Model-Label registrieren ctx = aiContext or (cls.__doc__ or "").strip().split("\n")[0] t(modelLabel, f"table.{className}", ctx) # Feld-Labels aus json_schema_extra["label"] extrahieren for fieldName, fieldInfo in cls.model_fields.items(): extra = (fieldInfo.json_schema_extra or {}) label = extra.get("label") if label: desc = fieldInfo.description or "" t(label, f"table.{className}.{fieldName}", desc) # Abwaertskompatibel: MODEL_LABELS befuellen MODEL_LABELS[className] = { "model": modelLabel, # str statt Dict "attributes": { name: (info.json_schema_extra or {}).get("label", name) for name, info in cls.model_fields.items() } } return cls return _decorator ``` **Vorteile:** - `registerModelLabels()` entfaellt komplett (115 Aufrufe in 29 Dateien geloescht) - Kein Import von `registerModelLabels` mehr noetig - AI-Kontext kommt automatisch aus `Field(description=...)` -- der Entwickler muss nichts extra schreiben - Neue Felder werden automatisch registriert (kein Vergessen moeglich) - `json_schema_extra["label"]` ist die Single Source of Truth fuer den deutschen Feldnamen **Migration:** Fuer jede Klasse: 1. `registerModelLabels(...)` Aufruf loeschen 2. `@i18nModel("Deutscher Modelname")` Decorator hinzufuegen 3. `json_schema_extra={"label": "Deutscher Feldname", ...}` zu jedem Field hinzufuegen (wo noch nicht vorhanden) `getModelLabels()` und `getModelLabel()` bleiben als API erhalten, lesen aber intern aus dem neuen System (MODEL_LABELS + t()-Cache). ### HTTPException-Texte -- Vorher/Nachher **Vorher** (hardcoded, kein Kontext): ```python raise HTTPException(status_code=403, detail="Zugriff verweigert") ``` **Nachher** (mit AI-Kontext): ```python raise HTTPException( status_code=403, detail=t("Zugriff verweigert", "api.routeSecurityLocal", "Fehlermeldung bei fehlendem Zugriff auf eine Ressource") ) ``` Der Kontext `api.` wird automatisch aus dem Dateinamen abgeleitet. Der `value` beschreibt die Situation fuer die AI. ### `TextMultilingual` UI-Renderer -- Vorher/Nachher **Vorher** (4 hardcoded Sprachen): ```tsx // FormGeneratorForm.tsx const LANGS = ['en', 'ge', 'fr', 'it']; // 4 fixe Input-Felder ``` **Nachher** (dynamisch aus availableLanguages): ```tsx const { availableLanguages } = useLanguage(); const langCodes = availableLanguages .filter(l => l.code !== 'xx') .map(l => ({ code: l.code, label: l.label })); // N Input-Felder basierend auf verfuegbaren Sprachen ``` ### Request-Middleware ```python # In app.py oder middleware @app.middleware("http") async def _i18nMiddleware(request, call_next): lang = request.headers.get("Accept-Language", "de")[:2] _setLanguage(lang) return await call_next(request) ``` ## Umsetzungs-Checkliste ### Phase 1: Infrastruktur + Decorator -- Cursor: **Opus 4.6** Architektur-Arbeit: neues Modul, Boot-Integration, Decorator-Logik, `attributeUtils` Umbau. Braucht tiefes Verstaendnis der bestehenden Codebasis (DB-Schema, Boot-Reihenfolge, ContextVar, async). - [x] `i18nRegistry.py` erstellen: `t()`, `@i18nModel`, `_REGISTRY`, `_CACHE`, `_setLanguage`, `_syncRegistryToDb`, `_loadCache` - [x] `app.py`: Boot-Hook nach DB-Init: `_syncRegistryToDb()` + `_loadCache()` - [x] `app.py`: Request-Middleware fuer `_setLanguage()` (aus Accept-Language oder User-Session) - [x] `routeI18n.py`: `_loadCache()` nach sync/update aufrufen (Cache invalidieren) - [x] `attributeUtils.py`: `getModelLabels()`/`getModelLabel()` auf neues MODEL_LABELS-Format (str statt Dict) + t()-Cache umstellen - [x] 2-3 Beispiel-Models migrieren (`datamodelBase.py`, `datamodelFeatures.py`) als Referenz fuer Phase 2 - [x] Gateway starten, pruefen ob Decorator + Boot-Sync funktioniert ### Phase 2: Pydantic-Models migrieren (~27 verbleibende Dateien) -- Cursor: **Auto / Fast** Repetitive Arbeit nach klarem Pattern aus Phase 1: Decorator drauf, `json_schema_extra["label"]` ergaenzen, `registerModelLabels()` loeschen. In Batches von 5-8 Dateien. - [x] Alle verbleibenden Pydantic-Models: `@i18nModel("Label")` Decorator + `json_schema_extra={"label": "..."}` zu Fields - [x] Alle `registerModelLabels()`-Aufrufe loeschen - [x] `from modules.shared.attributeUtils import registerModelLabels` Imports entfernen - [x] Nach jedem Batch: Gateway starten und pruefen ### Phase 3: HTTPException-Texte umstellen (~21 Dateien, ~150 Stellen) -- Cursor: **Auto / Fast** Mechanische Arbeit: `detail="Text"` wird zu `detail=routeApiMsg("Text")` mit `routeApiMsg = apiRouteContext("route")` (Shorthand fuer `t(key, "api.route", "")`). Skript: `platform-core/scripts/wrapRouteHttpDetails.py` (nur einzeilige `detail="..."`; mehrzeilige Imports und f-Strings manuell pruefen). - [x] Alle UI-sichtbaren `HTTPException(detail="...")` in `modules/**/route*.py` auf `routeApiMsg(...)` umgestellt - [x] Interne/technische Fehlermeldungen (`detail=str(e)`, `detail=f"...{e}..."`) unveraendert gelassen ### Phase 4: TextMultilingual UI (Frontend, 2-3 Dateien) -- Cursor: **Opus 4.6** React-Hooks, dynamische Rendering-Logik, `ge`->`de` Mapping. Braucht Verstaendnis des Frontend-i18n-Systems und FormGenerator-Architektur. - [x] `FormGeneratorForm.tsx`: `renderMultilingualField` dynamisch aus `availableLanguages` (Fallback auf `[en, ge]` wenn keine Sprachen geladen) - [x] `FormGeneratorTable.tsx`: `formatTextMultilingual` + `convertToDisplayString` dynamisch (hardcoded `langMap` durch zentrales `_toBackendLang` ersetzt) - [x] `TextMultilingual` Backend-Typ: `get_text()` und `from_dict()` akzeptieren jetzt `de` als Alias fuer `ge`. DB-Schema bleibt `ge` (bestehende Daten). ### Phase 5: Integration + Test -- Cursor: **Opus 4.6** End-to-End Pruefung, Debugging, Log-Analyse, ggf. Fixes. Braucht das staerkste Modell fuer Fehleranalyse. - [x] Gateway-Import aller Route-Module: 36/41 ok (5 Fehler = fehlende optionale Deps: aiohttp, langchain_core, tavily) - [x] `@i18nModel` registriert 90 Models mit 490 table-Keys in `_REGISTRY` + `MODEL_LABELS` - [x] `getModelLabel` / `getModelLabels` / `getModelAttributeDefinitions` lesen korrekt aus `i18nRegistry.MODEL_LABELS` - [x] `routeApiMsg` (api-Keys) registrieren sich lazy beim ersten `t()`-Aufruf zur Laufzeit - [x] `app.py`: Middleware (`_i18nMiddleware`) + Boot-Hooks (`_syncRegistryToDb`, `_loadCache`) vorhanden - [x] Admin-UI: Sprache synchronisieren, Gateway-Keys werden uebersetzt (manuell getestet) - [x] UI mit anderer Sprache testen: Fehlermeldungen, Tabellen-Labels, TextMultilingual-Felder (manuell getestet) ### Phase 6: Wiki / Coding-Conventions aktualisieren -- Cursor: **Auto / Fast** Reine Doku-Arbeit. Inhalte stehen bereits im Plan (siehe "Wiki-Anpassungen fuer Cursor AI Coding" unten). Nur Einpflegen und Formatieren. - [x] `d-guides/coding-conventions.md`: Backend-i18n-Regeln hinzugefuegt (t(), @i18nModel, json_schema_extra["label"], apiRouteContext) - [x] `d-guides/coding-conventions.md`: Regel fuer HTTPException-Texte mit routeApiMsg() - [x] `d-guides/coding-conventions.md`: Regel fuer neue Pydantic-Models (@i18nModel Pflicht) - [x] `b-reference/platform-core/architecture.md`: i18n-Architektur dokumentiert (i18nRegistry, Boot-Sync, Cache, Context-Namensraeume, Entry-Identitaet) - [x] `b-reference/ui-nyla/architecture.md`: TextMultilingual dynamisch + AdminLanguagesKeepAlive dokumentiert - [x] `TOPICS.md`: Thema "i18n / Mehrsprachigkeit" hinzugefuegt (Cross-Cutting + Aktive Arbeiten) ### Abschluss - [x] RBAC / Permissions: nicht betroffen - [x] Neutralisierung: nicht betroffen - [x] Navigation / Routing: nicht betroffen - [x] Billing-Impact: nicht betroffen ## Wiki-Anpassungen fuer Cursor AI Coding Die Coding-Conventions (`d-guides/coding-conventions.md`) werden um folgende Regeln erweitert, damit die Cursor AI beim Coden die Sprach- und Class-Regeln kennt: ### Backend-Ergaenzung: i18n-Pflicht fuer UI-sichtbare Texte ```markdown ### i18n-Pflicht: `t()` fuer alle UI-sichtbaren Texte im Gateway Jeder Text der im Frontend angezeigt wird (HTTPException-Details, API-Response-Messages, Erfolgs-/Fehlermeldungen) **muss** mit `t()` getaggt werden. from modules.shared.i18nRegistry import t # Fehlermeldung (context automatisch = "api") raise HTTPException(status_code=403, detail=t("Zugriff verweigert", "api.routeSecurity", "Fehlermeldung bei fehlendem Zugriff")) # Erfolgsmeldung return {"message": t("Datei erfolgreich hochgeladen", "api.routeFiles", "Bestaetigung nach Datei-Upload")} **Nicht** mit t() taggen: Log-Eintraege, AI-Prompts, interne technische Fehlermeldungen. ``` ### Backend-Ergaenzung: @i18nModel fuer Pydantic-Models ```markdown ### Pydantic-Models: @i18nModel Decorator Pflicht Jedes Pydantic-Model das im UI angezeigt wird (Tabellen, Formulare) **muss** den `@i18nModel` Decorator haben. Feld-Labels werden in `json_schema_extra["label"]` definiert. from modules.shared.i18nRegistry import i18nModel @i18nModel("Benutzer") class User(PowerOnModel): name: str = Field( description="Full name of the user", json_schema_extra={"label": "Name", "frontend_type": "text"} ) email: str = Field( description="Email address for login and notifications", json_schema_extra={"label": "E-Mail-Adresse", "frontend_type": "text"} ) - `@i18nModel("Deutscher Modelname")` -- der AI-Kontext kommt automatisch aus dem Class-Docstring - `json_schema_extra={"label": "Deutscher Feldname"}` -- Pflicht fuer jedes UI-sichtbare Feld - `Field(description=...)` -- wird als AI-Kontext fuer die Uebersetzung verwendet - **Kein** `registerModelLabels()` mehr verwenden (deprecated, wird beim Boot ignoriert) ``` ### Frontend-Ergaenzung: TextMultilingual dynamisch ```markdown ### TextMultilingual: Dynamische Sprachen TextMultilingual-Felder rendern automatisch Eingabefelder fuer alle verfuegbaren Sprachen (aus `availableLanguages`). Keine hardcodierten Sprach-Codes (en/ge/fr/it) mehr verwenden. **Geplanter UX-Mehrwert:** Pro mehrsprachigem Feld ein Button (z. B. „In alle Sprachen uebersetzen“), der den Inhalt der **Quellsprache** (Default: z. B. `en` als Pflichtfeld im Modell, oder die vom Nutzer befuellte Zeile / aktuelle UI-Sprache — festzulegen) per **KI** in alle **anderen** Sprachfelder uebernimmt — dieselbe Logik wie die Admin-Sprachen-AI (siehe *Phase 7b* im Hauptdokument). ``` ## Akzeptanzkriterien | # | Kriterium (Given-When-Then) | Prio | |---|---------------------------|------| | 1 | Given Gateway gestartet, When Admin oeffnet Sprachen-Seite, Then sind alle Gateway-Keys (context=api, table.*) im xx-Basisset sichtbar | must | | 2 | Given Sprache "fr" synchronisiert, When User mit language=fr eine HTTPException ausloest, Then ist die Fehlermeldung auf Franzoesisch | must | | 3 | Given Sprache "fr" synchronisiert, When User eine Tabelle oeffnet, Then sind Spalten-Labels auf Franzoesisch | must | | 4 | Given 3 Sprachen verfuegbar (de, en, fr), When User ein TextMultilingual-Feld editiert, Then sieht er 3 Eingabefelder (nicht 4 hardcoded) | must | | 5 | Given Gateway-Boot, When ~29 @i18nModel Decorators + ~150 t()-Aufrufe, Then Boot-Zeit erhoet sich um max 500ms | should | | 6 | Given neuer Gateway-Text mit t() hinzugefuegt, When Gateway neustartet, Then erscheint der Key automatisch im xx-Set | must | ## Testplan | ID | AC | Art | Automatisiert | Repo-Pfad | Status | |----|----|-----|--------------|-----------|--------| | T1 | 1 | api | nein | manuell: Admin-UI Sprachen-Seite | pending | | T2 | 2 | api | nein | manuell: HTTPException in anderer Sprache | pending | | T3 | 3 | ui | nein | manuell: Tabelle mit fr-Labels | pending | | T4 | 4 | ui | nein | manuell: TextMultilingual-Editor | pending | | T5 | 5 | perf | nein | manuell: Boot-Zeit messen | pending | | T6 | 6 | api | nein | manuell: neuen t()-Key hinzufuegen, Gateway neustarten | pending | ## Mengengeruest | Was | Anzahl | Dateien | |-----|--------|---------| | `registerModelLabels()` Aufrufe | ~115 | 29 | | `HTTPException(detail=...)` UI-sichtbar | ~150 | 21 | | `TextMultilingual` Pydantic-Felder | ~5 | 5 | | `TextMultilingual` Frontend-Renderer | 2 | FormGeneratorForm, FormGeneratorTable | ## Links - Frontend i18n: `ui-nyla/src/providers/language/LanguageContext.tsx` - Frontend Key-Scanner: `ui-nyla/vite.config.ts` (extractI18nKeys Plugin) - Gateway Model-Labels: `platform-core/modules/shared/attributeUtils.py` - Gateway i18n API: `platform-core/modules/routes/routeI18n.py` - UiLanguageSet Model: `platform-core/modules/datamodels/datamodelUiLanguage.py` - TextMultilingual: `platform-core/modules/datamodels/datamodelUtils.py` ## Phase 7 (Vorschlag): RBAC-Labels & Quick Actions — Abgleich mit dem Code ### Verifiziert (Stand Pruefung) | Mechanismus | Code / Pfad | Befund | |-------------|-------------|--------| | **TextMultilingual** | `platform-core/modules/datamodels/datamodelUtils.py` | Pydantic-Modell: **`en` Pflicht**, `de`/`fr`/`it` optional; `get_text(lang)` mit Fallback auf `en`. | | **Rollenbeschreibung in der DB** | `platform-core/modules/datamodels/datamodelRbac.py` — `Role.description: TextMultilingual` | Feld ist **explizit mehrsprachig**; im Schema `frontend_type: "multilingual"`. | | **Admin-Formular** | `FormGeneratorForm.tsx` | Felder mit Typ `multilingual` rendern **pro Sprache Eingaben** aus `availableLanguages` — passt zu **TextMultilingual-Objekten** (nicht zu einem einzelnen i18n-Key). | | **Template-Rollen (Code)** | `mainTrustee.py` etc. — `TEMPLATE_ROLES[].description` | Im Python **Dict** `{en,de,fr}`; beim Sync in die DB als **TextMultilingual**/`Role`-Record. | | **RBAC-Katalog (RAM)** | `rbacCatalog.py` — `registerDataObject` / `registerResourceObject` | Labels sind **lose Dicts** `{en,de,...}` im Speicher — **kein** Pydantic-TextMultilingual-Typ auf dem Katalog-Eintrag. | | **Feature-UI (Navigation)** | `UI_OBJECTS[].label` | Bereits auf **deutschen String** (Basis-Key) umgestellt; Navigation nutzt **`t()`** im Frontend. | | **Quick Actions** | `routeFeatureTrustee.py` — `GET .../quick-actions?language=` | Backend **loest** `label`/`description`-Dicts **serverseitig** mit `language` auf; Response = **fertige Strings**. | | **QuickActionBoard** | `QuickActionBoard.tsx` | Erwartet **`label: string`**, **`description: string`** — **keine** TextMultilingual-Objekte auf dem Draht. | | **TrusteeDashboardView** | `TrusteeDashboardView.tsx` | Laed Quick Actions neu bei **`currentLanguage`**-Wechsel (useEffect-Dependency). | **Kernpunkt:** Es gibt **zwei legitime Muster** im gleichen Produkt: 1. **Persistierte Entitaeten** (z. B. `Role`) mit **TextMultilingual** — Bearbeitung im UI als **Objekt mit Sprachfeldern** (bereits vorhanden). 2. **Statische Code-Listen** (Katalog-Dicts, Quick-Action-Definitionen) — heute **Dicts im Python** oder **serverseitige Aufloesung**; Navigation/UI-Labels der zweiten Schicht sind auf **String-Key + `t()`** migriert. ### Phase 7 — angepasste Empfehlung **Nicht** alles auf „ein deutscher String = einziger Key“ vereinheitlichen, wenn **TextMultilingual** und **FormGenerator** bereits passen: - **`Role.description` (und gleichartige DB-Felder):** Bei **TextMultilingual** bleiben: Übersetzungen koennen weiterhin **pro Sprache in der DB** gepflegt werden **oder** spaeter mit UiLanguageSet synchronisiert werden (separates Konzept: „Spiegelung“ vs. Duplikat vermeiden). - **`DATA_OBJECTS` / `RESOURCE_OBJECTS`:** Entweder (a) **Dicts** beibehalten und zusaetzlich **deutsche Basis-Texte** als i18n-Keys in `_REGISTRY` registrieren (`context=rbac.*`) fuer Admin/AI, **oder** (b) auf **TextMultilingual-Shape** im Katalog vereinheitlichen (Aufwand: API + Admin-Anzeige). - **Quick Actions:** Entweder (a) **serverseitige** Aufloesung beibehalten und **Gateway `t()`** auf die **deutschen Basis-Strings** aus den Dicts anwenden (Cache pro Request-Sprache), **oder** (b) nur noch **deutsche Keys** ausliefern und **`t()` im Frontend** (wie MandateNavigation) — dann `QuickActionBoard` um `t(action.label)` erweitern. **Konkreter naechster technischer Schritt (klein):** `_registerRbacLabels()` in `i18nRegistry.py` — aus allen `DATA_OBJECTS`/`RESOURCE_OBJECTS` die **de**-Texte (und optional Template-**description**-de) als Keys mit `context` `rbac.data` / `rbac.resource` / `rbac.role` ins **xx**-Set syncen, **ohne** das Datenmodell `TextMultilingual` zu ersetzen. **Nicht empfohlen:** `Role.description` auf einen reinen String-Key ohne Migration umstellen — bricht **FormGenerator**-Erwartung und bestehende DB-Daten, solange `TextMultilingual` Pflicht bleibt. ### Phase 7b (UX): TextMultilingual — Button „In alle Sprachen uebersetzen“ **Ziel:** Echten Mehrwert im Formular: Nutzer traegt den Text **einmal** in der gewuenschten **Ausgangs-Sprache** ein und kann per Klick die **uebrigen** Sprachfelder automatisch fuellen (statt fuenfmal manuell zu tippen). **Machbarkeit:** **Ja.** Technisch gut andockbar: | Baustein | Bemerkung | |----------|-----------| | **Gateway** | `routeI18n.py` enthaelt bereits `_translateBatch` (KI, Batches, Kapitalisierung `_matchCapitalization`). Ergaenzung: schlanker Endpoint z. B. `POST /api/i18n/translate-field` mit Body `{ "sourceText": "...", "sourceLang": "de", "targetLangs": ["en","fr"] }` und Response `{ "en": "...", "fr": "..." }` — **Auth + Billing** analog zu bestehenden Uebersetzungs-Jobs. | | **Frontend** | `FormGeneratorForm.tsx` — `renderMultilingualField`: neben dem Feld-Label oder unter den Inputs ein Button; onClick: API aufrufen, dann `handleMultilingualChange` fuer jede Zielsprache setzen (Quellsprache unveraendert lassen). Loading/Disabled waehrend der Anfrage; Fehler via Toast. | | **Quellsprache** | Produktregel festlegen: (A) immer **englisch** (`en`), weil im Modell Pflicht, oder (B) **aktuelle UI-Sprache** (`currentLanguage`), oder (C) die **erste nicht-leere** Zeile unter den Sprach-Inputs. Empfehlung: **(C)** mit Fallback auf `en`, damit es intuitiv bleibt. | | **Kosten / Limits** | Gleiche Policy wie Admin-AI-Uebersetzung (Billing-Callback, Rate-Limits). | **Abgrenzung:** Kein Ersatz fuer professionelles Review; KI kann Fachbegriffe falsch setzen — Button-Tooltip mit Hinweis optional. **Status:** ✅ Umgesetzt (Phase 7 + 7b). - `_registerRbacLabels()` registriert 118 Keys (rbac.data, rbac.resource, rbac.role, rbac.quickaction) im xx-Basisset. - `POST /api/i18n/translate-field` Endpoint fuer On-Demand-Uebersetzung von TextMultilingual-Feldern. - FormGenerator: Button „In alle Sprachen uebersetzen" in multilingualen Feldern. **Konkreter Umsetzungsplan (Arbeitspakete, Sprints, Abnahme):** siehe `c-work/1-plan/2026-04-gateway-i18n-phase-7-implementation.md`. ## Abschluss - [x] b-reference/ aktualisiert (platform-core/architecture.md, ui-nyla/architecture.md) - [x] TOPICS.md aktualisiert (neues Thema: i18n/Mehrsprachigkeit) - [ ] Dieses Dokument -> z-archive/ verschoben (nach finaler Validierung)