wiki/c-work/2-build/gateway-i18n-unified.md
2026-04-09 21:55:19 +02:00

22 KiB

Gateway i18n: Einheitliches Sprachsystem

Beschreibung und Kontext

Das Frontend nutzt bereits ein DB-basiertes Sprachsystem (t() -> UiLanguageSet -> AI-Uebersetzung). Das Gateway hat aktuell kein einheitliches System -- UI-sichtbare Texte sind hardcoded (deutsch/englisch), Model-Labels werden pro Datei mit registerModelLabels() in allen Sprachen manuell gepflegt, und TextMultilingual-Felder haben einen starren 4-Sprachen-Renderer.

Business-Treiber: Mehrsprachigkeit ist Pflicht fuer SaaS. Aktuell muss jede neue Sprache manuell in ~115 registerModelLabels()-Aufrufen und ~150 HTTPException-Texten nachgepflegt werden. Das skaliert nicht. Mit dem @i18nModel-Decorator entfallen die manuellen Aufrufe komplett.

Risiko bei Nicht-Umsetzung: Neue Sprachen erfordern Code-Aenderungen in 29+ Dateien. Inkonsistente Uebersetzungen. Kein AI-Uebersetzungsworkflow fuer Backend-Texte.

Fokus und kritische Details

  • Caching: @i18nModel Decorator wird bei Import-Time ausgefuehrt (~29 Klassen). Die t()-Funktion darf NICHT pro Aufruf die DB abfragen. Loesung: In-Memory-Cache beim Boot laden, danach O(1)-Lookup.
  • Boot-Reihenfolge: Der Gateway-Boot-Scan muss NACH DB-Init laufen, aber VOR dem ersten Request.
  • Abwaertskompatibilitaet: getModelLabels() und getModelLabel() muessen weiterhin funktionieren (werden von routeAttributes.py, routeSystem.py etc. aufgerufen). Die neue Implementierung ersetzt die interne Logik, nicht die API.
  • Context-Namensraum: Gateway-Texte bekommen context = "api.*", Model-Labels context = "table.<ClassName>.*". Kein Konflikt mit Frontend context = "ui".
  • AI-Kontext (value): Wie im Frontend wird der value im xx-Set als Kontext-Beschreibung fuer die AI genutzt. Fuer Model-Labels wird der Kontext automatisch aus Pydantic-Metadaten (Class-Docstring, Field-Description) extrahiert. Fuer HTTPExceptions wird er manuell mitgegeben oder aus Route-Name + HTTP-Status abgeleitet.

Ziel und Nicht-Ziele

  • Ziel: Alle UI-sichtbaren Gateway-Texte laufen ueber das bestehende UiLanguageSet-System und werden per AI uebersetzt wie Frontend-Texte.
  • Ziel: registerModelLabels() wird komplett durch @i18nModel Decorator ersetzt -- Labels und AI-Kontext werden automatisch aus Pydantic-Metadaten extrahiert.
  • Ziel: TextMultilingual-Felder im UI dynamisch fuer alle verfuegbaren Sprachen rendern (nicht nur 4 hardcoded).
  • Explizit NICHT: Log-Eintraege und AI-Prompts werden NICHT mehrsprachig. Interne Fehlermeldungen die nie im UI erscheinen bleiben englisch.

