diff options
6 files changed, 144 insertions, 0 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h index f0604cf635..2fbf591ce0 100644 --- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -403,6 +403,7 @@ namespace JS { P(substring) \ P(subtract) \ P(sup) \ + P(supportedLocalesOf) \ P(tan) \ P(tanh) \ P(test) \ diff --git a/Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.cpp index 36283eb16c..39f8b51229 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.cpp @@ -7,6 +7,7 @@ #include <AK/AllOf.h> #include <AK/AnyOf.h> #include <AK/CharacterTypes.h> +#include <AK/Function.h> #include <AK/QuickSort.h> #include <AK/TypeCasts.h> #include <LibJS/Runtime/Array.h> @@ -402,6 +403,77 @@ LocaleResult resolve_locale(Vector<String> const& requested_locales, LocaleOptio return result; } +// 9.2.8 LookupSupportedLocales ( availableLocales, requestedLocales ), https://tc39.es/ecma402/#sec-lookupsupportedlocales +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 = Unicode::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<Unicode::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 +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 +Array* supported_locales(GlobalObject& global_object, Vector<String> const& requested_locales, Value options) +{ + auto& vm = global_object.vm(); + + // 1. Set options to ? CoerceOptionsToObject(options). + auto* options_object = coerce_options_to_object(global_object, options); + if (vm.exception()) + return {}; + + // 2. Let matcher be ? GetOption(options, "localeMatcher", "string", ยซ "lookup", "best fit" ยป, "best fit"). + auto matcher = get_option(global_object, options_object, vm.names.localeMatcher, Value::Type::String, { "lookup"sv, "best fit"sv }, "best fit"sv); + if (vm.exception()) + return {}; + + Vector<String> supported_locales; + + // 3. If matcher is "best fit", then + if (matcher.as_string().string() == "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); + } + + // 5. Return CreateArrayFromList(supportedLocales). + return Array::create_from<String>(global_object, supported_locales, [&vm](auto& locale) { return js_string(vm, locale); }); +} + // 9.2.12 CoerceOptionsToObject ( options ), https://tc39.es/ecma402/#sec-coerceoptionstoobject Object* coerce_options_to_object(GlobalObject& global_object, Value options) { diff --git a/Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.h b/Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.h index be5c4c9e7d..03a4853fad 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.h +++ b/Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.h @@ -29,6 +29,9 @@ struct LocaleResult { Optional<Unicode::LocaleID> is_structurally_valid_language_tag(StringView locale); String canonicalize_unicode_locale_id(Unicode::LocaleID& locale); Vector<String> canonicalize_locale_list(GlobalObject&, Value locales); +Vector<String> best_fit_supported_locales(Vector<String> const& requested_locales); +Vector<String> lookup_supported_locales(Vector<String> const& requested_locales); +Array* supported_locales(GlobalObject&, Vector<String> const& requested_locales, Value options); Object* coerce_options_to_object(GlobalObject& global_object, Value options); Value get_option(GlobalObject& global_object, Value options, PropertyName const& property, Value::Type type, Vector<StringView> const& values, Fallback fallback); String insert_unicode_extension_and_canonicalize(Unicode::LocaleID locale_id, Unicode::LocaleExtension extension); diff --git a/Userland/Libraries/LibJS/Runtime/Intl/DisplayNamesConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Intl/DisplayNamesConstructor.cpp index d8990d8ee4..51099e859c 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/DisplayNamesConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/DisplayNamesConstructor.cpp @@ -5,6 +5,7 @@ */ #include <LibJS/Runtime/AbstractOperations.h> +#include <LibJS/Runtime/Array.h> #include <LibJS/Runtime/GlobalObject.h> #include <LibJS/Runtime/Intl/AbstractOperations.h> #include <LibJS/Runtime/Intl/DisplayNames.h> @@ -28,6 +29,10 @@ void DisplayNamesConstructor::initialize(GlobalObject& global_object) // 12.3.1 Intl.DisplayNames.prototype, https://tc39.es/ecma402/#sec-Intl.DisplayNames.prototype define_direct_property(vm.names.prototype, global_object.intl_display_names_prototype(), 0); + + u8 attr = Attribute::Writable | Attribute::Configurable; + define_native_function(vm.names.supportedLocalesOf, supported_locales_of, 1, attr); + define_direct_property(vm.names.length, Value(2), Attribute::Configurable); } @@ -125,4 +130,22 @@ Value DisplayNamesConstructor::construct(FunctionObject& new_target) return display_names; } +// 12.3.2 Intl.DisplayNames.supportedLocalesOf ( locales [ , options ] ), https://tc39.es/ecma402/#sec-Intl.DisplayNames.supportedLocalesOf +JS_DEFINE_NATIVE_FUNCTION(DisplayNamesConstructor::supported_locales_of) +{ + auto locales = vm.argument(0); + auto options = vm.argument(1); + + // 1. Let availableLocales be %DisplayNames%.[[AvailableLocales]]. + // No-op, availability of each requested locale is checked via Unicode::is_locale_available() + + // 2. Let requestedLocales be ? CanonicalizeLocaleList(locales). + auto requested_locales = canonicalize_locale_list(global_object, locales); + if (vm.exception()) + return {}; + + // 3. Return ? SupportedLocales(availableLocales, requestedLocales, options). + return supported_locales(global_object, requested_locales, options); +} + } diff --git a/Userland/Libraries/LibJS/Runtime/Intl/DisplayNamesConstructor.h b/Userland/Libraries/LibJS/Runtime/Intl/DisplayNamesConstructor.h index 5ea50241c6..3e7afc7143 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/DisplayNamesConstructor.h +++ b/Userland/Libraries/LibJS/Runtime/Intl/DisplayNamesConstructor.h @@ -23,6 +23,8 @@ public: private: virtual bool has_constructor() const override { return true; } + + JS_DECLARE_NATIVE_FUNCTION(supported_locales_of); }; } diff --git a/Userland/Libraries/LibJS/Tests/builtins/Intl/DisplayNames/DisplayNames.supportedLocalesOf.js b/Userland/Libraries/LibJS/Tests/builtins/Intl/DisplayNames/DisplayNames.supportedLocalesOf.js new file mode 100644 index 0000000000..a5d6ade92e --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Intl/DisplayNames/DisplayNames.supportedLocalesOf.js @@ -0,0 +1,43 @@ +describe("correct behavior", () => { + test("length is 1", () => { + expect(Intl.DisplayNames.supportedLocalesOf).toHaveLength(1); + }); + + test("basic functionality", () => { + // prettier-ignore + const values = [ + [[], []], + [undefined, []], + ["en", ["en"]], + [new Intl.Locale("en"), ["en"]], + [["en"], ["en"]], + [["en", "en-gb", "en-us"], ["en", "en-GB", "en-US"]], + [["en", "de", "fr"], ["en", "de", "fr"]], + [["en-foobar"], ["en-foobar"]], + [["en-foobar-u-abc"], ["en-foobar-u-abc"]], + [["aa", "zz"], []], + [["en", "aa", "zz"], ["en"]], + ]; + for (const [input, expected] of values) { + expect(Intl.DisplayNames.supportedLocalesOf(input)).toEqual(expected); + // "best fit" (implementation defined) just uses the same implementation as "lookup" at the moment + expect( + Intl.DisplayNames.supportedLocalesOf(input, { localeMatcher: "best fit" }) + ).toEqual(Intl.DisplayNames.supportedLocalesOf(input, { localeMatcher: "lookup" })); + } + }); +}); + +describe("errors", () => { + test("invalid value for localeMatcher option", () => { + expect(() => { + Intl.DisplayNames.supportedLocalesOf([], { localeMatcher: "foo" }); + }).toThrowWithMessage(RangeError, "foo is not a valid value for option localeMatcher"); + }); + + test("invalid language tag", () => { + expect(() => { + Intl.DisplayNames.supportedLocalesOf(["aaaaaaaaa"]); + }).toThrowWithMessage(RangeError, "aaaaaaaaa is not a structurally valid language tag"); + }); +}); |