Languages
easytg translates two things: its own built-in texts (Home, Cancel, "Please wait…") and, if you want, your app's messages.
The user's language
In this order:
- the language picked in your bot, saved with
app.setLocale(ctx, 'id')in the user session, so it applies in every chat (undefinedgoes back to automatic), - your
i18n.localefunction, if you set one, - the language of the user's Telegram app (
ctx.from.language_code), - for messages sent without an update (
sendTo,sendLater, broadcasts), which carry no language: the one their Telegram app had when they last wrote to the bot (kept in the user session when translations are configured).
pt-br falls back to pt; pt_BR, PT-BR and pt-br are the same.
// A language picker:
const language = page<{ set?: string }>('language').render(async ({ ctx, params, app, t }) => {
if (params.set) await app.setLocale(ctx, params.set);
return { text: t('language.saved') }; // already in the new language
});Renders, middlewares, dialogue steps and onFinish / onCancel receive the language as locale (as it was when they started), and app.localeOf(ctx) returns it elsewhere.
Your messages: t
new EasyTG({
i18n: {
messages: {
en: {
welcome: 'Hi **{name}**!',
cart: {
items: { one: '{count} item', other: '{count} items' },
empty: 'Your cart is empty',
checkout: 'Checkout',
},
},
id: {
welcome: 'Halo **{name}**!',
cart: { items: { other: '{count} barang' }, empty: 'Keranjang kosong', checkout: 'Bayar' },
},
},
fallbackLocale: 'en', // default
},
});t always uses the user's current language, also right after setLocale. It comes with every render, middleware, dialogue step, onFinish / onCancel, onText and payment handler:
page('cart').render(({ t, session, nav }) => {
const items = session.get<string[]>('cart') ?? [];
return {
text: items.length ? t('cart.items', { count: items.length }) : t('cart.empty'),
keyboard: [[nav.button(t('cart.checkout'), checkout)]],
};
});{name}placeholders are replaced byvars; unknown ones stay visible.- Nested keys are addressed with dots:
t('cart.empty'). - A message with
one/other(andzero,two,few,many) forms is chosen byvars.countwith the language's plural rules.zerois used for 0 when present. - A key missing in the user's language comes from
fallbackLocale. A key missing there too returns the key itself and logs a warning once. t.has(key)checks whether a key exists;t.localeis the language the messages come from.
User input in messages
t() inserts vars as they are, which is right for button labels and plain text. When the message is Markdown or HTML and a var comes from a user (a name, a search term), use t.md or t.html: they escape every var, like the md / html templates.
text: t.md('welcome', { name: ctx.from?.first_name }), // a name like "*[x](…)" can't inject formattingOutside renders
bot.command('help', async (ctx) => {
await app.session(ctx); // loads a language chosen with setLocale
await ctx.reply(app.t(ctx)('help'));
});
app.t('id')('cart.empty'); // a specific languageMenu labels can be translated too: replyMenu.button((locale, t) => t('menu.orders'), orders).
Built-in texts
easytg's own labels and messages follow the same language:
new EasyTG({
i18n: {
texts: { home: '🏠 Start' }, // override the built-in English texts
locales: { id }, // per language; 'id-ID' falls back to 'id'
},
});easytg itself ships English only. examples/locales/id.ts is a complete Indonesian translation to copy from.
Texts added after a release are optional in EasyTGTexts, so a full translation keeps compiling; the English text is used until you add yours. The ones added since 0.3.1: chooseAtLeast, chooseAtMost, expectNumber and expectDate (the multiChoice, number and datedialogue steps). Month and weekday names of the calendar come from Intl, in the user's language.
examples/search.ts uses both, with a language picker.