Im making an interactive children's story that only uses custom graphics and narrations.
I have all graphics in both English and Spanish.
I dont have any non-custom buttons, no text fields, not a single "standard" Apple app UI element anywhere. Everything is a custom PNG file.
Im simply looking for a method to allow the user to change from English to Spanish or vice-versa.
I created a custom English and a Spanish button already, which will highlight when pressed.
The app will (from what Ive read need to be restarted) and the app will then present its Spanish pages (and the Spanish text embedded in the PNG). Spanish narration will ensue.
At any point the user can elect to hit the English language button, and return to English if so desired.
Tabtale's "Mermaid Princess" app does this extremely well, and is far more than I need, but is a good example of what Im after. You click a button, change your region, the app restarts, and all the custom text and narrations result for the region youve selected.
I only have two regions (languages) so my need is much simpler.
But how in the world to actually pull this off ?
Cheers !
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