Android Tip: Proper Pluralization

Keep plural rules out of code with R.plurals.

Let’s face it: internationalization and localization is hard. It’s not something most programmers want to think about during the course of their day. There are enough things to keep in mind when writing a complex piece of software, figuring out how to prepare that software so that it can be translated into different languages, it’s a pain.

Yes, it is a pain. But it’s a necessary pain. Because even if you know there’s no chance that your application will be available in anything but its initial language, getting into the habit of writing easily internationalizable code is worth the headache to make it easier when you are eventually on a project that needs to be localized.

Take the following code that one might find in an Android activity that deals with widgets:

String getWidgetCount(int numWidgets) {
    return String.format("%d widgets", numWidgets);
}

How many problems can you find in this piece of code? For one, I hope it goes without saying that strings displayed to the user are better off in your strings.xml file:

<resources>
    <string name="widget_count">%d widgets<string>
</resources>

Which cleans up the code in your activity:

String getWidgetCount(int numWidgets) {
    return getString(R.string.widget_count, numWidgets);
}

That’s a bit better. Of course, there’s still a problem: if you only have one widget, it will display the text “1 widgets”. The easy way to “fix” this is to change “widgets” to something like “widget(s)”, but really, what year is this? 1989? Are you writing your killer app for MS-DOS 4? It’s the year 2010, there are flying cars and jetpacks, and you can present a better experience to your user than that.

So let’s try to do even better. Add another string that corresponds to the singular case to strings.xml:

<resources>
    <string name="widget_count1">%d widget<string>
    <string name="widget_count">%d widgets<string>
</resources>

And your activity code becomes this:

String getWidgetCount(int numWidgets) {
    int res;
    if (numWidgets == 1) {
        res = R.string.widget_count1;
    }
    else {
        res = R.string.widget_count;
    }
    return getString(res, numWidgets);
}

There, now you can display “1 widget” if there’s one or “N widgets” if there are none or more than one.

However, the goal of internationalizable code is to get all language-specific logic and formatting out of code and into configuration files! Fortunately for this case, Android allows us to do just that through a pair of somewhat hidden methods on the Resources object called getQuantityString. The methods look like this:

public String getQuantityString(int id, int quantity);
public String getQuantityString(int id, int quantity, Object… formatArgs);

What these do is choose the appropriate string given the supplied quantity value. This is exactly what we want! Unfortunately there’s a catch: how to really use these methods isn’t actually documented. Sure, you can read the method documentation right up on the reference pages, but it doesn’t tell you what to put into your strings file. So from here on, take all of this with that disclaimer.

Using getQuantityString requires a resource type other than <string>. You need the <plurals> type. The plurals type combines with an XML scheme called XLIFF to put the rules of pluralization into the XML file:

<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <plurals name="widget_count">
        <item quantity="one">1 widget</item>
        <item quantity="other">
            <xliff:g id="count">%d</xliff:g> widgets
        </item>
    </plurals>
</resources>

Notice the addition of the XLIFF namespace to the <resources> tag. This creates for you an R.plurals resource, which is suitable to pass to getQuantityString:

String getWidgetCount(int numWidgets) {
    return getResources().getQuantityString(R.plurals.widget_count,
                          numWidgets, numWidgets);
}

You may prefer the previous approach because it looks simpler: I prefer this one because it’s properly internationalized.

You may choose not to use this because it’s not documented and may change with newer versions of the SDK, though at least one framework engineer says it’s “not bad” to use now. It’s also used in many of the default Android apps like Email and Contacts. I assume as Android’s i18n support matures, we’ll see more documentation and more integration with technologies like XLIFF.

Until then, keep this in mind. Writing internationalized code can be hard — but in cases like this, it’s easy. But it’s only easy once you know that it should never be an afterthought.

About

I like wearing different hats. Follow me on Twitter. Connect with me on Google+