Files
remnawave-minishop/bot/middlewares/i18n.py
T
2025-05-14 08:43:23 +00:00

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)