What is Android Quantity strings (plurals) and How’s it used
Android has this nice feature to make your handling of strings with numbers easy. If you want to display slightly different messages that depend on how many items a user has selected, you can use Android’s plurals
element in your strings.xml
:
One of the best example, and most important one too, is showing Singulars and Plurals in application. For example if I am having 1 banana then the string should be like-
<string name="banana">I have a banana.</string>
But if I have more than 1 bananas then?
Then I have to add a condition in my code and declare another key with an integer place holder like-
<string name="bananas">I have more bananas.</string>
Looks like a problem, right? But no, android has a solution for it which most of us don’t know its “Quantity Strings” or you can say “Plurals”. In plurals you can give more than one statement to a resource based on the number of items (in our case bananas) you got. In-spite of only numbers there are many other words supported such as few, many, other etc.
Also one of the advantage of using plurals is when you have more than one language support in your application. Different languages have different way of representing singulars and plurals and also distinctive ways of differentiating. Plurals also helps you in that case for-example in English you will mention. You can use Android’s plurals
element in your strings.xml
:
<plurals name="number">
<item quantity="one">One item selected</item>
<item quantity="other">More items selected</item>
</plurals>
Of course you have to use this string somewhere in your code and you have to pass in the number — otherwise Android can’t help you. This also is no magic:
val numberStr = context.getResources().getQuantityString(
R.plurals.number, count);
println(numberStr)
If i pass 1 in count then the string will be print based on count i.e “one”. So output will be:
Output:- One item selected
And if i pass any other number in count exclude 1 then the string will be print based on count i.e “other”. So output will be:
Output:- More items selected
If user want to display particular count based on user input, we can also do like below:
val numberStr = context.getResources().getQuantityString(
R.plurals.number, count, noCount)
// String.xml file to use count
<plurals name="number">
<item quantity="one">One item selected</item>
<item quantity="other">%d items selected</item>
</plurals>
The first argument obviously refers to the above plurals
element of the strings.xml
file. The second defined which plural to use. And the third which value to use for substitution - that is with which value to replace the "%d"
formatting code. Lets understand by example:
val numberStr = context.getResources().getQuantityString(
R.plurals.number, 5, 3)
println("I have $numberStr ")
here i have passed 5 on count on second parameter and 3rd parameter i have passed 3. so output will be:
Output:- I have 3 items selected
Here, the first parameter R.plurals.number
is the resource name. The second parameter (5 in this example) is used to pick the correct quantity
string. The third parameter (also 3
in this example) is the format argument that will be used for substituting the format specifier %d
.
How to use getQuantityString in different use cases
As for the getQuantityString
method, there are two overloads methods: one with only the resource id and the quantity, and one with an additional Object... formatArgs
parameter.
For case 1., you can use the getQuantityString(@PluralsRes int id, int quantity)
method.
For all other cases, i. e. if you have any parameters, you need the getQuantityString(@PluralsRes int id, int quantity, Object... formatArgs)
overload. Note: all parameters have to be present in the parameter array. That means, if the resource string displays the quantity, the quantity variable will be passed twice to the function.
That is because the quantity
parameter of the method itself is not considered when resolving the positional parameters of your resource string.
So if these are your resources,
<resources>
<plurals name="test0">
<item quantity="one">Test ok</item>
<item quantity="other">Tests ok</item>
</plurals>
<plurals name="test1">
<item quantity="one">%d test ok</item>
<item quantity="other">%d tests ok</item>
</plurals>
<plurals name="test2">
<item quantity="one">%2$s: %1$d test ok</item>
<item quantity="other">%2$s: %1$d tests ok</item>
</plurals>
<plurals name="test3">
<item quantity="one">%3$s: %1$d test out of %2$d ok</item>
<item quantity="other">%3$s: %1$d tests out of %2$d ok</item>
</plurals>
</resources>
then the appropriate calls to getQuantityString
are:
int success = 1;
int total = 10;
String group = "Group name";
getResources().getQuantityString(R.plurals.test0, success)
// Test ok
getResources().getQuantityString(R.plurals.test1, success, success)
// 1 test ok
getResources().getQuantityString(R.plurals.test2, success, success, group)
// Group name: 1 test ok
getResources().getQuantityString(R.plurals.test3, success, success, total, group)
// Group name: 1 test out of 10 ok
success = 5;
getResources().getQuantityString(R.plurals.test0, success)
// Tests ok
getResources().getQuantityString(R.plurals.test1, success, success)
// 5 tests ok
getResources().getQuantityString(R.plurals.test2, success, success, group)
// Group name: 5 tests ok
getResources().getQuantityString(R.plurals.test3, success, success, total, group)
// Group name: 5 tests out of 10 ok
Quantity classes: understanding the quantity
parameter
As stated above, the key is to understand that the quantity
parameter of getQuantityString
is not used to replace the placeholders like %d
or %1$d
. Instead, it is used to determine the appropriate item
from the plurals
itself, in combination with the locale of the resource file.
Beware however that this is a less direct mapping than the attribute’s name and its possible values (zero
, one
, two
, few
, many
, other
) might suggest. For example, providing an additional <item quantity="zero">
will not work (at least not in English), even if the value of the quantity
parameter is 0.
The reason is that the way plurals
work in Android is by the concept of quantity classes. A quantity class is a set of quantity values that have the same grammatical rules in a given language. This crucially means that
- which quantity classes are used, and
- which numeric values are mapped to them
is dependent on the locale the respective resource file is for.
It is important to understand that both questions are decided only by grammatical necessity. Here are some examples:
- In Chinese or Korean, only
other
is used, because in these languages sentences don't grammatically differ based on the given quantity. - In English, there’s two classes:
one
for the literal value 1, andother
for all other values including 0. - In Irish, 1 is mapped to
one
, 2 is mapped totwo
, 3-6 isfew
, 7-10 ismany
, 0 and 11+ isother
. - In Slovenian, the value 1 and all values ending in 01 are mapped to
one
(1, 101, 3001, ...). 2 and values ending in 02 are mapped totwo
(2, 302, 1002, ...). 3, 4 and values ending in 03 or 04 are mapped tofew
(3, 4, 6004, ...). Anything else isother
(0, 11, 48, 312, ...). - In Polish, 5–19 and values ending in 05–19 are mapped to
many
(5, 12, 216, 4711, ...). Values ending in 2, 3 or 4 including 2-4 themselves are mapped tofew
(3, 42, 103, 12035374, ...). This respects however that 12, 13 and 14 are exceptions from this rule because they are mapped tomany
. (Side note: yes, grammatically speaking, 5 is many while 12035374 is few.) - Armenian is like English, with the exception that the value 0 is also mapped to
one
, because that's how their grammar works. You can see from this example that the quantity classone
doesn't even necessarily represent just one-ish numbers.
As you can see, it can get fairly complicated to determine the correct quantity class. That’s why getQuantityString
already does that for you, based on the quantity
parameter and the resource file's locale. The rules Android (mostly) plays by are defined in the Language Plural Rules of the Unicode Common Locale Data Repository. That is also where the names of the quantity classes come from.
All that means that the set of quantity classes needed to translate any quantity string can differ from language to language (Chinese just needs other
, English needs one
and other
, Irish needs all but zero
, etc.). Within one language however, all plurals
should each have the same number of items covering all quantity classes necessary for that particular language.
A call to getQuantityString
can be understood like this:
int success = 5;
int total = 10;
String group = "Group name";
getResources().getQuantityString(R.plurals.test3, success, success, total, group)
// \_____________/ \_____/ \___________________/
// | | |
// id: used to get the plurals resource | |
// quantity: used to determine the appropriate quantity class |
// formatArgs: used to positionally replace the placeholders %1, %2 and %3
The quantity
parameter's value of "5" will mean the used item
will be the one with the quantity class other
from Chinese, Korean, English, Slovenian and Armenian resource files, few
for Irish, and many
for Polish.
Non-integer quantities
Basically, the chosen class depends on language-specific rules again. It is neither universal how a class is chosen, nor guaranteed that any class required to cover all rules for integers is also used for any non-integers. Here are a few examples:
- For English, any value with decimals will always map to
other
. - For Slovenian, any value with decimals will always map to
few
. - For Irish, the choice depends on the integer part.
- For Polish, in contrast to the complex rules for integers, non-integers are always mapped to
other
like in English.
Note: This is how it should be according to the Language Plural Rules. Alas, Android has no readily available method for float
or double
at the moment.
Multiple quantities in one string
If your display text has multiple quantities, e. g. %d match(es) found in %d file(s).
, split it into three separate resources:
%d match(es)
(plurals
item)%d file(s)
(plurals
item)%1$s found in %2$s.
(ordinary parameterizedstrings
item)
You can then make the appropriate calls to getQuantityString
for 1 and 2, and then another one to getString
for the third, with the first two readily localized strings as formatArgs
.
The reason is to allow translators to switch the parameter order in the third resource, should the language require it. E.g., if the only valid syntax in a hypothetical language was In %d file(s) it found %d match(es).
, the translator could translate the plurals as usual, and then translate the third resource as In %2$s it found %1$s.
to account for the swapped order.
Possible plural quantities
Of course you cannot use any arbitrary number for the quantity
attribute. The possible quantifiers are limited to these:
- zero
- one
- two
- few
- many
- other
But not all languages support all values. English for example only knows about "one"
and "other"
.
Other languages like Arabic, Irish or the Slavic languages use either all or at least some more of these. You can find a full list of which selections work in which language on the Unicode consortium’s page.
The problem with plurals
If your Locale doesn’t support a quantity value according to the rules linked above, Android will ignore this selection.
Try to use a plural for zero. Does it work? Most probably not, because in most languages "zero"
deserves no special treatment. Only five languages have support for it.
If the quantity value is not supported for a language, Android uses the “other” selection — in compliance with above rules.
But what if you want to display a different text for zero elements? This definitely isn’t uncommon. Wouldn’t you prefer to see a text like “No element selected” to the text “0 elements selected”? Well, I would. It is much nicer to read. But your are out of luck here.
It get’s even worse if you consider ordinals: 1st, 2nd, 3rd, 4th and so on. How to deal with them? Well, not with Androids plurals element — that’s for sure. But, to be honest, I think ordinals should not pose a problem too often — even though this question has popped up on StackOverflow.
I have posted a feature request to Android’s issue tracker, so that you could force Android to use a selection even if it’s not part of the Unicode rules. Please vote for this issue by starring it!.
It is important to note that not all locales support every denomination of
quantity
. For example, the Chinese language does not have a concept ofone
item. English does not have azero
item, as it is grammatically the same asother
. Unsupported instances ofquantity
will be flagged by the IDE as Lint warnings, but won't cause complication errors if they are used.
Thanks for reading…