How to always get english string from NSError? - ios

I've got an iOS application that is designed with English-only strings. When running on an iOS device that is using another language for display (i.e. Settings/General/Language/iPhoneLanguage is something non-English) I would like to also show system errors in English since my font can't display non-english characters at the moment.
Is there a way to always get the english error message from NSError? Or to lookup the english error string in the system somehow manually?

After digging around I found a potential workaround.
First, it turns out that in most cases, iOS is returning English strings from [myNSError localizedDescription] when I'm running on a system that has the region set to, say, France, with the language set to, say, French. I believe this is because I have CFBundleDevelopmentRegion set to "en" in my info.plist. This is what I wanted to happen since I am trying to build an app that runs fully in English everywhere (for the moment).
However, in some cases, like if you try to buy something in the app and the network is off (this is my most reliable repro case), I will get NSErrors that return a localized error from [myNSError localizedDescription]. In this case: "Connexion à l’iTunes Store impossible."
When I get an error like this, a workaround that kinda works is to create an NSError manually using the domain and error code from the original. If you then ask it for the localizedDescription it will give you one in English, but it seems to be more general. In this case: "The operation couldn’t be completed.". What appears to be happening is that the system is filling in more detailed errors using the local language.
string GetEnglishLocalizedDescription(NSError *originalError)
{
if(originalError == nil)
{
return "";
}
else
{
NSString *originalNSErrorString = [originalError localizedDescription];
string originalUtf8String = originalNSErrorString == nil ? "" : [originalNSErrorString UTF8String];
// If we have a character outside of the US Ascii range
// Our font won't display it properly and this is probably
// a localized description
// https://tools.ietf.org/html/rfc3629
bool isUSAscii = true;
for(uint8_t c : originalUtf8String)
{
if(c > 0x7F)
{
isUSAscii = false;
break;
}
}
if(!isUSAscii)
{
// Has non-US Ascii characters, probably localized, get the standard error instead
NSString *englishNSErrorString = [[NSError errorWithDomain:originalError.domain code:originalError.code userInfo:nil] localizedDescription];
string englishUtf8String = englishNSErrorString == nil ? "" : [englishNSErrorString UTF8String];
return englishUtf8String;
}
else
{
return originalUtf8String;
}
}
}
Then, the trick is to figure out when you want to do this because you can't really tell in an obvious way that you got an error in the local language. In my case, I can just look for characters that my font can't display and convert it. If my font can display it, I guess I'm OK displaying it since either:
a) it is in english and the whole app is so nothing surprising or
b) it is actually localized just happens to be a string that has characters that are all also English characters. In this case, presumably the user will be able to read it in their local language.
One final gotcha using the code/algorithm above: It turns out the English error messages that Apple uses don't stick to the normal ASCII character set. They will use the fancy apostrophe in Unicode as opposed to the ASCII "'". So, the code above will think that is a "localized" message. Unclear what other characters they do this with. So this isn't really a full solution. It does solve the original question of "How do I always retrieve an English error message", but it doesn't fully solve my problem since i have no idea how to build a font that will even show all English error messages. Sigh.
Still have to decide the best approach here, but it is something.

Related

XCTest typeText cant switch keyboard to another language

