Localizing an application

Depending on the kind of application, localization can become much more than having the strings that appear in the GUI available in different languages. If you came to learn about those more in-depth techniques, dealing with formatting and using ICU etc., this isn't the article you seek... This article discusses the relatively simple problem of localizing an app's GUI strings.

1. Changes to the source code

The needed changes to the source code are minimal. You just have to include the Catalog.h header and use the macros it provides on all the strings that appear in the GUI. The macros are:

  • B_TRANSLATION_CONTEXT gives a hint to the people that later translate your strings where those appear. So entering

    #define B_TRANSLATION_CONTEXT "MainWindow"

    will designate the following strings to belong to the "MainWindow".

  • B_TRANSLATE_SYSTEM_NAME is reserved for the app's name, e.g. here it's used in the window title of an "AwesomeApp":

    BWindow(frame, B_TRANSLATE_SYSTEM_NAME("AwesomeApp"), B_TITLED_WINDOW,
  • B_TRANSLATE is the usually deployed macro to mark a string to be translated. For example:

    BString *string = new BString(B_TRANSLATE("Invert selection"));
  • B_TRANSLATE_COMMENT allows you to leave an explanatory comment for the translators as a second parameter. For example:

    BString string = B_TRANSLATE_COMMENT("Connection refused", "As it's shown in the status bar, translate the string as short as possible.");

Here are two slightly more involved changes to make certain situations properly localizable.

  • You mustn't concatenate strings by just stringing them together. The order of the strings may be different for different languages. Assuming the "filename" is a variable holding a file name, you often see something like this:

    BString text(filename);
    text += " already exists.";

    Instead, you should reference that file name in a variable, that you then replace with the file name:

    BString text(B_TRANSLATE("%file% already exists."));
    text.ReplaceAll("%file%", filename);
  • Correctly handling plural forms is where a localization can shine or suck.
    This sucks:

    BString text(B_TRANSLATE("You have %number% mail(s)."));
    text.ReplaceAll("%number%", numberOfMailsAsString);

    While this looks a bit more complicated, but is much nicer:

    BString text;
    static BMessageFormat format(B_TRANSLATE("{0, plural,"
    	"=0{You have no mail.}"
    	"=1{You have one mail.}"
    	"other{You have # mails.}}"));
    format.Format(text, numberOfMailsAsInt);

    The "#" is replaced with the integer variable "numberOfMailsAsInt" when formatting it all and putting it into the "text" string in the last line.
    You'll have to include "MessageFormat.h".

    2. Changes to the Makefile

    You find a sample Makefile at /system/develop/etc/Makefile that includes Haiku's makefile-engine which greatly simplifies things.

    • You have to fill in the APP_MIME_SIG, e.g. "APP_MIME_SIG = application/x-vnd.AwesomeApp".

    • As you have included Catalog.h in the source, don't forget to add localestub to the LIBS section.

    • Under LOCALES you list the two-letter language codes you'll provide, e.g. "LOCALE = de en es fr ja".

    3. Creating "catkeys"

    "catkeys" are the plain text files that contain your strings, the context you assigned, optionally an explanatory comment, and the (to be) translated string.

    You start by creating the English version – en.catkeys – like this:

    make catkeys

    This will create a folder locales with the en.catkeys in it. You'll duplicate this file, rename it to e.g. de.catkeys and send it to your German friend for translation. Here are the first lines of a catkeys file:

    1	English	application/x-vnd.AwesomeApp	680205015
    Add to ignore list					ListView						Add to ignore list
    Found no matches.					ListView						Found no matches.
    Show application path					SetupWindow				Show application path

    The first line always starts with a "1", then the language, the app's signature, and a checksum. The checksum has to stay the same across all translations!
    Then follow the rows of strings; the 1st column for the original string, the 2nd column shows the context, then optionally a comment, and the translated string last.

    The translator must only work on the last column string (and possibly replace the "English" of the first line with their language name in English, e.g. "German". But that is only for human readers of that file, it doesn't matter when compiling the application).

    You and your translators can have a much more convenient and safe way to work with catkeys files by installing CatKeysEditor from the HaikuDepot. It provides a simple and clean interface to the otherwise confusing string jungle presented when opening catkeys in a text editor...

    4. Compiling the application

    After you've got back all those translated catkeys and put them in the locales folder, you compile your app like this:

    make bindcatalogs

    So, after the regular built, you do the make bindcatalogs to put the catalogs created from the catkeys into the executable's resources.

    If you need catkeys translations for your apps, you may want to have a look at the Pootle site for 3rd party projects.