Internationalization (i18n) library for discord.py bots
Project description
discord-i18n
Internationalization (i18n) library for discord.py bots — inspired by ezcord.
- One YAML (or JSON) file per locale — same layout as ezcord
load_languages()helper mirrors ezcord's style- Pluralization (
one/other/zero) {placeholder}interpolation +{.general}variable expansion- Fallback locale chain (
en-US→en) @use_localedecorator injects a boundTranslatorinto slash-command callbacksI18nCogmixin — built-inself.t()in every cogcheck_missing_keys()— catch missing translations at startup or in CIlocalize_commands()— translate command names, descriptions, and options- Zero required runtime dependencies —
discord.pyandPyYAMLare optional extras
Installation
pip install discord-i18n # core only
pip install discord-i18n[yaml] # + PyYAML (recommended)
pip install discord-i18n[discord] # + discord.py
Quick start
1. Create your locale files
locales/
├── en-US.yaml
├── de.yaml
└── fr.yaml
locales/en-US.yaml (ezcord-style YAML):
ping:
response: "Pong! Latency: {latency}ms"
greet:
hello: "Hello, {name}!"
errors:
cooldown:
one: "Please wait {seconds} second."
other: "Please wait {seconds} seconds."
locales/de.yaml:
ping:
response: "Pong! Latenz: {latency}ms"
greet:
hello: "Hallo, {name}!"
errors:
cooldown:
one: "Bitte warte {seconds} Sekunde."
other: "Bitte warte {seconds} Sekunden."
2. Set up the bot
import discord
from discord import app_commands
from discord_i18n import I18n, load_languages, use_locale
# ezcord-style: load all YAML files from the locales/ folder
i18n = I18n(
default_locale="en-US",
localizations=load_languages("locales/"),
)
class MyBot(discord.Client):
def __init__(self):
super().__init__(intents=discord.Intents.default())
self.tree = app_commands.CommandTree(self)
async def setup_hook(self):
await self.tree.sync()
bot = MyBot()
3. Use in slash commands
With the @use_locale decorator
from discord_i18n import use_locale, Translator
@bot.tree.command()
@use_locale(i18n)
async def ping(interaction: discord.Interaction, t: Translator):
latency = round(bot.latency * 1000)
await interaction.response.send_message(t("ping.response", latency=latency))
Manually
from discord_i18n import locale_from_interaction
@bot.tree.command()
async def greet(interaction: discord.Interaction):
t = i18n.get_translator(locale_from_interaction(interaction))
await interaction.response.send_message(t("greet.hello", name=interaction.user.name))
Pluralization
@bot.tree.command()
@use_locale(i18n)
async def cooldown(interaction: discord.Interaction, t: Translator):
seconds = 5
msg = t("errors.cooldown", count=seconds, seconds=seconds)
await interaction.response.send_message(msg)
YAML file structure
Keys can be nested (ezcord style) or dot-separated:
# Nested (ezcord style)
commands:
ping:
response: "Pong!"
# Dot-notation also works when calling t()
# i18n.t("commands.ping.response")
Pluralization
Use a mapping with one, other, and optionally zero:
items:
count:
zero: "No items"
one: "{count} item"
other: "{count} items"
t("items.count", count=0) # → "No items"
t("items.count", count=1) # → "1 item"
t("items.count", count=5) # → "5 items"
general section
Values in general are available via {.key} anywhere in the same file — they are automatically expanded when the locale is loaded (no extra call needed):
general:
support_server: "https://discord.gg/example"
bot_name: "MyBot"
errors:
help: "Join our support server: {.support_server}"
footer: "Powered by {.bot_name}"
The
generalsection stays in the data so you can still look it up; its values are simply pre-expanded into every string that references them.
API reference
load_languages(path)
Loads all .yaml / .yml / .json files from path and returns a
{"locale": {...}} dict ready to pass to I18n.
localizations = load_languages("locales/")
# → {"en-US": {...}, "de": {...}, "fr": {...}}
A bare en.yaml is automatically expanded to both en-US and en-GB
(same as ezcord).
I18n
I18n(
default_locale="en-US", # fallback when no locale is found
localizations=load_languages("locales/"), # ezcord-style pre-built dict
# OR: locales_dir="locales/", # load from directory (JSON + YAML)
fallback_locale="en-US", # secondary fallback
missing_key_mode="key", # "key" | "empty" | "raise"
)
| Method | Description |
|---|---|
i18n.t(key, locale=..., count=..., **kwargs) |
Translate a key |
i18n.get_translator(locale) |
Return a bound Translator callable |
i18n.load() |
Load/reload from locales_dir |
i18n.add_locale(locale, data) |
Register translations programmatically |
i18n.available_locales |
List of loaded locale strings |
Translator
A callable returned by i18n.get_translator(locale):
t = i18n.get_translator("de")
t("greet.hello", name="Welt") # → "Hallo, Welt!"
t("errors.cooldown", count=3, seconds=3)
@use_locale(i18n)
Decorator that resolves the locale from the discord.Interaction and injects
a bound Translator as the t parameter:
@tree.command()
@use_locale(i18n)
async def my_cmd(interaction: discord.Interaction, t: Translator):
await interaction.response.send_message(t("some.key"))
locale_from_interaction(interaction) / locale_from_ctx(ctx)
Extract the locale string from an Interaction or a prefix-command Context.
I18nCog — built-in translation in every Cog
I18nCog is a mixin for discord.ext.commands.Cog that binds an I18n instance once and exposes self.t() everywhere in the cog:
from discord.ext import commands
from discord_i18n import I18n, load_languages, I18nCog
i18n = I18n(default_locale="en-US", localizations=load_languages("locales/"))
class GreetCog(I18nCog, commands.Cog, i18n=i18n):
def __init__(self, bot):
self.bot = bot
@app_commands.command()
async def greet(self, interaction: discord.Interaction):
# locale is resolved automatically from the Interaction
msg = self.t("greet.hello", interaction, name=interaction.user.name)
await interaction.response.send_message(msg)
You can also set the instance after class definition (useful in large projects):
class GreetCog(I18nCog, commands.Cog):
...
GreetCog.set_i18n(i18n)
check_missing_keys — catch missing translations early
Call this on startup or in your test suite to find keys that exist in the default locale but are missing in other languages:
from discord_i18n import check_missing_keys
missing = check_missing_keys(i18n)
# {"de": ["errors.cooldown.zero"], "fr": ["ping.description"]}
if missing:
print("Missing translations:", missing)
By default a WARNING is also logged for every affected locale. Pass warn=False to suppress logs and only use the return value.
Command localization
Discord lets you translate slash-command names, descriptions, and option names/descriptions per locale. discord-i18n applies these from the same YAML files.
1. Add a commands section to your locale files
# locales/de.yaml
commands:
ping:
name: "ping"
description: "Latenz des Bots prüfen"
greet:
name: "begrüßen"
description: "Eine Begrüßung senden"
options:
name:
name: "name"
description: "Der zu begrüßende Nutzer"
# Subcommand group
admin:
description: "Admin-Befehle"
ban:
description: "Einen Nutzer bannen"
options:
user:
name: "nutzer"
description: "Der zu bannende Nutzer"
2. Call localize_commands before syncing
from discord_i18n import load_languages, localize_commands
localizations = load_languages("locales/")
class MyBot(discord.Client):
def __init__(self):
super().__init__(intents=discord.Intents.default())
self.tree = app_commands.CommandTree(self)
async def setup_hook(self):
localize_commands(self.tree, localizations) # ← before sync
await self.tree.sync()
localize_commands sets name_localizations and description_localizations on every command / subcommand / option it finds in the commands block. The default_locale (default "en-US") values are also applied directly to the command object.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file discord_i18n-1.0.3.tar.gz.
File metadata
- Download URL: discord_i18n-1.0.3.tar.gz
- Upload date:
- Size: 20.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ce620c7dcef76fee0c609c74f82f70b15e08a62c281b36259cbf3470cde4fd88
|
|
| MD5 |
318f050998566f49cc97a0d8f9cd77d7
|
|
| BLAKE2b-256 |
96e96089fdad1c9f4e71b658b90fe92b2f894fc14b60ba82a9b117898337f12d
|
File details
Details for the file discord_i18n-1.0.3-py3-none-any.whl.
File metadata
- Download URL: discord_i18n-1.0.3-py3-none-any.whl
- Upload date:
- Size: 21.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c20683de558da07c346dadadb1755abfbfd344dfda5d3a278ee536ccc68addcf
|
|
| MD5 |
c1c18e7b2e943a91d4fd6649a11cbb0c
|
|
| BLAKE2b-256 |
33687f8fc2fee8910d589fd3ddbd2b7668a25d923330b72c782222e871eb3dab
|