Some checks failed
ci/woodpecker/pr/pipeline Pipeline failed
Add i18n support to the blog web UI with 4 languages (en/ru/fr/de),
80 translation keys, automatic Accept-Language detection, persistent
locale cookie, and a language switcher dropdown in the header.
- Infrastructure: TranslationService, translation dicts, convenience _()
- Presentation: locale middleware, /web/lang/{locale} switcher route
- Templates: all 9 templates use {{ _(key, current_locale) }}
- Tests: 26 tests across TranslationService, locale detection helpers
- Docs: TEST_MODEL.md and FEATURE_INFRASTRUCTURE.md updated with TC-UNIT-811-821
79 lines
2.5 KiB
Python
79 lines
2.5 KiB
Python
"""Translation service for i18n support.
|
|
|
|
This module provides the translation service that resolves translation keys
|
|
to localized strings using in-memory translation dictionaries. Falls back
|
|
from requested locale through English to the raw key.
|
|
"""
|
|
|
|
from app.infrastructure.i18n.translations import TRANSLATIONS
|
|
|
|
SUPPORTED_LOCALES = frozenset({"en", "ru", "fr", "de"})
|
|
DEFAULT_LOCALE = "en"
|
|
|
|
|
|
class TranslationService:
|
|
"""Service for resolving translation keys to localized strings.
|
|
|
|
Provides a singleton-like interface for translating UI strings
|
|
across the application. Falls back through requested locale to
|
|
English and finally to the raw key if no translation exists.
|
|
|
|
Attributes:
|
|
translations: Dictionary of locale to key to string mappings.
|
|
"""
|
|
|
|
_instance: "TranslationService | None" = None
|
|
|
|
def __init__(self) -> None:
|
|
"""Initialize translation service with translation data."""
|
|
self.translations = TRANSLATIONS
|
|
|
|
@classmethod
|
|
def get_instance(cls) -> "TranslationService":
|
|
"""Get or create the singleton instance.
|
|
|
|
Returns:
|
|
The shared TranslationService instance.
|
|
"""
|
|
if cls._instance is None:
|
|
cls._instance = cls()
|
|
return cls._instance
|
|
|
|
def get_text(self, key: str, locale: str = DEFAULT_LOCALE) -> str:
|
|
"""Get translated text for a given key and locale.
|
|
|
|
Resolves the key through the locale chain: requested locale,
|
|
then English fallback, then the raw key itself.
|
|
|
|
Args:
|
|
key: Translation key (e.g. ``nav.home``).
|
|
locale: Target locale code (e.g. ``en``, ``ru``, ``fr``, ``de``).
|
|
|
|
Returns:
|
|
Translated string if found, otherwise the English version
|
|
or the key itself as last resort.
|
|
"""
|
|
locale_translations = self.translations.get(locale)
|
|
if locale_translations is not None and key in locale_translations:
|
|
return locale_translations[key]
|
|
|
|
if locale != DEFAULT_LOCALE:
|
|
fallback = self.translations.get(DEFAULT_LOCALE, {}).get(key)
|
|
if fallback is not None:
|
|
return fallback
|
|
|
|
return key
|
|
|
|
|
|
def _(key: str, locale: str = DEFAULT_LOCALE) -> str:
|
|
"""Convenience function for translating a single key.
|
|
|
|
Args:
|
|
key: Translation key to look up.
|
|
locale: Target locale code.
|
|
|
|
Returns:
|
|
Translated string or the key itself if no translation found.
|
|
"""
|
|
return TranslationService.get_instance().get_text(key, locale)
|