Третий коммит, добавление share, share_kb, а также ADMIN_ID

This commit is contained in:
2025-07-22 13:50:14 +03:00
parent 849feb7beb
commit b98123f4dc
1479 changed files with 323549 additions and 11 deletions

View File

@@ -0,0 +1,21 @@
from .context import get_i18n, gettext, lazy_gettext, lazy_ngettext, ngettext
from .core import I18n
from .middleware import (
ConstI18nMiddleware,
FSMI18nMiddleware,
I18nMiddleware,
SimpleI18nMiddleware,
)
__all__ = (
"I18n",
"I18nMiddleware",
"SimpleI18nMiddleware",
"ConstI18nMiddleware",
"FSMI18nMiddleware",
"gettext",
"lazy_gettext",
"ngettext",
"lazy_ngettext",
"get_i18n",
)

View File

@@ -0,0 +1,23 @@
from typing import Any
from aiogram.utils.i18n.core import I18n
from aiogram.utils.i18n.lazy_proxy import LazyProxy
def get_i18n() -> I18n:
i18n = I18n.get_current(no_error=True)
if i18n is None:
raise LookupError("I18n context is not set")
return i18n
def gettext(*args: Any, **kwargs: Any) -> str:
return get_i18n().gettext(*args, **kwargs)
def lazy_gettext(*args: Any, **kwargs: Any) -> LazyProxy:
return LazyProxy(gettext, *args, **kwargs, enable_cache=False)
ngettext = gettext
lazy_ngettext = lazy_gettext

View File

@@ -0,0 +1,123 @@
import gettext
import os
from contextlib import contextmanager
from contextvars import ContextVar
from pathlib import Path
from typing import Dict, Generator, Optional, Tuple, Union
from aiogram.utils.i18n.lazy_proxy import LazyProxy
from aiogram.utils.mixins import ContextInstanceMixin
class I18n(ContextInstanceMixin["I18n"]):
def __init__(
self,
*,
path: Union[str, Path],
default_locale: str = "en",
domain: str = "messages",
) -> None:
self.path = path
self.default_locale = default_locale
self.domain = domain
self.ctx_locale = ContextVar("aiogram_ctx_locale", default=default_locale)
self.locales = self.find_locales()
@property
def current_locale(self) -> str:
return self.ctx_locale.get()
@current_locale.setter
def current_locale(self, value: str) -> None:
self.ctx_locale.set(value)
@contextmanager
def use_locale(self, locale: str) -> Generator[None, None, None]:
"""
Create context with specified locale
"""
ctx_token = self.ctx_locale.set(locale)
try:
yield
finally:
self.ctx_locale.reset(ctx_token)
@contextmanager
def context(self) -> Generator["I18n", None, None]:
"""
Use I18n context
"""
token = self.set_current(self)
try:
yield self
finally:
self.reset_current(token)
def find_locales(self) -> Dict[str, gettext.GNUTranslations]:
"""
Load all compiled locales from path
:return: dict with locales
"""
translations: Dict[str, gettext.GNUTranslations] = {}
for name in os.listdir(self.path):
if not os.path.isdir(os.path.join(self.path, name)):
continue
mo_path = os.path.join(self.path, name, "LC_MESSAGES", self.domain + ".mo")
if os.path.exists(mo_path):
with open(mo_path, "rb") as fp:
translations[name] = gettext.GNUTranslations(fp)
elif os.path.exists(mo_path[:-2] + "po"): # pragma: no cover
raise RuntimeError(f"Found locale '{name}' but this language is not compiled!")
return translations
def reload(self) -> None:
"""
Hot reload locales
"""
self.locales = self.find_locales()
@property
def available_locales(self) -> Tuple[str, ...]:
"""
list of loaded locales
:return:
"""
return tuple(self.locales.keys())
def gettext(
self, singular: str, plural: Optional[str] = None, n: int = 1, locale: Optional[str] = None
) -> str:
"""
Get text
:param singular:
:param plural:
:param n:
:param locale:
:return:
"""
if locale is None:
locale = self.current_locale
if locale not in self.locales:
if n == 1:
return singular
return plural if plural else singular
translator = self.locales[locale]
if plural is None:
return translator.gettext(singular)
return translator.ngettext(singular, plural, n)
def lazy_gettext(
self, singular: str, plural: Optional[str] = None, n: int = 1, locale: Optional[str] = None
) -> LazyProxy:
return LazyProxy(
self.gettext, singular=singular, plural=plural, n=n, locale=locale, enable_cache=False
)

View File

@@ -0,0 +1,13 @@
from typing import Any
try:
from babel.support import LazyProxy
except ImportError: # pragma: no cover
class LazyProxy: # type: ignore
def __init__(self, func: Any, *args: Any, **kwargs: Any) -> None:
raise RuntimeError(
"LazyProxy can be used only when Babel installed\n"
"Just install Babel (`pip install Babel`) "
"or aiogram with i18n support (`pip install aiogram[i18n]`)"
)

View File

