mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-09-11 05:25:32 +03:00
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:
parent
2c311448c7
commit
9c3a775395
Notes:
sideshowbarker
2024-07-16 20:12:13 +09:00
Author: https://github.com/trflynn89 Commit: https://github.com/LadybirdBrowser/ladybird/commit/9c3a775395 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/198
@ -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: ");
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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.
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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]]
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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]]
|
||||
|
@ -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>())
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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]]
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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");
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user