105 lines
3.0 KiB
Python
105 lines
3.0 KiB
Python
"""
|
|
i18n (Internationalization) module for AI Novel Generator.
|
|
Loads locale-specific JSON files and provides a simple t(key) function to retrieve translated strings.
|
|
|
|
Usage:
|
|
from locales.i18n import t, set_language
|
|
|
|
set_language("VI") # Switch to Vietnamese
|
|
print(t("app.title")) # Prints Vietnamese title
|
|
|
|
Language can also be set via environment variable APP_LANGUAGE (default: "VI").
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import logging
|
|
|
|
logger = logging.getLogger("i18n")
|
|
|
|
# Global state
|
|
_current_language = None
|
|
_translations = {}
|
|
_locales_dir = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
|
def load_locale(lang: str) -> dict:
|
|
"""Load a locale JSON file and return the translations dict."""
|
|
locale_path = os.path.join(_locales_dir, lang, "messages.json")
|
|
if not os.path.exists(locale_path):
|
|
logger.error(f"Locale file not found: {locale_path}")
|
|
return {}
|
|
|
|
try:
|
|
with open(locale_path, "r", encoding="utf-8") as f:
|
|
return json.load(f)
|
|
except (json.JSONDecodeError, IOError) as e:
|
|
logger.error(f"Failed to load locale '{lang}': {e}")
|
|
return {}
|
|
|
|
|
|
def set_language(lang: str) -> None:
|
|
"""Set the active language and load its translations."""
|
|
global _current_language, _translations
|
|
_current_language = lang
|
|
_translations = load_locale(lang)
|
|
if _translations:
|
|
logger.info(f"Language set to: {lang}")
|
|
else:
|
|
logger.warning(f"No translations loaded for language: {lang}")
|
|
|
|
|
|
def get_language() -> str:
|
|
"""Return the current language code."""
|
|
return _current_language or "VI"
|
|
|
|
|
|
def t(key: str, **kwargs) -> str:
|
|
"""
|
|
Translate a dot-notation key to the localized string.
|
|
|
|
Args:
|
|
key: Dot-separated key path, e.g. "tabs.rewrite" or "messages.polish_success"
|
|
**kwargs: Optional format parameters to interpolate into the string.
|
|
|
|
Returns:
|
|
The translated string, or the key itself if not found (for debugging).
|
|
"""
|
|
global _translations
|
|
|
|
# Auto-initialize if not yet loaded
|
|
if not _translations:
|
|
init_lang = os.getenv("APP_LANGUAGE", "VI")
|
|
set_language(init_lang)
|
|
|
|
# Traverse the nested dict
|
|
parts = key.split(".")
|
|
value = _translations
|
|
for part in parts:
|
|
if isinstance(value, dict) and part in value:
|
|
value = value[part]
|
|
else:
|
|
logger.warning(f"Missing translation key: '{key}' (language: {_current_language})")
|
|
return key # Return the key itself as fallback
|
|
|
|
if isinstance(value, str):
|
|
if kwargs:
|
|
try:
|
|
return value.format(**kwargs)
|
|
except KeyError:
|
|
return value
|
|
return value
|
|
|
|
# If value is a list (e.g. dropdown choices), return it directly
|
|
if isinstance(value, list):
|
|
return value
|
|
|
|
# If value is still a dict, return the key
|
|
logger.warning(f"Translation key '{key}' resolved to a dict, not a string")
|
|
return key
|
|
|
|
|
|
# Auto-initialize on import
|
|
_init_lang = os.getenv("APP_LANGUAGE", "VI")
|
|
set_language(_init_lang)
|