From 1a60a038e14f0c56f50052c03fe76c4933cda339 Mon Sep 17 00:00:00 2001 From: "Shane F. Carr" Date: Tue, 13 Jun 2023 00:36:41 +0000 Subject: [PATCH] ICU-21952 Add withoutLocale functions to LocalizedNumber[Range]Formatter See #2483 --- icu4c/source/i18n/number_fluent.cpp | 20 ++++++++++ icu4c/source/i18n/numrange_fluent.cpp | 21 +++++++++++ icu4c/source/i18n/unicode/numberformatter.h | 26 +++++++++++++ .../i18n/unicode/numberrangeformatter.h | 26 +++++++++++++ icu4c/source/test/intltest/numbertest.h | 1 + icu4c/source/test/intltest/numbertest_api.cpp | 25 +++++++++++++ .../source/test/intltest/numbertest_range.cpp | 18 +++++++++ .../test/number/NumberFormatterApiTest.java | 37 +++++++++++++++++-- .../test/number/NumberRangeFormatterTest.java | 16 ++++++++ .../icu/number/LocalizedNumberFormatter.java | 10 +++++ .../number/LocalizedNumberRangeFormatter.java | 10 +++++ 11 files changed, 206 insertions(+), 4 deletions(-) diff --git a/icu4c/source/i18n/number_fluent.cpp b/icu4c/source/i18n/number_fluent.cpp index 45d6b06c6df8..eeb8e1b7f3b4 100644 --- a/icu4c/source/i18n/number_fluent.cpp +++ b/icu4c/source/i18n/number_fluent.cpp @@ -430,6 +430,14 @@ UnlocalizedNumberFormatter::UnlocalizedNumberFormatter(const NFS& other) // No additional fields to assign } +UnlocalizedNumberFormatter::UnlocalizedNumberFormatter(const impl::MacroProps ¯os) { + fMacros = macros; +} + +UnlocalizedNumberFormatter::UnlocalizedNumberFormatter(impl::MacroProps &¯os) { + fMacros = macros; +} + // Make default copy constructor call the NumberFormatterSettings copy constructor. UnlocalizedNumberFormatter::UnlocalizedNumberFormatter(UNF&& src) noexcept : UNF(static_cast&&>(src)) {} @@ -726,6 +734,18 @@ int32_t LocalizedNumberFormatter::getCallCount() const { // Note: toFormat defined in number_asformat.cpp +UnlocalizedNumberFormatter LocalizedNumberFormatter::withoutLocale() const & { + MacroProps macros(fMacros); + macros.locale = Locale(); + return UnlocalizedNumberFormatter(macros); +} + +UnlocalizedNumberFormatter LocalizedNumberFormatter::withoutLocale() && { + MacroProps macros(std::move(fMacros)); + macros.locale = Locale(); + return UnlocalizedNumberFormatter(std::move(macros)); +} + const DecimalFormatSymbols* LocalizedNumberFormatter::getDecimalFormatSymbols() const { return fMacros.symbols.getDecimalFormatSymbols(); } diff --git a/icu4c/source/i18n/numrange_fluent.cpp b/icu4c/source/i18n/numrange_fluent.cpp index 0944f3024ff5..c500e981ac3c 100644 --- a/icu4c/source/i18n/numrange_fluent.cpp +++ b/icu4c/source/i18n/numrange_fluent.cpp @@ -212,6 +212,14 @@ UnlocalizedNumberRangeFormatter::UnlocalizedNumberRangeFormatter(NFS&& src) // No additional fields to assign } +UnlocalizedNumberRangeFormatter::UnlocalizedNumberRangeFormatter(const impl::RangeMacroProps ¯os) { + fMacros = macros; +} + +UnlocalizedNumberRangeFormatter::UnlocalizedNumberRangeFormatter(impl::RangeMacroProps &¯os) { + fMacros = macros; +} + UnlocalizedNumberRangeFormatter& UnlocalizedNumberRangeFormatter::operator=(const UNF& other) { NFS::operator=(static_cast&>(other)); // No additional fields to assign @@ -286,6 +294,19 @@ LocalizedNumberRangeFormatter UnlocalizedNumberRangeFormatter::locale(const Loca } +UnlocalizedNumberRangeFormatter LocalizedNumberRangeFormatter::withoutLocale() const & { + RangeMacroProps macros(fMacros); + macros.locale = Locale(); + return UnlocalizedNumberRangeFormatter(macros); +} + +UnlocalizedNumberRangeFormatter LocalizedNumberRangeFormatter::withoutLocale() && { + RangeMacroProps macros(std::move(fMacros)); + macros.locale = Locale(); + return UnlocalizedNumberRangeFormatter(std::move(macros)); +} + + FormattedNumberRange LocalizedNumberRangeFormatter::formatFormattableRange( const Formattable& first, const Formattable& second, UErrorCode& status) const { if (U_FAILURE(status)) { diff --git a/icu4c/source/i18n/unicode/numberformatter.h b/icu4c/source/i18n/unicode/numberformatter.h index 069324a9ef3e..82297d9cf67d 100644 --- a/icu4c/source/i18n/unicode/numberformatter.h +++ b/icu4c/source/i18n/unicode/numberformatter.h @@ -2497,11 +2497,18 @@ class U_I18N_API UnlocalizedNumberFormatter explicit UnlocalizedNumberFormatter( NumberFormatterSettings&& src) noexcept; + explicit UnlocalizedNumberFormatter(const impl::MacroProps ¯os); + + explicit UnlocalizedNumberFormatter(impl::MacroProps &¯os); + // To give the fluent setters access to this class's constructor: friend class NumberFormatterSettings; // To give NumberFormatter::with() access to this class's constructor: friend class NumberFormatter; + + // To give LNF::withoutLocale() access to this class's constructor: + friend class LocalizedNumberFormatter; }; /** @@ -2604,6 +2611,25 @@ class U_I18N_API LocalizedNumberFormatter */ Format* toFormat(UErrorCode& status) const; +#ifndef U_HIDE_DRAFT_API + /** + * Disassociate the locale from this formatter. + * + * @return The fluent chain. + * @draft ICU 74 + */ + UnlocalizedNumberFormatter withoutLocale() const &; + + /** + * Overload of withoutLocale() for use on an rvalue reference. + * + * @return The fluent chain. + * @see #withoutLocale + * @draft ICU 74 + */ + UnlocalizedNumberFormatter withoutLocale() &&; +#endif // U_HIDE_DRAFT_API + /** * Default constructor: puts the formatter into a valid but undefined state. * diff --git a/icu4c/source/i18n/unicode/numberrangeformatter.h b/icu4c/source/i18n/unicode/numberrangeformatter.h index 8ca20f31d714..a255eb4a73a2 100644 --- a/icu4c/source/i18n/unicode/numberrangeformatter.h +++ b/icu4c/source/i18n/unicode/numberrangeformatter.h @@ -462,11 +462,18 @@ class U_I18N_API UnlocalizedNumberRangeFormatter explicit UnlocalizedNumberRangeFormatter( NumberRangeFormatterSettings&& src) noexcept; + explicit UnlocalizedNumberRangeFormatter(const impl::RangeMacroProps ¯os); + + explicit UnlocalizedNumberRangeFormatter(impl::RangeMacroProps &¯os); + // To give the fluent setters access to this class's constructor: friend class NumberRangeFormatterSettings; // To give NumberRangeFormatter::with() access to this class's constructor: friend class NumberRangeFormatter; + + // To give LNRF::withoutLocale() access to this class's constructor: + friend class LocalizedNumberRangeFormatter; }; /** @@ -496,6 +503,25 @@ class U_I18N_API LocalizedNumberRangeFormatter FormattedNumberRange formatFormattableRange( const Formattable& first, const Formattable& second, UErrorCode& status) const; +#ifndef U_HIDE_DRAFT_API + /** + * Disassociate the locale from this formatter. + * + * @return The fluent chain. + * @draft ICU 74 + */ + UnlocalizedNumberRangeFormatter withoutLocale() const &; + + /** + * Overload of withoutLocale() for use on an rvalue reference. + * + * @return The fluent chain. + * @see #withoutLocale + * @draft ICU 74 + */ + UnlocalizedNumberRangeFormatter withoutLocale() &&; +#endif // U_HIDE_DRAFT_API + /** * Default constructor: puts the formatter into a valid but undefined state. * diff --git a/icu4c/source/test/intltest/numbertest.h b/icu4c/source/test/intltest/numbertest.h index 1ad8b28da756..08f402109b47 100644 --- a/icu4c/source/test/intltest/numbertest.h +++ b/icu4c/source/test/intltest/numbertest.h @@ -325,6 +325,7 @@ class NumberRangeFormatterTest : public IntlTestWithFieldPosition { void testFieldPositions(); void testCopyMove(); void toObject(); + void locale(); void testGetDecimalNumbers(); void test21684_Performance(); void test21358_SignPosition(); diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp index 4146247ff299..7bb5028cc5d9 100644 --- a/icu4c/source/test/intltest/numbertest_api.cpp +++ b/icu4c/source/test/intltest/numbertest_api.cpp @@ -5186,6 +5186,31 @@ void NumberFormatterApiTest::locale() { UnicodeString actual = NumberFormatter::withLocale(Locale::getFrench()).formatInt(1234, status) .toString(status); assertEquals("Locale withLocale()", u"1\u202f234", actual); + + LocalizedNumberFormatter lnf1 = NumberFormatter::withLocale("en").unitWidth(UNUM_UNIT_WIDTH_FULL_NAME) + .scale(Scale::powerOfTen(2)); + LocalizedNumberFormatter lnf2 = NumberFormatter::with() + .notation(Notation::compactLong()).locale("fr").unitWidth(UNUM_UNIT_WIDTH_FULL_NAME); + UnlocalizedNumberFormatter unf1 = lnf1.withoutLocale(); + UnlocalizedNumberFormatter unf2 = std::move(lnf2).withoutLocale(); + + assertFormatSingle( + u"Formatter after withoutLocale A", + u"unit/meter unit-width-full-name scale/100", + u"unit/meter unit-width-full-name scale/100", + unf1.unit(METER), + "it-IT", + 2, + u"200 metri"); + + assertFormatSingle( + u"Formatter after withoutLocale B", + u"compact-long unit/meter unit-width-full-name", + u"compact-long unit/meter unit-width-full-name", + unf2.unit(METER), + "ja-JP", + 2, + u"2 メートル"); } void NumberFormatterApiTest::skeletonUserGuideExamples() { diff --git a/icu4c/source/test/intltest/numbertest_range.cpp b/icu4c/source/test/intltest/numbertest_range.cpp index faab35d1a5c8..e599fde31946 100644 --- a/icu4c/source/test/intltest/numbertest_range.cpp +++ b/icu4c/source/test/intltest/numbertest_range.cpp @@ -53,6 +53,7 @@ void NumberRangeFormatterTest::runIndexedTest(int32_t index, UBool exec, const c TESTCASE_AUTO(testFieldPositions); TESTCASE_AUTO(testCopyMove); TESTCASE_AUTO(toObject); + TESTCASE_AUTO(locale); TESTCASE_AUTO(testGetDecimalNumbers); TESTCASE_AUTO(test21684_Performance); TESTCASE_AUTO(test21358_SignPosition); @@ -917,6 +918,23 @@ void NumberRangeFormatterTest::toObject() { } } +void NumberRangeFormatterTest::locale() { + IcuTestErrorCode status(*this, "locale"); + + LocalizedNumberRangeFormatter lnf = NumberRangeFormatter::withLocale("en") + .identityFallback(UNUM_IDENTITY_FALLBACK_RANGE); + UnlocalizedNumberRangeFormatter unf1 = lnf.withoutLocale(); + UnlocalizedNumberRangeFormatter unf2 = NumberRangeFormatter::with() + .identityFallback(UNUM_IDENTITY_FALLBACK_RANGE) + .locale("ar-EG") + .withoutLocale(); + + FormattedNumberRange res1 = unf1.locale("bn").formatFormattableRange(5, 5, status); + assertEquals(u"res1", u"\u09EB\u2013\u09EB", res1.toTempString(status)); + FormattedNumberRange res2 = unf2.locale("ja-JP").formatFormattableRange(5, 5, status); + assertEquals(u"res2", u"5\uFF5E5", res2.toTempString(status)); +} + void NumberRangeFormatterTest::testGetDecimalNumbers() { IcuTestErrorCode status(*this, "testGetDecimalNumbers"); diff --git a/icu4j/main/common_tests/src/test/java/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java b/icu4j/main/common_tests/src/test/java/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java index a63da3be0524..bdddc4b48d43 100644 --- a/icu4j/main/common_tests/src/test/java/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java +++ b/icu4j/main/common_tests/src/test/java/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java @@ -5395,10 +5395,39 @@ public void scale() { @Test public void locale() { // Coverage for the locale setters. - Assert.assertEquals(NumberFormatter.with().locale(ULocale.ENGLISH), NumberFormatter.with().locale(Locale.ENGLISH)); - Assert.assertEquals(NumberFormatter.with().locale(ULocale.ENGLISH), NumberFormatter.withLocale(ULocale.ENGLISH)); - Assert.assertEquals(NumberFormatter.with().locale(ULocale.ENGLISH), NumberFormatter.withLocale(Locale.ENGLISH)); - Assert.assertNotEquals(NumberFormatter.with().locale(ULocale.ENGLISH), NumberFormatter.with().locale(Locale.FRENCH)); + Assert.assertEquals(NumberFormatter.with().locale(ULocale.ENGLISH), + NumberFormatter.with().locale(Locale.ENGLISH)); + Assert.assertEquals(NumberFormatter.with().locale(ULocale.ENGLISH), + NumberFormatter.withLocale(ULocale.ENGLISH)); + Assert.assertEquals(NumberFormatter.with().locale(ULocale.ENGLISH), + NumberFormatter.withLocale(Locale.ENGLISH)); + Assert.assertNotEquals(NumberFormatter.with().locale(ULocale.ENGLISH), + NumberFormatter.with().locale(Locale.FRENCH)); + + LocalizedNumberFormatter lnf1 = NumberFormatter.withLocale(ULocale.ENGLISH).unitWidth(UnitWidth.FULL_NAME) + .scale(Scale.powerOfTen(2)); + LocalizedNumberFormatter lnf2 = NumberFormatter.with() + .notation(Notation.compactLong()).locale(ULocale.FRENCH).unitWidth(UnitWidth.FULL_NAME); + UnlocalizedNumberFormatter unf1 = lnf1.withoutLocale(); + UnlocalizedNumberFormatter unf2 = lnf2.withoutLocale(); + + assertFormatSingle( + "Formatter after withoutLocale A", + "unit/meter unit-width-full-name scale/100", + "unit/meter unit-width-full-name scale/100", + unf1.unit(MeasureUnit.METER), + ULocale.ITALY, + 2, + "200 metri"); + + assertFormatSingle( + "Formatter after withoutLocale B", + "compact-long unit/meter unit-width-full-name", + "compact-long unit/meter unit-width-full-name", + unf2.unit(MeasureUnit.METER), + ULocale.JAPAN, + 2, + "2 メートル"); } @Test diff --git a/icu4j/main/common_tests/src/test/java/com/ibm/icu/dev/test/number/NumberRangeFormatterTest.java b/icu4j/main/common_tests/src/test/java/com/ibm/icu/dev/test/number/NumberRangeFormatterTest.java index d4c9d3a47d15..04f8b703a8fc 100644 --- a/icu4j/main/common_tests/src/test/java/com/ibm/icu/dev/test/number/NumberRangeFormatterTest.java +++ b/icu4j/main/common_tests/src/test/java/com/ibm/icu/dev/test/number/NumberRangeFormatterTest.java @@ -823,6 +823,22 @@ public void testFieldPositions() { } } + @Test + public void locale() { + LocalizedNumberRangeFormatter lnf = NumberRangeFormatter.withLocale(ULocale.ENGLISH) + .identityFallback(RangeIdentityFallback.RANGE); + UnlocalizedNumberRangeFormatter unf1 = lnf.withoutLocale(); + UnlocalizedNumberRangeFormatter unf2 = NumberRangeFormatter.with() + .identityFallback(RangeIdentityFallback.RANGE) + .locale(ULocale.forLanguageTag("ar-EG")) + .withoutLocale(); + + FormattedNumberRange res1 = unf1.locale(ULocale.forLanguageTag("bn")).formatRange(5, 5); + assertEquals("res1", "\u09EB\u2013\u09EB", res1.toString()); + FormattedNumberRange res2 = unf2.locale(ULocale.forLanguageTag("ja-JP")).formatRange(5, 5); + assertEquals("res2", "5\uFF5E5", res2.toString()); + } + static final String[] allNSNames = NumberingSystem.getAvailableNames(); private class RangePatternSink extends UResource.Sink { diff --git a/icu4j/main/core/src/main/java/com/ibm/icu/number/LocalizedNumberFormatter.java b/icu4j/main/core/src/main/java/com/ibm/icu/number/LocalizedNumberFormatter.java index c34d889207f8..9154647c22cd 100644 --- a/icu4j/main/core/src/main/java/com/ibm/icu/number/LocalizedNumberFormatter.java +++ b/icu4j/main/core/src/main/java/com/ibm/icu/number/LocalizedNumberFormatter.java @@ -121,6 +121,16 @@ public Format toFormat() { return new LocalizedNumberFormatterAsFormat(this, resolve().loc); } + /** + * Disassociate the locale from this formatter. + * + * @return The fluent chain. + * @draft ICU 74 + */ + public UnlocalizedNumberFormatter withoutLocale() { + return new UnlocalizedNumberFormatter(this, KEY_LOCALE, null); + } + /** * Helper method that creates a FormattedStringBuilder and formats. */ diff --git a/icu4j/main/core/src/main/java/com/ibm/icu/number/LocalizedNumberRangeFormatter.java b/icu4j/main/core/src/main/java/com/ibm/icu/number/LocalizedNumberRangeFormatter.java index adec189adf78..1c07d028c6b6 100644 --- a/icu4j/main/core/src/main/java/com/ibm/icu/number/LocalizedNumberRangeFormatter.java +++ b/icu4j/main/core/src/main/java/com/ibm/icu/number/LocalizedNumberRangeFormatter.java @@ -82,6 +82,16 @@ public FormattedNumberRange formatRange(Number first, Number second) { return formatImpl(dq1, dq2, first.equals(second)); } + /** + * Disassociate the locale from this formatter. + * + * @return The fluent chain. + * @draft ICU 74 + */ + public UnlocalizedNumberRangeFormatter withoutLocale() { + return new UnlocalizedNumberRangeFormatter(this, KEY_LOCALE, null); + } + FormattedNumberRange formatImpl(DecimalQuantity first, DecimalQuantity second, boolean equalBeforeRounding) { if (fImpl == null) { fImpl = new NumberRangeFormatterImpl(resolve());