Skip to main content
Localization allows your bot’s commands to appear in different languages based on each user’s Discord language settings. This example demonstrates both automatic localization using resource bundles and manual localization.

Overview

JDA supports two approaches to localization:
  • Automatic: Using ResourceBundleLocalizationFunction with .properties files
  • Manual: Setting localizations directly on command objects
Both approaches allow you to translate:
  • Command names
  • Command descriptions
  • Option names and descriptions
  • Choice names

Automatic Localization with Resource Bundles

1

Create Resource Bundle Files

Create .properties files in your resources folder:MyCommands_es_ES.properties (Spanish):
ban.name=banear
ban.description=Banea a alguien
ban.member.name=miembro
ban.member.description=Banea a un miembro
ban.member.perm.name=permanente
ban.member.perm.description=Banea a un miembro permanentemente
ban.member.temp.name=temporal
ban.member.temp.description=Banea a un miembro temporalmente
ban.member.perm.user.name=usuario
ban.member.perm.user.description=El usuario a banear
MyCommands_fr.properties (French):
ban.name=bannir
ban.description=Bannir quelqu'un
ban.member.name=membre
ban.member.description=Bannir un membre
ban.member.perm.name=permanent
ban.member.perm.description=Bannir un membre de façon permanente
ban.member.temp.name=temporaire
ban.member.temp.description=Bannir un membre temporairement
ban.member.perm.user.name=utilisateur
ban.member.perm.user.description=L'utilisateur à bannir
2

Create Localization Function

Build a LocalizationFunction from your resource bundles:
LocalizationFunction localizationFunction = 
    ResourceBundleLocalizationFunction.fromBundles(
        "MyCommands",  // Base name of your .properties files
        DiscordLocale.SPANISH,
        DiscordLocale.FRENCH
    ).build();
3

Apply to Commands

Set the localization function on your slash command:
SlashCommandData slashCommandData = Commands.slash("ban", "Bans someone")
    .setLocalizationFunction(localizationFunction)
    .addSubcommandGroups(new SubcommandGroupData("member", "Bans a member")
        .addSubcommands(
            banSubcommand("perm", "Bans a member permanently"),
            banSubcommand("temp", "Bans a member temporarily")
        ));

How Resource Bundle Localization Works

The localization function automatically maps command structure to property keys:
Command path: /ban member perm
Property keys:
  - ban.name
  - ban.description
  - ban.member.name
  - ban.member.description
  - ban.member.perm.name
  - ban.member.perm.description
  - ban.member.perm.user.name (option)
  - ban.member.perm.user.description (option)
The property key structure follows the command hierarchy: command.subcommandGroup.subcommand.option.field

Manual Localization

For simpler commands or more control, use manual localization:
// Context menu command for messages
CommandData messageCommand = Commands.message("Show raw content")
    .setNameLocalization(DiscordLocale.SPANISH, "Mostrar el contenido en bruto")
    .setNameLocalization(DiscordLocale.FRENCH, "Afficher contenu brut");

// Context menu command for users
CommandData userCommand = Commands.user("Show avatar")
    .setNameLocalization(DiscordLocale.SPANISH, "Mostrar avatar")
    .setNameLocalization(DiscordLocale.FRENCH, "Afficher avatar");

Manual Description Localization

Commands.slash("hello", "Say hello")
    .setDescriptionLocalization(DiscordLocale.SPANISH, "Decir hola")
    .setDescriptionLocalization(DiscordLocale.FRENCH, "Dire bonjour")

Localizing Options and Choices

Options and choices are localized the same way:
private static SubcommandData banSubcommand(String name, String description) {
    return new SubcommandData(name, description)
        .addOptions(
            new OptionData(OptionType.STRING, "user", "The user to ban"),
            new OptionData(OptionType.INTEGER, "del_days", "The amount of days to delete messages")
                .addChoices(
                    new Command.Choice("1 Day", "1"),
                    new Command.Choice("7 Days", "7"),
                    new Command.Choice("14 Days", "14")
                )
        );
}
With resource bundles, add:
# Spanish
ban.member.perm.del_days.name=días_borrados
ban.member.perm.del_days.description=La cantidad de días para eliminar mensajes
ban.member.perm.del_days.choices.1_Day=1 Día
ban.member.perm.del_days.choices.7_Days=7 Días
ban.member.perm.del_days.choices.14_Days=14 Días

