157 lines
6.4 KiB
Python
157 lines
6.4 KiB
Python
import logging
|
|
import json
|
|
import os
|
|
import aiosqlite
|
|
from typing import Any, Awaitable, Callable, Dict, Optional
|
|
|
|
from aiogram import BaseMiddleware
|
|
from aiogram.types import TelegramObject, User, Update
|
|
|
|
from db.database import get_user, get_db_connection_manager, _setup_db_connection
|
|
from config.settings import Settings
|
|
|
|
|
|
class JsonI18n:
|
|
|
|
def __init__(self, path: str, default: str = "en", domain: str = "bot"):
|
|
self.domain = domain
|
|
self.path = path
|
|
self.default_lang = default
|
|
self.locales_data: Dict[str, Dict[str, str]] = {}
|
|
self._load_locales()
|
|
logging.info(
|
|
f"JsonI18n initialized. Loaded languages: {list(self.locales_data.keys())}. Default: {self.default_lang}"
|
|
)
|
|
|
|
def _load_locales(self):
|
|
if not os.path.isdir(self.path):
|
|
logging.error(f"Locales path not found: {self.path}")
|
|
return
|
|
for item in os.listdir(self.path):
|
|
if item.endswith(".json"):
|
|
lang_code = item.split(".")[0]
|
|
file_path = os.path.join(self.path, item)
|
|
try:
|
|
with open(file_path, "r", encoding="utf-8") as f:
|
|
self.locales_data[lang_code] = json.load(f)
|
|
except Exception as e:
|
|
logging.error(
|
|
f"Error loading locale {lang_code} from {file_path}: {e}"
|
|
)
|
|
|
|
def gettext(self, lang_code: Optional[str], key: str, **kwargs) -> str:
|
|
effective_lang_code = lang_code
|
|
if not effective_lang_code or effective_lang_code not in self.locales_data:
|
|
effective_lang_code = self.default_lang
|
|
lang_data = self.locales_data.get(effective_lang_code)
|
|
if lang_data is None:
|
|
logging.warning(
|
|
f"No language data for '{effective_lang_code}' (default: '{self.default_lang}'). Key '{key}'."
|
|
)
|
|
return key.format(**kwargs) if kwargs else key
|
|
text = lang_data.get(key)
|
|
if text is None:
|
|
if effective_lang_code != self.default_lang:
|
|
default_lang_data = self.locales_data.get(
|
|
self.default_lang, {})
|
|
text = default_lang_data.get(key)
|
|
if text is None: return key.format(**kwargs) if kwargs else key
|
|
try:
|
|
return text.format(**kwargs) if kwargs else text
|
|
except KeyError as e:
|
|
logging.warning(
|
|
f"Missing format key {e} for key '{key}' (lang: {effective_lang_code}). Text: '{text}'"
|
|
)
|
|
return text
|
|
except Exception as e:
|
|
logging.error(
|
|
f"Error formatting key '{key}' (lang: {effective_lang_code}): {e}. Text: '{text}'"
|
|
)
|
|
return text
|
|
|
|
|
|
_i18n_instance: Optional[JsonI18n] = None
|
|
|
|
|
|
def get_i18n_instance(path: str = "locales",
|
|
default: str = "en",
|
|
domain: str = "bot") -> JsonI18n:
|
|
global _i18n_instance
|
|
if _i18n_instance is None:
|
|
_i18n_instance = JsonI18n(path=path, default=default, domain=domain)
|
|
return _i18n_instance
|
|
|
|
|
|
class I18nMiddleware(BaseMiddleware):
|
|
|
|
def __init__(self, i18n: JsonI18n, settings: Settings):
|
|
super().__init__()
|
|
self.i18n = i18n
|
|
self.settings = settings
|
|
|
|
async def __call__(self, handler: Callable[[Update, Dict[str, Any]],
|
|
Awaitable[Any]], event: Update,
|
|
data: Dict[str, Any]) -> Any:
|
|
event_user: Optional[User] = data.get("event_from_user")
|
|
|
|
current_language = self.i18n.default_lang
|
|
|
|
if event_user:
|
|
logging.debug(
|
|
f"I18nMiddleware: Processing for user {event_user.id}")
|
|
|
|
try:
|
|
|
|
user_db_data = await get_user(event_user.id)
|
|
if user_db_data and user_db_data[
|
|
'language_code'] and user_db_data[
|
|
'language_code'] in self.i18n.locales_data:
|
|
current_language = user_db_data['language_code']
|
|
logging.debug(
|
|
f"I18nMiddleware: User {event_user.id} language loaded from DB: {current_language}"
|
|
)
|
|
else:
|
|
|
|
if event_user.language_code:
|
|
lang_prefix = event_user.language_code.split('-')[0]
|
|
if lang_prefix in self.i18n.locales_data:
|
|
current_language = lang_prefix
|
|
logging.debug(
|
|
f"I18nMiddleware: User {event_user.id} language set from Telegram client (prefix): {current_language}"
|
|
)
|
|
elif event_user.language_code in self.i18n.locales_data:
|
|
current_language = event_user.language_code
|
|
logging.debug(
|
|
f"I18nMiddleware: User {event_user.id} language set from Telegram client (full): {current_language}"
|
|
)
|
|
else:
|
|
|
|
logging.debug(
|
|
f"I18nMiddleware: User {event_user.id} Telegram client language '{event_user.language_code}' not supported. Using default: {current_language}"
|
|
)
|
|
else:
|
|
logging.debug(
|
|
f"I18nMiddleware: User {event_user.id} has no language_code from Telegram. Using default: {current_language}"
|
|
)
|
|
except Exception as e_db_lang:
|
|
|
|
logging.error(
|
|
f"I18nMiddleware: Error fetching user language from DB for user {event_user.id}: {e_db_lang}. Falling back."
|
|
)
|
|
if event_user.language_code:
|
|
lang_prefix = event_user.language_code.split('-')[0]
|
|
if lang_prefix in self.i18n.locales_data:
|
|
current_language = lang_prefix
|
|
elif event_user.language_code in self.i18n.locales_data:
|
|
current_language = event_user.language_code
|
|
|
|
data["i18n_data"] = {
|
|
"i18n_instance": self.i18n,
|
|
"current_language": current_language
|
|
}
|
|
logging.debug(
|
|
f"I18nMiddleware: Final current_language for event: {current_language}"
|
|
)
|
|
|
|
return await handler(event, data)
|