Automating Incremental Nib Localisation in Cocoa

· by Steve · Read in about 4 min · (705 Words)

Cocoa is already quite good at handling localisation - you have a folder per language where all your resources are loaded from, and you get tools for exporting your strings from your code (genstrings which exports NSLocalizedString macros) and from your user interface components (ibtool which can process nib files to export & import strings).

What isn’t covered very well in the docs though is how you might automate all this so that it’s efficient, incremental, and idiot-proof. And by idiot, I mean me after more than 7 days since reading the docs.

There are tools out there that purport to make this easier, such as AppleGlot and iLocalize, but to be honest I found they just made things more complicated. All I really wanted was an efficient way to batch export and import localised strings, to know what changed between versions, and to keep my previously localised strings automatically without overwriting them. Translators should need nothing more than a Unicode-capable text editor - no complex tools, no training, just their natural ability to turn English into a different language and type it into a text file.

I ended up with two Python scripts to handle this - one to handle the genstrings/NSLocalizedString route, which I wrote a while back and covered in a previous blog post. The second I wrote today to handle nibs, and also to build the individual language folders. I needed it to do this:

  1. Export strings from nibs for each language, merge with strings already translated, or
  2. Import strings for each language and create localised nibs based on the master English versions

Here’s my Python script to do this:

Usage: --basedir=<base> --stringsdir=<strdir>
    [--langdir=<langdir>] export|import

  export: Reads all the xib files in <base> and generates .strings files
        for every 2-char country code directory under <strdir>
        New strings will be added to existing files.
  import: Reads all the xib files in <base> and looks for .strings files
        in <strdir>/<lang> with the same name. Replaces strings in xib and
        writes the output to <langdir>/<lang>.lproj

So basically, you start with a base folder full of nib files (in English in my case), and point -stringsdir at a folder which contains (empty to begin with) subfolders corresponding to languages (like ‘fr’, ‘de’, ‘ja’ etc). When using the ‘export’ option, it will go through all your .xib files and generate strings from them using ibtool, once for each language. It will also turn these files into UTF-8 rather than UTF-16 (as ibtool exports them), so that you can put the files in hg/git without losing text diff support. You can give all these files to your translators.

The nice thing is that if you add a new control to your user interface, and you’ve already got translations in the -stringsdir folder, the script will merge the output from ibtool and the existing translation, only adding the new strings. You can then simply give the files to your translator with a ‘diff’ report (this is why you want UTF-8) to show them what they need to look at. The Apple localisation docs talk about ‘incremental’ localisation, but they totally miss this part - by ‘incremental’ they just mean taking a localised nib that you’ve manually changed and merging geometry settings, not generating a new strings file which doesn’t lose the previous translation.

The ‘import’ option does the reverse, it takes the translated strings and the base nibs, and creates localised nibs in the -langdir folder. Mostly the script just automates everything so it finds the files on its own, processes all languages, and highlights when there’s a missing translation (and uses the base version temporarily so the app still works). Note that I assume that you only manually author one nib (the base version) - personally I don’t have the resources to tweak geometry in every language so if you do, you’ll need to alter the script to cope with the ‘incremental’ localisation as Apple presents it in the docs.

Anyway, I just thought I’d post this in case it helps someone else - I experimented with a few different options and I found this one to be the best - it’s simple, uses tools you already have, and plays nice with source control. Result?