diff --git a/Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.cpp index c8972225699..b56fd660b2a 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.cpp @@ -21,6 +21,17 @@ namespace JS::Intl { +Optional locale_key_from_value(Value value) +{ + if (value.is_undefined()) + return OptionalNone {}; + if (value.is_null()) + return Empty {}; + if (value.is_string()) + return value.as_string().utf8_string(); + VERIFY_NOT_REACHED(); +} + // 6.2.2 IsStructurallyValidLanguageTag ( locale ), https://tc39.es/ecma402/#sec-isstructurallyvalidlanguagetag bool is_structurally_valid_language_tag(StringView locale) { @@ -366,9 +377,30 @@ static auto& find_key_in_value(T& value, StringView key) VERIFY_NOT_REACHED(); } +static Vector available_keyword_values(StringView locale, StringView key) +{ + auto key_locale_data = ::Locale::available_keyword_values(locale, key); + + Vector result; + result.ensure_capacity(key_locale_data.size()); + + for (auto& keyword : key_locale_data) + result.unchecked_append(move(keyword)); + + if (key == "hc"sv) { + // https://tc39.es/ecma402/#sec-intl.datetimeformat-internal-slots + // [[LocaleData]].[[]].[[hc]] must be « null, "h11", "h12", "h23", "h24" ». + result.prepend(Empty {}); + } + + return result; +} + // 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) { + static auto true_string = "true"_string; + // 1. Let matcher be options.[[localeMatcher]]. auto const& matcher = options.locale_matcher; MatcherResult matcher_result; @@ -416,7 +448,7 @@ LocaleResult resolve_locale(Vector const& requested_locales, LocaleOptio // b. Assert: Type(foundLocaleData) is Record. // c. Let keyLocaleData be foundLocaleData.[[]]. // d. Assert: Type(keyLocaleData) is List. - auto key_locale_data = ::Locale::available_keyword_values(found_locale, key); + 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. @@ -447,9 +479,9 @@ LocaleResult resolve_locale(Vector const& requested_locales, LocaleOptio } } // 4. Else if keyLocaleData contains "true", then - else if (key_locale_data.contains_slow("true"sv)) { + else if (key_locale_data.contains_slow(true_string)) { // a. Let value be "true". - value = "true"_string; + value = true_string; // b. Let supportedExtensionAddition be the string-concatenation of "-" and key. supported_extension_addition = ::Locale::Keyword { MUST(String::from_utf8(key)), {} }; @@ -464,15 +496,15 @@ LocaleResult resolve_locale(Vector const& requested_locales, LocaleOptio auto options_value = find_key_in_value(options, key); // iii. If Type(optionsValue) is String, then - if (options_value.has_value()) { + 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. - ::Locale::canonicalize_unicode_extension_values(key, *options_value); + ::Locale::canonicalize_unicode_extension_values(key, *options_string); // 3. If optionsValue is the empty String, then - if (options_value->is_empty()) { + if (options_string->is_empty()) { // a. Let optionsValue be "true". - options_value = "true"_string; + *options_string = true_string; } } diff --git a/Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.h b/Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.h index 5c0a686c6b0..71998734d61 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.h +++ b/Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.h @@ -21,25 +21,28 @@ namespace JS::Intl { +using LocaleKey = Variant; +Optional locale_key_from_value(Value); + struct LocaleOptions { Value locale_matcher; - Optional ca; // [[Calendar]] - Optional co; // [[Collation]] - Optional hc; // [[HourCycle]] - Optional kf; // [[CaseFirst]] - Optional kn; // [[Numeric]] - Optional nu; // [[NumberingSystem]] + Optional ca; // [[Calendar]] + Optional co; // [[Collation]] + Optional hc; // [[HourCycle]] + Optional kf; // [[CaseFirst]] + Optional kn; // [[Numeric]] + Optional nu; // [[NumberingSystem]] }; struct LocaleResult { String locale; String data_locale; - Optional ca; // [[Calendar]] - Optional co; // [[Collation]] - Optional hc; // [[HourCycle]] - Optional kf; // [[CaseFirst]] - Optional kn; // [[Numeric]] - Optional nu; // [[NumberingSystem]] + LocaleKey ca; // [[Calendar]] + LocaleKey co; // [[Collation]] + LocaleKey hc; // [[HourCycle]] + LocaleKey kf; // [[CaseFirst]] + LocaleKey kn; // [[Numeric]] + LocaleKey nu; // [[NumberingSystem]] }; struct PatternPartition { diff --git a/Userland/Libraries/LibJS/Runtime/Intl/CollatorConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Intl/CollatorConstructor.cpp index bf0f6b625b7..bdc3b8777eb 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/CollatorConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/CollatorConstructor.cpp @@ -62,16 +62,19 @@ static ThrowCompletionOr> initialize_collator(VM& vm, Col auto numeric = TRY(get_option(vm, *options, vm.names.numeric, OptionType::Boolean, {}, Empty {})); // 14. If numeric is not undefined, then - // a. Let numeric be ! ToString(numeric). + if (!numeric.is_undefined()) { + // a. Let numeric be ! ToString(numeric). + numeric = PrimitiveString::create(vm, MUST(numeric.to_string(vm))); + } + // 15. Set opt.[[kn]] to numeric. - if (!numeric.is_undefined()) - opt.kn = MUST(numeric.to_string(vm)); + opt.kn = locale_key_from_value(numeric); // 16. Let caseFirst be ? GetOption(options, "caseFirst", string, « "upper", "lower", "false" », undefined). - // 17. Set opt.[[kf]] to caseFirst. auto case_first = TRY(get_option(vm, *options, vm.names.caseFirst, OptionType::String, { "upper"sv, "lower"sv, "false"sv }, Empty {})); - if (!case_first.is_undefined()) - opt.kf = case_first.as_string().utf8_string(); + + // 17. Set opt.[[kf]] to caseFirst. + opt.kf = locale_key_from_value(case_first); // 18. Let relevantExtensionKeys be %Collator%.[[RelevantExtensionKeys]]. auto relevant_extension_keys = Collator::relevant_extension_keys(); @@ -83,20 +86,26 @@ static ThrowCompletionOr> initialize_collator(VM& vm, Col collator.set_locale(move(result.locale)); // 21. Let collation be r.[[co]]. + auto& collation_value = result.co; + // 22. If collation is null, let collation be "default". + if (collation_value.has()) + collation_value = "default"_string; + // 23. Set collator.[[Collation]] to collation. - collator.set_collation(result.co.has_value() ? result.co.release_value() : "default"_string); + collator.set_collation(move(collation_value.get())); // 24. If relevantExtensionKeys contains "kn", then - if (relevant_extension_keys.span().contains_slow("kn"sv) && result.kn.has_value()) { + if (relevant_extension_keys.span().contains_slow("kn"sv)) { // a. Set collator.[[Numeric]] to SameValue(r.[[kn]], "true"). - collator.set_numeric(same_value(PrimitiveString::create(vm, result.kn.release_value()), PrimitiveString::create(vm, "true"_string))); + collator.set_numeric(result.kn == "true"_string); } // 25. If relevantExtensionKeys contains "kf", then - if (relevant_extension_keys.span().contains_slow("kf"sv) && result.kf.has_value()) { + if (relevant_extension_keys.span().contains_slow("kf"sv)) { // a. Set collator.[[CaseFirst]] to r.[[kf]]. - collator.set_case_first(result.kf.release_value()); + if (auto* resolved_case_first = result.kf.get_pointer()) + collator.set_case_first(*resolved_case_first); } // 26. Let sensitivity be ? GetOption(options, "sensitivity", string, « "base", "accent", "case", "variant" », undefined). diff --git a/Userland/Libraries/LibJS/Runtime/Intl/DateTimeFormatConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Intl/DateTimeFormatConstructor.cpp index 93e393b030f..1aafa537682 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/DateTimeFormatConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/DateTimeFormatConstructor.cpp @@ -110,11 +110,11 @@ ThrowCompletionOr> create_date_time_format(VM& vm, // a. If calendar cannot be matched by the type Unicode locale nonterminal, throw a RangeError exception. if (!::Locale::is_type_identifier(calendar.as_string().utf8_string_view())) return vm.throw_completion(ErrorType::OptionIsNotValidValue, calendar, "calendar"sv); - - // 9. Set opt.[[ca]] to calendar. - opt.ca = calendar.as_string().utf8_string(); } + // 9. Set opt.[[ca]] to calendar. + opt.ca = locale_key_from_value(calendar); + // 10. Let numberingSystem be ? GetOption(options, "numberingSystem", string, empty, undefined). auto numbering_system = TRY(get_option(vm, *options, vm.names.numberingSystem, OptionType::String, {}, Empty {})); @@ -123,11 +123,11 @@ ThrowCompletionOr> create_date_time_format(VM& vm, // a. If numberingSystem cannot be matched by the type Unicode locale nonterminal, throw a RangeError exception. if (!::Locale::is_type_identifier(numbering_system.as_string().utf8_string_view())) return vm.throw_completion(ErrorType::OptionIsNotValidValue, numbering_system, "numberingSystem"sv); - - // 12. Set opt.[[nu]] to numberingSystem. - opt.nu = numbering_system.as_string().utf8_string(); } + // 12. Set opt.[[nu]] to numberingSystem. + opt.nu = locale_key_from_value(numbering_system); + // 13. Let hour12 be ? GetOption(options, "hour12", boolean, empty, undefined). auto hour12 = TRY(get_option(vm, *options, vm.names.hour12, OptionType::Boolean, {}, Empty {})); @@ -141,8 +141,7 @@ ThrowCompletionOr> create_date_time_format(VM& vm, } // 16. Set opt.[[hc]] to hourCycle. - if (!hour_cycle.is_nullish()) - opt.hc = hour_cycle.as_string().utf8_string(); + opt.hc = locale_key_from_value(hour_cycle); // 17. Let localeData be %DateTimeFormat%.[[LocaleData]]. // 18. Let r be ResolveLocale(%DateTimeFormat%.[[AvailableLocales]], requestedLocales, opt, %DateTimeFormat%.[[RelevantExtensionKeys]], localeData). @@ -153,12 +152,12 @@ ThrowCompletionOr> create_date_time_format(VM& vm, // 20. Let resolvedCalendar be r.[[ca]]. // 21. Set dateTimeFormat.[[Calendar]] to resolvedCalendar. - if (result.ca.has_value()) - date_time_format->set_calendar(result.ca.release_value()); + if (auto* resolved_calendar = result.ca.get_pointer()) + date_time_format->set_calendar(move(*resolved_calendar)); // 22. Set dateTimeFormat.[[NumberingSystem]] to r.[[nu]]. - if (result.nu.has_value()) - date_time_format->set_numbering_system(result.nu.release_value()); + if (auto* resolved_numbering_system = result.nu.get_pointer()) + date_time_format->set_numbering_system(move(*resolved_numbering_system)); // 23. Let dataLocale be r.[[dataLocale]]. auto data_locale = move(result.data_locale); @@ -184,8 +183,8 @@ ThrowCompletionOr> create_date_time_format(VM& vm, VERIFY(hour12.is_undefined()); // b. Let hc be r.[[hc]]. - if (result.hc.has_value()) - hour_cycle_value = ::Locale::hour_cycle_from_string(*result.hc); + if (auto* resolved_hour_cycle = result.hc.get_pointer()) + hour_cycle_value = ::Locale::hour_cycle_from_string(*resolved_hour_cycle); // c. If hc is null, set hc to dataLocaleData.[[hourCycle]]. if (!hour_cycle_value.has_value()) diff --git a/Userland/Libraries/LibJS/Runtime/Intl/DurationFormatConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Intl/DurationFormatConstructor.cpp index 918e7f659ac..40ad6e06f7a 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/DurationFormatConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/DurationFormatConstructor.cpp @@ -77,8 +77,7 @@ ThrowCompletionOr> DurationFormatConstructor::construct(Fun // 8. Let opt be the Record { [[localeMatcher]]: matcher, [[nu]]: numberingSystem }. LocaleOptions opt {}; opt.locale_matcher = matcher; - if (!numbering_system.is_undefined()) - opt.nu = numbering_system.as_string().utf8_string(); + opt.nu = locale_key_from_value(numbering_system); // 9. Let r be ResolveLocale(%DurationFormat%.[[AvailableLocales]], requestedLocales, opt, %DurationFormat%.[[RelevantExtensionKeys]], %DurationFormat%.[[LocaleData]]). auto result = resolve_locale(requested_locales, opt, DurationFormat::relevant_extension_keys()); @@ -110,8 +109,8 @@ ThrowCompletionOr> DurationFormatConstructor::construct(Fun duration_format->set_minutes_seconds_separator(move(digital_format.minutes_seconds_separator)); // 22. Set durationFormat.[[NumberingSystem]] to r.[[nu]]. - if (result.nu.has_value()) - duration_format->set_numbering_system(result.nu.release_value()); + if (auto* resolved_numbering_system = result.nu.get_pointer()) + duration_format->set_numbering_system(move(*resolved_numbering_system)); // 23. Let style be ? GetOption(options, "style", string, « "long", "short", "narrow", "digital" », "short"). auto style = TRY(get_option(vm, *options, vm.names.style, OptionType::String, { "long"sv, "short"sv, "narrow"sv, "digital"sv }, "short"sv)); diff --git a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.cpp index 12ed3f37737..2797f99a5fb 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.cpp @@ -106,11 +106,11 @@ ThrowCompletionOr> initialize_number_format(VM& vm, N // a. If numberingSystem does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception. if (!::Locale::is_type_identifier(numbering_system.as_string().utf8_string_view())) return vm.throw_completion(ErrorType::OptionIsNotValidValue, numbering_system, "numberingSystem"sv); - - // 8. Set opt.[[nu]] to numberingSystem. - opt.nu = numbering_system.as_string().utf8_string(); } + // 8. Set opt.[[nu]] to numberingSystem. + opt.nu = locale_key_from_value(numbering_system); + // 9. Let localeData be %NumberFormat%.[[LocaleData]]. // 10. Let r be ResolveLocale(%NumberFormat%.[[AvailableLocales]], requestedLocales, opt, %NumberFormat%.[[RelevantExtensionKeys]], localeData). auto result = resolve_locale(requested_locales, opt, NumberFormat::relevant_extension_keys()); @@ -122,8 +122,8 @@ ThrowCompletionOr> initialize_number_format(VM& vm, N number_format.set_data_locale(move(result.data_locale)); // 13. Set numberFormat.[[NumberingSystem]] to r.[[nu]]. - if (result.nu.has_value()) - number_format.set_numbering_system(result.nu.release_value()); + if (auto* resolved_numbering_system = result.nu.get_pointer()) + number_format.set_numbering_system(move(*resolved_numbering_system)); // 14. Perform ? SetNumberFormatUnitOptions(numberFormat, options). TRY(set_number_format_unit_options(vm, number_format, *options)); diff --git a/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatConstructor.cpp index 7bb234761b4..270424c6924 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatConstructor.cpp @@ -77,11 +77,11 @@ ThrowCompletionOr> RelativeTimeFormatConstructor::construct // a. If numberingSystem cannot be matched by the type Unicode locale nonterminal, throw a RangeError exception. if (!::Locale::is_type_identifier(numbering_system.as_string().utf8_string_view())) return vm.throw_completion(ErrorType::OptionIsNotValidValue, numbering_system, "numberingSystem"sv); - - // 10. Set opt.[[nu]] to numberingSystem. - opt.nu = numbering_system.as_string().utf8_string(); } + // 10. Set opt.[[nu]] to numberingSystem. + opt.nu = locale_key_from_value(numbering_system); + // 11. Let r be ResolveLocale(%Intl.RelativeTimeFormat%.[[AvailableLocales]], requestedLocales, opt, %Intl.RelativeTimeFormat%.[[RelevantExtensionKeys]], %Intl.RelativeTimeFormat%.[[LocaleData]]). auto result = resolve_locale(requested_locales, opt, RelativeTimeFormat::relevant_extension_keys()); @@ -95,8 +95,8 @@ ThrowCompletionOr> RelativeTimeFormatConstructor::construct relative_time_format->set_data_locale(move(result.data_locale)); // 15. Set relativeTimeFormat.[[NumberingSystem]] to r.[[nu]]. - if (result.nu.has_value()) - relative_time_format->set_numbering_system(result.nu.release_value()); + if (auto* resolved_numbering_system = result.nu.get_pointer()) + relative_time_format->set_numbering_system(move(*resolved_numbering_system)); // 16. Let style be ? GetOption(options, "style", string, « "long", "short", "narrow" », "long"). auto style = TRY(get_option(vm, *options, vm.names.style, OptionType::String, { "long"sv, "short"sv, "narrow"sv }, "long"sv));