LibJS: Ensure relevant extension keys are included in ICU locale data

This is a normative change in the ECMA-402 spec. See:
7508197

In our implementation, we don't have the affected AOs directly, as we
delegate to ICU. So instead, we must ensure we provide ICU a locale with
the relevant extension keys present.
This commit is contained in:
Timothy Flynn 2025-03-17 16:24:09 -04:00 committed by Tim Flynn
commit 00d00b84d3
Notes: github-actions[bot] 2025-03-18 15:48:22 +00:00
17 changed files with 82 additions and 67 deletions

View file

@ -500,6 +500,8 @@ ResolvedLocale resolve_locale(ReadonlySpan<String> requested_locales, LocaleOpti
// 12. Let supportedKeywords be a new empty List.
Vector<Unicode::Keyword> supported_keywords;
Vector<Unicode::Keyword> icu_keywords;
// 13. For each element key of relevantExtensionKeys, do
for (auto const& key : relevant_extension_keys) {
// a. Let keyLocaleData be foundLocaleData.[[<key>]].
@ -574,10 +576,23 @@ ResolvedLocale resolve_locale(ReadonlySpan<String> requested_locales, LocaleOpti
if (supported_keyword.has_value())
supported_keywords.append(supported_keyword.release_value());
if (auto* value_string = value.get_pointer<String>())
icu_keywords.empend(MUST(String::from_utf8(key)), *value_string);
// m. Set result.[[<key>]] to value.
find_key_in_value(result, key) = move(value);
}
// AD-HOC: For ICU, we need to form a locale with all relevant extension keys present.
if (icu_keywords.is_empty()) {
result.icu_locale = found_locale;
} else {
auto locale_id = Unicode::parse_unicode_locale_id(found_locale);
VERIFY(locale_id.has_value());
result.icu_locale = insert_unicode_extension_and_canonicalize(locale_id.release_value(), {}, move(icu_keywords));
}
// 14. If supportedKeywords is not empty, then
if (!supported_keywords.is_empty()) {
auto locale_id = Unicode::parse_unicode_locale_id(found_locale);

View file

@ -38,6 +38,7 @@ struct MatchedLocale {
struct ResolvedLocale {
String locale;
String icu_locale;
LocaleKey ca; // [[Calendar]]
LocaleKey co; // [[Collation]]
LocaleKey hc; // [[HourCycle]]

View file

@ -47,32 +47,32 @@ static Optional<Unicode::DateTimeFormat const&> get_or_create_formatter(StringVi
Optional<Unicode::DateTimeFormat const&> DateTimeFormat::temporal_plain_date_formatter()
{
return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_plain_date_formatter, m_temporal_plain_date_format);
return get_or_create_formatter(m_icu_locale, m_temporal_time_zone, m_temporal_plain_date_formatter, m_temporal_plain_date_format);
}
Optional<Unicode::DateTimeFormat const&> DateTimeFormat::temporal_plain_year_month_formatter()
{
return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_plain_year_month_formatter, m_temporal_plain_year_month_format);
return get_or_create_formatter(m_icu_locale, m_temporal_time_zone, m_temporal_plain_year_month_formatter, m_temporal_plain_year_month_format);
}
Optional<Unicode::DateTimeFormat const&> DateTimeFormat::temporal_plain_month_day_formatter()
{
return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_plain_month_day_formatter, m_temporal_plain_month_day_format);
return get_or_create_formatter(m_icu_locale, m_temporal_time_zone, m_temporal_plain_month_day_formatter, m_temporal_plain_month_day_format);
}
Optional<Unicode::DateTimeFormat const&> DateTimeFormat::temporal_plain_time_formatter()
{
return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_plain_time_formatter, m_temporal_plain_time_format);
return get_or_create_formatter(m_icu_locale, m_temporal_time_zone, m_temporal_plain_time_formatter, m_temporal_plain_time_format);
}
Optional<Unicode::DateTimeFormat const&> DateTimeFormat::temporal_plain_date_time_formatter()
{
return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_plain_date_time_formatter, m_temporal_plain_date_time_format);
return get_or_create_formatter(m_icu_locale, m_temporal_time_zone, m_temporal_plain_date_time_formatter, m_temporal_plain_date_time_format);
}
Optional<Unicode::DateTimeFormat const&> DateTimeFormat::temporal_instant_formatter()
{
return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_instant_formatter, m_temporal_instant_format);
return get_or_create_formatter(m_icu_locale, m_temporal_time_zone, m_temporal_instant_formatter, m_temporal_instant_format);
}
// 11.5.5 FormatDateTimePattern ( dateTimeFormat, patternParts, x, rangeFormatOptions ), https://tc39.es/ecma402/#sec-formatdatetimepattern

