Why locale affects plural rules in iOS 9?

Since iOS 9.0 I'm not getting the same results as before. It turns out that since iOS 9.0 when you use: -[NSString initWithFormat:locale:,...] it picks pluralization rules based on given locale (and not based on the language app is running in).


NSString *format = NSLocalizedString... from .stringsdict
NSLocale *locale = ... some locale
NSInteger number = 42; // random
NSString *pluralizedString = [[NSString alloc] initWithFormat:format locale:locale, number];


That means, if you pass in different locale, NSString picks wrong format string from .stringsdict. Is that a correct behavior, or is that a bug? I strongly believe that that is a bug, I've submitted it (22804555) months ago, but got no responce.


More specific example:


Let's have a .stringsdict:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>format_key</key>
    <dict>
        <key>NSStringLocalizedFormatKey</key>
        <string>%1$#@format_key_plural@</string>
        <key>format_key_plural</key>
        <dict>
            <key>NSStringFormatSpecTypeKey</key>
            <string>NSStringPluralRuleType</string>
            <key>NSStringFormatValueTypeKey</key>
            <string>lu</string>
            <key>zero</key>
            <string>ZERO</string>
            <key>one</key>
            <string>ONE</string>
            <key>few</key>
            <string>FEW</string>
            <key>many</key>
            <string>MANY</string>
            <key>other</key>
            <string>OTHER</string>
        </dict>
    </dict>
</dict>
</plist>


And code:

NSLog(@"[[NSBundle mainBundle] preferredLocalizations] = %@", [[NSBundle mainBundle] preferredLocalizations]); // language app is running in
NSLog(@"[NSLocale currentLocale].localeIdentifier = %@", [NSLocale currentLocale].localeIdentifier); // regional conventions
NSString *format = NSLocalizedString(@"format_key", nil); // from a .stringsdict provided above


NSUInteger numForMany = 5; // it should be mapped to "many" in Russian(ru) and "other" in English(en)


// ru
NSLocale *ruLocale = [NSLocale localeWithLocaleIdentifier:@"ru"];
NSString *ruResult = [[NSString alloc] initWithFormat:format locale:ruLocale, numForMany];
NSLog(@"%@ for %lu: %@", ruLocale.localeIdentifier, numForMany, ruResult);


// en
NSLocale *enLocale = [NSLocale localeWithLocaleIdentifier:@"en"];
NSString *enResult = [[NSString alloc] initWithFormat:format locale:enLocale, numForMany];
NSLog(@"%@ for %lu: %@", enLocale.localeIdentifier, numForMany, enResult);


// currentLocale
NSLocale *currentLocale = [NSLocale currentLocale];
NSString *currentLocaleResult = [[NSString alloc] initWithFormat:format locale:currentLocale, numForMany];
NSLog(@"%@ (currentLocale) for %lu: %@", currentLocale.localeIdentifier, numForMany, currentLocaleResult);


And that what it outputs with language set to russian, while regional settings are all set to english. So that currentLocale return en_US while preferred localization (the language app is running in) would be ru:


// iOS 8
2015-09-21 12:56:00.245 LocalizationTests[99346:9581633] [[NSBundle mainBundle] preferredLocalizations] = (
    ru
)
2015-09-21 12:56:00.271 LocalizationTests[99346:9581633] [NSLocale currentLocale].localeIdentifier = en_US
2015-09-21 12:56:00.272 LocalizationTests[99346:9581633] ru for 5: MANY
2015-09-21 12:56:00.272 LocalizationTests[99346:9581633] en for 5: MANY
2015-09-21 12:56:00.272 LocalizationTests[99346:9581633] en_US (currentLocale) for 5: MANY

That's good, it uses correct plural rules for the .stringsdict, which contains strings in Russian for Russian plural forms. No matter which NSLocale I provide, it uses correct plural rules and formats numbers according to settings from NSLocale.



// iOS 9
2015-09-21 13:00:31.472 LocalizationTests[99610:9610901] [[NSBundle mainBundle] preferredLocalizations] = (
    ru
)
2015-09-21 13:00:31.776 LocalizationTests[99610:9610901] [NSLocale currentLocale].localeIdentifier = en_US
2015-09-21 13:00:31.785 LocalizationTests[99610:9610901] ru for 5: MANY
2015-09-21 13:00:31.785 LocalizationTests[99610:9610901] en for 5: OTHER
2015-09-21 13:00:31.785 LocalizationTests[99610:9610901] en_US (currentLocale) for 5: OTHER

You see? In iOS 9 it uses given locale to get plural rules for! It would use different plural rules depending on given NSLocale instance while loading the same resource in a single language.



It breaks the concept of two different settings - language and regional conventions. At second it breaks existing codebases. So, if you compile against iOS SDK 9.0+ and user sets different region you'll end up with wrong text. How can we deal with that (without specifying locale based on language app is running in)?

Just FYI, I was curious about your issue and discussed it with various folks here at Apple. My conclusion is that the situation here is kinda murky and thus it’d be best to let iOS Engineering address this in the formal context of your bug report. If you’re looking for specific advice as to how to proceed, my recommendation is that you open a DTS tech support incident so that my colleague who handles internationalisation issues can dig into this in more depth.

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thank you for your answer. I've submitted the TSI (632719955).

Why locale affects plural rules in iOS 9?
 
 
Q