A reusable localization manager class for iOS

Today I would like to share with you a very simple and specific little class that may be very helpful if you encounter the same situation than me. Imagine that you need to manage the language of your app or game independently of the system language settings. It is kind a weird requirement but it is indeed a mandatory requirement for the iOS project I’m currently working on.

So, today I will share with you a little handy class that allows you to change the language of your app’s interface within the app and without the need of restarting. Moreover, the class uses the same dictionary system and files than the localization support offered by Apple.

http://dl.dropbox.com/u/7604222/waapp/builds/waapp.ipa

The Files

I have called this class “LanguageManager”. It is a static method based class. The needed data is stored in NSUserDefaults, so you won’t need to allocate it. Following you have the LanguageManager.h file:

#import <Foundation/Foundation.h>

// Supported languages.
#define kLMDefaultLanguage  @"en"
#define	kLMEnglish    @"en"
#define	kLMSpanish    @"es"

#define kLMSelectedLanguageKey  @"kLMSelectedLanguageKey"

@interface LanguageManager : NSObject {
}

+(BOOL) isSupportedLanguage:(NSString*)language;
+(NSString*) localizedString:(NSString*) key;
+(void) setSelectedLanguage:(NSString*)language;
+(NSString*) selectedLanguage;

@end

On line 3, we need to specify the supported languages and a default one. In this case, I’m supporting English and Spanish and the default one is English. On line 8 it is defined the key for the current selected language that will be stored on the NSUserDefaults.

We only have 4 methods that are self explanatory. As we will see later, the “localizedString:” method is the equivalent to the “NSLocalizedString()” macro from the Apple’s SDK.

Let’s have a look to the implementation of all the above methods:

#import "LanguageManager.h"

@implementation LanguageManager

+(BOOL) isSupportedLanguage:(NSString*)language {

    if ( [language isEqualToString:kLMEnglish] ) {
        return YES;
    }
    if ( [language isEqualToString:kLMSpanish] ) {
        return YES;
    }

    return NO;
}

+(NSString*) localizedString:(NSString*)key {
    NSString *selectedLanguage = [LanguageManager selectedLanguage];

    // Get the corresponding bundle path.
	NSString *path = [[NSBundle mainBundle] pathForResource:selectedLanguage ofType:@"lproj"];

    // Get the corresponding localized string.
	NSBundle* languageBundle = [NSBundle bundleWithPath:path];
	NSString* str = [languageBundle localizedStringForKey:key value:@"" table:nil];
	return str;
}

+(void) setSelectedLanguage:(NSString*)language {
    NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];

    // Check if desired language is supported.
    if ( [self isSupportedLanguage:language] ) {
        [userDefaults setObject:language forKey:kLMSelectedLanguageKey];
    } else {
        // if desired language is not supported, set selected language to nil.
        [userDefaults setObject:nil forKey:kLMSelectedLanguageKey];
    }
}

+(NSString*) selectedLanguage {
    // Get selected language from user defaults.
    NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    NSString *selectedLanguage = [userDefaults stringForKey:kLMSelectedLanguageKey];

    // if the language is not defined in user defaults yet...
    if (selectedLanguage == nil) {
        // Get the system language.
        NSArray* userLangs = [userDefaults objectForKey:@"AppleLanguages"];
        NSString *systemLanguage = [userLangs objectAtIndex:0];

        // if system language is supported by LanguageManager, set it as selected language.
        if ( [self isSupportedLanguage:systemLanguage] ) {
            [self setSelectedLanguage:systemLanguage];
            // if not...
        } else {
            // Set the LanguageManager default language as selected language.
            [self setSelectedLanguage:kLMDefaultLanguage];
        }
    }

    return [userDefaults stringForKey:kLMSelectedLanguageKey];
}

@end

Not so high tech over here. The only trick to use the system bundle independently from the system settings is in the “localizedString:” method. Here, I manipulate directly the language bundle to obtain the localized string I’m interested on.

In the “selectedLanguage:” method there is also some logic to obtain the current selected language, taking into account the possibility that the user has not yet modified the system language settings. So, in this particular case, the LanguageManager uses the language defined on the Settings app of the device.

Usage

The usage of this class is very very easy and transparent. Actually, it is like using the Apple’s localization support system. So, first you need to define your Localizable.strings files, as usual. One for each language you want to support.

When you need to obtain a localized string from one of the labels defined on your Localizable.strings files, you will use the “localizedString:” method just as it was the NSLocalizedString() macro:

NSString *aString = [LanguageManager localizedString:@"Hello"];

And that’s it! If you want to change the current language from within the app without restarting it, you use the following method:

[LanguageManager setSelectedLanguage:@"es"];

This line of code would set the current selected language to Spanish.

Conclusion

As you can see, it is a very simple and handy class. Probably the requirement it covers is not the most common one, but if you need to set the language of your app independently of the system language you will find this class very useful.

However, there is a problem. If you use libraries that are self localized with the Apple’s localization system, you will have to deal with it. You will need to modify those libraries or assume that there will be inconsistencies in the localization of your app’s interface in some cases.

You can download the source code of this class along with a demo project from GitHub.

Hope that helps! 🙂

This post is part of iDevBlogADay, a group of indie iPhone development blogs featuring two posts per day. You can keep up with iDevBlogADay through the web siteRSS feed, or Twitter.

9 thoughts on “A reusable localization manager class for iOS

  1. Hi Toni,

    Great post, as always.

    Only one thing. Maybe it’s better to cache the NSBundle on setSelectedLanguage, so you don’t create an instance in every call to localizedString? 🙂

  2. Nice post but I think what you’ve done is better done as functions. LanguageManager isn’t really a class since there is no data and no instance methods. If this were Java, then that is the only way to do it (all static methods) but it’s C so you can write functions. Instead of
    NSString *aString = [LanguageManager localizedString:@”Hello”];
    you have
    NSString *localizedString(NSString *key);

    • Well, LanguageManager is not a class from the design point of view, but it is conceptually. All the methods (or functions) are related and I think that makes sense to put them all into an entity.

      Moreover, LanguageManager actually has data. However I store it on NSUserDefaults.

      Maybe is a bit weird design, but I think it is very handy on the everyday work.

  3. Thank you for the wonderful solution. By the way, in my project, I have seperate UIs for Arabic and English. For an example if there is an XIB called hello.xib, then it is localized to Arabic and English because the Arabic content is displayed from right to left.
    So in your app, the strings to be displayed through the UI can be localized. But do you know how to switch to the Arabic UI, when the user clicks upon the language change button?
    Appreciate any help so much.

    Thank you,
    Amila.

Leave a Reply to Toni Sala Cancel reply