Skip to main content
The @commandkit/i18n plugin provides internationalization (i18n) support for your CommandKit bot using i18next. Translate commands, responses, and interactions into multiple languages.

Installation

npm install @commandkit/i18n i18next i18next-fs-backend

Setup

1

Create translation files

Create a locales directory with translation files:
src/app/locales/
├── en-US/
│   ├── ping.json
│   └── greet.json
└── es-ES/
    ├── ping.json
    └── greet.json
2

Add translations

Create translation files matching your command names:
src/app/locales/en-US/ping.json
{
  "$meta": {
    "name": "ping",
    "description": "Check bot latency"
  },
  "response": "Pong! Latency: {{latency}}ms"
}
src/app/locales/es-ES/ping.json
{
  "$meta": {
    "name": "ping",
    "description": "Verificar la latencia del bot"
  },
  "response": "¡Pong! Latencia: {{latency}}ms"
}
3

Import and add the plugin

Import the i18n plugin and add it to your CommandKit instance.
src/index.ts
import { CommandKit } from 'commandkit';
import { i18n } from '@commandkit/i18n';

new CommandKit({
  client,
  plugins: [i18n()],
});
4

Use translations in commands

Access translations using the locale() method:
src/commands/ping.ts
import type { CommandRunOptions } from 'commandkit';

export const run: CommandRunOptions = async ({ interaction, context }) => {
  const { t } = context.locale();
  
  const latency = Date.now() - interaction.createdTimestamp;
  
  await interaction.reply(
    t('response', { latency })
  );
};

Translation Files

File Structure

Translation files must be organized by locale and named after commands:
src/app/locales/
├── en-US/           # US English
│   ├── command1.json
│   └── command2.json
├── es-ES/           # Spanish
│   └── command1.json
├── fr/              # French
│   └── command1.json
└── ja/              # Japanese
    └── command1.json

Translation Format

{
  "$meta": {
    "name": "command-name",
    "description": "Command description",
    "options": {
      "option-name": {
        "name": "Localized option name",
        "description": "Localized option description",
        "choices": {
          "choice-value": "Localized choice name"
        }
      }
    }
  },
  "translation-key": "Translation value",
  "nested": {
    "key": "Nested translation"
  }
}

Example: Complete Command Translation

{
  "$meta": {
    "name": "user",
    "description": "Get user information",
    "options": {
      "target": {
        "name": "target",
        "description": "The user to get info about"
      },
      "format": {
        "name": "format",
        "description": "Output format",
        "choices": {
          "detailed": "Detailed",
          "compact": "Compact"
        }
      }
    }
  },
  "title": "User Information",
  "fields": {
    "username": "Username",
    "id": "User ID",
    "created": "Account Created",
    "joined": "Joined Server"
  },
  "error": {
    "not-found": "User not found"
  }
}

Configuration

Customize i18n behavior with configuration options:
src/index.ts
import { i18n } from '@commandkit/i18n';

new CommandKit({
  client,
  plugins: [
    i18n({
      i18nOptions: {
        defaultNS: 'default',
        fallbackLng: 'en-US',
        debug: false,
        interpolation: {
          escapeValue: false,
        },
      },
    }),
  ],
});
i18nOptions
object
i18next initialization options.
defaultNS
string
default:"'default'"
Default namespace for translations.
fallbackLng
string
default:"'en-US'"
Fallback language when translation is missing.
debug
boolean
default:"false"
Enable debug logging.
See i18next documentation for all options.
plugins
array
Additional i18next plugins to use.
import ICU from 'i18next-icu';

i18n({
  plugins: [ICU],
})

Usage

In Commands

Access translations using the locale() method:
src/commands/greet.ts
import type { CommandRunOptions } from 'commandkit';

export const run: CommandRunOptions = async ({ interaction, context }) => {
  const { t, locale } = context.locale();
  
  await interaction.reply({
    content: t('greeting', { username: interaction.user.username }),
    embeds: [{
      title: t('embed.title'),
      description: t('embed.description'),
    }],
  });
};

Interpolation

Pass variables to translations:
{
  "welcome": "Welcome {{username}}! You are member #{{count}}."
}
t('welcome', { 
  username: 'John', 
  count: 1234 
});
// "Welcome John! You are member #1234."

Nested Keys

Access nested translation keys:
{
  "errors": {
    "permission": "You don't have permission",
    "notfound": "Resource not found"
  }
}
t('errors.permission');
// "You don't have permission"

Plurals

Handle pluralization:
{
  "items": "{{count}} item",
  "items_plural": "{{count}} items"
}
t('items', { count: 1 });  // "1 item"
t('items', { count: 5 });  // "5 items"