Betroffene Module

  • Gateway:
    • modules/shared/i18nRegistry.py -- NEUES Modul: t(), @i18nModel, Boot-Scan, Cache, DB-Sync
    • modules/shared/attributeUtils.py -- getModelLabels()/getModelLabel() auf neues System umstellen, registerModelLabels() entfernen
    • modules/routes/routeI18n.py -- Erweiterung: Gateway-Keys in sync-xx integrieren
    • modules/datamodels/*.py (~29 Dateien) -- @i18nModel Decorator + json_schema_extra["label"], registerModelLabels() entfernen
    • modules/routes/*.py (~21 Dateien) -- HTTPException(detail=...) auf t() umstellen
    • app.py -- Boot-Hook fuer Gateway-Key-Scan
  • Frontend:
    • FormGeneratorForm.tsx -- TextMultilingual-Renderer dynamisch machen
    • FormGeneratorTable.tsx -- formatTextMultilingual dynamisch machen
    • AdminLanguagesPage.tsx -- Gateway-Keys in sync-xx anzeigen (optional)
  • DB-Migration: Nein (nutzt bestehendes UiLanguageSet-Schema)
  • Andere Komponenten: Keine

Entscheidungen

Datum Entscheidung Begruendung
2026-04-09 Gateway-t() mit In-Memory-Cache, kein DB-Call pro Aufruf Performance: ~115 registerModelLabels + ~150 HTTPException = ~265+ Aufrufe pro Request-Zyklus
2026-04-09 Boot-Scan beim App-Start, automatisch in DB schreiben Konsistenz: Jeder Gateway-Start hat ein vollstaendiges Key-Set in der DB
2026-04-09 Context-Prefix api fuer Gateway-UI-Texte, table.<Class> fuer Model-Labels Klare Trennung von Frontend (ui) und Backend-Texten
2026-04-09 TextMultilingual dynamisch aus availableLanguages rendern Skaliert mit neuen Sprachen ohne Code-Aenderung
2026-04-09 @i18nModel Decorator ersetzt registerModelLabels() komplett Eliminiert Duplikation: Labels + AI-Kontext kommen aus Pydantic-Metadaten. Entwickler schreibt weniger Code, neue Felder werden automatisch registriert

Architektur

Datenfluss

Gateway Boot:
  1. DB init (PostgreSQL)
  2. Module laden -> @i18nModel Decorator + t() Aufrufe -> Keys + Kontext in _REGISTRY sammeln
  3. Boot-Hook: _REGISTRY -> UiLanguageSet(xx) sync
     - Nur context="api"/"table.*" Keys mergen (UI-Keys context="ui" nicht anfassen)
     - Neue Keys hinzufuegen, verwaiste entfernen, Kontext (value) aktualisieren
  4. Sprach-Cache laden: alle UiLanguageSets -> In-Memory Dict

Request:
  User (language=fr) -> Middleware setzt _CURRENT_LANGUAGE="fr"
  -> Gateway -> t("Zugriff verweigert") -> Cache["fr"]["Zugriff verweigert"] -> "Accès refusé"

AI-Uebersetzung (Admin sync):
  xx-Entry: {context: "api.routeSecurityLocal", key: "Zugriff verweigert",
             value: "Fehlermeldung bei fehlendem Zugriff auf eine Ressource"}
  -> AI bekommt: {"key": "Zugriff verweigert",
                  "context": "Fehlermeldung bei fehlendem Zugriff auf eine Ressource"}
  -> AI uebersetzt kontextbewusst: "Access denied" (en), "Accès refusé" (fr)

Kontext-Strategie fuer die AI-Uebersetzung

Das Frontend speichert im xx-Set den value als Kontext-Beschreibung fuer die AI:

  • Entry: {context: "ui", key: "Abbrechen", value: "pages/admin/AdminLanguagesPage.tsx > AdminLanguagesPage"}
  • Die AI bekommt: {"key": "Abbrechen", "context": "Button in Admin-Sprachverwaltung"} und weiss damit, wie der Text zu uebersetzen ist.

Fuer das Backend bauen wir denselben Mechanismus. Der Kontext wird automatisch aus der Code-Struktur abgeleitet:

Kategorie context value (AI-Kontext) Beispiel
Model-Label (Tabelle) table.Feature Pydantic-Class-Docstring oder "Datenmodell: Feature" key="Feature", value="Datenmodell fuer installierbare Funktionsmodule"
Model-Attribut table.Feature.label Field-Description aus Pydantic Field(description=...) key="Bezeichnung", value="Anzeigename des Features (mehrsprachig)"
API-Fehlermeldung api.route.<routeName> Automatisch aus Route-Funktion + HTTP-Status key="Zugriff verweigert", value="HTTP 403 in routeSecurityLocal.login"
API-UI-Text api.<modul> Manuell uebergeben oder aus Funktionsname key="Datei hochgeladen", value="Erfolgsmeldung nach Datei-Upload"

Neues Modul: i18nRegistry.py

# gateway/modules/shared/i18nRegistry.py

_REGISTRY: Dict[str, I18nRegistryEntry] = {}  # key -> {context, value}
_CACHE: Dict[str, Dict[str, str]] = {}        # lang -> {key -> translation}
_CURRENT_LANGUAGE: ContextVar[str] = ContextVar("i18n_lang", default="de")

@dataclass
class I18nRegistryEntry:
    context: str    # z.B. "table.Feature.label", "api.routeSecurityLocal"
    value: str      # AI-Kontext: Beschreibung wo/wie der Text verwendet wird

def t(key: str, context: str = "api", value: str = "") -> str:
    """Tag und uebersetze einen UI-sichtbaren Text.
    Beim Import: registriert den Key mit Kontext und AI-Beschreibung.
    Zur Laufzeit: liefert die Uebersetzung aus dem Cache.

    Args:
        key: Deutscher Basis-Text (identisch mit Frontend-t())
        context: Herkunft des Texts (table.*, api.*)
        value: AI-Kontext-Beschreibung fuer die Uebersetzung
    """
    if key not in _REGISTRY:
        _REGISTRY[key] = I18nRegistryEntry(context=context, value=value)
    lang = _CURRENT_LANGUAGE.get()
    return _CACHE.get(lang, {}).get(key, key)

def _setLanguage(lang: str):
    """Setzt die Sprache fuer den aktuellen Request (Middleware)."""
    _CURRENT_LANGUAGE.set(lang)

async def _syncRegistryToDb():
    """Boot-Hook: Schreibt alle gesammelten Keys in UiLanguageSet(xx).
    Merged mit bestehenden UI-Keys (context=ui), ueberschreibt nur api/table-Keys.
    """

async def _loadCache():
    """Boot-Hook: Laedt alle UiLanguageSets in den In-Memory-Cache."""
    # Fuer jede Sprache: {key: value} aus entries[]
    # _CACHE["fr"] = {"Zugriff verweigert": "Accès refusé", ...}

registerModelLabels() entfaellt -- ersetzt durch @i18nModel Decorator

Analyse: registerModelLabels() dupliziert Information die bereits in der Pydantic-Klasse steckt:

  • Model-Label = deutscher Name -> kann aus Class-Docstring oder explizitem _label kommen
  • Attribut-Labels = deutsche Feldnamen -> koennen als json_schema_extra["label"] am Field definiert werden
  • AI-Kontext = Beschreibung -> kommt aus Field(description=...) und Class-Docstring

Ein Decorator kann all das automatisch extrahieren. registerModelLabels() wird komplett entfernt.

Vorher (~115 manuelle Aufrufe in 29 Dateien, pro Sprache gepflegt):

class Feature(PowerOnModel):
    """Feature-Definition (global, z.B. 'trustee', 'chatbot')."""
    code: str = Field(description="Unique feature code")
    label: TextMultilingual = Field(description="Feature label in multiple languages")
    icon: str = Field(default="", description="Icon identifier for the feature")

registerModelLabels(
    "Feature",
    {"en": "Feature", "de": "Feature", "fr": "Fonctionnalité"},
    {"code": {"en": "Code", "de": "Code", "fr": "Code"},
     "label": {"en": "Label", "de": "Bezeichnung", "fr": "Libellé"},
     "icon": {"en": "Icon", "de": "Symbol", "fr": "Icône"}},
)

Nachher (Decorator, kein separater Aufruf, keine manuellen Uebersetzungen):

@i18nModel("Feature", "Feature-Definition (global, z.B. 'trustee', 'chatbot')")
class Feature(PowerOnModel):
    """Feature-Definition (global, z.B. 'trustee', 'chatbot')."""
    code: str = Field(
        description="Unique feature code",
        json_schema_extra={"label": "Code", ...}
    )
    label: TextMultilingual = Field(
        description="Feature label in multiple languages",
        json_schema_extra={"label": "Bezeichnung", ...}
    )
    icon: str = Field(
        default="",
        description="Icon identifier for the feature",
        json_schema_extra={"label": "Symbol", ...}
    )

Was der Decorator macht:

def i18nModel(modelLabel: str, aiContext: str = ""):
    """Class-Decorator: registriert Model- und Feld-Labels fuer i18n.

    1. Registriert t(modelLabel, "table.<ClassName>", aiContext or docstring)
    2. Fuer jedes Field mit json_schema_extra["label"]:
       Registriert t(label, "table.<ClassName>.<fieldName>", field.description)
    3. Speichert alles in MODEL_LABELS fuer getModelLabels()/getModelLabel()
    """
    def _decorator(cls):
        className = cls.__name__
        # Model-Label registrieren
        ctx = aiContext or (cls.__doc__ or "").strip().split("\n")[0]
        t(modelLabel, f"table.{className}", ctx)

        # Feld-Labels aus json_schema_extra["label"] extrahieren
        for fieldName, fieldInfo in cls.model_fields.items():
            extra = (fieldInfo.json_schema_extra or {})
            label = extra.get("label")
            if label:
                desc = fieldInfo.description or ""
                t(label, f"table.{className}.{fieldName}", desc)

        # Abwaertskompatibel: MODEL_LABELS befuellen
        MODEL_LABELS[className] = {
            "model": modelLabel,  # str statt Dict
            "attributes": {
                name: (info.json_schema_extra or {}).get("label", name)
                for name, info in cls.model_fields.items()
            }
        }
        return cls
    return _decorator

Vorteile:

  • registerModelLabels() entfaellt komplett (115 Aufrufe in 29 Dateien geloescht)
  • Kein Import von registerModelLabels mehr noetig
  • AI-Kontext kommt automatisch aus Field(description=...) -- der Entwickler muss nichts extra schreiben
  • Neue Felder werden automatisch registriert (kein Vergessen moeglich)
  • json_schema_extra["label"] ist die Single Source of Truth fuer den deutschen Feldnamen

Migration: Fuer jede Klasse:

  1. registerModelLabels(...) Aufruf loeschen
  2. @i18nModel("Deutscher Modelname") Decorator hinzufuegen
  3. json_schema_extra={"label": "Deutscher Feldname", ...} zu jedem Field hinzufuegen (wo noch nicht vorhanden)

getModelLabels() und getModelLabel() bleiben als API erhalten, lesen aber intern aus dem neuen System (MODEL_LABELS + t()-Cache).

HTTPException-Texte -- Vorher/Nachher

Vorher (hardcoded, kein Kontext):

raise HTTPException(status_code=403, detail="Zugriff verweigert")

Nachher (mit AI-Kontext):

raise HTTPException(
    status_code=403,
    detail=t("Zugriff verweigert", "api.routeSecurityLocal", "Fehlermeldung bei fehlendem Zugriff auf eine Ressource")
)

Der Kontext api.<routeModulName> wird automatisch aus dem Dateinamen abgeleitet. Der value beschreibt die Situation fuer die AI.

TextMultilingual UI-Renderer -- Vorher/Nachher

Vorher (4 hardcoded Sprachen):

// FormGeneratorForm.tsx
const LANGS = ['en', 'ge', 'fr', 'it'];
// 4 fixe Input-Felder

Nachher (dynamisch aus availableLanguages):

const { availableLanguages } = useLanguage();
const langCodes = availableLanguages
  .filter(l => l.code !== 'xx')
  .map(l => ({ code: l.code, label: l.label }));
// N Input-Felder basierend auf verfuegbaren Sprachen

Request-Middleware

# In app.py oder middleware
@app.middleware("http")
async def _i18nMiddleware(request, call_next):
    lang = request.headers.get("Accept-Language", "de")[:2]
    _setLanguage(lang)
    return await call_next(request)

Umsetzungs-Checkliste

Phase 1: Infrastruktur + Decorator -- Cursor: Opus 4.6

Architektur-Arbeit: neues Modul, Boot-Integration, Decorator-Logik, attributeUtils Umbau. Braucht tiefes Verstaendnis der bestehenden Codebasis (DB-Schema, Boot-Reihenfolge, ContextVar, async).

  • i18nRegistry.py erstellen: t(), @i18nModel, _REGISTRY, _CACHE, _setLanguage, _syncRegistryToDb, _loadCache
  • app.py: Boot-Hook nach DB-Init: _syncRegistryToDb() + _loadCache()
  • app.py: Request-Middleware fuer _setLanguage() (aus Accept-Language oder User-Session)
  • routeI18n.py: _loadCache() nach sync/update aufrufen (Cache invalidieren)
  • attributeUtils.py: getModelLabels()/getModelLabel() auf neues MODEL_LABELS-Format (str statt Dict) + t()-Cache umstellen
  • 2-3 Beispiel-Models migrieren (datamodelBase.py, datamodelFeatures.py) als Referenz fuer Phase 2
  • Gateway starten, pruefen ob Decorator + Boot-Sync funktioniert

Phase 2: Pydantic-Models migrieren (~27 verbleibende Dateien) -- Cursor: Auto / Fast

Repetitive Arbeit nach klarem Pattern aus Phase 1: Decorator drauf, json_schema_extra["label"] ergaenzen, registerModelLabels() loeschen. In Batches von 5-8 Dateien.

  • Alle verbleibenden Pydantic-Models: @i18nModel("Label") Decorator + json_schema_extra={"label": "..."} zu Fields
  • Alle registerModelLabels()-Aufrufe loeschen
  • from modules.shared.attributeUtils import registerModelLabels Imports entfernen
  • Nach jedem Batch: Gateway starten und pruefen

Phase 3: HTTPException-Texte umstellen (~21 Dateien, ~150 Stellen) -- Cursor: Auto / Fast

Mechanische Arbeit: detail="Text" wird zu detail=t("Text", "api.routeName", "Beschreibung"). Klares Pattern, in Batches von 5-7 Dateien.

  • Alle UI-sichtbaren HTTPException(detail="...") in routes/*.py auf t() umstellen
  • Interne/technische Fehlermeldungen (die nie im UI erscheinen) NICHT umstellen

Phase 4: TextMultilingual UI (Frontend, 2-3 Dateien) -- Cursor: Opus 4.6

React-Hooks, dynamische Rendering-Logik, ge->de Mapping. Braucht Verstaendnis des Frontend-i18n-Systems und FormGenerator-Architektur.

  • FormGeneratorForm.tsx: renderMultilingualField dynamisch aus availableLanguages
  • FormGeneratorTable.tsx: formatTextMultilingual dynamisch
  • TextMultilingual Backend-Typ pruefen: ge -> de Mapping ggf. anpassen

Phase 5: Integration + Test -- Cursor: Opus 4.6

End-to-End Pruefung, Debugging, Log-Analyse, ggf. Fixes. Braucht das staerkste Modell fuer Fehleranalyse.

  • Gateway starten, pruefen ob Keys in UiLanguageSet(xx) erscheinen (context=api, table.*)
  • Admin-UI: Sprache synchronisieren, pruefen ob Gateway-Keys uebersetzt werden
  • UI mit anderer Sprache testen: Fehlermeldungen, Tabellen-Labels, TextMultilingual-Felder

Phase 6: Wiki / Coding-Conventions aktualisieren -- Cursor: Auto / Fast

Reine Doku-Arbeit. Inhalte stehen bereits im Plan (siehe "Wiki-Anpassungen fuer Cursor AI Coding" unten). Nur Einpflegen und Formatieren.

  • d-guides/coding-conventions.md: Backend-i18n-Regeln hinzufuegen (t(), @i18nModel, json_schema_extra["label"])
  • d-guides/coding-conventions.md: Regel fuer HTTPException-Texte mit t()
  • d-guides/coding-conventions.md: Regel fuer neue Pydantic-Models (@i18nModel Pflicht)
  • b-reference/gateway/architecture.md: i18n-Architektur dokumentieren (i18nRegistry, Boot-Sync, Cache)
  • b-reference/frontend-nyla/architecture.md: TextMultilingual dynamisch dokumentieren
  • TOPICS.md: Thema "i18n / Mehrsprachigkeit" hinzufuegen mit Verweis auf relevante Seiten

Abschluss

  • RBAC / Permissions: nicht betroffen
  • Neutralisierung: nicht betroffen
  • Navigation / Routing: nicht betroffen
  • Billing-Impact: nicht betroffen

Wiki-Anpassungen fuer Cursor AI Coding

Die Coding-Conventions (d-guides/coding-conventions.md) werden um folgende Regeln erweitert, damit die Cursor AI beim Coden die Sprach- und Class-Regeln kennt:

Backend-Ergaenzung: i18n-Pflicht fuer UI-sichtbare Texte

### i18n-Pflicht: `t()` fuer alle UI-sichtbaren Texte im Gateway

Jeder Text der im Frontend angezeigt wird (HTTPException-Details, API-Response-Messages,
Erfolgs-/Fehlermeldungen) **muss** mit `t()` getaggt werden.

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.

Backend-Ergaenzung: @i18nModel fuer Pydantic-Models

### 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")` -- der 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 (deprecated, wird beim Boot ignoriert)

Frontend-Ergaenzung: TextMultilingual dynamisch

### TextMultilingual: Dynamische Sprachen

TextMultilingual-Felder rendern automatisch Eingabefelder fuer alle verfuegbaren Sprachen
(aus `availableLanguages`). Keine hardcodierten Sprach-Codes (en/ge/fr/it) mehr verwenden.

Akzeptanzkriterien

# Kriterium (Given-When-Then) Prio
1 Given Gateway gestartet, When Admin oeffnet Sprachen-Seite, Then sind alle Gateway-Keys (context=api, table.*) im xx-Basisset sichtbar must
2 Given Sprache "fr" synchronisiert, When User mit language=fr eine HTTPException ausloest, Then ist die Fehlermeldung auf Franzoesisch must
3 Given Sprache "fr" synchronisiert, When User eine Tabelle oeffnet, Then sind Spalten-Labels auf Franzoesisch must
4 Given 3 Sprachen verfuegbar (de, en, fr), When User ein TextMultilingual-Feld editiert, Then sieht er 3 Eingabefelder (nicht 4 hardcoded) must
5 Given Gateway-Boot, When ~29 @i18nModel Decorators + ~150 t()-Aufrufe, Then Boot-Zeit erhoet sich um max 500ms should
6 Given neuer Gateway-Text mit t() hinzugefuegt, When Gateway neustartet, Then erscheint der Key automatisch im xx-Set must

Testplan

ID AC Art Automatisiert Repo-Pfad Status
T1 1 api nein manuell: Admin-UI Sprachen-Seite pending
T2 2 api nein manuell: HTTPException in anderer Sprache pending
T3 3 ui nein manuell: Tabelle mit fr-Labels pending
T4 4 ui nein manuell: TextMultilingual-Editor pending
T5 5 perf nein manuell: Boot-Zeit messen pending
T6 6 api nein manuell: neuen t()-Key hinzufuegen, Gateway neustarten pending

Mengengeruest

Was Anzahl Dateien
registerModelLabels() Aufrufe ~115 29
HTTPException(detail=...) UI-sichtbar ~150 21
TextMultilingual Pydantic-Felder ~5 5
TextMultilingual Frontend-Renderer 2 FormGeneratorForm, FormGeneratorTable
  • Frontend i18n: frontend_nyla/src/providers/language/LanguageContext.tsx
  • Frontend Key-Scanner: frontend_nyla/vite.config.ts (extractI18nKeys Plugin)
  • Gateway Model-Labels: gateway/modules/shared/attributeUtils.py
  • Gateway i18n API: gateway/modules/routes/routeI18n.py
  • UiLanguageSet Model: gateway/modules/datamodels/datamodelUiLanguage.py
  • TextMultilingual: gateway/modules/datamodels/datamodelUtils.py

Abschluss

  • b-reference/ aktualisiert (gateway/architecture.md, frontend-nyla/architecture.md)
  • TOPICS.md aktualisiert (neues Thema: i18n/Mehrsprachigkeit)
  • Dieses Dokument -> z-archive/ verschoben