Skip to main content

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-USen)
  • @use_locale decorator injects a bound Translator into slash-command callbacks
  • I18nCog mixin — built-in self.t() in every cog
  • check_missing_keys() — catch missing translations at startup or in CI
  • localize_commands() — translate command names, descriptions, and options
  • Zero required runtime dependencies — discord.py and PyYAML are 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 general section 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

discord_i18n-1.0.3.tar.gz (20.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

discord_i18n-1.0.3-py3-none-any.whl (21.4 kB view details)

Uploaded Python 3

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

Hashes for discord_i18n-1.0.3.tar.gz
Algorithm Hash digest
SHA256 ce620c7dcef76fee0c609c74f82f70b15e08a62c281b36259cbf3470cde4fd88
MD5 318f050998566f49cc97a0d8f9cd77d7
BLAKE2b-256 96e96089fdad1c9f4e71b658b90fe92b2f894fc14b60ba82a9b117898337f12d

See more details on using hashes here.

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

Hashes for discord_i18n-1.0.3-py3-none-any.whl
Algorithm Hash digest
SHA256 c20683de558da07c346dadadb1755abfbfd344dfda5d3a278ee536ccc68addcf
MD5 c1c18e7b2e943a91d4fd6649a11cbb0c
BLAKE2b-256 33687f8fc2fee8910d589fd3ddbd2b7668a25d923330b72c782222e871eb3dab

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page