LibJS: Update AOs involved in locale resolution to the latest ECMA-402

There have been a number of changes to the locale resolution AOs that
we've fallen behind on. Mostly editorial, but includes one normative
change to canonicalize Unicode extension keywords in the Intl.Locale
constructor.
This commit is contained in:
Timothy Flynn 2024-06-18 10:13:30 -04:00 committed by Andreas Kling
parent 2c311448c7
commit 9c3a775395
Notes: sideshowbarker 2024-07-16 20:12:13 +09:00
19 changed files with 242 additions and 311 deletions

View file

@ -666,8 +666,6 @@ ErrorOr<void> print_intl_number_format(JS::PrintContext& print_context, JS::Intl
TRY(print_type(print_context, "Intl.NumberFormat"sv));
TRY(js_out(print_context, "\n locale: "));
TRY(print_value(print_context, JS::PrimitiveString::create(number_format.vm(), number_format.locale()), seen_objects));
TRY(js_out(print_context, "\n dataLocale: "));
TRY(print_value(print_context, JS::PrimitiveString::create(number_format.vm(), number_format.data_locale()), seen_objects));
TRY(js_out(print_context, "\n numberingSystem: "));
TRY(print_value(print_context, JS::PrimitiveString::create(number_format.vm(), number_format.numbering_system()), seen_objects));
TRY(js_out(print_context, "\n style: "));
@ -875,8 +873,6 @@ ErrorOr<void> print_intl_duration_format(JS::PrintContext& print_context, JS::In
TRY(print_type(print_context, "Intl.DurationFormat"sv));
out("\n locale: ");
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.locale()), seen_objects));
out("\n dataLocale: ");
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.data_locale()), seen_objects));
out("\n numberingSystem: ");
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.numbering_system()), seen_objects));
out("\n style: ");

View file

