wiki/c-work/2-build/2026-04-i18n-static-text-elimination.md
2026-04-10 12:33:19 +02:00

369 lines
16 KiB
Markdown
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.

<!-- status: build -->
<!-- lastReviewed: 2026-04-10 -->
# 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** |
| 35 | Datamodels, `frontendTypes`, Frontend `t()` | offen |
**Umsetzung 12:** 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 17 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:** A1A11 (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:** B1B13 (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:** C1C12 (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:** D1D4 (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:** E1E7 (~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** |