Android plurals treatment of "zero" - android

If have the following plural ressource in my strings.xml:
<plurals name="item_shop">
<item quantity="zero">No item</item>
<item quantity="one">One item</item>
<item quantity="other">%d items</item>
</plurals>
I'm showing the result to the user using:
textView.setText(getQuantityString(R.plurals.item_shop, quantity, quantity));
It's working well with 1 and above, but if quantity is 0 then I see "0 items".
Is "zero" value supported only in Arabic language as the documentation seems to indicate?
Or am I missing something?

The Android resource method of internationalisation is quite limited. I have had much better success using the standard java.text.MessageFormat.
Basically, all you have to do is use the standard string resource like this:
<resources>
<string name="item_shop">{0,choice,0#No items|1#One item|1<{0} items}</string>
</resources>
Then, from the code all you have to do is the following:
String fmt = getResources().getText(R.string.item_shop).toString();
textView.setText(MessageFormat.format(fmt, amount));
You can read more about the format strings in the javadocs for MessageFormat

From http://developer.android.com/guide/topics/resources/string-resource.html#Plurals:
Note that the selection is made based on grammatical necessity. A string for zero in English will be ignored even if the quantity is 0, because 0 isn't grammatically different from 2, or any other number except 1 ("zero books", "one book", "two books", and so on). Don't be misled either by the fact that, say, two sounds like it could only apply to the quantity 2: a language may require that 2, 12, 102 (and so on) are all treated like one another but differently to other quantities. Rely on your translator to know what distinctions their language actually insists upon.
In conclusion, 'zero' is only used for certain languages (same goes for 'two' 'few' etc.) because the other languages do not have a special conjugation and therefore the 'zero' field is considered unnecessary

Here is a workaround I am using to handle this issue without switching to MessageFormat.
First I extract the "zero" string into its own string resource.
<string name="x_items_zero">No items.</string>
<plurals name="x_items">
<!-- NOTE: This "zero" value is never accessed but is kept here to show the intended usage of the "zero" string -->
<item quantity="zero">#string/x_items_zero</item>
<item quantity="one">One item.</item>
<item quantity="other">%d items.</item>
</plurals>
Then I have some convenience methods in my own ResourcesUtil
public static String getQuantityStringZero(Resources resources, int resId, int zeroResId, int quantity) {
if (quantity == 0) {
return resources.getString(zeroResId);
} else {
return resources.getQuantityString(resId, quantity, quantity);
}
}
public static String getQuantityStringZero(Resources resources, int resId, int zeroResId, int quantity, Object... formatArgs) {
if (quantity == 0) {
return resources.getString(zeroResId);
} else {
return resources.getQuantityString(resId, quantity, formatArgs);
}
}
Now anytime I want to use a specific string for quantity zero I call:
String pluralString = ResourcesUtil.getQuantityStringZero(
getContext().getResources(),
R.plural.x_items,
R.string.x_items_zero,
quantity
);
I wish there was something better but this at least gets the job done while keeping the string resource XML legible.

Android is using the CLDR plurals system, and this is just not how it works (so don't expect this to change).
The system is described here:
http://cldr.unicode.org/index/cldr-spec/plural-rules
In short, it's important to understand that "one" does not mean the number 1. Instead these keywords are categories, and the specific numbers n that belong to each category are defined by rules in the CLDR database:
http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
While there appears to be no language which uses "zero" for anything other than 0, there are languages which assign 0 to "one". There are certainly plenty of cases where "two" contains other numbers than just 2.
If Android where to allow you to do what you intended, your applications could not be properly translated into any number of languages with more complex plural rules.

If you are using data binding you can work around this with something like:
<TextView ...
android:text="#{collection.size() > 0 ? #plurals/plural_str(collection.size(), collection.size()) : #string/zero_str}"/>

I've wrote a Kotlin extension to handle all the scenarios that I can think about.
I have zeroResId as optional, so that sometimes we want to handle the zero by displaying "No Items", rather than "0 Items".
English treats zero grammatically as plural.
The selection of which string to use is made solely based on
grammatical necessity.
In English, a string for zero is ignored even
if the quantity is 0, because 0 isn't grammatically different from 2,
or any other number except 1 ("zero books", "one book", "two books",
and so on).
https://developer.android.com/guide/topics/resources/string-resource.html#Plurals
fun Context.getQuantityStringZero(
quantity: Int,
pluralResId: Int,
zeroResId: Int? = null
): String {
return if (zeroResId != null && quantity == 0) {
resources.getString(zeroResId)
} else {
resources.getQuantityString(pluralResId, quantity, quantity)
}
}

Android's implementation seems correct unlike iOS (see details here).
The correct way to implement this should be the same are in MessageFormat, which means that for non-grammatical categories, you should add explicit rules (using numbers instead of categories). A correct implementation could look like this:
<plurals name="item_shop">
<item quantity="0">No item</item>
<item quantity="1">One item</item>
<item quantity="one">%d item</item>
<item quantity="other">%d items</item>
</plurals>
See here you are using the number 0 instead of the plural category zero which does not apply to English.
Using MessageFormat, this would translate to this (you can test here):
{messageNumber, plural, =0 {No item.} =1 {One item.} one {# item.} other {# items.}}
In English the category one is equal to 1 so in the example one should never be used but this is not true for all languages, and when spelling out a number, you are better make sure that you know which plural rule applies for that language.

Related

How to use zero quantity case in Android quantity strings (plurals)?

I am trying to use the getQuantityString method in Resources to retrieve Quantity Strings based on android developer guidelines Quantity String in my Kotlin application.
One and the other case is ok, but zero is not working.
This is my code.
// text
txtNewProfileCount.text = resources.getQuantityString(
R.plurals.shortlists_profile_count,
filteredCandidateVMList.size,
filteredCandidateVMList.size
)
// plurals code
<plurals name="shortlists_profile_count">
<item quantity="zero">Sorry, there are no new profiles in your various shortlists</item>
<item quantity="one">Please review the new profile in your various shortlists</item>
<item quantity="other">Please review the %1$d new profiles in your various shortlists</item>
</plurals>
In case the quantity is one and other, the result is correct.
But in the zero cases, the result is Please review the 0 new profiles in your various shortlists.
This isn't the result I want.
How to solve when the quantity is zero?
The zero case is never used in English. English uses only one and other. Plural resources are intended only for grammatically unique constructs of specific languages. This is explained in the documentation:
The selection of which string to use is made solely based on
grammatical necessity. In English, a string for zero is ignored even
if the quantity is 0, because 0 isn't grammatically different from 2,
or any other number except 1 ("zero books", "one book", "two books",
and so on).
So you have to manually select a separate String resource using an if statement in your code.
Android Plurals is not support zero case now.
So you need to make your own function for this.
Here is one example.
fun getQuantityString(resources:Resources, resId:Int, quantity:Int, zeroResId:Int):String {
if (quantity == 0) {
return resources.getString(zeroResId)
} else {
return resources.getQuantityString(resId, quantity, quantity)
}
}

Android Plurals for float values

I would like to use plurals for my Android project.
However, the values I provide can be float values.
So for instance, when setting 1.5 stars, I want this to understand, it's not 1 star but 1.5 stars.
<plurals name="stars">
<item quantity="one">%d star</item>
<item quantity="other">%d stars</item>
</plurals>
However, the Android system seems to use integer values (%d) only.
The method looks like this:
String getQuantityString(#PluralsRes int id, int quantity, Object... formatArgs)
where quantity is defined as Int.
Is there any solution for this?
After doing further research, it appears there is no good solution for this.
As also seen in the other answers, they always require a lot of "manual processing" to it requiring no different workflow than creating separate string resources.
The general suggestion seems to be rounding / processing the float values manually (e.g checking whether the float value matches 1.0) and then using apropriate Int values for the plurals call.
But aside from not really using plurals then this comes with the problem of other languages (e.g. I have no clue if 1.5 stars would also be plural in another language as it is in English) and thus these rounding options may not apply universally.
So the answer is: there seems to be no perfect solution (meaning solved "automatically" by the Android system).
What I actually do therefore is to simply pick exceptions and use different Strings there.
So the (pseudo code) way of doing currently looks like
// optionally wrap different languages around
// if language == English
when (amountStars) {
is 1.0 -> getString(R.string.stars_singular, 1)
... ->
else -> getString(R.string.stars_plural, amountStars)
}
// if language == Chinese ...
where additional cases have to be "hard coded". So for example you have to decide whether 0 means
"0 stars" (plural string) or
"no star" (singular string)
But there seems no real benefit of using plurals over separate string resources with common placeholders. On the other hand this (at last for me) gives more flexibility for formatting options. For example, one may create a text like "1 star and a half" where it becomes singular again (even though numerically we would write 1.5 stars).
Don't use plurals for fractional numbers. Just stick with basic string resources and use a placeholder:
<string name="fractional_stars">%1$s stars</string>
getString(R.string.fractional_stars, 0.5F.toString())
or
<string name="fractional_stars">% stars</string>
getString(R.string.half_a_star).replace("%", 0.5F.toString())
Simply do this:
getQuantityString(R.plurals.stars, quantity > 1f ? 2 : 1, quantity):
And replace the %d in your strings with %f.
getQuantityString takes a quantity of type Int and Object... formatArgs
If you round the quantity to Int you would make sure that any value in 1.0 -> 1.99 is a single item and other than that is a plural
resources.getQuantityString(
R.plurals.products_left_in_stock_message_plural,
leftInStock.toInt(), leftInStock.toString()
)
So you only round it the quantity but pass the actually value as an argument
<plurals name="products_left_in_stock_message_plural">
<item quantity="one">Only one item is available from this product</item>
<item quantity="other">There are only %s item is available from this product</item>
</plurals>

How to use Android quantity strings (plurals)

I am trying to use the getQuantityString method in Resources to retrieve quantity strings (plurals) based on Android Developer guidelines Quantity string (plurals)
The error I am getting is
Error:(604) Multiple substitutions specified in non-positional format; did you mean to add the formatted="false" attribute?
Error:(604) Found tag where is expected
when I set up plurals as below
<plurals name="productCount">
<item quantity="one" formatted="true">%1$d of %2$d product</item>
<item quantity="other" formatted="true">%1$d of %2$d products</item>
</plurals>
And trying to read it as below
productIndexCountText.setText(getResources().getQuantityString(R.plurals.productCount, position, size));
One workaround is to break the string up to use plural only for the last part of the string and concatenate the two parts. But I am trying to avoid doing that if possible.
You don't need to set the "formatted" attribute for any of those items. When using quantity strings, there are only three possibilities:
the resource string is plain text and does not contain any parameters
the resource string contains only one parameter (most likely the quantity); use %d or whatever format you need
the resource string contains multiple parameters; all parameters have to be explicitly accessed by their position, for example %1$d
As for the getQuantityString method, there are two overloads: 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, and other for all other values including 0.
In Irish, 1 is mapped to one, 2 is mapped to two, 3-6 is few, 7-10 is many, 0 and 11+ is other.
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 to two (2, 302, 1002, ...). 3, 4 and values ending in 03 or 04 are mapped to few (3, 4, 6004, ...). Anything else is other (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 to few (3, 42, 103, 12035374, ...). This respects however that 12, 13 and 14 are exceptions from this rule because they are mapped to many. (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 class one 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.
Conclusion
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.
There are two special cases I'd also briefly mention:
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 parameterized strings 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.

Plural definition is ignored for zero quantity

I use plurals to compile a quantity string for an Android application. I follow exactly what one can find in the tutorials:
res.getQuantityString(
R.plurals.number_of_comments, commentsCount, commentsCount);
Here is the definition of the plurals:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<plurals name="number_of_comments">
<item quantity="zero">No comments</item>
<item quantity="one">One comment</item>
<item quantity="other">%d comments</item>
</plurals>
</resources>
Interesting enough, the output string is odd to what I definied:
commentsCount = 0 => "0 comments"
commentsCount = 1 => "One comment"
commentsCount = 2 => "2 comments"
I guess this is because the docs state When the language requires special treatment of the number 0 (as in Arabic). for zero quantity. Is there any way to force my definition?
According to the documentation :
The selection of which string to use is made solely based on
grammatical necessity. In English, a string for zero will be ignored
even if the quantity is 0, because 0 isn't grammatically different
from 2, or any other number except 1 ("zero books", "one book", "two
books", and so on).
If you still want to use a custom string for zero, you can load a different string when the quantity is zero :
if (commentsCount == 0)
str = res.getString(R.string.number_of_comments_zero);
else
str = res.getQuantityString(R.plurals.number_of_comments, commentsCount, commentsCount);
In Kotlin (thanks to Dalmas):
val result = commentsCount.takeIf { it != 0 }?.let {
resources.getQuantityString(R.plurals.number_of_comments, it, it)
} ?: resources.getString(R.string.number_of_comments_zero)
Plural is Unicode form. all of plural value here.
In English, plural for zero like 2, 3,4 so you must if else this value to use others string for this.

getQuantityString returns wrong string with 0 value

In an android app, I have the following string resources:
<plurals name="test">
<item quantity="zero">"I have 0 item"</item>
<item quantity="one">"I have 1 item"</item>
<item quantity="other">"I have several items"</item>
</plurals>
And the following line of code:
String text = getResources().getQuantityString(R.plurals.test, 0)
which I would expect to return
I have 0 item
But it actually returns
I have 1 item
Why ?
Quantity Strings are broken on some Plattforms and phones as the issue Tracker and this discussion "Should Plurals and Quantity Strings be used" points out. It depends on many factors which you cannot control (i.e. localization on the phone).
One solution can be to take an external library like this one, which mimes the same functionallity.
Another solution is stated in the documentation of plurals in android. Avoid using it and use "quantity-neutral" formulations like "Books: 1"
Change the code like this
String text = getResources().getQuantityString(R.plurals.test, 0,0);

Categories

Resources