Internationalization (i18n)
Internationalization is the process to provide automated translations based on locales (languages) extracted from any user input.
TSCord uses the excellent typesafe-i18n package, that provides a fully typed translation system, with string templating and many more!
Click here to view its documentation and understand how it works.
In fact, this library is a program that will watch your translation files. If it detects any change, it will re-generate a type file in consequence. This is how it achieves complete typesafety!
Even if this system is good, it is known to be really slow and painful with a really large translation codebase. I however chose it because a simple discord bot, except for particular cases, will never reach this limit.
This "program" can be launched as a standalone using npm run dev:i18
, but anyway it is runned in parallel of the bot when using the npm run dev
command!
Write translations
You first need to choose a base translation language. It is from this language and file that typesafe-i18n
will generate the type file.
By default, TSCord have two translation languages :
en
(default)fr
Their translation file are respectively located in:
src/i18n/en/index.ts
src/i18n/fr/index.ts
Your workflow should be as follow:
- Edit the base translation language file first (
en
by default) as you need - Run the generator with either
npm run dev:i18n
ornpm run dev
- Then, when the type file is updated, you can edit the other language translation files without errors
- If you still have errors displayed in your editor, restart the Typescript Server if your IDE
E.g:
- en
- fr
import type { BaseTranslation } from "../i18n-types"
const en: BaseTranslation = {
GREET: 'Hello!'
}
export default en
import type { Translation } from "../i18n-types"
const fr: Translation = {
GREET: 'Bonjour !'
}
export default fr
Use translations
Basic usage
To use translations, nothing more simple:
import { L } from '@/i18n'
const locale = 'en'
console.log(L[locale]['GREET']()) // -> Hello!
Locale extractor
TSCord have a built-in function to extract a valid locale from any discord interaction:
import { Discord, Slash } from 'discordx'
import { CommandInteraction } from 'discord.js'
import { L, getLocaleFromInteraction } from '@/i18n'
@Discord()
class Example {
@Slash({ name: 'greet' })
async greet(
interaction: CommandInteraction
) {
const locale = getLocaleFromInteraction(interaction)
// will respond 'Hello!' to a user who have his discord client in english,
// and 'Bonjour !' to a french one.
interaction.followUp(L[locale]['GREET']())
}
}
Automatic detection
And that's not all!
We've automated this process to free you from importing L
and getLocaleFromInteraction
in nearly every files.
In fact, every interaction handler will automaticaly receive the locale and localized L
function as an auto injected parameter as follow:
import { Discord, Slash } from 'discordx'
import { CommandInteraction } from 'discord.js'
import { L, getLocaleFromInteraction } from '@/i18n'
@Discord()
class Example {
@Slash({ name: 'greet' })
async greet(
interaction: CommandInteraction,
client: Client, // -> if you want to use these auto injected data, you MUST put the client because discordx injects it anyway!
// if you don't, then the object passed as the InteractionData one will be in fact the client ☠️
{ sanitizedLocale, localize }: InteractionData
) {
console.log(sanitizedLocale) // -> en
interaction.followUp(localize['GREET']()) // -> 'Hello!'
}
}
Discord-side translation
Quite recently, Discord has made available the possibility to submit multiple name and description depending on the language of the user's client. So for example, you can submit a slash command with a name of hello for english users and bonjour for french users (the english one should also be the fallback language for the one you haven't referenced).
this is the "raw" way of doing that with discordx
:
@Slash({
name: 'hello',
nameLocalizations: {
en: 'hello',
fr: 'bonjour'
},
description: {
en: 'Reply hello to the user',
fr: 'Répond bonjour à l\'utilisateur'
}
})
//...
@Slash
should be imported from '@/decorators', as explained on this page.
But to avoid having to put this text-heavy thing in the code, we recommand you to put these in the translation files. Then, you have 2 ways to reference it on the Slash (or ContexMenus, or SlashOption, etc...) decorators:
- localizationSource
- name
We added an optional localizationSource
property to the component decorators.
It will automaticaly seek a NAME
and a DESCRIPTION
property at the given path in the translation file.
For example, this code:
@Slash({
name: 'hello',
localizationSource: 'COMMANDS.HELLO'
})
//...
Will search translations at COMMANDS.HELLO.NAME
for nameLocalizations
and COMMANDS.HELLO.DESCRIPTION
for descriptionLocalizations
.
Only for @Slash
.
If no localizationSource
is provided, the name
property will be used as the localization source.
E.g:
@Slash({
name: 'hello'
})
Will search translations at COMMANDS.HELLO.NAME
and COMMANDS.HELLO.DESCRIPTION
.