Overriding Your iPhone’s Localization In-App
For my upcoming flash card app for the iPad, I had the unusual challenge of needing to be able to change the app localization based on a user’s selection rather than just depending on the language globally set by the user on their iPhone. The reason is that I want the user to be able to change the language of the flash cards independent of the iPhone as a way of learning new languages.
Now, one thing I knew I wanted to do was to be able to leverage the Localizable.strings files so that I could keep a consistent, localized format for my strings. It would be easy to keep the list of flash card strings programmatically, but it seems silly to store these strings in a different format or write my own class to process the Localizable.strings` format.
So, how does one access a specific localization file? If you’re like me and always supporting localized resources, you are familiar with the NSLocalizedString method. If not, here’s how you use it to support localized strings:
flashCardLabel.text = NSLocalizedString(@"Apple", nil);
Your app will automatically swap in the appropriate string based on the key you provide. (The second argument is ignored in code but used as a comment when processed by genstrings, which I choose to set to nil.) If you Command-Double Click NSLocalizedString, you will find that it’s actually simply a macro for calling an NSBundle method:
[[NSBundle mainBundle] localizedStringForKey:key value:@"" table:nil]
This is the key to our solution. There are actually some other methods we an use instead that don’t force us to use the main bundle which is wired to give us strings based on the iPhone’s locale!
To accomplish this, we first have to load the bundle we need. Here’s how we do it:
// find the path to the bundle based on the locale
NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"Localizable" ofType:@"strings" inDirectory:nil forLocalization:@"es"];
// load it!
NSBundle *spanishBundle = [[NSBundle alloc] initWithPath:[bundlePath stringByDeletingLastPathComponent]];
The trick to initializing the bundle is to make sure you do not specify a filename. That tripped me up for a while.
Now, you can retrieve your localized string using NSLocalizedStringFromTableInBundle:
NSLocalizedStringFromTableInBundle(@"Apple", nil, spanishBundle, nil);
NSLocalizedStringFromTableInBundle lets you specify the specific bundle to pull from, though you do need to explicitly specify nil for the tableName. (This is hidden from you normally.)
Now, you can take this code, handle the bundle loading in one place, and simply make that final call whenever you need to know a specific, localized string. I recommend making your own macro as well since the 4-argument can make it tricky to remember the argument order. :-)
Enjoy your manzana!