Dealing with Localization of Plural Nouns in iOS

Originally published on Macoscope’s blog.

In order to fully take advantage of the fact that an application is available worldwide, one must first localize it properly. Depending on the application, the region, and a host of other factors, the process of localization itself may change and require different amounts of resources.

Apple provides iOS developers with a number of different tools and utilities that significantly reduce the burden cost of localization. Using the NSLocalizedString macro together with .strings files is (and rightly so) considered a standard nowadays, but relying solely on the two can sometimes lead to suboptimal solutions. In this post I would like to demonstrate a way to handling localized strings that have to deal with plural forms.

Plural nouns

Discovering Plural Noun Quirks

Sooner or later, almost every application displays a string the contents of which depend on the quantity of some particular item. A naive implementation of such a scenario would look something like this:

if items.count == 0 { 
  print(“No items”) 
} else if items.count == 1 {
  print(“One item”) 
} else { 
  print(items.count + “ items”) 
}

As it turns out, it is far from being legitimate (assuming that we want to have an application that is, or can be, easily localized into multiple languages). Different languages have different rules for plurals and a generic approach to dealing with plural nouns is much more complex than one supporting only English. I strongly encourage everyone to have a quick look at the description of these plural rules available here. Perusal of this document is a truly eye-opening experience when it comes to revealing the hidden complexities of a seemingly straightforward problem.

While in English we often end up with three branches of code (one for when the number of items is equal to 0, another for when it is equal to 1, and a final one for when it is greater than 1), in other languages the number can rise significantly. In case of Arabic, for example, the number of required branches is five. That’s huge. Moreover, not only does the number of possible branches change, but so do the rules describing which of the branches should be used for particular number changes across different languages - the number “11,” for example, may fall into different categories in each of them.

Because of this complexity, dealing with all possible scenarios by creating additional branches of code would be cumbersome to say the least and totally unmaintainable in the long run due to the rising number of localizations. It would also require a non-trivial amount of work on the part of the programmer every time an application would start supporting a new language. Fortunately, Apple provides us with a utility that addresses this specific problem.

Introducing Apple’s Solution for Working with Location of Plural Nouns

Starting with iOS 7 and OS X 10.9, Apple began including .stringsdict files that allow us to specify how a particular string should look like when dealing with different quantities of an item or items. Details of the format in question are outlined here.

Let’s start with a simple scenario and pretend that we need to display a string presenting the number of software engineers at work. 👷 Unfortunately, Xcode doesn’t provide a template for .stringsdict files, so we must create one from scratch.

The easiest way to do that is by adding a new file using the ‘Property list’ template and naming it Localizable.stringsdict (It is also possible to use names other than Localizable but this requires using NSLocalizedStringFromTable macro instead of the simpler NSLocalizedString macro). In Xcode, choose File -> New -> File and choose Property List from the dialog:

Panel presenting available templates

After the file is created, remove its .plist extension added automatically by Xcode:

'.plist' extension of created file

The contents of the created file look something like this:

Empty '.plist' file

In order for Apple’s plural noun localization magic to work, the file must be filled with couple of entries (my custom templates add all of them automatically to make it faster 🏎). Here’s an example of what a filled file looks like:

Filled '.stringsdict' file

A couple of comments regarding the contents of the file:

  1.  The root dictionary of the .plist contains one key for each of the localized strings that have to deal with the problem of plural nouns. These keys are used to retrieve the localized string using the NSLocalizedString macro. This particular entry is annotated with 1. The form of this key is unrestricted.
  2.  The entry annotated with 2 is a key-value pair, wherein the key is equal to NSStringFormatSpecTypeKey and the value describes the format of the final localized string. It can (and probably should) contain variable strings that depend on the count of some variable. The names of such substrings are preceded by %#@ characters and followed by a @ character.
  3.  The entry annotated with 3 is a result of the value stored in the entry annotated with 2 . As 2 contains a variable string named programmers, a key with the exact same name appears in the entry annotated with 3.
  4.  The NSStringFormatSpecTypeKey key always points to the NSStringPluralTuleType value (as this is currently the only available option).
  5.  The NSStringFormatValueTypeKey key points to a string format specifier determining the type of number the passed variable should be treated as - float, double, integer, etc. The possible values for this field are presented here.
  6.  The keys zero, one, two, few, many, other: some languages do without them - specific usage rules are outlined in the Unicode Language Plural Rules overview. Only the other key is required, the rest are optional. If a dictionary lacks a specific key that a given language requires, the value stored for other key is used.

To work around the cumbersome process of creating .stringsdict files, I created a custom Xcode template that makes the entire process really straightforward. 😉 It prefills the created files according to the requirements outlined above, which makes the whole process a lot faster and less error prone. It is available here and can be installed using the Alcatraz plugin manager.

In order to take advantage of the created .stringsdict file, we have to use the NSLocalizedString macro. Example usage is presented below:

let localizedStringFormat = NSLocalizedString("%d programmer(s) at work", comment: "")  
    
print(NSString.localizedStringWithFormat(localizedStringFormat, 0))
//There are no programmers at work 
print(NSString.localizedStringWithFormat(localizedStringFormat, 1))
//There is one programmers at work 
print(NSString.localizedStringWithFormat(localizedStringFormat, 2))
//There are 2 programmers at work

As we can see, we get properly localized string without introducing any branching into the code. That’s awesome. 🎉    

Further Investigations

.stringsdict files help out a lot with simple scenarios (such as the one described above) but they’re of even greater help when dealing with more complex ones. Let’s imagine that our localized string depends on two numbers - we want to create a string that says how many adults and children are present. The .stringsdict file for such a scenario would look like this:

'.stringdict' for string containing two plural nouns

Example usage of of such a .stringdict file is outlined below:

let localizedStringFormat = 
    NSLocalizedString("%d adult(s) and %d child/children", comment: "")

print(NSString.localizedStringWithFormat(localizedStringFormat, 0, 0))
//No adults and no children
print(NSString.localizedStringWithFormat(localizedStringFormat, 0, 1))
//No adults and one child
print(NSString.localizedStringWithFormat(localizedStringFormat, 0, 2))
//No adults and 2 children
print(NSString.localizedStringWithFormat(localizedStringFormat, 1, 0))
//One adult and no children
print(NSString.localizedStringWithFormat(localizedStringFormat, 1, 1))
//One adult and one child
print(NSString.localizedStringWithFormat(localizedStringFormat, 1, 2))
//One adult and 2 children
print(NSString.localizedStringWithFormat(localizedStringFormat, 2, 0))
//2 adults and no children
print(NSString.localizedStringWithFormat(localizedStringFormat, 2, 1))
//2 adults and one child
print(NSString.localizedStringWithFormat(localizedStringFormat, 2, 2))
//2 adults and 2 children  

We get nine different strings depending on the values passed during the string initialization process. All of this without even one branch in code. 😄    

Conclusion

Working with plural rules isn’t as simple as it may seem. Their naive implementation for multiple languages leads to unmaintainable code. To address that specific problem, Apple developed a new .stringsdict file format. Although the initial setup for .stringsdict files isn’t as simple as it could be, it’s still the best solution for dealing with localized plural nouns in the long run.