Localizing with Plurals and Genders

 ·  iOS, Development  ·  Tagged Internationalization, Localization, Plural Rules and Gender Rules

You’ve just finished your latest application. Congrats! Even though you’re only releasing it in English, you’ve used NSLocalizedString to internationalize your user-facing strings. With strings that require dynamic quantities—“5 items” vs. “1 item”—you handled both the singular and plural cases. You shipped the app and moved on to your next project.

Suddenly your app is a hit… in Russia? You get a lot of feedback that your users want a Russian translation. Easy! You’ll just ship a Russian version of your strings file. Then you realize the problem: in English you can get away with that quick one/not-one check when pluralizing nouns; those are the only two plural categories in the language. Other languages aren’t so simple. Your Russian users have four different rules for pluralizing their nouns. Arabic speakers have six! Thankfully, there are a few tools to help you pluralize nouns for all of them.

Plural Rules

The Unicode Common Locale Data Repository (CLDR) Project provides tables, charts, and other resources to help software developers correctly internationalize their apps. For this article, we are most interested in Language Plural Rules. This chart provides a guide for how each language uses plurality with nouns for cardinal numbers (1 thing, 2 things) and with ordinal numbers (1st thing, 2nd thing). It does this by defining some short mnemonic categories and associated rules. The category nameszero, one, two, few, many, and other—are common across all languages; the rules define the mapping between a quantity and the plural category to use. For example, in English, the rules are:

onei = 1 and v = 0 (where i = integer value and v = number of visible fraction digits)
other ⇒ No rule, or any other number.

With this rule we now know that, in English, we'd say 0 things, 1 thing, 1.5 things, or 2 things.

Russian rules are a little more complicated:

onev = 0 and i % 10 = 1 and i % 100 ≠ 11
fewv = 0 and i % 10 = 2..4 and i % 100 ≠ 12..14
many ⇒ (v = 0 and i % 10 = 0) or (v = 0 and i % 10 = 5..9) or (v = 0 and i % 100 = 11..14)
other ⇒ No rule, or any other number.

Gender Rules

Supporting gender in languages—that is, properly handling pronouns—follows a similar pattern to the Plural Rules. English has three options: Male, Female, and Neutral. Polish has five. Because there's so much variation across languages, systems like the CLDR just assign each gender a number—0 for male, 1 for female, and 2 for neutral— instead of a mnemonic category.

Supporting these Rules in your App

Now that you know how the rules are defined, how do you make use of them to correctly internationalize your iOS app? There are a few open source solutions and even one from Apple as of iOS 7 and OS X 10.9, but none are perfect. Choosing one really depends on your needs and tastes.

TTTLocalizedPluralString

One of the many open source libraries by Mattt Thompson is TTTLocalizedPluralString It works with your existing Localizable.strings file. For each plural noun, just include a separate string for each plural category mentioned above. For example:

%d Person (plural rule: one) = "One Person";
%d Person (plural rule: other) = "%d People";

When you need to display that string, use TTTLocalizedPluralString instead of using NSLocalizedString, passing in the quantity.

return TTTLocalizedPluralString(count, @"Person", nil);

The library then decides which category-tagged string to load based on the rules in the CLDR.

Unfortunately, this library does not support gender rules, so you'll still have to do that on your own.

Smartling iOS-i8n

Smartling provides another library for this called iOS-i8n. Similar to TTTLocalizedPluralString, you provide options in your existing Localizable.strings file for each category.

"%d songs found##{one}" = "One song found";
"%d songs found##{other}" = "%d songs found";

Then use SLPluralizedString, or one of it's variants, instead of NSLocalizedString.

return SLPluralizedString(@"%d songs found", 4, nil);

We’ve used this library on several projects at Two Toasters as it provides a simple interface to the plural rules.

Unfortunately, this library also has no support for gender rules.

Apple's Localizable.stringsdict

Last year, Apple announced built-in support for plural and gender rules in the Foundation Release Notes for iOS 7 and OS X 10.9. It's a little more complicated than the open source libraries above. To get started, you need to:

  1. Create a Localizable.stringsdict file (this is simply a plist). Note: you must still have a .strings file of the same name.
  2. Add the localizable dictionary details to the plist (more on this in a moment).
  3. In your application, use +[NSString localizedStringWithFormat:] with the result of NSLocalizedString as the format.

This is all pretty straightforward. Download a sample based on the examples below.

Say you are writing an app that tracks and displays how many David Hasselhoff movies you’ve watched. We can set this up in our .stringsdict file. Again, this is just a plist, so we can edit it with either a plist editor or your text editor of choice.

First, we’ll create a new key with a dictionary value:

<key>%lu out of %lu Hasselhoff movies watched</key>
<dict></dict>

This key is the value you’ll request later with NSLocalizedString.

Next we’ll add data to the dictionary to identify what we want to provide as a replacement.

<key>%lu out of %lu Hasselhoff movies watched</key>
<dict>
    <key>NSStringLocalizedFormatKey</key>
    <string>%1$#@lu_hasselhoff_viewings@</string>
</dict>

Here we supply another format as the value for our initial key. @lu_hasselhoff_viewings@ is the new key, but where is it defined? We’ll add another key-value pair with this definition.

<key>%lu out of %lu Hasselhoff movies watched</key>
<dict>
    <key>NSStringLocalizedFormatKey</key>
    <string>%1$#@lu_hasselhoff_viewings@</string>
    <key>lu_hasselhoff_viewings</key>
    <dict>
        <key>NSStringFormatSpecTypeKey</key>
        <string>NSStringPluralRuleType</string>
        <key>NSStringFormatValueTypeKey</key>
        <string>lu</string>
        <key>zero</key>
        <string>No Hasselhoff Movies? You are hassling the Hoff.</string>
        <key>one</key>
        <string>Only one? You can do better.</string>
        <key>other</key>
        <string>%lu of %lu movies watched.</string>
    </dict>
</dict>

Now we are getting to those plural rule categories I mentioned earlier. This dictionary identifies the NSStringFormatSpecTypeKey, which in this case is NSStringPluralRuleType. I define the value type of the format value passed in, in this case the formatter is expecting %lu so the value is lu. Finally I define the different values to use depending on what the supplied value is. If it is 0, then it will load the zero key.

This is obviously much more complicated than the open source libraries above, but it's also more flexible. In the stringsdict above, I created a new key called lu_hasselhoff_viewings and the rules for replacing it. With this format, you can place keys inside of other keys and build much more complicated patterns. This also means you can change the string replacement rules in the .stringsdict without changing the code requesting a string.

The stringsdict option even ostensibly supports gender rules via NSStringGenderRuleType. Unfortunately, as of iOS 7.1, the gender rule type only results in (null) being returned from the strings dictionary. If you’d like to see more about this, there is a radar submitted to Apple (or if you are an Apple engineer, <rdar://16670931>).

Summary

As you can see, there are a lot of options for properly internationalizing your application. Whether you go with the Apple solution or work with something available in the open source community, the most important thing is that you think ahead and build this into your application from the beginning. Retroactively internationalizing all your app’s strings is a lot more work than doing it incrementally throughout development. Just like using NSLocalizedString even before you need it, doing just a little more work to properly handle plurals and genders will help you reach a global audience faster.