Translation

In order to make you bot translatable you have to add a minimal number of hooks to your Python code.

These hooks are called translation strings.

The aiogram translation utils is build on top of GNU gettext Python module and Babel library.

Installation

Babel is required to make simple way to extract translation strings from your code

Can be installed from pip directly:

pip install Babel

or as aiogram extra dependency:

pip install aiogram[i18n]

Make messages translatable

In order to gettext need to know what the strings should be translated you will need to write translation strings.

For example:

from aiogram import html
from aiogram.utils.i18n import gettext as _

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

Danger

f-strings can’t be used as translations string because any dynamic variables should be added to message after getting translated message

Also if you want to use translated string in keyword- or magic- filters you will need to use lazy gettext calls:

from aiogram import F
from aiogram.utils.i18n import lazy_gettext as __

@router.message(F.text == __("My menu entry"))
...

Danger

Lazy gettext calls should always be used when the current language is not know at the moment

Danger

Lazy gettext can’t be used as value for API methods or any Telegram Object (like aiogram.types.inline_keyboard_button.InlineKeyboardButton or etc.)

Working with plural forms

The gettext from aiogram.utils.i18n is the one alias for two functions _gettext_ and _ngettext_ of GNU gettext Python module. Therefore, the wrapper for message strings is the same _(). You need to pass three parameters to the function: a singular string, a plural string, and a value.

Configuring engine

After you messages is already done to use gettext your bot should know how to detect user language

On top of your application the instance of aiogram.utils.i18n.I18n should be created

i18n = I18n(path="locales", default_locale="en", domain="messages")

After that you will need to choose one of builtin I18n middleware or write your own.

Builtin middlewares:

SimpleI18nMiddleware

class aiogram.utils.i18n.middleware.SimpleI18nMiddleware(i18n: I18n, i18n_key: str | None = 'i18n', middleware_key: str = 'i18n_middleware')[source]

Simple I18n middleware.

Chooses language code from the User object received in event

__init__(i18n: I18n, i18n_key: str | None = 'i18n', middleware_key: str = 'i18n_middleware') None[source]

Create an instance of middleware

Parameters:
  • i18n – instance of I18n

  • i18n_key – context key for I18n instance

  • middleware_key – context key for this middleware

ConstI18nMiddleware

class aiogram.utils.i18n.middleware.ConstI18nMiddleware(locale: str, i18n: I18n, i18n_key: str | None = 'i18n', middleware_key: str = 'i18n_middleware')[source]

Const middleware chooses statically defined locale

__init__(locale: str, i18n: I18n, i18n_key: str | None = 'i18n', middleware_key: str = 'i18n_middleware') None[source]

Create an instance of middleware

Parameters:
  • i18n – instance of I18n

  • i18n_key – context key for I18n instance

  • middleware_key – context key for this middleware

FSMI18nMiddleware

class aiogram.utils.i18n.middleware.FSMI18nMiddleware(i18n: I18n, key: str = 'locale', i18n_key: str | None = 'i18n', middleware_key: str = 'i18n_middleware')[source]

This middleware stores locale in the FSM storage

__init__(i18n: I18n, key: str = 'locale', i18n_key: str | None = 'i18n', middleware_key: str = 'i18n_middleware') None[source]

Create an instance of middleware

Parameters:
  • i18n – instance of I18n

  • i18n_key – context key for I18n instance

  • middleware_key – context key for this middleware

async set_locale(state: FSMContext, locale: str) None[source]

Write new locale to the storage

Parameters:
  • state – instance of FSMContext

  • locale – new locale

I18nMiddleware

or define you own based on abstract I18nMiddleware middleware:

class aiogram.utils.i18n.middleware.I18nMiddleware(i18n: I18n, i18n_key: str | None = 'i18n', middleware_key: str = 'i18n_middleware')[source]

Abstract I18n middleware.

__init__(i18n: I18n, i18n_key: str | None = 'i18n', middleware_key: str = 'i18n_middleware') None[source]

Create an instance of middleware

Parameters:
  • i18n – instance of I18n

  • i18n_key – context key for I18n instance

  • middleware_key – context key for this middleware

abstract async get_locale(event: TelegramObject, data: Dict[str, Any]) str[source]

Detect current user locale based on event and context.

This method must be defined in child classes

Parameters:
  • event

  • data

Returns:

setup(router: Router, exclude: Set[str] | None = None) BaseMiddleware[source]

Register middleware for all events in the Router

Parameters:
  • router

  • exclude

Returns:

Deal with Babel

Step 1 Extract messages

pybabel extract --input-dirs=. -o locales/messages.pot

Here is --input-dirs=. - path to code and the locales/messages.pot is template where messages will be extracted and messages is translation domain.

Working with plural forms

Extracting with Pybabel all strings options:

  • -k _:1,1t -k _:1,2 - for both singular and plural

  • -k __ - for lazy strings

pybabel extract -k _:1,1t -k _:1,2 -k __ --input-dirs=. -o locales/messages.pot

Note

Some useful options:

  • Add comments for translators, you can use another tag if you want (TR) --add-comments=NOTE

  • Contact email for bugreport --msgid-bugs-address=EMAIL

  • Disable comments with string location in code --no-location

  • Copyrights --copyright-holder=AUTHOR

  • Set project name --project=MySuperBot

  • Set version --version=2.2

Step 2: Init language

pybabel init -i locales/messages.pot -d locales -D messages -l en
  • -i locales/messages.pot - pre-generated template

  • -d locales - translations directory

  • -D messages - translations domain

  • -l en - language. Can be changed to any other valid language code (For example -l uk for ukrainian language)

Step 3: Translate texts

To open .po file you can use basic text editor or any PO editor, e.g. Poedit

Just open the file named locales/{language}/LC_MESSAGES/messages.po and write translations

Step 4: Compile translations

pybabel compile -d locales -D messages

Step 5: Updating messages

When you change the code of your bot you need to update po & mo files

  • Step 5.1: regenerate pot file: command from step 1

  • Step 5.2: update po files
    pybabel update -d locales -D messages -i locales/messages.pot
    
  • Step 5.3: update your translations: location and tools you know from step 3

  • Step 5.4: compile mo files: command from step 4