Legan Studio
Все статьи
~ 6 мин чтения

Многоязычность Telegram-бота: i18n на практике

Как сделать многоязычный Telegram-бот: структура переводов, выбор языка, плюрализация, fallback. Практические подходы и подводные камни.

  • Telegram
  • архитектура
  • разработка

Многоязычный бот — это не «прогнать тексты через автоперевод и положить в словарь». Это инфраструктурное решение: где хранить переводы, как определять язык пользователя, что делать с плюрализацией, как обновлять тексты без релизов. Разберём по частям.

Где брать язык пользователя

Telegram отдаёт language_code в User объекте — двухбуквенный ISO-639-1 код («ru», «en», «de»). Это подсказка, а не приговор: пользователь может пользоваться ботом на русском, имея английскую раскладку Telegram. Поэтому:

  1. На старте берём language_code как дефолт.
  2. Сразу даём кнопку «🇷🇺 RU / 🇬🇧 EN» для явного выбора.
  3. Сохраняем выбор в БД пользователя — это финальный источник истины.
  4. В настройках всегда позволяем сменить язык.

language_code иногда приходит расширенный («pt-br», «zh-hans») — оставляйте только основной язык, если у вас нет региональных переводов.

Где хранить переводы

Три уровня зрелости:

  • Файлы в репозитории. JSON или YAML по языкам: locales/ru.json, locales/en.json. Подходит для большинства проектов.
  • gettext (.po/.mo). Стандарт мира перевода, есть инструменты (POEdit, Crowdin, Lokalise). Для команды переводчиков и больших объёмов.
  • БД или CMS. Когда тексты меняет маркетинг без релиза. Минус — нужно кэшировать.

Для типового бота на 200–500 строк JSON в репо плюс нормальная структура ключей закрывают потребности.

Структура ключей

Хорошие ключи — иерархические и контекстные. Не «error1», «error2», а lead.error.phone_invalid, menu.tariffs.title. Это спасает от коллизий и помогает переводчику понять контекст.

{
  "lead": {
    "title": "Оставьте заявку",
    "error": {
      "phone_invalid": "Проверьте номер телефона"
    }
  }
}

Не складывайте интерполяции в ключ. Параметры — через шаблон: "greeting": "Привет, {name}!". Переводчик видит структуру и не путается.

Плюрализация

Русский, польский, чешский имеют сложные правила множественных форм. Английский — только две формы (one/other), русский — три (one/few/many).

Стандарт — CLDR Plural Rules. Большинство фреймворков (i18next, gettext, формат Fluent от Mozilla) поддерживают их из коробки.

"orders": {
  "one": "У вас {count} заказ",
  "few": "У вас {count} заказа",
  "many": "У вас {count} заказов"
}

Не пытайтесь «угадать» правила if-else’ами. Используйте библиотеку, иначе на каком-нибудь «21 день» бот скажет «дней» вместо «день».

Fallback

Что делать, если ключа нет в текущем языке? Варианты:

  • Вернуть ключ как есть — для разработки видно, что не переведено.
  • Откатиться на дефолтный язык (обычно английский или русский) — для продакшена.
  • Логировать пропуски — собирать список «надо перевести».

В продакшене стандартная схема: пытаемся найти на языке пользователя, если нет — на дефолте, если нет — пишем ключ и логируем. Это убережёт от пустых сообщений.

Динамический контент

Тексты статичные (меню, кнопки, ошибки) — в файлах переводов. Динамические (названия товаров, описания услуг) — в БД с полем-словарём: title_ru, title_en или JSON-полем title: { ru: "...", en: "..." }.

Контент-менеджер заполняет оба поля при создании. Если переводчика нет — оставляем поле пустым, и при выдаче делаем fallback на основной язык.

Кнопки и команды

Нюанс Telegram: команды бота (/start, /help) переводятся через setMyCommands с параметром language_code. Можно настроить разные команды для русских и английских пользователей.

Кнопки клавиатур и callback’ов — обычные строки в локализованных меню. Используйте callback_data как идентификатор действия (buy_premium), а не как локализованный текст — иначе после смены языка старые кнопки перестанут работать.

Тестирование переводов

Минимальный набор автотестов:

  1. Все ключи присутствуют во всех языках (lint на parity).
  2. Плейсхолдеры ({name}) совпадают между языками.
  3. Длина переведённого текста не выходит за лимиты Telegram (4096 символов сообщение, 64 — кнопка).
  4. Smoke-тест воронки на каждом языке.

