> ## Documentation Index
> Fetch the complete documentation index at: https://www.chapterjs.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Translated messages

> Answer each person in their language with one declaration per language, understand which language a message speaks, and translate what the bot says by itself

A bot that speaks several languages does not put an `if` on the language in every file: it declares its texts once per language, and every function receives `t`, which picks the right one. This page gives your bot two languages, and makes `/ping` answer with `t('pong')`.

## One declaration per language

A language is declared with `language()`: `locale` says which one, `texts` holds the texts of the bot by key. Create one file per language in `src/messages/`:

```ts src/messages/en-US.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { language } from 'chapterjs';

export default language({
  locale: 'en-US',
  default: true,
  texts: {
    pong: 'Pong!',
    welcome: 'Welcome {name}, you are member #{count}!',
    next: 'Page {page}',
    members: { one: '{count} member', other: '{count} members' },
  },
});
```

```ts src/messages/fr.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { language } from 'chapterjs';

export default language({
  locale: 'fr',
  texts: {
    pong: 'Pong !',
    welcome: 'Bienvenue {name}, tu es le membre n°{count} !',
    next: 'Page {page}',
    members: { one: '{count} membre', other: '{count} membres' },
  },
});
```

* **`locale` is the language**, as Discord writes it: `'en-US'`, `'en-GB'`, `'fr'`, `'de'`, `'es-ES'`, `'pt-BR'`, `'ja'`... A word Discord does not know is underlined, and refused when the file loads, with the list.
* **`default: true`** marks the language used when the one asked for is not there. Exactly one file says so: with `chapterjs dev` running, two files saying so are both underlined in the editor.
* **A placeholder** is a name between braces: `{name}`, `{count}`. It takes a text or a number, and a number is written the way the language writes numbers (`1,234` in English, `1 234` in French).
* **A plural** has one form per quantity (`one`, `other`, and `zero`, `two`, `few`, `many` for the languages that need them). `count` picks the form by the rules of the language.

The default language has every text, and the other languages may have only some of them: what is missing comes from the default. A text another language has but the default lacks, or a `{name}` that became `{nom}`, is reported on that file, and that language is left out until it matches.

## t: a text in the right language

Every function of your project (commands, events, buttons, forms, tasks) receives `t`. Change the welcome of the events chapter to use it:

```ts src/events/hello.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { event } from 'chapterjs';

export default event({
  name: 'memberJoin',
  async run({ member, guild, t }) {
    const channel = guild.systemChannelId
      ? guild.channels.get(guild.systemChannelId)
      : undefined;
    if (!channel?.isTextBased()) return;
    await channel.send(
      t('welcome', { name: member.displayName, count: guild.memberCount ?? 1 })
    );
  },
});
```

Type `t('` in your editor: it offers `pong`, `welcome`, `next`, `members`, the keys of the default language. Pick `welcome`, and the second argument asks for `name` and `count`: the placeholders of the text, read from the file. Forget `count`, and the line is underlined. The types come from the language file itself, on every keystroke, with nothing generated from its content.

A plural takes its `count` the same way:

```ts src/commands/members.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { command } from 'chapterjs';

export default command({
  name: 'members',
  description: 'Counts the members of this server',
  async run({ interaction, guild, t }) {
    await interaction.reply(t('members', { count: guild.memberCount ?? 0 }));
  },
});
```

## Which language t speaks

Here is the subtle part, and the reason `t` exists rather than an `if (interaction.locale === 'fr')`. A message is sent **once**, and everyone in the channel reads the same text: Discord does not translate it per reader. So `t` speaks the language of **those who will read** what you send, not of the person who used the command:

| What you send | The language |
| - | - |
| An answer only the person sees (`ephemeral: true` on the command, the button or the form), or a form | The one of the person |
| A private message | The one of the person |
| A public message in a server | The one of the server, when Discord knows it |
| The `ready` event, a task | The default one |

Discord only knows the language of a **Community** server (a setting of the server). Elsewhere it says `en-US` whatever the members speak, so a public message there uses your default language. A bot in one language puts that language as the default and never thinks about it again; a bot in several follows the big servers, which set theirs.

