# 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`)
## i18n Grundprinzip: Uebersetzen an der Quelle
Jeder Text wird **an der Quelle** uebersetzt. Backend-Strings werden im Backend uebersetzt, Frontend-Strings im Frontend. Der Empfaenger rendert direkt — keine doppelte Uebersetzung, keine Checks.
**Backend liefert IMMER in der korrekten Sprache.** Das Frontend muss nichts pruefen oder nochmals uebersetzen.
| Text-Quelle | Wer uebersetzt | Empfaenger |
|---|---|---|
| Frontend-Komponente (Button, Label, h1) | Frontend: `t('Speichern')` | rendert direkt |
| Backend statische Struktur (Navigation, Katalog) | Backend: `t()` registriert Key, `resolveText()` liefert zur Request-Zeit | Frontend: `item.uiLabel` direkt rendern |
| Backend API-Response (Fehlermeldung, Erfolg) | Backend: `t("Zugriff verweigert")` | Frontend: `error.detail` direkt rendern |
| Backend DB-Wert (TextMultilingual) | Backend: `resolveText(role.description)` | Frontend: `role.description` direkt rendern |
**Kernregel:** Strings vom Backend sind **immer bereits uebersetzt**. Das Frontend darf `t()` **nie** auf Backend-Werte anwenden — weder `t(item.label)` noch `t(variable)`. Keine `typeof === 'object'` Checks, keine Fallback-Ketten. Nur eigene Frontend-Literale wie `t('Speichern')`.
**Redundanz vermeiden:** Derselbe Text darf nicht an zwei Stellen mit `t()` getaggt werden.
## 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 (Navigation-Labels, Feature-Labels, Role-Descriptions etc.) sind bereits uebersetzt und werden direkt gerendert:
```tsx
// FALSCH - Backend-Wert nochmal durch t()
label: t(item.uiLabel) // Backend hat schon uebersetzt!
{t(plan.title)}
// Backend hat schon uebersetzt!
// RICHTIG - Backend-Wert direkt rendern
label: item.uiLabel
{plan.title}
```
- 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"
## 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() registriert, resolveText() liefert
Statische Dicts (Navigation, Kataloge) haben **zwei Schritte**:
1. **Import-Zeit:** `t()` im Dict **registriert** den Key (und gibt den deutschen String zurueck, der im Dict gespeichert wird)
2. **Request-Zeit:** `resolveText()` in der Route nimmt den deutschen String und liefert die Uebersetzung fuer die aktuelle Sprache
```python
# mainSystem.py — t() registriert Keys bei Import-Zeit
from modules.shared.i18nRegistry import t
NAVIGATION_SECTIONS = [
{"title": t("Meine Sicht"), "items": [
{"label": t("Uebersicht"), "objectKey": "ui.system.home", ...},
]},
]
```
```python
# routeSystem.py — resolveText() uebersetzt zur Request-Zeit
from modules.shared.i18nRegistry import resolveText
def _formatBlockItem(item):
return {"uiLabel": resolveText(item["label"]), ...}
```
**Warum zwei Schritte?** `t()` wird bei Import-Zeit ausgewertet — der Rueckgabewert ist immer deutsch (Default-Sprache). Der Wert im Dict ist daher ein fester deutscher String. `resolveText()` nimmt diesen deutschen String zur Request-Zeit und liefert die korrekte Uebersetzung.
**Warum Modul-Ebene?** `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, ...)
/
main.py # FEATURE_CODE, Registrierung
routeFeature.py # HTTP-Endpunkte
interfaceFeature.py # DB-Interface
datamodelFeature.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