Decide the user's language
Platforms give hints:
- Telegram:
from.language_code(an IETF tag such asesorpt-br), when the user's client shares it. - Discord:
localeon interactions (the user's client language) andguild_localefor community servers. - Slack: the user's locale is available through
users.infowithinclude_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.PluralRulesexposes 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
setMyCommandswithlanguage_codefor 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_localizationsanddescription_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.NumberFormatandIntl.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.