Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions icu4c/source/i18n/messageformat2_formatter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -281,16 +281,16 @@ namespace message2 {
UErrorCode& status) const {
NULL_ON_ERROR(status);

if (isBuiltInFunction(functionName)) {
return standardMFFunctionRegistry.getFunction(functionName);
}
if (hasCustomMFFunctionRegistry()) {
const MFFunctionRegistry& customMFFunctionRegistry = getCustomMFFunctionRegistry();
Function* function = customMFFunctionRegistry.getFunction(functionName);
if (function != nullptr) {
return function;
}
}
if (isBuiltInFunction(functionName)) {
return standardMFFunctionRegistry.getFunction(functionName);
}
Comment on lines 283 to +293

@srl295 srl295 Jun 24, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only strictly necessary change, for overriding.

// Either there is no custom function registry and the function
// isn't built-in, or the function doesn't exist in either the built-in
// or custom registry.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ static constexpr std::u16string_view YEAR = u"year";
per https://github.com/unicode-org/message-format-wg/blob/main/spec/registry.md
as of https://github.com/unicode-org/message-format-wg/releases/tag/LDML45-alpha
*/
class StandardFunctions {
class U_I18N_API_CLASS StandardFunctions { // TODO: had to export StandardFunctions
friend class MessageFormatter;

public:
Expand All @@ -98,6 +98,8 @@ static constexpr std::u16string_view YEAR = u"year";
std::u16string_view optionName,
UErrorCode& errorCode);

// TODO: ICU-23424 had to make this public to be callable
public:
class DateTime;
class DateTimeValue;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ namespace message2 {
Locale locale;
UMFBidiOption dir;
UnicodeString id;

public: // TODO ICU-23424 had to make this public so FunctionContext is constructable
FunctionContext(const Locale& loc, UMFBidiOption d, UnicodeString i)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than making the constructor public, I would prefer to add a public method that takes a FunctionContext and returns a new FunctionContext with a different locale, like:

FunctionContext withLocale(const Locale& loc)

because I don't see a use case for custom functions creating a FunctionContext with a different directionality or ID.

: locale(loc), dir(d), id(i) {}
}; // class FunctionContext
Expand Down
231 changes: 231 additions & 0 deletions icu4c/source/test/intltest/messageformat2test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
#include "unicode/gregocal.h"
#include "unicode/messageformat2.h"
#include "messageformat2test.h"
// TODO ICU-23424: had to include internal .h to be able to chain load internal functions
#include "messageformat2_function_registry_internal.h"

using namespace icu::message2;

Expand All @@ -31,6 +33,7 @@ TestMessageFormat2::runIndexedTest(int32_t index, UBool exec,
TESTCASE_AUTO(testLowLoneSurrogate);
TESTCASE_AUTO(testLoneSurrogateInQuotedLiteral);
TESTCASE_AUTO(dataDrivenTests);
TESTCASE_AUTO(testSetFormatLocale);
TESTCASE_AUTO_END;
}

Expand Down Expand Up @@ -479,6 +482,234 @@ void TestMessageFormat2::dataDrivenTests() {
jsonTestsFromFiles(errorCode);
}

namespace EnFunctionTest {

// //TODO: This function is duplicated from messageformat2test_custom.cpp
// // but should it be a library function?
// static UnicodeString getStringOption(const FunctionOptionsMap &opt, const UnicodeString &k) {
// if (opt.count(k) == 0) {
// return {};
// }
// UErrorCode localErrorCode = U_ZERO_ERROR;
// const message2::FunctionValue *optVal = opt.at(k);
// if (optVal == nullptr) {
// return {};
// }
// const UnicodeString &formatted = optVal->formatToString(localErrorCode);
// if (U_SUCCESS(localErrorCode)) {
// return formatted;
// }
// const UnicodeString &original = optVal->unwrap().getString(localErrorCode);
// if (U_SUCCESS(localErrorCode)) {
// return original;
// }
// return {};
// }

// //TODO: This function is duplicated from messageformat2test_custom.cpp
// // but should it be a library function?
// static bool hasStringOption(const FunctionOptionsMap &opt, const UnicodeString &k,
// const UnicodeString &v) {
// return getStringOption(opt, k) == v;
// }
// class EnDateValue : public FunctionValue {
// public:
// UnicodeString formatToString(UErrorCode&) const override;
// EnDateValue();
// virtual ~EnDateValue();
// private:
// friend class EnDateFunction;

// UnicodeString formattedString;
// EnDateValue(const FunctionValue&, const FunctionOptions&, UErrorCode&);
// }; // class EnDateValue


// UnicodeString EnDateValue::formatToString(UErrorCode& status) const {
// (void) status;
// return formattedString;
// }
// EnDateValue::~EnDateValue() {}


// EnDateValue::EnDateValue(const FunctionValue& arg,
// const FunctionOptions& options,
// UErrorCode& errorCode) {
// if (U_FAILURE(errorCode)) {
// return;
// }
// innerValue = arg.unwrap();
// opts = options;

// const icu::message2::Formattable* toFormat = &innerValue;
// if (U_FAILURE(errorCode)) {
// errorCode = U_MF_OPERAND_MISMATCH_ERROR;
// return;
// }

// // FunctionOptionsMap opt = opts.getOptions();

// // bool useFormal = hasStringOption(opt, "formality", "formal");
// // UnicodeString length = getStringOption(opt, "length");
// // if (length.length() == 0) {
// // length = "short";
// // }

// const FormattableObject* fp = toFormat->getObject(errorCode);
// if (errorCode == U_ILLEGAL_ARGUMENT_ERROR) {
// errorCode = U_MF_FORMATTING_ERROR;
// return;
// }

// if (fp == nullptr || fp->tag() != u"person") {
// errorCode = U_MF_FORMATTING_ERROR;
// return;
// }
// const icu::message2::Formattable* p = static_cast<const icu::message2::Formattable*>(fp);

// p->

// if (length == "long") {
// formattedString += title;
// formattedString += " ";
// formattedString += firstName;
// formattedString += " ";
// formattedString += lastName;
// } else if (length == "medium") {
// if (useFormal) {
// formattedString += firstName;
// formattedString += " ";
// formattedString += lastName;
// } else {
// formattedString += title;
// formattedString += " ";
// formattedString += firstName;
// }
// } else if (useFormal) {
// // Default to "short" length
// formattedString += title;
// formattedString += " ";
// formattedString += lastName;
// } else {
// formattedString += firstName;
// }
// }
class EnDateFunction : public Function {
public:
EnDateFunction(UErrorCode& status);
LocalPointer<FunctionValue> call(const FunctionContext &, const FunctionValue &,
const FunctionOptions &, UErrorCode &) override;
virtual ~EnDateFunction();

private:
LocalPointer<Function> delegate;
};

EnDateFunction::EnDateFunction(UErrorCode& status)
: delegate(icu::message2::StandardFunctions::DateTime::date(status)) {

}

LocalPointer<FunctionValue> EnDateFunction::call(const FunctionContext &context,
const FunctionValue &arg,
const FunctionOptions &opts,
UErrorCode &errorCode) {
(void)context;

if (U_FAILURE(errorCode)) {
return LocalPointer<FunctionValue>();
}
FunctionContext enContext(Locale::getEnglish(), context.getDirection(), context.getID());

return delegate->call(enContext, arg, opts, errorCode);
}

EnDateFunction::~EnDateFunction() {}
} // namespace EnFunctionTest


void TestMessageFormat2::testSetFormatLocale() {
IcuTestErrorCode errorCode(*this, "testSetFormatLocale");
UParseError parseError;

std::map<UnicodeString, icu::message2::Formattable> argsBuilder;
argsBuilder["count"] = message2::Formattable((int64_t)13);
argsBuilder["name"] = message2::Formattable("US");
argsBuilder["birthday"] = message2::Formattable("1776-07-04");
icu::message2::MessageArguments args(argsBuilder, errorCode);
UnicodeString expectedResult(u"PASS 13 @ 7/4/76");

const UnicodeString orig_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");

{
// try with a modified pattern
// build the function registry
icu::message2::MFFunctionRegistry::Builder builder(errorCode);
MFFunctionRegistry functionRegistry =
builder
.adoptFunction(data_model::FunctionName("en_date"),
new EnFunctionTest::EnDateFunction(errorCode), errorCode)
.build();

UnicodeString modified_pattern = orig_pattern;
modified_pattern.findAndReplace(u":date", u":en_date");
icu::message2::MessageFormatter mf =
MessageFormatter::Builder(errorCode)
.setErrorHandlingBehavior(MessageFormatter::U_MF_BEST_EFFORT)
.setPattern(modified_pattern, parseError, errorCode)
.setFunctionRegistry(functionRegistry)
.setLocale(Locale("pl"))
.build(errorCode);

if (errorCode.errIfFailureAndReset("build")) {
errln("UParseError = %d:%d", parseError.line, parseError.offset);
return;
}

UnicodeString result = mf.formatToString(args, errorCode);
if (errorCode.errIfFailureAndReset("formatToString")) {
return;
}
assertEquals("testSetFormatLocale", expectedResult, result);

}
{
// try with the original pattern
// build the function registry
icu::message2::MFFunctionRegistry::Builder builder(errorCode);
MFFunctionRegistry functionRegistry =
builder
.adoptFunction(data_model::FunctionName("date"), // override
new EnFunctionTest::EnDateFunction(errorCode), errorCode)
.build();

icu::message2::MessageFormatter mf =
MessageFormatter::Builder(errorCode)
.setErrorHandlingBehavior(MessageFormatter::U_MF_BEST_EFFORT)
.setPattern(orig_pattern, parseError, errorCode)
.setFunctionRegistry(functionRegistry)
.setLocale(Locale("pl"))
.build(errorCode);

if (errorCode.errIfFailureAndReset("build")) {
errln("UParseError = %d:%d", parseError.line, parseError.offset);
return;
}

UnicodeString result = mf.formatToString(args, errorCode);
if (errorCode.errIfFailureAndReset("formatToString")) {
return;
}
assertEquals("testSetFormatLocale", expectedResult, result);

}
}

TestCase::~TestCase() {}
TestCase::Builder::~Builder() {}

Expand Down
1 change: 1 addition & 0 deletions icu4c/source/test/intltest/messageformat2test.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class TestMessageFormat2: public IntlTest {
void testBidiAPI(void);
void testAPI(void);
void testAPISimple(void);
void testSetFormatLocale(void);

private:
void jsonTestsFromFiles(IcuTestErrorCode&);
Expand Down
Loading