View file

@ -38,6 +38,9 @@ public:
String const& locale() const { return m_locale; }
void set_locale(String locale) { m_locale = move(locale); }
String const& icu_locale() const { return m_icu_locale; }
void set_icu_locale(String icu_locale) { m_icu_locale = move(icu_locale); }
String const& calendar() const { return m_calendar; }
void set_calendar(String calendar) { m_calendar = move(calendar); }
@ -107,6 +110,7 @@ private:
GC::Ptr<NativeFunction> m_bound_format; // [[BoundFormat]]
// Non-standard. Stores the ICU date-time formatters for the Intl object's formatting options.
String m_icu_locale;
OwnPtr<Unicode::DateTimeFormat> m_formatter;
OwnPtr<Unicode::DateTimeFormat> m_temporal_plain_date_formatter;
OwnPtr<Unicode::DateTimeFormat> m_temporal_plain_year_month_formatter;

View file

@ -149,6 +149,7 @@ ThrowCompletionOr<GC::Ref<DateTimeFormat>> create_date_time_format(VM& vm, Funct
// 18. Set dateTimeFormat.[[Locale]] to r.[[Locale]].
date_time_format->set_locale(move(result.locale));
date_time_format->set_icu_locale(move(result.icu_locale));
// 19. Let resolvedCalendar be r.[[ca]].
// 20. Set dateTimeFormat.[[Calendar]] to resolvedCalendar.
@ -355,7 +356,7 @@ ThrowCompletionOr<GC::Ref<DateTimeFormat>> create_date_time_format(VM& vm, Funct
// d. Let styles be resolvedLocaleData.[[styles]].[[<resolvedCalendar>]].
// e. Let bestFormat be DateTimeStyleFormat(dateStyle, timeStyle, styles).
formatter = Unicode::DateTimeFormat::create_for_date_and_time_style(
date_time_format->locale(),
date_time_format->icu_locale(),
time_zone,
format_options.hour_cycle,
format_options.hour12,
@ -443,7 +444,7 @@ ThrowCompletionOr<GC::Ref<DateTimeFormat>> create_date_time_format(VM& vm, Funct
}
formatter = Unicode::DateTimeFormat::create_for_pattern_options(
date_time_format->locale(),
date_time_format->icu_locale(),
time_zone,
best_format);
}

View file

@ -89,7 +89,7 @@ ThrowCompletionOr<GC::Ref<Object>> DurationFormatConstructor::construct(Function
// 11. Let resolvedLocaleData be r.[[LocaleData]].
// 12. Let digitalFormat be resolvedLocaleData.[[DigitalFormat]].
auto digital_format = Unicode::digital_format(duration_format->locale());
auto digital_format = Unicode::digital_format(result.icu_locale);
// 13. Set durationFormat.[[HourMinuteSeparator]] to digitalFormat.[[HourMinuteSeparator]].
duration_format->set_hour_minute_separator(move(digital_format.hours_minutes_separator));

View file

@ -184,8 +184,7 @@ ThrowCompletionOr<GC::Ref<Object>> NumberFormatConstructor::construct(FunctionOb
// Non-standard, create an ICU number formatter for this Intl object.
auto formatter = Unicode::NumberFormat::create(
number_format->locale(),
number_format->numbering_system(),
result.icu_locale,
number_format->display_options(),
number_format->rounding_options());
number_format->set_formatter(move(formatter));

View file

@ -87,8 +87,7 @@ ThrowCompletionOr<GC::Ref<Object>> PluralRulesConstructor::construct(FunctionObj
// Non-standard, create an ICU number formatter for this Intl object.
auto formatter = Unicode::NumberFormat::create(
plural_rules->locale(),
{},
result.icu_locale,
{},
plural_rules->rounding_options());

View file

@ -112,7 +112,7 @@ ThrowCompletionOr<GC::Ref<Object>> RelativeTimeFormatConstructor::construct(Func
// 20. Let relativeTimeFormat.[[NumberFormat]] be ! Construct(%Intl.NumberFormat%, « locale »).
// 21. Let relativeTimeFormat.[[PluralRules]] be ! Construct(%Intl.PluralRules%, « locale »).
auto formatter = Unicode::RelativeTimeFormat::create(
relative_time_format->locale(),
result.icu_locale,
relative_time_format->style());
relative_time_format->set_formatter(move(formatter));