I'm building an application that will be localized in a future version, so I want to setup it to be ready for that.
At the moment I have only one language (French) and the fr.lproj folder contains the Localizable.strings with french translations for the related keys.
The problem is that If I set my device to English I don't receive the French default translations, but I see the Keys name that I use in NSLocalizedString.
For example if I try to get the title for a View Controller with:
NSLocalizedStrings(#"viewController_Title",nil);
The view controller, for device with English language shows "viewController_title" as title, while if I set the French language it works with no problem.
How can I deal with that?
Your problem is that you need a language to fallback to.
As far as I know, there is no official way around it, I've written methods like this in the past:
NSString * L(NSString * translation_key) {
NSString * s = NSLocalizedString(translation_key, nil);
if (![[[NSLocale preferredLanguages] objectAtIndex:0] isEqualToString:#"en"] && [s isEqualToString:translation_key]) {
NSString * path = [[NSBundle mainBundle] pathForResource:#"en" ofType:#"lproj"];
NSBundle * languageBundle = [NSBundle bundleWithPath:path];
s = [languageBundle localizedStringForKey:translation_key value:#"" table:nil];
}
return s;
}
In this case, using L(#"viewController_Title"); would return the string for the default language, in this case being English.
Shameless self-citation
In this string file " Localizable.strings" you need to declare localization like this
French.strings
"viewController_Title" = "ViewController_Title_In_Frech";
English.strings
"viewController_Title" = "ViewController_Title_In_English";
You need to use the localized string like this
NSLocalizedStringFromTable(Key, LanguageType, #"N/A")
ex:
NSLocalizedStringFromTable("viewController_Title", English, #"N/A");
Note : Change the language type programmatically then you can get the respective Localized string. And localized declaration is must in the relevant strings file.
Your project is set to use English as the default language.
In your Info.plist file:
Set "Localization native development region" to French.
Missing translations will now fall back to French instead of English.
As mentioned in the other answers, By default English is used in case of missing language.
There are two solutions:
1. Add localization string file for english as well (with the same content as french localized string has)
2. Or add the following code in main method of main.m before calling UIApplicationMain
//sets the french as default language
NSArray *lang = [[NSUserDefaults standardUserDefaults] stringArrayForKey:#"AppleLanguages"];
if ([lang count] > 0 && (![[lang objectAtIndex:0] isEqualToString:#"fr"]) ) {
NSLog(#"language is neither de nor fr. forcing de");
[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObjects:#"fr", #"en", nil] forKey:#"AppleLanguages"];
}
Related
I am working on an ancient iOS app for a program that runs off VB6. It is passing strings over in a non unicode format including Hebrew characters which once they are parsed are displayed as "àáðø ãøåøé"
I am assuming it is being encoded in Windows Hebrew.
I can't seem to find anything in the apple documentation that explains how to handle this case. And most searches bring up solutions in Swift, no Obj-C. I tried this:
NSString *hebrewPickup = [pickupText stringByApplyingTransform:NSStringTransformLatinToHebrew reverse:false];
But that just gave me this:
"ðø ַ̃øַ̊øֵ"
I am stumped.
EDIT: Based on JosefZ's comment I have tried to encode back using CP1252, but the issue is that CP1255 is not in the list of NSStringEncodings. But seems like it would solve my issue.
NSData *pickupdata = [pickupText dataUsingEncoding:NSWindowsCP1252StringEncoding];
NSString *convPick = [[NSString alloc] initWithData:pickupdata encoding:NSWindowsCP1254StringEncoding];
NSString *hebrewPickup = [convPick stringByApplyingTransform:NSStringTransformLatinToHebrew reverse:false];
Ok, if any poor soul ends up here, this is how I ended up fixing it. I needed to add some Swift into my Obj-C code. (If only I could magically just rebuild the whole project in Swift instead.)
Here is the info on that: https://developer.apple.com/documentation/swift/imported_c_and_objective-c_apis/importing_swift_into_objective-c
Making use of this Swift Package: https://github.com/Cosmo/ISO8859
I added the following code to a new swift file.
#objc class ConvertString: NSObject {
#objc func convertToHebrew(str:String) -> NSString {
let strData = str.data(using: .windowsCP1252);
let bytes: Data = strData!;
if let test = String(bytes, iso8859Encoding: ISO8859.part8) {
return test as NSString;
}
let test = "";
return test as NSString;
}
}
Then in the Obj-C project I was able to call it like so:
ConvertString *stringConverter = [ConvertString new];
NSString *pickupTextFixed = [stringConverter convertToHebrewWithStr:pickupText];
NSString *deliverTextFixed = [stringConverter convertToHebrewWithStr:deliverText];
XCUIApplication *app = [[XCUIApplication alloc] init];
[app.buttons[#"login"] tap];
See as above, in tests.m file, I want get login button by string "login", But, my app support multi-languages. How to get current language of my app.
The method below is the method how I get current Language. User set language in setting view of my app. I store current Language in NSUserDefaults.
+ (NSString*)currentLanguage
{
NSUserDefaults *lanUser = [NSUserDefaults standardUserDefaults];
NSString *currentLanguage = [lanUser valueForKey:kCNOGASingularLanguage];
if([currentLanguage length]==0)
currentLanguage = kApplicationLanguageEnglish;
return currentLanguage;
}
Storing the user's language in your app's UserDefaults and then accessing that from your UITest won't work. Your app and the UITest are running as separate processes, which means that your UITest cannot access your app's UserDefaults
There is a simple solution: To become independent from the users language you can set the accessibilityIdentifier on your UIButton and then access the button via the accessibilityIdentifier:
In your app:
button.accessibilityIdentifier = #"login";
In your UITest:
[app.buttons[#"login"] tap];
The accessibilityIdentifier is never displayed and VoiceOver also does not read it, so it does not have to be localized. Just make sure you are using accessibilityIdentifier and not accessibilityLabel. Because accessibilityLabel will be read by VoiceOver for handicapped users and should be localized.
ALTERNATIVE
If you cannot use accessibilityIdentifier to query your UI elements you could use your app's LocalizableStrings file in your UITests to get the localized button title (in this case):
First add your Localizable.strings file to your UITest target, then access the file via the UITest bundle to get the localized strings (I use a little helper method for that):
func localized(_ key: String) -> String {
let uiTestBundle = Bundle(for: AClassFromYourUITests.self)
return NSLocalizedString(key, bundle: uiTestBundle, comment: "")
}
I wrote a little blog post about this a while back with some more details.
I am creating an app that for now I would like to offer in English and German. I checked the Base localization mark in the project configuration and added German. I left English as the development language.
Then I created a file Translation.plist which basically consists of dictionaries that I call categories, e.g. I have one dictionary for button texts, label texts etc. Each of the category dictionaries again consists of dictionaries that contain two Strings: value and comment. Translation.plist is localized via XCode. The folders Base.lproj, en.lproj and de.lproj exist and contain a copy of the plist-file as expected.
Then I created a class Translator.swift that is supposed to load the Translation.plist file as an NSDictionary depending on the user's preferred locale. The code looks like this:
func relevantDictionary(category: String) -> NSDictionary {
let preferredLocale = Bundle.main.preferredLocalizations.first ?? "Base"
NSLog("User's preferred locale is \(preferredLocale)")
guard let url = Bundle.main.url(forResource: "Translation", withExtension: "plist") else {
fatalError("Could not find Translation.plist")
}
NSLog("Using \(url.absoluteURL) for translation")
guard let root = NSDictionary(contentsOf: url) else {
fatalError("Could not find dictionary for category (locale=\(preferredLocale)")
}
guard let relevant = root.value(forKey: category) as? NSDictionary else {
fatalError("Could not create dictionary from Translation.plist")
}
return relevant
}
Then I created a String extension that uses the Translator as follows:
func localize(category: String) -> String {
return Translator.instance.translate(category: category, string: self)
}
With this I call the Translator by "yes".localize("button"). In English I would expect "Yes", in German I would expect "Ja". The log says the following:
2017-07-05 08:45:24.728 myApp[13598:35048360] User's preferred locale is de_DE
2017-07-05 08:45:24.728 myApp[13598:35048360] Using file:///Users/me/Library/Developer/CoreSimulator/Devices/A39D3318-943D-4EFE-BB97-5C2218279132/data/Containers/Bundle/Application/4614E696-B52E-4C30-BBE8-3C76F6392413/myApp.app/Base.lproj/Translation.plist for translation
I wonder why this happens and what I have missed. I would have expected that de.lproj/Translation.plist is loaded instead of Base.lproj/Translation.plist.
Any help is highly appreciated.
You can do it with single .plist file. You don't need to create different .plist files for it.
Firstly, Add English and German countries with locale in Project -> info
https://i.stack.imgur.com/M4QIY.png
Once, you added countries with locale in Project -> info then add localizable.strings file in your bundle.
https://i.stack.imgur.com/lnjgL.png
At the end, just add country's locale in your language support class.
NSArray* languages = #[#"en", #"de"];`enter code here`
NSString *current = [languages objectAtIndex:0];
[self setLanguage:current];
Hope it would help you.
We have code like the following to retrieved the user language preference:
NSString *language = [[NSLocale preferredLanguages] firstObject];
Before iOS 8.4, language is "zh-Hans", "de", "ru", "ja" and etc. But since iOS 9, I notice that there is additional three characters "-US" appended to language. For example, "zh-Hans" becomes "zh-Hans-US"
I can find any documentation about this change. I assume that I could do something like the following to workaround this issue.
NSRange range = [language rangeOfString:#"-US"];
if (range.location!=NSNotFound && language.length==range.location+3) {
// If the last 3 chars are "-US", remove it
language = [language substringToIndex:range.location];
}
However, I am not sure whether it is safe to do so. It seems that "-US" is the location where the user is using the app? But this doesn't really make sense because we are in Canada. Has any body from other part of the world tried this?
Apple has started adding regions onto the language locales in iOS 9. Per Apple's docs, it has a fallback mechanism now if no region is specified. If you need to only support some languages, here is how I worked around it, per Apple's docs suggestion:
NSArray<NSString *> *availableLanguages = #[#"en", #"es", #"de", #"ru", #"zh-Hans", #"ja", #"pt"];
self.currentLanguage = [[[NSBundle preferredLocalizationsFromArray:availableLanguages] firstObject] mutableCopy];
This will automatically assign one of the languages in the array based off the User's language settings without having to worry about regions.
Source: Technical Note TN2418
To extract the region I think this is a better solution:
// Format is Lang - Region
NSString *fullString = [[NSLocale preferredLanguages] firstObject];
NSMutableArray *langAndRegion = [NSMutableArray arrayWithArray:[fullString componentsSeparatedByString:#"-"]];
// Region is the last item
NSString *region = [langAndRegion objectAtIndex:langAndRegion.count - 1];
// We remove region
[langAndRegion removeLastObject];
// We recreate array with the lang
NSString *lang = [langAndRegion componentsJoinedByString:#"-"];
Swift 5: Remove region from preferred language
Using Locale.preferredLanguages.first gives you the preferred App language (which can be different than device language for the user).
In order to support the script code and language code (but to remove the region code) I think it is best to create a locale given the preferred language and grab the information we need from there.
if let pref = Locale.preferredLanguages.first {
let locale = Locale(identifier: pref)
let code = [locale.languageCode, locale.scriptCode].compactMap{$0}.joined(separator: "-")
print(code)
}
So first we get the preferred app language, Then create a locale from the language.
To get the language code we create an array with locale.languageCode and the locale.scriptCode (which may be nil), remove any nil values with compactMap and then join the values with a "-".
This should allow support for Simplified Chinese and Traditional, and let Apple handle the region instead of assuming it will always be there.
I have a number of strings that have internet links embedded within them that worked fine until I applied NSLocalizedString to each of them for a localization in Spanish. Now the links in the strings are not recognized or operate as such in my app either for English (the base language) or Spanish.
I have been unable to determine why this is happening and haven't found any reference to this issue online. Is there some special formatting that I have to do to the URL part of my strings when using NSLocalizedString that I didn't have to when using NSString? I would greatly appreciate any help that anyone could offer with a solution to my issue?
Here is an example of one of my NSLocalizedStrings and its use in forming the contentString:
aboutContentText = NSLocalizedString(#"\"The Visitation\", by 1737, Jerónimo Ezquerra (1660-1737), http://commons.wikimedia.org/wiki/File:Jerónimo_Ezquerra_Visitation.jpg\n", #"aboutContentText-2nd Joyful Mystery");
contentString = [[NSMutableAttributedString alloc]
initWithString: aboutContentText attributes: contentAttributes2];
Don't localize the URLs, localize only the text:
NSString *preamble = NSLocalizedString(#"\"The Visitation\", by 1737, Jerónimo Ezquerra (1660-1737)", #"preamble aboutContentText-2nd Joyful Mystery");
NSString *urlString = #"http://commons.wikimedia.org/wiki/File:Jerónimo_Ezquerra_Visitation.jpg";
NSString *aboutContentText = [NSString stringWithFormat:#"%#, %#\n", preamble, urlString];
NSLog(#"aboutContentText: %#", aboutContentText);
NSLog output:
aboutContentText: "The Visitation", by 1737, Jerónimo Ezquerra (1660-1737), http://commons.wikimedia.org/wiki/File:Jerónimo_Ezquerra_Visitation.jpg