Change app language in iOS without restarting the app - ios

I have seems some apps can change the language internally within the app without the need of restarting the app, I am wondering how they are implemented.
For example, for us using NSLocalizedString, I know it is possible to set the language at runtime at main.m when your AppDelegate is not initialized, but once it is initialized (particularly your view controller is created), change it has not effect until the next restart
[[NSUserDefaults standardUserDefaults]
setObject:[NSMutableArray arrayWithObjects:language, nil]
forKey:#"AppleLanguages"];
Anyone have idea how those dynamic language change can be done without restarting the app?

There's some discussion of other approaches here, in particular a notification based approach:
iOS: How to change app language programmatically WITHOUT restarting the app?
In my view there are really three tasks here:
(1) re-localization of resources automatically loaded from nibs. (for example if you dynamically instantiate another custom UIView from a nib, the "old" language strings and settings (images, text direction) will still be loaded)
(2) re-localization of strings currently displayed on the screen.
(3) re-localization of strings inserted by the developer (you) in program code.
Let's start with (3). If you look for the definition you will notice that NSLocalizedString is a macro. So if you don't want to change existing code too much, you can probably solve the problem of (3) by creating a new header file. In that header file, #undef and then re-#define NSLocalizedString to pick the localized string from the appropriate place--not the one that iOS defaults to, but one that you keep track of in some global variable (e.g., in an app delegate ivar). If you don't want to redefine NSLocalizedString but you still make your own alternative , you should probably still #undef NSLocalizedString if you don't want future developers to accidentally call it instead of the macro you replace it with. Not an ideal solution, but maybe the most practical.
As for (1), if you haven't done your localization in Interface Builder, but rather you do it dynamically in viewDidLoad, etc., no problem. You can use the same behavior just discussed (i.e., the modified NSLocalizedString, etc.). Otherwise you can either (a) implement a notification system as described in the link above (complicated), or (b) consider moving localization from IB to viewDidLoad, or (c) try overriding initWithNibName: and swap out the object loaded with the old language resources, with one loaded with the new language resources. This was an approach mentioned by Mohamed at the very bottom of this discussion: http://learning-ios.blogspot.ca/2011/04/advance-localization-in-ios-apps.html. He claims it causes problems (viewDidLoad isn't called). Even if it doesn't work, trying it out might point you towards something that does.
Finally, (2) is presumably the easiest task: just remove and re-add the current view (or in some cases, just redraw it).

the idea is to write a new macro like NSLocalizedString which should check if to take the translation from another specific bundle or not.
The method 2 in this article explain exactly how to do it.
In this particular case, the author doesn't use a new macro, but directly set a custom class for [NSBundle mainBundle].
I hope that #holex will understand the problem reading this.

I'm always using this way, it works perfectly, it might help you as well.
you should set all the texts with NSLocalizableString(...) for the UI for the current language in the -viewWillAppear: method of your every UIViewController.
using this way you (I mean, the users) don't need to restart the application after changing the language of iOS in the Settings.
of course, I'm using the Apple's standard localisation architecture.
UPDATE on (24 Oct 2013)
I've experienced the –viewWillAppear: method won't be performed for the actual view when the application enters to foreground; to solve that issue I also commit the procedure (see above) when I receive UIApplicationWillEnterForegroundNotification notification in the view.

