How to change localization internally in your iOS application
Unfortunately, there’s no official way provided by Apple for this purpose. Let’s look at two methods for solving this problem.
Method #1
Apple provides a way to specify an application-specific language, by updating the “AppleLanguages” key in NSUserDefaults. For example:
[[NSUserDefaults standardUserDefaults] setObject:@"fr" forKey:@"AppleLanguages"]; [[NSUserDefaults standardUserDefaults] synchronize];
For working this method, you’ll have to set it before UIKit initialized.
#import <UIKit/UIKit.h> #import "AppDelegate.h" #import "LanguageManager.h" int main(int argc, char * argv[]) { @autoreleasepool { [[NSUserDefaults standardUserDefaults] setObject:@"fr" forKey:@"AppleLanguages"]; [[NSUserDefaults standardUserDefaults] synchronize]; return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } }
The problem of this method is that the app has to be relaunched to take effect.
Method #2
The solution is to swap the mainBundle of our application as soon as the user changes their language preferences inside the app.
See the category for NSBundle.
Header:
#import <Foundation/Foundation.h> @interface NSBundle (Language) + (void)setLanguage:(NSString *)language; @end
Implementation:
#import "NSBundle+Language.h" #import <objc/runtime.h> static const char kBundleKey = 0; @interface BundleEx : NSBundle @end @implementation BundleEx - (NSString *)localizedStringForKey:(NSString *)key value:(NSString *)value table:(NSString *)tableName { NSBundle *bundle = objc_getAssociatedObject(self, &kBundleKey); if (bundle) { return [bundle localizedStringForKey:key value:value table:tableName]; } else { return [super localizedStringForKey:key value:value table:tableName]; } } @end @implementation NSBundle (Language) + (void)setLanguage:(NSString *)language { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ object_setClass([NSBundle mainBundle],[BundleEx class]); }); id value = language ? [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:language ofType:@"lproj"]] : nil; objc_setAssociatedObject([NSBundle mainBundle], &kBundleKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } @end
In this method, a problem that may arise is updating elements on active screens. You can reload your rootViewController from our application delegate, will always work reliably.
- (void)reloadRootViewController { AppDelegate *delegate = [UIApplication sharedApplication].delegate; NSString *storyboardName = @"Main"; UIStoryboard *storybaord = [UIStoryboard storyboardWithName:storyboardName bundle:nil]; delegate.window.rootViewController = [storybaord instantiateInitialViewController]; }
All code you can see in this repository. With a simple example.
Please, use for free and like it
Note: In the example project by default the app uses method #2. You can disable this. Just comment define USE_ON_FLY_LOCALIZATION.
More details on the blog here.