Третий коммит, добавление share, share_kb, а также ADMIN_ID
This commit is contained in:
21
myenv/Lib/site-packages/aiogram/utils/i18n/__init__.py
Normal file
21
myenv/Lib/site-packages/aiogram/utils/i18n/__init__.py
Normal 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",
|
||||
)
|
23
myenv/Lib/site-packages/aiogram/utils/i18n/context.py
Normal file
23
myenv/Lib/site-packages/aiogram/utils/i18n/context.py
Normal 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
|
123
myenv/Lib/site-packages/aiogram/utils/i18n/core.py
Normal file
123
myenv/Lib/site-packages/aiogram/utils/i18n/core.py
Normal 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
|
||||
)
|
13
myenv/Lib/site-packages/aiogram/utils/i18n/lazy_proxy.py
Normal file
13
myenv/Lib/site-packages/aiogram/utils/i18n/lazy_proxy.py
Normal 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]`)"
|
||||
)
|
187
myenv/Lib/site-packages/aiogram/utils/i18n/middleware.py
Normal file
187
myenv/Lib/site-packages/aiogram/utils/i18n/middleware.py
Normal 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
|
Reference in New Issue
Block a user