mirror of
https://github.com/unicode-org/icu.git
synced 2025-04-05 05:25:34 +00:00
parent
aebe91cdda
commit
f6361ebf76
12 changed files with 464 additions and 35 deletions
|
@ -24,19 +24,26 @@ import com.ibm.icu.util.MeasureUnit;
|
|||
import com.ibm.icu.util.ULocale;
|
||||
import com.ibm.icu.util.UResourceBundle;
|
||||
|
||||
/**
|
||||
* Takes care of formatting currency and measurement unit names, as well as populating the gender of measure units.
|
||||
*/
|
||||
public class LongNameHandler
|
||||
implements MicroPropsGenerator, ModifierStore, LongNameMultiplexer.ParentlessMicroPropsGenerator {
|
||||
|
||||
private static final int DNAM_INDEX = StandardPlural.COUNT;
|
||||
private static final int PER_INDEX = StandardPlural.COUNT + 1;
|
||||
static final int ARRAY_LENGTH = StandardPlural.COUNT + 2;
|
||||
private static int i = 0;
|
||||
private static final int DNAM_INDEX = StandardPlural.COUNT + i++;
|
||||
private static final int PER_INDEX = StandardPlural.COUNT + i++;
|
||||
private static final int GENDER_INDEX = StandardPlural.COUNT + i++;
|
||||
static final int ARRAY_LENGTH = StandardPlural.COUNT + i++;
|
||||
|
||||
private static int getIndex(String pluralKeyword) {
|
||||
// pluralKeyword can also be "dnam" or "per"
|
||||
// pluralKeyword can also be "dnam", "per" or "gender"
|
||||
if (pluralKeyword.equals("dnam")) {
|
||||
return DNAM_INDEX;
|
||||
} else if (pluralKeyword.equals("per")) {
|
||||
return PER_INDEX;
|
||||
} else if (pluralKeyword.equals("gender")) {
|
||||
return GENDER_INDEX;
|
||||
} else {
|
||||
return StandardPlural.fromString(pluralKeyword).ordinal();
|
||||
}
|
||||
|
@ -71,14 +78,16 @@ public class LongNameHandler
|
|||
UResource.Table pluralsTable = value.getTable();
|
||||
for (int i = 0; pluralsTable.getKeyAndValue(i, key, value); ++i) {
|
||||
String keyString = key.toString();
|
||||
if (keyString.equals("case") || keyString.equals("gender")) {
|
||||
// TODO: @Hugo to fix for new grammatical stuff
|
||||
|
||||
if (keyString.equals("case")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int index = getIndex(keyString);
|
||||
if (outArray[index] != null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String formatString = value.getString();
|
||||
outArray[index] = formatString;
|
||||
}
|
||||
|
@ -86,11 +95,11 @@ public class LongNameHandler
|
|||
}
|
||||
|
||||
// NOTE: outArray MUST have at least ARRAY_LENGTH entries. No bounds checking is performed.
|
||||
|
||||
static void getMeasureData(
|
||||
ULocale locale,
|
||||
MeasureUnit unit,
|
||||
UnitWidth width,
|
||||
String unitDisplayCase,
|
||||
String[] outArray) {
|
||||
PluralTableSink sink = new PluralTableSink(outArray);
|
||||
ICUResourceBundle resource;
|
||||
|
@ -98,6 +107,7 @@ public class LongNameHandler
|
|||
locale);
|
||||
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) {
|
||||
|
@ -115,6 +125,29 @@ public class LongNameHandler
|
|||
key.append(unit.getSubtype());
|
||||
}
|
||||
|
||||
// Grab desired case first, if available. Then grab nominative case to fill
|
||||
// in the gaps.
|
||||
//
|
||||
// TODO(icu-units#138): check that fallback is spec-compliant
|
||||
if (width == UnitWidth.FULL_NAME
|
||||
&& unitDisplayCase != null
|
||||
&& !unitDisplayCase.isEmpty()) {
|
||||
StringBuilder caseKey = new StringBuilder();
|
||||
caseKey.append(key);
|
||||
caseKey.append("/case/");
|
||||
caseKey.append(unitDisplayCase);
|
||||
|
||||
try {
|
||||
resource.getAllItemsWithFallback(caseKey.toString(), sink);
|
||||
// TODO(icu-units#138): our fallback logic is not spec-compliant: we
|
||||
// check the given case, then go straight to the no-case data. The spec
|
||||
// states we should first look for case="nominative". As part of #138,
|
||||
// either get the spec changed, or add unit tests that warn us if
|
||||
// case="nominative" data differs from no-case data?
|
||||
} catch (MissingResourceException e) {
|
||||
// continue.
|
||||
}
|
||||
}
|
||||
try {
|
||||
resource.getAllItemsWithFallback(key.toString(), sink);
|
||||
} catch (MissingResourceException e) {
|
||||
|
@ -161,6 +194,28 @@ public class LongNameHandler
|
|||
}
|
||||
}
|
||||
|
||||
private static String getDeriveCompoundRule(ULocale locale, String feature, String structure) {
|
||||
ICUResourceBundle derivationsBundle =
|
||||
(ICUResourceBundle) UResourceBundle
|
||||
.getBundleInstance(ICUData.ICU_BASE_NAME, "grammaticalFeatures");
|
||||
|
||||
derivationsBundle = (ICUResourceBundle) derivationsBundle.get("grammaticalData");
|
||||
derivationsBundle = (ICUResourceBundle) derivationsBundle.get("derivations");
|
||||
|
||||
ICUResourceBundle stackBundle;
|
||||
try {
|
||||
// TODO: use standard normal locale resolution algorithms rather than just grabbing language:
|
||||
stackBundle = (ICUResourceBundle) derivationsBundle.get(locale.getLanguage());
|
||||
} catch (MissingResourceException e) {
|
||||
stackBundle = (ICUResourceBundle) derivationsBundle.get("root");
|
||||
}
|
||||
|
||||
stackBundle = (ICUResourceBundle) stackBundle.get("compound");
|
||||
stackBundle = (ICUResourceBundle) stackBundle.get(feature);
|
||||
|
||||
return stackBundle.getString(structure);
|
||||
}
|
||||
|
||||
////////////////////////
|
||||
/// END DATA LOADING ///
|
||||
////////////////////////
|
||||
|
@ -168,6 +223,8 @@ public class LongNameHandler
|
|||
private final Map<StandardPlural, SimpleModifier> modifiers;
|
||||
private final PluralRules rules;
|
||||
private final MicroPropsGenerator parent;
|
||||
// Grammatical gender of the formatted result.
|
||||
private String gender = "";
|
||||
|
||||
private LongNameHandler(
|
||||
Map<StandardPlural, SimpleModifier> modifiers,
|
||||
|
@ -180,7 +237,7 @@ public class LongNameHandler
|
|||
|
||||
public static String getUnitDisplayName(ULocale locale, MeasureUnit unit, UnitWidth width) {
|
||||
String[] measureData = new String[ARRAY_LENGTH];
|
||||
getMeasureData(locale, unit, width, measureData);
|
||||
getMeasureData(locale, unit, width, "", measureData);
|
||||
return measureData[DNAM_INDEX];
|
||||
}
|
||||
|
||||
|
@ -207,6 +264,7 @@ public class LongNameHandler
|
|||
* @param locale The desired locale.
|
||||
* @param unit The measure unit to construct a LongNameHandler for.
|
||||
* @param width Specifies the desired unit rendering.
|
||||
* @param unitDisplayCase
|
||||
* @param rules Plural rules.
|
||||
* @param parent Plural rules.
|
||||
*/
|
||||
|
@ -214,6 +272,7 @@ public class LongNameHandler
|
|||
ULocale locale,
|
||||
MeasureUnit unit,
|
||||
UnitWidth width,
|
||||
String unitDisplayCase,
|
||||
PluralRules rules,
|
||||
MicroPropsGenerator parent) {
|
||||
if (unit.getType() == null) {
|
||||
|
@ -240,24 +299,91 @@ public class LongNameHandler
|
|||
}
|
||||
}
|
||||
}
|
||||
return forCompoundUnit(locale, unit, perUnit, width, rules, parent);
|
||||
return forCompoundUnit(locale, unit, perUnit, width, unitDisplayCase, rules, parent);
|
||||
}
|
||||
|
||||
String[] simpleFormats = new String[ARRAY_LENGTH];
|
||||
getMeasureData(locale, unit, width, simpleFormats);
|
||||
getMeasureData(locale, unit, width, unitDisplayCase, simpleFormats);
|
||||
// TODO(ICU4J): Reduce the number of object creations here?
|
||||
Map<StandardPlural, SimpleModifier> modifiers = new EnumMap<>(
|
||||
StandardPlural.class);
|
||||
LongNameHandler result = new LongNameHandler(modifiers, rules, parent);
|
||||
result.simpleFormatsToModifiers(simpleFormats, NumberFormat.Field.MEASURE_UNIT);
|
||||
if (simpleFormats[GENDER_INDEX] != null) {
|
||||
result.gender = simpleFormats[GENDER_INDEX];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and applies deriveComponent rules from CLDR's grammaticalFeatures.xml.
|
||||
* <pre>
|
||||
* Consider a deriveComponent rule that looks like this:
|
||||
* </pre>
|
||||
* <deriveComponent feature="case" structure="per" value0="compound" value1="nominative"/>
|
||||
* <p>
|
||||
* Instantiating an instance as follows:
|
||||
* <pre>
|
||||
* DerivedComponents d(loc, "case", "per", "foo");
|
||||
* </pre>
|
||||
* <p>
|
||||
* Applying the rule in the XML element above, <code>d.value0()</code> will be "foo", and
|
||||
* <code>d.value1()</code> will be "nominative".
|
||||
* <p>
|
||||
* <p>
|
||||
* In case of any kind of failure, value0() and value1() will simply return "".
|
||||
*/
|
||||
private static class DerivedComponents {
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public DerivedComponents(ULocale locale,
|
||||
String feature,
|
||||
String structure,
|
||||
String compoundValue) {
|
||||
ICUResourceBundle derivationsBundle =
|
||||
(ICUResourceBundle) UResourceBundle
|
||||
.getBundleInstance(ICUData.ICU_BASE_NAME, "grammaticalFeatures");
|
||||
derivationsBundle = (ICUResourceBundle) derivationsBundle.get("grammaticalData");
|
||||
derivationsBundle = (ICUResourceBundle) derivationsBundle.get("derivations");
|
||||
|
||||
ICUResourceBundle stackBundle;
|
||||
try {
|
||||
// TODO: use standard normal locale resolution algorithms rather than just grabbing language:
|
||||
stackBundle = (ICUResourceBundle) derivationsBundle.get(locale.getLanguage());
|
||||
} catch (MissingResourceException e) {
|
||||
stackBundle = (ICUResourceBundle) derivationsBundle.get("root");
|
||||
}
|
||||
|
||||
stackBundle = (ICUResourceBundle) stackBundle.get("component");
|
||||
stackBundle = (ICUResourceBundle) stackBundle.get(feature);
|
||||
stackBundle = (ICUResourceBundle) stackBundle.get(structure);
|
||||
|
||||
String value = stackBundle.getString(0);
|
||||
if (value.compareTo("compound") == 0) {
|
||||
this.value0 = compoundValue;
|
||||
} else {
|
||||
this.value0 = value;
|
||||
}
|
||||
|
||||
value = stackBundle.getString(1);
|
||||
if (value.compareTo("compound") == 0) {
|
||||
this.value1 = compoundValue;
|
||||
} else {
|
||||
this.value1 = value;
|
||||
}
|
||||
}
|
||||
|
||||
public final String value0, value1;
|
||||
}
|
||||
|
||||
private static LongNameHandler forCompoundUnit(
|
||||
ULocale locale,
|
||||
MeasureUnit unit,
|
||||
MeasureUnit perUnit,
|
||||
UnitWidth width,
|
||||
String unitDisplayCase,
|
||||
PluralRules rules,
|
||||
MicroPropsGenerator parent) {
|
||||
if (unit.getType() == null || perUnit.getType() == null) {
|
||||
|
@ -267,10 +393,20 @@ public class LongNameHandler
|
|||
"Unsanctioned units, not yet supported: " + unit.getIdentifier() + "/" +
|
||||
perUnit.getIdentifier());
|
||||
}
|
||||
|
||||
DerivedComponents derivedPerCases = new DerivedComponents(locale, "case", "per", unitDisplayCase);
|
||||
|
||||
|
||||
String[] primaryData = new String[ARRAY_LENGTH];
|
||||
getMeasureData(locale, unit, width, primaryData);
|
||||
getMeasureData(locale, unit, width, derivedPerCases.value0, primaryData);
|
||||
String[] secondaryData = new String[ARRAY_LENGTH];
|
||||
getMeasureData(locale, perUnit, width, secondaryData);
|
||||
getMeasureData(locale, perUnit, width, derivedPerCases.value1, secondaryData);
|
||||
|
||||
// TODO(icu-units#139): implement these rules:
|
||||
// - <deriveComponent feature="plural" structure="per" ...>
|
||||
// - This has impact on multiSimpleFormatsToModifiers(...) below too.
|
||||
//
|
||||
// These rules are currently (ICU 69) all the same and hard-coded below.
|
||||
String perUnitFormat;
|
||||
if (secondaryData[PER_INDEX] != null) {
|
||||
perUnitFormat = secondaryData[PER_INDEX];
|
||||
|
@ -286,14 +422,34 @@ public class LongNameHandler
|
|||
// Some "one" pattern may not contain "{0}". For example in "ar" or "ne" locale.
|
||||
String secondaryCompiled = SimpleFormatterImpl
|
||||
.compileToStringMinMaxArguments(secondaryFormat, sb, 0, 1);
|
||||
String secondaryString = SimpleFormatterImpl.getTextWithNoArguments(secondaryCompiled)
|
||||
.trim();
|
||||
String secondaryFormatString = SimpleFormatterImpl.getTextWithNoArguments(secondaryCompiled);
|
||||
|
||||
// TODO(icu-units#28): do not use regular expression
|
||||
String secondaryString = secondaryFormatString.replaceAll("(^\\h*)|(\\h*$)",""); // Trim all spaces.
|
||||
|
||||
perUnitFormat = SimpleFormatterImpl.formatCompiledPattern(compiled, "{0}", secondaryString);
|
||||
}
|
||||
Map<StandardPlural, SimpleModifier> modifiers = new EnumMap<>(
|
||||
StandardPlural.class);
|
||||
LongNameHandler result = new LongNameHandler(modifiers, rules, parent);
|
||||
result.multiSimpleFormatsToModifiers(primaryData, perUnitFormat, NumberFormat.Field.MEASURE_UNIT);
|
||||
|
||||
// Gender
|
||||
String val = getDeriveCompoundRule(locale, "gender", "per");
|
||||
|
||||
assert (val != null && val.length() == 1);
|
||||
switch (val.charAt(0)) {
|
||||
case '0':
|
||||
result.gender = primaryData[GENDER_INDEX];
|
||||
break;
|
||||
case '1':
|
||||
result.gender = secondaryData[GENDER_INDEX];
|
||||
break;
|
||||
default:
|
||||
// Data error. Assert-fail in debug mode, else return no gender.
|
||||
assert false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -336,6 +492,7 @@ public class LongNameHandler
|
|||
MicroProps micros = parent.processQuantity(quantity);
|
||||
StandardPlural pluralForm = RoundingUtils.getPluralSafe(micros.rounder, rules, quantity);
|
||||
micros.modOuter = modifiers.get(pluralForm);
|
||||
micros.gender = this.gender;
|
||||
return micros;
|
||||
}
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@ public class LongNameMultiplexer implements MicroPropsGenerator {
|
|||
public static LongNameMultiplexer forMeasureUnits(ULocale locale,
|
||||
List<MeasureUnit> units,
|
||||
NumberFormatter.UnitWidth width,
|
||||
String unitDisplayCase,
|
||||
PluralRules rules,
|
||||
MicroPropsGenerator parent) {
|
||||
LongNameMultiplexer result = new LongNameMultiplexer(parent);
|
||||
|
@ -60,10 +61,10 @@ public class LongNameMultiplexer implements MicroPropsGenerator {
|
|||
result.fMeasureUnits.add(unit);
|
||||
if (unit.getComplexity() == MeasureUnit.Complexity.MIXED) {
|
||||
MixedUnitLongNameHandler mlnh = MixedUnitLongNameHandler
|
||||
.forMeasureUnit(locale, unit, width, rules, null);
|
||||
.forMeasureUnit(locale, unit, width, unitDisplayCase, rules, null);
|
||||
result.fHandlers.add(mlnh);
|
||||
} else {
|
||||
LongNameHandler lnh = LongNameHandler.forMeasureUnit(locale, unit, width, rules, null);
|
||||
LongNameHandler lnh = LongNameHandler.forMeasureUnit(locale, unit, width, unitDisplayCase, rules, null);
|
||||
result.fHandlers.add(lnh);
|
||||
}
|
||||
}
|
||||
|
@ -75,7 +76,7 @@ public class LongNameMultiplexer implements MicroPropsGenerator {
|
|||
// one of the units provided to the factory function.
|
||||
@Override
|
||||
public MicroProps processQuantity(DecimalQuantity quantity) {
|
||||
// We call parent->processQuantity() from the Multiplexer, instead of
|
||||
// We call parent.processQuantity() from the Multiplexer, instead of
|
||||
// letting LongNameHandler handle it: we don't know which LongNameHandler to
|
||||
// call until we've called the parent!
|
||||
MicroProps micros = this.fParent.processQuantity(quantity);
|
||||
|
|
|
@ -27,6 +27,7 @@ public class MacroProps implements Cloneable {
|
|||
public IntegerWidth integerWidth;
|
||||
public Object symbols;
|
||||
public UnitWidth unitWidth;
|
||||
public String unitDisplayCase;
|
||||
public SignDisplay sign;
|
||||
public DecimalSeparatorDisplay decimal;
|
||||
public Scale scale;
|
||||
|
@ -63,6 +64,8 @@ public class MacroProps implements Cloneable {
|
|||
symbols = fallback.symbols;
|
||||
if (unitWidth == null)
|
||||
unitWidth = fallback.unitWidth;
|
||||
if (unitDisplayCase == null)
|
||||
unitDisplayCase = fallback.unitDisplayCase;
|
||||
if (sign == null)
|
||||
sign = fallback.sign;
|
||||
if (decimal == null)
|
||||
|
@ -91,6 +94,7 @@ public class MacroProps implements Cloneable {
|
|||
integerWidth,
|
||||
symbols,
|
||||
unitWidth,
|
||||
unitDisplayCase,
|
||||
sign,
|
||||
decimal,
|
||||
affixProvider,
|
||||
|
@ -119,6 +123,7 @@ public class MacroProps implements Cloneable {
|
|||
&& Objects.equals(integerWidth, other.integerWidth)
|
||||
&& Objects.equals(symbols, other.symbols)
|
||||
&& Objects.equals(unitWidth, other.unitWidth)
|
||||
&& Objects.equals(unitDisplayCase, other.unitDisplayCase)
|
||||
&& Objects.equals(sign, other.sign)
|
||||
&& Objects.equals(decimal, other.decimal)
|
||||
&& Objects.equals(affixProvider, other.affixProvider)
|
||||
|
|
|
@ -44,6 +44,7 @@ public class MicroProps implements Cloneable, MicroPropsGenerator {
|
|||
public Precision rounder;
|
||||
public Grouper grouping;
|
||||
public boolean useCurrency;
|
||||
public String gender;
|
||||
|
||||
// Internal fields:
|
||||
private final boolean immutable;
|
||||
|
|
|
@ -51,11 +51,15 @@ public class MixedUnitLongNameHandler
|
|||
* @param mixedUnit The mixed measure unit to construct a
|
||||
* MixedUnitLongNameHandler for.
|
||||
* @param width Specifies the desired unit rendering.
|
||||
* @param unitDisplayName
|
||||
* @param rules PluralRules instance.
|
||||
* @param parent MicroPropsGenerator instance.
|
||||
*/
|
||||
public static MixedUnitLongNameHandler forMeasureUnit(ULocale locale, MeasureUnit mixedUnit,
|
||||
NumberFormatter.UnitWidth width, PluralRules rules,
|
||||
public static MixedUnitLongNameHandler forMeasureUnit(ULocale locale,
|
||||
MeasureUnit mixedUnit,
|
||||
NumberFormatter.UnitWidth width,
|
||||
String unitDisplayName,
|
||||
PluralRules rules,
|
||||
MicroPropsGenerator parent) {
|
||||
assert (mixedUnit.getComplexity() == MeasureUnit.Complexity.MIXED);
|
||||
|
||||
|
@ -66,7 +70,7 @@ public class MixedUnitLongNameHandler
|
|||
for (int i = 0; i < individualUnits.size(); i++) {
|
||||
// Grab data for each of the components.
|
||||
String[] unitData = new String[LongNameHandler.ARRAY_LENGTH];
|
||||
LongNameHandler.getMeasureData(locale, individualUnits.get(i), width, unitData);
|
||||
LongNameHandler.getMeasureData(locale, individualUnits.get(i), width, unitDisplayName, unitData);
|
||||
result.fMixedUnitData.add(unitData);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,13 +3,11 @@
|
|||
package com.ibm.icu.impl.number;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.ibm.icu.impl.units.ComplexUnitsConverter;
|
||||
import com.ibm.icu.impl.units.MeasureUnitImpl;
|
||||
import com.ibm.icu.impl.units.UnitsRouter;
|
||||
import com.ibm.icu.util.Measure;
|
||||
import com.ibm.icu.util.MeasureUnit;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
|
||||
|
|
|
@ -28,10 +28,14 @@ public class FormattedNumber implements FormattedValue {
|
|||
final DecimalQuantity fq;
|
||||
final MeasureUnit outputUnit;
|
||||
|
||||
FormattedNumber(FormattedStringBuilder nsb, DecimalQuantity fq, MeasureUnit outputUnit) {
|
||||
// Grammatical gender of the formatted result.
|
||||
final String gender;
|
||||
|
||||
FormattedNumber(FormattedStringBuilder nsb, DecimalQuantity fq, MeasureUnit outputUnit, String gender) {
|
||||
this.string = nsb;
|
||||
this.fq = fq;
|
||||
this.outputUnit = outputUnit;
|
||||
this.gender = gender;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -133,6 +137,16 @@ public class FormattedNumber implements FormattedValue {
|
|||
return this.outputUnit;
|
||||
}
|
||||
|
||||
/**
|
||||
* The gender of the formatted output.
|
||||
*
|
||||
* @internal ICU 69 technology preview
|
||||
* @deprecated This API is for technology preview only.
|
||||
*/
|
||||
public String getGender() {
|
||||
return this.gender;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated This API is ICU internal only.
|
||||
|
|
|
@ -102,7 +102,7 @@ public class LocalizedNumberFormatter extends NumberFormatterSettings<LocalizedN
|
|||
MeasureUnit unit = input.getUnit();
|
||||
FormattedStringBuilder string = new FormattedStringBuilder();
|
||||
MicroProps micros = formatImpl(fq, unit, string);
|
||||
return new FormattedNumber(string, fq, micros.outputUnit);
|
||||
return new FormattedNumber(string, fq, micros.outputUnit, micros.gender);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -127,7 +127,7 @@ public class LocalizedNumberFormatter extends NumberFormatterSettings<LocalizedN
|
|||
private FormattedNumber format(DecimalQuantity fq) {
|
||||
FormattedStringBuilder string = new FormattedStringBuilder();
|
||||
MicroProps micros = formatImpl(fq, string);
|
||||
return new FormattedNumber(string, fq, micros.outputUnit);
|
||||
return new FormattedNumber(string, fq, micros.outputUnit, micros.gender);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -62,10 +62,10 @@ class NumberFormatterImpl {
|
|||
MacroProps macros,
|
||||
DecimalQuantity inValue,
|
||||
FormattedStringBuilder outString) {
|
||||
MicroProps micros = preProcessUnsafe(macros, inValue);
|
||||
int length = writeNumber(micros, inValue, outString, 0);
|
||||
writeAffixes(micros, outString, 0, length);
|
||||
return micros;
|
||||
MicroProps result = preProcessUnsafe(macros, inValue);
|
||||
int length = writeNumber(result, inValue, outString, 0);
|
||||
writeAffixes(result, outString, 0, length);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -93,10 +93,10 @@ class NumberFormatterImpl {
|
|||
* Evaluates the "safe" MicroPropsGenerator created by "fromMacros".
|
||||
*/
|
||||
public MicroProps format(DecimalQuantity inValue, FormattedStringBuilder outString) {
|
||||
MicroProps micros = preProcess(inValue);
|
||||
int length = writeNumber(micros, inValue, outString, 0);
|
||||
writeAffixes(micros, outString, 0, length);
|
||||
return micros;
|
||||
MicroProps result = preProcess(inValue);
|
||||
int length = writeNumber(result, inValue, outString, 0);
|
||||
writeAffixes(result, outString, 0, length);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -226,6 +226,9 @@ class NumberFormatterImpl {
|
|||
}
|
||||
micros.nsName = ns.getName();
|
||||
|
||||
// Default gender: none.
|
||||
micros.gender = "";
|
||||
|
||||
// Resolve the symbols. Do this here because currency may need to customize them.
|
||||
if (macros.symbols instanceof DecimalFormatSymbols) {
|
||||
micros.symbols = (DecimalFormatSymbols) macros.symbols;
|
||||
|
@ -375,6 +378,10 @@ class NumberFormatterImpl {
|
|||
|
||||
// Outer modifier (CLDR units and currency long names)
|
||||
if (isCldrUnit) {
|
||||
String unitDisplayCase = null;
|
||||
if (macros.unitDisplayCase != null) {
|
||||
unitDisplayCase = macros.unitDisplayCase;
|
||||
}
|
||||
if (rules == null) {
|
||||
// Lazily create PluralRules
|
||||
rules = PluralRules.forLocale(macros.loc);
|
||||
|
@ -389,6 +396,7 @@ class NumberFormatterImpl {
|
|||
macros.loc,
|
||||
usagePrefsHandler.getOutputUnits(),
|
||||
unitWidth,
|
||||
unitDisplayCase,
|
||||
pluralRules,
|
||||
chain);
|
||||
} else if (isMixedUnit) {
|
||||
|
@ -396,6 +404,7 @@ class NumberFormatterImpl {
|
|||
macros.loc,
|
||||
macros.unit,
|
||||
unitWidth,
|
||||
unitDisplayCase,
|
||||
pluralRules,
|
||||
chain);
|
||||
} else {
|
||||
|
@ -403,7 +412,13 @@ class NumberFormatterImpl {
|
|||
if (macros.perUnit != null) {
|
||||
unit = unit.product(macros.perUnit.reciprocal());
|
||||
}
|
||||
chain = LongNameHandler.forMeasureUnit(macros.loc, unit, unitWidth, pluralRules, chain);
|
||||
chain = LongNameHandler.forMeasureUnit(
|
||||
macros.loc,
|
||||
unit,
|
||||
unitWidth,
|
||||
unitDisplayCase,
|
||||
pluralRules,
|
||||
chain);
|
||||
}
|
||||
} else if (isCurrency && unitWidth == UnitWidth.FULL_NAME) {
|
||||
if (rules == null) {
|
||||
|
|
|
@ -45,7 +45,8 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
|
|||
static final int KEY_THRESHOLD = 14;
|
||||
static final int KEY_PER_UNIT = 15;
|
||||
static final int KEY_USAGE = 16;
|
||||
static final int KEY_MAX = 17;
|
||||
static final int KEY_UNIT_DISPLAY_CASE = 17;
|
||||
static final int KEY_MAX = 18;
|
||||
|
||||
private final NumberFormatterSettings<?> parent;
|
||||
private final int key;
|
||||
|
@ -548,6 +549,18 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
|
|||
return create(KEY_USAGE, usage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the desired case for a unit formatter's output (e.g.
|
||||
* accusative, dative, genitive).
|
||||
*
|
||||
* @return The fluent chain
|
||||
* @internal ICU 69 technology preview
|
||||
* @deprecated This API is for technology preview only.
|
||||
*/
|
||||
public T unitDisplayCase(String unitDisplayCase) {
|
||||
return create(KEY_UNIT_DISPLAY_CASE, unitDisplayCase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to set a starting macros.
|
||||
*
|
||||
|
@ -675,6 +688,9 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
|
|||
case KEY_USAGE:
|
||||
macros.usage = (String) current.value;
|
||||
break;
|
||||
case KEY_UNIT_DISPLAY_CASE:
|
||||
macros.unitDisplayCase = (String) current.value;
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unknown key: " + current.key);
|
||||
}
|
||||
|
|
|
@ -947,6 +947,10 @@ class NumberSkeletonImpl {
|
|||
throw new UnsupportedOperationException(
|
||||
"Cannot generate number skeleton with custom padder");
|
||||
}
|
||||
if (macros.unitDisplayCase != null && !macros.unitDisplayCase.isEmpty()) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Cannot generate number skeleton with custom unit display case");
|
||||
}
|
||||
if (macros.affixProvider != null) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Cannot generate number skeleton with custom affix provider");
|
||||
|
|
|
@ -1922,6 +1922,220 @@ public class NumberFormatterApiTest extends TestFmwk {
|
|||
"123,12 CN¥");
|
||||
}
|
||||
|
||||
public static class UnitInflectionTestCase {
|
||||
public final String locale;
|
||||
public final String unitDisplayCase;
|
||||
public final double value;
|
||||
public final String expected;
|
||||
|
||||
UnitInflectionTestCase(String locale, String unitDisplayCase, double value, String expected) {
|
||||
this.locale = locale;
|
||||
this.unitDisplayCase = unitDisplayCase;
|
||||
this.value = value;
|
||||
this.expected = expected;
|
||||
}
|
||||
|
||||
public static void runUnitInflectionsTestCases(UnlocalizedNumberFormatter unf,
|
||||
String skeleton,
|
||||
String conciseSkeleton,
|
||||
UnitInflectionTestCase cases[]) {
|
||||
for (UnitInflectionTestCase t : cases) {
|
||||
String skel;
|
||||
String cSkel;
|
||||
if (t.unitDisplayCase == null || t.unitDisplayCase.isEmpty()) {
|
||||
unf = unf.unitDisplayCase("");
|
||||
skel = skeleton;
|
||||
cSkel = conciseSkeleton;
|
||||
} else {
|
||||
unf = unf.unitDisplayCase(t.unitDisplayCase);
|
||||
skel = null;
|
||||
cSkel = null;
|
||||
}
|
||||
|
||||
assertFormatSingle(
|
||||
"\"" + skeleton + "\", locale=\"" + t.locale + "\", case=\"" +
|
||||
(t.unitDisplayCase != null ? t.unitDisplayCase : "")
|
||||
+ "\", value=" + t.value,
|
||||
skel,
|
||||
cSkel,
|
||||
unf, new ULocale(t.locale),
|
||||
t.value,
|
||||
t.expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unitInflections() {
|
||||
UnlocalizedNumberFormatter unf;
|
||||
String skeleton;
|
||||
String conciseSkeleton;
|
||||
|
||||
{
|
||||
// Simple inflected form test - test case based on the example in CLDR's
|
||||
// grammaticalFeatures.xml
|
||||
unf = NumberFormatter.with().unit(NoUnit.PERCENT).unitWidth(UnitWidth.FULL_NAME);
|
||||
skeleton = "percent unit-width-full-name";
|
||||
conciseSkeleton = "% unit-width-full-name";
|
||||
final UnitInflectionTestCase percentCases[] = {
|
||||
new UnitInflectionTestCase("ru", null, 10, "10 процентов"), // many
|
||||
new UnitInflectionTestCase("ru", "genitive", 10, "10 процентов"), // many
|
||||
new UnitInflectionTestCase("ru", null, 33, "33 процента"), // few
|
||||
new UnitInflectionTestCase("ru", "genitive", 33, "33 процентов"), // few
|
||||
new UnitInflectionTestCase("ru", null, 1, "1 процент"), // one
|
||||
new UnitInflectionTestCase("ru", "genitive", 1, "1 процента"), // one
|
||||
};
|
||||
|
||||
for (UnitInflectionTestCase testCase :
|
||||
percentCases) {
|
||||
UnitInflectionTestCase.runUnitInflectionsTestCases(unf, skeleton, conciseSkeleton, percentCases);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Testing "de" rules:
|
||||
// <deriveComponent feature="case" structure="per" value0="compound" value1="accusative"/>
|
||||
// <deriveComponent feature="plural" structure="per" value0="compound" value1="one"/>
|
||||
//
|
||||
// per-patterns use accusative, but happen to match nominative, so we're
|
||||
// not testing value1 in the first rule above.
|
||||
|
||||
unf = NumberFormatter.with().unit(MeasureUnit.METER).unitWidth(UnitWidth.FULL_NAME);
|
||||
skeleton = "unit/meter unit-width-full-name";
|
||||
conciseSkeleton = "unit/meter unit-width-full-name";
|
||||
final UnitInflectionTestCase meterCases[] = {
|
||||
new UnitInflectionTestCase("de", null, 1, "1 Meter"),
|
||||
new UnitInflectionTestCase("de", "genitive", 1, "1 Meters"),
|
||||
new UnitInflectionTestCase("de", null, 2, "2 Meter"),
|
||||
new UnitInflectionTestCase("de", "dative", 2, "2 Metern"),
|
||||
};
|
||||
UnitInflectionTestCase.runUnitInflectionsTestCases(unf, skeleton, conciseSkeleton, meterCases);
|
||||
|
||||
unf = NumberFormatter.with().unit(MeasureUnit.DAY).unitWidth(UnitWidth.FULL_NAME);
|
||||
skeleton = "unit/day unit-width-full-name";
|
||||
conciseSkeleton = "unit/day unit-width-full-name";
|
||||
final UnitInflectionTestCase dayCases[] = {
|
||||
new UnitInflectionTestCase("de", null, 1, "1 Tag"),
|
||||
new UnitInflectionTestCase("de", "genitive", 1, "1 Tages"),
|
||||
new UnitInflectionTestCase("de", null, 2, "2 Tage"),
|
||||
new UnitInflectionTestCase("de", "dative", 2, "2 Tagen"),
|
||||
};
|
||||
UnitInflectionTestCase.runUnitInflectionsTestCases(unf, skeleton, conciseSkeleton, dayCases);
|
||||
|
||||
// Day has a perUnitPattern
|
||||
unf = NumberFormatter.with()
|
||||
.unit(MeasureUnit.forIdentifier("meter-per-day"))
|
||||
.unitWidth(UnitWidth.FULL_NAME);
|
||||
skeleton = "unit/meter-per-day unit-width-full-name";
|
||||
conciseSkeleton = "unit/meter-per-day unit-width-full-name";
|
||||
final UnitInflectionTestCase meterPerDayCases[] = {
|
||||
new UnitInflectionTestCase("de", null, 1, "1 Meter pro Tag"),
|
||||
new UnitInflectionTestCase("de", "genitive", 1, "1 Meters pro Tag"),
|
||||
new UnitInflectionTestCase("de", null, 2, "2 Meter pro Tag"),
|
||||
new UnitInflectionTestCase("de", "dative", 2, "2 Metern pro Tag"),
|
||||
// testing code path that falls back to "root" but does not inflect:
|
||||
new UnitInflectionTestCase("af", null, 1, "1 meter per dag"),
|
||||
new UnitInflectionTestCase("af", "dative", 1, "1 meter per dag"),
|
||||
};
|
||||
UnitInflectionTestCase.runUnitInflectionsTestCases(unf, skeleton, conciseSkeleton, meterPerDayCases);
|
||||
|
||||
// Decade does not have a perUnitPattern at this time (CLDR 39 / ICU
|
||||
// 69), so we can test for the correct form of the per part:
|
||||
unf = NumberFormatter.with()
|
||||
.unit(MeasureUnit.forIdentifier("parsec-per-decade"))
|
||||
.unitWidth(UnitWidth.FULL_NAME);
|
||||
skeleton = "unit/parsec-per-decade unit-width-full-name";
|
||||
conciseSkeleton = "unit/parsec-per-decade unit-width-full-name";
|
||||
// Fragile test cases: these cases will break when whitespace is more
|
||||
// consistently applied.
|
||||
final UnitInflectionTestCase parsecPerDecadeCases[] = {
|
||||
new UnitInflectionTestCase("de", null, 1, "1\u00A0Parsec pro Jahrzehnt"),
|
||||
new UnitInflectionTestCase("de", "genitive", 1, "1 Parsec pro Jahrzehnt"),
|
||||
new UnitInflectionTestCase("de", null, 2, "2\u00A0Parsec pro Jahrzehnt"),
|
||||
new UnitInflectionTestCase("de", "dative", 2, "2 Parsec pro Jahrzehnt"),
|
||||
};
|
||||
UnitInflectionTestCase.runUnitInflectionsTestCases(unf, skeleton, conciseSkeleton, parsecPerDecadeCases);
|
||||
}
|
||||
{
|
||||
// Testing inflection of mixed units:
|
||||
unf = NumberFormatter.with()
|
||||
.unit(MeasureUnit.forIdentifier("meter-and-centimeter"))
|
||||
.unitWidth(UnitWidth.FULL_NAME);
|
||||
skeleton = "unit/meter-and-centimeter unit-width-full-name";
|
||||
conciseSkeleton = "unit/meter-and-centimeter unit-width-full-name";
|
||||
final UnitInflectionTestCase meterPerDayCases[] = {
|
||||
// TODO(CLDR-14502): check that these inflections are correct, and
|
||||
// whether CLDR needs any rules for them (presumably CLDR spec
|
||||
// should mention it, if it's a consistent rule):
|
||||
new UnitInflectionTestCase("de", null, 1.01, "1 Meter, 1 Zentimeter"),
|
||||
new UnitInflectionTestCase("de", "genitive", 1.01, "1 Meters, 1 Zentimeters"),
|
||||
new UnitInflectionTestCase("de", "genitive", 1.1, "1 Meters, 10 Zentimeter"),
|
||||
new UnitInflectionTestCase("de", "dative", 1.1, "1 Meter, 10 Zentimetern"),
|
||||
new UnitInflectionTestCase("de", "dative", 2.1, "2 Metern, 10 Zentimetern"),
|
||||
};
|
||||
UnitInflectionTestCase.runUnitInflectionsTestCases(unf, skeleton, conciseSkeleton, meterPerDayCases);
|
||||
}
|
||||
// TODO: add a usage case that selects between preferences with different
|
||||
// genders (e.g. year, month, day, hour).
|
||||
// TODO: look at "↑↑↑" cases: check that inheritance is done right.
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unitGender() {
|
||||
class TestCase {
|
||||
public String locale;
|
||||
public String unitIdentifier;
|
||||
public String expectedGender;
|
||||
|
||||
public TestCase(String locale, String unitIdentifier, String expectedGender) {
|
||||
this.locale = locale;
|
||||
this.unitIdentifier = unitIdentifier;
|
||||
this.expectedGender = expectedGender;
|
||||
}
|
||||
}
|
||||
|
||||
TestCase cases[] = {
|
||||
new TestCase("de", "meter", "masculine"),
|
||||
new TestCase("de", "minute", "feminine"),
|
||||
new TestCase("de", "hour", "feminine"),
|
||||
new TestCase("de", "day", "masculine"),
|
||||
new TestCase("de", "year", "neuter"),
|
||||
new TestCase("fr", "minute", "feminine"),
|
||||
new TestCase("fr", "hour", "feminine"),
|
||||
new TestCase("fr", "day", "masculine"),
|
||||
// grammaticalFeatures deriveCompound "per" rule:
|
||||
new TestCase("de", "meter-per-hour", "masculine"),
|
||||
new TestCase("af", "meter-per-hour", null),
|
||||
// TODO(ICU-21494): determine whether list genders behave as follows,
|
||||
// and implement proper getListGender support (covering more than just
|
||||
// two genders):
|
||||
// // gender rule for lists of people: de "neutral", fr "maleTaints"
|
||||
// new TestCase("de", "day-and-hour-and-minute", "neuter"),
|
||||
// new TestCase("de", "hour-and-minute", "feminine"),
|
||||
// new TestCase("fr", "day-and-hour-and-minute", "masculine"),
|
||||
// new TestCase("fr", "hour-and-minute", "feminine"),
|
||||
};
|
||||
|
||||
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))
|
||||
.unitWidth(UnitWidth.FULL_NAME)
|
||||
.locale(new ULocale(t.locale));
|
||||
fn = formatter.format(1.1);
|
||||
assertEquals("Testing gender, unit: " + t.unitIdentifier +
|
||||
", locale: " + t.locale,
|
||||
t.expectedGender, fn.getGender());
|
||||
}
|
||||
|
||||
// Make sure getGender does not return garbage for genderless languages
|
||||
formatter = NumberFormatter.with().locale(ULocale.ENGLISH);
|
||||
fn = formatter.format(1.1);
|
||||
assertEquals("getGender for a genderless language", "", fn.getGender());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unitPercent() {
|
||||
assertFormatDescending(
|
||||
|
|
Loading…
Add table
Reference in a new issue