Перейти к содержанию
Innovialabs

Как сделать Telegram-бота на русском, узбекском и английском

Практика4 мин

Коротко

Берите language_code только как подсказку, сразу давайте выбрать язык и храните выбор в базе, все тексты держите в файлах gettext или Fluent, а описания команд задавайте для каждого языка через setMyCommands.

Короткий ответ

Мультиязычный бот собирается из четырёх частей:

  1. Определение языка — при первом /start берите language_code пользователя как значение по умолчанию.
  2. Явный выбор — сразу предлагайте сменить язык кнопками и сохраняйте выбор в базе.
  3. Файлы переводов — все тексты выносите из кода в gettext (.po) или Fluent (.ftl).
  4. Локализованные команды — описания команд и самого бота задаются отдельно для каждого языка через Bot API.

Шаг 1. Язык из language_code

В каждом апдейте Telegram передаёт поле from.language_code — язык интерфейса приложения у пользователя: ru, en, uz, иногда с регионом, например en-GB. Учитывайте три вещи:

  • поле необязательное и может отсутствовать;
  • оно показывает язык приложения, а не язык, на котором человеку удобно читать: многие в Узбекистане держат Telegram на русском или английском;
  • значит, это только подсказка, а не окончательное решение.

Порядок выбора языка: сохранённый выбор → language_code, если язык поддерживается → язык по умолчанию.

Шаг 2. Выбор языка пользователем

При первом запуске покажите приветствие и inline-кнопки «Русский», «O‘zbekcha», «English». Подписи кнопок пишите на самом языке: человек найдёт свой вариант, даже если остальной текст ему непонятен. Добавьте команду /language и пункт в настройках, чтобы язык можно было сменить в любой момент.

Выбор храните в базе вместе с профилем пользователя, а не в памяти процесса — иначе после перезапуска бот «забудет» язык. Пример middleware для aiogram 3:

from aiogram.utils.i18n import I18n, I18nMiddleware

class UserLocaleMiddleware(I18nMiddleware):
    async def get_locale(self, event, data) -> str:
        user = data.get("event_from_user")
        if user is None:
            return self.i18n.default_locale
        saved = await get_saved_locale(user.id)  # чтение из вашей БД
        if saved:
            return saved
        code = (user.language_code or "").split("-")[0]
        if code in self.i18n.available_locales:
            return code
        return self.i18n.default_locale

i18n = I18n(path="locales", default_locale="ru", domain="messages")
UserLocaleMiddleware(i18n).setup(dp)

Сразу после выбора ответьте уже на новом языке — например, внутри with i18n.use_locale(code):.

Шаг 3. Файлы переводов: gettext или Fluent

gettext (.po / .mo)Fluent (.ftl)
Форматисходная строка и её переводключ и сообщение
Множественное числоправила в заголовке .po, функция ngettextварианты по категориям CLDR прямо в сообщении
ИнструментыBabel, Poedit, большинство сервисов переводовменьше инструментов, файлы проще читать
В aiogramвстроенный модуль aiogram.utils.i18nчерез сторонние пакеты

Цикл работы с gettext через Babel:

pybabel extract -k _ -k __ -o locales/messages.pot .
pybabel init -i locales/messages.pot -d locales -D messages -l uz
pybabel compile -d locales -D messages

После изменений в коде вместо init используйте pybabel update. В Fluent множественное число описывается в самом сообщении:

cart-items = { $count ->
    [one] В корзине { $count } товар
    [few] В корзине { $count } товара
   *[many] В корзине { $count } товаров
}

Это важно именно для нашей тройки: в русском три формы, в английском две, а в узбекском существительное после числа не меняется («5 ta mahsulot»).

Правила, которые экономят время:

  • не склеивайте фразы из кусков — используйте плейсхолдеры, порядок слов в языках разный;
  • давайте переводчику контекст: «Открыть» в меню и в статусе заказа могут переводиться по-разному;
  • не переводите callback_data — это технические идентификаторы;
  • для reply-кнопок сравнивайте текст через ленивый перевод, например F.text == __("Каталог"), а лучше используйте inline-кнопки с callback_data.

Шаг 4. Локализованные команды

Метод setMyCommands принимает language_code. Telegram показывает список команд для языка интерфейса пользователя, а если такого нет — список по умолчанию. Так же работают setMyName, setMyDescription и setMyShortDescription.

from aiogram.types import BotCommand

COMMANDS = {
    "ru": [BotCommand(command="start", description="Начать"),
           BotCommand(command="language", description="Сменить язык")],
    "uz": [BotCommand(command="start", description="Boshlash"),
           BotCommand(command="language", description="Tilni o‘zgartirish")],
    "en": [BotCommand(command="start", description="Start"),
           BotCommand(command="language", description="Change language")],
}

async def setup_commands(bot):
    await bot.set_my_commands(COMMANDS["en"])  # список по умолчанию
    for lang, commands in COMMANDS.items():
        await bot.set_my_commands(commands, language_code=lang)

Если пользователь выбрал в боте язык, отличный от языка приложения, задайте ему команды персонально через scope=BotCommandScopeChat(chat_id=...).

Частые ошибки

  • тексты прямо в хендлерах — добавление языка превращается в поиск по всему коду;
  • кнопки не проверены на всех языках — длинные подписи обрезаются;
  • даты, суммы и телефоны форматируются одинаково для всех — используйте форматирование по локали, например через Babel;
  • уведомления менеджерам уходят на языке клиента, а не получателя.

Частые вопросы

Нет. Команда пишется латиницей и одинакова для всех, например /start. Переводятся только описания, которые видны в меню.

Поможем с этим

Если хотите не разбираться самим, а получить результат — этим мы занимаемся каждый день.

Обсудить проект

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.