`NSLocale preferredLanguages` contains "-US" since iOS 9 - ios

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.

Related

UITextChecker completionsForPartialWordRange

I'm building custom keyboard extension and want to implement autocompletion, like Apple does.
As I see method completionsForPartialWordRangereturns list of words sorted alphabetically. How can I get results sorted by usage?
The docs for completionsForPartialWordRange:inString:language: say:
The strings in the array are in the order they should be presented to the user—that is, more probable completions come first in the array.
However, the results are very clearly sorted in alphabetical order, and it's not true that "more probable completions come first in the array." The below was tested with iOS 9:
NSString *partialWord = #"th";
UITextChecker *textChecker = [[UITextChecker alloc] init];
NSArray *completions = [textChecker completionsForPartialWordRange:NSMakeRange(0, partialWord.length) inString:partialWord language:#"en"];
iOS word completions for "th":
thalami,
thalamic,
thalamus,
thalassic,
thalidomide,
thallium,
...
the,
...
So, the results will need to be sorted again after obtaining the word completions.
The OS X NSSpellChecker version of this method does not have the same problem:
NSString *partialWord = #"th";
NSArray *completions = [[NSSpellChecker sharedSpellChecker] completionsForPartialWordRange:NSMakeRange(0, partialWord.length) inString:partialWord language:#"en" inSpellDocumentWithTag:0];
List of complete words from the spell checker dictionary in the order they should be presented to the user.
Mac OS X word completions for "th":
the,
this,
that,
they,
thanks,
there,
that's,
...
Filing a radar bug report would be a good idea, so that the behavior will hopefully be fixed in a later version of iOS. I've reported this as rdar://24226582 if you'd like to duplicate.
Swift 4.0
func autoSuggest(_ word: String) -> [String]? {
let textChecker = UITextChecker()
let availableLangueages = UITextChecker.availableLanguages
let preferredLanguage = (availableLangueages.count > 0 ? availableLangueages[0] : "en-US");
let completions = textChecker.completions(forPartialWordRange: NSRange(0..<word.utf8.count), in: word, language: preferredLanguage)
return completions
}

Arabic characters (effective power bug) crash my swift iOS app. how do I properly sanitize inputs to avoid this and related problems?

the text that caused the crash is the following:
the error occurred at the following line:
let size = CGSize(width: 250, height: DBL_MAX)
let font = UIFont.systemFontOfSize(16.0)
let attributes = [
NSFontAttributeName:font ,
NSParagraphStyleAttributeName: paraStyle
]
var rect = text.boundingRectWithSize(size, options:.UsesLineFragmentOrigin, attributes: attributes, context: nil)
where text variable contains the inputted string
parastyle is declared as follows:
let paraStyle = NSMutableParagraphStyle()
paraStyle.lineBreakMode = NSLineBreakMode.ByWordWrapping
My initial idea is that the system font can't handle these characters and I need to do an NSCharacterSet, but I'm not sure how to either just ban characters that'll crash my app or make it so i can handle this input (ideal). I don't want to ban emojis/emoticons either.
Thanks!
Not an answer but some information and that possibly provids a way code way to avoid it.
Updated to information from The Register:
The problem isn’t with the Arabic characters themselves, but in how the unicode representing them is processed by CoreText, which is a library of software routines to help apps display text on screens.
The bug causes CoreText to access memory that is invalid, which forces the operating system to kill off the currently running program: which could be your text message app, your terminal, or in the case of the notification screen, a core part of the OS.
From Reddit but this may not be completely correct:
It only works when the message has to be abbreviated with ‘…’. This is usually on the lock screen and main menu of Messages.app.
The words effective and power can be anything as long as they’re on two different lines, which forces the Arabic text farther down the message where some of the letters will be replaced with ‘…’
The crash happens when the first dot replaces part of one of the Arabic characters (they require more than one byte to store) Normally there are safety checks to make sure half characters aren’t stored, but this replacement bypasses those checks for whatever reason.
My solution is the next category:
static NSString *const CRASH_STRING = #"\u0963h \u0963 \u0963";
#implementation NSString (CONEffectivePower)
- (BOOL)isDangerousStringForCurrentOS
{
if (IS_IOS_7_OR_LESS || IS_IOS_8_4_OR_HIGHER) {
return NO;
}
return [self containsEffectivePowerText];
}
- (BOOL)containsEffectivePowerText
{
return [self containsString:CRASH_STRING];
}
#end
Filter all characters to have same directionality. Unfortunately, I'm only aware of such API in Java.
Don't even try. This is a bug in the operating system that will be fixed. It's not your problem. If you try to fix it, you are just wasting your time. And you are very likely to introduce bugs - when you say you "sanitise" input that means you cannot handle some perfectly fine input.
The company I work at develops a multiplatform group video chat.
In Crashlytics report we started noticing that some users are "effectively" trolling iOS users using this famous unicode sequence.
We can't just sit and wait for Apple to fix this bug.
So, I've worked on this problem, this is the shortest crashing sequence I got:
// unichar representation
unichar crashChars[8] = {1585, 1611, 32, 2403, 32, 2403, 32, 2403};
// string representation
NSString *crashString = #"\u0631\u064b \u0963 \u0963 \u0963"
So, I decided to filter out all text messages that contains two U+0963 'ॣ' symbols with one symbol between them (hope you are able to decipher this phrase)
My code from NSString+Extensions category.
static const unichar kDangerousSymbol = 2403;
- (BOOL)isDangerousUnicode {
NSUInteger distance = 0;
NSUInteger charactersFound = 0;
for (NSUInteger i = 0; i < self.length; i++) {
unichar character = [self characterAtIndex:i];
if (charactersFound) {
distance++;
}
if (distance > 2) {
charactersFound = 0;
}
if (kDangerousSymbol == character) {
charactersFound++;
}
if (charactersFound > 1 && distance > 0) {
return YES;
}
}
return NO;
}
Lousy Specta test:
SpecBegin(NSStringExtensions)
describe(#"NSString+Extensions", ^{
//....
it(#"should detect dangerous Unicode sequences", ^{
expect([#"\u0963 \u0963" isDangerousUnicode]).to.beTruthy();
expect([#"\u0631\u064b \u0963 \u0963 \u0963" isDangerousUnicode]).to.beTruthy();
expect([#"\u0631\u064b \u0963 \u0963 \u0963" isDangerousUnicode]).to.beFalsy();
});
//....
});
SpecEnd
I'm not sure if it's OK to "discriminate" messages with too many "devanagari vowel sign vocalic ll".
I'm open to corrections, suggestions, criticism :).
I would love to see a better solution to this problem.

NSLocalizedString breaks webpage URL in string using Xcode

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

Collect iOS currency symbols for bitmap font?

My app is displaying the price of an in app purchase product. How can I (at design time) enumerate all the currency symbols and characters used in all of Apple's international app stores? I am displaying text in my app using "texture atlas" based bitmap fonts, i.e. I have to manually include each character I want to display.
I realize that this is a moving target, so I plan to make my logic forgiving. For example if some future equivalent of the Euro symbol is added by Apple and somebody's running an old version of my app, I will silently drop that character and just display the numeric part as "2.99" or "2,99" etc.
But how can I make my list as accurate as possible today, per Apple's official list?
Here's how the string is formatted (straight from Apple's sample):
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
[numberFormatter setLocale:product.priceLocale];
NSString *formattedString = [numberFormatter stringFromNumber:product.price];
Nobody's rushing to answer this, so here's the best option I've found so far. Basically, the approach would be scraping the currency symbols and Latin character alternates from the following page:
http://en.wikipedia.org/wiki/Currency_symbol#List_of_presently-circulating_currency_symbols
Notice that there is a generic currency symbol that can be used as a fallback.
If anybody has a better answer (meaning somehow gleaned from Apple), I'll be happy to accept your answer over this one.
I quickly wrote a Swift playground to grab the NumberFormatter's output for every available locale. Filtering this to only include currency symbols and punctuation gives a relatively complete set of characters to include.
let price = 0 as NSDecimalNumber
let availableIdentifiers = Locale.availableIdentifiers
var allCurrencySymbols: String = ""
for identifier in availableIdentifiers
{
let locale = Locale(identifier: identifier)
let formatter = NumberFormatter()
formatter.formatterBehavior = NumberFormatter.Behavior.behavior10_4
formatter.numberStyle = NumberFormatter.Style.currency
formatter.locale = locale
let formattedPrice = formatter.string(from: price)!
let currencySymbolsOnly = formattedPrice.replacingOccurrences(of: "0", with: "")
allCurrencySymbols.append(currencySymbolsOnly)
}
var set = Set<Character>()
let allCurrencySymbolsMinusDuplicates = String(allCurrencySymbols.characters.filter{ set.insert($0).inserted } )
print(allCurrencySymbolsMinusDuplicates)
On my Mac, this produces the output…
, ¤KMFCABuRE.‏₪٠٫۰$০₹YDTShN¥H€₺₦L₸rOP£៛၀nG₵денКМأم०UفجقVsk/zł؋Q༠Zد֏رسل₱؜یاoʻmكብርXdjbI₾ع​₽сом₼ت₩f₡¥Wරුeب₭नेरूtإë₴l৳يp₫лвč₮
…which you can use to create your bitmap font. But remember your source font will need to support the characters, too.

Localizable.strings in iOS and not localized languages

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"];
}

Resources