l18n fixes

This commit is contained in:
ValueOn AG 2026-04-12 14:05:11 +02:00
parent d16e1029a7
commit 1cb99f8639
2 changed files with 192 additions and 18 deletions

View file

@ -1,5 +1,5 @@
<!-- status: canonical -->
<!-- lastReviewed: 2026-04-11 -->
<!-- lastReviewed: 2026-04-12 -->
# Coding-Konventionen
@ -10,6 +10,23 @@
- **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
@ -37,7 +54,18 @@ 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.
- **`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!
<h1>{t(plan.title)}</h1> // Backend hat schon uebersetzt!
// RICHTIG - Backend-Wert direkt rendern
label: item.uiLabel
<h1>{plan.title}</h1>
```
- Fuer statische Frontend-Maps (Wochentage, Monatsnamen, Status-Labels) wird `t()` per `switch`-Funktion mit Literalen aufgerufen, nicht ueber Map-Lookup:
```tsx
@ -86,29 +114,35 @@ return {"message": t("Datei erfolgreich hochgeladen", "api.routeFiles",
**Nicht** mit `t()` taggen: Log-Eintraege, AI-Prompts, interne technische Fehlermeldungen.
### Statische Dicts mit i18n-Keys: t() auf Modul-Ebene registrieren
### Statische Dicts mit i18n-Keys: t() registriert, resolveText() liefert
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.
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
# 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()}
NAVIGATION_SECTIONS = [
{"title": t("Meine Sicht"), "items": [
{"label": t("Uebersicht"), "objectKey": "ui.system.home", ...},
]},
]
```
**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.
```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()

View file

@ -0,0 +1,140 @@
<!-- status: canonical -->
<!-- lastReviewed: 2026-04-12 -->
# Ungenutzte API-Endpunkte — Kandidaten zum Loeschen
Analyse vom 2026-04-12. Geprueft gegen:
- `frontend_nyla/src/` (UI)
- `service-teams-browser-bot/` (Teams Bot)
- `private-llm/` (Private LLM)
Keiner dieser Endpunkte wird in irgendeinem der drei Repos referenziert.
---
## Komplett ungenutzte Route-Module
### `routeMessaging.py` — 14 Endpunkte (alle loeschen)
| Method | Path | Funktion |
|--------|------|----------|
| GET | `/api/messaging/subscriptions` | Subscriptions auflisten |
| POST | `/api/messaging/subscriptions` | Subscription erstellen |
| GET | `/api/messaging/subscriptions/{id}` | Subscription abrufen |
| PUT | `/api/messaging/subscriptions/{id}` | Subscription aktualisieren |
| DELETE | `/api/messaging/subscriptions/{id}` | Subscription loeschen |
| GET | `/api/messaging/subscriptions/{id}/registrations` | Registrierungen auflisten |
| POST | `/api/messaging/subscriptions/{id}/subscribe` | Registrierung erstellen |
| DELETE | `/api/messaging/subscriptions/{id}/unsubscribe` | Abmelden |
| GET | `/api/messaging/registrations` | Alle Registrierungen |
| PUT | `/api/messaging/registrations/{id}` | Registrierung aktualisieren |
| DELETE | `/api/messaging/registrations/{id}` | Registrierung loeschen |
| POST | `/api/messaging/trigger/{id}` | Subscription ausloesen |
| GET | `/api/messaging/deliveries` | Zustellungen auflisten |
| GET | `/api/messaging/deliveries/{id}` | Zustellung abrufen |
### `routeSecurityAdmin.py` — 9 Endpunkte (alle loeschen)
| Method | Path | Funktion |
|--------|------|----------|
| GET | `/api/admin/tokens` | Token/Sessions auflisten |
| POST | `/api/admin/tokens/revoke/user` | Tokens per User widerrufen |
| POST | `/api/admin/tokens/revoke/session` | Session widerrufen |
| POST | `/api/admin/tokens/revoke/id` | Token per ID widerrufen |
| POST | `/api/admin/tokens/revoke/mandate` | Tokens per Mandate widerrufen |
| GET | `/api/admin/databases` | Datenbanken auflisten |
| GET | `/api/admin/databases/{name}/tables` | Tabellen auflisten |
| POST | `/api/admin/databases/{name}/tables/{table}/drop` | Tabelle loeschen |
| POST | `/api/admin/databases/drop` | Datenbank loeschen |
### `routeAdminRbacExport.py` — 4 Endpunkte (alle loeschen)
| Method | Path | Funktion |
|--------|------|----------|
| GET | `/api/rbac/export/global` | Globale RBAC exportieren |
| POST | `/api/rbac/import/global` | Globale RBAC importieren |
| GET | `/api/rbac/export/mandate` | Mandate RBAC exportieren |
| POST | `/api/rbac/import/mandate` | Mandate RBAC importieren |
---
## Teilweise ungenutzte Route-Module
### `routeVoiceGoogle.py` — 8 Endpunkte loeschen
Genutzt werden: `GET /languages`, `GET /voices`, `POST /stt/token`.
| Method | Path | Funktion |
|--------|------|----------|
| POST | `/voice-google/speech-to-text` | Audio STT |
| POST | `/voice-google/detect-language` | Sprache erkennen |
| POST | `/voice-google/translate` | Text uebersetzen |
| POST | `/voice-google/realtime-interpreter` | Interpreter-Pipeline |
| POST | `/voice-google/text-to-speech` | TTS |
| GET | `/voice-google/health` | Health Check |
| GET | `/voice-google/settings` | Voice-Einstellungen lesen |
| POST | `/voice-google/settings` | Voice-Einstellungen speichern |
### `routeClickup.py` — 10 Endpunkte loeschen
Genutzt werden: `GET /{id}/tasks/{taskId}`, `GET /{id}/lists/{listId}`, `GET /{id}/teams/{teamId}`, `GET /{id}/lists/{listId}/fields`, `GET /{id}/lists/{listId}/tasks`.
| Method | Path | Funktion |
|--------|------|----------|
| GET | `/api/clickup/{id}/teams` | Teams auflisten |
| GET | `/api/clickup/{id}/teams/{teamId}/spaces` | Spaces auflisten |
| GET | `/api/clickup/{id}/spaces/{spaceId}/folders` | Folders auflisten |
| GET | `/api/clickup/{id}/spaces/{spaceId}/lists` | Listen in Space |
| GET | `/api/clickup/{id}/folders/{folderId}/lists` | Listen in Folder |
| GET | `/api/clickup/{id}/teams/{teamId}/tasks/search` | Tasks suchen |
| GET | `/api/clickup/{id}/user` | ClickUp User |
| POST | `/api/clickup/{id}/lists/{listId}/tasks` | Task erstellen |
| PUT | `/api/clickup/{id}/tasks/{taskId}` | Task aktualisieren |
| DELETE | `/api/clickup/{id}/tasks/{taskId}` | Task loeschen |
### `routeSharepoint.py` — 3 Endpunkte loeschen
Genutzt wird: `GET /api/sharepoint/folder-options` (ohne connectionId).
| Method | Path | Funktion |
|--------|------|----------|
| GET | `/api/sharepoint/{id}/sites` | SharePoint Sites |
| GET | `/api/sharepoint/{id}/sites/{siteId}/folders` | Folders unter Site |
| GET | `/api/sharepoint/{id}/folder-options` | Folder-Optionen (mit connectionId) |
### `routeFeatureGraphicalEditor.py` — 5 Endpunkte loeschen
| Method | Path | Funktion |
|--------|------|----------|
| GET | `/api/workflows/{id}/info` | Workflow-Engine Info |
| POST | `/api/workflows/{id}/schedule-sync` | Schedule-Trigger sync |
| POST | `/api/workflows/{id}/workflows/{wfId}/webhooks/{epId}` | Webhook registrieren |
| POST | `/api/workflows/{id}/workflows/{wfId}/forms/{epId}/submit` | Formular-Submit |
| POST | `/api/workflows/{id}/runs/{runId}/resume` | Pausierten Run fortsetzen |
---
## Einzelne ungenutzte Endpunkte
| Route-Datei | Method | Path | Funktion |
|-------------|--------|------|----------|
| `routeAdminRbacRules.py` | GET | `/api/rbac/catalog/stats` | RBAC Katalog-Statistiken |
| `routeI18n.py` | GET | `/api/i18n/user-language-options` | Sprach-Optionen fuer User |
| `routeI18n.py` | PUT | `/api/i18n/sets/update-all` | Alle Sprachsets aktualisieren |
| `routeSecurityGoogle.py` | GET | `/api/google/config` | Google OAuth Config |
| `routeSecurityGoogle.py` | POST | `/api/google/verify` | Token-Verifikation |
| `routeSecurityMsft.py` | POST | `/api/msft/cleanup` | MSFT Auth Cleanup |
| `routeAdminUserAccessOverview.py` | GET | `/api/admin/user-access-overview/{userId}/effective-permissions` | Effektive Berechtigungen |
| `routeDataFiles.py` | GET | `/api/files/stats` | Datei-Statistiken |
| `routeBilling.py` | GET | `/api/billing/admin/transactions/{id}/filter-values` | Filter-Werte Admin-Transaktionen |
---
## Zusammenfassung
| Kategorie | Endpunkte |
|-----------|-----------|
| Komplett ungenutzte Module | 27 |
| Teilweise ungenutzte Module | 26 |
| Einzelne Endpunkte | 9 |
| **Total** | **62** |