diff --git a/icu4c/source/i18n/plurrule.cpp b/icu4c/source/i18n/plurrule.cpp index 0798b92c5e80..e7d5b5b3fc1d 100644 --- a/icu4c/source/i18n/plurrule.cpp +++ b/icu4c/source/i18n/plurrule.cpp @@ -56,6 +56,7 @@ static const UChar PK_VAR_N[]={LOW_N,0}; static const UChar PK_VAR_I[]={LOW_I,0}; static const UChar PK_VAR_F[]={LOW_F,0}; static const UChar PK_VAR_T[]={LOW_T,0}; +static const UChar PK_VAR_E[]={LOW_E,0}; static const UChar PK_VAR_V[]={LOW_V,0}; static const UChar PK_WITHIN[]={LOW_W,LOW_I,LOW_T,LOW_H,LOW_I,LOW_N,0}; static const UChar PK_DECIMAL[]={LOW_D,LOW_E,LOW_C,LOW_I,LOW_M,LOW_A,LOW_L,0}; @@ -600,6 +601,7 @@ PluralRuleParser::parse(const UnicodeString& ruleData, PluralRules *prules, UErr case tVariableI: case tVariableF: case tVariableT: + case tVariableE: case tVariableV: U_ASSERT(curAndConstraint != nullptr); curAndConstraint->digitsType = type; @@ -984,6 +986,8 @@ static UnicodeString tokenString(tokenType tok) { s.append(LOW_V); break; case tVariableT: s.append(LOW_T); break; + case tVariableE: + s.append(LOW_E); break; default: s.append(TILDE); } @@ -1160,6 +1164,7 @@ PluralRuleParser::checkSyntax(UErrorCode &status) case tVariableI: case tVariableF: case tVariableT: + case tVariableE: case tVariableV: if (type != tIs && type != tMod && type != tIn && type != tNot && type != tWithin && type != tEqual && type != tNotEqual) { @@ -1176,6 +1181,7 @@ PluralRuleParser::checkSyntax(UErrorCode &status) type == tVariableI || type == tVariableF || type == tVariableT || + type == tVariableE || type == tVariableV || type == tAt)) { status = U_UNEXPECTED_TOKEN; @@ -1207,6 +1213,7 @@ PluralRuleParser::checkSyntax(UErrorCode &status) type != tVariableI && type != tVariableF && type != tVariableT && + type != tVariableE && type != tVariableV) { status = U_UNEXPECTED_TOKEN; } @@ -1384,6 +1391,8 @@ PluralRuleParser::getKeyType(const UnicodeString &token, tokenType keyType) keyType = tVariableF; } else if (0 == token.compare(PK_VAR_T, 1)) { keyType = tVariableT; + } else if (0 == token.compare(PK_VAR_E, 1)) { + keyType = tVariableE; } else if (0 == token.compare(PK_VAR_V, 1)) { keyType = tVariableV; } else if (0 == token.compare(PK_IS, 2)) { @@ -1481,6 +1490,8 @@ PluralOperand tokenTypeToPluralOperand(tokenType tt) { return PLURAL_OPERAND_V; case tVariableT: return PLURAL_OPERAND_T; + case tVariableE: + return PLURAL_OPERAND_E; default: UPRV_UNREACHABLE; // unexpected. } diff --git a/icu4c/source/i18n/plurrule_impl.h b/icu4c/source/i18n/plurrule_impl.h index 0dc44fb62e93..ada938c4bb7b 100644 --- a/icu4c/source/i18n/plurrule_impl.h +++ b/icu4c/source/i18n/plurrule_impl.h @@ -138,6 +138,7 @@ enum tokenType { tVariableF, tVariableV, tVariableT, + tVariableE, tDecimal, tInteger, tEOF diff --git a/icu4c/source/test/intltest/plurults.cpp b/icu4c/source/test/intltest/plurults.cpp index a70c362b7ab6..b8a6c770ec11 100644 --- a/icu4c/source/test/intltest/plurults.cpp +++ b/icu4c/source/test/intltest/plurults.cpp @@ -31,6 +31,7 @@ #include "number_decimalquantity.h" using icu::number::impl::DecimalQuantity; +using namespace icu::number; void setupResult(const int32_t testSource[], char result[], int32_t* max); UBool checkEqual(const PluralRules &test, char *result, int32_t max); @@ -49,6 +50,7 @@ void PluralRulesTest::runIndexedTest( int32_t index, UBool exec, const char* &na TESTCASE_AUTO(testGetSamples); TESTCASE_AUTO(testWithin); TESTCASE_AUTO(testGetAllKeywordValues); + TESTCASE_AUTO(testCompactDecimalPluralKeyword); TESTCASE_AUTO(testOrdinal); TESTCASE_AUTO(testSelect); TESTCASE_AUTO(testAvailbleLocales); @@ -595,6 +597,88 @@ PluralRulesTest::testGetAllKeywordValues() { } } +void +PluralRulesTest::testCompactDecimalPluralKeyword() { + IcuTestErrorCode errorCode(*this, "testCompactDecimalPluralKeyword"); + + LocalPointer rules(PluralRules::createRules( + u"one: i = 0,1 @integer 0, 1 @decimal 0.0~1.5; " + u"many: e = 0 and i % 1000000 = 0 and v = 0 or e != 0 .. 5; " + u"other: @integer 2~17, 100, 1000, 10000, 100000, 1000000, " + u" @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …", errorCode)); + + if (U_FAILURE(errorCode)) { + errln("Couldn't instantiate plurals rules from string, with error = %s", u_errorName(errorCode)); + return; + } + + const char* localeName = "fr-FR"; + Locale locale = Locale::createFromName(localeName); + + struct TestCase { + const char16_t* skeleton; + const int input; + const char16_t* expectedFormattedOutput; + const char16_t* expectedPluralRuleKeyword; + } cases[] = { + // unlocalized formatter skeleton, input, string output, plural rule keyword + {u"", 0, u"0", u"one"}, + {u"compact-long", 0, u"0", u"one"}, + + {u"", 1, u"1", u"one"}, + {u"compact-long", 1, u"1", u"one"}, + + {u"", 2, u"2", u"other"}, + {u"compact-long", 2, u"2", u"other"}, + + {u"", 1000000, u"1 000 000", u"many"}, + {u"compact-long", 1000000, u"1 million", u"many"}, + + {u"", 1000001, u"1 000 001", u"other"}, + {u"compact-long", 1000001, u"1 million", u"many"}, + + {u"", 120000, u"1 200 000", u"other"}, + {u"compact-long", 1200000, u"1,2 millions", u"many"}, + + {u"", 1200001, u"1 200 001", u"other"}, + {u"compact-long", 1200001, u"1,2 millions", u"many"}, + + {u"", 2000000, u"2 000 000", u"many"}, + {u"compact-long", 2000000, u"2 millions", u"many"}, + }; + for (const auto& cas : cases) { + const char16_t* skeleton = cas.skeleton; + const int input = cas.input; + const char16_t* expectedPluralRuleKeyword = cas.expectedPluralRuleKeyword; + + UnicodeString actualPluralRuleKeyword = + getPluralKeyword(rules, locale, input, skeleton); + + UnicodeString message(UnicodeString(localeName) + u" " + DoubleToUnicodeString(input)); + assertEquals(message, expectedPluralRuleKeyword, actualPluralRuleKeyword); + } +} + +UnicodeString PluralRulesTest::getPluralKeyword(const LocalPointer &rules, Locale locale, double number, const char16_t* skeleton) { + IcuTestErrorCode errorCode(*this, "getPluralKeyword"); + UnlocalizedNumberFormatter ulnf = NumberFormatter::forSkeleton(skeleton, errorCode); + if (errorCode.errIfFailureAndReset("PluralRules::getPluralKeyword(, , %d, %s) failed", number, skeleton)) { + return nullptr; + } + LocalizedNumberFormatter formatter = ulnf.locale(locale); + + const FormattedNumber fn = formatter.formatDouble(number, errorCode); + if (errorCode.errIfFailureAndReset("NumberFormatter::formatDouble(%d) failed", number)) { + return nullptr; + } + + UnicodeString pluralKeyword = rules->select(fn, errorCode); + if (errorCode.errIfFailureAndReset("PluralRules->select(FormattedNumber of %d) failed", number)) { + return nullptr; + } + return pluralKeyword; +} + void PluralRulesTest::testOrdinal() { IcuTestErrorCode errorCode(*this, "testOrdinal"); LocalPointer pr(PluralRules::forLocale("en", UPLURAL_TYPE_ORDINAL, errorCode)); diff --git a/icu4c/source/test/intltest/plurults.h b/icu4c/source/test/intltest/plurults.h index 784b0d3bf276..301f852e2977 100644 --- a/icu4c/source/test/intltest/plurults.h +++ b/icu4c/source/test/intltest/plurults.h @@ -32,6 +32,7 @@ class PluralRulesTest : public IntlTest { void testGetSamples(); void testWithin(); void testGetAllKeywordValues(); + void testCompactDecimalPluralKeyword(); void testOrdinal(); void testSelect(); void testAvailbleLocales(); @@ -43,6 +44,8 @@ class PluralRulesTest : public IntlTest { void assertRuleValue(const UnicodeString& rule, double expected); void assertRuleKeyValue(const UnicodeString& rule, const UnicodeString& key, double expected); + UnicodeString getPluralKeyword(const LocalPointer &rules, + Locale locale, double number, const char16_t* skeleton); void checkSelect(const LocalPointer &rules, UErrorCode &status, int32_t line, const char *keyword, ...); void compareLocaleResults(const char* loc1, const char* loc2, const char* loc3);