Supported Discord Locales

JDA supports all Discord locales through the DiscordLocale enum:
  • ENGLISH_US - English (United States)
  • ENGLISH_UK - English (United Kingdom)
  • SPANISH - Spanish
  • FRENCH - French
  • GERMAN - German
  • ITALIAN - Italian
  • JAPANESE - Japanese
  • KOREAN - Korean
  • CHINESE_CHINA - Chinese (China)
  • CHINESE_TAIWAN - Chinese (Taiwan)
  • PORTUGUESE_BRAZILIAN - Portuguese (Brazil)
  • RUSSIAN - Russian
  • TURKISH - Turkish
  • And more…

Custom Localization Function

You can implement your own LocalizationFunction for custom behavior:
public class CustomLocalizationFunction implements LocalizationFunction {
    @Override
    public Map<DiscordLocale, String> apply(String localizationKey) {
        // Load translations from database, API, or custom format
        Map<DiscordLocale, String> translations = new HashMap<>();
        
        // Example: wildcard support
        if (localizationKey.contains("user")) {
            translations.put(DiscordLocale.SPANISH, "usuario");
            translations.put(DiscordLocale.FRENCH, "utilisateur");
        }
        
        return translations;
    }
}
Benefits of custom implementations:
  • Load translations from databases or APIs
  • Support wildcards to reduce duplication
  • Use alternative file formats (JSON, YAML, etc.)
  • Dynamic language loading without recompilation

Complete Example

import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.interactions.DiscordLocale;
import net.dv8tion.jda.api.interactions.commands.Command;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.*;
import net.dv8tion.jda.api.interactions.commands.localization.LocalizationFunction;
import net.dv8tion.jda.api.interactions.commands.localization.ResourceBundleLocalizationFunction;

import javax.security.auth.login.LoginException;

public class LocalizationExample {
    public static void main(String[] args) throws LoginException {
        String token = "BOT_TOKEN_HERE";
        JDA jda = JDABuilder.createLight(token).build();

        // Automatic localization with resource bundles
        LocalizationFunction localizationFunction = 
            ResourceBundleLocalizationFunction.fromBundles(
                "MyCommands", 
                DiscordLocale.SPANISH, 
                DiscordLocale.FRENCH
            ).build();

        // Slash command with automatic localization
        SlashCommandData slashCommandData = Commands.slash("ban", "Bans someone")
            .setLocalizationFunction(localizationFunction)
            .addSubcommandGroups(new SubcommandGroupData("member", "Bans a member")
                .addSubcommands(
                    banSubcommand("perm", "Bans a member permanently"),
                    banSubcommand("temp", "Bans a member temporarily")
                ));

        // Manual localization for context menu commands
        CommandData messageCommand = Commands.message("Show raw content")
            .setNameLocalization(DiscordLocale.SPANISH, "Mostrar el contenido en bruto")
            .setNameLocalization(DiscordLocale.FRENCH, "Afficher contenu brut");

        CommandData userCommand = Commands.user("Show avatar")
            .setNameLocalization(DiscordLocale.SPANISH, "Mostrar avatar")
            .setNameLocalization(DiscordLocale.FRENCH, "Afficher avatar");

        // Register all commands
        jda.updateCommands()
            .addCommands(slashCommandData, messageCommand, userCommand)
            .queue();
    }

    private static SubcommandData banSubcommand(String name, String description) {
        return new SubcommandData(name, description)
            .addOptions(
                new OptionData(OptionType.STRING, "user", "The user to ban"),
                new OptionData(OptionType.INTEGER, "del_days", "The amount of days to delete messages")
                    .addChoices(
                        new Command.Choice("1 Day", "1"),
                        new Command.Choice("7 Days", "7"),
                        new Command.Choice("14 Days", "14")
                    )
            );
    }
}

Best Practices

  • Always provide English as the base language
  • Keep property file keys consistent with command structure
  • Test localizations with Discord’s language settings
  • Consider using manual localization for simple commands
  • Use resource bundles for complex command hierarchies
Command names in some languages must follow Discord’s naming rules (lowercase, no spaces, etc.). Some translations may need adjustment.

Build docs developers (and LLMs) love