Browse Source

LibJS: Handle Unicode locale extensions in LookupMatcher AO

Timothy Flynn 3 years ago
parent
commit
6f5fb87d3b

+ 33 - 11
Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.cpp

@@ -251,16 +251,27 @@ static Optional<String> best_available_locale(StringView const& locale)
     }
 }
 
+struct MatcherResult {
+    String locale;
+    Vector<Unicode::Extension> extensions {};
+};
+
 // 9.2.3 LookupMatcher ( availableLocales, requestedLocales ), https://tc39.es/ecma402/#sec-lookupmatcher
-static LocaleResult lookup_matcher(Vector<String> const& requested_locales)
+static MatcherResult lookup_matcher(Vector<String> const& requested_locales)
 {
     // 1. Let result be a new Record.
-    LocaleResult result {};
+    MatcherResult result {};
 
     // 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());
+
+        auto extensions = move(locale_id->extensions);
+        locale_id->private_use_extensions.clear();
+
         // a. Let noExtensionsLocale be the String value that is locale with any Unicode locale extension sequences removed.
-        auto const& no_extensions_locale = locale; // FIXME: Handle extensions.
+        auto no_extensions_locale = JS::Intl::canonicalize_unicode_locale_id(*locale_id);
 
         // b. Let availableLocale be BestAvailableLocale(availableLocales, noExtensionsLocale).
         auto available_locale = best_available_locale(no_extensions_locale);
@@ -270,10 +281,12 @@ static LocaleResult lookup_matcher(Vector<String> const& requested_locales)
             // i. Set result.[[locale]] to availableLocale.
             result.locale = available_locale.release_value();
 
-            // FIXME: Handle extensions.
             // ii. If locale and noExtensionsLocale are not the same String value, then
-            //     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.
+            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 = move(extensions);
+            }
 
             // iii. Return result.
             return result;
@@ -289,7 +302,7 @@ static LocaleResult lookup_matcher(Vector<String> const& requested_locales)
 }
 
 // 9.2.4 BestFitMatcher ( availableLocales, requestedLocales ), https://tc39.es/ecma402/#sec-bestfitmatcher
-static LocaleResult best_fit_matcher(Vector<String> const& requested_locales)
+static MatcherResult best_fit_matcher(Vector<String> const& 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.
@@ -301,23 +314,30 @@ LocaleResult resolve_locale(Vector<String> const& requested_locales, LocaleOptio
 {
     // 1. Let matcher be options.[[localeMatcher]].
     auto const& matcher = options.locale_matcher;
-    LocaleResult result;
+    MatcherResult matcher_result;
 
     // 2. If matcher is "lookup", then
     if (matcher.is_string() && (matcher.as_string().string() == "lookup"sv)) {
         // a. Let r be LookupMatcher(availableLocales, requestedLocales).
-        result = lookup_matcher(requested_locales);
+        matcher_result = lookup_matcher(requested_locales);
     }
     // 3. Else,
     else {
         // a. Let r be BestFitMatcher(availableLocales, requestedLocales).
-        result = best_fit_matcher(requested_locales);
+        matcher_result = best_fit_matcher(requested_locales);
     }
+
     // 4. Let foundLocale be r.[[locale]].
+    auto found_locale = move(matcher_result.locale);
+
     // 5. Let result be a new Record.
+    LocaleResult result {};
+
     // 6. Set result.[[dataLocale]] to foundLocale.
 
-    // FIXME: Handle extensions.
+    // FIXME: Currently, the only caller to this method has an empty [[RelevantExtensionKeys]] internal slot,
+    //        so this block isn't testable. When a caller has a non-empty slot, implement the below steps.
+    //
     // 7. If r has an [[extension]] field, then
     //     a. Let components be ! UnicodeExtensionComponents(r.[[extension]]).
     //     b. Let keywords be components.[[Keywords]].
@@ -357,7 +377,9 @@ LocaleResult resolve_locale(Vector<String> const& requested_locales, LocaleOptio
     //     k. Append supportedExtensionAddition to supportedExtension.
     // 10. If the number of elements in supportedExtension is greater than 2, then
     //     a. Let foundLocale be InsertUnicodeExtensionAndCanonicalize(foundLocale, supportedExtension).
+
     // 11. Set result.[[locale]] to foundLocale.
+    result.locale = move(found_locale);
 
     // 12. Return result.
     return result;

+ 11 - 0
Userland/Libraries/LibJS/Tests/builtins/Intl/DisplayNames/DisplayNames.prototype.resolvedOptions.js

@@ -28,4 +28,15 @@ describe("correct behavior", () => {
             fallback: "code",
         });
     });
+
+    test("locales with extensions", () => {
+        const en = new Intl.DisplayNames("en-t-en", { type: "language" });
+        expect(en.resolvedOptions().locale).toBe("en");
+
+        const es419 = new Intl.DisplayNames("es-419-u-1k-aaa", { type: "language" });
+        expect(es419.resolvedOptions().locale).toBe("es-419");
+
+        const zhHant = new Intl.DisplayNames(["zh-Hant-x-aaa"], { type: "language" });
+        expect(zhHant.resolvedOptions().locale).toBe("zh-Hant");
+    });
 });