Back Part 4 – Localize texts using PO files 4 | ♥ 6



PO files are part of the GNU GetText localization technique. It’s a very simple format that can be edited using various tools, like POEdit (available on Windows, Mac & Linux).

The philosophy

The “classic” way to do things is to write “dev English” sentences in your code, then use this Dev-English to kind-of translate to “proper English” (which will be used in the release version of your game), or translated to “proper whatever-language” you might want.

Note that your Dev-English are the keys for the translation texts. So changing your Dev-English texts will break the keys. The good thing is POedit handles that well, and properly suggests “lost” translations.

My implementation

My GetText implementation has 3 elements:

  • GetText.hx (from deepnightLibs): the lib that can read/write data files
  • Lang.hx (from gameBase): the main API to access texts from my code
  • LangParser.hx (from gameBase): a separate script that parses all source code files and extracts strings directly from them (built using the Haxe “makefile” langParser.hxml).

If you want to use the GetText lib for Haxe, you will need to install it:

haxelib install deepnightLibs


Marking translatable strings with Lang.hx

In your code, just use calls like: Lang._("Some translatable sentence"). This will allow our tools to extract all the strings that require some translation, so we can feed our PO tool.

For example:

function myStuff() {
  trace("This is an untranslated text");
  trace( Lang.t._("This text is meant to be translated, and will be extracted.") );

Extracting texts

You can run haxe langParser.hxml from the command line to extract all strings in Lang.t._(...) calls.

All these strings will be stored inside a POT file, which is a Catalog file.

Using POedit to translate

In my above example, the POT file is my “Dev-English” string catalog. It should be stored it in the /res/lang folder.

In POedit, start a new translation by setting the target language (could be French for example, or English again, if you want to turn your dev-English into something better).

Import your POT catalog using the menu Catalog -> Update from POT. This will add all the untranslated strings extracted by the LangParser script.

Translate or validate existing entries “as-is”, and save your PO file. It will also generate a MO file which is basically the optimized/binary version of the PO file.

Import translations into your code

Modify the Lang.init() method to import new languages. All your Lang.t._("My string that needs translation") calls will be replaced by the translated string found in your MO file on runtime.

Important: all the PO and MO files should be stored in /res/lang folder!

Advanced features

Strings with parameters

If you need some dynamic content inside a translatable string, you use the ::myVar:: system. To replace myVar in your string by the actual value you want, you need to provide an anonymous object to the Lang.t._(“…”) call.

See the following examples:

Lang.t._("Hello ::name::! How are you?", { });
Lang.t._("::v:: + ::v:: equals ::eq::", { v:5, eq:10 });

Note that the compiler will check any anomaly here:

Lang.t._("Hello ::name::! How are you?");
Lang.t._("Hello ::username::! How are you?", { name:"foo" });

Both will pop a compile error of variable name mismatching.

Adding translator comment

Sometimes, you may have some strings that could translate differently based on context.

For example, in your app, you might have a setting for some font size, and one possible value is the string “Large“. In this case, it means “Big“. For another setting, like a world size, you might have a setting string “Large” again, but this time it means “Vast”. They would translate in different ways in French for example.

To disambiguate these strings, you can use Translator comments:

Lang.t._("Large||for a font size"); 
Lang.t._("Large||for a level size");

Both will return “Large” at runtime by default, but they can now have distinct translations, if needed (so the return will vary in this case).

Warning: due to some technical limitations, you shouldn’t have ANY space around the “||” (double pipes) characters.

In POEdit, translation comments appear right in front of the corresponding entry:

Leave a Reply

Your email address will not be published. Required fields are marked *

  1. hosey:

    Any thoughts on making this work live. Switching languages without recompiling?

    June 16, 2020 at 19:34
  2. hosey:

    Thank you for a response. Ya. My biggest issue with non js/html browser releases currently is multiple fonts. I was hoping you had the silver bullet just lying around.

    May 1, 2020 at 02:33
  3. Hosey:

    Any thoughts on fonts? Like including Chinese and English?

    April 30, 2020 at 21:33
    • Sébastien Bénard:

      This tutorial only describes text extraction for translations purpose: having a complete workflow for a Chinese translation is something more complicated as it implies some specificity. It would require a dedicated guide :)

      April 30, 2020 at 22:34