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 )