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