ICU-21123 Support FormattedNumber::getGender() for "short" and "narrow" formatting too

See #1617
This commit is contained in:
Hugo van der Merwe 2021-03-03 22:04:35 +00:00
parent b79c299f90
commit 119dfa4f24
4 changed files with 88 additions and 49 deletions

View file

@ -228,6 +228,10 @@ const char *getGenderForBuiltin(const Locale &locale, MeasureUnit builtinUnit, U
// 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 @@ class InflectedPluralSink : public ResourceSink {
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 @@ void getMeasureData(const Locale &locale,
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 @@ void getMeasureData(const Locale &locale,
// 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);
}

View file

@ -2287,15 +2287,14 @@ void NumberFormatterApiTest::unitGender() {
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))

View file

@ -141,10 +141,8 @@ public class LongNameHandler
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 @@ public class LongNameHandler
// 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 @@ public class LongNameHandler
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 @@ public class LongNameHandler
if (width == UnitWidth.SHORT) {
return;
}
} catch (Exception e) {
} catch (MissingResourceException e) {
// Continue: fall back to short
}
@ -338,25 +345,40 @@ public class LongNameHandler
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 @@ public class LongNameHandler
key.append(compoundKey);
try {
return resource.getStringWithFallback(key.toString());
} catch (Exception e) {
} catch (MissingResourceException e) {
if (width == UnitWidth.SHORT) {
return "";
}
@ -440,7 +462,7 @@ public class LongNameHandler
key.append(compoundKey);
try {
return resource.getStringWithFallback(key.toString());
} catch (Exception e) {
} catch (MissingResourceException e) {
return "";
}
}
@ -502,7 +524,7 @@ public class LongNameHandler
} else {
this.value1 = value;
}
} catch (Exception e) {
} catch (MissingResourceException e) {
// Fall back to uninflected.
}
}

View file

@ -2278,14 +2278,13 @@ public class NumberFormatterApiTest extends TestFmwk {
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))