fixed sysuser and removed redundant fallbacks

This commit is contained in:
ValueOn AG 2026-04-11 22:23:49 +02:00
parent cb18a584a2
commit d16e1029a7
3 changed files with 78 additions and 32 deletions

View file

@ -49,14 +49,14 @@ Ergänzend typische Root-Dateien und Bereiche im Repo: `main.tsx`, `App.tsx`, `a
- **Geschützte Bereiche:** Route-Guards (z. B. `ProtectedRoute`) prüfen Authentifizierung; Redirect bei fehlender Session.
- **Haupt-App:** Nach Login **`MainLayout.tsx`** mit **`MandateNavigation`** (Sidebar) und `<Outlet />` (React Router).
- **Seiten-Mapping:** `pageRegistry.tsx` definiert `PAGE_REGISTRY` und `FEATURE_REGISTRY`; Seiten werden lazy geladen. `core/PageManager/` enthält ergänzende Infrastruktur (State Preservation, Lifecycle-Hooks).
- **i18n:** DB-backed via `LanguageContext` (`t()`-Hook). Sprachsets werden dynamisch via public API geladen (`GET /api/i18n/sets/{code}`). Key-Konvention: **Deutscher Klartext = Key**. Jeder neue/geänderte UI-Text MUSS mit `t('Deutscher Klartext')` getaggt werden. Variable Interpolation: `t('Text {var}', {var: 'Wert'})`. Fallback: Ziel-Set → `de`-Set → Key selbst. Keine statischen Locale-Files. Das xx-Basisset enthaelt sowohl UI-Keys (context="ui") als auch Gateway-Keys (context="api.*", "table.*") — beide werden per AI uebersetzt.
- **TextMultilingual:** Felder vom Typ `TextMultilingual` rendern dynamisch Eingabefelder fuer alle verfuegbaren Sprachen (aus `availableLanguages`). Keine hardcodierten Sprach-Codes. Sprachcodes folgen ISO 639-1 (en, de, fr, it, …).
- **i18n:** DB-backed via `LanguageContext` (`t()`-Hook). Sprachsets werden dynamisch via public API geladen (`GET /api/i18n/sets/{code}`). Key-Konvention: **Deutscher Klartext = Key**. Jeder neue/geaenderte UI-Text MUSS mit `t('Deutscher Klartext')` getaggt werden. **`t()` darf NUR String-Literale enthalten** — `t(variable)` ist verboten. Werte vom Backend (Labels, Descriptions) sind bereits uebersetzt und werden direkt gerendert. Variable Interpolation: `t('Text {var}', {var: 'Wert'})`. Fallback: Ziel-Set → `de`-Set → `[key]` (eckige Klammern machen fehlende Uebersetzungen sichtbar). Keine statischen Locale-Files. Fuer statische Frontend-Maps (z.B. Wochentage, Monatsnamen, Status-Labels) wird `t()` per `switch`/Funktion mit Literalen aufgerufen, nicht ueber Map-Lookup.
- **TextMultilingual:** Felder vom Typ `TextMultilingual` rendern dynamisch Eingabefelder fuer alle verfuegbaren Sprachen (aus `availableLanguages`). `xx` ist das Pflichtfeld (Quelltext). Keine hardcodierten Sprach-Codes. Sprachcodes folgen ISO 639-1 (en, de, fr, it, …).
- **Theme:** global über Context; API-Requests mit Auth-Header (Interceptor in zentralem API-Client).
## UI-Regeln
- **Keine Browser-Dialoge** (`alert` / `confirm` / `prompt`) — stattdessen `useConfirm()` und `usePrompt()` Hooks
- **i18n-Pflicht:** Jeder UI-Text (Label, Button, Placeholder, Tooltip, Fehlermeldung) MUSS mit `t('Deutscher Klartext')` getaggt werden. Hardcodierte deutsche Strings im JSX sind nicht erlaubt. Import: `const { t } = useLanguage();`
- **i18n-Pflicht:** Jeder UI-Text (Label, Button, Placeholder, Tooltip, Fehlermeldung) MUSS mit `t('Deutscher Klartext')` getaggt werden. Hardcodierte deutsche Strings im JSX sind nicht erlaubt. Import: `const { t } = useLanguage();`. **`t()` darf NUR String-Literale enthalten** — nie Variablen. Backend-Werte (feature.label, role.description etc.) sind bereits uebersetzt und werden direkt gerendert.
- Alle internen Funktionen mit `_` Prefix
- camelCase für Variablen und Funktionen

View file

@ -1,5 +1,5 @@
<!-- status: canonical -->
<!-- lastReviewed: 2026-04-10 -->
<!-- lastReviewed: 2026-04-11 -->
<!-- verifiedAgainst: gateway (codebase audit 2026-04-07, post Automation Unification) -->
# Gateway -- Architektur
@ -102,16 +102,37 @@ Weitere Interface-Dateien im Ordner (z. B. Voice, Tickets, Messaging, Bootstrap)
## i18n (Mehrsprachigkeit)
Das Gateway nutzt dasselbe DB-basierte Sprachsystem wie das Frontend (`UiLanguageSet`). Alle UI-sichtbaren Texte (HTTPException-Details, Model-Labels, API-Messages) werden ueber `t()` getaggt und per AI uebersetzt.
Das Gateway nutzt ein DB-basiertes Sprachsystem (`UiLanguageSet`). Alle UI-sichtbaren Texte (HTTPException-Details, Model-Labels, API-Messages) werden ueber `t()` getaggt und per AI uebersetzt.
| Komponente | Datei | Zweck |
|------------|-------|-------|
| `i18nRegistry.py` | `modules/shared/` | `t()`, `@i18nModel` Decorator, `_REGISTRY`, `_CACHE`, `_setLanguage`, Boot-Sync |
| `attributeUtils.py` | `modules/shared/` | `getModelLabels()` / `getModelLabel()` lesen aus `i18nRegistry.MODEL_LABELS` |
| `routeI18n.py` | `modules/routes/` | Admin-API: Sprachset-CRUD, AI-Uebersetzung, xx-Sync |
| `i18nRegistry.py` | `modules/shared/` | `t()`, `resolveText()`, `@i18nModel` Decorator, `_REGISTRY`, `_CACHE`, `_setLanguage`, Boot-Sync |
| `attributeUtils.py` | `modules/shared/` | `getModelLabels()` / `getModelLabel()` lesen aus `i18nRegistry.MODEL_LABELS`, nutzen `resolveText()` |
| `routeI18n.py` | `modules/routes/` | Admin-API: Sprachset-CRUD, AI-Uebersetzung, xx-Sync, TextMultilingual-Batch-Uebersetzung |
| `app.py` | `gateway/` | Boot-Hooks (`_syncRegistryToDb`, `_loadCache`), Request-Middleware (`_setLanguage`) |
**Boot-Reihenfolge:**
### Architektur-Regeln
1. **`t()` NUR mit String-Literalen:** `t("Speichern")` ist korrekt, `t(variable)` ist verboten. Jeder i18n-Key muss als Literal im Code stehen, damit er beim Import registriert wird.
2. **Backend uebersetzt, Frontend rendert:** Das Backend liefert uebersetzte Strings via API. Das Frontend ruft `t()` nur fuer eigene statische UI-Texte auf, nie fuer Werte die vom Backend kommen.
3. **Fallback `[key]`:** Wenn eine Uebersetzung fehlt, gibt `t()` `[key]` zurueck (z.B. `[Speichern]`), damit fehlende Uebersetzungen im UI sofort sichtbar sind.
4. **Keine lokalen Resolve-Helfer:** Alle Label-Aufloesung laeuft ueber `resolveText()` (zentral in `i18nRegistry.py`). Keine `_resolveLabel()`, `_resolveTextMultilingual()`, `_storeLabelText()` etc. in Route-Dateien.
### Zentrale Funktionen
**`t(key: str) -> str`** — Tag + Translate:
- Bei Import: registriert Key in `_REGISTRY`
- Bei Runtime: liefert `_CACHE[lang][key]`, Fallback `[key]`
- Sprache kommt aus `_CURRENT_LANGUAGE` ContextVar (gesetzt durch Middleware)
**`resolveText(value: Any, lang: Optional[str] = None) -> str`** — Universelle Label-Aufloesung:
- `str``t(value)` (behandelt als i18n-Key)
- `dict` → Nutzersprache aus Kontext (oder `lang` wenn gesetzt), dann `xx`, dann erster nicht-leerer Wert (keine eckigen Klammern — das ist Inhalt, kein fehlender UI-Key)
- `TextMultilingual``model_dump()` dann dict-Logik
- `None``""`
### Boot-Reihenfolge
1. Module laden → `@i18nModel` Decorators + `t()`-Aufrufe registrieren Keys in `_REGISTRY`
2. `_syncRegistryToDb()`:
- Scannt Route-Dateien nach `routeApiMsg("…")` (api.* Keys)
@ -121,13 +142,19 @@ Das Gateway nutzt dasselbe DB-basierte Sprachsystem wie das Frontend (`UiLanguag
- Merged Gateway-Keys ins xx-Basisset (UI-Keys bleiben erhalten)
3. `_loadCache()`: Laedt alle `UiLanguageSets` in den In-Memory-Cache (`_CACHE`)
**Request-Flow:** Middleware setzt `_setLanguage(lang)` aus `Accept-Language` Header → `t("Key")` liefert Uebersetzung aus `_CACHE[lang]` (O(1) Dict-Lookup, kein DB-Call)
### Request-Flow
**Context-Namensraeume:** `ui` (Frontend), `api.<routeModuleName>` (HTTPExceptions), `table.<ClassName>` (Model-Labels), `table.<ClassName>.<fieldName>` (Feld-Labels), `nav` (Navigation/Feature-Labels), `rbac.data` / `rbac.resource` / `rbac.role` / `rbac.quickaction` (RBAC-Katalog)
Middleware setzt `_setLanguage(lang)` aus `Accept-Language` Header → `t("Key")` liefert Uebersetzung aus `_CACHE[lang]` (O(1) Dict-Lookup, kein DB-Call). `resolveText()` nutzt denselben Mechanismus.
**Entries-Identitaet:** Ein Entry wird durch `(key, context)` eindeutig identifiziert — derselbe Text kann mit verschiedenen Contexts existieren (z.B. einmal als UI-Key, einmal als API-Key).
### TextMultilingual
**TextMultilingual-Uebersetzung:** `POST /api/i18n/translate-field` — On-Demand-KI-Uebersetzung fuer einzelne Texte in mehrere Zielsprachen (genutzt vom FormGenerator-Button „In alle Sprachen uebersetzen").
Felder vom Typ `TextMultilingual` speichern Benutzertexte mehrsprachig. `xx` ist das Pflichtfeld (Quelltext/Deutsch), weitere Sprachen werden dynamisch als Extra-Felder gespeichert. Bei neuer Sprache werden alle TextMultilingual-Felder per Batch-Job automatisch uebersetzt (`routeI18n._translateTextMultilingualFields`). On-Demand: `POST /api/i18n/translate-field`.
### Context-Namensraeume
`ui` (Frontend), `api.<routeModuleName>` (HTTPExceptions), `table.<ClassName>` (Model-Labels), `table.<ClassName>.<fieldName>` (Feld-Labels), `nav` (Navigation/Feature-Labels), `rbac.data` / `rbac.resource` / `rbac.role` / `rbac.quickaction` (RBAC-Katalog)
**Entries-Identitaet:** Ein Entry wird durch `(key, context)` eindeutig identifiziert — derselbe Text kann mit verschiedenen Contexts existieren.
## Regeln / Invarianten

View file

@ -1,5 +1,5 @@
<!-- status: canonical -->
<!-- lastReviewed: 2026-04-05 -->
<!-- lastReviewed: 2026-04-11 -->
# Coding-Konventionen
@ -37,6 +37,24 @@ t('Offen (Status)') // vs. t('Offen (Zustand)')
```
- **Key = deutscher Klartext** (kein Dot-Notation-Schema)
- **`t()` NUR mit String-Literalen** — `t(variable)` ist verboten. Backend-Werte (feature.label, role.description etc.) sind bereits uebersetzt und werden direkt gerendert.
- Fuer statische Frontend-Maps (Wochentage, Monatsnamen, Status-Labels) wird `t()` per `switch`-Funktion mit Literalen aufgerufen, nicht ueber Map-Lookup:
```tsx
// FALSCH - t() mit Variable
const labels: Record<string, string> = { budget: 'Budget-Vergleich', kpi: 'KPI-Dashboard' };
{t(labels[tabId])}
// RICHTIG - t() mit String-Literal per switch
function _tabLabel(tabId: string, t: (k: string) => string): string {
switch (tabId) {
case 'budget': return t('Budget-Vergleich');
case 'kpi': return t('KPI-Dashboard');
default: return tabId;
}
}
```
- Kein Plural-Framework -- separate Keys verwenden: `t('1 Eintrag')` vs. `t('{count} Eintraege', { count })`
- Fehlende Keys erscheinen als `[Text]` (eckige Klammern = unuebersetzt)
- Admin synchronisiert Sprachsets ueber Administration → System → UI-Sprachen → "Alle aktualisieren"
@ -50,7 +68,9 @@ t('Offen (Status)') // vs. t('Offen (Zustand)')
### i18n-Pflicht: `t()` fuer alle UI-sichtbaren Gateway-Texte
Jeder Text der im Frontend angezeigt wird (HTTPException-Details, API-Response-Messages, Erfolgs-/Fehlermeldungen) **muss** mit `t()` getaggt werden.
Jeder Text der im Frontend angezeigt wird (HTTPException-Details, API-Response-Messages, Erfolgs-/Fehlermeldungen) **muss** mit `t()` getaggt werden (nur **String-Literale** in `t()`).
**Dynamische Werte** (Feld aus DB, Katalog, verschachteltes Dict): `resolveText(wert)` aus `modules.shared.i18nRegistry` verwenden — nicht `t(variable)`. `resolveText` akzeptiert optional `lang="fr"` fuer Kontexte ohne Request-Middleware (z.B. Scheduler mit expliziter Nutzersprache).
```python
from modules.shared.i18nRegistry import t
@ -73,8 +93,7 @@ Wenn ein Modul statische Dicts/Listen mit UI-sichtbaren Texten definiert (z.B. `
```python
from modules.shared.i18nRegistry import t
# Keys auf Modul-Ebene registrieren (Import-Zeit)
_KEYS = [t("Erste Session"), t("3-Tage-Serie"), t("Wochenserie")]
# Alle Keys auf Modul-Ebene mit t("...") registrieren (Import-Zeit), siehe z.B. CommCoach BADGE_DEFINITIONS
# Dict mit deutschen Quelltexten als Strings (NICHT t() im Dict!)
BADGE_DEFINITIONS = {
@ -82,29 +101,29 @@ BADGE_DEFINITIONS = {
"streak_3": {"label": "3-Tage-Serie", "icon": "fire"},
}
# Bei Runtime nochmals t() aufrufen fuer aktuelle Sprache
# Runtime: resolveText (nicht t(variable))
from modules.shared.i18nRegistry import resolveText
def getBadgeDefinitions():
return {k: {**v, "label": t(v["label"])} for k, v in BADGE_DEFINITIONS.items()}
return {k: {**v, "label": resolveText(v["label"])} for k, v in BADGE_DEFINITIONS.items()}
```
**Warum?** `t()` registriert Keys nur beim ersten Aufruf. Wenn `t()` nur in einer Funktion steht, wird der Key erst beim ersten Request registriert -- **nach** dem Boot-Sync. Der Key fehlt dann im `xx`-Set und kann nicht uebersetzt werden.
### TextMultilingual-Felder: Backend loest auf
### TextMultilingual-Felder: Backend loest auf via resolveText()
`TextMultilingual`-Felder (User-Content in mehreren Sprachen, z.B. `Role.description`) werden im Backend via `_resolveTextMultilingual()` aufgeloest bevor sie ans Frontend geliefert werden. Das Frontend erhaelt immer einen **String**, nie ein Dict.
`TextMultilingual`-Felder (User-Content in mehreren Sprachen, z.B. `Role.description`) werden im Backend via `resolveText()` aufgeloest bevor sie ans Frontend geliefert werden. Das Frontend erhaelt immer einen **String**, nie ein Dict.
```python
from modules.shared.i18nRegistry import _getLanguage
from modules.shared.i18nRegistry import resolveText
def _resolveTextMultilingual(value) -> str:
if isinstance(value, str):
return value
if isinstance(value, dict):
lang = _getLanguage()
return value.get(lang) or value.get("de") or value.get("en") or ""
return str(value) if value else ""
# In Route-Handlern:
"description": resolveText(role.description),
"label": resolveText(featureDef.get("label")) or featureCode,
```
**Keine lokalen Resolve-Helfer:** `_resolveTextMultilingual()`, `_resolveLabel()`, `_storeLabelText()`, `_featureLabelPlain()`, `_pickInvocationTitleLabel()` etc. sind verboten. Immer `resolveText()` aus `i18nRegistry` verwenden.
### Frontend: Keine Dict-Fallbacks fuer Labels
Das Frontend darf **keine** `label?.de || label?.en || Object.values(...)` Ketten verwenden. Wenn das Backend korrekt arbeitet, sind alle Labels Strings. Defensive `typeof === 'object'` Checks verdecken Backend-Fehler und sind verboten.
@ -152,19 +171,19 @@ class User(PowerOnModel):
### Feature-Module: Labels als deutsche Basis-Strings
Neue `DATA_OBJECTS`, `RESOURCE_OBJECTS` und `UI_OBJECTS` Labels verwenden **deutsche Klartext-Strings** als Basis-Key. Bestehende `{en, de, fr}`-Dicts werden beim Boot automatisch ueber `_registerRbacLabels()` registriert (Kontext `rbac.data`, `rbac.resource`, `rbac.role`, `rbac.quickaction`). Fuer **neue** Eintraege: `de`-Text als Basis-Key verwenden, `en`/`fr` im Dict fuer Legacy-Anzeige bis vollstaendige Migration.
`DATA_OBJECTS`, `RESOURCE_OBJECTS` und `UI_OBJECTS` Labels verwenden **deutsche Klartext-Strings** als Basis-Key. Labels werden beim Boot automatisch ueber `_registerRbacLabels()` registriert und erscheinen im xx-Basisset. Bei Runtime werden sie via `resolveText()` aufgeloest.
```python
DATA_OBJECTS = [
{
"objectKey": "data.uam.UserInDB",
"label": {"en": "User", "de": "Benutzer", "fr": "Utilisateur"},
"label": "Benutzer",
"meta": {"table": "UserInDB", "namespace": "uam"}
},
]
```
Die `de`-Texte erscheinen automatisch im xx-Basisset und koennen per AI uebersetzt werden.
Legacy `{en, de, fr}`-Dicts werden via `_extractRegistrySourceText()` auf den `xx`-Key reduziert. Neue Eintraege: immer **String**, kein Dict.
## Projektstruktur Gateway