Skip to content

Localization & Translation

Revxrsal edited this page Mar 16, 2022 · 4 revisions

In an attempt to make Lamp accessible to as much audience as possible, a simple and powerful localization API was integrated into the framework to make it easy to share and access languages and translations.

The localization API can be mainly accessed in the Translator interface.

This page will outline the API, how to include different languages, and how to create other sources for translation.

Overview

The localization is built entirely on the idea of locale-key-value pairs.

You provide a LocaleReader. A locale reader is a source of key-value pairs for a single locale (language). An example of a locale reader could be a JSON or YML file, a resource bundle, and whatnot.

Locale readers are then registered into a Translator. Every command handler maintains an instance of a Translator, accessible with commandHandler.getTranslator(). To add a locale reader:

commandHandler.getTranslator().add(my locale reader);

Built-in translations

Lamp includes built-in translation files for English (possibly more languages by the time you're reading this?), in the form of properties files as resource bundles:

  • lamp_en.properties - The common module
  • lamp-bukkit_en.properties for Bukkit
  • lamp-bungee_en.properties for Bungee
  • lamp-jda_en.properties for JDA
  • lamp-velocity_en.properties for Velocity
  • lamp-sponge_en.properties for Sponge

If you would like to completely override these messages using your own resource bundles, do the following:

  1. Create a properties file with the name of your bundle and then the language preceded by an underscore. Example: myproject_en.properties
  2. Override any message keys (see the original locale files) with the values you want
  3. Register your resource bundle using commandHandler.getTranslator().addResourceBundle("myproject"). By default, all language locales will be registered (if you have fr and es, they will be registered as well). It is possible to register for only certain locales using the overload of addResourceBundle.

Global locale and per-actor locale

It is possible to have a global locale in the translator, available in getTranslator().getLocale() and getTranslator().setLocale(Locale). This locale will be the default locale when an actor doesn't have a locale, when no locale is provided in Translator.get(String), or when the actor's locale does not contain a mapping for the given message.

Responding with localized messages

The Translator interface contains simple get(String) and get(String, Locale) methods for quickly grabbing the value for a specific locale.

CommandActor also contains shortcut methods: replyLocalized(String, ...) for replying and formatting a message, and errorLocalized(String, ...) for error messages.

@Description annotations can also refer to localized keys. Instead of directly supplying the value, use the following syntax #{...}:

@Description("#{my-message-key}")

This will automatically grab the mapping for the translator's current locale.

Custom locale readers

You may want to have your users be able to configure messages. In that case, it is possible to integrate custom LocaleReaders that read from your file. An example implementation in JSON:

import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import lombok.SneakyThrows;
import revxrsal.commands.locales.LocaleReader;

import java.io.BufferedReader;
import java.io.File;
import java.nio.file.Files;
import java.util.Locale;

public class JsonLocaleReader implements LocaleReader {

    private final JsonObject object;
    private final Locale locale;

    @SneakyThrows
    public JsonLocaleReader(File file, Locale locale) {
        this.locale = locale;
        try (BufferedReader reader = Files.newBufferedReader(file.toPath())) {
            object = (JsonObject) JsonParser.parseReader(reader);
        }
    }

    @Override public boolean containsKey(String key) {
        return object.has(key);
    }

    @Override public String get(String key) {
        return object.getAsJsonPrimitive(key).getAsString();
    }

    @Override public Locale getLocale() {
        return locale;
    }
}

Then, we can use it as follows:

commandHandler.getTranslator().add(new JsonLocaleReader("en_US.json", Locales.ENGLISH));
{
   "missing-argument": "No value specified for {0}.",
   "...": "..."
}

Formatting

Since messages may contain placeholders, we can use positioned placeholders {n} inside our strings.

format("Welcome to {0}, {1}! {1} is a nice name.", "Lamp", "Bob")

Results in:

Welcome to Lamp, Bob! Bob is a nice name.