From 6b980d8017f6bb3161064179c86e3a2d0a22f451 Mon Sep 17 00:00:00 2001 From: geekiot Date: Sat, 8 Nov 2025 17:54:31 +0500 Subject: [PATCH 1/7] Add [localization]: add i18n middleware --- src/bot/middlewares/i18n.py | 38 +++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/bot/middlewares/i18n.py diff --git a/src/bot/middlewares/i18n.py b/src/bot/middlewares/i18n.py new file mode 100644 index 0000000..6d323e6 --- /dev/null +++ b/src/bot/middlewares/i18n.py @@ -0,0 +1,38 @@ +__all__ = ["I18nMiddleware"] + + +from typing import Any, Dict + +from aiogram.types import TelegramObject +from aiogram.utils.i18n import SimpleI18nMiddleware + +from database.models import TelegramUser + + +class I18nMiddleware(SimpleI18nMiddleware): + """ + Middleware for providing the user's language code. + Used for localizing bot messages. + """ + + async def get_locale( + self, + event: TelegramObject, + data: Dict[str, Any], + ) -> str: + """ + Retrieving the user's language code. + + Args: + event (TelegramObject): The incoming event. + data (Dict[str, Any]): Data for the event. + + Returns: + str: user's language code. + """ + telegram_user: TelegramUser | None = data.get("telegram_user") + + if telegram_user is None: + return self.i18n.default_locale + + return telegram_user.lang -- 2.47.3 From 8320ab805e84eb3afd35e0384caa65b3595a950a Mon Sep 17 00:00:00 2001 From: geekiot Date: Sat, 8 Nov 2025 17:57:16 +0500 Subject: [PATCH 2/7] Add [localization]: connect i18n middleware to the bot --- src/bot/__init__.py | 13 ++++++++++--- src/bot/middlewares/__init__.py | 7 +++++++ src/config/settings.py | 6 ++++++ src/main.py | 9 +++------ 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/bot/__init__.py b/src/bot/__init__.py index ec331db..ee72106 100644 --- a/src/bot/__init__.py +++ b/src/bot/__init__.py @@ -1,8 +1,11 @@ __all__ = ["start_bot"] +from pathlib import Path + from aiogram import Bot, Dispatcher from aiogram.fsm.storage.redis import RedisStorage +from aiogram.utils.i18n import I18n from loguru import logger as loguru_logger from redis.asyncio.client import Redis from sqlalchemy.ext.asyncio import async_sessionmaker @@ -16,6 +19,7 @@ async def start_bot( bot_token: str, redis_protocol: Redis, session_maker: async_sessionmaker, + i18n_domain: str, ) -> None: """ Start Telegram bot. @@ -24,6 +28,7 @@ async def start_bot( bot_token (str): Telegram API bot token. redis_client (Redis): async configured client for redis. session_maker (AsyncSession): async database session maker. + i18n_domain (str): the bot locales domain. """ bot = Bot(bot_token) @@ -31,13 +36,15 @@ async def start_bot( storage=RedisStorage(redis=redis_protocol), ) + locales_dir = Path(".") / "src" / "locales" + i18n = I18n(path=locales_dir, default_locale="ru", domain=i18n_domain) + connect_middlewares( dispatcher=dispatcher, session_maker=session_maker, + i18n=i18n, ) - loguru_logger.debug( - "Middlewares have been successfully connected." - ) + loguru_logger.debug("Middlewares have been successfully connected.") include_routers(dispatcher) loguru_logger.debug("Routers have been successfully included.") diff --git a/src/bot/middlewares/__init__.py b/src/bot/middlewares/__init__.py index e35d46d..5777346 100644 --- a/src/bot/middlewares/__init__.py +++ b/src/bot/middlewares/__init__.py @@ -2,9 +2,11 @@ __all__ = ["connect_middlewares"] from aiogram import Dispatcher +from aiogram.utils.i18n import I18n from sqlalchemy.ext.asyncio import async_sessionmaker from .database import DatabaseSessionMiddleware +from .i18n import I18nMiddleware from .registration import RegistrationMiddleware from .telegram_user import TelegramUserMiddleware @@ -12,6 +14,7 @@ from .telegram_user import TelegramUserMiddleware def connect_middlewares( dispatcher: Dispatcher, session_maker: async_sessionmaker, + i18n: I18n, ) -> None: """ Connecting all middleware for the dispatcher. @@ -19,10 +22,12 @@ def connect_middlewares( 1. DatabaseSessionMiddleware (outer): provides a session. 2. TelegramUserMiddleware (outer): uses session to get a user. 3. RegistrationMiddleware (outer): uses user to check status. + 4. I18nMiddleware (inter): uses session to get user's locale code. Args: dispatcher (Dispatcher): bot dispatcher. session_maker (async_sessionmaker): database sessionmaker. + i18n (I18n): aiogram bot i18n object. """ dispatcher.update.outer_middleware( DatabaseSessionMiddleware(session_maker=session_maker) @@ -31,3 +36,5 @@ def connect_middlewares( dispatcher.update.outer_middleware(TelegramUserMiddleware()) dispatcher.update.outer_middleware(RegistrationMiddleware()) + + dispatcher.update.middleware(I18nMiddleware(i18n=i18n)) diff --git a/src/config/settings.py b/src/config/settings.py index 6ebcf88..e016906 100644 --- a/src/config/settings.py +++ b/src/config/settings.py @@ -31,6 +31,10 @@ class Settings(BaseSettings): Enable SQLAlchemy engine echo. Defaults to True. + bot_i18n_domain (str): + The bot locales i18n domain. + Defaults to "messages". + redis_host (str): Redis host name. Defaults to "redis" @@ -70,6 +74,8 @@ class Settings(BaseSettings): f"{self.postgres_port}/{self.postgres_name}" ) + bot_i18n_domain: str = Field(frozen=True, default="messages") + redis_host: str = Field(frozen=True, default="redis") redis_port: int = Field(frozen=True, default=6379) diff --git a/src/main.py b/src/main.py index 3302862..565a9fa 100644 --- a/src/main.py +++ b/src/main.py @@ -34,9 +34,7 @@ async def process_args() -> None: args = parser.parse_args() - loguru_logger.debug( - f"List of arguments passed to the program: {args}" - ) + loguru_logger.debug(f"List of arguments passed to the program: {args}") match args.db: case "create": @@ -48,9 +46,7 @@ async def process_args() -> None: await drop_db(engine) exit(0) case "migrate": - loguru_logger.debug( - "Attempt to make database migrations..." - ) + loguru_logger.debug("Attempt to make database migrations...") exit(0) @@ -71,6 +67,7 @@ async def main() -> None: bot_token=settings.bot_token, redis_protocol=redis_protocol, session_maker=session_maker, + i18n_domain=settings.bot_i18n_domain, ) -- 2.47.3 From b63f60ea413b7a502cc66e94276e56746a269925 Mon Sep 17 00:00:00 2001 From: geekiot Date: Sat, 8 Nov 2025 17:57:47 +0500 Subject: [PATCH 3/7] Add [localization]: add localization to the bot messages --- src/bot/handlers/unverified/callbacks/menu.py | 13 +-- src/bot/handlers/unverified/commands/menu.py | 31 ++++-- src/bot/handlers/verified/callbacks/menu.py | 3 +- src/bot/handlers/verified/commands/help.py | 10 +- src/bot/handlers/verified/commands/menu.py | 3 +- src/bot/utils/keyboards/__init__.py | 16 +-- src/locales/en/LC_MESSAGES/messages.po | 102 +++++++++++++++++- src/locales/ru/LC_MESSAGES/messages.po | 102 +++++++++++++++++- 8 files changed, 250 insertions(+), 30 deletions(-) diff --git a/src/bot/handlers/unverified/callbacks/menu.py b/src/bot/handlers/unverified/callbacks/menu.py index b9e81ec..a900940 100644 --- a/src/bot/handlers/unverified/callbacks/menu.py +++ b/src/bot/handlers/unverified/callbacks/menu.py @@ -1,6 +1,7 @@ from aiogram import F from aiogram.fsm.context import FSMContext from aiogram.types import CallbackQuery, Message +from aiogram.utils.i18n import gettext as _ from sqlalchemy.ext.asyncio import AsyncSession from bot.handlers import unverified_registry @@ -28,7 +29,7 @@ async def call_start_verification( return await call.message.edit_text( - "Choose your role.", + _("verification choose role menu"), reply_markup=get_verification_role_markup(), ) @@ -61,12 +62,12 @@ async def call_change_verification_role( ) await state.set_state(VerificationStates.enter_full_name) await call.message.edit_text( - "Please enter your full name.", + _("verification enter full name message"), reply_markup=None, ) else: await call.message.edit_text( - text="Incorrect role. Try again.", + text=_("verification incorrect role error message"), reply_markup=None, ) @@ -86,7 +87,7 @@ async def call_reset_verification( await state.clear() await call.message.answer( - "Successfully reset verification.", + _("verification reset verification menu"), reply_markup=get_start_verification_markup(), ) @@ -119,12 +120,12 @@ async def call_finish_verification( if status == VerificationStatus.SUCCESSFULLY_BOUND: await call.message.edit_text( - "Sucessfully!", + _("verification successfully bound message"), reply_markup=None, ) return await call.message.edit_text( - f"Error: {status}", + _("verification status error template").format(status), reply_markup=get_start_verification_markup(), ) diff --git a/src/bot/handlers/unverified/commands/menu.py b/src/bot/handlers/unverified/commands/menu.py index 8242a93..0d92e10 100644 --- a/src/bot/handlers/unverified/commands/menu.py +++ b/src/bot/handlers/unverified/commands/menu.py @@ -1,5 +1,6 @@ from aiogram.fsm.context import FSMContext from aiogram.types import Message +from aiogram.utils.i18n import gettext as _ from bot.handlers import unverified_registry from bot.utils.keyboards import ( @@ -22,7 +23,9 @@ async def cmd_enter_verification_full_name( full_name = message.text if full_name is None: - await message.answer("Please, enter your full name again.") + await message.answer( + text=_("verification enter full name again message"), + ) return await state.update_data(full_name=full_name) @@ -31,8 +34,10 @@ async def cmd_enter_verification_full_name( data = await state.get_data() await message.answer( - "Next, enter your {}.".format( - "Student ID" if data["role"] == "student" else "Teacher ID" + text=_("verification enter self id template").format( + _("student id") + if data["role"] == "student" + else _("teacher id"), ) ) @@ -49,7 +54,9 @@ async def cmd_enter_verification_self_id( self_id = message.text if self_id is None or not self_id.isdigit(): - await message.answer("Please, enter your ID again.") + await message.answer( + text=_("verification enter your id again message"), + ) return await state.update_data(self_id=self_id) @@ -59,10 +66,14 @@ async def cmd_enter_verification_self_id( if role == "student": await state.set_state(VerificationStates.enter_group_id) - await message.answer("Next, enter your Group ID") + await message.answer( + text=_("verification enter your group id message"), + ) return - await message.answer("Temporaly Unavailable.") + await message.answer( + text=_("verification temporaly unavailable message"), + ) await state.clear() @@ -78,7 +89,9 @@ async def cmd_enter_verification_group_id( group_id = message.text if group_id is None: - await message.answer("Please, enter your ID again.") + await message.answer( + text=_("verification incorrect group id message"), + ) return await state.update_data(group_id=group_id) @@ -95,7 +108,7 @@ async def cmd_enter_verification_finish( message: Message, ) -> None: await message.answer( - text="Ok. Next?", + text=_("verification info enter end menu"), reply_markup=get_verification_finish_markup(), ) @@ -106,6 +119,6 @@ async def cmd_enter_verification_finish( ) async def msg_start_verification(message: Message) -> None: await message.answer( - "Hello, I don’t know you. I suggest we get to know each other.", + _("unverified user hello menu"), reply_markup=get_start_verification_markup(), ) diff --git a/src/bot/handlers/verified/callbacks/menu.py b/src/bot/handlers/verified/callbacks/menu.py index 65f9d1f..120e572 100644 --- a/src/bot/handlers/verified/callbacks/menu.py +++ b/src/bot/handlers/verified/callbacks/menu.py @@ -1,4 +1,5 @@ from aiogram.types import CallbackQuery, Message +from aiogram.utils.i18n import gettext as _ from bot.handlers import verified_registry from bot.utils.keyboards import get_menu_markup @@ -16,6 +17,6 @@ async def cmd_menu(call: CallbackQuery) -> None: return await call.message.edit_text( - "Menu Callback Verified Text", + text=_("university members call menu"), reply_markup=get_menu_markup(), ) diff --git a/src/bot/handlers/verified/commands/help.py b/src/bot/handlers/verified/commands/help.py index 49701c2..7a08331 100644 --- a/src/bot/handlers/verified/commands/help.py +++ b/src/bot/handlers/verified/commands/help.py @@ -1,4 +1,5 @@ from aiogram.types import Message +from aiogram.utils.i18n import gettext as _ from bot.handlers import verified_registry from bot.utils.types import ChatType @@ -10,10 +11,9 @@ from database import TelegramUser chat_types=ChatType.PRIVATE, description="Help Command Verified Description", ) -async def cmd_help( - message: Message, telegram_user: TelegramUser -) -> None: +async def cmd_help(message: Message, telegram_user: TelegramUser) -> None: await message.answer( - "Help Command Verified Text. " - f"Your locale is {telegram_user.lang}" + text=_("help command check user lang template").format( + telegram_user.lang, + ), ) diff --git a/src/bot/handlers/verified/commands/menu.py b/src/bot/handlers/verified/commands/menu.py index 6d2db9a..b250809 100644 --- a/src/bot/handlers/verified/commands/menu.py +++ b/src/bot/handlers/verified/commands/menu.py @@ -1,4 +1,5 @@ from aiogram.types import Message +from aiogram.utils.i18n import gettext as _ from bot.handlers import verified_registry from bot.utils.keyboards import get_menu_markup @@ -12,6 +13,6 @@ from bot.utils.types import ChatType ) async def cmd_menu(message: Message) -> None: await message.answer( - "Menu Command Verified Text", + text=_("university members command menu"), reply_markup=get_menu_markup(), ) diff --git a/src/bot/utils/keyboards/__init__.py b/src/bot/utils/keyboards/__init__.py index 0b07776..d1df109 100644 --- a/src/bot/utils/keyboards/__init__.py +++ b/src/bot/utils/keyboards/__init__.py @@ -6,6 +6,7 @@ __all__ = [ ] +from aiogram.utils.i18n import gettext as _ from aiogram.utils.keyboard import ( InlineKeyboardBuilder, InlineKeyboardButton, @@ -20,7 +21,7 @@ def get_start_verification_markup() -> InlineKeyboardMarkup: builder = InlineKeyboardBuilder() builder.add( InlineKeyboardButton( - text="Start Verification", + text=_("start verification button"), callback_data="verification_start", ) ) @@ -31,11 +32,11 @@ def get_verification_role_markup() -> InlineKeyboardMarkup: builder = InlineKeyboardBuilder() builder.add( InlineKeyboardButton( - text="Student", + text=_("student"), callback_data="verification_role_student", ), InlineKeyboardButton( - text="Teacher", + text=_("teacher"), callback_data="verification_role_teacher", ), ) @@ -47,11 +48,11 @@ def get_verification_finish_markup() -> InlineKeyboardMarkup: builder = InlineKeyboardBuilder() builder.add( InlineKeyboardButton( - text="Finish", + text=_("verification finish button"), callback_data="verification_finish", ), InlineKeyboardButton( - text="Reset", + text=_("verification reset button"), callback_data="verification_reset", ), ) @@ -65,6 +66,9 @@ def get_menu_markup() -> InlineKeyboardMarkup: """ builder = InlineKeyboardBuilder() builder.add( - InlineKeyboardButton(text="Test Menu", callback_data="menu"), + InlineKeyboardButton( + text=_("verified menu button"), + callback_data="menu", + ), ) return builder.as_markup() diff --git a/src/locales/en/LC_MESSAGES/messages.po b/src/locales/en/LC_MESSAGES/messages.po index a9eb3c9..c82d5a4 100644 --- a/src/locales/en/LC_MESSAGES/messages.po +++ b/src/locales/en/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2025-10-19 17:13+0500\n" +"POT-Creation-Date: 2025-11-08 17:38+0500\n" "PO-Revision-Date: 2025-10-19 17:54+0500\n" "Last-Translator: FULL NAME \n" "Language: en\n" @@ -18,3 +18,103 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.17.0\n" +#: src/bot/handlers/unverified/callbacks/menu.py:32 +msgid "verification choose role menu" +msgstr "" + +#: src/bot/handlers/unverified/callbacks/menu.py:65 +msgid "verification enter full name message" +msgstr "" + +#: src/bot/handlers/unverified/callbacks/menu.py:70 +msgid "verification incorrect role error message" +msgstr "" + +#: src/bot/handlers/unverified/callbacks/menu.py:90 +msgid "verification reset verification menu" +msgstr "" + +#: src/bot/handlers/unverified/callbacks/menu.py:123 +msgid "verification successfully bound message" +msgstr "" + +#: src/bot/handlers/unverified/callbacks/menu.py:129 +msgid "verification status error template" +msgstr "" + +#: src/bot/handlers/unverified/commands/menu.py:27 +msgid "verification enter full name again message" +msgstr "" + +#: src/bot/handlers/unverified/commands/menu.py:37 +msgid "verification enter self id template" +msgstr "" + +#: src/bot/handlers/unverified/commands/menu.py:38 +msgid "student id" +msgstr "" + +#: src/bot/handlers/unverified/commands/menu.py:40 +msgid "teacher id" +msgstr "" + +#: src/bot/handlers/unverified/commands/menu.py:58 +msgid "verification enter your id again message" +msgstr "" + +#: src/bot/handlers/unverified/commands/menu.py:70 +msgid "verification enter your group id message" +msgstr "" + +#: src/bot/handlers/unverified/commands/menu.py:75 +msgid "verification temporaly unavailable message" +msgstr "" + +#: src/bot/handlers/unverified/commands/menu.py:93 +msgid "verification incorrect group id message" +msgstr "" + +#: src/bot/handlers/unverified/commands/menu.py:111 +msgid "verification info enter end menu" +msgstr "" + +#: src/bot/handlers/unverified/commands/menu.py:122 +msgid "unverified user hello menu" +msgstr "" + +#: src/bot/handlers/verified/callbacks/menu.py:20 +msgid "university members call menu" +msgstr "" + +#: src/bot/handlers/verified/commands/help.py:16 +msgid "help command check user lang template" +msgstr "" + +#: src/bot/handlers/verified/commands/menu.py:16 +msgid "university members command menu" +msgstr "" + +#: src/bot/utils/keyboards/__init__.py:24 +msgid "start verification button" +msgstr "" + +#: src/bot/utils/keyboards/__init__.py:35 +msgid "student" +msgstr "" + +#: src/bot/utils/keyboards/__init__.py:39 +msgid "teacher" +msgstr "" + +#: src/bot/utils/keyboards/__init__.py:51 +msgid "verification finish button" +msgstr "" + +#: src/bot/utils/keyboards/__init__.py:55 +msgid "verification reset button" +msgstr "" + +#: src/bot/utils/keyboards/__init__.py:70 +msgid "verified menu button" +msgstr "" + diff --git a/src/locales/ru/LC_MESSAGES/messages.po b/src/locales/ru/LC_MESSAGES/messages.po index a467032..ea4ffc9 100644 --- a/src/locales/ru/LC_MESSAGES/messages.po +++ b/src/locales/ru/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2025-10-19 17:13+0500\n" +"POT-Creation-Date: 2025-11-08 17:38+0500\n" "PO-Revision-Date: 2025-10-19 15:07+0500\n" "Last-Translator: FULL NAME \n" "Language: ru\n" @@ -19,3 +19,103 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.17.0\n" +#: src/bot/handlers/unverified/callbacks/menu.py:32 +msgid "verification choose role menu" +msgstr "" + +#: src/bot/handlers/unverified/callbacks/menu.py:65 +msgid "verification enter full name message" +msgstr "" + +#: src/bot/handlers/unverified/callbacks/menu.py:70 +msgid "verification incorrect role error message" +msgstr "" + +#: src/bot/handlers/unverified/callbacks/menu.py:90 +msgid "verification reset verification menu" +msgstr "" + +#: src/bot/handlers/unverified/callbacks/menu.py:123 +msgid "verification successfully bound message" +msgstr "" + +#: src/bot/handlers/unverified/callbacks/menu.py:129 +msgid "verification status error template" +msgstr "" + +#: src/bot/handlers/unverified/commands/menu.py:27 +msgid "verification enter full name again message" +msgstr "" + +#: src/bot/handlers/unverified/commands/menu.py:37 +msgid "verification enter self id template" +msgstr "" + +#: src/bot/handlers/unverified/commands/menu.py:38 +msgid "student id" +msgstr "" + +#: src/bot/handlers/unverified/commands/menu.py:40 +msgid "teacher id" +msgstr "" + +#: src/bot/handlers/unverified/commands/menu.py:58 +msgid "verification enter your id again message" +msgstr "" + +#: src/bot/handlers/unverified/commands/menu.py:70 +msgid "verification enter your group id message" +msgstr "" + +#: src/bot/handlers/unverified/commands/menu.py:75 +msgid "verification temporaly unavailable message" +msgstr "" + +#: src/bot/handlers/unverified/commands/menu.py:93 +msgid "verification incorrect group id message" +msgstr "" + +#: src/bot/handlers/unverified/commands/menu.py:111 +msgid "verification info enter end menu" +msgstr "" + +#: src/bot/handlers/unverified/commands/menu.py:122 +msgid "unverified user hello menu" +msgstr "" + +#: src/bot/handlers/verified/callbacks/menu.py:20 +msgid "university members call menu" +msgstr "" + +#: src/bot/handlers/verified/commands/help.py:16 +msgid "help command check user lang template" +msgstr "" + +#: src/bot/handlers/verified/commands/menu.py:16 +msgid "university members command menu" +msgstr "" + +#: src/bot/utils/keyboards/__init__.py:24 +msgid "start verification button" +msgstr "" + +#: src/bot/utils/keyboards/__init__.py:35 +msgid "student" +msgstr "" + +#: src/bot/utils/keyboards/__init__.py:39 +msgid "teacher" +msgstr "" + +#: src/bot/utils/keyboards/__init__.py:51 +msgid "verification finish button" +msgstr "" + +#: src/bot/utils/keyboards/__init__.py:55 +msgid "verification reset button" +msgstr "" + +#: src/bot/utils/keyboards/__init__.py:70 +msgid "verified menu button" +msgstr "" + -- 2.47.3 From 94ef0845f71b015c8d9dfe2b8774c2f8b41c9d05 Mon Sep 17 00:00:00 2001 From: geekiot Date: Sat, 8 Nov 2025 18:37:34 +0500 Subject: [PATCH 4/7] Add [localization]: add ru locale --- src/locales/en/LC_MESSAGES/messages.po | 2 +- src/locales/ru/LC_MESSAGES/messages.po | 53 +++++++++++++------------- 2 files changed, 27 insertions(+), 28 deletions(-) diff --git a/src/locales/en/LC_MESSAGES/messages.po b/src/locales/en/LC_MESSAGES/messages.po index c82d5a4..e3667b5 100644 --- a/src/locales/en/LC_MESSAGES/messages.po +++ b/src/locales/en/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2025-11-08 17:38+0500\n" +"POT-Creation-Date: 2025-11-08 17:57+0500\n" "PO-Revision-Date: 2025-10-19 17:54+0500\n" "Last-Translator: FULL NAME \n" "Language: en\n" diff --git a/src/locales/ru/LC_MESSAGES/messages.po b/src/locales/ru/LC_MESSAGES/messages.po index ea4ffc9..2d6d3d9 100644 --- a/src/locales/ru/LC_MESSAGES/messages.po +++ b/src/locales/ru/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2025-11-08 17:38+0500\n" +"POT-Creation-Date: 2025-11-08 17:57+0500\n" "PO-Revision-Date: 2025-10-19 15:07+0500\n" "Last-Translator: FULL NAME \n" "Language: ru\n" @@ -21,101 +21,100 @@ msgstr "" #: src/bot/handlers/unverified/callbacks/menu.py:32 msgid "verification choose role menu" -msgstr "" +msgstr "Выберите вашу роль в УрФУ." #: src/bot/handlers/unverified/callbacks/menu.py:65 msgid "verification enter full name message" -msgstr "" +msgstr "Теперь введите ваше ФИО." #: src/bot/handlers/unverified/callbacks/menu.py:70 msgid "verification incorrect role error message" -msgstr "" +msgstr "Неправильно выбранная роль!" #: src/bot/handlers/unverified/callbacks/menu.py:90 msgid "verification reset verification menu" -msgstr "" +msgstr "Процесс верификации был отменен." #: src/bot/handlers/unverified/callbacks/menu.py:123 msgid "verification successfully bound message" -msgstr "" +msgstr "Верификация успешно завершена.\nТеперь вы можете пользоваться меню - /menu" #: src/bot/handlers/unverified/callbacks/menu.py:129 msgid "verification status error template" -msgstr "" +msgstr "Не удалось завершить верификацию.\nОшибка: {}" #: src/bot/handlers/unverified/commands/menu.py:27 msgid "verification enter full name again message" -msgstr "" +msgstr "Введите ваше ФИО, пожалуйста." #: src/bot/handlers/unverified/commands/menu.py:37 msgid "verification enter self id template" -msgstr "" +msgstr "Пожалуйста, введите ваш {}." #: src/bot/handlers/unverified/commands/menu.py:38 msgid "student id" -msgstr "" +msgstr "номер вашего студенческого" #: src/bot/handlers/unverified/commands/menu.py:40 msgid "teacher id" -msgstr "" +msgstr "табельный номер" #: src/bot/handlers/unverified/commands/menu.py:58 msgid "verification enter your id again message" -msgstr "" +msgstr "Повторите ввод вашего номера." #: src/bot/handlers/unverified/commands/menu.py:70 msgid "verification enter your group id message" -msgstr "" +msgstr "Введите номер вашей группы." #: src/bot/handlers/unverified/commands/menu.py:75 msgid "verification temporaly unavailable message" -msgstr "" +msgstr "Извините, но данная верификация пока недоступна." #: src/bot/handlers/unverified/commands/menu.py:93 msgid "verification incorrect group id message" -msgstr "" +msgstr "Пожалуйста, введите корректный номер группы." #: src/bot/handlers/unverified/commands/menu.py:111 msgid "verification info enter end menu" -msgstr "" +msgstr "Вы успешно ввели все необходимые данные.\nЧто теперь?" #: src/bot/handlers/unverified/commands/menu.py:122 msgid "unverified user hello menu" -msgstr "" +msgstr "Привет-привет. Кажется мы с тобой раньше не видились. Или к твой Telegram аккаунт не связан с преподавателем/студентом УрФУ." #: src/bot/handlers/verified/callbacks/menu.py:20 msgid "university members call menu" -msgstr "" +msgstr "Вызов этого меню доступен только для подтвержденных пользователей." #: src/bot/handlers/verified/commands/help.py:16 msgid "help command check user lang template" -msgstr "" +msgstr "Это тестовое меню для помощи.\nЯ помогать пока не могу, но могу сказать, какой у тебя язык: {}" #: src/bot/handlers/verified/commands/menu.py:16 msgid "university members command menu" -msgstr "" +msgstr "Это меню доступно только для подтверженных пользователей." #: src/bot/utils/keyboards/__init__.py:24 msgid "start verification button" -msgstr "" +msgstr "Начать верификацию" #: src/bot/utils/keyboards/__init__.py:35 msgid "student" -msgstr "" +msgstr "Студент" #: src/bot/utils/keyboards/__init__.py:39 msgid "teacher" -msgstr "" +msgstr "Преподаватель" #: src/bot/utils/keyboards/__init__.py:51 msgid "verification finish button" -msgstr "" +msgstr "Завершить" #: src/bot/utils/keyboards/__init__.py:55 msgid "verification reset button" -msgstr "" +msgstr "Отмена" #: src/bot/utils/keyboards/__init__.py:70 msgid "verified menu button" -msgstr "" - +msgstr "Меню" -- 2.47.3 From 77d65b9d7372a34d3d2ffbf52786bf99b2bacf54 Mon Sep 17 00:00:00 2001 From: geekiot Date: Sat, 8 Nov 2025 18:56:31 +0500 Subject: [PATCH 5/7] Add [localization]: add menu to choose language --- .../handlers/unverified/callbacks/language.py | 58 +++++++++++++++++++ src/bot/utils/keyboards/__init__.py | 25 +++++++- src/bot/utils/registry/__init__.py | 2 +- 3 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 src/bot/handlers/unverified/callbacks/language.py diff --git a/src/bot/handlers/unverified/callbacks/language.py b/src/bot/handlers/unverified/callbacks/language.py new file mode 100644 index 0000000..8f2d2f4 --- /dev/null +++ b/src/bot/handlers/unverified/callbacks/language.py @@ -0,0 +1,58 @@ +from aiogram import F +from aiogram.types import CallbackQuery, Message +from aiogram.utils.i18n import gettext as _ +from sqlalchemy.ext.asyncio import AsyncSession + +from bot.handlers import unverified_registry +from bot.utils.keyboards import get_language_choose_markup +from bot.utils.types import ChatType +from database import TelegramUser + + +@unverified_registry.register( + triggers="choose_language", + description="Choose language callback menu", + chat_types=ChatType.PRIVATE, + is_callback=True, +) +async def call_choose_language( + call: CallbackQuery, +) -> None: + if not isinstance(call.message, Message): + return + + await call.message.edit_text( + text=_("unverified choose language menu"), + reply_markup=get_language_choose_markup(), + ) + + +@unverified_registry.register( + filters=F.data.startswith("choose_language_"), # pyrefly: ignore + description="Choose language callback menu", + chat_types=ChatType.PRIVATE, + is_callback=True, +) +async def call_choosed_language( + call: CallbackQuery, + telegram_user: TelegramUser, + session: AsyncSession, +) -> None: + if not isinstance(call.message, Message) or call.data is None: + return + + code = call.data.split("_")[-1] + + telegram_user.lang = code + + await session.commit() + + await call.message.edit_text( + text=_( + "unverified choose language successfully template", + locale=code, + ).format( + code, + ), + reply_markup=None, + ) diff --git a/src/bot/utils/keyboards/__init__.py b/src/bot/utils/keyboards/__init__.py index d1df109..ee2b341 100644 --- a/src/bot/utils/keyboards/__init__.py +++ b/src/bot/utils/keyboards/__init__.py @@ -1,5 +1,6 @@ __all__ = [ "get_start_verification_markup", + "get_language_choose_markup", "get_verification_role_markup", "get_verification_finish_markup", "get_menu_markup", @@ -23,8 +24,30 @@ def get_start_verification_markup() -> InlineKeyboardMarkup: InlineKeyboardButton( text=_("start verification button"), callback_data="verification_start", - ) + ), + InlineKeyboardButton( + text=_("choose language button"), + callback_data="choose_language", + ), ) + builder.adjust(1) + return builder.as_markup() + + +def get_language_choose_markup() -> InlineKeyboardMarkup: + builder = InlineKeyboardBuilder() + + builder.add( + InlineKeyboardButton( + text="en", callback_data="choose_language_en" + ), + InlineKeyboardButton( + text="ru", callback_data="choose_language_ru" + ), + ) + + builder.adjust(2) + return builder.as_markup() diff --git a/src/bot/utils/registry/__init__.py b/src/bot/utils/registry/__init__.py index fd2bef3..159ed4e 100644 --- a/src/bot/utils/registry/__init__.py +++ b/src/bot/utils/registry/__init__.py @@ -103,7 +103,7 @@ class RouterRegistry: if isinstance(chat_types, ChatType): chat_types = [chat_types] - if isinstance(filters, (Filter, State)): + if isinstance(filters, (Filter, State, MagicFilter)): filters = [filters] elif filters is None: filters = list() -- 2.47.3 From 30715bbafb7ff3815484964c7368c78f9ced8f7e Mon Sep 17 00:00:00 2001 From: geekiot Date: Sat, 8 Nov 2025 18:57:08 +0500 Subject: [PATCH 6/7] Update [localization]: update locales & add new ru phrases --- src/locales/en/LC_MESSAGES/messages.po | 26 ++++++++++---- src/locales/ru/LC_MESSAGES/messages.po | 48 +++++++++++++++++++------- 2 files changed, 54 insertions(+), 20 deletions(-) diff --git a/src/locales/en/LC_MESSAGES/messages.po b/src/locales/en/LC_MESSAGES/messages.po index e3667b5..8488ffb 100644 --- a/src/locales/en/LC_MESSAGES/messages.po +++ b/src/locales/en/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2025-11-08 17:57+0500\n" +"POT-Creation-Date: 2025-11-08 18:50+0500\n" "PO-Revision-Date: 2025-10-19 17:54+0500\n" "Last-Translator: FULL NAME \n" "Language: en\n" @@ -18,6 +18,14 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.17.0\n" +#: src/bot/handlers/unverified/callbacks/language.py:25 +msgid "unverified choose language menu" +msgstr "" + +#: src/bot/handlers/unverified/callbacks/language.py:52 +msgid "unverified choose language successfully template" +msgstr "" + #: src/bot/handlers/unverified/callbacks/menu.py:32 msgid "verification choose role menu" msgstr "" @@ -94,27 +102,31 @@ msgstr "" msgid "university members command menu" msgstr "" -#: src/bot/utils/keyboards/__init__.py:24 +#: src/bot/utils/keyboards/__init__.py:25 msgid "start verification button" msgstr "" -#: src/bot/utils/keyboards/__init__.py:35 +#: src/bot/utils/keyboards/__init__.py:29 +msgid "choose language button" +msgstr "" + +#: src/bot/utils/keyboards/__init__.py:58 msgid "student" msgstr "" -#: src/bot/utils/keyboards/__init__.py:39 +#: src/bot/utils/keyboards/__init__.py:62 msgid "teacher" msgstr "" -#: src/bot/utils/keyboards/__init__.py:51 +#: src/bot/utils/keyboards/__init__.py:74 msgid "verification finish button" msgstr "" -#: src/bot/utils/keyboards/__init__.py:55 +#: src/bot/utils/keyboards/__init__.py:78 msgid "verification reset button" msgstr "" -#: src/bot/utils/keyboards/__init__.py:70 +#: src/bot/utils/keyboards/__init__.py:93 msgid "verified menu button" msgstr "" diff --git a/src/locales/ru/LC_MESSAGES/messages.po b/src/locales/ru/LC_MESSAGES/messages.po index 2d6d3d9..9fcbf80 100644 --- a/src/locales/ru/LC_MESSAGES/messages.po +++ b/src/locales/ru/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2025-11-08 17:57+0500\n" +"POT-Creation-Date: 2025-11-08 18:50+0500\n" "PO-Revision-Date: 2025-10-19 15:07+0500\n" "Last-Translator: FULL NAME \n" "Language: ru\n" @@ -19,6 +19,14 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.17.0\n" +#: src/bot/handlers/unverified/callbacks/language.py:25 +msgid "unverified choose language menu" +msgstr "Добро пожаловать в меню выбора языка." + +#: src/bot/handlers/unverified/callbacks/language.py:52 +msgid "unverified choose language successfully template" +msgstr "Язык успешно выбран: {}" + #: src/bot/handlers/unverified/callbacks/menu.py:32 msgid "verification choose role menu" msgstr "Выберите вашу роль в УрФУ." @@ -37,11 +45,15 @@ msgstr "Процесс верификации был отменен." #: src/bot/handlers/unverified/callbacks/menu.py:123 msgid "verification successfully bound message" -msgstr "Верификация успешно завершена.\nТеперь вы можете пользоваться меню - /menu" +msgstr "" +"Верификация успешно завершена.\n" +"Теперь вы можете пользоваться меню - /menu" #: src/bot/handlers/unverified/callbacks/menu.py:129 msgid "verification status error template" -msgstr "Не удалось завершить верификацию.\nОшибка: {}" +msgstr "" +"Не удалось завершить верификацию.\n" +"Ошибка: {}" #: src/bot/handlers/unverified/commands/menu.py:27 msgid "verification enter full name again message" @@ -53,7 +65,7 @@ msgstr "Пожалуйста, введите ваш {}." #: src/bot/handlers/unverified/commands/menu.py:38 msgid "student id" -msgstr "номер вашего студенческого" +msgstr "номер студенческого" #: src/bot/handlers/unverified/commands/menu.py:40 msgid "teacher id" @@ -77,11 +89,15 @@ msgstr "Пожалуйста, введите корректный номер г #: src/bot/handlers/unverified/commands/menu.py:111 msgid "verification info enter end menu" -msgstr "Вы успешно ввели все необходимые данные.\nЧто теперь?" +msgstr "" +"Вы успешно ввели все необходимые данные.\n" +"Что теперь?" #: src/bot/handlers/unverified/commands/menu.py:122 msgid "unverified user hello menu" -msgstr "Привет-привет. Кажется мы с тобой раньше не видились. Или к твой Telegram аккаунт не связан с преподавателем/студентом УрФУ." +msgstr "" +"Привет-привет. Кажется мы с тобой раньше не видились. Или к твой Telegram " +"аккаунт не связан с преподавателем/студентом УрФУ." #: src/bot/handlers/verified/callbacks/menu.py:20 msgid "university members call menu" @@ -89,32 +105,38 @@ msgstr "Вызов этого меню доступен только для по #: src/bot/handlers/verified/commands/help.py:16 msgid "help command check user lang template" -msgstr "Это тестовое меню для помощи.\nЯ помогать пока не могу, но могу сказать, какой у тебя язык: {}" +msgstr "" +"Это тестовое меню для помощи.\n" +"Я помогать пока не могу, но могу сказать, какой у тебя язык: {}" #: src/bot/handlers/verified/commands/menu.py:16 msgid "university members command menu" msgstr "Это меню доступно только для подтверженных пользователей." -#: src/bot/utils/keyboards/__init__.py:24 +#: src/bot/utils/keyboards/__init__.py:25 msgid "start verification button" msgstr "Начать верификацию" -#: src/bot/utils/keyboards/__init__.py:35 +#: src/bot/utils/keyboards/__init__.py:29 +msgid "choose language button" +msgstr "Выбрать язык" + +#: src/bot/utils/keyboards/__init__.py:58 msgid "student" msgstr "Студент" -#: src/bot/utils/keyboards/__init__.py:39 +#: src/bot/utils/keyboards/__init__.py:62 msgid "teacher" msgstr "Преподаватель" -#: src/bot/utils/keyboards/__init__.py:51 +#: src/bot/utils/keyboards/__init__.py:74 msgid "verification finish button" msgstr "Завершить" -#: src/bot/utils/keyboards/__init__.py:55 +#: src/bot/utils/keyboards/__init__.py:78 msgid "verification reset button" msgstr "Отмена" -#: src/bot/utils/keyboards/__init__.py:70 +#: src/bot/utils/keyboards/__init__.py:93 msgid "verified menu button" msgstr "Меню" -- 2.47.3 From a62e7d20b250b520292c8ad88abcf2f8c092d43d Mon Sep 17 00:00:00 2001 From: geekiot Date: Sun, 9 Nov 2025 12:03:00 +0500 Subject: [PATCH 7/7] Add [localization]: add autogenerated locale keyboard --- src/bot/__init__.py | 1 + .../handlers/unverified/callbacks/language.py | 24 +++++++++++++++---- src/bot/utils/keyboards/__init__.py | 16 ++++--------- src/locales/en/LC_MESSAGES/messages.po | 20 +++++++++------- src/locales/ru/LC_MESSAGES/messages.po | 20 +++++++++------- 5 files changed, 49 insertions(+), 32 deletions(-) diff --git a/src/bot/__init__.py b/src/bot/__init__.py index ee72106..0a264b2 100644 --- a/src/bot/__init__.py +++ b/src/bot/__init__.py @@ -38,6 +38,7 @@ async def start_bot( locales_dir = Path(".") / "src" / "locales" i18n = I18n(path=locales_dir, default_locale="ru", domain=i18n_domain) + loguru_logger.debug(f"I18n locales: {i18n.locales.keys()}") connect_middlewares( dispatcher=dispatcher, diff --git a/src/bot/handlers/unverified/callbacks/language.py b/src/bot/handlers/unverified/callbacks/language.py index 8f2d2f4..c86fc8c 100644 --- a/src/bot/handlers/unverified/callbacks/language.py +++ b/src/bot/handlers/unverified/callbacks/language.py @@ -1,5 +1,6 @@ from aiogram import F from aiogram.types import CallbackQuery, Message +from aiogram.utils.i18n import I18n from aiogram.utils.i18n import gettext as _ from sqlalchemy.ext.asyncio import AsyncSession @@ -10,39 +11,52 @@ from database import TelegramUser @unverified_registry.register( - triggers="choose_language", + triggers="choose_locale", description="Choose language callback menu", chat_types=ChatType.PRIVATE, is_callback=True, ) -async def call_choose_language( +async def call_choose_locale( call: CallbackQuery, + i18n: I18n, ) -> None: if not isinstance(call.message, Message): return + locales = tuple(i18n.locales.keys()) + await call.message.edit_text( text=_("unverified choose language menu"), - reply_markup=get_language_choose_markup(), + reply_markup=get_language_choose_markup(locales), ) @unverified_registry.register( - filters=F.data.startswith("choose_language_"), # pyrefly: ignore + filters=F.data.startswith("choose_locale_"), # pyrefly: ignore description="Choose language callback menu", chat_types=ChatType.PRIVATE, is_callback=True, ) -async def call_choosed_language( +async def call_change_to_choosed_locale( call: CallbackQuery, telegram_user: TelegramUser, session: AsyncSession, + i18n: I18n, ) -> None: if not isinstance(call.message, Message) or call.data is None: return code = call.data.split("_")[-1] + locales = tuple(i18n.locales.keys()) + + if code not in locales: + await call.message.edit_text( + text=_("incorrect locale code"), + reply_markup=get_language_choose_markup(locales), + ) + return + telegram_user.lang = code await session.commit() diff --git a/src/bot/utils/keyboards/__init__.py b/src/bot/utils/keyboards/__init__.py index ee2b341..6a1181f 100644 --- a/src/bot/utils/keyboards/__init__.py +++ b/src/bot/utils/keyboards/__init__.py @@ -27,26 +27,20 @@ def get_start_verification_markup() -> InlineKeyboardMarkup: ), InlineKeyboardButton( text=_("choose language button"), - callback_data="choose_language", + callback_data="choose_locale", ), ) builder.adjust(1) return builder.as_markup() -def get_language_choose_markup() -> InlineKeyboardMarkup: +def get_language_choose_markup(locales: tuple) -> InlineKeyboardMarkup: builder = InlineKeyboardBuilder() - builder.add( - InlineKeyboardButton( - text="en", callback_data="choose_language_en" - ), - InlineKeyboardButton( - text="ru", callback_data="choose_language_ru" - ), - ) + for code in locales: + builder.button(text=code, callback_data=f"choose_locale_{code}") - builder.adjust(2) + builder.adjust(3) return builder.as_markup() diff --git a/src/locales/en/LC_MESSAGES/messages.po b/src/locales/en/LC_MESSAGES/messages.po index 8488ffb..3765f7a 100644 --- a/src/locales/en/LC_MESSAGES/messages.po +++ b/src/locales/en/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2025-11-08 18:50+0500\n" +"POT-Creation-Date: 2025-11-09 11:56+0500\n" "PO-Revision-Date: 2025-10-19 17:54+0500\n" "Last-Translator: FULL NAME \n" "Language: en\n" @@ -18,11 +18,15 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.17.0\n" -#: src/bot/handlers/unverified/callbacks/language.py:25 +#: src/bot/handlers/unverified/callbacks/language.py:29 msgid "unverified choose language menu" msgstr "" -#: src/bot/handlers/unverified/callbacks/language.py:52 +#: src/bot/handlers/unverified/callbacks/language.py:55 +msgid "incorrect locale code" +msgstr "" + +#: src/bot/handlers/unverified/callbacks/language.py:66 msgid "unverified choose language successfully template" msgstr "" @@ -110,23 +114,23 @@ msgstr "" msgid "choose language button" msgstr "" -#: src/bot/utils/keyboards/__init__.py:58 +#: src/bot/utils/keyboards/__init__.py:52 msgid "student" msgstr "" -#: src/bot/utils/keyboards/__init__.py:62 +#: src/bot/utils/keyboards/__init__.py:56 msgid "teacher" msgstr "" -#: src/bot/utils/keyboards/__init__.py:74 +#: src/bot/utils/keyboards/__init__.py:68 msgid "verification finish button" msgstr "" -#: src/bot/utils/keyboards/__init__.py:78 +#: src/bot/utils/keyboards/__init__.py:72 msgid "verification reset button" msgstr "" -#: src/bot/utils/keyboards/__init__.py:93 +#: src/bot/utils/keyboards/__init__.py:87 msgid "verified menu button" msgstr "" diff --git a/src/locales/ru/LC_MESSAGES/messages.po b/src/locales/ru/LC_MESSAGES/messages.po index 9fcbf80..289eba8 100644 --- a/src/locales/ru/LC_MESSAGES/messages.po +++ b/src/locales/ru/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2025-11-08 18:50+0500\n" +"POT-Creation-Date: 2025-11-09 11:56+0500\n" "PO-Revision-Date: 2025-10-19 15:07+0500\n" "Last-Translator: FULL NAME \n" "Language: ru\n" @@ -19,11 +19,15 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.17.0\n" -#: src/bot/handlers/unverified/callbacks/language.py:25 +#: src/bot/handlers/unverified/callbacks/language.py:29 msgid "unverified choose language menu" msgstr "Добро пожаловать в меню выбора языка." -#: src/bot/handlers/unverified/callbacks/language.py:52 +#: src/bot/handlers/unverified/callbacks/language.py:55 +msgid "incorrect locale code" +msgstr "Неподдерживаемый язык! Пожалуйста, попробуйте ещё раз." + +#: src/bot/handlers/unverified/callbacks/language.py:66 msgid "unverified choose language successfully template" msgstr "Язык успешно выбран: {}" @@ -121,22 +125,22 @@ msgstr "Начать верификацию" msgid "choose language button" msgstr "Выбрать язык" -#: src/bot/utils/keyboards/__init__.py:58 +#: src/bot/utils/keyboards/__init__.py:52 msgid "student" msgstr "Студент" -#: src/bot/utils/keyboards/__init__.py:62 +#: src/bot/utils/keyboards/__init__.py:56 msgid "teacher" msgstr "Преподаватель" -#: src/bot/utils/keyboards/__init__.py:74 +#: src/bot/utils/keyboards/__init__.py:68 msgid "verification finish button" msgstr "Завершить" -#: src/bot/utils/keyboards/__init__.py:78 +#: src/bot/utils/keyboards/__init__.py:72 msgid "verification reset button" msgstr "Отмена" -#: src/bot/utils/keyboards/__init__.py:93 +#: src/bot/utils/keyboards/__init__.py:87 msgid "verified menu button" msgstr "Меню" -- 2.47.3