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>
101 lines
3.4 KiB
Python
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
|