Skip to content

Use Internationalization (I18n)

Internationalization (often abbreviated as "i18n" because there are 18 letters between "i" and "n") is the process of designing software so it can be adapted to various languages without engineering changes. Allay provides a powerful i18n system that allows plugins to support multiple languages easily.

How I18n Works in Allay

Allay's i18n system uses translation keys to represent translatable text. A translation key follows the format:

namespace:path.to.key

For example: - minecraft:commands.generic.syntax - A vanilla Minecraft translation key - allay:command.gc.completed - An Allay server translation key - myplugin:greeting.welcome - A custom plugin translation key

When you use a translation key, the i18n system looks up the corresponding text in the language file based on the current language setting.

Using the I18n API

Basic Translation

To translate a text that contains a translation key, use the I18n.get().tr() method:

import org.allaymc.api.message.I18n;

public class MyPlugin extends Plugin {
    public void someMethod() {
        // Translate using the default language
        String translated = I18n.get().tr("myplugin:greeting.welcome");

        // Translate with arguments (using %1, %2, etc. as placeholders)
        String withArgs = I18n.get().tr("myplugin:greeting.hello", playerName);
    }
}

Translate with Specific Language

You can also specify a language code to translate to:

1
2
3
4
5
6
7
8
import org.allaymc.api.message.I18n;
import org.allaymc.api.message.LangCode;

// Translate to Chinese (Simplified)
String chinese = I18n.get().tr(LangCode.zh_CN, "myplugin:greeting.welcome");

// Translate to Japanese
String japanese = I18n.get().tr(LangCode.ja_JP, "myplugin:greeting.welcome");

Available Language Codes

Allay supports the following language codes:

Code Language
en_US English (United States)
en_GB English (United Kingdom)
zh_CN Chinese (Simplified)
zh_TW Chinese (Traditional)
ja_JP Japanese
ko_KR Korean
de_DE German
fr_FR French (France)
fr_CA French (Canada)
es_ES Spanish (Spain)
es_MX Spanish (Mexico)
it_IT Italian
pt_BR Portuguese (Brazil)
pt_PT Portuguese (Portugal)
ru_RU Russian
pl_PL Polish
nl_NL Dutch
tr_TR Turkish
... and more

Add Language Files to Your Plugin

To add i18n support to your plugin, you need to create language files in the correct location.

File Structure

Place your language files in the assets/lang/ directory inside your plugin JAR:

your-plugin.jar
├── plugin.json
├── assets/
│   └── lang/
│       ├── en_US.json
│       ├── zh_CN.json
│       ├── ja_JP.json
│       └── ... (other languages)
└── your/plugin/classes/

Language File Format

Each language file is a JSON file with translation keys and their corresponding translations:

assets/lang/en_US.json
{
  "myplugin:greeting.welcome": "Welcome to our server!",
  "myplugin:greeting.hello": "Hello, %1!",
  "myplugin:command.mycommand.description": "This is my custom command",
  "myplugin:command.mycommand.success": "Command executed successfully for %1",
  "myplugin:error.notfound": "Could not find %1 in %2"
}
assets/lang/zh_CN.json
{
  "myplugin:greeting.welcome": "欢迎来到我们的服务器!",
  "myplugin:greeting.hello": "你好,%1!",
  "myplugin:command.mycommand.description": "这是我的自定义命令",
  "myplugin:command.mycommand.success": "成功为 %1 执行命令",
  "myplugin:error.notfound": "无法在 %2 中找到 %1"
}

Parameter Placeholders

You can use placeholders in your translations:

Placeholder Description
%1, %2, %3, ... Ordered parameters (recommended)
%s Unordered string parameter
%d Unordered integer parameter

Example:

{
  "myplugin:message.trade": "%1 traded %2 items with %3 for %4 emeralds"
}
// Result: "Steve traded 5 items with Alex for 10 emeralds"
I18n.get().tr("myplugin:message.trade", "Steve", 5, "Alex", 10);

Using I18n in Commands

You can use translation keys in command descriptions and output messages:

import org.allaymc.api.command.Command;
import org.allaymc.api.command.tree.CommandTree;
import org.allaymc.api.message.TrKeys;

public class MyCommand extends Command {
    public MyCommand() {
        // Use a translation key for the description
        super("mycommand", "myplugin:command.mycommand.description", "myplugin.command.mycommand");
    }

    @Override
    public void prepareCommandTree(CommandTree tree) {
        tree.getRoot()
                .playerTarget("player")
                .exec(context -> {
                    var players = context.getResult(0);
                    for (var player : players) {
                        // Use translation key in output (will be translated server-side)
                        context.addOutput("myplugin:command.mycommand.success", player.getDisplayName());
                    }
                    return context.success();
                });
    }
}

Send Translatable Messages to Players

When sending messages to players, you can use sendTranslatable() to send translatable messages:

1
2
3
4
5
6
// Send a translatable message to the player
// The message will be translated based on the player's language setting
player.sendTranslatable("myplugin:greeting.welcome");

// With arguments
player.sendTranslatable("myplugin:greeting.hello", player.getDisplayName());

Fallback Language

If a translation key is not found in the specified language, the system will fall back to en_US (English). This means you should always provide at least an en_US.json file with all your translation keys.

Best Practices

  1. Use a consistent namespace: Always prefix your translation keys with your plugin name (e.g., myplugin:category.key)

  2. Organize keys hierarchically: Use dots to organize keys into logical groups (e.g., myplugin:command.mycommand.description)

  3. Always provide English translations: Include en_US.json as the fallback language

  4. Use ordered parameters: Prefer %1, %2 over %s, %d for better readability and flexibility across languages (word order may differ)

  5. Keep translations concise: Especially for in-game messages that appear in limited space

Example: Complete Plugin with I18n

Here's a complete example of a plugin with i18n support:

plugin.json:

{
  "name": "MyPlugin",
  "entrance": "com.example.myplugin.MyPlugin",
  "version": "1.0.0"
}

assets/lang/en_US.json:

{
  "myplugin:greeting.welcome": "Welcome to the server, %1!",
  "myplugin:greeting.goodbye": "Goodbye, %1! See you next time!",
  "myplugin:command.greet.description": "Greet a player"
}

assets/lang/zh_CN.json:

{
  "myplugin:greeting.welcome": "欢迎来到服务器,%1!",
  "myplugin:greeting.goodbye": "再见,%1!下次再见!",
  "myplugin:command.greet.description": "向玩家问好"
}

MyPlugin.java:

import org.allaymc.api.eventbus.EventHandler;
import org.allaymc.api.eventbus.event.server.PlayerJoinEvent;
import org.allaymc.api.eventbus.event.server.PlayerQuitEvent;
import org.allaymc.api.message.I18n;
import org.allaymc.api.plugin.Plugin;
import org.allaymc.api.registry.Registries;

public class MyPlugin extends Plugin {
    @Override
    public void onEnable() {
        Registries.COMMANDS.register(new GreetCommand());
        getPluginManager().registerListener(this, new MyListener());
    }
}

class MyListener {
    @EventHandler
    public void onPlayerJoin(PlayerJoinEvent event) {
        var player = event.getPlayer();
        // Translate server-side and send to player
        String message = I18n.get().tr("myplugin:greeting.welcome", player.getDisplayName());
        player.sendMessage(message);
    }

    @EventHandler
    public void onPlayerQuit(PlayerQuitEvent event) {
        var player = event.getPlayer();
        String message = I18n.get().tr("myplugin:greeting.goodbye", player.getDisplayName());
        // Log to console
        System.out.println(message);
    }
}

Comments