Skip to content

Building a simple translated plugin using I18N

BlackyPaw edited this page Aug 1, 2016 · 1 revision

This guide assumes that you have followed the steps outlined in the previous guide 'Getting Started' and have already set up your environment accordingly. If you have not yet read said article, please go ahead and read it before continuing with this one.

Before we can begin to bring translatable messages to your next project we will first have to talk about how translating messages with I18N works in general. Everything that follows will assume that you are working on Spigot but the same steps are repeatable for BungeeCord and other platforms as well.

Translations on themselves may be stored in any format anywhere you can access them later on: be it a .properties file bundled with your plugin's .JAR-file or be it a MySQL database with tables for each translation. The only requirement is that there is a TranslationStorage implementation which is able to load your translation from the underlying storage you chose beforehand. I18N's API ships with one translation storage implementation which you may use for loading your translations from .properties files which is available on all platforms. There are API extensions which add additional implementations or you could even roll your own but these are topics for sometime later. For this example, we will be using the PropertyTranslationStorage shipping with I18N per default.

So, to get this started with we first declare our new translation storage:

// Somewhere in your plugin's class:

I18N<UUID> i18n = I18NSpigotAdapter.getI18N();
PropertyTranslationStorage storage = new PropertyTranslationStorage( i18n, new File( this.getDataFolder(), "translations" ) );
storage.loadLanguage( Locale.ENGLISH );

Let's pick this code snippet apart line by line: the first line will grab an implementation of the I18N interface from our platform adapter (I18NSpigotAdapter in this case, could be I18NBungeeCordAdapter for BungeeCord, etc.). We then pass the returned instance into the constructor of our PropertyTranslationStorage and additionally pass him a directory from which it will load our .properties files later on. For this example, we have chosen to store our translation inside the plugins//translations folder of our local Spigot installation (note that we omitted the check whether or not the folder actually exists here which is obviously something that you will usually have to ensure beforehand). After that we instruct the translation storage to load the English translation of our plugin into memory. Actually, this will look for a file called en.properties inside the folder we passed to our translation storage on construction. The name of the file the storage will look for when loading a language into memory will always be named after the respective language's ISO639 language code (e.g. 'de' for German, 'es' for Spanish, 'fr' for French, etc.). As good practice you should generally strive to load all translations you provide for your plugin right when your plugin gets enabled as loading languages might be an expensive operation depending on the translation storage you chose.

After we have created the translation storage we are going to use we can now create a so called Localizer. A Localizer is basically a wrapper around our translation storage which might or might not provide you with additional functionality depending on whether your platform supports it (see the guide about injection for more information on this topic). Thus you should never pass around your translation storage but always a localizer instance. Creating one is done in only one line:

// i18n is the same instance we retrieved in the previous code snippet
// storage is the translation storage we just initialized
Localizer localizer = i18n.createLocalizer( storage );

So now, let's send an actual message to a player:

player.sendMessage( localizer.translateDirect( i18n.getLocale( player.getUniqueId() ), "example-message", player.getName() );

Basically, this line will send the player the translation of the message be named "example-message" in his or her native language and passes in the player's name as an additional argument.

The last puzzle piece we need to make our plugin work is the file called en.properties inside the plugins//translations folder we set up when creating the translation storage. For this example it simply contains one line:

example-message = Hello there, {0}!

As you can see we wrote {0} where one would usually expect the player's name to appear. You have probably guessed it already but this is where I18N will insert the first argument it was given after the name of the message when we invoked .translateDirect( ... ). In general you can pass in as many arguments as you please which you may access in your translation files as {0} for the first argument, {1} for the second argument, and so on.

So, if you have followed this guide so far and have compiled the snippets shown so far into a full plugin and added the respective translation file into the folder we set up above, you should have just created your first translated plugin. Obviously the code shown so far still seems a little bulky but fear not: if you go on reading the guides in this wiki you will soon learn some more techniques for achieving the same results with even less lines of code.

One final remark at this point: What you were shown in this guide may not only be used for sending player messages. You can also translate scoreboards, item stacks and almost anything else you'd like using the same techniques. Yet I strongly advise you to first learn about injection before actually trying to do anything of this sort as knowledge about it will simplify the code you had to write a lot.

In case you'd like to experiment a little on your own:
» Try to translate example-message into your own native tongue (or a foreign tongue if you are a native English speaker).
» Adjust the fallbackLocale setting in your I18N configuration files to the shorthand of your mother language to see the message you just translated popping up without you changing a single line of code in your own plugin.
» In case you have a MySQL database ready: Configure a database-backed locale resolver in I18N's configuration to see what happens if you generate your plugin message several times when changing your native language using /language several times.