Localization of Plural Forms

February 28, 2017

A Quick Recap

In a previous post, Chris explained how to get started with localizing an iOS application. In this post, we’re going to talk about a more advanced localization topic which will help us fix a grammatical error in the previous post’s example. To recap, here’s the basic mechanism by which you can obtain a localized string which contains variable elements defined by your code:

let msg = String.localizedStringWithFormat(
    NSLocalizedString(
        "There are %d oranges in the box",
        comment: "There are {number} oranges in the box"
    ),
    orangeCount
)

This has the obvious shortcoming that if you have 1 orange, the message will be “There are 1 oranges in the box”, which is not grammatically correct (at least in English). In the past, I have been guilty of “fixing” this by using a different format string if the count value is plural or not:

let fmt: String
if orangeCount != 1 {
    fmt = NSLocalizedString(
        "There are %d oranges in the box",
        comment: "There are {number} oranges in the box
    )
} else {
    fmt = NSLocalizedString(
        "There is %d orange in the box",
        comment: "There is 1 orange in the box
    )
}
let msg = String.localizedStringWithFormat(fmt, orangeCount)

This is better, but some other languages have more than two plural categories. Arabic, for example, has six plural categories! The variations between languages would be extremely difficult to manage yourself, so Apple has taken care of this for us with the stringsdict file mechanism.

iOS Pluralization Support

The stringsdict file lets you provide formats for the following plural cases: none, one, two, few, many, and other (English only uses one and other). Add a new file to your project called Localizable.stringsdict and check the language that the file applies to in the Localization section of the File Inspector. It is also required to have a Localizable.strings file present (even if it is empty) so that the Localizable.stringsdict file will be used by the iOS frameworks. Below is what the Localizable.stringsdict file needs to look like to make our box of oranges example work:


<plist version="1.0">
<dict>
    <key>orange_count</key>
    <dict>
        <key>NSStringLocalizedFormatKey</key>
        <string>%1$#@oranges@</string>
        <key>oranges</key>
        <dict>
            <key>NSStringFormatSpecTypeKey</key>
            <string>NSStringPluralRuleType</string>
            <key>NSStringFormatValueTypeKey</key>
            <string>d</string>
            <key>one</key>
            <string>There is one orange in the box</string>
            <key>other</key>
            <string>There are %1$d oranges in the box</string>
        </dict>
    </dict>
</dict>
</plist>

The top-level <key></key> is the key of the localized string passed to NSLocalizedString. I prefer to use a short description of what the string is about for the key instead of the default English text. That way, if the english translation changes, it is not necessary to change the string key in all of the Localizable.strings and Localizable.stringsdict files. In this example, I’ll name the string orange_count. If you use a key that is not the default text, make sure that you also have an entry in your base Localizable.strings file which specifies the default text.

The next step is to name the placeholders so that their plural forms can be specified later. The value %1$#@oranges@ for the NSStringLocalizedFormatKey key specifies that the first placeholder (1$) is called “oranges” (@oranges@). It is very important to use position indexes (like 1$) for strings with multiple placeholders because the format for another language may switch positions of the placeholders. The position indicies specify where the positional arguments to String.locallizedStringWithFormat() should be placed within our localized string. If no position is given, the arguments are placed into the string in the order that they are given, which may be incorrect.

A sub-dictionary for each named placeholder must also be given. This dictionary contains information about the language rule applied to the placeholder and the format type of the placeholder. NSStringFormatSpecTypeKey specifies the language rule applied and the only option is NSStringPluralRuleType. NSStringFormatValueTypeKey specifies the format of the number using a standard string format specifier. For an integer number, we’ll use d (which formats as a signed integer).

The plural forms of the placeholder are then specified with the none, one, two, few, many, and other keys. Though the only required key is other, you may need to specify additional keys depending on the rules of the languages you are targeting. In the example above, I’ve included both other and one for the English example above, because those are the only two forms used in English.

let msg = String.localizedStringWithFormat(
    NSLocalizedString("orange_count", comment: "Count of oranges in the box"),
    orangeCount
)

// Result:
// For 0 oranges: "There are 0 oranges in the box"
// For 1 orange: "There is one orange in the box"
// For 10 oranges: "There are 10 oranges in the box"
<span class="c1">// Result:</span>
<span class="c1">// For 0 oranges: "There are 0 oranges in the box"</span>
<span class="c1">// For 1 orange: "There is one orange in the box"</span>
<span class="c1">// For 10 oranges: "There are 10 oranges in the box"</span>

Android Pluralization Support

Localizing plural forms in Android is even easier than iOS! Android uses plurals resources, which are very similar to strings resources. Plural and string resources both live in your strings.xml file (and its localized variants) and use the same quantity keys as iOS:

<plurals name="orange_count">
    <item quantity="one">There is one orange in the box</item>
    <item quantity="other">There are %1$d oranges in the box</item>
</plurals>

You can then easily generate a formatted result for a given quantity using getQuantityString():

val msg = resources.getQuantityString(
    R.plurals.orange_count,
    orangeCount, // Used to determine which form of the quantity string to use
    orangeCount  // The quantity value to be inserted into the format string
)

// Result:
// For 0 oranges: "There are 0 oranges in the box"
// For 1 orange: "There is one orange in the box"
// For 10 oranges: "There are 10 oranges in the box"

Thats it! With just a little bit of extra work, your app can be localized correctly and you can clean up some conditional logic in your source code!

Ready to get started?

Call us at 616-594-0269 or send us a note below.
Visit our office @ 452 Ada Drive SE Suite 300 Ada, Michigan 49301
Send us an e-mail @ info@michiganlabs.com