Menu

Language Changer: in-app language selection in iOS

Alan Chung By Alan Chung

Many apps on the iOS app store have the ability to change the language independently of the language setting of the device, but this is not catered for in the standard iOS localisation features. Carry on reading to find out how to change the language of your app independently of the device setting.

Setting up

Apple provides features for internationalisation of strings, storyboards and image files within your app, but the one we will focus on is strings (and images indirectly).

Localised strings can be found in the Localisable.strings file. This won’t be in your project by default, so go ahead and add it by navigating to File > New > File… (⌘N). Then pick Strings File from the Resources section. Be sure to name it Localizable.strings as this is the standard place where the operating system looks for your translations.

Strings file dialog in XCode

You’ll then need to select the new Localizable.strings file in the navigator on the left, then click the Localize… button on the right hand panel of Xcode. Then add the languages you want to cater for.

Language Localisation dialog in XCode

Usually we would use the NSLocalizedString() macro in the place of an ordinary string when we want to localise a particular string. When running the app, this displays the appropriate string using the language setting of the device if the string has a value in the Localizable.strings file. However we want to show the string for the language the user has selected within the app rather than for the language setting of the device, so we need to write our own version of NSLocalizedString(). But before we do that, we need a way to keep track of which language the user has selected.

The model

The Locale class in the example project is a model for this. Properties include a name to display, a language code and a country code for the flag images which we’ll look at soon.

#import <Foundation/Foundation.h>

@interface Locale : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *languageCode;
@property (nonatomic, copy) NSString *countryCode;

- (id)initWithLanguageCode:(NSString *)languageCode countryCode:(NSString *)countryCode name:(NSString *)name;

@end

The manager

In the LanguageManager class we have our method that acts as a modified version of NSLocalizedString(). It looks in the relevant .lproj folder of your project for a Localisable.strings file, then gets the translated string for the given key. There’s also a macro in Constants.h to mimic the syntax for usage of NSLocalizedString(). I’ve very creatively called it CustomLocalisedString().

- (NSString *)getTranslationForKey:(NSString *)key {
    
    // Get the language code.
    NSString *languageCode = [[[NSUserDefaults standardUserDefaults] stringForKey:DEFAULTS_KEY_LANGUAGE_CODE] lowercaseString];

    // Get the relevant language bundle.
    NSString *bundlePath = [[NSBundle mainBundle] pathForResource:languageCode ofType:@"lproj"];
    NSBundle *languageBundle = [NSBundle bundleWithPath:bundlePath];
    
    // Get the translated string using the language bundle.
    NSString *translatedString = [languageBundle localizedStringForKey:key value:@"" table:nil];
    
    if (translatedString.length < 1) {
        
        // There is no localizable strings file for the selected language.
        translatedString = NSLocalizedStringWithDefaultValue(key, nil, [NSBundle mainBundle], key, key);
    }
    
    return translatedString;
}

The LanguageManager class also handles the selection of a different Locale. To persist between launches, the example app uses the NSUserDefaults, but this can be done using another method if you prefer.

- (void)setLanguageWithLocale:(Locale *)locale {
    
    [[NSUserDefaults standardUserDefaults] setObject:locale.languageCode forKey:DEFAULTS_KEY_LANGUAGE_CODE];
    [[NSUserDefaults standardUserDefaults] synchronize];
}

Putting it together

Finally we need a way to refresh the strings that are on display when the user picks a different Locale. (Note: this doesn’t need to be done for every view controller in your app, just the ones where the locale can change during the lifetime of the view controller.) See the -setupLocalisableElements method in the ViewController class. You might notice that this also changes the image being displayed. In this example we are using the countryCode property on the selected Locale to get the name of the appropriate image file.

- (void)setupLocalisableElements {
    
    self.title = CustomLocalisedString(@"Title", @"The string to display in the navigation bar.");
    
    self.textView.text = CustomLocalisedString(@"Text", @"The string to display in the text view.");
    self.textView.contentOffset = CGPointZero;
    
    // Flag images are named after the country code of the Locale.
    UIImage *flagImage = [UIImage imageNamed:[[LanguageManager sharedLanguageManager] getSelectedLocale].countryCode];
    [self.flagImageView setImage:flagImage];
}

The example project also selects a Locale automatically on first launch depending on the language setting of the device. This is done in the -application:didFinishLaunchingWithOptions: method in the AppDelegate.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    
    NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    LanguageManager *languageManager = [LanguageManager sharedLanguageManager];
    
    // Check whether the language code has already been set.
    if (![userDefaults stringForKey:DEFAULTS_KEY_LANGUAGE_CODE]) {
        
        NSLog(@"No language set - trying to find the right setting for the device locale.");
        NSLocale *currentLocale = [NSLocale currentLocale];
        
        // Iterate through available localisations to find the matching one for the device locale.
        for (Locale *localisation in languageManager.availableLocales) {
            if ([localisation.languageCode caseInsensitiveCompare:[currentLocale objectForKey:NSLocaleLanguageCode]] == NSOrderedSame) {
                [languageManager setLanguageWithLocale:localisation];
                break;
            }
        }
        
        // If the device locale doesn't match any of the available ones, just pick the first one.
        if (![userDefaults stringForKey:DEFAULTS_KEY_LANGUAGE_CODE]) {
            NSLog(@"Couldn't find the right localisation - using default.");
            [languageManager setLanguageWithLocale:languageManager.availableLocales[0]];
        }
    }
    else {
        NSLog(@"The language has already been set :)");
    }
    return YES;
}

There you have it, a way to change the locale of your app without having to change your device setting. Take a look on GitHub for the full source code of the example project. Now go forth and have a bash at changing languages in your own app!

Categories

Tutorials
comments powered by Disqus

We’re a top UK digital agency - wahoo!

By Sophie Hardbattle

We're Eden Agency - one of the UK's top digital agencies.

Read more

An Introduction to Building a SpriteKit-UIKit Hybrid App

By Alan Chung

A guide to help you get started on making an iOS app that seamlessly combines the visual punch of SpriteKit with the practicality of UIKit.

Read more

How to build a material design prototype using Sketch and Pixate - Part Three

By Mike Scamell

Part three of a three-part tutorial on building a material design prototype. This section focuses on adding more detail to the prototype in Pixate.

Read more

We're Hiring - Web Developer Role

By Craig Gilchrist

We are currently seeking a full time full-stack web developer to join the team at our Knaresborough office.

Read more

How to build a material design prototype using Sketch and Pixate - Part Two

By Mike Scamell

Part two of a three-part tutorial on building a material design prototype. This section focuses on creating a interactive login screen in Pixate.

Read more