Skip to content

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:

  1. the language picked in your bot, saved with app.setLocale(ctx, 'id') in the user session, so it applies in every chat (undefined goes back to automatic),
  2. your i18n.locale function, if you set one,
  3. the language of the user's Telegram app (ctx.from.language_code),
  4. 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.

ts
// 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 ​

ts
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:

ts
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 by vars; unknown ones stay visible.
  • Nested keys are addressed with dots: t('cart.empty').
  • A message with one / other (and zero, two, few, many) forms is chosen by vars.count with the language's plural rules. zero is 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.locale is 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.

ts
text: t.md('welcome', { name: ctx.from?.first_name }), // a name like "*[x](…)" can't inject formatting

Outside renders ​

ts
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 language

Menu 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:

ts
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.

MIT licensed