Specific Locale

Get translations for a specific locale:
import type { CommandRunOptions } from 'commandkit';

export const run: CommandRunOptions = async ({ interaction, context }) => {
  // Get user's locale
  const { t } = context.locale();
  
  // Force a specific locale
  const { t: tSpanish } = context.locale('es-ES');
  
  await interaction.reply({
    content: t('message'),
    embeds: [{
      title: tSpanish('alternative'),
    }],
  });
};

Hooks

locale()

Get localization context. Works in commands and events.
import { locale } from '@commandkit/i18n';

const { t, i18n, locale: currentLocale } = locale();

// Or specify a locale
const { t } = locale('fr');

fetchT()

Get a translation function for a specific locale and namespace:
import { fetchT } from '@commandkit/i18n';

const t = fetchT('en-US', 'common');
const translated = t('hello');

useI18n()

Get the i18next instance:
import { useI18n } from '@commandkit/i18n';

const i18n = useI18n();
const languages = i18n.languages;

Event Translations

Translate content in event handlers:
src/events/ready/log.ts
import { locale } from '@commandkit/i18n';
import type { Client } from 'discord.js';

export default function (client: Client) {
  const { t } = locale('en-US');
  console.log(t('bot-ready', { username: client.user!.tag }));
}
Create event translation files:
src/app/locales/en-US/ready.event.json
{
  "bot-ready": "Bot {{username}} is ready!"
}

Context Menu Commands

Translate user and message context menu commands:
{
  "$meta:user-ctx": {
    "name": "View Profile"
  }
}

Advanced Examples

Multi-Language Embed

src/commands/stats.ts
import type { CommandRunOptions } from 'commandkit';

export const run: CommandRunOptions = async ({ interaction, context }) => {
  const { t } = context.locale();
  
  const stats = await getServerStats(interaction.guildId!);
  
  await interaction.reply({
    embeds: [{
      title: t('stats.title'),
      fields: [
        {
          name: t('stats.members'),
          value: stats.members.toString(),
          inline: true,
        },
        {
          name: t('stats.messages'),
          value: stats.messages.toString(),
          inline: true,
        },
        {
          name: t('stats.channels'),
          value: stats.channels.toString(),
          inline: true,
        },
      ],
      footer: {
        text: t('stats.footer'),
      },
    }],
  });
};

Language Selector Command

src/commands/language.ts
import { useI18n } from '@commandkit/i18n';
import type { CommandRunOptions } from 'commandkit';

export const run: CommandRunOptions = async ({ interaction, context }) => {
  const { t, locale } = context.locale();
  const i18n = useI18n();
  
  const languages = i18n.languages.map((lang) => ({
    label: t(`languages.${lang}`),
    value: lang,
    default: lang === locale,
  }));
  
  await interaction.reply({
    content: t('select-language'),
    components: [{
      type: 1,
      components: [{
        type: 3,
        customId: 'language-select',
        options: languages,
      }],
    }],
  });
};

Supported Locales

CommandKit supports all Discord locales:
  • en-US - English (US)
  • en-GB - English (UK)
  • es-ES - Spanish
  • fr - French
  • de - German
  • it - Italian
  • pt-BR - Portuguese (Brazil)
  • ru - Russian
  • ja - Japanese
  • ko - Korean
  • zh-CN - Chinese (Simplified)
  • zh-TW - Chinese (Traditional)
  • And more…

Best Practices

Organize by Command

Name translation files after commands for automatic namespace matching.

Use Fallback Language

Always provide en-US translations as fallback.

Consistent Keys

Use the same translation keys across all locales.

Context Matters

Provide translators with context about where text appears.

API Reference

i18n(options?)

Creates the i18n plugin instance. Parameters:
  • options.plugins - Additional i18next plugins
  • options.i18nOptions - i18next initialization options
Returns: [I18nPlugin, I18nCliTemplatePlugin]

locale(locale?)

Gets the localization context. Parameters:
  • locale - Optional locale to use (defaults to detected locale)
Returns: CommandLocalizationContext
interface CommandLocalizationContext {
  t: (key: string, options?: Record<string, unknown>) => string;
  locale: Locale;
  i18n: i18n;
}

fetchT(lng, ns?, keyPrefix?)

Fetches a translation function. Parameters:
  • lng - Locale or array of locales
  • ns - Namespace (optional)
  • keyPrefix - Key prefix (optional)
Returns: TFunction

useI18n()

Gets the i18next instance. Returns: i18n Throws: Error if i18n plugin is not registered.

Build docs developers (and LLMs) love