105

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?

EricLarch
  • 5,653
  • 5
  • 31
  • 37
  • 23
    This issue is reported here http://code.google.com/p/android/issues/detail?id=8287 . Not fixed yet :/ – OcuS Apr 13 '11 at 16:12
  • 5
    I don't think its a bug: 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) – ByteMe May 24 '12 at 01:10
  • 3
    Sadly not all languages are identical to english – Armand Jun 16 '16 at 11:43
  • 4
    I think this is a bug in the sense that, grammar aside, 99% of the times I needed to use this, I needed the _missing_ functionality. So, yes, I believe this is a bug Google will probably never fix. As such, a workaround is needed. You see, APIs are there to help consumers, get things done, not to exclusively represent abstract concepts from the real world. If Zero would have been supported out of the box, both groups (those who need the grammar correctness and those who don’t), could get away without having to use something different. – Martin Marconcini Jul 24 '18 at 19:45

7 Answers7

93

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&lt;{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

voghDev
  • 5,641
  • 2
  • 37
  • 41
Elias Mårtenson
  • 3,820
  • 23
  • 32
  • 8
    Nice workaround. I can imagine just right now what kind of mess the people that translate my applications will put in my files... :) – OcuS Apr 18 '11 at 13:59
  • That would be up to the translators. :-) The benefit here is that they have a lot of freedom as to exactly how it should be done. Also, it's much cleaner and easier for the developer that writes it. – Elias Mårtenson Apr 18 '11 at 15:19
  • 1
    How do I compose format string for "few" numbers (numbers ending 2,3,4, but not ending with 12,13,14)? – vokilam Jul 26 '13 at 09:09
  • 1
    The Java `MessageFormat` class does not provide for this. What I would recommend is that you pass in the value of the last digit as an extra parameter which allows you to do selection on that. This is needed for Russian, at least, and probably other similar languages as well. – Elias Mårtenson Jul 29 '13 at 05:04
  • 10
    Wow, that's ugly. Logic like this should go in code, not translations. Plurals are only supposed to help with language differences for numbers. With the recommendation for "few", you've now successfully moved logic into translations and translations into logic. – DennisK Jan 03 '14 at 09:27
  • 4
    It's certainly not a nice workaround. Just add a string for no_items and an if clause before. If (items.count == 0 ) { use the string } else { use the plural } and THEN let the translators deal with the problem in the strings.xml file… – Martin Marconcini Feb 20 '14 at 00:38
  • @MartínMarconcini Actually, for US English, if you are using two values, there would typically be a value that's used for both zero and greater than one (the plural), and different value for one (the singular). If you're making a distinction for zero, then you need three values. – GreyBeardedGeek Jan 27 '15 at 21:12
  • @GreyBeardedGeek The whole thread is about how Android does not support the third value for 0. You may want to show “no items” or “0 items”, with the Plurals engine, that choice is not possible. Yes, you have “No Items” for zero, “1 item” for singular and “n Items” for plural. This is not supported in the Plurals engine. You only get `0 Items` from the `N items` and “1 item” for the singular. – Martin Marconcini Jul 24 '18 at 19:48
  • 2
    @MartínMarconcini you are correct ... over 3 years later :-) – GreyBeardedGeek Jul 25 '18 at 01:19
  • 1
    @GreyBeardedGeek you’d not believe it but someone linked me this question/answer and we were all “TIL”. Then I realized there was a comment (by me) that was 3 years old. `¯\_(ツ)_/¯` Good to see that Android is fixing the problems users report in the daily usage instead of what we get: abstractions that sometimes are ok, sometimes are good, sometimes are mediocre. – Martin Marconcini Jul 25 '18 at 01:23
  • `java.text.MessageFormat` is orders of magnitude slower than other methods such as string concatenation or `String.format()` – ccpizza Feb 15 '20 at 21:30
53

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

ByteMe
  • 1,436
  • 12
  • 15
  • 7
    "A string for zero in English will be ignored even if the quantity is 0, because 0 isn't grammatically different from 2" ... how about "I have no fruit" vs. "I have an Apple and an Orange"? – Jeffrey Blattman Nov 05 '12 at 23:20
  • 4
    There are lots of different ways to word 0 vs 2. But the idea behind these resources is to use them with a number. So something like: I have 0 apples. I have 1 apple. I have 2 apples. – ByteMe Nov 08 '12 at 21:46
  • 7
    that's too bad. they shouldn't have tried to make an language-specific assumptions. everyone i know that's used plurals has been confused by this. – Jeffrey Blattman Nov 08 '12 at 23:27
  • Making language specific assumptions is the whole point of this system. There might be a language where the grammatical form for the quantity 0 is also used for 10, 100 etc. If you were abusing the "zero" category in English for a special string, that string could not be translated to such a language. – miracle2k Nov 25 '12 at 20:28
  • 2
    @miracle2k: Message are not translated by category, so whatever categories or messages you have in one language does not affect another language. So if you have a 'zero' class message in English for the zero count only, and another language is using the 'zero' class for counts 10,100 that does not matter because you do not translate a 'zero' class message. On the other hand: if you create a separate message resource for the 0 count in English, then you are creating a workflow issue for translators because that case is already covered by the original 'zero' class message in their language. – Basel Shishani Jan 02 '13 at 11:43
  • 2
    It's true that enabling the "zero" category in English as a convenience feature could be done, but it's not "clean", and only encourages bad behavior. You might get the idea of putting text encouraging the user to add items into the "zero" string - then possibly untranslatable. Strictly speaking, the OP's example ("No items") already exhibits that problem, because a translator might want to phrase the 0-case using words as well. It's not a workflow issue if you consider "%s item" to be the singular version of "%s items", and "No items" to be something linguistically different altogether. – miracle2k Jan 02 '13 at 23:03
19

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.

Justin Fiedler
  • 6,478
  • 3
  • 21
  • 25
13

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.

miracle2k
  • 29,597
  • 21
  • 65
  • 64
  • 6
    Be careful when posting copy and paste boilerplate/verbatim answers to multiple questions, these tend to be flagged as "spammy" by the community. If you're doing this then it usually means the questions are duplicates so flag them as such instead: http://stackoverflow.com/questions/8473816 – Kev Mar 02 '12 at 23:59
2

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}"/>
Mark
  • 3,334
  • 1
  • 22
  • 27
2

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)
    }
}
Morgan Koh
  • 2,297
  • 24
  • 24
0

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.

Nicolas Bouvrette
  • 4,295
  • 1
  • 39
  • 53