import com.ibm.icu.impl.StandardPlural;
/**
- * A ParameterizedModifier by itself is NOT a Modifier. Rather, it wraps a data structure containing two
- * or more Modifiers and returns the modifier appropriate for the current situation.
+ * This implementation of ModifierStore adopts references to Modifiers.
+ *
+ * (This is named "adopting" because in C++, this class takes ownership of the Modifiers.)
*/
-public class ParameterizedModifier {
+public class AdoptingModifierStore implements ModifierStore {
private final Modifier positive;
private final Modifier zero;
private final Modifier negative;
* <p>
* If this constructor is used, a plural form CANNOT be passed to {@link #getModifier}.
*/
- public ParameterizedModifier(Modifier positive, Modifier zero, Modifier negative) {
+ public AdoptingModifierStore(Modifier positive, Modifier zero, Modifier negative) {
this.positive = positive;
this.zero = zero;
this.negative = negative;
* <p>
* If this constructor is used, a plural form MUST be passed to {@link #getModifier}.
*/
- public ParameterizedModifier() {
+ public AdoptingModifierStore() {
this.positive = null;
this.zero = null;
this.negative = null;
frozen = true;
}
- public Modifier getModifier(int signum) {
+ public Modifier getModifierWithoutPlural(int signum) {
assert frozen;
assert mods == null;
return signum == 0 ? zero : signum < 0 ? negative : positive;
}
private static int getModIndex(int signum, StandardPlural plural) {
+ assert signum >= -1 && signum <= 1;
+ assert plural != null;
return plural.ordinal() * 3 + (signum + 1);
}
}
}
@Override
- public boolean equalsModifier(Modifier other) {
+ public Parameters getParameters() {
+ return null;
+ }
+
+ @Override
+ public boolean semanticallyEquivalent(Modifier other) {
if (!(other instanceof ConstantAffixModifier)) {
return false;
}
private final boolean overwrite;
private final boolean strong;
+ // Parameters: used for number range formatting
+ private final Parameters parameters;
+
public ConstantMultiFieldModifier(
NumberStringBuilder prefix,
NumberStringBuilder suffix,
boolean overwrite,
boolean strong) {
+ this(prefix, suffix, overwrite, strong, null);
+ }
+
+ public ConstantMultiFieldModifier(
+ NumberStringBuilder prefix,
+ NumberStringBuilder suffix,
+ boolean overwrite,
+ boolean strong,
+ Parameters parameters) {
prefixChars = prefix.toCharArray();
suffixChars = suffix.toCharArray();
prefixFields = prefix.toFieldArray();
suffixFields = suffix.toFieldArray();
this.overwrite = overwrite;
this.strong = strong;
+ this.parameters = parameters;
}
@Override
}
@Override
- public boolean equalsModifier(Modifier other) {
+ public Parameters getParameters() {
+ return parameters;
+ }
+
+ @Override
+ public boolean semanticallyEquivalent(Modifier other) {
if (!(other instanceof ConstantMultiFieldModifier)) {
return false;
}
ConstantMultiFieldModifier _other = (ConstantMultiFieldModifier) other;
+ if (parameters != null && _other.parameters != null && parameters.obj == _other.parameters.obj) {
+ return true;
+ }
return Arrays.equals(prefixChars, _other.prefixChars) && Arrays.equals(prefixFields, _other.prefixFields)
&& Arrays.equals(suffixChars, _other.suffixChars) && Arrays.equals(suffixFields, _other.suffixFields)
&& overwrite == _other.overwrite && strong == _other.strong;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundle;
-public class LongNameHandler implements MicroPropsGenerator {
+public class LongNameHandler implements MicroPropsGenerator, ModifierStore {
private static final int DNAM_INDEX = StandardPlural.COUNT;
private static final int PER_INDEX = StandardPlural.COUNT + 1;
String[] simpleFormats = new String[ARRAY_LENGTH];
getCurrencyLongNameData(locale, currency, simpleFormats);
// TODO(ICU4J): Reduce the number of object creations here?
- Map<StandardPlural, SimpleModifier> modifiers = new EnumMap<StandardPlural, SimpleModifier>(
+ Map<StandardPlural, SimpleModifier> modifiers = new EnumMap<>(
StandardPlural.class);
- simpleFormatsToModifiers(simpleFormats, null, modifiers);
- return new LongNameHandler(modifiers, rules, parent);
+ LongNameHandler result = new LongNameHandler(modifiers, rules, parent);
+ result.simpleFormatsToModifiers(simpleFormats, null);
+ return result;
}
public static LongNameHandler forMeasureUnit(
getMeasureData(locale, unit, width, simpleFormats);
// TODO: What field to use for units?
// TODO(ICU4J): Reduce the number of object creations here?
- Map<StandardPlural, SimpleModifier> modifiers = new EnumMap<StandardPlural, SimpleModifier>(
+ Map<StandardPlural, SimpleModifier> modifiers = new EnumMap<>(
StandardPlural.class);
- simpleFormatsToModifiers(simpleFormats, null, modifiers);
- return new LongNameHandler(modifiers, rules, parent);
+ LongNameHandler result = new LongNameHandler(modifiers, rules, parent);
+ result.simpleFormatsToModifiers(simpleFormats, null);
+ return result;
}
private static LongNameHandler forCompoundUnit(
perUnitFormat = SimpleFormatterImpl.formatCompiledPattern(compiled, "{0}", secondaryString);
}
// TODO: What field to use for units?
- Map<StandardPlural, SimpleModifier> modifiers = new EnumMap<StandardPlural, SimpleModifier>(
+ Map<StandardPlural, SimpleModifier> modifiers = new EnumMap<>(
StandardPlural.class);
- multiSimpleFormatsToModifiers(primaryData, perUnitFormat, null, modifiers);
- return new LongNameHandler(modifiers, rules, parent);
+ LongNameHandler result = new LongNameHandler(modifiers, rules, parent);
+ result.multiSimpleFormatsToModifiers(primaryData, perUnitFormat, null);
+ return result;
}
- private static void simpleFormatsToModifiers(
+ private void simpleFormatsToModifiers(
String[] simpleFormats,
- NumberFormat.Field field,
- Map<StandardPlural, SimpleModifier> output) {
+ NumberFormat.Field field) {
StringBuilder sb = new StringBuilder();
for (StandardPlural plural : StandardPlural.VALUES) {
String simpleFormat = getWithPlural(simpleFormats, plural);
String compiled = SimpleFormatterImpl.compileToStringMinMaxArguments(simpleFormat, sb, 0, 1);
- output.put(plural, new SimpleModifier(compiled, field, false));
+ Modifier.Parameters parameters = new Modifier.Parameters();
+ parameters.obj = this;
+ parameters.signum = 0;
+ parameters.plural = plural;
+ modifiers.put(plural, new SimpleModifier(compiled, field, false, parameters));
}
}
- private static void multiSimpleFormatsToModifiers(
+ private void multiSimpleFormatsToModifiers(
String[] leadFormats,
String trailFormat,
- NumberFormat.Field field,
- Map<StandardPlural, SimpleModifier> output) {
+ NumberFormat.Field field) {
StringBuilder sb = new StringBuilder();
String trailCompiled = SimpleFormatterImpl.compileToStringMinMaxArguments(trailFormat, sb, 1, 1);
for (StandardPlural plural : StandardPlural.VALUES) {
String compoundFormat = SimpleFormatterImpl.formatCompiledPattern(trailCompiled, leadFormat);
String compoundCompiled = SimpleFormatterImpl
.compileToStringMinMaxArguments(compoundFormat, sb, 0, 1);
- output.put(plural, new SimpleModifier(compoundCompiled, field, false));
+ Modifier.Parameters parameters = new Modifier.Parameters();
+ parameters.obj = this;
+ parameters.signum = 0;
+ parameters.plural = plural;
+ modifiers.put(plural, new SimpleModifier(compoundCompiled, field, false, parameters));
}
}
micros.modOuter = modifiers.get(copy.getStandardPlural(rules));
return micros;
}
+
+ @Override
+ public Modifier getModifier(int signum, StandardPlural plural) {
+ return modifiers.get(plural);
+ }
}
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;
+import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.text.NumberFormat.Field;
/**
public boolean containsField(Field currency);
/**
- * Returns whether the affixes owned by this modifier are equal to the ones owned by the given modifier.
+ * A fill-in for getParameters(). obj will always be set; if non-null, the other
+ * two fields are also safe to read.
*/
- public boolean equalsModifier(Modifier other);
+ public static class Parameters {
+ public ModifierStore obj;
+ public int signum;
+ public StandardPlural plural;
+ }
+
+ /**
+ * Gets a set of "parameters" for this Modifier.
+ */
+ public Parameters getParameters();
+
+ /**
+ * Returns whether this Modifier is *semantically equivalent* to the other Modifier;
+ * in many cases, this is the same as equal, but parameters should be ignored.
+ */
+ public boolean semanticallyEquivalent(Modifier other);
}
--- /dev/null
+// © 2018 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package com.ibm.icu.impl.number;
+
+import com.ibm.icu.impl.StandardPlural;
+
+/**
+ * This is *not* a modifier; rather, it is an object that can return modifiers
+ * based on given parameters.
+ *
+ * @author sffc
+ */
+public interface ModifierStore {
+ /**
+ * Returns a Modifier with the given parameters (best-effort).
+ */
+ Modifier getModifier(int signum, StandardPlural plural);
+}
NumberStringBuilder b = new NumberStringBuilder();
if (needsPlurals()) {
// Slower path when we require the plural keyword.
- ParameterizedModifier pm = new ParameterizedModifier();
+ AdoptingModifierStore pm = new AdoptingModifierStore();
for (StandardPlural plural : StandardPlural.VALUES) {
setNumberProperties(1, plural);
pm.setModifier(1, plural, createConstantModifier(a, b));
Modifier zero = createConstantModifier(a, b);
setNumberProperties(-1, null);
Modifier negative = createConstantModifier(a, b);
- ParameterizedModifier pm = new ParameterizedModifier(positive, zero, negative);
+ AdoptingModifierStore pm = new AdoptingModifierStore(positive, zero, negative);
return new ImmutablePatternModifier(pm, null, parent);
}
}
}
public static class ImmutablePatternModifier implements MicroPropsGenerator {
- final ParameterizedModifier pm;
+ final AdoptingModifierStore pm;
final PluralRules rules;
final MicroPropsGenerator parent;
ImmutablePatternModifier(
- ParameterizedModifier pm,
+ AdoptingModifierStore pm,
PluralRules rules,
MicroPropsGenerator parent) {
this.pm = pm;
public void applyToMicros(MicroProps micros, DecimalQuantity quantity) {
if (rules == null) {
- micros.modMiddle = pm.getModifier(quantity.signum());
+ micros.modMiddle = pm.getModifierWithoutPlural(quantity.signum());
} else {
// TODO: Fix this. Avoid the copy.
DecimalQuantity copy = quantity.createCopy();
}
@Override
- public boolean equalsModifier(Modifier other) {
+ public Parameters getParameters() {
+ // This method is not currently used.
+ assert false;
+ return null;
+ }
+
+ @Override
+ public boolean semanticallyEquivalent(Modifier other) {
// This method is not currently used. (unsafe path not used in range formatting)
assert false;
return false;
private final int suffixOffset;
private final int suffixLength;
+ // Parameters: used for number range formatting
+ private final Parameters parameters;
+
/** TODO: This is copied from SimpleFormatterImpl. */
private static final int ARG_NUM_LIMIT = 0x100;
/** Creates a modifier that uses the SimpleFormatter string formats. */
public SimpleModifier(String compiledPattern, Field field, boolean strong) {
+ this(compiledPattern, field, strong, null);
+ }
+
+ /** Creates a modifier that uses the SimpleFormatter string formats. */
+ public SimpleModifier(String compiledPattern, Field field, boolean strong, Parameters parameters) {
assert compiledPattern != null;
this.compiledPattern = compiledPattern;
this.field = field;
this.strong = strong;
+ this.parameters = parameters;
int argLimit = SimpleFormatterImpl.getArgumentLimit(compiledPattern);
if (argLimit == 0) {
}
@Override
- public boolean equalsModifier(Modifier other) {
+ public Parameters getParameters() {
+ return parameters;
+ }
+
+ @Override
+ public boolean semanticallyEquivalent(Modifier other) {
if (!(other instanceof SimpleModifier)) {
return false;
}
SimpleModifier _other = (SimpleModifier) other;
+ if (parameters != null && _other.parameters != null && parameters.obj == _other.parameters.obj) {
+ return true;
+ }
return compiledPattern.equals(_other.compiledPattern) && field == _other.field && strong == _other.strong;
}
--- /dev/null
+// © 2018 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package com.ibm.icu.impl.number.range;
+
+import java.util.MissingResourceException;
+
+import com.ibm.icu.impl.ICUData;
+import com.ibm.icu.impl.ICUResourceBundle;
+import com.ibm.icu.impl.StandardPlural;
+import com.ibm.icu.impl.UResource;
+import com.ibm.icu.util.ULocale;
+import com.ibm.icu.util.UResourceBundle;
+
+/**
+ * @author sffc
+ *
+ */
+public class StandardPluralRanges {
+
+ StandardPlural[] flatTriples;
+ int numTriples = 0;
+
+ ////////////////////
+
+ private static final class PluralRangesDataSink extends UResource.Sink {
+
+ StandardPluralRanges output;
+
+ PluralRangesDataSink(StandardPluralRanges output) {
+ this.output = output;
+ }
+
+ @Override
+ public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
+ UResource.Array entriesArray = value.getArray();
+ output.setCapacity(entriesArray.getSize());
+ for (int i = 0; entriesArray.getValue(i, value); ++i) {
+ UResource.Array pluralFormsArray = value.getArray();
+ pluralFormsArray.getValue(0, value);
+ StandardPlural first = StandardPlural.fromString(value.getString());
+ pluralFormsArray.getValue(1, value);
+ StandardPlural second = StandardPlural.fromString(value.getString());
+ pluralFormsArray.getValue(2, value);
+ StandardPlural result = StandardPlural.fromString(value.getString());
+ output.addPluralRange(first, second, result);
+ }
+ }
+ }
+
+ private static void getPluralRangesData(
+ ULocale locale,
+ StandardPluralRanges out) {
+ StringBuilder sb = new StringBuilder();
+ ICUResourceBundle resource;
+ resource = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, "pluralRanges");
+ sb.append("locales/");
+ sb.append(locale.getLanguage());
+ String key = sb.toString();
+ String set;
+ try {
+ set = resource.getStringWithFallback(key);
+ } catch (MissingResourceException e) {
+ // Not all languages are covered: fail gracefully
+ return;
+ }
+
+ sb.setLength(0);
+ sb.append("rules/");
+ sb.append(set);
+ key = sb.toString();
+ PluralRangesDataSink sink = new PluralRangesDataSink(out);
+ resource.getAllItemsWithFallback(key, sink);
+ }
+
+ ////////////////////
+
+ public StandardPluralRanges(ULocale locale) {
+ getPluralRangesData(locale, this);
+ }
+
+ /** Used for data loading. */
+ private void addPluralRange(StandardPlural first, StandardPlural second, StandardPlural result) {
+ flatTriples[3 * numTriples] = first;
+ flatTriples[3 * numTriples + 1] = second;
+ flatTriples[3 * numTriples + 2] = result;
+ numTriples++;
+ }
+
+ /** Used for data loading. */
+ private void setCapacity(int length) {
+ flatTriples = new StandardPlural[length*3];
+ }
+
+ public StandardPlural resolve(StandardPlural first, StandardPlural second) {
+ for (int i = 0; i < numTriples; i++) {
+ if (first == flatTriples[3 * i] && second == flatTriples[3 * i + 1]) {
+ return flatTriples[3 * i + 2];
+ }
+ }
+ // Default fallback
+ return StandardPlural.OTHER;
+ }
+
+}
import com.ibm.icu.impl.ICUData;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.SimpleFormatterImpl;
+import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.impl.UResource;
import com.ibm.icu.impl.number.DecimalQuantity;
import com.ibm.icu.impl.number.MicroProps;
import com.ibm.icu.impl.number.SimpleModifier;
import com.ibm.icu.impl.number.range.PrefixInfixSuffixLengthHelper;
import com.ibm.icu.impl.number.range.RangeMacroProps;
+import com.ibm.icu.impl.number.range.StandardPluralRanges;
import com.ibm.icu.number.NumberRangeFormatter.RangeCollapse;
import com.ibm.icu.number.NumberRangeFormatter.RangeIdentityFallback;
import com.ibm.icu.number.NumberRangeFormatter.RangeIdentityResult;
*/
class NumberRangeFormatterImpl {
- NumberFormatterImpl formatterImpl1;
- NumberFormatterImpl formatterImpl2;
- boolean fSameFormatters;
+ final NumberFormatterImpl formatterImpl1;
+ final NumberFormatterImpl formatterImpl2;
+ final boolean fSameFormatters;
- NumberRangeFormatter.RangeCollapse fCollapse;
- NumberRangeFormatter.RangeIdentityFallback fIdentityFallback;
+ final NumberRangeFormatter.RangeCollapse fCollapse;
+ final NumberRangeFormatter.RangeIdentityFallback fIdentityFallback;
- String fRangePattern;
- SimpleModifier fApproximatelyModifier;
+ // Should be final, but they are set in a helper function, not the constructor proper.
+ // TODO: Clean up to make these fields actually final.
+ /* final */ String fRangePattern;
+ /* final */ SimpleModifier fApproximatelyModifier;
+
+ final StandardPluralRanges fPluralRanges;
+
+ ////////////////////
// Helper function for 2-dimensional switch statement
int identity2d(RangeIdentityFallback a, RangeIdentityResult b) {
out.fApproximatelyModifier = new SimpleModifier(sink.approximatelyPattern, null, false);
}
+ ////////////////////
+
public NumberRangeFormatterImpl(RangeMacroProps macros) {
formatterImpl1 = new NumberFormatterImpl(macros.formatter1 != null ? macros.formatter1.resolve()
: NumberFormatter.withLocale(macros.loc).resolve());
// numberFormatterBoth() or similar.
getNumberRangeData(macros.loc, "latn", this);
+
+ // TODO: Get locale from PluralRules instead?
+ fPluralRanges = new StandardPluralRanges(macros.loc);
}
public FormattedNumberRange format(DecimalQuantity quantity1, DecimalQuantity quantity2, boolean equalBeforeRounding) {
// TODO: Write this as MicroProps operator==() ?
// TODO: Avoid the redundancy of these equality operations with the
// ones in formatRange?
- if (!micros1.modInner.equalsModifier(micros2.modInner)
- || !micros1.modMiddle.equalsModifier(micros2.modMiddle)
- || !micros1.modOuter.equalsModifier(micros2.modOuter)) {
+ if (!micros1.modInner.semanticallyEquivalent(micros2.modInner)
+ || !micros1.modMiddle.semanticallyEquivalent(micros2.modMiddle)
+ || !micros1.modOuter.semanticallyEquivalent(micros2.modOuter)) {
formatRange(quantity1, quantity2, string, micros1, micros2);
return new FormattedNumberRange(string, quantity1, quantity2, RangeIdentityResult.NOT_EQUAL);
}
case UNIT:
{
// OUTER MODIFIER
- collapseOuter = micros1.modOuter.equalsModifier(micros2.modOuter);
+ collapseOuter = micros1.modOuter.semanticallyEquivalent(micros2.modOuter);
if (!collapseOuter) {
// Never collapse inner mods if outer mods are not collapsable
}
// MIDDLE MODIFIER
- collapseMiddle = micros1.modMiddle.equalsModifier(micros2.modMiddle);
+ collapseMiddle = micros1.modMiddle.semanticallyEquivalent(micros2.modMiddle);
if (!collapseMiddle) {
// Never collapse inner mods if outer mods are not collapsable
}
// INNER MODIFIER
- collapseInner = micros1.modInner.equalsModifier(micros2.modInner);
+ collapseInner = micros1.modInner.semanticallyEquivalent(micros2.modInner);
// All done checking for collapsability.
break;
if (collapseInner) {
// Note: this is actually a mix of prefix and suffix, but adding to infix length works
- h.lengthInfix += micros1.modInner.apply(string, h.index0(), h.index3());
+ Modifier mod = resolveModifierPlurals(micros1.modInner, micros2.modInner);
+ h.lengthInfix += mod.apply(string, h.index0(), h.index3());
} else {
h.length1 += micros1.modInner.apply(string, h.index0(), h.index1());
h.length2 += micros2.modInner.apply(string, h.index2(), h.index3());
if (collapseMiddle) {
// Note: this is actually a mix of prefix and suffix, but adding to infix length works
- h.lengthInfix += micros1.modMiddle.apply(string, h.index0(), h.index3());
+ Modifier mod = resolveModifierPlurals(micros1.modMiddle, micros2.modMiddle);
+ h.lengthInfix += mod.apply(string, h.index0(), h.index3());
} else {
h.length1 += micros1.modMiddle.apply(string, h.index0(), h.index1());
h.length2 += micros2.modMiddle.apply(string, h.index2(), h.index3());
if (collapseOuter) {
// Note: this is actually a mix of prefix and suffix, but adding to infix length works
- h.lengthInfix += micros1.modOuter.apply(string, h.index0(), h.index3());
+ Modifier mod = resolveModifierPlurals(micros1.modOuter, micros2.modOuter);
+ h.lengthInfix += mod.apply(string, h.index0(), h.index3());
} else {
h.length1 += micros1.modOuter.apply(string, h.index0(), h.index1());
h.length2 += micros2.modOuter.apply(string, h.index2(), h.index3());
}
}
+ Modifier resolveModifierPlurals(Modifier first, Modifier second) {
+ Modifier.Parameters firstParameters = first.getParameters();
+ if (firstParameters == null) {
+ // No plural form; return a fallback (e.g., the first)
+ return first;
+ }
+
+ Modifier.Parameters secondParameters = second.getParameters();
+ if (secondParameters == null) {
+ // No plural form; return a fallback (e.g., the first)
+ return first;
+ }
+
+ // Get the required plural form from data
+ StandardPlural resultPlural = fPluralRanges.resolve(firstParameters.plural, secondParameters.plural);
+
+ // Get and return the new Modifier
+ assert firstParameters.obj == secondParameters.obj;
+ assert firstParameters.signum == secondParameters.signum;
+ Modifier mod = firstParameters.obj.getModifier(firstParameters.signum, resultPlural);
+ assert mod != null;
+ return mod;
+ }
+
}
}
@Override
- public boolean equalsModifier(Modifier other) {
+ public Parameters getParameters() {
+ // This method is not currently used.
+ assert false;
+ return null;
+ }
+
+ @Override
+ public boolean semanticallyEquivalent(Modifier other) {
// This method is not currently used. (unsafe path not used in range formatting)
assert false;
return false;
}
@Override
- public boolean equalsModifier(Modifier other) {
+ public Parameters getParameters() {
+ return null;
+ }
+
+ @Override
+ public boolean semanticallyEquivalent(Modifier other) {
if (!(other instanceof ScientificModifier)) {
return false;
}
version https://git-lfs.github.com/spec/v1
-oid sha256:92dc0a5ca71ac54537a6c7c42c2f80ccbd3298d9ebf69c3d732199230d5100c6
-size 12487439
+oid sha256:6dadae4ae83d956325f4e089327e824bb32dbf75c98e9c2815ffa7521f3505ca
+size 12512661
version https://git-lfs.github.com/spec/v1
-oid sha256:d2308b3498ce1c2b869b60b5a0f7cea6f08aff2fac046ae260e10688c15c60b2
+oid sha256:b5ffb95eb91501a9f61ee31333e392ff183e0f7dddefb592f04b90fe2ac3490c
size 92857
import org.junit.Test;
+import com.ibm.icu.number.LocalizedNumberFormatter;
import com.ibm.icu.number.LocalizedNumberRangeFormatter;
import com.ibm.icu.number.Notation;
import com.ibm.icu.number.NumberFormatter;
import com.ibm.icu.number.NumberRangeFormatter.RangeCollapse;
import com.ibm.icu.number.NumberRangeFormatter.RangeIdentityFallback;
import com.ibm.icu.number.Precision;
+import com.ibm.icu.number.UnlocalizedNumberFormatter;
import com.ibm.icu.number.UnlocalizedNumberRangeFormatter;
import com.ibm.icu.util.Currency;
import com.ibm.icu.util.MeasureUnit;
NumberRangeFormatter.with()
.numberFormatterBoth(NumberFormatter.with().unit(MeasureUnit.METER).unitWidth(UnitWidth.FULL_NAME)),
new ULocale("en-us"),
- "1 meter – 5 meters", // TODO: This doesn't collapse because the plurals are different. Fix?
+ "1–5 meters",
"~5 meters",
"~5 meters",
- "0–3 meters", // Note: It collapses when the plurals are the same
+ "0–3 meters",
"~0 meters",
"3–3,000 meters",
"3,000–5,000 meters",
NumberRangeFormatter.with()
.numberFormatterBoth(NumberFormatter.with().unit(MeasureUnit.FAHRENHEIT).unitWidth(UnitWidth.FULL_NAME)),
new ULocale("fr-FR"),
- "1 degré Fahrenheit – 5 degrés Fahrenheit",
+ "1–5 degrés Fahrenheit",
"~5 degrés Fahrenheit",
"~5 degrés Fahrenheit",
- "0 degré Fahrenheit – 3 degrés Fahrenheit",
+ "0–3 degrés Fahrenheit",
"~0 degré Fahrenheit",
"3–3 000 degrés Fahrenheit",
"3 000–5 000 degrés Fahrenheit",
"~5,000 m",
"5,000–5,000,000 m");
+ assertFormatRange(
+ "Default collapse, long-form compact notation",
+ NumberRangeFormatter.with()
+ .numberFormatterBoth(NumberFormatter.with().notation(Notation.compactLong())),
+ new ULocale("de-CH"),
+ "1–5",
+ "~5",
+ "~5",
+ "0–3",
+ "~0",
+ "3–3 Tausend",
+ "3–5 Tausend",
+ "~5 Tausend",
+ "~5 Tausend",
+ "5 Tausend – 5 Millionen");
+
+ assertFormatRange(
+ "Unit collapse, long-form compact notation",
+ NumberRangeFormatter.with()
+ .collapse(RangeCollapse.UNIT)
+ .numberFormatterBoth(NumberFormatter.with().notation(Notation.compactLong())),
+ new ULocale("de-CH"),
+ "1–5",
+ "~5",
+ "~5",
+ "0–3",
+ "~0",
+ "3–3 Tausend",
+ "3 Tausend – 5 Tausend",
+ "~5 Tausend",
+ "~5 Tausend",
+ "5 Tausend – 5 Millionen");
+
assertFormatRange(
"Default collapse on measurement unit with compact-short notation",
NumberRangeFormatter.with()
"5,000–5,000,000");
}
+ @Test
+ public void testPlurals() {
+ // Locale sl has interesting plural forms:
+ // GBP{
+ // one{"britanski funt"}
+ // two{"britanska funta"}
+ // few{"britanski funti"}
+ // other{"britanskih funtov"}
+ // }
+ ULocale locale = new ULocale("sl");
+
+ UnlocalizedNumberFormatter unf = NumberFormatter.with()
+ .unit(GBP)
+ .unitWidth(UnitWidth.FULL_NAME)
+ .precision(Precision.integer());
+ LocalizedNumberFormatter lnf = unf.locale(locale);
+
+ // For comparison, run the non-range version of the formatter
+ assertEquals(Integer.toString(1), "1 britanski funt", lnf.format(1).toString());
+ assertEquals(Integer.toString(2), "2 britanska funta", lnf.format(2).toString());
+ assertEquals(Integer.toString(3), "3 britanski funti", lnf.format(3).toString());
+ assertEquals(Integer.toString(5), "5 britanskih funtov", lnf.format(5).toString());
+
+ LocalizedNumberRangeFormatter lnrf = NumberRangeFormatter.with()
+ .numberFormatterBoth(unf)
+ .identityFallback(RangeIdentityFallback.RANGE)
+ .locale(locale);
+
+ Object[][] cases = new Object[][] {
+ {1, 1, "1–1 britanski funti"}, // one + one -> few
+ {1, 2, "1–2 britanska funta"}, // one + two -> two
+ {1, 3, "1–3 britanski funti"}, // one + few -> few
+ {1, 5, "1–5 britanskih funtov"}, // one + other -> other
+ {2, 1, "2–1 britanski funti"}, // two + one -> few
+ {2, 2, "2–2 britanska funta"}, // two + two -> two
+ {2, 3, "2–3 britanski funti"}, // two + few -> few
+ {2, 5, "2–5 britanskih funtov"}, // two + other -> other
+ {3, 1, "3–1 britanski funti"}, // few + one -> few
+ {3, 2, "3–2 britanska funta"}, // few + two -> two
+ {3, 3, "3–3 britanski funti"}, // few + few -> few
+ {3, 5, "3–5 britanskih funtov"}, // few + other -> other
+ {5, 1, "5–1 britanski funti"}, // other + one -> few
+ {5, 2, "5–2 britanska funta"}, // other + two -> two
+ {5, 3, "5–3 britanski funti"}, // other + few -> few
+ {5, 5, "5–5 britanskih funtov"}, // other + other -> other
+ };
+ for (Object[] cas : cases) {
+ int first = (Integer) cas[0];
+ int second = (Integer) cas[1];
+ String expected = (String) cas[2];
+ String message = Integer.toString(first) + " " + Integer.toString(second);
+ String actual = lnrf.formatRange(first, second).toString();
+ assertEquals(message, expected, actual);
+ }
+ }
+
static void assertFormatRange(
String message,
UnlocalizedNumberRangeFormatter f,