From 9c3a775395a6480fa5ef1f83c95e83e6bac9bc0b Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Tue, 18 Jun 2024 10:13:30 -0400 Subject: [PATCH] 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. --- Userland/Libraries/LibJS/Print.cpp | 4 - .../LibJS/Runtime/Intl/AbstractOperations.cpp | 364 ++++++++---------- .../LibJS/Runtime/Intl/AbstractOperations.h | 19 +- .../Runtime/Intl/CollatorConstructor.cpp | 6 +- .../LibJS/Runtime/Intl/DateTimeFormat.h | 5 - .../Intl/DateTimeFormatConstructor.cpp | 10 +- .../Runtime/Intl/DisplayNamesConstructor.cpp | 6 +- .../LibJS/Runtime/Intl/DurationFormat.h | 4 - .../Intl/DurationFormatConstructor.cpp | 8 +- .../Runtime/Intl/ListFormatConstructor.cpp | 4 +- .../LibJS/Runtime/Intl/LocaleConstructor.cpp | 68 ++-- .../LibJS/Runtime/Intl/NumberFormat.h | 5 - .../Runtime/Intl/NumberFormatConstructor.cpp | 7 +- .../Runtime/Intl/PluralRulesConstructor.cpp | 7 +- .../LibJS/Runtime/Intl/RelativeTimeFormat.h | 4 - .../Intl/RelativeTimeFormatConstructor.cpp | 5 +- .../Runtime/Intl/SegmenterConstructor.cpp | 6 +- .../LibJS/Runtime/StringPrototype.cpp | 15 +- .../Intl/Locale/Locale.prototype.calendar.js | 6 + 19 files changed, 242 insertions(+), 311 deletions(-) diff --git a/Userland/Libraries/LibJS/Print.cpp b/Userland/Libraries/LibJS/Print.cpp index 635ccc0b96e..fbac8b93b3c 100644 --- a/Userland/Libraries/LibJS/Print.cpp +++ b/Userland/Libraries/LibJS/Print.cpp @@ -666,8 +666,6 @@ ErrorOr 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 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: "); diff --git a/Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.cpp index 2a130c7c081..0dbb1726611 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.cpp @@ -16,7 +16,6 @@ #include #include #include -#include #include namespace JS::Intl { @@ -263,97 +262,75 @@ ThrowCompletionOr> canonicalize_locale_list(VM& vm, Value locales return seen; } -// 9.2.2 BestAvailableLocale ( availableLocales, locale ), https://tc39.es/ecma402/#sec-bestavailablelocale -Optional best_available_locale(StringView locale) +// 9.2.3 LookupMatchingLocaleByPrefix ( availableLocales, requestedLocales ), https://tc39.es/ecma402/#sec-lookupmatchinglocalebyprefix +Optional lookup_matching_locale_by_prefix(ReadonlySpan 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 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 const& requested_locales) +// 9.2.4 LookupMatchingLocaleByBestFit ( availableLocales, requestedLocales ), https://tc39.es/ecma402/#sec-lookupmatchinglocalebybestfit +Optional lookup_matching_locale_by_best_fit(ReadonlySpan 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 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 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 const& requested_locales, LocaleOptions const& options, ReadonlySpan relevant_extension_keys) +ResolvedLocale resolve_locale(ReadonlySpan requested_locales, LocaleOptions const& options, ReadonlySpan 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 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.[[]]. + // 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.[[]]. - // b. Assert: Type(foundLocaleData) is Record. - // c. Let keyLocaleData be foundLocaleData.[[]]. - // d. Assert: Type(keyLocaleData) is List. + // a. Let keyLocaleData be foundLocaleData.[[]]. + // 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 [[]], then - // i. Let optionsValue be options.[[]]. - // ii. Assert: Type(optionsValue) is either String, Undefined, or Null. + // g. Assert: options has a field [[]]. + // h. Let optionsValue be options.[[]]. + // 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() : 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.[[]] 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.[[]] 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 lookup_supported_locales(Vector const& requested_locales) -{ - // 1. Let subset be a new empty List. - Vector 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 best_fit_supported_locales(Vector 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 supported_locales(VM& vm, Vector const& requested_locales, Value options) +// 9.2.8 FilterLocales ( availableLocales, requestedLocales, options ), https://tc39.es/ecma402/#sec-lookupsupportedlocales +ThrowCompletionOr filter_locales(VM& vm, ReadonlySpan requested_locales, Value options) { auto& realm = *vm.current_realm(); @@ -591,24 +530,41 @@ ThrowCompletionOr supported_locales(VM& vm, Vector 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 supported_locales; + // 3. Let subset be a new empty List. + Vector 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 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(realm, supported_locales, [&vm](auto& locale) { return PrimitiveString::create(vm, move(locale)); }).ptr(); + // 5. Return CreateArrayFromList(subset). + return Array::create_from(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 coerce_options_to_object(VM& vm, Value options) { auto& realm = *vm.current_realm(); @@ -623,9 +579,9 @@ ThrowCompletionOr 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 get_boolean_or_string_number_format_option(VM& vm, Object const& options, PropertyKey const& property, ReadonlySpan string_values, StringOrBoolean fallback) { // 1. Let value be ? Get(options, property). @@ -655,7 +611,7 @@ ThrowCompletionOr 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> default_number_option(VM& vm, Value value, int minimum, int maximum, Optional fallback) { // 1. If value is undefined, return fallback. @@ -673,7 +629,7 @@ ThrowCompletionOr> 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> get_number_option(VM& vm, Object const& options, PropertyKey const& property, int minimum, int maximum, Optional fallback) { // 1. Assert: Type(options) is Object. diff --git a/Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.h b/Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.h index 43f20e25440..a0fc8ab8ea6 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.h +++ b/Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.h @@ -14,7 +14,7 @@ #include #include #include -#include +#include namespace JS::Intl { @@ -31,9 +31,13 @@ struct LocaleOptions { Optional 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> canonicalize_locale_list(VM&, Value locales); -Optional best_available_locale(StringView locale); -String insert_unicode_extension_and_canonicalize(::Locale::LocaleID locale_id, ::Locale::LocaleExtension extension); -LocaleResult resolve_locale(Vector const& requested_locales, LocaleOptions const& options, ReadonlySpan relevant_extension_keys); -ThrowCompletionOr supported_locales(VM&, Vector const& requested_locales, Value options); +Optional lookup_matching_locale_by_prefix(ReadonlySpan requested_locales); +Optional lookup_matching_locale_by_best_fit(ReadonlySpan requested_locales); +String insert_unicode_extension_and_canonicalize(::Locale::LocaleID locale_id, Vector attributes, Vector<::Locale::Keyword> keywords); +ResolvedLocale resolve_locale(ReadonlySpan requested_locales, LocaleOptions const& options, ReadonlySpan relevant_extension_keys); +ThrowCompletionOr filter_locales(VM& vm, ReadonlySpan requested_locales, Value options); ThrowCompletionOr coerce_options_to_object(VM&, Value options); ThrowCompletionOr get_boolean_or_string_number_format_option(VM& vm, Object const& options, PropertyKey const& property, ReadonlySpan string_values, StringOrBoolean fallback); ThrowCompletionOr> default_number_option(VM&, Value value, int minimum, int maximum, Optional fallback); diff --git a/Userland/Libraries/LibJS/Runtime/Intl/CollatorConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Intl/CollatorConstructor.cpp index bdc3b8777eb..4cef412326a 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/CollatorConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/CollatorConstructor.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023, Tim Flynn + * Copyright (c) 2022-2024, Tim Flynn * * 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)); } } diff --git a/Userland/Libraries/LibJS/Runtime/Intl/DateTimeFormat.h b/Userland/Libraries/LibJS/Runtime/Intl/DateTimeFormat.h index c4c723fa7fb..72ba2c3bcb4 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/DateTimeFormat.h +++ b/Userland/Libraries/LibJS/Runtime/Intl/DateTimeFormat.h @@ -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 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; }; diff --git a/Userland/Libraries/LibJS/Runtime/Intl/DateTimeFormatConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Intl/DateTimeFormatConstructor.cpp index 0f346acfbc8..493ecd7e13c 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/DateTimeFormatConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/DateTimeFormatConstructor.cpp @@ -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> 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.[[]]. Optional<::Locale::HourCycle> hour_cycle_value; @@ -188,7 +184,7 @@ ThrowCompletionOr> 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. diff --git a/Userland/Libraries/LibJS/Runtime/Intl/DisplayNamesConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Intl/DisplayNamesConstructor.cpp index f356eeb3819..95101057d8f 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/DisplayNamesConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/DisplayNamesConstructor.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2023, Tim Flynn + * Copyright (c) 2021-2024, Tim Flynn * * 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)); } } diff --git a/Userland/Libraries/LibJS/Runtime/Intl/DurationFormat.h b/Userland/Libraries/LibJS/Runtime/Intl/DurationFormat.h index 622432225e3..667e96a2f58 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/DurationFormat.h +++ b/Userland/Libraries/LibJS/Runtime/Intl/DurationFormat.h @@ -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]] diff --git a/Userland/Libraries/LibJS/Runtime/Intl/DurationFormatConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Intl/DurationFormatConstructor.cpp index 40ad6e06f7a..0f2b38c840e 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/DurationFormatConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/DurationFormatConstructor.cpp @@ -89,12 +89,12 @@ ThrowCompletionOr> 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]].[[]]. // 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)); } } diff --git a/Userland/Libraries/LibJS/Runtime/Intl/ListFormatConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Intl/ListFormatConstructor.cpp index 69d2e581a95..7bd9ecd0236 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/ListFormatConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/ListFormatConstructor.cpp @@ -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)); } } diff --git a/Userland/Libraries/LibJS/Runtime/Intl/LocaleConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Intl/LocaleConstructor.cpp index 186b3270996..e26c4f9d868 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/LocaleConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/LocaleConstructor.cpp @@ -111,15 +111,13 @@ static ThrowCompletionOr 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 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 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 value {}; - ::Locale::Keyword* entry = nullptr; - // b. If keywords contains an element whose [[Key]] is the same as key, then + Optional 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 [[]]. - // e. Let optionsValue be options.[[]]. - auto options_value = field_from_key(options, key); + // c. Assert: options has a field [[]]. + // d. Let overrideValue be options.[[]]. + 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.[[]] to value. + // f. Set result.[[]] 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; } diff --git a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.h b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.h index ec33f29b47b..c94b8971ca6 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.h +++ b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.h @@ -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 m_min_fraction_digits {}; // [[MinimumFractionDigits]] Optional 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 m_currency; // [[Currency]] diff --git a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.cpp index 2797f99a5fb..d738d67443e 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.cpp @@ -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> 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()) diff --git a/Userland/Libraries/LibJS/Runtime/Intl/PluralRulesConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Intl/PluralRulesConstructor.cpp index b7b97c72330..a14f4b5d0ed 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/PluralRulesConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/PluralRulesConstructor.cpp @@ -77,9 +77,6 @@ ThrowCompletionOr> 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)); } } diff --git a/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormat.h b/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormat.h index 54e4518162f..39e0f194cb3 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormat.h +++ b/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormat.h @@ -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]] diff --git a/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatConstructor.cpp index 270424c6924..c966f9521cc 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatConstructor.cpp @@ -92,7 +92,6 @@ ThrowCompletionOr> 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()) @@ -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)); } } diff --git a/Userland/Libraries/LibJS/Runtime/Intl/SegmenterConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Intl/SegmenterConstructor.cpp index 0001b3fbf13..1c3bdac315b 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/SegmenterConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/SegmenterConstructor.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2022, Idan Horowitz - * Copyright (c) 2023, Tim Flynn + * Copyright (c) 2023-2024, Tim Flynn * * 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)); } } diff --git a/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp b/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp index f5ced032159..67c419720c8 100644 --- a/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp @@ -1275,17 +1275,16 @@ static ThrowCompletionOr 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(); 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 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(); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Intl/Locale/Locale.prototype.calendar.js b/Userland/Libraries/LibJS/Tests/builtins/Intl/Locale/Locale.prototype.calendar.js index 6ece7cc45f2..c4cf0df4351 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Intl/Locale/Locale.prototype.calendar.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Intl/Locale/Locale.prototype.calendar.js @@ -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"); }); });