@ -16,7 +16,6 @@
#include <LibJS/Runtime/Intl/SingleUnitIdentifiers.h>
#include <LibJS/Runtime/VM.h>
#include <LibJS/Runtime/ValueInlines.h>
#include <LibLocale/Locale.h>
#include <LibLocale/UnicodeKeywords.h>
namespace JS::Intl {
@ -263,97 +262,75 @@ ThrowCompletionOr<Vector<String>> canonicalize_locale_list(VM& vm, Value locales
return seen;
}
// 9.2.2 BestAvailableLocale ( availableLocales, locale ), https://tc39.es/ecma402/#sec-bestavailablelocale
Optional<StringView> best_available_locale(StringView locale)
// 9.2.3 LookupMatchingLocaleByPrefix ( availableLocales, requestedLocales ), https://tc39.es/ecma402/#sec-lookupmatchinglocalebyprefix
Optional<MatchedLocale> lookup_matching_locale_by_prefix(ReadonlySpan<String> requested_locales)
{
// 1. Let candidate be locale.
StringView candidate = locale;
// 2. Repeat,
while (true) {
// a. If availableLocales contains candidate, return candidate.
if (::Locale::is_locale_available(candidate))
return candidate;
// b. Let pos be the character index of the last occurrence of "-" (U+002D) within candidate. If that character does not occur, return undefined.
auto pos = candidate.find_last('-');
if (!pos.has_value())
return {};
// c. If pos ≥ 2 and the character "-" occurs at index pos-2 of candidate, decrease pos by 2.
if ((*pos >= 2) && (candidate[*pos - 2] == '-'))
pos = *pos - 2;
// d. Let candidate be the substring of candidate from position 0, inclusive, to position pos, exclusive.
candidate = candidate.substring_view(0, *pos);
}
}
struct MatcherResult {
String locale;
Vector<::Locale::Extension> extensions {};
};
// 9.2.3 LookupMatcher ( availableLocales, requestedLocales ), https://tc39.es/ecma402/#sec-lookupmatcher
static MatcherResult lookup_matcher(Vector<String> const& requested_locales)
{
// 1. Let result be a new Record.
MatcherResult result {};
// 2. For each element locale of requestedLocales, do
for (auto const& locale : requested_locales) {
// 1. For each element locale of requestedLocales, do
for (auto locale : requested_locales) {
auto locale_id = ::Locale::parse_unicode_locale_id(locale);
VERIFY(locale_id.has_value());
// a. Let noExtensionsLocale be the String value that is locale with any Unicode locale extension sequences removed.
auto extensions = locale_id->remove_extension_type<::Locale::LocaleExtension>();
auto no_extensions_locale = locale_id->to_string();
// a. Let extension be empty.
Optional<::Locale::Extension> extension;
String locale_without_extension;
// b. Let availableLocale be ! BestAvailableLocale(availableLocales, noExtensionsLocale).
auto available_locale = best_available_locale(no_extensions_locale);
// b. If locale contains a Unicode locale extension sequence, then
if (auto extensions = locale_id->remove_extension_type<::Locale::LocaleExtension>(); !extensions.is_empty()) {
VERIFY(extensions.size() == 1);
// c. If availableLocale is not undefined, then
if (available_locale.has_value()) {
// i. Set result.[[locale]] to availableLocale.
result.locale = MUST(String::from_utf8(*available_locale));
// i. Set extension to the Unicode locale extension sequence of locale.
extension = extensions.take_first();
// ii. If locale and noExtensionsLocale are not the same String value, then
if (locale != no_extensions_locale) {
// 1. Let extension be the String value consisting of the substring of the Unicode locale extension sequence within locale.
// 2. Set result.[[extension]] to extension.
result.extensions.extend(move(extensions));
// ii. Set locale to the String value that is locale with any Unicode locale extension sequences removed.
locale = locale_id->to_string();
}
// c. Let prefix be locale.
StringView prefix { locale };
// d. Repeat, while prefix is not the empty String,
while (!prefix.is_empty()) {
// i. If availableLocales contains prefix, return the Record { [[locale]]: prefix, [[extension]]: extension }.
if (::Locale::is_locale_available(prefix))
return MatchedLocale { MUST(String::from_utf8(prefix)), move(extension) };
// ii. If prefix contains "-" (code unit 0x002D HYPHEN-MINUS), let pos be the index into prefix of the last
// occurrence of "-"; else let pos be 0.
auto position = prefix.find_last('-').value_or(0);
// iii. Repeat, while pos ≥ 2 and the substring of prefix from pos - 2 to pos - 1 is "-",
while (position >= 2 && prefix.substring_view(position - 2, 1) == '-') {
// 1. Set pos to pos - 2.
position -= 2;
}
// iii. Return result.
return result;
// iv. Set prefix to the substring of prefix from 0 to pos.
prefix = prefix.substring_view(0, position);
}
}
// 3. Let defLocale be ! DefaultLocale().
// 4. Set result.[[locale]] to defLocale.
result.locale = MUST(String::from_utf8(::Locale::default_locale()));
// 5. Return result.
return result;
// 2. Return undefined.
return {};
}
// 9.2.4 BestFitMatcher ( availableLocales, requestedLocales ), https://tc39.es/ecma402/#sec-bestfitmatcher
static MatcherResult best_fit_matcher(Vector<String> const& requested_locales)
// 9.2.4 LookupMatchingLocaleByBestFit ( availableLocales, requestedLocales ), https://tc39.es/ecma402/#sec-lookupmatchinglocalebybestfit
Optional<MatchedLocale> lookup_matching_locale_by_best_fit(ReadonlySpan<String> requested_locales)
{
// The algorithm is implementation dependent, but should produce results that a typical user of the requested locales would
// perceive as at least as good as those produced by the LookupMatcher abstract operation.
return lookup_matcher(requested_locales);
// The algorithm is implementation dependent, but should produce results that a typical user of the requested locales
// would consider at least as good as those produced by the LookupMatchingLocaleByPrefix algorithm.
return lookup_matching_locale_by_prefix(requested_locales);
}
// 9.2.6 InsertUnicodeExtensionAndCanonicalize ( locale, extension ), https://tc39.es/ecma402/#sec-insert-unicode-extension-and-canonicalize
String insert_unicode_extension_and_canonicalize(::Locale::LocaleID locale, ::Locale::LocaleExtension extension)
// 9.2.6 InsertUnicodeExtensionAndCanonicalize ( locale, attributes, keywords ), https://tc39.es/ecma402/#sec-insert-unicode-extension-and-canonicalize
String insert_unicode_extension_and_canonicalize(::Locale::LocaleID locale, Vector<String> attributes, Vector<::Locale::Keyword> keywords)
{
// Note: This implementation differs from the spec in how the extension is inserted. The spec assumes
// the input to this method is a string, and is written such that operations are performed on parts
// of that string. LibUnicode gives us the parsed locale in a structure, so we can mutate that
// of that string. LibLocale gives us the parsed locale in a structure, so we can mutate that
// structure directly.
locale.extensions.append(move(extension));
locale.extensions.append(::Locale::LocaleExtension { move(attributes), move(keywords) });
// 10. Return CanonicalizeUnicodeLocaleId(newLocale).
return JS::Intl::canonicalize_unicode_locale_id(locale.to_string());
}
@ -373,7 +350,7 @@ static auto& find_key_in_value(T& value, StringView key)
if (key == "nu"sv)
return value.nu;
// If you hit this point, you must add any missing keys from [[RelevantExtensionKeys]] to LocaleOptions and LocaleResult.
// If you hit this point, you must add any missing keys from [[RelevantExtensionKeys]] to LocaleOptions and ResolvedLocale.
VERIFY_NOT_REACHED();
}
@ -397,191 +374,153 @@ static Vector<LocaleKey> available_keyword_values(StringView locale, StringView
}
// 9.2.7 ResolveLocale ( availableLocales, requestedLocales, options, relevantExtensionKeys, localeData ), https://tc39.es/ecma402/#sec-resolvelocale
LocaleResult resolve_locale(Vector<String> const& requested_locales, LocaleOptions const& options, ReadonlySpan<StringView> relevant_extension_keys)
ResolvedLocale resolve_locale(ReadonlySpan<String> requested_locales, LocaleOptions const& options, ReadonlySpan<StringView> relevant_extension_keys)
{
static auto true_string = "true"_string;
// 1. Let matcher be options.[[localeMatcher]].
auto const& matcher = options.locale_matcher;
MatcherResult matcher_result;
Optional<MatchedLocale> matcher_result;
// 2. If matcher is "lookup", then
if (matcher.is_string() && (matcher.as_string().utf8_string_view()) == "lookup"sv) {
// a. Let r be ! LookupMatcher(availableLocales, requestedLocales).
matcher_result = lookup_matcher(requested_locales);
if (matcher.is_string() && matcher.as_string().utf8_string_view() == "lookup"sv) {
// a. Let r be LookupMatchingLocaleByPrefix(availableLocales, requestedLocales).
matcher_result = lookup_matching_locale_by_prefix(requested_locales);
}
// 3. Else,
else {
// a. Let r be ! BestFitMatcher(availableLocales, requestedLocales).
matcher_result = best_fit_matcher(requested_locales);
// a. Let r be LookupMatchingLocaleByBestFit(availableLocales, requestedLocales).
matcher_result = lookup_matching_locale_by_best_fit(requested_locales);
}
// 4. Let foundLocale be r.[[locale]].
auto found_locale = move(matcher_result.locale);
// 4. If r is undefined, set r to the Record { [[locale]]: DefaultLocale(), [[extension]]: empty }.
if (!matcher_result.has_value())
matcher_result = MatchedLocale { MUST(String::from_utf8(::Locale::default_locale())), {} };
// 5. Let result be a new Record.
LocaleResult result {};
// 5. Let foundLocale be r.[[locale]].
auto found_locale = move(matcher_result->locale);
// 6. Set result.[[dataLocale]] to foundLocale.
result.data_locale = found_locale;
// 6. Let foundLocaleData be localeData.[[<foundLocale>]].
// 7. Assert: Type(foundLocaleData) is Record.
// 8. Let result be a new Record.
// 9. Set result.[[LocaleData]] to foundLocaleData.
ResolvedLocale result {};
// 7. If r has an [[extension]] field, then
Vector<::Locale::Keyword> keywords;
for (auto& extension : matcher_result.extensions) {
if (!extension.has<::Locale::LocaleExtension>())
continue;
// a. Let components be ! UnicodeExtensionComponents(r.[[extension]]).
auto& components = extension.get<::Locale::LocaleExtension>();
// 10. If r.[[extension]] is not empty, then
if (matcher_result->extension.has_value()) {
// a. Let components be UnicodeExtensionComponents(r.[[extension]]).
auto& components = matcher_result->extension->get<::Locale::LocaleExtension>();
// b. Let keywords be components.[[Keywords]].
keywords = move(components.keywords);
break;
}
// 11. Else,
// a. Let keywords be a new empty List.
// 8. Let supportedExtension be "-u".
::Locale::LocaleExtension supported_extension {};
// 12. Let supportedKeywords be a new empty List.
Vector<::Locale::Keyword> supported_keywords;
// 9. For each element key of relevantExtensionKeys, do
// 13. For each element key of relevantExtensionKeys, do
for (auto const& key : relevant_extension_keys) {
// a. Let foundLocaleData be localeData.[[<foundLocale>]].
// b. Assert: Type(foundLocaleData) is Record.
// c. Let keyLocaleData be foundLocaleData.[[<key>]].
// d. Assert: Type(keyLocaleData) is List.
// a. Let keyLocaleData be foundLocaleData.[[<key>]].
// b. Assert: keyLocaleData is a List.
auto key_locale_data = available_keyword_values(found_locale, key);
// e. Let value be keyLocaleData[0].
// f. Assert: Type(value) is either String or Null.
// c. Let value be keyLocaleData[0].
// d. Assert: value is a String or value is null.
auto value = key_locale_data[0];
// g. Let supportedExtensionAddition be "".
Optional<::Locale::Keyword> supported_extension_addition {};
// e. Let supportedKeyword be empty.
Optional<::Locale::Keyword> supported_keyword;
// h. If r has an [[extension]] field, then
for (auto& entry : keywords) {
// i. If keywords contains an element whose [[Key]] is the same as key, then
if (entry.key != key)
continue;
// f. If keywords contains an element whose [[Key]] is key, then
if (auto entry = keywords.find_if([&](auto const& entry) { return entry.key == key; }); entry != keywords.end()) {
// i. Let entry be the element of keywords whose [[Key]] is key.
// ii. Let requestedValue be entry.[[Value]].
auto requested_value = entry->value;
// 1. Let entry be the element of keywords whose [[Key]] is the same as key.
// 2. Let requestedValue be entry.[[Value]].
auto requested_value = entry.value;
// 3. If requestedValue is not the empty String, then
// iii. If requestedValue is not the empty String, then
if (!requested_value.is_empty()) {
// a. If keyLocaleData contains requestedValue, then
// 1. If keyLocaleData contains requestedValue, then
if (key_locale_data.contains_slow(requested_value)) {
// i. Let value be requestedValue.
// a. Set value to requestedValue.
value = move(requested_value);
// ii. Let supportedExtensionAddition be the string-concatenation of "-", key, "-", and value.
supported_extension_addition = ::Locale::Keyword { MUST(String::from_utf8(key)), move(entry.value) };
// b. Set supportedKeyword to the Record { [[Key]]: key, [[Value]]: value }.
supported_keyword = ::Locale::Keyword { MUST(String::from_utf8(key)), move(entry->value) };
}
}
// 4. Else if keyLocaleData contains "true", then
// iv. Else if keyLocaleData contains "true", then
else if (key_locale_data.contains_slow(true_string)) {
// a. Let value be "true".
// 1. Set value to "true".
value = true_string;
// b. Let supportedExtensionAddition be the string-concatenation of "-" and key.
supported_extension_addition = ::Locale::Keyword { MUST(String::from_utf8(key)), {} };
// 2. Set supportedKeyword to the Record { [[Key]]: key, [[Value]]: "" }.
supported_keyword = ::Locale::Keyword { MUST(String::from_utf8(key)), {} };
}
break;
}
// i. If options has a field [[<key>]], then
// i. Let optionsValue be options.[[<key>]].
// ii. Assert: Type(optionsValue) is either String, Undefined, or Null.
// g. Assert: options has a field [[<key>]].
// h. Let optionsValue be options.[[<key>]].
// i. Assert: optionsValue is a String, or optionsValue is either undefined or null.
auto options_value = find_key_in_value(options, key);
// iii. If Type(optionsValue) is String, then
// j. If optionsValue is a String, then
if (auto* options_string = options_value.has_value() ? options_value->get_pointer<String>() : nullptr) {
// 1. Let optionsValue be the string optionsValue after performing the algorithm steps to transform Unicode extension values to canonical syntax per Unicode Technical Standard #35 LDML § 3.2.1 Canonical Unicode Locale Identifiers, treating key as ukey and optionsValue as uvalue productions.
// 2. Let optionsValue be the string optionsValue after performing the algorithm steps to replace Unicode extension values with their canonical form per Unicode Technical Standard #35 LDML § 3.2.1 Canonical Unicode Locale Identifiers, treating key as ukey and optionsValue as uvalue productions.
// i. Let ukey be the ASCII-lowercase of key.
// NOTE: `key` is always lowercase, and this step is likely to be removed:
// https://github.com/tc39/ecma402/pull/846#discussion_r1428263375
// ii. Set optionsValue to CanonicalizeUValue(ukey, optionsValue).
*options_string = ::Locale::canonicalize_unicode_extension_values(key, *options_string);
// 3. If optionsValue is the empty String, then
// iii. If optionsValue is the empty String, then
if (options_string->is_empty()) {
// a. Let optionsValue be "true".
// 1. Set optionsValue to "true".
*options_string = true_string;
}
}
// iv. If SameValue(optionsValue, value) is false and keyLocaleData contains optionsValue, then
// k. If SameValue(optionsValue, value) is false and keyLocaleData contains optionsValue, then
if (options_value.has_value() && (options_value != value) && key_locale_data.contains_slow(*options_value)) {
// 1. Let value be optionsValue.
// i. Set value to optionsValue.
value = options_value.release_value();
// 2. Let supportedExtensionAddition be "".
supported_extension_addition.clear();
// ii. Set supportedKeyword to empty.
supported_keyword.clear();
}
// j. Set result.[[<key>]] to value.
find_key_in_value(result, key) = move(value);
// l. If supportedKeyword is not empty, append supportedKeyword to supportedKeywords.
if (supported_keyword.has_value())
supported_keywords.append(supported_keyword.release_value());
// k. Set supportedExtension to the string-concatenation of supportedExtension and supportedExtensionAddition.
if (supported_extension_addition.has_value())
supported_extension.keywords.append(supported_extension_addition.release_value());
// m. Set result.[[<key>]] to value.
find_key_in_value(result, key) = move(value);
}
// 10. If supportedExtension is not "-u", then
if (!supported_extension.keywords.is_empty()) {
// 14. If supportedKeywords is not empty, then
if (!supported_keywords.is_empty()) {
auto locale_id = ::Locale::parse_unicode_locale_id(found_locale);
VERIFY(locale_id.has_value());
// a. Set foundLocale to InsertUnicodeExtensionAndCanonicalize(foundLocale, supportedExtension).
found_locale = insert_unicode_extension_and_canonicalize(locale_id.release_value(), move(supported_extension));
// a. Let supportedAttributes be a new empty List.
// b. Set foundLocale to InsertUnicodeExtensionAndCanonicalize(foundLocale, supportedAttributes, supportedKeywords).
found_locale = insert_unicode_extension_and_canonicalize(locale_id.release_value(), {}, move(supported_keywords));
}
// 11. Set result.[[locale]] to foundLocale.
// 15. Set result.[[locale]] to foundLocale.
result.locale = move(found_locale);
// 12. Return result.
// 16. Return result.
return result;
}
// 9.2.8 LookupSupportedLocales ( availableLocales, requestedLocales ), https://tc39.es/ecma402/#sec-lookupsupportedlocales
static Vector<String> lookup_supported_locales(Vector<String> const& requested_locales)
{
// 1. Let subset be a new empty List.
Vector<String> subset;
// 2. For each element locale of requestedLocales, do
for (auto const& locale : requested_locales) {
auto locale_id = ::Locale::parse_unicode_locale_id(locale);
VERIFY(locale_id.has_value());
// a. Let noExtensionsLocale be the String value that is locale with any Unicode locale extension sequences removed.
locale_id->remove_extension_type<::Locale::LocaleExtension>();
auto no_extensions_locale = locale_id->to_string();
// b. Let availableLocale be ! BestAvailableLocale(availableLocales, noExtensionsLocale).
auto available_locale = best_available_locale(no_extensions_locale);
// c. If availableLocale is not undefined, append locale to the end of subset.
if (available_locale.has_value())
subset.append(locale);
}
// 3. Return subset.
return subset;
}
// 9.2.9 BestFitSupportedLocales ( availableLocales, requestedLocales ), https://tc39.es/ecma402/#sec-bestfitsupportedlocales
static Vector<String> best_fit_supported_locales(Vector<String> const& requested_locales)
{
// The BestFitSupportedLocales abstract operation returns the subset of the provided BCP 47
// language priority list requestedLocales for which availableLocales has a matching locale
// when using the Best Fit Matcher algorithm. Locales appear in the same order in the returned
// list as in requestedLocales. The steps taken are implementation dependent.
// :yakbrain:
return lookup_supported_locales(requested_locales);
}
// 9.2.10 SupportedLocales ( availableLocales, requestedLocales, options ), https://tc39.es/ecma402/#sec-supportedlocales
ThrowCompletionOr<Array*> supported_locales(VM& vm, Vector<String> const& requested_locales, Value options)
// 9.2.8 FilterLocales ( availableLocales, requestedLocales, options ), https://tc39.es/ecma402/#sec-lookupsupportedlocales
ThrowCompletionOr<Array*> filter_locales(VM& vm, ReadonlySpan<String> requested_locales, Value options)
{
auto& realm = *vm.current_realm();
@ -591,24 +530,41 @@ ThrowCompletionOr<Array*> supported_locales(VM& vm, Vector<String> const& reques
// 2. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit").
auto matcher = TRY(get_option(vm, *options_object, vm.names.localeMatcher, OptionType::String, { "lookup"sv, "best fit"sv }, "best fit"sv));
Vector<String> supported_locales;
// 3. Let subset be a new empty List.
Vector<String> subset;
// 3. If matcher is "best fit", then
if (matcher.as_string().utf8_string_view() == "best fit"sv) {
// a. Let supportedLocales be BestFitSupportedLocales(availableLocales, requestedLocales).
supported_locales = best_fit_supported_locales(requested_locales);
}
// 4. Else,
else {
// a. Let supportedLocales be LookupSupportedLocales(availableLocales, requestedLocales).
supported_locales = lookup_supported_locales(requested_locales);
// 4. For each element locale of requestedLocales, do
for (auto const& locale : requested_locales) {
auto locale_id = ::Locale::parse_unicode_locale_id(locale);
VERIFY(locale_id.has_value());
// a. Let noExtensionsLocale be the String value that is locale with any Unicode locale extension sequences removed.
locale_id->remove_extension_type<::Locale::LocaleExtension>();
auto no_extensions_locale = locale_id->to_string();
Optional<MatchedLocale> match;
// b. If matcher is "lookup", then
if (matcher.as_string().utf8_string_view() == "lookup"sv) {
// i. Let match be LookupMatchingLocaleByPrefix(availableLocales, noExtensionsLocale).
match = lookup_matching_locale_by_prefix({ { no_extensions_locale } });
}
// c. Else,
else {
// i. Let match be LookupMatchingLocaleByBestFit(availableLocales, noExtensionsLocale).
match = lookup_matching_locale_by_best_fit({ { no_extensions_locale } });
}
// d. If match is not undefined, append locale to subset.
if (match.has_value())
subset.append(locale);
}
// 5. Return CreateArrayFromList(supportedLocales).
return Array::create_from<String>(realm, supported_locales, [&vm](auto& locale) { return PrimitiveString::create(vm, move(locale)); }).ptr();
// 5. Return CreateArrayFromList(subset).
return Array::create_from<String>(realm, subset, [&vm](auto& locale) { return PrimitiveString::create(vm, move(locale)); }).ptr();
}
// 9.2.12 CoerceOptionsToObject ( options ), https://tc39.es/ecma402/#sec-coerceoptionstoobject
// 9.2.10 CoerceOptionsToObject ( options ), https://tc39.es/ecma402/#sec-coerceoptionstoobject
ThrowCompletionOr<Object*> coerce_options_to_object(VM& vm, Value options)
{
auto& realm = *vm.current_realm();
@ -623,9 +579,9 @@ ThrowCompletionOr<Object*> coerce_options_to_object(VM& vm, Value options)
return TRY(options.to_object(vm)).ptr();
}
// NOTE: 9.2.13 GetOption has been removed and is being pulled in from ECMA-262 in the Temporal proposal.
// NOTE: 9.2.11 GetOption has been removed and is being pulled in from ECMA-262 in the Temporal proposal.
// 9.2.14 GetBooleanOrStringNumberFormatOption ( options, property, stringValues, fallback ), https://tc39.es/ecma402/#sec-getbooleanorstringnumberformatoption
// 9.2.12 GetBooleanOrStringNumberFormatOption ( options, property, stringValues, fallback ), https://tc39.es/ecma402/#sec-getbooleanorstringnumberformatoption
ThrowCompletionOr<StringOrBoolean> get_boolean_or_string_number_format_option(VM& vm, Object const& options, PropertyKey const& property, ReadonlySpan<StringView> string_values, StringOrBoolean fallback)
{
// 1. Let value be ? Get(options, property).
@ -655,7 +611,7 @@ ThrowCompletionOr<StringOrBoolean> get_boolean_or_string_number_format_option(VM
return StringOrBoolean { *it };
}
// 9.2.15 DefaultNumberOption ( value, minimum, maximum, fallback ), https://tc39.es/ecma402/#sec-defaultnumberoption
// 9.2.13 DefaultNumberOption ( value, minimum, maximum, fallback ), https://tc39.es/ecma402/#sec-defaultnumberoption
ThrowCompletionOr<Optional<int>> default_number_option(VM& vm, Value value, int minimum, int maximum, Optional<int> fallback)
{
// 1. If value is undefined, return fallback.
@ -673,7 +629,7 @@ ThrowCompletionOr<Optional<int>> default_number_option(VM& vm, Value value, int
return floor(value.as_double());
}
// 9.2.16 GetNumberOption ( options, property, minimum, maximum, fallback ), https://tc39.es/ecma402/#sec-getnumberoption
// 9.2.14 GetNumberOption ( options, property, minimum, maximum, fallback ), https://tc39.es/ecma402/#sec-getnumberoption
ThrowCompletionOr<Optional<int>> get_number_option(VM& vm, Object const& options, PropertyKey const& property, int minimum, int maximum, Optional<int> fallback)
{
// 1. Assert: Type(options) is Object.

View file

@ -14,7 +14,7 @@
#include <LibJS/Runtime/Completion.h>
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
#include <LibJS/Runtime/Value.h>
#include <LibLocale/Forward.h>
#include <LibLocale/Locale.h>
namespace JS::Intl {
@ -31,9 +31,13 @@ struct LocaleOptions {
Optional<LocaleKey> nu; // [[NumberingSystem]]
};
struct LocaleResult {
struct MatchedLocale {
String locale;
Optional<::Locale::Extension> extension;
};
struct ResolvedLocale {
String locale;
String data_locale;
LocaleKey ca; // [[Calendar]]
LocaleKey co; // [[Collation]]
LocaleKey hc; // [[HourCycle]]
@ -49,10 +53,11 @@ String canonicalize_unicode_locale_id(StringView locale);
bool is_well_formed_currency_code(StringView currency);
bool is_well_formed_unit_identifier(StringView unit_identifier);
ThrowCompletionOr<Vector<String>> canonicalize_locale_list(VM&, Value locales);
Optional<StringView> best_available_locale(StringView locale);
String insert_unicode_extension_and_canonicalize(::Locale::LocaleID locale_id, ::Locale::LocaleExtension extension);
LocaleResult resolve_locale(Vector<String> const& requested_locales, LocaleOptions const& options, ReadonlySpan<StringView> relevant_extension_keys);
ThrowCompletionOr<Array*> supported_locales(VM&, Vector<String> const& requested_locales, Value options);
Optional<MatchedLocale> lookup_matching_locale_by_prefix(ReadonlySpan<String> requested_locales);
Optional<MatchedLocale> lookup_matching_locale_by_best_fit(ReadonlySpan<String> requested_locales);
String insert_unicode_extension_and_canonicalize(::Locale::LocaleID locale_id, Vector<String> attributes, Vector<::Locale::Keyword> keywords);
ResolvedLocale resolve_locale(ReadonlySpan<String> requested_locales, LocaleOptions const& options, ReadonlySpan<StringView> relevant_extension_keys);
ThrowCompletionOr<Array*> filter_locales(VM& vm, ReadonlySpan<String> requested_locales, Value options);
ThrowCompletionOr<Object*> coerce_options_to_object(VM&, Value options);
ThrowCompletionOr<StringOrBoolean> get_boolean_or_string_number_format_option(VM& vm, Object const& options, PropertyKey const& property, ReadonlySpan<StringView> string_values, StringOrBoolean fallback);
ThrowCompletionOr<Optional<int>> default_number_option(VM&, Value value, int minimum, int maximum, Optional<int> fallback);

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022-2023, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2022-2024, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -199,8 +199,8 @@ JS_DEFINE_NATIVE_FUNCTION(CollatorConstructor::supported_locales_of)
// 2. Let requestedLocales be ? CanonicalizeLocaleList(locales).
auto requested_locales = TRY(canonicalize_locale_list(vm, locales));
// 3. Return ? SupportedLocales(availableLocales, requestedLocales, options).
return TRY(supported_locales(vm, requested_locales, options));
// 3. Return ? FilterLocales(availableLocales, requestedLocales, options).
return TRY(filter_locales(vm, requested_locales, options));
}
}

View file

@ -39,9 +39,6 @@ public:
String const& locale() const { return m_locale; }
void set_locale(String locale) { m_locale = move(locale); }
String const& data_locale() const { return m_data_locale; }
void set_data_locale(String data_locale) { m_data_locale = move(data_locale); }
String const& calendar() const { return m_calendar; }
void set_calendar(String calendar) { m_calendar = move(calendar); }
@ -80,8 +77,6 @@ private:
Optional<::Locale::DateTimeStyle> m_time_style; // [[TimeStyle]]
GCPtr<NativeFunction> m_bound_format; // [[BoundFormat]]
String m_data_locale;
// Non-standard. Stores the ICU date-time formatter for the Intl object's formatting options.
OwnPtr<::Locale::DateTimeFormat> m_formatter;
};

View file

@ -77,8 +77,8 @@ JS_DEFINE_NATIVE_FUNCTION(DateTimeFormatConstructor::supported_locales_of)
// 2. Let requestedLocales be ? CanonicalizeLocaleList(locales).
auto requested_locales = TRY(canonicalize_locale_list(vm, locales));
// 3. Return ? SupportedLocales(availableLocales, requestedLocales, options).
return TRY(supported_locales(vm, requested_locales, options));
// 3. Return ? FilterLocales(availableLocales, requestedLocales, options).
return TRY(filter_locales(vm, requested_locales, options));
}
// 11.1.2 CreateDateTimeFormat ( newTarget, locales, options, required, defaults ), https://tc39.es/ecma402/#sec-createdatetimeformat
@ -160,10 +160,6 @@ ThrowCompletionOr<NonnullGCPtr<DateTimeFormat>> create_date_time_format(VM& vm,
date_time_format->set_numbering_system(move(*resolved_numbering_system));
// 23. Let dataLocale be r.[[dataLocale]].
auto data_locale = move(result.data_locale);
// Non-standard, the data locale is needed for LibUnicode lookups while formatting.
date_time_format->set_data_locale(data_locale);
// 24. Let dataLocaleData be localeData.[[<dataLocale>]].
Optional<::Locale::HourCycle> hour_cycle_value;
@ -188,7 +184,7 @@ ThrowCompletionOr<NonnullGCPtr<DateTimeFormat>> create_date_time_format(VM& vm,
// c. If hc is null, set hc to dataLocaleData.[[hourCycle]].
if (!hour_cycle_value.has_value())
hour_cycle_value = ::Locale::default_hour_cycle(data_locale);
hour_cycle_value = ::Locale::default_hour_cycle(date_time_format->locale());
}
// 28. Set dateTimeFormat.[[HourCycle]] to hc.

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021-2023, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2021-2024, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -147,8 +147,8 @@ JS_DEFINE_NATIVE_FUNCTION(DisplayNamesConstructor::supported_locales_of)
// 2. Let requestedLocales be ? CanonicalizeLocaleList(locales).
auto requested_locales = TRY(canonicalize_locale_list(vm, locales));
// 3. Return ? SupportedLocales(availableLocales, requestedLocales, options).
return TRY(supported_locales(vm, requested_locales, options));
// 3. Return ? FilterLocales(availableLocales, requestedLocales, options).
return TRY(filter_locales(vm, requested_locales, options));
}
}

View file

@ -57,9 +57,6 @@ public:
void set_locale(String locale) { m_locale = move(locale); }
String const& locale() const { return m_locale; }
void set_data_locale(String data_locale) { m_data_locale = move(data_locale); }
String const& data_locale() const { return m_data_locale; }
void set_numbering_system(String numbering_system) { m_numbering_system = move(numbering_system); }
String const& numbering_system() const { return m_numbering_system; }
@ -173,7 +170,6 @@ private:
static StringView display_to_string(Display);
String m_locale; // [[Locale]]
String m_data_locale; // [[DataLocale]]
String m_numbering_system; // [[NumberingSystem]]
String m_hours_minutes_separator; // [[HourMinutesSeparator]]
String m_minutes_seconds_separator; // [[MinutesSecondsSeparator]]

View file

@ -89,12 +89,12 @@ ThrowCompletionOr<NonnullGCPtr<Object>> DurationFormatConstructor::construct(Fun
duration_format->set_locale(move(locale));
// 12. Set durationFormat.[[DataLocale]] to r.[[dataLocale]].
duration_format->set_data_locale(move(result.data_locale));
// NOTE: The [[dataLocale]] internal slot no longer exists.
// 13. Let dataLocale be durationFormat.[[DataLocale]].
// 14. Let dataLocaleData be durationFormat.[[LocaleData]].[[<dataLocale>]].
// 15. Let digitalFormat be dataLocaleData.[[DigitalFormat]].
auto digital_format = ::Locale::digital_format(duration_format->data_locale());
auto digital_format = ::Locale::digital_format(duration_format->locale());
// 16. Let twoDigitHours be digitalFormat.[[TwoDigitHours]].
// 17. Set durationFormat.[[TwoDigitHours]] to twoDigitHours.
@ -172,8 +172,8 @@ JS_DEFINE_NATIVE_FUNCTION(DurationFormatConstructor::supported_locales_of)
// 2. Let requestedLocales be ? CanonicalizeLocaleList(locales).
auto requested_locales = TRY(canonicalize_locale_list(vm, locales));
// 3. Return ? SupportedLocales(availableLocales, requestedLocales, options).
return TRY(supported_locales(vm, requested_locales, options));
// 3. Return ? FilterLocales(availableLocales, requestedLocales, options).
return TRY(filter_locales(vm, requested_locales, options));
}
}

View file

@ -112,8 +112,8 @@ JS_DEFINE_NATIVE_FUNCTION(ListFormatConstructor::supported_locales_of)
// 2. Let requestedLocales be ? CanonicalizeLocaleList(locales).
auto requested_locales = TRY(canonicalize_locale_list(vm, locales));
// 3. Return ? SupportedLocales(availableLocales, requestedLocales, options).
return TRY(supported_locales(vm, requested_locales, options));
// 3. Return ? FilterLocales(availableLocales, requestedLocales, options).
return TRY(filter_locales(vm, requested_locales, options));
}
}

View file

@ -111,15 +111,13 @@ static ThrowCompletionOr<String> apply_options_to_tag(VM& vm, StringView tag, Ob
// 14.1.3 ApplyUnicodeExtensionToTag ( tag, options, relevantExtensionKeys ), https://tc39.es/ecma402/#sec-apply-unicode-extension-to-tag
static LocaleAndKeys apply_unicode_extension_to_tag(StringView tag, LocaleAndKeys options, ReadonlySpan<StringView> relevant_extension_keys)
{
// 1. Assert: Type(tag) is String.
// 2. Assert: tag matches the unicode_locale_id production.
auto locale_id = ::Locale::parse_unicode_locale_id(tag);
VERIFY(locale_id.has_value());
Vector<String> attributes;
Vector<::Locale::Keyword> keywords;
// 3. If tag contains a substring that is a Unicode locale extension sequence, then
// 1. If tag contains a substring that is a Unicode locale extension sequence, then
for (auto& extension : locale_id->extensions) {
if (!extension.has<::Locale::LocaleExtension>())
continue;
@ -134,7 +132,7 @@ static LocaleAndKeys apply_unicode_extension_to_tag(StringView tag, LocaleAndKey
break;
}
// 4. Else,
// 2. Else,
// a. Let attributes be a new empty List.
// b. Let keywords be a new empty List.
@ -156,69 +154,67 @@ static LocaleAndKeys apply_unicode_extension_to_tag(StringView tag, LocaleAndKey
VERIFY_NOT_REACHED();
};
// 5. Let result be a new Record.
// 3. Let result be a new Record.
LocaleAndKeys result {};
// 6. For each element key of relevantExtensionKeys, do
// 4. For each element key of relevantExtensionKeys, do
for (auto const& key : relevant_extension_keys) {
// a. Let value be undefined.
Optional<String> value {};
::Locale::Keyword* entry = nullptr;
// b. If keywords contains an element whose [[Key]] is the same as key, then
Optional<String> value;
// a. If keywords contains an element whose [[Key]] is key, then
if (auto it = keywords.find_if([&](auto const& k) { return key == k.key; }); it != keywords.end()) {
// i. Let entry be the element of keywords whose [[Key]] is the same as key.
// i. Let entry be the element of keywords whose [[Key]] is key.
entry = &(*it);
// ii. Let value be entry.[[Value]].
value = entry->value;
}
// c. Else,
// b. Else,
// i. Let entry be empty.
// ii. Let value be undefined.
// d. Assert: options has a field [[<key>]].
// e. Let optionsValue be options.[[<key>]].
auto options_value = field_from_key(options, key);
// c. Assert: options has a field [[<key>]].
// d. Let overrideValue be options.[[<key>]].
auto override_value = field_from_key(options, key);
// f. If optionsValue is not undefined, then
if (options_value.has_value()) {
// i. Assert: Type(optionsValue) is String.
// ii. Let value be optionsValue.
value = options_value.release_value();
// e. If overrideValue is not undefined, then
if (override_value.has_value()) {
// i. Set value to CanonicalizeUValue(key, overrideValue).
value = ::Locale::canonicalize_unicode_extension_values(key, *override_value);
// iii. If entry is not empty, then
// ii. If entry is not empty, then
if (entry != nullptr) {
// 1. Set entry.[[Value]] to value.
entry->value = *value;
}
// iv. Else,
// iii. Else,
else {
// 1. Append the Record { [[Key]]: key, [[Value]]: value } to keywords.
keywords.append({ MUST(String::from_utf8(key)), *value });
keywords.empend(MUST(String::from_utf8(key)), *value);
}
}
// g. Set result.[[<key>]] to value.
// f. Set result.[[<key>]] to value.
field_from_key(result, key) = move(value);
}
// 7. Let locale be the String value that is tag with any Unicode locale extension sequences removed.
// 5. Let locale be the String value that is tag with any Unicode locale extension sequences removed.
locale_id->remove_extension_type<::Locale::LocaleExtension>();
auto locale = locale_id->to_string();
// 8. Let newExtension be a Unicode BCP 47 U Extension based on attributes and keywords.
::Locale::LocaleExtension new_extension { move(attributes), move(keywords) };
// 9. If newExtension is not the empty String, then
if (!new_extension.attributes.is_empty() || !new_extension.keywords.is_empty()) {
// a. Let locale be ! InsertUnicodeExtensionAndCanonicalize(locale, newExtension).
locale = insert_unicode_extension_and_canonicalize(locale_id.release_value(), move(new_extension));
// 6. If attributes is not empty or keywords is not empty, then
if (!attributes.is_empty() || !keywords.is_empty()) {
// a. Set result.[[locale]] to InsertUnicodeExtensionAndCanonicalize(locale, attributes, keywords).
result.locale = insert_unicode_extension_and_canonicalize(locale_id.release_value(), move(attributes), move(keywords));
}
// 7. Else,
else {
// a. Set result.[[locale]] to CanonicalizeUnicodeLocaleId(locale).
result.locale = canonicalize_unicode_locale_id(locale);
}
// 10. Set result.[[locale]] to locale.
result.locale = move(locale);
// 11. Return result.
// 8. Return result.
return result;
}

View file

@ -34,9 +34,6 @@ public:
String const& locale() const { return m_locale; }
void set_locale(String locale) { m_locale = move(locale); }
String const& data_locale() const { return m_data_locale; }
void set_data_locale(String data_locale) { m_data_locale = move(data_locale); }
int min_integer_digits() const { return m_min_integer_digits; }
void set_min_integer_digits(int min_integer_digits) { m_min_integer_digits = min_integer_digits; }
@ -85,7 +82,6 @@ protected:
private:
String m_locale; // [[Locale]]
String m_data_locale; // [[DataLocale]]
int m_min_integer_digits { 0 }; // [[MinimumIntegerDigits]]
Optional<int> m_min_fraction_digits {}; // [[MinimumFractionDigits]]
Optional<int> m_max_fraction_digits {}; // [[MaximumFractionDigits]]
@ -173,7 +169,6 @@ private:
virtual void visit_edges(Visitor&) override;
String m_locale; // [[Locale]]
String m_data_locale; // [[DataLocale]]
String m_numbering_system; // [[NumberingSystem]]
::Locale::NumberFormatStyle m_style; // [[Style]]
Optional<String> m_currency; // [[Currency]]

View file

@ -76,8 +76,8 @@ JS_DEFINE_NATIVE_FUNCTION(NumberFormatConstructor::supported_locales_of)
// 2. Let requestedLocales be ? CanonicalizeLocaleList(locales).
auto requested_locales = TRY(canonicalize_locale_list(vm, locales));
// 3. Return ? SupportedLocales(availableLocales, requestedLocales, options).
return TRY(supported_locales(vm, requested_locales, options));
// 3. Return ? FilterLocales(availableLocales, requestedLocales, options).
return TRY(filter_locales(vm, requested_locales, options));
}
// 15.1.2 InitializeNumberFormat ( numberFormat, locales, options ), https://tc39.es/ecma402/#sec-initializenumberformat
@ -118,8 +118,7 @@ ThrowCompletionOr<NonnullGCPtr<NumberFormat>> initialize_number_format(VM& vm, N
// 11. Set numberFormat.[[Locale]] to r.[[locale]].
number_format.set_locale(move(result.locale));
// 12. Set numberFormat.[[DataLocale]] to r.[[dataLocale]].
number_format.set_data_locale(move(result.data_locale));
// 12. Set numberFormat.[[LocaleData]] to r.[[LocaleData]].
// 13. Set numberFormat.[[NumberingSystem]] to r.[[nu]].
if (auto* resolved_numbering_system = result.nu.get_pointer<String>())

View file

@ -77,9 +77,6 @@ ThrowCompletionOr<NonnullGCPtr<Object>> PluralRulesConstructor::construct(Functi
// 10. Set pluralRules.[[Locale]] to r.[[locale]].
plural_rules->set_locale(move(result.locale));
// Non-standard, the data locale is used by our NumberFormat implementation.
plural_rules->set_data_locale(move(result.data_locale));
// 11. Let t be ? GetOption(options, "type", string, « "cardinal", "ordinal" », "cardinal").
auto type = TRY(get_option(vm, *options, vm.names.type, OptionType::String, AK::Array { "cardinal"sv, "ordinal"sv }, "cardinal"sv));
@ -114,8 +111,8 @@ JS_DEFINE_NATIVE_FUNCTION(PluralRulesConstructor::supported_locales_of)
// 2. Let requestedLocales be ? CanonicalizeLocaleList(locales).
auto requested_locales = TRY(canonicalize_locale_list(vm, locales));
// 3. Return ? SupportedLocales(availableLocales, requestedLocales, options).
return TRY(supported_locales(vm, requested_locales, options));
// 3. Return ? FilterLocales(availableLocales, requestedLocales, options).
return TRY(filter_locales(vm, requested_locales, options));
}
}

View file

@ -35,9 +35,6 @@ public:
String const& locale() const { return m_locale; }
void set_locale(String locale) { m_locale = move(locale); }
String const& data_locale() const { return m_data_locale; }
void set_data_locale(String data_locale) { m_data_locale = move(data_locale); }
String const& numbering_system() const { return m_numbering_system; }
void set_numbering_system(String numbering_system) { m_numbering_system = move(numbering_system); }
@ -56,7 +53,6 @@ private:
explicit RelativeTimeFormat(Object& prototype);
String m_locale; // [[Locale]]
String m_data_locale; // [[DataLocale]]
String m_numbering_system; // [[NumberingSystem]]
::Locale::Style m_style { ::Locale::Style::Long }; // [[Style]]
::Locale::NumericDisplay m_numeric { ::Locale::NumericDisplay::Always }; // [[Numeric]]

View file

@ -92,7 +92,6 @@ ThrowCompletionOr<NonnullGCPtr<Object>> RelativeTimeFormatConstructor::construct
relative_time_format->set_locale(locale);
// 14. Set relativeTimeFormat.[[LocaleData]] to r.[[LocaleData]].
relative_time_format->set_data_locale(move(result.data_locale));
// 15. Set relativeTimeFormat.[[NumberingSystem]] to r.[[nu]].
if (auto* resolved_numbering_system = result.nu.get_pointer<String>())
@ -132,8 +131,8 @@ JS_DEFINE_NATIVE_FUNCTION(RelativeTimeFormatConstructor::supported_locales_of)
// 2. Let requestedLocales be ? CanonicalizeLocaleList(locales).
auto requested_locales = TRY(canonicalize_locale_list(vm, locales));
// 3. Return ? SupportedLocales(availableLocales, requestedLocales, options).
return TRY(supported_locales(vm, requested_locales, options));
// 3. Return ? FilterLocales(availableLocales, requestedLocales, options).
return TRY(filter_locales(vm, requested_locales, options));
}
}

View file

@ -1,6 +1,6 @@
/*
* Copyright (c) 2022, Idan Horowitz <idan.horowitz@serenityos.org>
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2023-2024, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -100,8 +100,8 @@ JS_DEFINE_NATIVE_FUNCTION(SegmenterConstructor::supported_locales_of)
// 2. Let requestedLocales be ? CanonicalizeLocaleList(locales).
auto requested_locales = TRY(canonicalize_locale_list(vm, locales));
// 3. Return ? SupportedLocales(availableLocales, requestedLocales, options).
return TRY(supported_locales(vm, requested_locales, options));
// 3. Return ? FilterLocales(availableLocales, requestedLocales, options).
return TRY(filter_locales(vm, requested_locales, options));
}
}

View file

@ -1275,17 +1275,16 @@ static ThrowCompletionOr<String> transform_case(VM& vm, String const& string, Va
}
VERIFY(requested_locale.has_value());
// 4. Let noExtensionsLocale be the String value that is requestedLocale with any Unicode locale extension sequences (6.2.1) removed.
// 4. Let noExtensionsLocale be the String value that is requestedLocale with any Unicode locale extension sequences removed.
requested_locale->remove_extension_type<Locale::LocaleExtension>();
auto no_extensions_locale = requested_locale->to_string();
// 5. Let availableLocales be a List with language tags that includes the languages for which the Unicode Character Database contains language sensitive case mappings. Implementations may add additional language tags if they support case mapping for additional locales.
// 6. Let locale be ! BestAvailableLocale(availableLocales, noExtensionsLocale).
auto locale = Intl::best_available_locale(no_extensions_locale);
// 6. Let match be LookupMatchingLocaleByPrefix(availableLocales, noExtensionsLocale).
auto match = Intl::lookup_matching_locale_by_prefix({ { no_extensions_locale } });
// 7. If locale is undefined, set locale to "und".
if (!locale.has_value())
locale = "und"sv;
// 7. If match is not undefined, let locale be match.[[locale]]; else let locale be "und".
StringView locale = match.has_value() ? match->locale : "und"sv;
// 8. Let codePoints be StringToCodePoints(S).
@ -1295,13 +1294,13 @@ static ThrowCompletionOr<String> transform_case(VM& vm, String const& string, Va
// 9. If targetCase is lower, then
case TargetCase::Lower:
// a. Let newCodePoints be a List whose elements are the result of a lowercase transformation of codePoints according to an implementation-derived algorithm using locale or the Unicode Default Case Conversion algorithm.
new_code_points = MUST(string.to_lowercase(*locale));
new_code_points = MUST(string.to_lowercase(locale));
break;
// 10. Else,
case TargetCase::Upper:
// a. Assert: targetCase is upper.
// b. Let newCodePoints be a List whose elements are the result of an uppercase transformation of codePoints according to an implementation-derived algorithm using locale or the Unicode Default Case Conversion algorithm.
new_code_points = MUST(string.to_uppercase(*locale));
new_code_points = MUST(string.to_uppercase(locale));
break;
default:
VERIFY_NOT_REACHED();

View file

@ -12,5 +12,11 @@ describe("normal behavior", () => {
expect(new Intl.Locale("en-u-ca-abc").calendar).toBe("abc");
expect(new Intl.Locale("en", { calendar: "abc" }).calendar).toBe("abc");
expect(new Intl.Locale("en-u-ca-abc", { calendar: "def" }).calendar).toBe("def");
expect(new Intl.Locale("en", { calendar: "islamicc" }).calendar).toBe("islamic-civil");
expect(new Intl.Locale("en-u-ca-islamicc").calendar).toBe("islamic-civil");
expect(new Intl.Locale("en", { calendar: "ethiopic-amete-alem" }).calendar).toBe("ethioaa");
expect(new Intl.Locale("en-u-ca-ethiopic-amete-alem").calendar).toBe("ethioaa");
});
});