`t.locale` says which language was taken, and `t.in('fr', 'welcome', { ... })` gives a text in another language, for a message to someone else. When the exact language is not in your files, `t` takes the closest one (`en-GB` reads the `en-US` texts), and the default otherwise.

> [!WARNING]
>
> `t` is given to your function **before** it runs, for the audience the file
> declares. A command that is not `ephemeral` but answers with `ephemeral: true`
> at the call keeps the language of the server in that answer. When a command's
> answers are for the person, put `ephemeral: true` on the command: `t` then
> speaks their language, and it is the default of its answers too.

## The texts of buttons, menus and forms

A label is written in the file of the component, where there is no `t` yet: the message is not sent. To translate it, make it a function of `t` and the data; it runs when the message is sent, for who will read it:

```ts src/components/next-page.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { button } from 'chapterjs';

const nextPage = button({
  label: ({ t, data }) => t('next', { page: data.page + 1 }),
  data: { page: 'number' },
  async run({ interaction, data }) {
    await interaction.update({
      components: [nextPage({ page: data.page + 1 })],
    });
  },
});

export default nextPage;
```

This works for the `label` of a button, the `placeholder` of a menu and its options, the `title` of a form and the texts of its fields.

## What the bot answers by itself

When the bot can't run what was asked (a command used in the wrong place, a button of an old message, a `run` that threw), it answers by itself, in English. These phrases are translated in `framework`, with the words for a command, a button, a menu and a form, which `{what}` stands for:

```ts src/messages/fr.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { language } from 'chapterjs';

export default language({
  locale: 'fr',
  texts: {
    pong: 'Pong !',
    welcome: 'Bienvenue {name}, tu es le membre n°{count} !',
    next: 'Page {page}',
    members: { one: '{count} membre', other: '{count} membres' },
  },
  framework: {
    command: 'commande',
    button: 'bouton',
    guildOnly: "Cette {what} ne s'utilise que sur un serveur.",
    failed: "Quelque chose s'est mal passé avec cette {what}.",
  },
});
```

A phrase is answered in the language of the person; one a file does not translate stays in English. Every phrase, with its English text, is in the [Reference](https://www.chapterjs.org/reference/messages.md#frameworktexts).

## The descriptions of commands

What Discord shows of a command before it is used (its name, its description, its options) is registered, not sent by the bot, so it can't go through `t`. It is the one thing translated by data. A command can leave `description` out of its file and have it in the language files instead, by the name of the command:

```ts src/messages/en-US.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { language } from 'chapterjs';

export default language({
  locale: 'en-US',
  default: true,
  texts: {
    pong: 'Pong!',
    welcome: 'Welcome {name}, you are member #{count}!',
    next: 'Page {page}',
    members: { one: '{count} member', other: '{count} members' },
  },
  commands: {
    dice: {
      description: 'Rolls a die',
      options: { sides: { description: 'How many sides' } },
    },
  },
});
```

The default language gives the description everyone sees; the other languages give their translations (`name` too, when the command should have another name in that language). A command has its texts in one place: with `description` in its file, a language file that mentions it is an error; without it, the file must not have `locales`. Move the description of `/ping` there too, and remove it from its file: the `tickets`, `music`, `community` and `moderation` templates do that for every command.

## Points of attention

* **Keys are typed from the default file.** Add a text there first, then in the others; the editor and the loader both check it.
* **Braces written as is are doubled**: `'{{name}}'` shows `{name}`.
* **The language of a server is `en-US` unless it is a Community server.** Do not be surprised when a public message in a French server comes out in the default language.
* **Without a language, there is no `t`**: it does not exist, in the types nor at runtime. Skip the language files if your bot speaks one language and you prefer plain texts.

## Recap

* `language({ locale, texts })` holds the texts of one language by key; one of them is `default: true`.
* `t('key', { placeholders })` gives the text in the language of who will read it; the editor checks keys and placeholders.
* A label of a component is a function of `t`; the phrases of the bot go in `framework`; the descriptions of commands in `commands`.

> [card] [Organising a growing project](https://www.chapterjs.org/tutorial/organising.md)
>
> Share code, store data, and keep files short.

> [card] [Guide: Translated messages](https://www.chapterjs.org/translated-messages.md)
>
> The same, in short.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.