Skip to content

Commit af06e3d

Browse files
committed
Add localization
1 parent 4c013ac commit af06e3d

File tree

9 files changed

+76
-15
lines changed

9 files changed

+76
-15
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ APP__DEBUG=true
22
APP__PORT=8080
33
APP__URL=https://my.domain
44
APP__WEBHOOK_PATH=/webhook/tg
5+
APP__DEFAULT_LANGUAGE=en
56

67
TG_BOT__TOKEN=

bot/configs/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ class App(BaseSettings):
99
port: int = 8080
1010
url: str = 'https://my.domain'
1111
webhook_path: str = '/webhook/tg'
12+
default_language: str = 'en'
1213

1314

1415
class TgBot(BaseSettings):

bot/handlers/handlers.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from aiogram.types import CallbackQuery, Message
55

66
from bot.callbacks.callback import SaveCallbackFactory
7+
from bot.i18n.lexicon import Lexicon
78
from bot.keyboards.keyboards import Keyboards
89
from bot.services.bot_service import BotService
910
from bot.states.bot_state import BotState
@@ -15,11 +16,14 @@ def __init__(self, bot: Bot, kb: Keyboards, bot_service: BotService):
1516
self.kb = kb
1617
self.bot_service = bot_service
1718

18-
async def start_command(self, message: Message):
19-
await message.answer(text='Hi user! This start message.', reply_markup=self.kb.get_start_button())
19+
async def start_command(self, message: Message, lexicon: Lexicon, user_lang: str | None):
20+
await message.answer(
21+
text=lexicon.get_text('Hi user! This start message.', user_lang),
22+
reply_markup=self.kb.get_start_button(),
23+
)
2024

21-
async def help_command(self, message: Message):
22-
await message.answer(text='This help message.')
25+
async def help_command(self, message: Message, lexicon: Lexicon, user_lang: str | None):
26+
await message.answer(text=lexicon.get_text('This help message.', user_lang))
2327

2428
async def answer(self, message: Message, answer: str):
2529
await self.__send_message(message.chat.id, answer, message.message_id)
@@ -39,9 +43,12 @@ async def answer_fsm_state_2(self, message: Message, state: FSMContext):
3943
await message.answer(f"Step 1: {data['step_1']}\nStep 2: {data['step_2']}")
4044
await state.clear()
4145

42-
async def answer_inline_button(self, message: Message):
46+
async def answer_inline_button(self, message: Message, lexicon: Lexicon):
4347
callback = SaveCallbackFactory(message_id=message.message_id).pack()
44-
await message.answer(text='This help message', reply_markup=self.kb.get_inline_button(callback_data=callback))
48+
await message.answer(
49+
text=lexicon.get_text('Inline button message'),
50+
reply_markup=self.kb.get_inline_button(callback_data=callback),
51+
)
4552

4653
async def reply(self, message: Message):
4754
await message.reply(text=self.bot_service.upper(message.text))

bot/i18n/en.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
LEXICON_EN: dict[str, str] = {}

bot/i18n/lexicon.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from bot.i18n.en import LEXICON_EN
2+
from bot.i18n.ru import LEXICON_RU
3+
4+
5+
class Lexicon:
6+
def __init__(self, default_language: str):
7+
self.default_language = default_language
8+
9+
def get_text(self, param: str, lang: str | None = None) -> str:
10+
if lang is None:
11+
lang = self.default_language
12+
13+
match lang:
14+
case 'ru':
15+
ret = LEXICON_RU.get(param, param)
16+
case 'en':
17+
ret = LEXICON_EN.get(param, param)
18+
case _:
19+
ret = LEXICON_EN.get(param, param)
20+
21+
return ret

bot/i18n/ru.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
LEXICON_RU: dict[str, str] = {
2+
'Hi user! This start message.': 'Привет, пользователь! Это стартовое сообщение.',
3+
'This help message.': 'Это справочное сообщение.',
4+
}

bot/middlewares/i18n.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from collections.abc import Awaitable, Callable
2+
from typing import Any
3+
4+
from aiogram import BaseMiddleware
5+
from aiogram.types import TelegramObject, User
6+
7+
8+
class I18nMiddleware(BaseMiddleware):
9+
async def __call__(
10+
self,
11+
handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]],
12+
event: TelegramObject,
13+
data: dict[str, Any],
14+
) -> Any:
15+
user: User = data.get('event_from_user')
16+
17+
if user is None:
18+
return await handler(event, data)
19+
20+
data['user_lang'] = user.language_code
21+
22+
return await handler(event, data)

bot/middlewares/throttling.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
logger = logging.getLogger(__name__)
1010

11-
CACHE = TTLCache(maxsize=10_000, ttl=2) # Максимальный размер кэша - 10000 ключей, время жизни ключа - 5 секунд
11+
CACHE = TTLCache(maxsize=10_000, ttl=2) # Максимальный размер кэша - 10000 ключей, время жизни ключа - 2 секунды
1212

1313

1414
class ThrottlingMiddleware(BaseMiddleware):
@@ -20,10 +20,11 @@ async def __call__(
2020
) -> Any:
2121
user: User = data.get('event_from_user')
2222

23-
if user.id in CACHE:
24-
logger.debug(f'Throttling for user {user.id}')
25-
return
23+
if user is not None:
24+
if user.id in CACHE:
25+
logger.debug(f'Throttling for user {user.id}')
26+
return
2627

27-
CACHE[user.id] = True
28+
CACHE[user.id] = True
2829

2930
return await handler(event, data)

main.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
from bot.callbacks.callback import SaveCallbackFactory
1616
from bot.configs.config import App, Settings, TgBot
1717
from bot.handlers.handlers import Handlers
18+
from bot.i18n.lexicon import Lexicon
1819
from bot.keyboards.keyboards import Keyboards
20+
from bot.middlewares.i18n import I18nMiddleware
1921
from bot.middlewares.throttling import ThrottlingMiddleware
2022
from bot.services.bot_service import BotService
2123
from bot.states.bot_state import BotState
@@ -88,11 +90,12 @@ def register_handlers(dp: Dispatcher, hd: Handlers) -> None:
8890

8991

9092
def register_middlewares(dp: Dispatcher) -> None:
91-
dp.update.middleware(ThrottlingMiddleware())
93+
dp.update.outer_middleware(I18nMiddleware())
94+
dp.update.outer_middleware(ThrottlingMiddleware())
9295

9396

94-
def register_workflow_data(dp: Dispatcher) -> None:
95-
dp.workflow_data.update({'answer': 'Hello'})
97+
def register_workflow_data(dp: Dispatcher, settings: Settings) -> None:
98+
dp.workflow_data.update({'answer': 'Hello', 'lexicon': Lexicon(settings.app.default_language)})
9699

97100

98101
def create_app(bot: Bot, dp: Dispatcher, settings: App) -> FastAPI:
@@ -114,7 +117,7 @@ def main() -> None:
114117
app = create_app(bot, dp, settings.app)
115118
register_handlers(dp, Handlers(bot, Keyboards(), BotService()))
116119
register_middlewares(dp)
117-
register_workflow_data(dp)
120+
register_workflow_data(dp, settings)
118121

119122
logger.debug('Application is running')
120123

0 commit comments

Comments
 (0)