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!

Josh Friend
Josh Friend
Development Practice Co-Lead

Stay in the loop with our latest content!

Select the topics you’re interested to receive our new relevant content in your inbox. Don’t worry, we won’t spam you.

Time Check
Development

Time Check

June 6, 2019

Read Karl's blog post about an interesting development challenge related to time.

Read more
Download a collection of 3 key articles on the state of digital today
Business

Download a collection of 3 key articles on the state of digital today

April 14, 2020

As presented by Forbes. Download the top-3 most popular articles published by Michigan Software Labs.

Read more
Why Agile Works to Provide Purpose
Process

Why Agile Works to Provide Purpose

December 23, 2019

As the Delivery Practice Lead, I have the opportunity to meet with executive leaders and stakeholders across a wide range of industries. I get to see the impact that an Agile process can make in the life of an organization and a team.

Read more
View more articles