Глава 2. Интернационализация в Aiogram утилитами GNU gettext.

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

Во фреймворке Aiogram предусмотрена возможность использования интернационализации (https://docs.aiogram.dev/en/latest/utils/i18n.html) с помощью библиотеки GNU gettext (https://docs.python.org/3/library/gettext.html) и инструментов Babel (https://babel.pocoo.org/en/latest/).

Установка пакета для переводов происходит через дополнительную зависимость командой

pip install aiogram[i18n]

Либо можно непосредственно установить сам Babel

pip install Babel

Первый и важный шаг для работы по интернационализации — нам нужно подготовить наш код таким образом, чтобы он смог использовать файлы перевода и загружать необходимые фразы из нужной локали. Для этого все переводимые строки необходимо обернуть функцией gettext. Функцию подстановки перевода из gettext принято обозначать _ - одинарное нижнее подчеркивание, а вызов этой функции _().

from aiogram import html
from aiogram.utils.i18n import gettext as _
# импортируем модуль gettext из aiogram utils как _

Обертываем все строки, которые нуждаются в переводе функцией gettext.

Было:

async def my_handler(message: Message) -> None:
    await message.answer(f"Hello, {html.quote(message.from_user.full_name)}!")

Стало:

async def my_handler(message: Message) -> None:
    await message.answer(_("Hello, {name}!").format(name=html.quote(message.from_user.full_name)))

Внимание

Обратите внимание, что Gettext не может использовать f-строки. Поскольку при использовании f-строк нельзя сначала создать шаблон, а затем его использовать. Это происходит из-за того, что f-строка сразу выполняется и в нее подставляются значения переменных, которые должны быть определены ранее. А у нас сначала должен произойти перевод с подстановкой в шаблон строки. Поэтому нужно использовать метод строк format().

Более того, когда нам необходимо использовать перевод в фильтрах ключевых слов или магических фильтрах, то нужно будет использовать ленивые вызовы gettext - lazy_gettext, которые будут обозначены __ - двойное подчеркивание, а вызов этой функции __().

from aiogram import F
from aiogram.utils.i18n import lazy_gettext as __
# Выше мы импортируем функцию ленивого вызова gettext как _ _

В документации особо обращено внимание на то, что ленивые вызовы lazy gettext всегда следует использовать, если текущий язык в данный момент неизвестен.

Также важно, что lazy gettext нельзя использовать в качестве значения для методов API или любого объекта Telegram (например, для aiogram.types.inline_keyboard_button.InlineKeyboardButton и т. д.).

Конфигурация движка перевода

Сначала в коде проекта мы создаем объект класса I18n, чтобы было понятно, какой язык будет использоваться:

i18n = I18n(path="locales", default_locale="en", domain="my-super-bot")

Здесь:

path= путь к папкам с локалями. В данном случае путь будет сформирован будет так: locales/{language}/LC_MESSAGES/файл_перевода.mo, а мы указываем верхний уровень locales, исходя из нашей структуры:

...
locales
├── messages.pot
├── en
│   └── LC_MESSAGES
│       └── my-super-bot.mo
├── ru
│   └── LC_MESSAGES
│       └── my-super-bot.mo
...

default_locale= локаль по умолчанию, английская «en».

domain= домен - это название домена переводов в gettext, по сути это название приложения, для которого будет создана локаль (используется чаще название того приложения, что мы переводим). Мы назвали «my-super-bot».

Движок перевода - это middleware для I18n. И теперь нам необходимо выбрать движок перевода, основанный на 4-х встроенных в aiogram классах middleware из aiogram.utils.i18n.middleware:

  1. SimpleI18nMiddleware - выбирает код языка из объекта User, полученного в событии. Однако не все клиенты Telegram отдают это значение. Очень часто объект language_code не заполнен и является пустой строкой.

  2. ConstI18nMiddleware - выбирает статически определенную локаль.

  3. FSMI18nMiddleware - хранит локаль в хранилище FSM.

  4. I18nMiddleware - это базовый абстрактный класс для наследования и создания собственного обработчика.

Наш код будет выглядеть примерно так:

 1from aiogram import Bot, Dispatcher, F
 2from aiogram.types import Message
 3
 4from aiogram.utils.i18n import gettext as _
 5from aiogram.utils.i18n import lazy_gettext as __
 6from aiogram.utils.i18n import I18n, ConstI18nMiddleware
 7
 8TOKEN = "token"
 9dp = Dispatcher()
10
11@dp.message(F.text == __('Test'))
12async def test1(message: Message) -> None:
13    await message.answer(_("Hello, {name}!").format(name=html.quote(message.from_user.full_name)))
14
15def main() -> None:
16    bot = Bot(TOKEN, parse_mode="HTML")
17    i18n = I18n(path="locales", default_locale="en", domain="my-super-bot")
18    dp.message.outer_middleware(ConstI18nMiddleware(locale='en', i18n=i18n))
19
20    dp.run_polling(bot)
21
22if __name__ == "__main__":
23    main()