From 1ad636c31501b1f407a3bf504d14689c1d5c7f5a Mon Sep 17 00:00:00 2001 From: Elliott Hughes Date: Thu, 1 Jul 2010 16:51:48 -0700 Subject: [PATCH] Defer to ICU's knowledge of language-specific grammatical quantity rules. Also improve the documentation to make it a little less unclear what this is all about. In particular, explain why the original submitter's complaint about "zero" never being used in English, is expected behavior. Bug: 2663392 Change-Id: Iade3b4f5c549ce01a95fd0e7e5c6ea394178eda3 --- .../java/android/content/res/PluralRules.java | 111 ------------------ core/java/android/content/res/Resources.java | 58 +++++++-- .../guide/topics/resources/string-resource.jd | 67 ++++++++--- 3 files changed, 102 insertions(+), 134 deletions(-) delete mode 100644 core/java/android/content/res/PluralRules.java diff --git a/core/java/android/content/res/PluralRules.java b/core/java/android/content/res/PluralRules.java deleted file mode 100644 index 2dce3c10197b7..0000000000000 --- a/core/java/android/content/res/PluralRules.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.content.res; - -import java.util.Locale; - -/* - * Yuck-o. This is not the right way to implement this. When the ICU PluralRules - * object has been integrated to android, we should switch to that. For now, yuck-o. - */ - -abstract class PluralRules { - - static final int QUANTITY_OTHER = 0x0000; - static final int QUANTITY_ZERO = 0x0001; - static final int QUANTITY_ONE = 0x0002; - static final int QUANTITY_TWO = 0x0004; - static final int QUANTITY_FEW = 0x0008; - static final int QUANTITY_MANY = 0x0010; - - static final int ID_OTHER = 0x01000004; - - abstract int quantityForNumber(int n); - - final int attrForNumber(int n) { - return PluralRules.attrForQuantity(quantityForNumber(n)); - } - - static final int attrForQuantity(int quantity) { - // see include/utils/ResourceTypes.h - switch (quantity) { - case QUANTITY_ZERO: return 0x01000005; - case QUANTITY_ONE: return 0x01000006; - case QUANTITY_TWO: return 0x01000007; - case QUANTITY_FEW: return 0x01000008; - case QUANTITY_MANY: return 0x01000009; - default: return ID_OTHER; - } - } - - static final String stringForQuantity(int quantity) { - switch (quantity) { - case QUANTITY_ZERO: - return "zero"; - case QUANTITY_ONE: - return "one"; - case QUANTITY_TWO: - return "two"; - case QUANTITY_FEW: - return "few"; - case QUANTITY_MANY: - return "many"; - default: - return "other"; - } - } - - static final PluralRules ruleForLocale(Locale locale) { - String lang = locale.getLanguage(); - if ("cs".equals(lang)) { - if (cs == null) cs = new cs(); - return cs; - } - else { - if (en == null) en = new en(); - return en; - } - } - - private static PluralRules cs; - private static class cs extends PluralRules { - int quantityForNumber(int n) { - if (n == 1) { - return QUANTITY_ONE; - } - else if (n >= 2 && n <= 4) { - return QUANTITY_FEW; - } - else { - return QUANTITY_OTHER; - } - } - } - - private static PluralRules en; - private static class en extends PluralRules { - int quantityForNumber(int n) { - if (n == 1) { - return QUANTITY_ONE; - } - else { - return QUANTITY_OTHER; - } - } - } -} - diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 0608cc02bf3dd..5ac55c47df83c 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -16,7 +16,6 @@ package android.content.res; - import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; @@ -41,6 +40,8 @@ import java.io.InputStream; import java.lang.ref.WeakReference; import java.util.Locale; +import libcore.icu.NativePluralRules; + /** * Class for accessing an application's resources. This sits on top of the * asset manager of the application (accessible through getAssets()) and @@ -52,6 +53,8 @@ public class Resources { private static final boolean DEBUG_CONFIG = false; private static final boolean TRACE_FOR_PRELOAD = false; + private static final int ID_OTHER = 0x01000004; + // Use the current SDK version code. If we are a development build, // also allow the previous SDK version + 1. private static final int sSdkVersion = Build.VERSION.SDK_INT @@ -86,7 +89,7 @@ public class Resources { /*package*/ final AssetManager mAssets; private final Configuration mConfiguration = new Configuration(); /*package*/ final DisplayMetrics mMetrics = new DisplayMetrics(); - PluralRules mPluralRule; + private NativePluralRules mPluralRule; private CompatibilityInfo mCompatibilityInfo; private Display mDefaultDisplay; @@ -203,9 +206,17 @@ public class Resources { } /** + * Return the character sequence associated with a particular resource ID for a particular + * numerical quantity. + * + *

See String + * Resources for more on quantity strings. + * * @param id The desired resource identifier, as generated by the aapt * tool. This integer encodes the package, type, and resource * entry. The value 0 is an invalid identifier. + * @param quantity The number used to get the correct string for the current language's + * plural rules. * * @throws NotFoundException Throws NotFoundException if the given ID does not exist. * @@ -213,29 +224,52 @@ public class Resources { * possibly styled text information. */ public CharSequence getQuantityText(int id, int quantity) throws NotFoundException { - PluralRules rule = getPluralRule(); - CharSequence res = mAssets.getResourceBagText(id, rule.attrForNumber(quantity)); + NativePluralRules rule = getPluralRule(); + CharSequence res = mAssets.getResourceBagText(id, + attrForQuantityCode(rule.quantityForInt(quantity))); if (res != null) { return res; } - res = mAssets.getResourceBagText(id, PluralRules.ID_OTHER); + res = mAssets.getResourceBagText(id, ID_OTHER); if (res != null) { return res; } throw new NotFoundException("Plural resource ID #0x" + Integer.toHexString(id) + " quantity=" + quantity - + " item=" + PluralRules.stringForQuantity(rule.quantityForNumber(quantity))); + + " item=" + stringForQuantityCode(rule.quantityForInt(quantity))); } - private PluralRules getPluralRule() { + private NativePluralRules getPluralRule() { synchronized (mSync) { if (mPluralRule == null) { - mPluralRule = PluralRules.ruleForLocale(mConfiguration.locale); + mPluralRule = NativePluralRules.forLocale(mConfiguration.locale); } return mPluralRule; } } + private static int attrForQuantityCode(int quantityCode) { + switch (quantityCode) { + case NativePluralRules.ZERO: return 0x01000005; + case NativePluralRules.ONE: return 0x01000006; + case NativePluralRules.TWO: return 0x01000007; + case NativePluralRules.FEW: return 0x01000008; + case NativePluralRules.MANY: return 0x01000009; + default: return ID_OTHER; + } + } + + private static String stringForQuantityCode(int quantityCode) { + switch (quantityCode) { + case NativePluralRules.ZERO: return "zero"; + case NativePluralRules.ONE: return "one"; + case NativePluralRules.TWO: return "two"; + case NativePluralRules.FEW: return "few"; + case NativePluralRules.MANY: return "many"; + default: return "other"; + } + } + /** * Return the string value associated with a particular resource ID. It * will be stripped of any styled text information. @@ -290,6 +324,9 @@ public class Resources { * stripped of any styled text information. * {@more} * + *

See String + * Resources for more on quantity strings. + * * @param id The desired resource identifier, as generated by the aapt * tool. This integer encodes the package, type, and resource * entry. The value 0 is an invalid identifier. @@ -312,6 +349,9 @@ public class Resources { * Return the string value associated with a particular resource ID for a particular * numerical quantity. * + *

See String + * Resources for more on quantity strings. + * * @param id The desired resource identifier, as generated by the aapt * tool. This integer encodes the package, type, and resource * entry. The value 0 is an invalid identifier. @@ -1334,7 +1374,7 @@ public class Resources { } synchronized (mSync) { if (mPluralRule != null) { - mPluralRule = PluralRules.ruleForLocale(config.locale); + mPluralRule = NativePluralRules.forLocale(config.locale); } } } diff --git a/docs/html/guide/topics/resources/string-resource.jd b/docs/html/guide/topics/resources/string-resource.jd index 81c5d55d6dba6..2db38f17eafaa 100644 --- a/docs/html/guide/topics/resources/string-resource.jd +++ b/docs/html/guide/topics/resources/string-resource.jd @@ -12,8 +12,8 @@ your application with strings:

XML resource that provides a single string.
String Array
XML resource that provides an array of strings.
-
Plurals
-
XML resource that carries different strings for different pluralizations +
Quantity Strings (Plurals)
+
XML resource that carries different strings for different quantities of the same word or phrase.
@@ -218,13 +218,30 @@ getStringArray}(R.array.planets_array); -

Plurals

+

Quantity Strings (Plurals)

-

A pair of strings that each provide a different plural form of the same word or phrase, -which you can collectively reference from the application. When you request the plurals -resource using a method such as {@link android.content.res.Resources#getQuantityString(int,int) -getQuantityString()}, you must pass a "count", which will determine the plural form you -require and return that string to you.

+

Different languages have different rules for grammatical agreement with quantity. In English, +for example, the quantity 1 is a special case. We write "1 book", but for any other quantity we'd +write "n books". This distinction between singular and plural is very common, but other +languages make finer distinctions. The full set supported by Android is zero, +one, two, few, many, and other. + +

The rules for deciding which case to use for a given language and quantity can be very complex, +so Android provides you with methods such as +{@link android.content.res.Resources#getQuantityString(int,int) getQuantityString()} to select +the appropriate resource for you. + +

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", et cetera). +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 (et cetera) are all treated like one +another but differently to other quantities. Rely on your translator to know what distinctions +their language actually insists upon. + +

It's often possible to avoid quantity strings by using quantity-neutral formulations such as +"Books: 1". This will make your life and your translators' lives easier, if it's a style that's +in keeping with your application.

Note: A plurals collection is a simple resource that is referenced using the value provided in the {@code name} attribute (not the name of the XML @@ -251,7 +268,7 @@ In Java: R.plurals.plural_name <plurals name="plural_name"> <item - quantity=["one" | "other"] + quantity=["zero" | "one" | "two" | "few" | "many" | "other"] >text_string</item> </plurals> </resources> @@ -285,16 +302,27 @@ Styling, below, for information about to properly style and format your stri

attributes:

quantity
-
Keyword. A value indicating the case in which this string should be used. Valid -values: +
Keyword. A value indicating when this string should be used. Valid +values, with non-exhaustive examples in parentheses: - + - + + + + + + + + + + + + +
ValueDescription
{@code one}When there is one (a singular string).{@code zero}When the language requires special treatment of the number 0 (as in Arabic).
{@code other}When the quantity is anything other than one (a plural -string, but also used when the count is zero).{@code one}When the language requires special treatment of numbers like one (as with the number 1 in English and most other languages; in Russian, any number ending in 1 but not ending in 11 is in this class).
{@code two}When the language requires special treatment of numbers like two (as in Welsh).
{@code few}When the language requires special treatment of "small" numbers (as with 2, 3, and 4 in Czech; or numbers ending 2, 3, or 4 but not 12, 13, or 14 in Polish).
{@code many}When the language requires special treatment of "large" numbers (as with numbers ending 11-99 in Maltese).
{@code other}When the language does not require special treatment of the given quantity.
@@ -314,6 +342,17 @@ string, but also used when the count is zero). <item quantity="other">%d songs found.</item> </plurals> </resources> + +

XML file saved at {@code res/values-pl/strings.xml}:

+
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <plurals name="numberOfSongsAvailable">
+        <item quantity="one">Znaleziono jedną piosenkę.</item>
+        <item quantity="few">Znaleziono %d piosenki.</item>
+        <item quantity="other">Znaleziono %d piosenek.</item>
+    </plurals>
+</resources>
 

Java code: