Internationalization
Add a translations.yml file to an extension to localize slash command names/descriptions and user-facing messages. Botkit picks the right language from the guild's locale (or falls back to en-US).
Translation strings can include:
| Placeholder | Example | You provide |
|---|---|---|
| Your own values | {latency}, {user} |
Python .format(...) when sending the message |
| Slash commands | {commands.help}, {commands.admin__kick} |
Name of an existing slash command in YAML |
| App emojis | {emojis.checkmark} |
Name of an emoji uploaded to the bot application |
Where to put translation files
Either next to the extension:
src/extensions/my_ext/translations.yml
Or centralized (same <name> as the extension folder):
src/translations/my_ext.yml
Botkit loads the file when the extension starts. Top-level strings from the file are also available on the extension's config as config["translations"].
File layout
commands:
# localize slash / bridge commands (optional)
strings:
# general messages for this extension (optional)
Localizing commands
Under commands, use the same name as in your Python code (the cog method / decorator name), not the translated label shown in Discord.
You can nest up to three levels for command groups:
commands:
ping:
name:
en-US: ping
fr: ping
description:
en-US: Get the bot's latency
fr: Obtenir la latence du bot
options:
ephemeral:
name:
en-US: ephemeral
description:
en-US: Whether the response should be ephemeral
strings:
response:
en-US: "Pong! Latency is {latency}ms. Try {commands.help} next."
fr: "Pong ! Latence : {latency} ms. Essayez {commands.help} ensuite."
admin:
name:
en-US: admin
description:
en-US: Admin commands
commands:
kick:
name:
en-US: kick
description:
en-US: Kick a member
strings:
success:
en-US: "Done {emojis.checkmark}"
| Block | What it does |
|---|---|
name |
Localized command name shown in Discord |
description |
Localized command description |
options.<name> |
Localized slash option name and description |
commands |
Subcommands inside a group |
strings |
Messages for this command (see below) |
See src/extensions/ping/translations.yml in the repo for a full working example.
Extension-wide messages
Use top-level strings for text that is not tied to one command:
strings:
welcome:
en-US: "Welcome! Use {commands.help} or {commands.ping} to get started."
fr: "Bienvenue ! Utilisez {commands.help} ou {commands.ping} pour commencer."
Supported locales
Use Discord locale tags as YAML keys, for example:
en-US, en-GB, fr, de, es-ES, es-419, pt-BR, ja, ko, nl, pl, ru, it, zh-CN, zh-TW, and others Discord supports.
Always include en-US — Botkit uses it when no translation exists for the user's locale.
Using translations in your code
Command messages (ctx.translations)
Define strings under commands.<cmd>.strings. In the handler, read them from ctx.translations:
from src import custom
@discord.slash_command(name="ping")
async def ping(self, ctx: custom.ApplicationContext) -> None:
text = ctx.translations.response.format(
latency=round(self.bot.latency * 1000),
)
await ctx.respond(text)
Works the same for prefix / bridge commands when you use custom.Context — Botkit loads command strings into ctx.translations automatically.
Extension-wide messages (apply_locale)
For top-level strings, pick the locale and read the key:
from src.i18n import apply_locale
locale = ctx.guild.preferred_locale if ctx.guild else None
t = apply_locale(config["translations"], locale)
await ctx.respond(t.welcome)
Pass your own placeholders with .format():
message = t.greeting.format(username=ctx.author.display_name)
{commands.x} — link to slash commands
Insert clickable slash command mentions in translated text.
strings:
hint:
en-US: "Need help? Run {commands.help}."
moderation:
en-US: "Use {commands.admin__kick} to remove members."
| In YAML | Refers to |
|---|---|
{commands.ping} |
Slash command /ping |
{commands.admin__kick} |
Subcommand /admin kick — use __ between group and subcommand names |
{commands.tools__config__reload} |
Deeper groups: __ between each level |
Rules:
- The command must exist and be registered on the bot.
- Placeholders work after the bot is online. Before that, the raw
{commands.name}may appear in text. - If the name does not match any command, the placeholder is left unchanged in the message.
Example
commands:
help:
strings:
footer:
en-US: "Also try {commands.ping} to check latency."
ping:
strings:
response:
en-US: "Pong! {latency}ms — see {commands.help} for more."
await ctx.respond(
ctx.translations.response.format(latency=round(self.bot.latency * 1000)),
)
{commands.help} is filled in by Botkit; {latency} is filled in by your .format().
{emojis.x} — app emoji mentions
Reference emojis uploaded to the bot application by name:
strings:
success:
en-US: "Saved {emojis.checkmark}"
failure:
en-US: "Something went wrong {emojis.cross_mark}"
| In YAML | Refers to |
|---|---|
{emojis.checkmark} |
App emoji named checkmark |
Requirements:
- Upload the emoji to your bot in the Developer Portal (or via API).
- The name in YAML must match the emoji name exactly.
- Keep
bot.cache_app_emojis: truein config (default) so emoji data is available.
Like {commands.x}, emoji placeholders work once the bot is online. Use emoji names you have actually created — a missing name causes an error when the string is resolved.
Quick reference
| I want to… | In translations.yml |
In Python |
|---|---|---|
Localize /command name & description |
commands.<name>.name / .description |
Nothing — applied at startup |
| Localize a slash option | commands.<name>.options.<opt> |
Nothing — applied at startup |
| Message tied to one command | commands.<name>.strings.<key> |
ctx.translations.<key> |
| General extension text | strings.<key> |
apply_locale(config["translations"], locale).<key> |
| Mention another command | {commands.other} or {commands.group__sub} in the string |
Usually nothing extra |
| Show an app emoji | {emojis.name} in the string |
Usually nothing extra |
| Insert dynamic values | {my_key} in the string |
.format(my_key=…) |
Help pages (bundled extension)
The help extension uses a separate YAML format under src/extensions/help/pages/ for help categories and command docs shown in /help. That is not the same schema as translations.yml.
The help extension still has its own translations.yml for the /help command itself (for example UI labels like "Tips & Tricks"). See that extension's readme if you customize help content.