Heloo!
I am working with uitests on iOS and am using typeText method to enter a string into a textField. The application is multilingual, so the test case involves entering a string in different languages. However, the method fails for strings other than the current keyboard language (cannot switch the keyboard language to enter this string, although the simulator has a keyboard with this language).
I haven't been able to solve this problem for a week now. I did not find ways to switch the keyboard language for typeText, or otherwise solve the problem.
Please, help!
UPD (for drunkencheetah):
I use this method as XCUIElement extension:
func clearAndTypeText(_ text: String) {
let typedText = self.value as? String
focusIfNeeded()
if typedText != nil {
let deleteText = String(repeating: XCUIKeyboardKey.delete.rawValue, count: typedText!.count)
typeText(deleteText)
}
typeText(text)
}
firstTextField.clearAndTypeText("English12345") // Result - "English12345"
secontTextField.clearAndTypeText("文本123") // Chinese as example. Result -> "123"
// This will take a very long time to print.
If I manage to manually switch the keyboard language (while running the test) from English to Chinese, the text will be printed. Otherwise, only numbers
typeText() should function regardless of the current keyboard language. I've just tested typing text in Bulgarian(Cyrillic) and Chinese without issues.
Since your application is multilingual you should make sure you are locating the element respective to the current application language(if not using an accessibility identifier).
Also make sure the element has keyboard focus - use tap() on it before attempting typeText() just in case.
Make sure if running on simulator that I/O > Keyboard > Connect hardware keyboard is disabled
As Mike Collins suggests in the comments you could use the pasteboard (only on simulator!) like this:
UIPasteboard.general.string = "teststring"
textElement.doubleTap()
app.menuItems["Paste"].tap()
Note that this will not work on real devices.

Using the wrong Localizable.string

I have an iOS app with a default language (English).
It also has a French localization and works as expected in both languages.
Here is the problem I am having:
When setting the device to a language other than English(default) or a language for which localization is provided (for example setting the device to Japanese); the app does not fall as expected in the default language. But it keeps the last language used for the app.
Why is that? And how can I fix it?
I have found a few post with a similar issue, but the solutions proposed did not work for me. For example this one, where the problem is similar to mine.
I am using Xcode version 11.1 and iOS version 12.4.2.
iOS will have a list of preferred language order based on the previously selected languages.
So, if you changed from English then to an unmapped language (e.g ko), the application will have as AppleLanguages the array:
[ko-(ZoneCode), en-(ZoneCode)].
You can avoid this procedure using this code below:
let defaultCultureCode: String = "en"
let defaults = UserDefaults.standard
let currentAppleLanguages = defaults.stringArray(forKey: "AppleLanguages")
if let currentLanguages = currentAppleLanguages {
if(!currentLanguages.isEmpty && !(currentLanguages.first?.contains(["en", "fr"]))!) {
defaults.removeObject(forKey: "AppleLanguages")
defaults.set([defaultCultureCode], forKey: "AppleLanguages")
defaults.synchronize()
}
}
I used this string extension to check if the current languages contain the available language:
extension String {
public func contains(_ elements: [String]) -> Bool {
var haveElementOnString = false
if elements.count == 0 { return false }
else { elements.forEach{ element in haveElementOnString = haveElementOnString || self.contains(element)} }
return haveElementOnString
}
}
The next step it's up to you to define the best strategy.
In my application context, it will always be defined as English by default because it is the only .strings file available. All the other supported strings will be downloaded and then a message will be displayed to the user warning that new languages are available and will be applied next time.
There's a lot of information on StackOverflow about bundle.localizedStringForKey or NSLocalizedString that could help you to found the best solution for what you need.
Also, this is only the validation and update for the current locale and the default language. this doesn't include the Region section (e.g. ko-KR (KR)).
I found an interesting text on this topic that could be useful
How not to do localization.
I hope it solves your problem!

How to get Localized NSError

