diff --git a/icu4c/source/common/ucurr.cpp b/icu4c/source/common/ucurr.cpp index 78716013b1..802eafba16 100644 --- a/icu4c/source/common/ucurr.cpp +++ b/icu4c/source/common/ucurr.cpp @@ -690,7 +690,13 @@ ucurr_getName(const UChar* currency, key.append("/", ec2); key.append(buf, ec2); s = ures_getStringByKeyWithFallback(rb.getAlias(), key.data(), len, &ec2); - } else { + if (ec2 == U_MISSING_RESOURCE_ERROR) { + *ec = U_USING_FALLBACK_WARNING; + ec2 = U_ZERO_ERROR; + choice = UCURR_SYMBOL_NAME; + } + } + if (s == NULL) { ures_getByKey(rb.getAlias(), CURRENCIES, rb.getAlias(), &ec2); ures_getByKeyWithFallback(rb.getAlias(), buf, rb.getAlias(), &ec2); s = ures_getStringByIndex(rb.getAlias(), choice, len, &ec2); diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp index 2d925eee5b..f27292ff6e 100644 --- a/icu4c/source/test/intltest/numbertest_api.cpp +++ b/icu4c/source/test/intltest/numbertest_api.cpp @@ -779,7 +779,7 @@ void NumberFormatterApiTest::unitCurrency() { NumberFormatter::with().unit(PTE).unitWidth(UNUM_UNIT_WIDTH_NARROW), Locale("pt-PT"), 444444.55, - u"444,444$55 PTE"); + u"444,444$55 \u200B"); assertFormatSingle( u"Currency-dependent symbols (Test ISO Code)", diff --git a/icu4c/source/test/intltest/numfmtst.cpp b/icu4c/source/test/intltest/numfmtst.cpp index 26ae404645..4625b13016 100644 --- a/icu4c/source/test/intltest/numfmtst.cpp +++ b/icu4c/source/test/intltest/numfmtst.cpp @@ -123,6 +123,7 @@ void NumberFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &n TESTCASE_AUTO(TestCases); TESTCASE_AUTO(TestCurrencyNames); + TESTCASE_AUTO(Test20484_NarrowSymbolFallback); TESTCASE_AUTO(TestCurrencyAmount); TESTCASE_AUTO(TestCurrencyUnit); TESTCASE_AUTO(TestCoverage); @@ -2097,6 +2098,50 @@ void NumberFormatTest::TestCurrencyNames(void) { // TODO add more tests later } +void NumberFormatTest::Test20484_NarrowSymbolFallback(){ + IcuTestErrorCode status(*this, "Test20484_NarrowSymbolFallback"); + + struct TestCase { + const char* locale; + const char16_t* isoCode; + const char16_t* expectedShort; + const char16_t* expectedNarrow; + UErrorCode expectedNarrowError; + } cases[] = { + {"en-US", u"CAD", u"CA$", u"$", U_USING_DEFAULT_WARNING}, // narrow: fallback to root + {"en-US", u"CDF", u"CDF", u"CDF", U_USING_FALLBACK_WARNING}, // narrow: fallback to short + {"sw-CD", u"CDF", u"FC", u"FC", U_USING_FALLBACK_WARNING}, // narrow: fallback to short + {"en-US", u"GEL", u"GEL", u"₾", U_USING_DEFAULT_WARNING}, // narrow: fallback to root + {"ka-GE", u"GEL", u"₾", u"₾", U_USING_FALLBACK_WARNING}, // narrow: fallback to ka + {"ka", u"GEL", u"₾", u"₾", U_ZERO_ERROR}, // no fallback on narrow + }; + for (const auto& cas : cases) { + status.setScope(cas.isoCode); + UBool choiceFormatIgnored; + int32_t lengthIgnored; + const UChar* actualShort = ucurr_getName( + cas.isoCode, + cas.locale, + UCURR_SYMBOL_NAME, + &choiceFormatIgnored, + &lengthIgnored, + status); + status.errIfFailureAndReset(); + const UChar* actualNarrow = ucurr_getName( + cas.isoCode, + cas.locale, + UCURR_NARROW_SYMBOL_NAME, + &choiceFormatIgnored, + &lengthIgnored, + status); + status.expectErrorAndReset(cas.expectedNarrowError); + assertEquals(UnicodeString("Short symbol: ") + cas.locale + u": " + cas.isoCode, + cas.expectedShort, actualShort); + assertEquals(UnicodeString("Narrow symbol: ") + cas.locale + ": " + cas.isoCode, + cas.expectedNarrow, actualNarrow); + } +} + void NumberFormatTest::TestCurrencyUnit(void){ UErrorCode ec = U_ZERO_ERROR; static const UChar USD[] = u"USD"; diff --git a/icu4c/source/test/intltest/numfmtst.h b/icu4c/source/test/intltest/numfmtst.h index 0451067eb4..b62cde5523 100644 --- a/icu4c/source/test/intltest/numfmtst.h +++ b/icu4c/source/test/intltest/numfmtst.h @@ -153,6 +153,8 @@ class NumberFormatTest: public CalendarTimeZoneTest { void TestCurrencyNames(void); + void Test20484_NarrowSymbolFallback(void); + void TestCurrencyAmount(void); void TestCurrencyUnit(void); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/CurrencyDisplayNames.java b/icu4j/main/classes/core/src/com/ibm/icu/text/CurrencyDisplayNames.java index 9bdd00dce6..21297aaf97 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/CurrencyDisplayNames.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/CurrencyDisplayNames.java @@ -122,8 +122,8 @@ public abstract class CurrencyDisplayNames { /** * Returns the narrow symbol for the currency with the provided ISO code. - * If there is no data for narrow symbol, substitutes isoCode, or returns - * null if noSubstitute was set in the factory method. + * If there is no data for narrow symbol, substitutes the default symbol, + * or returns null if noSubstitute was set in the factory method. * * @param isoCode the three-letter ISO code. * @return the narrow symbol. diff --git a/icu4j/main/classes/currdata/src/com/ibm/icu/impl/ICUCurrencyDisplayInfoProvider.java b/icu4j/main/classes/currdata/src/com/ibm/icu/impl/ICUCurrencyDisplayInfoProvider.java index b0fe8de769..9fb70c58e0 100644 --- a/icu4j/main/classes/currdata/src/com/ibm/icu/impl/ICUCurrencyDisplayInfoProvider.java +++ b/icu4j/main/classes/currdata/src/com/ibm/icu/impl/ICUCurrencyDisplayInfoProvider.java @@ -95,7 +95,7 @@ public class ICUCurrencyDisplayInfoProvider implements CurrencyDisplayInfoProvid /** * Cache for symbolMap() and nameMap(). */ - private volatile SoftReference parsingDataCache = new SoftReference(null); + private volatile SoftReference parsingDataCache = new SoftReference<>(null); /** * Cache for getUnitPatterns(). @@ -124,8 +124,8 @@ public class ICUCurrencyDisplayInfoProvider implements CurrencyDisplayInfoProvid } static class ParsingData { - Map symbolToIsoCode = new HashMap(); - Map nameToIsoCode = new HashMap(); + Map symbolToIsoCode = new HashMap<>(); + Map nameToIsoCode = new HashMap<>(); } //////////////////////// @@ -170,9 +170,8 @@ public class ICUCurrencyDisplayInfoProvider implements CurrencyDisplayInfoProvid NarrowSymbol narrowSymbol = fetchNarrowSymbol(isoCode); // Fall back to ISO Code - // TODO: Should this fall back to the regular symbol instead of the ISO code? if (narrowSymbol.narrowSymbol == null && fallback) { - return isoCode; + return getSymbol(isoCode); } return narrowSymbol.narrowSymbol; } @@ -289,7 +288,7 @@ public class ICUCurrencyDisplayInfoProvider implements CurrencyDisplayInfoProvid CurrencySink sink = new CurrencySink(!fallback, CurrencySink.EntrypointTable.TOP); sink.parsingData = result; rb.getAllItemsWithFallback("", sink); - parsingDataCache = new SoftReference(result); + parsingDataCache = new SoftReference<>(result); } return result; } @@ -297,7 +296,7 @@ public class ICUCurrencyDisplayInfoProvider implements CurrencyDisplayInfoProvid Map fetchUnitPatterns() { Map result = unitPatternsCache; if (result == null) { - result = new HashMap(); + result = new HashMap<>(); CurrencySink sink = new CurrencySink(!fallback, CurrencySink.EntrypointTable.CURRENCY_UNIT_PATTERNS); sink.unitPatterns = result; rb.getAllItemsWithFallback("CurrencyUnitPatterns", sink); 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 69f3e6b89e..3d655aff44 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 @@ -741,7 +741,7 @@ public class NumberFormatterApiTest { NumberFormatter.with().unit(PTE).unitWidth(UnitWidth.NARROW), ULocale.forLanguageTag("pt-PT"), 444444.55, - "444,444$55 PTE"); + "444,444$55 \u200B"); assertFormatSingle( "Currency-dependent symbols (Test)", diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/util/CurrencyTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/util/CurrencyTest.java index 0e8de689b5..7c8a3ccedc 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/util/CurrencyTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/util/CurrencyTest.java @@ -238,6 +238,30 @@ public class CurrencyTest extends TestFmwk { USX.getName(en_US, Currency.LONG_NAME, isChoiceFormat)); } + @Test + public void test20484_NarrowSymbolFallback() { + Object[][] cases = new Object[][] { + {"en-US", "CAD", "CA$", "$"}, + {"en-US", "CDF", "CDF", "CDF"}, + {"sw-CD", "CDF", "FC", "FC"}, + {"en-US", "GEL", "GEL", "₾"}, + {"ka-GE", "GEL", "₾", "₾"}, + {"ka", "GEL", "₾", "₾"}, + }; + for (Object[] cas : cases) { + ULocale locale = new ULocale((String) cas[0]); + String isoCode = (String) cas[1]; + String expectedShort = (String) cas[2]; + String expectedNarrow = (String) cas[3]; + + CurrencyDisplayNames cdn = CurrencyDisplayNames.getInstance(locale); + assertEquals("Short symbol: " + locale + ": " + isoCode, + expectedShort, cdn.getSymbol(isoCode)); + assertEquals("Narrow symbol: " + locale + ": " + isoCode, + expectedNarrow, cdn.getNarrowSymbol(isoCode)); + } + } + @Test public void testGetName_Locale_Int_String_BooleanArray() { Currency currency = Currency.getInstance(ULocale.CHINA);