OK, this was much harder than I had expected…
Basically I am exchanging NSBundle's method that will be invoked by NSLocalizedString(…)
by using a category on NSBundle and a technique called isa-swizzeling
NSBundle+Language.h
#import <Foundation/Foundation.h>
@interface NSBundle (Language)
+(void)setLanguage:(NSString*)language;
@end
NSBundle+Language.m
#import "NSBundle+Language.h"
#import <objc/runtime.h>
static const char associatedLanguageBundle=0;
@interface PrivateBundle : NSBundle
@end
@implementation PrivateBundle
-(NSString*)localizedStringForKey:(NSString *)key
value:(NSString *)value
table:(NSString *)tableName
{
NSBundle* bundle=objc_getAssociatedObject(self, &associatedLanguageBundle);
return bundle ? [bundle localizedStringForKey:key
value:value
table:tableName] : [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],[PrivateBundle class]);
});
objc_setAssociatedObject([NSBundle mainBundle], &associatedLanguageBundle, language ?
[NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:language ofType:@"lproj"]] : nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
The AppDelegate will listen for LANGUAGE_WILL_CHANGE
notifications, set the language and broadcast a notification LANGUAGE_DID_CHANGE
AppDelegate.m
#import "AppDelegate.h"
#import "NSBundle+Language.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(languageWillChange:) name:@"LANGUAGE_WILL_CHANGE" object:nil];
NSString *targetLang = [[NSUserDefaults standardUserDefaults] objectForKey:@"selectedLanguage"];
[NSBundle setLanguage:targetLang?:@"en"];
return YES;
}
-(void)languageWillChange:(NSNotification *) noti
{
NSString *targetLang = [noti object];
[[NSUserDefaults standardUserDefaults] setObject:targetLang forKey:@"selectedLanguage"];
[NSBundle setLanguage:targetLang];
[[NSNotificationCenter defaultCenter] postNotificationName:@"LANGUAGE_DID_CHANGE" object:targetLang];
}
@end
A BaseViewController will post LANGUAGE_WILL_CHANGE
and listen for LANGUAGE_DID_CHANGE
BaseViewController.h
#import <UIKit/UIKit.h>
@interface BaseViewController : UIViewController
-(void) languageDidChange;
- (IBAction)switchLanguage:(id)sender;
@end
BaseViewController.m
#import "BaseViewController.h"
@interface BaseViewController ()
@property (weak, nonatomic) IBOutlet UIButton *englishButton;
@property (weak, nonatomic) IBOutlet UIButton *spanishButton;
@end
@implementation BaseViewController
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(languageDidChangeNotification:) name:@"LANGUAGE_DID_CHANGE" object:nil];
}
- (IBAction)switchLanguage:(id)sender {
NSString *localString;
if (self.englishButton == sender) {
localString = @"en";
} else if(self.spanishButton == sender){
localString = @"es";
}
if (localString) {
[[NSNotificationCenter defaultCenter] postNotificationName:@"LANGUAGE_WILL_CHANGE" object:localString];
}
}
-(void)languageDidChangeNotification:(NSNotification *)notification
{
[self languageDidChange];
}
-(void)languageDidChange
{
}
@end
Now any view controller that subclasses BaseViewController
can implement languageDidChange
to call NSLocalizedString
.
ViewController.m
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UILabel *label;
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self languageDidChange];
}
-(void)languageDidChange
{
self.label.text = NSLocalizedString(@"Hello World", nil);
self.imageView.image = [UIImage imageNamed:NSLocalizedString(@"image.png", nil)];
}
@end
Ad you see, I am simply localising the image name, I added the images en_image.png
and es_image.png
to the Images asset bundle and map them in the localizable strings
"image.png" = "en_image.png";
and
"image.png" = "es_image.png";
Result

You'll find this example code here: https://github.com/vikingosegundo/ImmidiateLanguageChange