Logo Icon Logo
A Crowd-sourced Cookbook on Writing Great Android® Apps
GitHub logo Twitter logo OReilly Book Cover Art

Formatting with Correct Plurals

Author: Ian Darwin -- Published? true -- FormatLanguage: W

Problem:

You're displaying something like "Found "+ n + " items", but in English, "Found 1 reviews" is ungrammatical. You want "Found 1 review" for the case n == 1.

Solution:

For simple, English-only results, use a conditional statement. For better results, that can be internationalized, use a ChoiceFormat. On Android, you can use <plural> in an XML Resources file.

Discussion:

The "quick and dirty" answer is to use Java's ternary operator (cond ? trueval : falseval) in a string concatenation. Since in English, for most nouns, both zero and plurals get an 's' appended to the noun in English ("no books, one book, two books"), we need only test for n==1.

// FormatPlurals.java 
public static void main(String argv[]) { 
 report(0); 
 report(1); 
 report(2); 
} 
/** report -- using conditional operator */
public static void report(int n) { 
 System.out.println("Found " + n + " item" + (n==1?"":"s")); 
}

Running this on JavaSE as a main program shows the following output:

$ java FormatPlurals
Found 0 items
Found 1 item
Found 2 items
$

The final println statement is short for:

if (n==1)
 System.out.println("Found " + n + " item"); 
else
 System.out.println("Found " + n + " items");

This is a lot longer, in fact, so Java's ternary conditional operator is worth learning.

Of course you can't use this arbitrarily, because English is a strange and somewhat idiosyncratic language. Some nouns like bus require 'es', while others like "cash" are collective knows with no plural (you can have two flocks of geese or two stacks of cash, but you cannot have "two cashes"). Some nouns, like "fish", can be considered plural as-is, although "fishes" is also a correct plural.

A Better Way

The ChoiceFormat class from java.text is ideal for handling plurals; it lets you specify singular and plural (or, more generally, range) variations on the noun. It is capable of more, but here I'll show only a couple of the simpler uses. I specify the values 0, 1, and 2 (or more), and the string values to print corresponding to each number. The numbers are then formatted according to the range they fall into:

import java.text.*;

/**
 * Format a plural correctly, using a ChoiceFormat.
 * @author Ian F. Darwin, http://www.darwinsys.com/
 * @version $Id: FormatPluralsChoice.java,v 1.7 2010/06/22 16:31:20 ian Exp $
 */
public class FormatPluralsChoice extends FormatPlurals {

	// ChoiceFormat to just give pluralized word
	static double[] limits = { 0, 1, 2 };
	static String[] formats = { "reviews", "review", "reviews"};
	static ChoiceFormat pluralizedFormat = 
		new ChoiceFormat(limits, formats);

	// ChoiceFormat to give English text version, quantified
	static ChoiceFormat quantizedFormat = new ChoiceFormat(
		"0#no reviews|1#one review|1<many reviews");

	// Test data
	static int[] data = { -1, 0, 1, 2, 3 };

	public static void main(String[] argv) {
		System.out.println("Pluralized Format");
		for (int i : data) {
			System.out.println("Found " + i + " " +
				pluralizedFormat.format(i));
		}
		
		System.out.println("Quantized Format");
		for (int i : data) {
			System.out.println("Found " + 
				quantizedFormat.format(i));
		}
	}
}

Either of these loops generates similar output to the basic version. The code using the ChoiceFormat is slightly longer, but more general, and lends itself better to internationalization. Put the string for the "quantized" form constructor into strings.xml and it will be part of your localization actions.

Best Way of All (Android-only)

Create a file in /res/values/somefilename.xml containing something like:

<?xml version="1.0" encoding="utf-8"?> 
<resources> 
<plurals name="numberOfSongsAvailable"> 
<item quantity="one">One item found.</item> 
<item quantity="other">%d items found.</item> 
</plurals> 
</resources> 

In your code you then use the following:

int count = getNumberOfsongsAvailable(); 
Resources res = getResources(); 
String songsFound = res.getQuantityString(R.plurals.numberOfSongsAvailable, count);

(This part suggested by Tomas Persson.)

See Also:

For the Android-only way, see [1].

Download:

The source code for this project can be downloaded from http://javacook.darwinsys.com/javasrc/numbers/FormatPluralsChoice.java.
mudderman 2010-07-13 08:25:03.831 I find Android's built-in mechanism for this far more simple. if you put a file in your /res/values/filename_of_choice.xml and do something like: <?xml version="1.0" encoding="utf-8"?> <resources> <plurals name="numberOfSongsAvailable"> <item quantity="one">One song found.</item> <item quantity="other">%d songs found.</item> </plurals> </resources> you can then in your code use the following: int count = getNumberOfsongsAvailable(); Resources res = getResources(); String songsFound = res.getQuantityString(R.plurals.numberOfSongsAvailable, count); For reference: http://developer.android.com/guide/topics/resources/string-resource.html#Plurals