Crowdin/Lokalise валидируют структуру и плейсхолдеры на стороне платформы.

Итого

Многоязычность бота — это структура (файлы или CMS), плюрализация по CLDR, осознанный fallback и явное хранение языка в профиле пользователя. Не путайте language_code Telegram с реальным выбором — давайте кнопку. Начинать с одного языка и закладывать i18n на старте дешевле, чем пытаться добавить второй язык поверх «жёсткой» русской версии.

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

Как определить язык пользователя в Telegram-боте?

Telegram отдаёт language_code в User объекте — двухбуквенный ISO-639-1 код («ru», «en», «de»). Это подсказка, а не приговор: пользователь может пользоваться ботом на русском, имея английскую раскладку Telegram. На старте берём language_code как дефолт. Сразу даём кнопку «🇷🇺 RU / 🇬🇧 EN» для явного выбора. Сохраняем выбор в БД пользователя — это финальный источник истины. В настройках всегда позволяем сменить язык. language_code иногда приходит расширенный («pt-br», «zh-hans») — оставляйте только основной язык, если у вас нет региональных переводов.

Где хранить переводы для многоязычного Telegram-бота?

Три уровня зрелости. Файлы в репозитории — JSON или YAML по языкам (locales/ru.json, locales/en.json), подходит для большинства проектов. Gettext (.po/.mo) — стандарт мира перевода, есть инструменты POEdit, Crowdin, Lokalise; для команды переводчиков и больших объёмов. БД или CMS — когда тексты меняет маркетинг без релиза, минус — нужно кэшировать. Для типового бота на 200–500 строк JSON в репо плюс нормальная структура ключей закрывают потребности. С ростом языков и объёмов имеет смысл переходить на Crowdin/Lokalise.

Как структурировать ключи переводов в боте?

Хорошие ключи — иерархические и контекстные. Не «error1», «error2», а lead.error.phone_invalid, menu.tariffs.title. Это спасает от коллизий и помогает переводчику понять контекст. Не складывайте интерполяции в ключ — параметры через шаблон: «greeting»: «Привет, {name}!». Переводчик видит структуру и не путается. Иерархия по областям продукта: lead, menu, payments, errors, common. В каждой области свой словарь. Это масштабируется и при росте до 1000+ строк, и при добавлении новых языков переводчики работают в привычной структуре.

Как реализовать плюрализацию в Telegram-боте на русском?

Через CLDR Plural Rules. Русский имеет три формы (one/few/many): 1 заказ, 2 заказа, 5 заказов. Английский — две (one/other). Большинство фреймворков (i18next, gettext, формат Fluent от Mozilla) поддерживают CLDR из коробки. Пример: «orders»: { «one»: «У вас {count} заказ», «few»: «У вас {count} заказа», «many»: «У вас {count} заказов» }. Не пытайтесь «угадать» правила if-else’ами. Используйте библиотеку, иначе на каком-нибудь «21 день» бот скажет «дней» вместо «день» — типичный баг ручной плюрализации.

Что делать, если перевод отсутствует в нужном языке?

Стратегия fallback. Что делать если ключа нет в текущем языке? Варианты: вернуть ключ как есть — для разработки видно, что не переведено. Откатиться на дефолтный язык (обычно английский или русский) — для продакшена. Логировать пропуски — собирать список «надо перевести». В продакшене стандартная схема: пытаемся найти на языке пользователя, если нет — на дефолте, если нет — пишем ключ и логируем. Это убережёт от пустых сообщений. Crowdin/Lokalise валидируют структуру и плейсхолдеры на стороне платформы, что дополнительно ловит проблему до прода.

Как переводить команды бота /start, /help в Telegram?

Через setMyCommands с параметром language_code. Можно настроить разные команды для русских и английских пользователей. Кнопки клавиатур и callback’ов — обычные строки в локализованных меню. Используйте callback_data как идентификатор действия (buy_premium), а не как локализованный текст — иначе после смены языка старые кнопки перестанут работать. Минимальный набор автотестов: все ключи присутствуют во всех языках (lint на parity), плейсхолдеры совпадают между языками, длина не выходит за лимиты Telegram (4096 сообщение, 64 кнопка), smoke-тест воронки на каждом языке.