My implementation uses a class to change the language and access the current language bundle. It's an example so if you were to use different languages than I am then change the methods to use your exact language codes.
This class will access the preferred languages from NSLocale and take the first object which is the language being used.
#implementation OSLocalization
+ (NSBundle *)currentLanguageBundle
{
// Default language incase an unsupported language is found
NSString *language = #"en";
if ([NSLocale preferredLanguages].count) {
// Check first object to be of type "en","es" etc
// Codes seen by my eyes: "en-US","en","es-US","es" etc
NSString *letterCode = [[NSLocale preferredLanguages] objectAtIndex:0];
if ([letterCode rangeOfString:#"en"].location != NSNotFound) {
// English
language = #"en";
} else if ([letterCode rangeOfString:#"es"].location != NSNotFound) {
// Spanish
language = #"es";
} else if ([letterCode rangeOfString:#"fr"].location != NSNotFound) {
// French
language = #"fr";
} // Add more if needed
}
return [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:language ofType:#"lproj"]];
}
/// Check if preferred language is English
+ (BOOL)isCurrentLanguageEnglish
{
if (![NSLocale preferredLanguages].count) {
// Just incase check for no items in array
return YES;
}
if ([[[NSLocale preferredLanguages] objectAtIndex:0] rangeOfString:#"en"].location == NSNotFound) {
// No letter code for english found
return NO;
} else {
// Tis English
return YES;
}
}
/* Swap language between English & Spanish
* Could send a string argument to directly pass the new language
*/
+ (void)changeCurrentLanguage
{
if ([self isCurrentLanguageEnglish]) {
[[NSUserDefaults standardUserDefaults] setObject:#[#"es"] forKey:#"AppleLanguages"];
} else {
[[NSUserDefaults standardUserDefaults] setObject:#[#"en"] forKey:#"AppleLanguages"];
}
}
#end
Use the class above to reference a string file / image / video / etc:
// Access a localized image
[[OSLocalization currentLanguageBundle] pathForResource:#"my_image_name.png" ofType:nil]
// Access a localized string from Localizable.strings file
NSLocalizedStringFromTableInBundle(#"StringKey", nil, [OSLocalization currentLanguageBundle], #"comment")
Change language in-line like below or update the "changeCurrentLanguage" method in the class above to take a string parameter referencing the new language code.
// Change the preferred language to Spanish
[[NSUserDefaults standardUserDefaults] setObject:#[#"es"] forKey:#"AppleLanguages"];

I was stuck in same issue, my requirement was "User can select language from drop down & application have to work according selected language (English or arabic)" What i have done i create two XIB and fetch XIB and Text according selected language. In this way user can select language. I used NSBundle for the same. like
For XIB
self.homeScreen = [[HomeScreen alloc] initWithNibName:#"HomeScreen" bundle:[CommonData sharedCommonData].languageBundle];
For Text
_lblHeading.text = [self languageSelectedStringForKey:#"ViewHeadingInfo"];
/**
This method is responsible for selecting language bundle according to user's selection.
#param: the string which is to be converted in selected language.
#return: the converted string.
#throws:
*/
-(NSString*) languageSelectedStringForKey:(NSString*) key
{
NSString* str=[[CommonData sharedCommonData].languageBundle localizedStringForKey:key value:#"" table:nil];
return str;
}

You need to load another bundle like this(where #"en" could be locale you need):
NSString *path = [[NSBundle mainBundle] pathForResource:#"en" ofType:#"lproj"];
NSBundle *languageBundle = [NSBundle bundleWithPath:path];
and make macros/function like NSLocalizedString which use your loaded bundle or use methods on that bundle directly like this
[languageBundle localizedStringForKey:key value:value table:tableName];
[[NSBundle mainBundle] localizations] lists all app localizations(including "Base").
Also I wrote helper class which does this(note that it has ReactiveCocoa as a dependency). It allows language change without app restart and sends current locale each time it's changed.

Related

UIWebview not loading the correct localized version of website

In my app I have a link in the menu that takes the user to our FAQ page online. I'm using a webview to load the page and so far so good.
But the problem is, my online page auto detects the user language and loads the correct localized version of the page.
If I open safari on the simulator and load my url, it gets redirected to the correct localized version of it, but using UIWebview on the app it loads the english (default) language.
I've done some research online, but couldn't find anything related to this matter.
Is there something I can/need to pass in order to the correct localized version to be loaded?
Thanks
EDIT:
I'm currently getting the phone language and passing in the URL, but what I'm looking for is for the UIWebview to behave like safari does, by calling the main URL and having the website choose the version based on your language.
You can get the app supported languages as an array of strings with [[NSBundle mainBundle] localizations]; and compare it with the system language like this:
+ (NSString *)preferredLanguage {
NSString *res = #"en";
NSArray *appSupportedLanguages = [[NSBundle mainBundle] localizations];
NSArray *appleLanguages = [NSLocale preferredLanguages];
if (appleLanguages && appleLanguages.count > 0) {
NSString *shortLangId = [appleLanguages.firstObject substringToIndex:2];
if ([appSupportedLanguages containsObject:shortLangId]) {
res = shortLangId;
}
}
return res;
}
The reason i'm using the shortened version here is that iOS 9 changed some languages' ids and it breaks my app — you should probably make some minor changes here. Nevertheless, this method allows you to get the language id you can use to open the proper FAQ page either like https://.../faq_fr.htm or with some server-side processing like https://...?lang=fr.

how to change app language inside app in ios sdk

I am thinking how to update all UILabel having storyboard and UITableviewController as subview while i change language in select language page of my app not device language.
can anyone help me how can i resolved this issues
You can manage this using Micro Like this
open .pch file in supporting file and write down like this and one more thing you need to decide languageId first that you pass as "number"
#define kCancelText(number) (number == 1 ? #"Cancel" : (number == 2 ? #"取消" : (number == 4 ? #"cancelar" :#"Cancel" )))
after define this you need to call like this
NSLog(#"Varible Print :: %#", kCancelText(1)); //Note : here 1 is language id .. I have define three language . 1. English 2. Mandarin 3. Spanish
You can use according to language id .. One more thing you don't need for import any this for this because its always global in app.
For this you can use NSNotificationCenter through which you can post your Notification with a key, now in all the classes where you need to make changes after posting this notification, you can just add a NSNotificationObserver with the same key, from where you can call a method and do changes what so ever you require.
For using NSNotification use this link.
For that you have to this steps:
1>first if you have two language like arabic and English then add two .strings file
Messages_en.strings
Messages_ar.strings
// this is Messages_en.strings file in this declare key and value
Messages_en.strings
"answer" = "Answer";
Messages_ar.strings
"answer" = "الإجابة";
2> make all label custom label like MyUILabel_custom and add all labels in all screens of this class so if you want to modify something in viewwillappear like in arabic you want to change alignment you can do in that
3> when button is clicked language should be stored in NSUserDefaults
and then you can access it everywhere
[[NSUserDefaults standardUserDefaults]setValue:#"en" forKey:#"lang"];
4> In Constants.h file add this line
#define IsArabic ([[[NSUserDefaults standardUserDefaults] valueForKey:#"lang"] isEqualToString:#"ar"]? YES : NO)
#define getString(key) NSLocalizedStringFromTable(key, (IsArabic ? #"Messages_ar" : #"Messages_en"), #"Fix it:-(")
5> you can check this way in all screens viewwillappear this way
self.lblRewardsPoint.text = getString(#"rewards_point");
6>In tableview also you can do this way you have to reload all data and if your data came from api then you have to recall the api .
-(NSString *)GetLocalString:(NSString *)text{
NSString *path;
path = [[NSBundle mainBundle] pathForResource:currentLanguageDocumentPath ofType:#"lproj"];
NSBundle* languageBundle = [NSBundle bundleWithPath:path];
NSString* str=[languageBundle localizedStringForKey:text value:#"" table:nil];
return str;
}
Here just add string files of languages in project and pass there file name in 'currentLanguageDocumentPath'.
text -> key of text .

iOS Localization (setting it in the app)

in my application I must have two languages, but the problem is that they should be switched by pressing a button in the application (on the first screen, or screen settings). As I understand, all the methods of localization (Localizing the Storyboard, Localizing Dynamic Strings) based on the language settings of the iPhone. The only option that comes to my mind - do it by the record in NSUserDefault about language preference, and in ViewDidLoad methods of all ViewControllers check the record about language and in accordance with it set strings, picture and so on. Can it be done on a more clever way?
Try this:
If de is the new language selected by the user. Also assure that you are reinitiating the current view.
[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObjects:#"de", nil]
forKey:#"AppleLanguages"];
See the below link and source code
http://learning-ios.blogspot.com/2011/04/advance-localization-in-ios-apps.html
and here is the code of one sample app https://github.com/object2dot0/Advance-Localization-in-ios-apps
All the best.
this tutorial & sample app helpful you more
localization-tutorial-for-ios
ios-programming-tutorial-localization-apps
Ya The way you are following for Localization is not up to Mark.
Please Follow this beautiful tutorial http://www.raywenderlich.com/2876/localization-tutorial-for-ios
You have to manually mange the currently selected language into user default and load the bundle folder according to selected language key like, i have done this and worked like charm
if ([currentLanguage isEqualToString:#"fr"]) { // If French.
languageBundle = nil;
NSString* path = [[NSBundle mainBundle] pathForResource:#"fr" ofType:#"lproj"];
languageBundle = [NSBundle bundleWithPath:path];
}

iOS - NSLocalizedString is only returning the key string

I'm having some trouble debugging my NSLocalizedString implementation. Should be simple, but whatever I do, the function only returns the KEY string.
I'm using XCode 4.5 and iOS6, so I:
Added a new file called File.strings.
In my project settings I added English and Spanish as language settings.
Clicked "Make Localized" in the file inspector, and made sure that both English and Spanish options were selected, and also that the Target membership to my target was selected.
Added "KEY" = "TestEnglish"; to my english File.strings
Added "KEY" = "TestSpanish"; to my spanish File.strings
Added NSLog(#"Localization: %#\n", NSLocalizedString(#"KEY", nil)); to my .m file.
When I run the app, the value "KEY" is always displayed printed in the NSLog.
To jump into this a bit more, I tried this as well:
NSString *path = [[NSBundle mainBundle] pathForResource:#"en" ofType:#"lproj"];
NSString *str = [[NSBundle bundleWithPath:path] localizedStringForKey:#"KEY" value:#"" table:nil];
NSLog(#"Localization: %#\n", str);
and still the value "KEY" is printed, yet, path is a valid path.
Does anyone have any clue how to debug this? I feel like I've read every SO question/answer out there, but none of the suggestions help.
I realize that NSLocalizedString returns the KEY string when it cannot match a key, but I don't see how I can debug why my app might not be matching the KEY.
I've also deleted/cleaned the app about a dozen times.
If you specify table:nil, then NSBundle will try to fetch the localization from the default table (the one in SOMELANG.lproj/Localizable.strings). If you have the localization elsewhere, you should explicitly specify the table using table:#"File" (or use the NSLocalizedStringFromTable() macro in a similar manner:
NSString *value = NSLocalizedStringFromTable(#"key", #"File", nil);
Rename the InfoPlist.strings file to Localizable.strings (double clic) and then you will get the correct string for that key.
In my case the issue was with the case of the string:
"bla.bla.blabla.BookSlot" whereas the Localizable.strings had it defined as "bla.bla.blabla.Bookslot"
So, double-check that the key string is in the correct case. Better yet, copy-paste.

Why does my iOS app only detect the current language properly on first run?

I am localizing my iOS app, and in the Simulator it runs correctly in my chosen language every time.
When testing on my iPhone 5, it only detects the language properly the first time the app runs. Every other time I recompile and run my app on the device, it detects "en" as the language, even though I am testing with Español ("es") selected.
I detect the language using:
[[[NSBundle mainBundle] preferredLocalizations] objectAtIndex:0]
I've also used:
[[NSLocale preferredLanguages] objectAtIndex:0]
Same result.
If I kill the app after the first run, and restart it on the device, it continues to detect the language properly.
But if I kill the app and then recompile/restart via Xcode after the initial run, it will load with "en" (English) detected instead.
After that, killing and re-starting the app continuously detects as English unless I delete the app completely, and recompile/reinstall/run the app via Xcode. The cycle then repeats... subsequent rebuild/restart without first deleting the app from the device results in misdetection.
All other apps on my device display with Spanish language the entire time. The entire UI shows in Spanish.
UPDATE: I've now tested on my iPad (3rd gen) also running iOS 6, and am experiencing the same behavior.
UPDATE 2:
In didFinishLaunchingWithOptions, I have this code to detect language: (language is an NSString*):
language = [[NSLocale preferredLanguages] objectAtIndex:0];
Followed by this debugging statement, to compare the value I'm getting, as well as a slightly different way of detecting it, just for debugging:
NSLog(#"Detected language: %# / %#", language, [[[NSBundle mainBundle] preferredLocalizations] objectAtIndex:0]);
The output shows as "Detected language: es / es" when the app works properly in Spanish mode, and then shows as "Detected language: en / en" when it doesn't. Still no idea why it decides to load as English sometimes...
UPDATE 4: I appreciate everybody's answers, and I've tried the various suggestions. Unfortunately I was unable to award the +100 bounty as none of the suggestions seemed to fix the issue. If someone does ultimate find a solution that works for me, I will award another +50 bounty to them at that time.
UPDATE 5: I have updated from Xcode 4.5 to 4.5.2, and experiencing this same issue.
UPDATE 6: I have now created a new test project from scratch, and it works perfectly fine! Obviously something must be wrong in the way my project is laid out, or perhaps in one of the data files. I guess my next journey will be to re-create the project from scratch, copying file data over one by one...
UPDATE 7 (MONTHS LATER): Sadly, I am again facing this issue after temporarily resolving it (seemingly) by painstakingly recreating my project. On first load, the language is correctly rendered, but on subsequent loads, it reverts back to English.
SOLVED See my final solution below. Thanks for the help everyone. I may dole out some of the bounty since it will go to waste anyway.
I have FINALLY solved this problem after many months! Thanks to all for the help (I also had some good back and forth with an Apple developer via the dev channels).
TL;DR: I was accidentally syncing language preferences (among many other unexpected things) between devices, using my app's iCloud key value store (via MKiCloudSync)! Read on...
I am using a third-party class called MKiCloudSync, which helps with syncing [NSUserDefaults standardUserDefaults] to my app's iCloud key value store. My intention when I began using it was to let it handle some user favorites syncing in the background.
However, not understanding how standardUserDefaults works, what I didn't realize is that there are a lot of other things being written into standardUserDefaults other than just my own custom app settings!
So what was happening was this:
Start the app up for the first time. Fresh standardUserDefaults in place, and the internal "AppleLanguages" key that stores the ordered list of language preferences is correct based on the current device choices.
App displays properly in the designated language.
In the background, MKiCloudSync syncs ALL standardUserDefaults to iCloud. Conversely, if you had run this app elsewhere, say with an English set device, that device would have also synced it's language settings up to iCloud. So now this current running app is actually having it's language preferences overwritten.
BOOM ... next time the app is run, no matter what you have selected on the device, it's whatever was pulled down from iCloud that will be used as the default language!
What I plan to do to solve the issue with my next app update:
Use a forked version of MKiCloudSync that allows for syncing only whitelisted key names.
Add code that will do a one-time cleanup, first cleaning out the iCloud keystore for my app, then (based on this SO answer), calling this code to reset the user defaults:
NSString *appDomain = [[NSBundle mainBundle] bundleIdentifier];
[[NSUserDefaults standardUserDefaults] removePersistentDomainForName:appDomain];
In my testing so far, this sort of solves the issue... unfortunately, the user would have to restart the app for the language fix to kick in. However, my guess is most users are not experiencing this issue, as they are unlikely to be using multiple devices with different default languages.
In settings->general->international, there is an option to set the location language etc. if you set that to the language you are trying to test it will work if your code is correct.
I tested your steps on my iPhone 5 without issues. This leads me to think there's something else at play here: most probably there's something interferring with the way in which you're reading the locale value.
The steps I'd recommend you take to help you debug this issue are:
Post the complete code of the method in which you're obtaining the preferred language value. Make sure the method is executed each time the app is run.
Make sure the code you post includes the location of the NSLog directive you're using to test for the language setting.
Are you storing the preferred language somewhere else after the first run?
Try with following code:
LocalizationSystem.h===
#import <Foundation/Foundation.h>
#define AMLocalizedString(key, comment) \
[[LocalizationSystem sharedLocalSystem] localizedStringForKey:(key) value:(comment)]
#define LocalizationSetLanguage(language) \
[[LocalizationSystem sharedLocalSystem] setLanguage:(language)]
#define LocalizationGetLanguage \
[[LocalizationSystem sharedLocalSystem] getLanguage]
#define LocalizationReset \
[[LocalizationSystem sharedLocalSystem] resetLocalization]
#interface LocalizationSystem : NSObject {
NSString *language;
}
+ (LocalizationSystem *)sharedLocalSystem;
//gets the string localized
- (NSString *)localizedStringForKey:(NSString *)key value:(NSString *)comment;
//sets the language
- (void) setLanguage:(NSString*) language;
//gets the current language
- (NSString*) getLanguage;
//resets this system.
- (void) resetLocalization;
#end
LocalizationSystem.m===
#import "LocalizationSystem.h"
#implementation LocalizationSystem
//Singleton instance
static LocalizationSystem *_sharedLocalSystem = nil;
//Current application bundle to get the languages.
static NSBundle *bundle = nil;
+ (LocalizationSystem *)sharedLocalSystem{
#synchronized([LocalizationSystem class])
{
if (!_sharedLocalSystem){
[[self alloc] init];
}
return _sharedLocalSystem;
}
// to avoid compiler warning
return nil;
}
+(id)alloc{
#synchronized([LocalizationSystem class])
{
NSAssert(_sharedLocalSystem == nil, #"Attempted to allocate a second instance of a singleton.");
_sharedLocalSystem = [super alloc];
return _sharedLocalSystem;
}
// to avoid compiler warning
return nil;
}
- (id)init{
if ((self = [super init]))
{
//empty.
bundle = [NSBundle mainBundle];
}
return self;
}
// Gets the current localized string as in NSLocalizedString.
- (NSString *)localizedStringForKey:(NSString *)key value:(NSString *)comment{
return [bundle localizedStringForKey:key value:comment table:nil];
}
// If this function is not called it will use the default OS language.
// If the language does not exists y returns the default OS language.
- (void) setLanguage:(NSString*) l{
NSLog(#"preferredLang: %#", l);
NSString *path = [[ NSBundle mainBundle ] pathForResource:l ofType:#"lproj" ];
if (path == nil)
//in case the language does not exists
[self resetLocalization];
else
bundle = [[NSBundle bundleWithPath:path] retain];
[[NSUserDefaults standardUserDefaults] setObject: [NSArray arrayWithObjects:l, nil] forKey:#"AppleLanguages"];
}
// Just gets the current setted up language.
// returns "es","fr",...
//
// example call:
// NSString * currentL = LocalizationGetLanguage;
- (NSString*) getLanguage{
NSArray* languages = [[NSUserDefaults standardUserDefaults] objectForKey:#"AppleLanguages"];
NSString *preferredLang = [languages objectAtIndex:0];
return preferredLang;
}
// Resets the localization system, so it uses the OS default language.
//
// example call:
// LocalizationReset;
- (void) resetLocalization{
bundle = [NSBundle mainBundle];
}
#end
This code works perfectly as you mentioned.
It worked for me and that game is live now app store, if you want to check(HueShapes).
Do you by chance use NSUserDefaults to save something language related?
Look into your Simulator App directory -> Library -> Preferences -> <YourAppBundleName>.plist
See: How to force NSLocalizedString to use a specific language for description of the NSUserDefaults method of setting a language.
Perhaps you just save your language and thus detection just returns the saved value.

Resources