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 &amp;quot;LanguageManager.h&amp;quot;

@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:@&amp;quot;lproj&amp;quot;];

    // Get the corresponding localized string.
	NSBundle* languageBundle = [NSBundle bundleWithPath:path];
	NSString* str = [languageBundle localizedStringForKey:key value:@&amp;quot;&amp;quot; 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:@&amp;quot;AppleLanguages&amp;quot;];
        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.

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.

Advertisements

About Toni Sala

Indie game designer and developer
This entry was posted in Home, idevblogaday, Tutorials and tagged , , , , , , , , . Bookmark the permalink.

5 Responses to A reusable localization manager class for iOS

  1. Luis Medel says:

    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. Marc Respass says:

    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);

    • Toni Sala says:

      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. I have written something similar for the CoconutKit framework: https://github.com/defagos/CoconutKit/blob/master/CoconutKit/Sources/Core/NSBundle+HLSDynamicLocalization.m

    One of the benefits of this approach is that you can still use the NSLocalizedString macros.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s