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 withlanguage(): locale says which one, texts holds the texts of the bot by key. Create one file per language in src/messages/:
src/messages/en-US.ts
src/messages/fr.ts
localeis 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: truemarks the language used when the one asked for is not there. Exactly one file says so: withchapterjs devrunning, 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,234in English,1 234in French). - A plural has one form per quantity (
one,other, andzero,two,few,manyfor the languages that need them).countpicks the form by the rules of the language.
{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) receivest. Change the welcome of the events chapter to use it:
src/events/hello.ts
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:
src/commands/members.ts
Which language t speaks
Here is the subtle part, and the reasont 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:
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.
The texts of buttons, menus and forms
A label is written in the file of the component, where there is not 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:
src/components/next-page.ts
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, arun 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:
src/messages/fr.ts
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 throught. 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:
src/messages/en-US.ts
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-USunless 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 isdefault: 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 inframework; the descriptions of commands incommands.
Organising a growing project
Share code, store data, and keep files short.
Guide: Translated messages
The same, in short.