Глава 5. Интернационализация в Aiogram с использованием Fluent.

Подготовка кода

Для хорошей поддержки интернационализации и локализации разработчики Aiogram создали отдельный пакет aiogram_i18n https://github.com/aiogram/i18n, в который добавили поддержку движка локализации Fluent для Python https://github.com/projectfluent/python-fluent.

Установим его с помощью команды:

pip install aiogram_i18n

Также нам потребуются FluentCompileCore и FluentRuntimeCore

pip install fluent_compiler
pip install fluent.runtime

В предыдущем разделе мы рассказывали о том, что подход к созданию файлов перевода у Fluent в корне отличается от привычного. По сути теперь интернационализация полностью в ваших руках, а локализацию выполняет ядро Python Fluent. У нас теперь нет шаблонов перевода и мы не извлекаем строки из исходного кода нашего проекта. Хотя есть различные плагины для IDE, которые могут нам помочь с извлечением строк. Отказ от использования строк английского текста как ключей, накладывает некоторые ограничения в угоду гибкости самих переводов. Теперь нам нужно самим проектировать ПО так, чтобы перевод был возможен. И по-началу будет много ручной работы. Проект Fluent - 2017 год, и по меркам gettext 1995 года, он еще молодой. Инструменты автоматизации, которые я нашел, созданы в основном для JavaScript (в силу специфики разработки под локализацию браузера Firefox). Поэтому пока будем создавать файлы перевода вручную.

Создадим код нашего нового проекта:

lesson2.py
 1import asyncio
 2from logging import basicConfig, INFO
 3from typing import Any
 4
 5from aiogram import Router, Dispatcher, F, Bot
 6from aiogram.enums import ParseMode
 7from aiogram.filters import CommandStart
 8from aiogram.types import Message
 9
10from aiogram_i18n import I18nContext, LazyProxy, I18nMiddleware
11from aiogram_i18n.cores.fluent_runtime_core import FluentRuntimeCore
12from aiogram_i18n.types import (
13    ReplyKeyboardMarkup, KeyboardButton
14    # you should import mutable objects from here if you want to use LazyProxy in them
15)
16
17router = Router(name=__name__)
18rkb = ReplyKeyboardMarkup(
19    keyboard=[
20        [KeyboardButton(text=LazyProxy("help"))]  # or L.help()
21    ], resize_keyboard=True)
22
23@router.message(CommandStart())
24async def cmd_start(message: Message, i18n: I18nContext) -> Any:
25    name = message.from_user.mention_html()
26    return message.reply(
27        text=i18n.get("hello", user=name),  # or i18n.hello(user=name)
28        reply_markup=rkb)
29
30
31@router.message(F.text == LazyProxy("help"))
32async def cmd_help(message: Message) -> Any:
33    return message.reply(text="-- " + message.text + " --")
34
35async def main() -> None:
36    basicConfig(level=INFO)
37    bot = Bot("TOKEN", parse_mode=ParseMode.HTML)
38    i18n_middleware = I18nMiddleware(
39        core=FluentRuntimeCore(
40            path="locales/{locale}/LC_MESSAGES",
41        ),
42        default_locale="ru")
43    dp = Dispatcher()
44    dp.include_router(router)
45    i18n_middleware.setup(dispatcher=dp)
46    await dp.start_polling(bot)
47
48
49if __name__ == "__main__":
50    asyncio.run(main())

Мы импортировали следующие объекты:

10from aiogram_i18n import I18nContext, LazyProxy, I18nMiddleware
11from aiogram_i18n.cores.fluent_runtime_core import FluentRuntimeCore
12from aiogram_i18n.types import (
13    ReplyKeyboardMarkup, KeyboardButton
14    # you should import mutable objects from here if you want to use LazyProxy in them
15)

Нам понадобится сам движок FluentRuntimeCore, также контекст I18nContext и один из вариантов middleware (для примера я взял I18nMiddleware). Также нужны нам LazyProxy - ленивые строки, какие мы видели в lazy gettext. И изменяемые объекты Aiogram, такие, как клавиатура, которые нужно импортировать именно из aiogram_i18n.types. Эти объекты нам нужны, когда работа с объектом происходит, но язык еще не известен, так как код выполняется еще за пределами роутеров. И перевод будет добавлен лениво, то есть в самом конце перед передачей пользователю.

Создадим объект нашего middleware:

38i18n_middleware = I18nMiddleware(
39        core=FluentRuntimeCore(
40            path="locales/{locale}/LC_MESSAGES", # путь к папке локалей
41            ),
42        default_locale="ru") # язык интерфейса. Переключать научимся позже.

И зарегистрируем его через встроенный метод setup (в этом методе реализована регистрация компонентов в нужном порядке)

45i18n_middleware.setup(dispatcher=dp)

Создадим файл переводов в формате FTL (Fluent Translation List).

Файл следующего содержания с английским переводом my-super-bot.ftl положим в папку locales/en/LC_MESSAGES:

locales/en/LC_MESSAGES/my-super-bot.ftl
hello = Hello, <b>{ $user }</b>!
cur-lang = Your current language: <i>{ $language }</i>
help = Help

Файл с русским переводом my-super-bot.ftl положим в папку locales/ru/LC_MESSAGES:

locales/ru/LC_MESSAGES/my-super-bot.ftl
hello = Привет, <b>{ $user }</b>!
cur-lang = Текущий язык : <i>{ $language }</i>
help = Помощь

Запустим и проверим работу на русском языке. Затем изменим язык в middleware и проверим на английском:

38i18n_middleware = I18nMiddleware(
39        core=FluentRuntimeCore(
40            path="locales/{locale}/LC_MESSAGES", # путь к папке локалей
41            ),
42        default_locale="en")