From d16e1029a7a20616bc62c7651fc64573d2b197c3 Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Sat, 11 Apr 2026 22:23:49 +0200
Subject: [PATCH] fixed sysuser and removed redundant fallbacks
---
b-reference/frontend-nyla/architecture.md | 6 +--
b-reference/gateway/architecture.md | 47 +++++++++++++++----
d-guides/coding-conventions.md | 57 +++++++++++++++--------
3 files changed, 78 insertions(+), 32 deletions(-)
diff --git a/b-reference/frontend-nyla/architecture.md b/b-reference/frontend-nyla/architecture.md
index bdfe67d..5430185 100644
--- a/b-reference/frontend-nyla/architecture.md
+++ b/b-reference/frontend-nyla/architecture.md
@@ -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 `` (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
diff --git a/b-reference/gateway/architecture.md b/b-reference/gateway/architecture.md
index 6730c59..5ad442e 100644
--- a/b-reference/gateway/architecture.md
+++ b/b-reference/gateway/architecture.md
@@ -1,5 +1,5 @@
-
+
# 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.` (HTTPExceptions), `table.` (Model-Labels), `table..` (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.` (HTTPExceptions), `table.` (Model-Labels), `table..` (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
diff --git a/d-guides/coding-conventions.md b/d-guides/coding-conventions.md
index 38e18bf..16e0ece 100644
--- a/d-guides/coding-conventions.md
+++ b/d-guides/coding-conventions.md
@@ -1,5 +1,5 @@
-
+
# 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 = { 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