localizedStringForKey:value:table ignores Base localization - ios

My application need to be localised in 2 languages: German and English. German should be Base language. It means that app should always localize to German except the language on device is english (in this case it should be on english)
I have custom *.string files for localisation and use localizedStringForKey:value:table to localise strings.
When I have only base localisation everything works fine. But in case if I add english localisation, in some reason localizedStringForKey:value:table just ignore Base localisation and always use English (for all languages)
Here how it looks like after I've added english:
and here is how I localise strings:
[[NSBundle mainBundle] localizedStringForKey:#"key" value:#"" table:#"Shared"]
I'm testing on simulator and here is my language screen:
Does anybody know what could be a problem? Thanks in advance!

As I mentioned in the comments, you need to set the Localization native development region (CFBundleDevelopmentRegion) in the Info.plist to your language code. Xcode seems to set it to a en_GB or de_DE style region code, setting it to de (no region) will fix it. Note that by default it is en, but selecting United Kingdom or Germany will change it to use the longer codes.

I have another solution, I think it works for you:
NSString* NSCustomLocalizedString( NSString *key , NSString *comment)
{
NSString *rs = nil;
if( [[NSUserDefaults standardUserDefaults] integerForKey:KEY_LANGUAGE ] == e_language_japanese)
{
rs = NSLocalizedStringFromTable(key,#"Localizable.strings-ja",nil);
}
else
{
rs = NSLocalizedStringFromTable(key,#"Localizable.strings-en",nil);
}
return rs;
}

Related

Why [NSBundle localizedStringForKey] does not return the iPhone language localised string?

Why localizedStringForKey returns the localization of the development language first instead of the language of the iPhone? The "Bundle Search Pattern" in the documentation says:
If a .lproj folder exists for the preferred language, that localization is used. Otherwise, the bundle searches for a .lproj folder matching the next preferred language, and so on, until one is found. If there is no localization for a preferred language, the bundle chooses the development language localization.
Then the bundle searches for the resource in the following order [...]
But it does not make sense to me.
The test iPhone is in Chinese, french keyboard, the bundle contains 32 languages and the development language localization in English. I don't understand why it makes sense to return the English localization first. Why not is the Chinese one as this the iPhone language?
NSArray* arr = [[NSUserDefaults standardUserDefaults] objectForKey:#"AppleLanguages"];
// ( zh-Hant-FR, en-FR, fr-FR )
NSArray* localizations = [bundle localizations];
// ar,bg,ca,cs,da,de,el,en,es,fr,he,hu,id,it,ja,ko,ms,nb,nl,nn,pl,pt,pt_BR,ro,ru,sv,th,tr,uk,vi,zh-Hans,zh-Hant
NSArray* preferredLocalizations = [bundle localizations];
// same output
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
NSString* str = [bundle localizedStringForKey:#"hello" value:#"" table:#"mytable"];
// hello
How should I fix this? I would like localizedStringForKeyto always return the iPhone language localization whatever the development language.
Use this
NSLocalizedStringWithDefaultValue("requiredTranslationKey", #"StringFileName", [NSBundle mainBundle], #"", #"");

Finding the current localization of iOS app in code

Connected issue: Non-English default language for iOS App?
I am wondering how to check the current localization of my app in code.
For example:
I want to check if the app is running with German localization (.strings, storyboard)
This line of code:
[[[NSLocale preferredLanguages] objectAtIndex:0] isEqualToString:#"de"]
doesn't provide the correct answer. Because if a user's language list in device settings looks like this for example (french, german, english) and we have only localization for english and german, application is launched in german but the above condition isn't fulfilled.
My naive solution would be to create a localized string "LanguageCode" which is set to "de" in the german string file and to "en" in the english string file
e.g.:
if ([NSLocalizedString(#"LanguageCode", #"en, de etc.") isEqualToString:#"de"]) {
// german
}
NSString * language = [[NSLocale preferredLanguages] objectAtIndex:0];
This will return a two letter code for the currently selected language. "en" for English, "es" for Spanish, "de" for German, etc. For more examples, please see this Wikipedia entry (in particular, the 639-1 column):
Check the first entry of Bundle's preferredLocalizations property, which is:
an array of NSString objects containing language IDs for localizations
in the bundle. The strings are ordered according to the user's
language preferences and available localizations.
For example, if you're checking to see if your German localization are being used:
if Bundle.main.preferredLocalizations.first == "de" {
// Is using German
}

Localization fallback language

I want to localize a couple of strings in my iPhone app and it works so far for the 5 languages I've selected. However, I have one issue, if I haven't defined a key for a specific language I want it to choose english as a fallback because I know that I have defined every string for sure in english. The thing is that it's not very important that all keys are translated why it's okay to show english even if the iPhone is set to spanish, for instance.
Example:
en:
"HELLO" = "hello" ;
"PERSON" = "my friend" ;
es:
"HELLO" = "hola" ;
Expected Outcome:
If the iPhone is set to english I want it to say: hello, my friend and if the languange is set to spanish I want it to say hola, my friend. Currently the app will output hola, PERSON. (I use the NSLocalizedString method).
There is probably a really easy solution but I couldn't find any good ones.
Thanks for the help,
Mattias
The other answers were definitely correct but it wasn't exactly what I wanted. Instead I found another way to do it and wrote a static method that works perfectly for me in my environment and I wanted to add it here if anybody else wants to acheive the same thing:
+ (NSString *)localizedStringForKey:(NSString *)key table:(NSString *)table
{
NSString *str = [[NSBundle mainBundle] localizedStringForKey:key value:#"NA" table:table];
if ([str isEqualToString:#"NA"])
{
NSString *enPath = [[NSBundle mainBundle] pathForResource:#"en" ofType:#"lproj"];
return [[NSBundle bundleWithPath:enPath] localizedStringForKey:key value:#"NA" table:table];
}
return str;
}
the documentation of the function NSLocalizedString says:
Return Value
The result of invoking localizedStringForKey:value:table: on the main bundle and a nil table.
and for localizedStringForKey:value:table: says:
Return Value
A localized version of the string designated by key in table tableName. If value is nil or an empty string, and a localized string is not found in the table, returns key. If key and value are both nil, returns the empty string.
so you have localize the string for all your languages or change the value of the key you are using
The easy solution is to use the English text as the key. For example:
[NSString stringWithFormat:NSLocalizedString(#"%#, %#", "format for greeting"),
NSLocalizedString(#"Hello", "salutation"),
NSLocalizedString(#"my friend", "person")]
If there's no localized version of the string, the English key is used.

List application available language

I'm trying to print the list of languages supported by my app.
Let me explain a bit more : I have an app in two language (let's say english and french), and I'm looking for a way to print "en, fr" programmatically, so that if I added a third language, such as dutch, my function would then print "en, fr, nl"
You have to use NSBundle's localizations method
The code should be as follows:
NSArray *array = [[NSBundle mainBundle] localizations];
NSLog(#"Supported localizations : %#", array);

Change app language in iOS without restarting the app

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.

Resources