226 lines
9.6 KiB
Markdown
226 lines
9.6 KiB
Markdown
<!-- status: canonical -->
|
|
<!-- lastReviewed: 2026-04-11 -->
|
|
|
|
# Coding-Konventionen
|
|
|
|
## Naming
|
|
|
|
- Alle internen Funktionen beginnen mit `_` Prefix (nicht exportierbar)
|
|
- **camelCase** fuer Variablen und Funktionsnamen (kein snake_case)
|
|
- **PascalCase** fuer Klassen und Pydantic-Models
|
|
- Dateien: `camelCase` fuer Module (z.B. `mainServiceAi.py`, `routeBilling.py`)
|
|
|
|
## Frontend (React/TypeScript)
|
|
|
|
- Keine Browser-Dialoge (`alert`, `confirm`, `prompt`) -- stattdessen `useConfirm()` / `usePrompt()` Hooks
|
|
- CSS Modules fuer Styling
|
|
- Hooks-Pattern fuer State und API-Zugriffe (`useApiRequest`, `useBilling`, etc.)
|
|
- Fehler propagieren -- keine stillen Fallbacks bei kritischen Pfaden
|
|
|
|
### i18n-Pflicht: `t()` fuer alle UI-Texte
|
|
|
|
Jeder sichtbare Text im UI (Labels, Buttons, Placeholders, Tooltips, Fehlermeldungen) **muss** mit `t()` getaggt werden. Hardcodierte deutsche Strings im JSX sind nicht erlaubt.
|
|
|
|
```tsx
|
|
import { useLanguage } from '../../providers/language/LanguageContext';
|
|
|
|
const { t } = useLanguage();
|
|
|
|
// Einfacher Text
|
|
t('Speichern')
|
|
|
|
// Mit Variablen-Interpolation
|
|
t('{count} Eintraege gefunden', { count: String(total) })
|
|
|
|
// Gleicher Text, anderer Kontext → Klammer als Kontext-Hinweis
|
|
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"
|
|
|
|
## Backend (FastAPI/Python)
|
|
|
|
- **Pydantic-Models** als einzige Quelle fuer UI-Feld-Definitionen
|
|
- **`PowerOnModel`** als Basis mit System-Audit-Feldern (`sysCreatedAt`, `sysCreatedBy`, `sysModifiedAt`, `sysModifiedBy`)
|
|
- Fehler propagieren -- Exceptions explizit werfen, nicht schlucken
|
|
- Config ueber `APP_CONFIG` (aus `modules/shared/configuration.py`)
|
|
|
|
### 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 (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
|
|
|
|
# Fehlermeldung (context automatisch = "api")
|
|
raise HTTPException(status_code=403, detail=t("Zugriff verweigert", "api.routeSecurity",
|
|
"Fehlermeldung bei fehlendem Zugriff"))
|
|
|
|
# Erfolgsmeldung
|
|
return {"message": t("Datei erfolgreich hochgeladen", "api.routeFiles",
|
|
"Bestaetigung nach Datei-Upload")}
|
|
```
|
|
|
|
**Nicht** mit `t()` taggen: Log-Eintraege, AI-Prompts, interne technische Fehlermeldungen.
|
|
|
|
### Statische Dicts mit i18n-Keys: t() auf Modul-Ebene registrieren
|
|
|
|
Wenn ein Modul statische Dicts/Listen mit UI-sichtbaren Texten definiert (z.B. `BADGE_DEFINITIONS`, Level-Labels), muessen die Keys **auf Modul-Ebene** via `t()` registriert werden. Nur so erscheinen sie im `xx`-Basisset und koennen uebersetzt werden.
|
|
|
|
```python
|
|
from modules.shared.i18nRegistry import t
|
|
|
|
# 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 = {
|
|
"first_session": {"label": "Erste Session", "icon": "star"},
|
|
"streak_3": {"label": "3-Tage-Serie", "icon": "fire"},
|
|
}
|
|
|
|
# Runtime: resolveText (nicht t(variable))
|
|
from modules.shared.i18nRegistry import resolveText
|
|
|
|
def getBadgeDefinitions():
|
|
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 via resolveText()
|
|
|
|
`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 resolveText
|
|
|
|
# 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.
|
|
|
|
```tsx
|
|
// FALSCH - verdeckt Backend-Fehler
|
|
const name = typeof m.name === 'object' ? m.name.de || m.name.en : m.name;
|
|
|
|
// RICHTIG - Backend liefert String
|
|
const name = m.label || m.name || m.id;
|
|
```
|
|
|
|
Fuer Route-Module gibt es den Shorthand `apiRouteContext`:
|
|
|
|
```python
|
|
from modules.shared.i18nRegistry import apiRouteContext
|
|
routeApiMsg = apiRouteContext("routeBilling")
|
|
|
|
raise HTTPException(status_code=403, detail=routeApiMsg("Zugriff verweigert"))
|
|
```
|
|
|
|
### Pydantic-Models: `@i18nModel` Decorator Pflicht
|
|
|
|
Jedes Pydantic-Model das im UI angezeigt wird (Tabellen, Formulare) **muss** den `@i18nModel` Decorator haben. Feld-Labels werden in `json_schema_extra["label"]` definiert.
|
|
|
|
```python
|
|
from modules.shared.i18nRegistry import i18nModel
|
|
|
|
@i18nModel("Benutzer")
|
|
class User(PowerOnModel):
|
|
name: str = Field(
|
|
description="Full name of the user",
|
|
json_schema_extra={"label": "Name", "frontend_type": "text"}
|
|
)
|
|
email: str = Field(
|
|
description="Email address for login and notifications",
|
|
json_schema_extra={"label": "E-Mail-Adresse", "frontend_type": "text"}
|
|
)
|
|
```
|
|
|
|
- `@i18nModel("Deutscher Modelname")` -- AI-Kontext kommt automatisch aus dem Class-Docstring
|
|
- `json_schema_extra={"label": "Deutscher Feldname"}` -- Pflicht fuer jedes UI-sichtbare Feld
|
|
- `Field(description=...)` -- wird als AI-Kontext fuer die Uebersetzung verwendet
|
|
- **Kein** `registerModelLabels()` mehr verwenden (entfernt)
|
|
|
|
### Feature-Module: Labels als deutsche Basis-Strings
|
|
|
|
`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": "Benutzer",
|
|
"meta": {"table": "UserInDB", "namespace": "uam"}
|
|
},
|
|
]
|
|
```
|
|
|
|
Legacy `{en, de, fr}`-Dicts werden via `_extractRegistrySourceText()` auf den `xx`-Key reduziert. Neue Eintraege: immer **String**, kein Dict.
|
|
|
|
## Projektstruktur Gateway
|
|
|
|
```
|
|
gateway/
|
|
app.py # FastAPI-App, Middleware, Startup
|
|
config.ini # Statische Konfiguration
|
|
modules/
|
|
auth/ # JWT, OAuth, CSRF, Authentication
|
|
datamodels/ # Pydantic-Models (zentrale Quelle)
|
|
features/ # Feature-Module (workspace, automation, ...)
|
|
<name>/
|
|
main<Name>.py # FEATURE_CODE, Registrierung
|
|
routeFeature<Name>.py # HTTP-Endpunkte
|
|
interfaceFeature<Name>.py # DB-Interface
|
|
datamodelFeature<Name>.py # Feature-spezifische Models
|
|
interfaces/ # DB-Interfaces (CRUD, Queries)
|
|
routes/ # Core-Routes (billing, admin, GDPR, ...)
|
|
security/ # RBAC-Engine
|
|
serviceCenter/
|
|
core/ # serviceSecurity, serviceUtils, serviceStreaming
|
|
services/ # serviceAi, serviceChat, serviceAgent, ...
|
|
registry.py # Service-Registrierung und Dependencies
|
|
shared/ # configuration.py, Utilities
|
|
system/ # registry.py (Feature-Discovery)
|
|
workflows/
|
|
methods/ # Unified Action Library
|
|
processing/ # WorkflowProcessor, Modes
|
|
automation/ # v1 Runtime
|
|
automation2/ # v2 Engine
|
|
connectors/ # Externe Systeme (DB, SharePoint, Jira, ...)
|
|
aicore/ # AI-Provider-Plugins, Model-Selector
|
|
```
|
|
|
|
## Anti-Patterns
|
|
|
|
- Keine impliziten Type-Conversions in API-Responses
|
|
- Keine DB-Queries in Route-Handlern (immer ueber Interfaces)
|
|
- Kein direkter `os.environ`-Zugriff -- immer `APP_CONFIG`
|
|
- Keine hartkodierten Secrets -- verschluesselt in Env-Dateien
|