# Statische Texte eliminieren — vollständige i18n-Migration ## Fortschritt | Phase | Inhalt | Status | |-------|--------|--------| | 1 | Feature-Module (`mainXxx.py`), `registry.py`, `interfaceBootstrap.py` | **done** | | 2 | `nodeDefinitions/*`, `portTypes.py`, `nodeRegistry.py`, `entryPoints.py` | **done** | | 3–5 | Datamodels, `frontendTypes`, Frontend `t()` | offen | **Umsetzung 1–2:** Skript `local/scripts/_flatten_i18n_dicts.py` (mehrsprachige Dicts → deutscher String / `json.dumps`). Anschliessend manuell: `mainSystem.py` AICore-`providerLabels`-Fallback; `portTypes.PortField.description` auf `str`; `_deriveFormPayloadSchema` / `_deriveTransformSchema`; `entryPoints._normalize_title` liefert `str`. **`Role.description`** bleibt `TextMultilingual`: `coerce_text_multilingual()` in `datamodelUtils.py` wandelt Template-Strings/Dicts; Bootstrap- und Feature-`Role(...)`-Aufrufe nutzen `coerce_text_multilingual(...)`. ## Problemstellung Trotz Phase 1–7 der i18n-Unification existieren noch **~640+ statische mehrsprachige Dicts** (`{"en":…, "de":…, "fr":…}`) im Gateway und **~60+ Stellen** im Frontend. Diese Dicts: - Müssen bei jeder neuen Sprache manuell erweitert werden - Umgehen das zentrale i18n-System (`t()`, Admin-UI, AI-Übersetzung) - Sind inkonsistent (manche nur en/de, manche nur en/fr, manche en/de/fr) **Ziel:** Jeder UI-sichtbare Text wird als **deutscher Klartext-Key** gespeichert und zur Laufzeit über `t()` übersetzt. Mehrsprachige Dicts verschwinden komplett. --- ## Architektur-Entscheidung ### Prinzip: Deutscher Klartext = i18n-Key ``` # VORHER (statisch, 3 Sprachen hardcoded): "label": {"en": "Documents", "de": "Dokumente", "fr": "Documents"} # NACHHER (dynamisch, beliebig viele Sprachen): "label": "Dokumente" ``` Beim Boot registriert `_registerXxxLabels()` den deutschen Text als Key im `xx`-Basisset. `t("Dokumente")` liefert zur Laufzeit die Übersetzung in der aktuellen Sprache. ### Sonderfälle | Fall | Lösung | |------|--------| | `outputLabels` (Listen von Strings pro Sprache) | Jedes Element einzeln als Key: `"Ja"`, `"Nein"` | | `_ISO_LABELS` (Sprachnamen) | Bleiben statisch — sind ISO-Referenzdaten, keine UI-Texte | | Locale-Code-Maps (`"de": "de-DE"`) | Bleiben statisch — technische Mappings | | `BUILTIN_PLANS` (Subscription) | Werden zu Keys, AI übersetzt | | Datamodel `frontend_options` | Labels werden zu Keys, Boot-Registrierung | --- ## Datei-Index (vollständig) ### Gateway — Feature-Module (Kategorie A: RBAC/Katalog-Labels) | # | Datei | Stellen | Muster | Phase | |---|-------|---------|--------|-------| | A1 | `features/trustee/mainTrustee.py` | ~62 | `DATA_OBJECTS`, `RESOURCE_OBJECTS`, `UI_OBJECTS`, `TEMPLATE_ROLES`, Workflow-Gruppen, Rollen-Beschreibungen | 1 | | A2 | `system/mainSystem.py` | ~27 | `DATA_OBJECTS`, LLM-Provider-Labels | 1 | | A3 | `features/commcoach/mainCommcoach.py` | ~17 | `DATA_OBJECTS`, `RESOURCE_OBJECTS`, `TEMPLATE_ROLES` | 1 | | A4 | `features/teamsbot/mainTeamsbot.py` | ~11 | `DATA_OBJECTS`, `RESOURCE_OBJECTS`, `TEMPLATE_ROLES` | 1 | | A5 | `features/workspace/mainWorkspace.py` | ~10 | `DATA_OBJECTS`, `RESOURCE_OBJECTS`, `TEMPLATE_ROLES` | 1 | | A6 | `features/chatbot/mainChatbot.py` | ~7 | `DATA_OBJECTS`, `TEMPLATE_ROLES` | 1 | | A7 | `features/neutralization/mainNeutralization.py` | ~7 | `DATA_OBJECTS`, `RESOURCE_OBJECTS` | 1 | | A8 | `features/realEstate/mainRealEstate.py` | ~6 | `DATA_OBJECTS`, `RESOURCE_OBJECTS` | 1 | | A9 | `features/graphicalEditor/mainGraphicalEditor.py` | ~6 | Permissions, Rollen-Beschreibungen | 1 | | A10 | `serviceCenter/registry.py` | ~14 | Service-Kategorie-Labels | 1 | | A11 | `interfaces/interfaceBootstrap.py` | ~4 | Template-Rollen-Beschreibungen (admin/user/viewer/sysadmin) | 1 | **Summe Kategorie A: ~171 Stellen, 11 Dateien** ### Gateway — Graph-Editor Node-Definitionen (Kategorie B) | # | Datei | Stellen | Muster | Phase | |---|-------|---------|--------|-------| | B1 | `graphicalEditor/nodeDefinitions/clickup.py` | ~50 | Node-Label, Parameter-Descriptions | 2 | | B2 | `graphicalEditor/nodeDefinitions/input.py` | ~31 | Node-Label, Parameter-Descriptions | 2 | | B3 | `graphicalEditor/portTypes.py` | ~33 | Port-Feld-Descriptions | 2 | | B4 | `graphicalEditor/nodeDefinitions/sharepoint.py` | ~27 | Node-Label, Parameter-Descriptions | 2 | | B5 | `graphicalEditor/nodeDefinitions/email.py` | ~27 | Node-Label, Parameter-Descriptions | 2 | | B6 | `graphicalEditor/nodeDefinitions/ai.py` | ~23 | Node-Label, Parameter-Descriptions | 2 | | B7 | `graphicalEditor/nodeDefinitions/trustee.py` | ~20 | Node-Label, Parameter-Descriptions | 2 | | B8 | `graphicalEditor/nodeDefinitions/flow.py` | ~14 | Node-Label, `outputLabels` (Sonderfall: Liste) | 2 | | B9 | `graphicalEditor/nodeDefinitions/data.py` | ~9 | Node-Label, Parameter-Descriptions | 2 | | B10 | `graphicalEditor/nodeDefinitions/triggers.py` | ~8 | Node-Label, Parameter-Descriptions | 2 | | B11 | `graphicalEditor/nodeDefinitions/file.py` | ~7 | Node-Label, Parameter-Descriptions | 2 | | B12 | `graphicalEditor/nodeRegistry.py` | ~10 | Kategorie-Labels | 2 | | B13 | `graphicalEditor/entryPoints.py` | ~3 | Entry-Point-Titel | 2 | **Summe Kategorie B: ~262 Stellen, 13 Dateien** ### Gateway — Datamodel-Options (Kategorie C) | # | Datei | Stellen | Muster | Phase | |---|-------|---------|--------|-------| | C1 | `datamodels/datamodelRbac.py` | ~19 | Scope/Access-Level Option-Labels (en/de/fr) | 3 | | C2 | `datamodels/datamodelChat.py` | ~7 | Status/Mode Option-Labels (en/fr, kein de!) | 3 | | C3 | `datamodels/datamodelMessaging.py` | ~11 | Channel/Status Option-Labels (en/fr) | 3 | | C4 | `datamodels/datamodelNotification.py` | ~8 | Type/Status Option-Labels (en/de) | 3 | | C5 | `datamodels/datamodelUam.py` | ~7 | Subscription-Status + Sprach-Options (en/de/fr) | 3 | | C6 | `datamodels/datamodelSubscription.py` | ~8 | Plan-Titel + Descriptions (en/de/fr, teils ohne fr) | 3 | | C7 | `datamodels/datamodelFiles.py` | ~4 | Scope Option-Labels (en/de) | 3 | | C8 | `datamodels/datamodelDataSource.py` | ~4 | Scope Option-Labels (en/de) | 3 | | C9 | `datamodels/datamodelFeatureDataSource.py` | ~4 | Scope Option-Labels (en/de) | 3 | | C10 | `datamodels/datamodelUiLanguage.py` | ~3 | Sync-Status Option-Labels (de/en) | 3 | | C11 | `features/trustee/datamodelFeatureTrustee.py` | ~13 | Währung + Dokumenttyp Option-Labels | 3 | | C12 | `features/neutralization/datamodelFeatureNeutralizer.py` | ~4 | Scope Option-Labels | 3 | **Summe Kategorie C: ~92 Stellen, 12 Dateien** ### Gateway — Sonstige (Kategorie D) | # | Datei | Stellen | Muster | Phase | |---|-------|---------|--------|-------| | D1 | `shared/frontendTypes.py` | ~14 | `CUSTOM_TYPE_DESCRIPTIONS` (aktuell ungenutzt) | 4 | | D2 | `features/trustee/accounting/connectors/accountingConnectorBexio.py` | ~4 | Connector-Label + Feld-Labels | 4 | | D3 | `features/trustee/accounting/connectors/accountingConnectorRma.py` | ~4 | Connector-Label + Feld-Labels | 4 | | D4 | `features/trustee/accounting/connectors/accountingConnectorAbacus.py` | ~5 | Connector-Label + Feld-Labels | 4 | **Summe Kategorie D: ~27 Stellen, 4 Dateien** ### Frontend (Kategorie E) | # | Datei | Stellen | Muster | Phase | |---|-------|---------|--------|-------| | E1 | `pages/Store.tsx` | ~5 | `FEATURE_DESCRIPTIONS` Dict (de/en/fr) | 5 | | E2 | `types/mandate.ts` | ~45 | Statische Navigation-Labels (de/en) | 5 | | E3 | `pages/views/trustee/TrusteeAbschlussView.tsx` | ~6 | Tile-Titel/Descriptions (de/en/fr) | 5 | | E4 | `pages/views/trustee/TrusteeAnalyseView.tsx` | ~4 | Tile-Descriptions (de/en) | 5 | | E5 | `components/UnifiedDataBar/UnifiedDataBar.tsx` | ~3 | `_TAB_LABELS` (de/en/fr) | 5 | | E6 | `api/featuresApi.ts` | ~4 | Mock-Labels (de/en) | 5 | | E7 | Diverse (12 Dateien) | ~12 | Hardcoded deutsche Strings ohne `t()` | 5 | **Summe Kategorie E: ~79 Stellen, ~18 Dateien** ### Ausnahmen (bleiben statisch) | Datei | Grund | |-------|-------| | `routes/routeI18n.py` (`_ISO_LABELS`) | ISO-Referenzdaten (Sprachnamen), kein UI-Text | | `serviceAgent/coreTools/_mediaTools.py` | Locale-Code-Map (`"de"→"de-DE"`), technisch | | `serviceAgent/conversationManager.py` | Sprach-Name-Map für Agent-Kontext, technisch | --- ## Gesamtübersicht | Kategorie | Dateien | Stellen | Komplexität | |-----------|---------|---------|-------------| | A: Feature-Module | 11 | ~171 | Mittel — uniformes Muster, braucht Boot-Registrierung | | B: Node-Definitionen | 13 | ~262 | Mittel — uniformes Muster, braucht eigene Registrierung | | C: Datamodel-Options | 12 | ~92 | Hoch — verschiedene Patterns, braucht generische Lösung | | D: Sonstige Gateway | 4 | ~27 | Niedrig — einfache Ersetzung | | E: Frontend | ~18 | ~79 | Niedrig — `t()` wrappen | | **Total** | **~58** | **~631** | | --- ## Umsetzungsplan (5 Phasen) ### Phase 1: Feature-Module (Kategorie A) — Composer-geeignet **Aufwand:** Mittel | **Modell:** Composer (schnelles Modell) | **Risiko:** Niedrig **Vorbereitung (einmalig, Opus):** - `_registerRbacLabels()` in `i18nRegistry.py` erweitern: neben `DATA_OBJECTS`, `RESOURCE_OBJECTS`, `TEMPLATE_ROLES` auch scannen: - `UI_OBJECTS[].label` - Workflow-Gruppen-Labels (`TRUSTEE_WORKFLOW_GROUPS`, `TRUSTEE_SERVICE_CATEGORIES`) - Rollen-`description` Blöcke - Service-Kategorie-Labels (`serviceCenter/registry.py`) - Bootstrap-Rollen (`interfaceBootstrap.py`) **Transformation (pro Datei, Composer):** Jedes `"label": {"en": "X", "de": "Y", "fr": "Z"}` wird zu `"label": "Y"` (deutscher Text). Jedes `"description": {"en": "X", "de": "Y", "fr": "Z"}` wird zu `"description": "Y"`. **Regel für Composer:** ``` In der Datei [DATEI]: - Ersetze jedes Dict {"en": "...", "de": "DEUTSCH", "fr": "..."} durch den deutschen Wert "DEUTSCH" - Ersetze jedes Dict {"de": "DEUTSCH", "en": "...", "fr": "..."} durch "DEUTSCH" - Wenn kein "de" vorhanden: nimm "en" Wert - Lasse alle anderen Felder unverändert ``` **Dateien:** A1–A11 (11 Dateien) **Akzeptanzkriterien:** - [ ] Kein `"fr":` mehr in den 11 Dateien (ausser Ausnahmen) - [ ] `_registerRbacLabels()` registriert alle neuen Key-Kategorien - [ ] Gateway startet fehlerfrei - [ ] Admin-UI Sprachen-Seite zeigt neue Keys im xx-Set --- ### Phase 2: Node-Definitionen (Kategorie B) — Composer-geeignet **Aufwand:** Mittel | **Modell:** Composer (schnelles Modell) | **Risiko:** Niedrig **Vorbereitung (einmalig, Opus):** - Neue Funktion `_registerNodeLabels()` in `i18nRegistry.py`: - Scannt `STATIC_NODE_TYPES` aus `nodeDefinitions/__init__.py` - Registriert `label`, `description`, `parameters[].description`, `outputLabels[]` als Keys - Context: `node.label`, `node.desc`, `node.param`, `node.output` - Scannt `portTypes.PORT_TYPE_CATALOG` für Port-Feld-Descriptions - Context: `port.desc` - Scannt `nodeRegistry` Kategorie-Labels - Context: `node.category` - Scannt `entryPoints` Titel - Context: `node.entry` **Transformation (pro Datei, Composer):** Identisches Muster wie Phase 1: Dict → deutscher String. **Sonderfall `flow.py` `outputLabels`:** ```python # VORHER: "outputLabels": {"en": ["Yes", "No"], "de": ["Ja", "Nein"], "fr": ["Oui", "Non"]} # NACHHER: "outputLabels": ["Ja", "Nein"] ``` **Dateien:** B1–B13 (13 Dateien) **Akzeptanzkriterien:** - [ ] Kein `"fr":` mehr in den 13 Dateien - [ ] `_registerNodeLabels()` registriert alle Node-Keys - [ ] Graph-Editor zeigt Nodes korrekt an (Labels übersetzt) - [ ] Node-Parameter-Descriptions im Config-Panel korrekt --- ### Phase 3: Datamodel-Options (Kategorie C) — Opus nötig **Aufwand:** Hoch | **Modell:** Opus | **Risiko:** Mittel **Warum Opus:** Die Patterns sind uneinheitlich (en/fr, en/de, en/de/fr), die `frontend_options` Struktur variiert, und die Boot-Registrierung muss generisch über alle Datamodels funktionieren. **Vorbereitung:** - Neue generische Funktion `_registerDatamodelOptionLabels()` in `i18nRegistry.py`: - Scannt alle Pydantic-Modelle mit `json_schema_extra` → `frontend_options` - Extrahiert `label` Dicts und registriert den deutschen (oder englischen) Text als Key - Context: `option.{ModelName}.{fieldName}` - `BUILTIN_PLANS` in `datamodelSubscription.py`: `title`/`description` zu Keys **Transformation (pro Datei):** ```python # VORHER: {"value": "active", "label": {"en": "Active", "de": "Aktiv", "fr": "Actif"}} # NACHHER: {"value": "active", "label": "Aktiv"} ``` **Problem: Fehlende Sprachen.** Einige Dicts haben kein `"de"` (z.B. `datamodelChat.py` nur en/fr). Hier wird `"en"` als Fallback genommen und der englische Text als Key registriert — die AI-Übersetzung erzeugt dann `de` und `fr`. **Dateien:** C1–C12 (12 Dateien) **Akzeptanzkriterien:** - [ ] Kein `"fr":` / `"en":` Dict-Pattern mehr in den 12 Dateien - [ ] `_registerDatamodelOptionLabels()` registriert alle Option-Keys - [ ] Frontend Select-Felder zeigen korrekte Labels - [ ] Subscription-Plan-Texte korrekt übersetzt --- ### Phase 4: Sonstige Gateway (Kategorie D) — Composer-geeignet **Aufwand:** Niedrig | **Modell:** Composer (schnelles Modell) | **Risiko:** Niedrig **Transformation:** - `frontendTypes.py`: `CUSTOM_TYPE_DESCRIPTIONS` komplett entfernen (ungenutzt) + `getCustomTypeDescription()` und `registerCustomType()` vereinfachen oder entfernen - Accounting-Connectors: `displayName()` und Feld-Labels zu deutschen Strings **Dateien:** D1–D4 (4 Dateien) **Akzeptanzkriterien:** - [ ] `CUSTOM_TYPE_DESCRIPTIONS` entfernt - [ ] Accounting-Connector-Labels als deutsche Strings - [ ] Kein `"fr":` mehr in den 4 Dateien --- ### Phase 5: Frontend (Kategorie E) — Composer-geeignet **Aufwand:** Niedrig | **Modell:** Composer (schnelles Modell) | **Risiko:** Niedrig **Transformation:** - Alle `{"en": "X", "de": "Y", "fr": "Z"}` Dicts durch `t("Y")` ersetzen - Alle hardcodierten deutschen Strings in JSX-Attributen mit `t()` wrappen - `mandate.ts` Navigation-Labels: zu `t()`-Keys (Backend liefert bereits i18n-Keys) - `_TAB_LABELS` in `UnifiedDataBar.tsx`: zu `t()`-Aufrufen - `FEATURE_DESCRIPTIONS` in `Store.tsx`: zu `t()`-Keys **Dateien:** E1–E7 (~18 Dateien) **Akzeptanzkriterien:** - [ ] Kein statisches `de:`/`en:`/`fr:` Dict-Pattern im Frontend - [ ] Alle UI-Texte via `t()` getaggt - [ ] Sprache wechseln → alle Texte ändern sich --- ## Composer-Anweisungen (Copy-Paste-fertig) ### Für Phase 1 + 2 + 4 (uniforme Dict→String Ersetzung): ``` Aufgabe: Ersetze in [DATEI] alle statischen mehrsprachigen Dicts durch den deutschen Klartext-String. Regeln: 1. {"en": "...", "de": "DEUTSCH", "fr": "..."} → "DEUTSCH" 2. {"de": "DEUTSCH", "en": "...", "fr": "..."} → "DEUTSCH" 3. {"en": "ENGLISH", "fr": "..."} (kein "de") → "ENGLISH" 4. {"en": "ENGLISH", "de": "DEUTSCH"} (kein "fr") → "DEUTSCH" 5. Listen-Sonderfall: {"en": [...], "de": [LISTE], "fr": [...]} → [LISTE] 6. Nur "label", "description", "title", "displayName" Felder ändern 7. Keine Imports, Funktionssignaturen oder Logik ändern 8. Keine Kommentare hinzufügen ``` ### Für Phase 5 (Frontend t()-Wrapping): ``` Aufgabe: Ersetze in [DATEI] alle statischen Texte durch t()-Aufrufe. Regeln: 1. {"en": "...", "de": "DEUTSCH", "fr": "..."} → t("DEUTSCH") 2. Hardcoded string "DEUTSCH" in label/title/placeholder/aria-label → t("DEUTSCH") 3. Import { useLanguage } from '...LanguageContext' hinzufügen falls fehlend 4. const { t } = useLanguage(); in der Komponente falls fehlend 5. Keine Logik ändern, nur Texte wrappen ``` --- ## Reihenfolge und Abhängigkeiten ``` Phase 1 ──→ Phase 2 ──→ Phase 3 ──→ Phase 4 (A) (B) (C) (D) │ ▼ Phase 5 (E) ``` - Phase 1 muss zuerst: erweitert `_registerRbacLabels()` als Grundlage - Phase 2 nach 1: braucht das Pattern von Phase 1 - Phase 3 nach 2: komplexeste Phase, braucht neue generische Registrierung - Phase 4 + 5: unabhängig voneinander, nach Phase 1 --- ## Aufwandsschätzung | Phase | Modell | Dateien | Geschätzte Zeit | |-------|--------|---------|-----------------| | 1 (Vorbereitung) | Opus | 1 | 15 min | | 1 (Transformation) | Composer | 11 | 30 min | | 2 (Vorbereitung) | Opus | 1 | 15 min | | 2 (Transformation) | Composer | 13 | 30 min | | 3 | Opus | 13 | 45 min | | 4 | Composer | 4 | 10 min | | 5 | Composer | 18 | 30 min | | **Total** | | **~58** | **~3 Stunden** |