Using gettext in a Cocoa application

Vim uses gettext (from libintl) to support localized messages like (I’m guessing) many *nix programs do. Mac OS X on the other hand uses bundles for localized messages so I had to struggle somewhat to get these two to cooperate when internationalizing MacVim. In this post I’ll describe how gettext and Cocoa decides which language to use for localized messages and how I managed to get both to choose the same language.

The “International” System Preference is used to control which language to use for localized messages. Unfortunately, Cocoa applications uses the “Language” tab setting whereas gettext uses the “Formats” tab. For example, if I choose English as my preferred language in the “Language” tab, but my “Formats” tab region set to “Sweden”, then gettext will use Swedish for messages (if available) but Cocoa will use English.

My preference was to get gettext to behave in the same manner as Cocoa instead of the other way around. Fortunately, this is made easy via the LANGUAGE environment variable. This is a colon separated list of preferred languages (e.g. sv:en) and it takes precedence over the LC_* and LANG environment variables when gettext chooses which language to use for localized messages. The list of user-preferred languages (as set in the “Language” tab) can be accessed via +[NSLocale preferredLanguages]. The following Objective-C code sets LANGUAGE to match the user’s choice:

NSArray *languages = [NSLocale preferredLanguages];
if (languages && [languages count] > 0) {
    int i, count = [languages count];
    for (i = 0; i < count; ++i) {
        if ([[languages objectAtIndex:i]
                isEqualToString:@"en"]) {
            count = i+1;
            break;
        }
    }
    NSRange r = { 0, count };
    NSString *s = [[languages subarrayWithRange:r]
            componentsJoinedByString:@":"];
    setenv("LANGUAGE", [s UTF8String], 0);
}

One note about this code: I’ve only included fallback languages up to (and including) “en” (English) since Vim has strings in English in the actual code and hence there is no .mo file for English translations. If I did not disregard all languages after “en” then English would never be used. Also note that the last parameter in the call to setenv() is 0 so that if the user has already set LANGUAGE then we do not override the user’s choice.