9

I am using a single, localized Storyboard for my application. The app has to have an option to change language on the fly. So far I figured out how to change the language which is used during the app launch:

[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObjects:@"de", @"en", @"fr", nil] forKey:@"AppleLanguages"];
[[NSUserDefaults standardUserDefaults] synchronize]; //to make the change immediate

And managed to reload my entire application by removing all subviews of the key window, reloading the initial view controller from the storyboard and making it the new root view controller. (It has some problems, like managing open resources, but it can be done.)

The only thing I couldn't figure out is how do I force iOS to reload the storyboard in the new language? It seems storyboards are cached somehow, and -UIStoryboad:stroyboardWithName:fromNib does not reload it entirely, so it appears in the last language.

I've already considered two other options:

  • having different storyboard files for each language (it is unamanageable, I don't want to do this)

  • using event listeners in all of my view controllers which respond to a language change and set the new strings on the UI (which is also has to be called every time any interface element gets instantiated from the storyboard). This solution requires more time, than we'd like to spend with this, and also highly increases the possibility of bugs.

Is there some more clever way? I don't want to keep the current state of my application, so restarting it like it was killed and realunched seems a reasonable resolution.

gklka
  • 2,459
  • 1
  • 26
  • 53
  • This sounds like a really, really horrible way of approaching localisation. You'd be much better off creating a plist storing key value pairs for your translations, then updating each of the view elements in the viewWillAppear method of each view controller. This would allow for on the fly changing and be much easier to implement. – GuybrushThreepwood Jan 23 '14 at 10:27
  • How do you change the language? In app or via system wide settings? If it's the latter, the complete UI should be updated automatically (ofc only if you localised your strings/storyboard). If it's the former... does that really make sense? – Marc Jan 23 '14 at 10:27
  • @Ohnomycoco: `-viewWillAppear` sounds good, but it is basically the same as my second attempt with events. – gklka Jan 23 '14 at 11:11
  • @MarcMosby: I have a settings for setting language inside the app. This makse sense only because many people use their phones in english instead of their native language, because Apple has some very terrible OS translations (which does not apply to all apps). – gklka Jan 23 '14 at 11:13
  • But do they really want to switch languages every ten seconds? Why don't you let the user choose the language right after the app launched? Or make it a setting, and apply after each start of app? I think you are making things too complicated. – Marc Jan 23 '14 at 11:46
  • @MarcMosby I totally agree your fears, but this is the specification I've got and I have to implement this somehow :/ – gklka Jan 23 '14 at 11:47
  • I have a perfect solution on international dynamically switch languages: http://stackoverflow.com/a/26941905/3912901 – Blowing a big balloon Nov 15 '14 at 02:46

2 Answers2

15

So I found that the problem is with Base translation of my Storyboard, which means I had only one Storyboard file and one .strings file per language for it. It can't be done using Base translations, Apple does not support it.

So my workaround was the following:

  • Convert Base translations to one Storyboard each language (you can do it in Xcode when you click the Storyboard and set the type to Storyboard from Strings). You can delete the Base at the end.

  • Translate the strings in your Storyboards

  • In the button handler of your language change button do the following:

(I am using tab controller as root controller, you should change this to your root controller type.)

[[NSDefaults standardDefaults] setObject:[NSArray arrayWithObject:@"hu"] forKey:@"AppleLanguages"];
[[NSDefaults standardDefaults] synchronize];

GKAppDelegate *appDelegate = (GKAppDelegate *)[[UIApplication sharedApplication] delegate];        
UITabBarController* tabBar = (UITabBarController *)appDelegate.window.rootViewController;

// reload the storyboard in the selected language
NSString *bundlePath = [[NSBundle mainBundle] pathForResource:appDelegate.lang ofType:@"lproj"];
NSBundle *bundle = [NSBundle bundleWithPath:bundlePath];
UIStoryboard *storyBoard = [UIStoryboard storyboardWithName:@"Main" bundle:bundle];

// reload the view controllers
UINavigationController *placesTab = (UINavigationController *)[storyBoard instantiateViewControllerWithIdentifier:@"placesTab"];
UINavigationController *informationTab = (UINavigationController *)[storyBoard instantiateViewControllerWithIdentifier:@"informationTab"];

// set them
NSArray *newViewControllers = @[placesTab, informationTab];
tabBar.viewControllers = newViewControllers;
  • this is not enough. After this, strings form Storyboard should change language, but strings from code don't. For that you have to do this:

In your appname.pch file:

#define currentLanguageBundle [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:[[NSLocale preferredLanguages] objectAtIndex:0] ofType:@"lproj"]]

And you should change all of this:

NSLocalizedString(@"key", @"comment")

to this:

NSLocalizedStringFromTableInBundle(@"key", nil, currentLanguageBundle, @"");

Please notice, that I removed the second parameter, the comment. It seems comments with non-ASCII letters can cause trouble, so it's better to avoid them at this point.

  • now every string has to work fine. There is only one thing left: images which you set in your Storyboards disappear when you change language, because iOS does not find them in the bundle. The solution for this is to localize all of them (using the "Localize..." button in the File inspector). This obviously duplicates the images, but don't worry, will not affect your .ipa size, because ZIP handles duplicates very well. (I've got only a 800 → 850k size bump.)

The adventages of this method:

  • you can use system tools to gather strings to .strings files

  • the UI will not flicker when you change language

  • you don't have to write a lot of code or use a lot of outlets

  • language changig code needs to be ran only once, when the user pushes the button

I hope this helps, and maybe you can improve my solution.

gklka
  • 2,459
  • 1
  • 26
  • 53
  • Great answer, thanks, you can accept your own answer BTW – shannoga May 16 '14 at 06:24
  • 5
    This answer isn't sufficient with an auto-layout , it will not refresh view if you used different language direction (right-to-left) – wod May 26 '14 at 06:54
  • 1
    Worth noting is that setting an object for `AppleLanguages` in `NSUSerDefault` is not removed when closing the app nor when changing the system language. Make sure to reset it appropriately using `[[NSDefaults standardDefaults] removeObjectForKey:@"AppleLanguages"]` to use the system language again. – Sunkas Oct 21 '14 at 15:45
  • I have tested today on both iOS 7&8, you don't have to convert the .strings to storyboard and don't have to delete base as well, once you specify the bundle, it can automatically load the localized string – Wingzero Jul 22 '15 at 11:13
  • 1
    The above definition for 'currentLanguageBundle' is not efficient. Several calls are done each time a string is read with 'NSLocalizedStringFromTableInBundle'. Better to implement a class as appearing in Mauro's answer here: http://stackoverflow.com/questions/1669645/how-to-force-nslocalizedstring-to-use-a-specific-language – ishahak Oct 11 '15 at 18:14
0

I have the application with on the fly language change and I just pop all the controller and reinitialise the Navigation controller.

Retro
  • 3,985
  • 2
  • 17
  • 41
  • 3
    As I mentioned, closing and instaniating view controllers from storyboard does load them again in the language which was active when the app was started. – gklka Jan 23 '14 at 11:39
  • 1
    Well if you not have the Two Storybord then you have to use lots of IBOutles for your view element and set there title with NSLocalized string and change your resource folder from en.iproj to your.lproj – Retro Jan 23 '14 at 11:43
  • 1
    What I am really looking for is some kind of magic, which forces iOS to reload localisation, to have the newly instantiated view controllers appear in the right language. – gklka Jan 23 '14 at 11:53