@@ -0,0 +1,187 @@
from abc import ABC, abstractmethod
from typing import Any, Awaitable, Callable, Dict, Optional, Set
try:
from babel import Locale, UnknownLocaleError
except ImportError: # pragma: no cover
Locale = None # type: ignore
class UnknownLocaleError(Exception): # type: ignore
pass
from aiogram import BaseMiddleware, Router
from aiogram.fsm.context import FSMContext
from aiogram.types import TelegramObject, User
from aiogram.utils.i18n.core import I18n
class I18nMiddleware(BaseMiddleware, ABC):
"""
Abstract I18n middleware.
"""
def __init__(
self,
i18n: I18n,
i18n_key: Optional[str] = "i18n",
middleware_key: str = "i18n_middleware",
) -> None:
"""
Create an instance of middleware
:param i18n: instance of I18n
:param i18n_key: context key for I18n instance
:param middleware_key: context key for this middleware
"""
self.i18n = i18n
self.i18n_key = i18n_key
self.middleware_key = middleware_key
def setup(
self: BaseMiddleware, router: Router, exclude: Optional[Set[str]] = None
) -> BaseMiddleware:
"""
Register middleware for all events in the Router
:param router:
:param exclude:
:return:
"""
if exclude is None:
exclude = set()
exclude_events = {"update", *exclude}
for event_name, observer in router.observers.items():
if event_name in exclude_events:
continue
observer.outer_middleware(self)
return self
async def __call__(
self,
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
event: TelegramObject,
data: Dict[str, Any],
) -> Any:
current_locale = await self.get_locale(event=event, data=data) or self.i18n.default_locale
if self.i18n_key:
data[self.i18n_key] = self.i18n
if self.middleware_key:
data[self.middleware_key] = self
with self.i18n.context(), self.i18n.use_locale(current_locale):
return await handler(event, data)
@abstractmethod
async def get_locale(self, event: TelegramObject, data: Dict[str, Any]) -> str:
"""
Detect current user locale based on event and context.
**This method must be defined in child classes**
:param event:
:param data:
:return:
"""
pass
class SimpleI18nMiddleware(I18nMiddleware):
"""
Simple I18n middleware.
Chooses language code from the User object received in event
"""
def __init__(
self,
i18n: I18n,
i18n_key: Optional[str] = "i18n",
middleware_key: str = "i18n_middleware",
) -> None:
super().__init__(i18n=i18n, i18n_key=i18n_key, middleware_key=middleware_key)
if Locale is None: # pragma: no cover
raise RuntimeError(
f"{type(self).__name__} can be used only when Babel installed\n"
"Just install Babel (`pip install Babel`) "
"or aiogram with i18n support (`pip install aiogram[i18n]`)"
)
async def get_locale(self, event: TelegramObject, data: Dict[str, Any]) -> str:
if Locale is None: # pragma: no cover
raise RuntimeError(
f"{type(self).__name__} can be used only when Babel installed\n"
"Just install Babel (`pip install Babel`) "
"or aiogram with i18n support (`pip install aiogram[i18n]`)"
)
event_from_user: Optional[User] = data.get("event_from_user", None)
if event_from_user is None or event_from_user.language_code is None:
return self.i18n.default_locale
try:
locale = Locale.parse(event_from_user.language_code, sep="-")
except UnknownLocaleError:
return self.i18n.default_locale
if locale.language not in self.i18n.available_locales:
return self.i18n.default_locale
return locale.language
class ConstI18nMiddleware(I18nMiddleware):
"""
Const middleware chooses statically defined locale
"""
def __init__(
self,
locale: str,
i18n: I18n,
i18n_key: Optional[str] = "i18n",
middleware_key: str = "i18n_middleware",
) -> None:
super().__init__(i18n=i18n, i18n_key=i18n_key, middleware_key=middleware_key)
self.locale = locale
async def get_locale(self, event: TelegramObject, data: Dict[str, Any]) -> str:
return self.locale
class FSMI18nMiddleware(SimpleI18nMiddleware):
"""
This middleware stores locale in the FSM storage
"""
def __init__(
self,
i18n: I18n,
key: str = "locale",
i18n_key: Optional[str] = "i18n",
middleware_key: str = "i18n_middleware",
) -> None:
super().__init__(i18n=i18n, i18n_key=i18n_key, middleware_key=middleware_key)
self.key = key
async def get_locale(self, event: TelegramObject, data: Dict[str, Any]) -> str:
fsm_context: Optional[FSMContext] = data.get("state")
locale = None
if fsm_context:
fsm_data = await fsm_context.get_data()
locale = fsm_data.get(self.key, None)
if not locale:
locale = await super().get_locale(event=event, data=data)
if fsm_context:
await fsm_context.update_data(data={self.key: locale})
return locale
async def set_locale(self, state: FSMContext, locale: str) -> None:
"""
Write new locale to the storage
:param state: instance of FSMContext
:param locale: new locale
"""
await state.update_data(data={self.key: locale})
self.i18n.current_locale = locale