ICU-21123 Support FormattedNumber::getGender() for "short" and "narrow" formatting too
See #1617
diff --git a/icu4c/source/i18n/number_longnames.cpp b/icu4c/source/i18n/number_longnames.cpp
index be62a13..7e5e3ca 100644
--- a/icu4c/source/i18n/number_longnames.cpp
+++ b/icu4c/source/i18n/number_longnames.cpp
@@ -228,6 +228,10 @@
// case. It loads all plural forms, because selection between plural forms is
// dependent upon the value being formatted.
//
+// See data/unit/de.txt and data/unit/fr.txt for examples - take a look at
+// units/compound/power2: German has case, French has differences for gender,
+// but no case.
+//
// TODO(icu-units#138): Conceptually similar to PluralTableSink, however the
// tree structures are different. After homogenizing the structures, we may be
// able to unify the two classes.
@@ -336,6 +340,11 @@
UnicodeString *outArray;
};
+// Fetches localised formatting patterns for the given subKey. See documentation
+// for InflectedPluralSink for details.
+//
+// Data is loaded for the appropriate unit width, with missing data filled in
+// from unitsShort.
void getInflectedMeasureData(StringPiece subKey,
const Locale &locale,
const UNumberUnitWidth &width,
@@ -429,28 +438,40 @@
LocalUResourceBundlePointer unitsBundle(ures_open(U_ICUDATA_UNIT, locale.getName(), &status));
if (U_FAILURE(status)) { return; }
+ CharString subKey;
+ subKey.append("/", status);
+ subKey.append(unit.getType(), status);
+ subKey.append("/", status);
+
// Map duration-year-person, duration-week-person, etc. to duration-year, duration-week, ...
// TODO(ICU-20400): Get duration-*-person data properly with aliases.
- StringPiece subtypeForResource;
int32_t subtypeLen = static_cast<int32_t>(uprv_strlen(unit.getSubtype()));
if (subtypeLen > 7 && uprv_strcmp(unit.getSubtype() + subtypeLen - 7, "-person") == 0) {
- subtypeForResource = {unit.getSubtype(), subtypeLen - 7};
+ subKey.append({unit.getSubtype(), subtypeLen - 7}, status);
} else {
- subtypeForResource = unit.getSubtype();
+ subKey.append({unit.getSubtype(), subtypeLen}, status);
+ }
+
+ if (width != UNUM_UNIT_WIDTH_FULL_NAME) {
+ UErrorCode localStatus = status;
+ CharString genderKey;
+ genderKey.append("units", localStatus);
+ genderKey.append(subKey, localStatus);
+ genderKey.append("/gender", localStatus);
+ StackUResourceBundle fillIn;
+ ures_getByKeyWithFallback(unitsBundle.getAlias(), genderKey.data(), fillIn.getAlias(),
+ &localStatus);
+ outArray[GENDER_INDEX] = ures_getUnicodeString(fillIn.getAlias(), &localStatus);
}
CharString key;
key.append("units", status);
- // TODO(icu-units#140): support gender for other unit widths.
if (width == UNUM_UNIT_WIDTH_NARROW) {
key.append("Narrow", status);
} else if (width == UNUM_UNIT_WIDTH_SHORT) {
key.append("Short", status);
}
- key.append("/", status);
- key.append(unit.getType(), status);
- key.append("/", status);
- key.append(subtypeForResource, status);
+ key.append(subKey, status);
// Grab desired case first, if available. Then grab no-case data to fill in
// the gaps.
@@ -486,10 +507,8 @@
// TODO(ICU-13353): The fallback to short does not work in ICU4C.
// Manually fall back to short (this is done automatically in Java).
key.clear();
- key.append("unitsShort/", status);
- key.append(unit.getType(), status);
- key.append("/", status);
- key.append(subtypeForResource, status);
+ key.append("unitsShort", status);
+ key.append(subKey, status);
ures_getAllItemsWithFallback(unitsBundle.getAlias(), key.data(), sink, status);
}
diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp
index 40124c2..2540a1a 100644
--- a/icu4c/source/test/intltest/numbertest_api.cpp
+++ b/icu4c/source/test/intltest/numbertest_api.cpp
@@ -2287,15 +2287,14 @@
LocalizedNumberFormatter formatter;
FormattedNumber fn;
for (const TestCase &t : cases) {
- // TODO(icu-units#140): make this work for more than just UNUM_UNIT_WIDTH_FULL_NAME
- // formatter = NumberFormatter::with()
- // .unit(MeasureUnit::forIdentifier(t.unitIdentifier, status))
- // .locale(Locale(t.locale));
- // fn = formatter.formatDouble(1.1, status);
- // assertEquals(UnicodeString("Testing gender with default width, unit: ") + t.unitIdentifier +
- // ", locale: " + t.locale,
- // t.expectedGender, fn.getGender(status));
- // status.assertSuccess();
+ formatter = NumberFormatter::with()
+ .unit(MeasureUnit::forIdentifier(t.unitIdentifier, status))
+ .locale(Locale(t.locale));
+ fn = formatter.formatDouble(1.1, status);
+ assertEquals(UnicodeString("Testing gender with default width, unit: ") + t.unitIdentifier +
+ ", locale: " + t.locale,
+ t.expectedGender, fn.getGender(status));
+ status.assertSuccess();
formatter = NumberFormatter::with()
.unit(MeasureUnit::forIdentifier(t.unitIdentifier, status))
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java
index 608fa01..eeb959d 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java
@@ -141,10 +141,8 @@
key.append("/gender");
try {
- ICUResourceBundle stackBundle =
- (ICUResourceBundle)unitsBundle.getWithFallback(key.toString());
- return stackBundle.getString();
- } catch (Exception e) {
+ return unitsBundle.getWithFallback(key.toString()).getString();
+ } catch (MissingResourceException e) {
// TODO(icu-units#28): "$unitRes/gender" does not exist. Do we want to
// check whether the parent "$unitRes" exists? Then we could return
// U_MISSING_RESOURCE_ERROR for incorrect usage (e.g. builtinUnit not
@@ -161,6 +159,10 @@
// case. It loads all plural forms, because selection between plural forms is
// dependent upon the value being formatted.
//
+ // See data/unit/de.txt and data/unit/fr.txt for examples - take a look at
+ // units/compound/power2: German has case, French has differences for
+ // gender, but no case.
+ //
// TODO(icu-units#138): Conceptually similar to PluralTableSink, however the
// tree structures are different. After homogenizing the structures, we may be
// able to unify the two classes.
@@ -259,6 +261,11 @@
String[] outArray;
}
+ // Fetches localised formatting patterns for the given subKey. See
+ // documentation for InflectedPluralSink for details.
+ //
+ // Data is loaded for the appropriate unit width, with missing data filled
+ // in from unitsShort.
static void getInflectedMeasureData(String subKey,
ULocale locale,
UnitWidth width,
@@ -284,7 +291,7 @@
if (width == UnitWidth.SHORT) {
return;
}
- } catch (Exception e) {
+ } catch (MissingResourceException e) {
// Continue: fall back to short
}
@@ -338,25 +345,40 @@
ICUResourceBundle resource;
resource = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_UNIT_BASE_NAME,
locale);
+
+ StringBuilder subKey = new StringBuilder();
+ subKey.append("/");
+ subKey.append(unit.getType());
+ subKey.append("/");
+
+ // Map duration-year-person, duration-week-person, etc. to duration-year, duration-week, ...
+ // TODO(ICU-20400): Get duration-*-person data properly with aliases.
+ if (unit.getSubtype() != null && unit.getSubtype().endsWith("-person")) {
+ subKey.append(unit.getSubtype(), 0, unit.getSubtype().length() - 7);
+ } else {
+ subKey.append(unit.getSubtype());
+ }
+
+ if (width != UnitWidth.FULL_NAME) {
+ StringBuilder genderKey = new StringBuilder();
+ genderKey.append("units");
+ genderKey.append(subKey);
+ genderKey.append("/gender");
+ try {
+ outArray[GENDER_INDEX] = resource.getWithFallback(genderKey.toString()).getString();
+ } catch (MissingResourceException e) {
+ // continue
+ }
+ }
+
StringBuilder key = new StringBuilder();
key.append("units");
- // TODO(icu-units#140): support gender for other unit widths.
if (width == UnitWidth.NARROW) {
key.append("Narrow");
} else if (width == UnitWidth.SHORT) {
key.append("Short");
}
- key.append("/");
- key.append(unit.getType());
- key.append("/");
-
- // Map duration-year-person, duration-week-person, etc. to duration-year, duration-week, ...
- // TODO(ICU-20400): Get duration-*-person data properly with aliases.
- if (unit.getSubtype() != null && unit.getSubtype().endsWith("-person")) {
- key.append(unit.getSubtype(), 0, unit.getSubtype().length() - 7);
- } else {
- key.append(unit.getSubtype());
- }
+ key.append(subKey);
// Grab desired case first, if available. Then grab nominative case to fill
// in the gaps.
@@ -427,7 +449,7 @@
key.append(compoundKey);
try {
return resource.getStringWithFallback(key.toString());
- } catch (Exception e) {
+ } catch (MissingResourceException e) {
if (width == UnitWidth.SHORT) {
return "";
}
@@ -440,7 +462,7 @@
key.append(compoundKey);
try {
return resource.getStringWithFallback(key.toString());
- } catch (Exception e) {
+ } catch (MissingResourceException e) {
return "";
}
}
@@ -502,7 +524,7 @@
} else {
this.value1 = value;
}
- } catch (Exception e) {
+ } catch (MissingResourceException e) {
// Fall back to uninflected.
}
}
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
index cd56e27..a50c282 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
@@ -2278,14 +2278,13 @@
LocalizedNumberFormatter formatter;
FormattedNumber fn;
for (TestCase t : cases) {
- // // TODO(icu-units#140): make this work for more than just UnitWidth.FULL_NAME
- // formatter = NumberFormatter.with()
- // .unit(MeasureUnit.forIdentifier(t.unitIdentifier))
- // .locale(new ULocale(t.locale));
- // fn = formatter.format(1.1);
- // assertEquals("Testing gender with default width, unit: " + t.unitIdentifier +
- // ", locale: " + t.locale,
- // t.expectedGender, fn.getGender());
+ formatter = NumberFormatter.with()
+ .unit(MeasureUnit.forIdentifier(t.unitIdentifier))
+ .locale(new ULocale(t.locale));
+ fn = formatter.format(1.1);
+ assertEquals("Testing gender with default width, unit: " + t.unitIdentifier +
+ ", locale: " + t.locale,
+ t.expectedGender, fn.getGender());
formatter = NumberFormatter.with()
.unit(MeasureUnit.forIdentifier(t.unitIdentifier))