Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best practice using NSLocalizedString

NSLocalizedString has a few limitations, but it is so central to Cocoa that it's unreasonable to write custom code to handle localization, meaning you will have to use it. That said, a little tooling can help, here is how I proceed:

Updating the strings file

genstrings overwrites your string files, discarding all your previous translations. I wrote update_strings.py to parse the old strings file, run genstrings and fill in the blanks so that you don't have to manually restore your existing translations. The script tries to match the existing string files as closely as possible to avoid having too big a diff when updating them.

Naming your strings

If you use NSLocalizedString as advertised:

NSLocalizedString(@"Cancel or continue?", @"Cancel notice message when a download takes too long to proceed");

You may end up defining the same string in another part of your code, which may conflict as the same english term may have different meaning in different contexts (OK and Cancel come to mind). That is why I always use a meaningless all-caps string with a module-specific prefix, and a very precise description:

NSLocalizedString(@"DOWNLOAD_CANCEL_OR_CONTINUE", @"Cancel notice window title when a download takes too long to proceed");

Using the same string in different places

If you use the same string multiple times, you can either use a macro as you did, or cache it as an instance variable in your view controller or your data source. This way you won't have to repeat the description which may get stale and get inconsistent among instances of the same localization, which is always confusing. As instance variables are symbols, you will be able to use auto-completion on these most common translations, and use "manual" strings for the specific ones, which would only occur once anyway.

I hope you'll be more productive with Cocoa localization with these tips!


As for autocompletition for strings in Xcode, you could try https://github.com/questbeat/Lin.


Agree with ndfred, but I would like to add this:

Second parameter can be use as ... default value!!

(NSLocalizedStringWithDefaultValue does not work properly with genstring, that's why I proposed this solution)

Here is my Custom implementation that use NSLocalizedString that use comment as default value:

1 . In your pre compiled header (.pch file) , redefine the 'NSLocalizedString' macro:

// cutom NSLocalizedString that use macro comment as default value
#import "LocalizationHandlerUtil.h"

#undef NSLocalizedString
#define NSLocalizedString(key,_comment) [[LocalizationHandlerUtil singleton] localizedString:key  comment:_comment]

2. create a class to implement the localization handler

#import "LocalizationHandlerUtil.h"

@implementation LocalizationHandlerUtil

static LocalizationHandlerUtil * singleton = nil;

+ (LocalizationHandlerUtil *)singleton
{
    return singleton;
}

__attribute__((constructor))
static void staticInit_singleton()
{
    singleton = [[LocalizationHandlerUtil alloc] init];
}

- (NSString *)localizedString:(NSString *)key comment:(NSString *)comment
{
    // default localized string loading
    NSString * localizedString = [[NSBundle mainBundle] localizedStringForKey:key value:key table:nil];

    // if (value == key) and comment is not nil -> returns comment
    if([localizedString isEqualToString:key] && comment !=nil)
        return comment;

    return localizedString;
}

@end

3. Use it!

Make sure you add a Run script in your App Build Phases so you Localizable.strings file will be updated at each build, i.e., new localized string will be added in your Localized.strings file:

My build phase Script is a shell script:

Shell: /bin/sh
Shell script content: find . -name \*.m | xargs genstrings -o MyClassesFolder

So when you add this new line in your code:

self.title = NSLocalizedString(@"view_settings_title", @"Settings");

Then perform a build, your ./Localizable.scripts file will contain this new line:

/* Settings */
"view_settings_title" = "view_settings_title";

And since key == value for 'view_settings_title', the custom LocalizedStringHandler will returns the comment, i.e. 'Settings"

Voilà :-)


In Swift I'm using the following, e.g. for button "Yes" in this case:

NSLocalizedString("btn_yes", value: "Yes", comment: "Yes button")

Note usage of the value: for the default text value. The first parameter serves as the translation ID. The advantage of using the value: parameter is that the default text can be changed later but the translation ID remains the same. The Localizable.strings file will contain "btn_yes" = "Yes";

If the value: parameter was not used then the first parameter would be used for both: for the translation ID and also for the default text value. The Localizable.strings file would contain "Yes" = "Yes";. This kind of managing localization files seems to be strange. Especially if the translated text is long then the ID is long as well. Whenever any character of the default text value is changed, then the translation ID gets changed as well. This leads to issues when external translation systems are used. Changing of the translation ID is understood as adding new translation text, which may not be always desired.