I have done a lot of google search without success.
I would like to use the system localization of NSError (In my case in french)
I have try many things but the wording are always in english.
The configuration off my app :
In the plist :
CFBundleDevelopmentRegion = fr_FR
In the pbxproj:
developmentRegion = fr;
knownRegions = (
fr,
Base,
);
When I call the property "localizedDescription" I always get english version like this link (NSError localizedDescription always returns english error message) but the solution doesn't work for me...
I don't found what I'm missing.
In this other link NSURLConnection returns NSError with only english as language? they copy the strings but I don't think it's the better way, we should be able to access the file without copy it.
For information when I use an UIBarButtonItem like Cancel, it's localized in french.
Thanks in advance for your help.
First you should have a localisation file with key and value strings.
Now when you want to show the localisation error, you can use following method
NSLocalizedString(<#key#>, <#comment#>)
I have had the same issue the solution was to set the info.plist attribute Localization native development region to your region: France in this case

Get currency symbol from currency codes in Objective-C

I'm having trouble getting the currency symbol (e.g. '$') out of currency codes (e.g. 'USD').
I'm currently trying this way:
NSString *code = [NSLocale ISOCurrencyCodes][indexPath.row];
NSLog(#"%#", [[NSLocale currentLocale] displayNameForKey:NSLocaleCurrencySymbol value:code]);
Receiving (null) as result... Looking for the correct way to get the currency symbol from currency codes.
Even though I haven't seen the result yet, I'm pretty sure this will give repeated entries (Euro zone countries for example will be repeated, and will end up with many different '€' symbols). Is there a better approach to this problem?
EDIT: I'm not having problems with NSNumberFormatter, what I need is a list of all currencies supported in iOS for the user to select one (I will handle the formatting of the correct currency with the NSNumberFormatter, not having trouble with that).
Thanks
I can't reproduce your issue. The following works fine:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
#autoreleasepool {
for (NSString *code in [NSLocale ISOCurrencyCodes]) {
NSLog(#"%# -> %#", code, [[NSLocale currentLocale] displayNameForKey:NSLocaleCurrencySymbol value:code]);
}
}
return 0;
}
I suspect that your problem is that indexPath.row isn't what you expect it to be.
You shouldn't expect many duplicates (if any). The list of currency codes doesn't list countries; it lists currencies. So there's just one EUR which maps to €. On my system, all but two resolve (EQE and LSM which are discontinued currencies).
Keep in mind that there are 299 different currencies in the list I'm looking at, so your pick list may be much longer than you're thinking.
I don't know if you've figured this out already.
I've had similar issues running on the emulator, but not a device. If you're running on the emulator, see if manually setting the language and region helps. I've noticed this is specific to when your scheme is relying on "System Language" and "System Region". If I manually set the language to "English" and region to "United States" that it can determine the currency symbols again.
Steps to do so:
Click on your scheme -> click "edit scheme" -> make sure you've selected the run option in the side bar -> "Options" -> Application Language and Application Region set to not defaults system.
Let me know if this helps.

iOS what is the proper way to handle inserting localized text into an app programmatically?

I'm working on the localization of my app and am intersted if it is a good idea to "centralize" all localizable strings in my app to be provided from some static method: [AppStrings stringWithType:type].
On one hand, it seems like a centralized method would make editing the localizable strings easier in the future, but on the other hand, the string itself is no longer readable, and I will have to define a lot of enumerated types.
What is the proper way to do localization of strings in a large project (100+ strings to be localized)? Do I embed NSLocalizedString() in code, or should I try to somehow centralize providing these strings?
typedef enum : NSUInteger {
ksCheckingCredentials,
ksError
} kStringType;
+(NSString*)stringWithType:(int)type
{
switch (type) {
case ksCheckingCredentials:
return NSLocalizedString(#"Checking Credentials",
#"Inform the user that credentials check is being performed");
break;
case ksError:
return NSLocalizedString(#"Error",
#"Generic error message to display to the user");
break;
default:
break;
}
}
-- OR--
Do I just use the code below at a 100 different places in my app?
self.errorLabel.text = NSLocalizedString(#"Error",#"Generic error message to display to the user");
Chances are you're going to want to reuse the translations throughout the app. For instance, odds are everywhere there is an error you want that same localized error string. The way i've done this in the past is:
Make a LocalizedDefs.h file (name isn't important), and import it throughout your app. In there you'll put a series of #define's for your NSLocalized strings.
Example:
#define ksLocalizedError NSLocalizedString(#"Error", #"Generic error message to display to the user")
Then in your code wherever you want that error, you'll simply put in ksLocalizedError.
Finally you want your translations (obviously) so in your Localizeable.strings files simply setup the translations.
Example: "Error" = "Error";

Resources