docker.videokonverter/video-konverter/app/services/i18n.py
data 6d0b8936c5 feat: VideoKonverter v4.0 - Streaming-Client Ausbau
TV-App komplett ueberarbeitet: i18n (DE/EN), Multi-User Quick-Switch,
3 Themes (Dark/Medium/Light), 3 Ansichten (Grid/Liste/Detail),
Filter (Quellen/Genre/Rating/Sortierung), Merkliste, 5-Sterne-Bewertung,
Watch-Status, Player-Overlay (Audio/Untertitel/Qualitaet/Naechste Episode),
Episoden-Thumbnails, Suchverlauf, Queue-Bugfix (delete_source).

5 neue DB-Tabellen, 10+ neue API-Endpunkte, ~3800 neue Zeilen Code.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 07:39:12 +01:00

101 lines
3.4 KiB
Python

"""Internationalisierung (i18n) fuer die TV-App.
Laedt Uebersetzungen aus JSON-Dateien und stellt Jinja2-Filter bereit."""
import json
import logging
import os
from typing import Optional
# Verfuegbare Sprachen
SUPPORTED_LANGS = ("de", "en")
DEFAULT_LANG = "de"
# Cache fuer geladene Uebersetzungen
_translations: dict[str, dict] = {}
def load_translations(static_dir: str) -> None:
"""Laedt alle Uebersetzungsdateien aus static/tv/i18n/"""
i18n_dir = os.path.join(static_dir, "tv", "i18n")
for lang in SUPPORTED_LANGS:
filepath = os.path.join(i18n_dir, f"{lang}.json")
if os.path.isfile(filepath):
with open(filepath, "r", encoding="utf-8") as f:
_translations[lang] = json.load(f)
logging.info(f"i18n: Sprache '{lang}' geladen ({filepath})")
else:
logging.warning(f"i18n: Datei nicht gefunden: {filepath}")
if not _translations:
logging.error("i18n: Keine Uebersetzungen geladen!")
def get_text(key: str, lang: str = DEFAULT_LANG, **kwargs) -> str:
"""Gibt uebersetzten Text fuer einen Punkt-separierten Schluessel zurueck.
Beispiel: get_text('nav.home', 'de') -> 'Startseite'
Platzhalter: get_text('player.next_in', 'de', seconds=10)"""
translations = _translations.get(lang, _translations.get(DEFAULT_LANG, {}))
parts = key.split(".")
value = translations
for part in parts:
if isinstance(value, dict):
value = value.get(part)
else:
value = None
break
if value is None:
# Fallback auf Default-Sprache
if lang != DEFAULT_LANG:
return get_text(key, DEFAULT_LANG, **kwargs)
# Key als Fallback zurueckgeben
return key
if not isinstance(value, str):
return key
# Platzhalter ersetzen
if kwargs:
for k, v in kwargs.items():
value = value.replace(f"{{{k}}}", str(v))
return value
def get_all_translations(lang: str = DEFAULT_LANG) -> dict:
"""Gibt alle Uebersetzungen fuer eine Sprache zurueck (fuer JS)"""
return _translations.get(lang, _translations.get(DEFAULT_LANG, {}))
def setup_jinja2_i18n(app) -> None:
"""Registriert i18n-Filter und Globals in Jinja2-Environment.
Muss NACH aiohttp_jinja2.setup() aufgerufen werden."""
import aiohttp_jinja2
env = aiohttp_jinja2.get_env(app)
# Filter: {{ 'nav.home'|t }} oder {{ 'nav.home'|t('en') }}
def t_filter(key: str, lang: str = None) -> str:
# Sprache wird pro Request gesetzt (siehe Middleware)
if lang is None:
lang = getattr(env, "_current_lang", DEFAULT_LANG)
return get_text(key, lang)
env.filters["t"] = t_filter
# Global-Funktion: {{ t('nav.home') }} oder {{ t('nav.home', seconds=10) }}
def t_func(key: str, lang: str = None, **kwargs) -> str:
if lang is None:
lang = getattr(env, "_current_lang", DEFAULT_LANG)
return get_text(key, lang, **kwargs)
env.globals["t"] = t_func
env.globals["SUPPORTED_LANGS"] = SUPPORTED_LANGS
logging.info("i18n: Jinja2-Filter und Globals registriert")
def set_request_lang(app, lang: str) -> None:
"""Setzt die Sprache fuer den aktuellen Request.
Wird vom TV-Auth-Middleware aufgerufen."""
import aiohttp_jinja2
env = aiohttp_jinja2.get_env(app)
env._current_lang = lang if lang in SUPPORTED_LANGS else DEFAULT_LANG