diff --git a/icu4c/source/i18n/messageformat2.cpp b/icu4c/source/i18n/messageformat2.cpp index 93cbcee0b8c0..6448980c894d 100644 --- a/icu4c/source/i18n/messageformat2.cpp +++ b/icu4c/source/i18n/messageformat2.cpp @@ -305,7 +305,7 @@ FunctionContext MessageFormatter::makeFunctionContext(const FunctionOptions& opt UMFBidiOption dir = ubidi_getMFOption(options.getStringFunctionOption(options::U_DIR)); UnicodeString id = options.getStringFunctionOption(options::U_ID); - return FunctionContext(locale, dir, id); + return FunctionContext(locale, formattingLocale, dir, id); } // Resolves a function's options diff --git a/icu4c/source/i18n/messageformat2_formatter.cpp b/icu4c/source/i18n/messageformat2_formatter.cpp index 0cce2b267a02..e84e15076822 100644 --- a/icu4c/source/i18n/messageformat2_formatter.cpp +++ b/icu4c/source/i18n/messageformat2_formatter.cpp @@ -76,6 +76,11 @@ namespace message2 { return *this; } + MessageFormatter::Builder& MessageFormatter::Builder::setFormattingLocale(const Locale& loc) { + formattingLocale = loc; + return *this; + } + MessageFormatter::Builder& MessageFormatter::Builder::setDataModel(MFDataModel&& newDataModel) { clearState(); hasPattern = false; @@ -119,7 +124,11 @@ namespace message2 { return MessageFormatter(*this, errorCode); } - MessageFormatter::Builder::Builder(UErrorCode& errorCode) : locale(Locale::getDefault()), customMFFunctionRegistry(nullptr) { + MessageFormatter::Builder::Builder(UErrorCode& errorCode) : + locale(Locale::getDefault()), + formattingLocale(), + customMFFunctionRegistry(nullptr) { + formattingLocale.setToBogus(); // indicating setFormattingLocale was not called // Initialize errors errors = new StaticErrors(errorCode); CHECK_ERROR(errorCode); @@ -152,7 +161,10 @@ namespace message2 { // MessageFormatter - MessageFormatter::MessageFormatter(const MessageFormatter::Builder& builder, UErrorCode &success) : locale(builder.locale), customMFFunctionRegistry(builder.customMFFunctionRegistry) { + MessageFormatter::MessageFormatter(const MessageFormatter::Builder& builder, UErrorCode &success) : + locale(builder.locale), + formattingLocale(builder.formattingLocale.isBogus() ? builder.locale : builder.formattingLocale), + customMFFunctionRegistry(builder.customMFFunctionRegistry) { CHECK_ERROR(success); // Set up the standard function registry @@ -236,6 +248,7 @@ namespace message2 { cleanup(); locale = std::move(other.locale); + formattingLocale = std::move(other.formattingLocale); standardMFFunctionRegistry = std::move(other.standardMFFunctionRegistry); customMFFunctionRegistry = other.customMFFunctionRegistry; dataModel = std::move(other.dataModel); diff --git a/icu4c/source/i18n/messageformat2_function_registry.cpp b/icu4c/source/i18n/messageformat2_function_registry.cpp index 0142ca1d5a29..2220ec42bb6f 100644 --- a/icu4c/source/i18n/messageformat2_function_registry.cpp +++ b/icu4c/source/i18n/messageformat2_function_registry.cpp @@ -734,15 +734,16 @@ StandardFunctions::NumberValue::NumberValue(const Number& parent, return; } + formattingLocale = context.getFormattingLocale(); locale = context.getLocale(); opts = options.mergeOptions(arg.getResolvedOptions(), errorCode); innerValue = arg.unwrap(); functionName = UnicodeString(parent.isInteger ? "integer" : "number"); inputDir = context.getDirection(); - dir = outputDirectionalityFromUDir(inputDir, locale); + dir = outputDirectionalityFromUDir(inputDir, formattingLocale); number::LocalizedNumberFormatter realFormatter; - realFormatter = formatterForOptions(parent, locale, opts, errorCode); + realFormatter = formatterForOptions(parent, formattingLocale, opts, errorCode); int64_t integerValue = 0; @@ -1063,6 +1064,7 @@ StandardFunctions::DateTimeValue::DateTimeValue(DateTime::DateTimeType type, } locale = context.getLocale(); + formattingLocale = context.getFormattingLocale(); opts = options.mergeOptions(arg.getResolvedOptions(), errorCode); innerValue = arg.unwrap(); switch (type) { @@ -1077,7 +1079,7 @@ StandardFunctions::DateTimeValue::DateTimeValue(DateTime::DateTimeType type, break; } inputDir = context.getDirection(); - dir = outputDirectionalityFromUDir(inputDir, locale); + dir = outputDirectionalityFromUDir(inputDir, formattingLocale); const Formattable* source = &innerValue; @@ -1119,19 +1121,19 @@ StandardFunctions::DateTimeValue::DateTimeValue(DateTime::DateTimeType type, timeStyle = stringToStyle(opts.getStringFunctionOption(timeStyleName), errorCode); if (useDate && !useTime) { - df.adoptInstead(DateFormat::createDateInstance(dateStyle, locale)); + df.adoptInstead(DateFormat::createDateInstance(dateStyle, formattingLocale)); } else if (useTime && !useDate) { - df.adoptInstead(DateFormat::createTimeInstance(timeStyle, locale)); + df.adoptInstead(DateFormat::createTimeInstance(timeStyle, formattingLocale)); } else { - df.adoptInstead(DateFormat::createDateTimeInstance(dateStyle, timeStyle, locale)); + df.adoptInstead(DateFormat::createDateTimeInstance(dateStyle, timeStyle, formattingLocale)); } } else if (type == DateTimeType::kDate) { dateStyle = stringToStyle(opts.getStringFunctionOption(styleName), errorCode); - df.adoptInstead(DateFormat::createDateInstance(dateStyle, locale)); + df.adoptInstead(DateFormat::createDateInstance(dateStyle, formattingLocale)); } else { // :time timeStyle = stringToStyle(opts.getStringFunctionOption(styleName), errorCode); - df.adoptInstead(DateFormat::createTimeInstance(timeStyle, locale)); + df.adoptInstead(DateFormat::createTimeInstance(timeStyle, formattingLocale)); } } else { // Build up a skeleton based on the field options, then use that to @@ -1483,6 +1485,7 @@ StandardFunctions::StringValue::StringValue(const FunctionContext& context, const FunctionOptions&, UErrorCode& status) { CHECK_ERROR(status); + formattingLocale = context.getFormattingLocale(); locale = context.getLocale(); innerValue = val.unwrap(); functionName = UnicodeString("string"); @@ -1490,7 +1493,7 @@ StandardFunctions::StringValue::StringValue(const FunctionContext& context, dir = stringOutputDirection(inputDir); // No options // Convert to string - formattedString = formattableToString(context.getLocale(), innerValue, status); + formattedString = formattableToString(formattingLocale, innerValue, status); } void StandardFunctions::StringValue::selectKeys(const UnicodeString* keys, diff --git a/icu4c/source/i18n/unicode/messageformat2.h b/icu4c/source/i18n/unicode/messageformat2.h index 3b0e4887a53c..3380c546a495 100644 --- a/icu4c/source/i18n/unicode/messageformat2.h +++ b/icu4c/source/i18n/unicode/messageformat2.h @@ -126,6 +126,18 @@ namespace message2 { */ U_I18N_API const Locale& getLocale() const { return locale; } + /** + * Acceses the formatting locale that this `MessageFormatter` object + * was created with. If no formatting locale was explicitly set, + * returns the same locale as getLocale(). + * + * @return A reference to the formatting locale. + * + * @internal ICU 79 technology preview + * @deprecated This API is for technology preview only. + */ + U_I18N_API const Locale& getFormattingLocale() { return formattingLocale; } + /** * Serializes the data model as a string in MessageFormat 2.0 syntax. * @@ -230,6 +242,7 @@ namespace message2 { // Ignored if `setPattern()` wasn't called StaticErrors* errors; Locale locale; + Locale formattingLocale; // Not owned const MFFunctionRegistry* customMFFunctionRegistry; // Error behavior; see comment in `MessageFormatter` class @@ -244,7 +257,7 @@ namespace message2 { void clearState(); public: /** - * Sets the locale to use for formatting. + * Sets the locale to use for this context * * @param locale The desired locale. * @return A reference to the builder. @@ -253,6 +266,18 @@ namespace message2 { * @deprecated This API is for technology preview only. */ U_I18N_API Builder& setLocale(const Locale& locale); + /** + * Sets the locale used by formatter factories (:number, :date, + * etc.) for rendering output. Selector factories (plural rules) + * continue to use the locale set via setLocale(). + * The formatting locale defaults to the same one set by setLocale(). + * @param locale The desired locale + * @return A reference to the builder. + * + * @internal ICU 79 technology preview + * @deprecated This API is for technology preview only. + */ + U_I18N_API Builder& setFormattingLocale(const Locale& locale); /** * Sets the pattern (contents of the message) and parses it * into a data model. If a data model was @@ -501,8 +526,10 @@ namespace message2 { void clearErrors() const; void cleanup() noexcept; - // The locale this MessageFormatter was created with - /* const */ Locale locale; + // The locale set by setLocale() + Locale locale; + // The locale set by setformattinglocale() + Locale formattingLocale; // Registry for built-in functions MFFunctionRegistry standardMFFunctionRegistry; diff --git a/icu4c/source/i18n/unicode/messageformat2_function_registry.h b/icu4c/source/i18n/unicode/messageformat2_function_registry.h index af5379bd5c7c..f281693073ba 100644 --- a/icu4c/source/i18n/unicode/messageformat2_function_registry.h +++ b/icu4c/source/i18n/unicode/messageformat2_function_registry.h @@ -247,6 +247,15 @@ namespace message2 { * @deprecated This API is for technology preview only. */ U_I18N_API const Locale& getLocale() const { return locale; } + /** + * Returns the formatting locale from this context. + * + * @return Formatting Locale the context was created with. + * + * @internal ICU 79 technology preview + * @deprecated This API is for technology preview only. + */ + U_I18N_API const Locale& getFormattingLocale() const { return formattingLocale; } /** * Returns the text direction from this context. * @@ -270,11 +279,12 @@ namespace message2 { friend class MessageFormatter; Locale locale; + Locale formattingLocale; UMFBidiOption dir; UnicodeString id; - FunctionContext(const Locale& loc, UMFBidiOption d, UnicodeString i) - : locale(loc), dir(d), id(i) {} + FunctionContext(const Locale& loc, const Locale& formatLoc, UMFBidiOption d, UnicodeString i) + : locale(loc), formattingLocale(formatLoc), dir(d), id(i) {} }; // class FunctionContext class FunctionValue; @@ -515,13 +525,21 @@ namespace message2 { */ UnicodeString fallback; /** - * Locale from u:locale option. + * Locale from function context. * Must be set from function context. * * @internal ICU 79 technology preview * @deprecated This API is for technology preview only. */ Locale locale; + /** + * Formatting Locale from function context. + * Must be set from function context. + * + * @internal ICU 79 technology preview + * @deprecated This API is for technology preview only. + */ + Locale formattingLocale; /** * Directionality of formatted result. * Defaults to U_MF_DIRECTIONALITY_UNKNOWN if not set diff --git a/icu4c/source/test/intltest/messageformat2test.cpp b/icu4c/source/test/intltest/messageformat2test.cpp index 1880ff117dba..9586a6b0709e 100644 --- a/icu4c/source/test/intltest/messageformat2test.cpp +++ b/icu4c/source/test/intltest/messageformat2test.cpp @@ -31,6 +31,7 @@ TestMessageFormat2::runIndexedTest(int32_t index, UBool exec, TESTCASE_AUTO(testLowLoneSurrogate); TESTCASE_AUTO(testLoneSurrogateInQuotedLiteral); TESTCASE_AUTO(dataDrivenTests); + TESTCASE_AUTO(testSetFormatLocale); TESTCASE_AUTO_END; } @@ -479,6 +480,43 @@ void TestMessageFormat2::dataDrivenTests() { jsonTestsFromFiles(errorCode); } +void TestMessageFormat2::testSetFormatLocale() { + IcuTestErrorCode errorCode(*this, "testSetFormatLocale"); + UParseError parseError; + + UnicodeString pattern(u"" + ".input {$count :number} .input {$name :string} .input {$birthday :date}\n" + ".match $count\n" + "many {{PASS {$count} @ {$birthday :date style=short}}}\n" + "* {{FAIL wrong bucket {$count} @ {$birthday :date style=short}}}\n" + ); + MessageFormatter mf = MessageFormatter::Builder(errorCode) + .setErrorHandlingBehavior(MessageFormatter::U_MF_BEST_EFFORT) + .setPattern(pattern, parseError, errorCode) + .setLocale(Locale("pl")) + .setFormattingLocale(Locale("en")) + .build(errorCode); + + if (errorCode.errIfFailureAndReset("build")) { + errln("UParseError = %d:%d", parseError.line, parseError.offset); + return; + } + + std::map argsBuilder; + argsBuilder["count"] = message2::Formattable((int64_t)13); + argsBuilder["name"] = message2::Formattable("US"); + argsBuilder["birthday"] = message2::Formattable("1776-07-04"); + MessageArguments args(argsBuilder, errorCode); + + UnicodeString result = mf.formatToString(args, errorCode); + if (errorCode.errIfFailureAndReset("formatToString")) { + return; + } + UnicodeString expectedResult(u"PASS 13 @ 7/4/76"); + assertEquals("testSetFormatLocale", expectedResult, result); +} + + TestCase::~TestCase() {} TestCase::Builder::~Builder() {} diff --git a/icu4c/source/test/intltest/messageformat2test.h b/icu4c/source/test/intltest/messageformat2test.h index 69ca49345f5f..ec80b88f29bd 100644 --- a/icu4c/source/test/intltest/messageformat2test.h +++ b/icu4c/source/test/intltest/messageformat2test.h @@ -53,6 +53,7 @@ class TestMessageFormat2: public IntlTest { void testBidiAPI(void); void testAPI(void); void testAPISimple(void); + void testSetFormatLocale(void); private: void jsonTestsFromFiles(IcuTestErrorCode&);