wiki/d-guides/coding-conventions.md
2026-04-11 22:23:49 +02:00

9.6 KiB

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.

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-Literalent(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:
// 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).

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.

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.

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.

// 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:

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.

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.

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