Guide 23

Internationalizing bots: language detection, message catalogs, plurals, formats and right-to-left text

Translating strings is the easy part. Good localization also covers plurals, formats, command menus and the length of the result.

Guide2 min readUpdated

Decide the user's language

Platforms give hints:

  • Telegram: from.language_code (an IETF tag such as es or pt-br), when the user's client shares it.
  • Discord: locale on interactions (the user's client language) and guild_locale for community servers.
  • Slack: the user's locale is available through users.info with include_locale=true.
  • WhatsApp and Messenger: no reliable language field; infer from the phone prefix with care or ask.

Use the hint as a default, offer a /language command, and store the choice. A hint is not a promise: a person's interface language may not be the language they want to chat in.

Message catalogs

Keep strings out of code, keyed by ID:

# en.ftl (Project Fluent syntax)
items-in-cart = { $count ->
    [one] You have one item in your cart.
   *[other] You have { $count } items in your cart.
}
  • Use a format with plural rules, such as ICU MessageFormat or Project Fluent. Languages have different plural categories (for example, Arabic has six, Japanese one); Intl.PluralRules exposes them.
  • Use named placeholders, not string concatenation, because word order changes between languages.
  • Give translators context: where the text appears and its length limit.
  • Fall back to a default language when a key is missing, and report missing keys in tests.

Localize the bot's surface

  • Telegram: call setMyCommands with language_code for each language; users see the menu in their app language. The command list builder produces the JSON. Bot name, description and short description can also be set per language.
  • Discord: application commands support name_localizations and description_localizations. See the Discord command builder.
  • Buttons and keyboards need translating too, and button text must still fit.

Formats

  • Dates, numbers and currencies: use Intl.DateTimeFormat, Intl.NumberFormat and Intl.RelativeTimeFormat (or Babel in Python) with the user's locale.
  • Time: store UTC, display in the user's time zone. Plan schedules with the time zone scheduler.
  • Discord renders <t:UNIX:F> timestamps in each reader's local time; use them in shared channels. The timestamp converter builds them.

Length and direction

  • Translations are often longer than English, so check limits on buttons, embeds and messages with the message splitter.
  • Arabic, Hebrew and Persian are right-to-left. Mixed text with numbers or Latin names can display out of order; Unicode isolates (U+2068 and U+2069) around inserted values help.

LLM bots

Tell the model which language to answer in based on stored preference, not on the last message alone, and evaluate quality per language: models are uneven across languages.

Check your understanding

1. Why use a message format with plural rules?
2. How do you localize Telegram's command menu?