diff --git a/Libraries/LibJS/Print.cpp b/Libraries/LibJS/Print.cpp index 202695dae98..b2f9975b4ef 100644 --- a/Libraries/LibJS/Print.cpp +++ b/Libraries/LibJS/Print.cpp @@ -717,6 +717,8 @@ ErrorOr print_intl_plural_rules(JS::PrintContext& print_context, JS::Intl: TRY(print_value(print_context, JS::PrimitiveString::create(plural_rules.vm(), plural_rules.locale()), seen_objects)); TRY(js_out(print_context, "\n type: ")); TRY(print_value(print_context, JS::PrimitiveString::create(plural_rules.vm(), plural_rules.type_string()), seen_objects)); + TRY(js_out(print_context, "\n notation: ")); + TRY(print_value(print_context, JS::PrimitiveString::create(plural_rules.vm(), plural_rules.notation_string()), seen_objects)); TRY(js_out(print_context, "\n minimumIntegerDigits: ")); TRY(print_value(print_context, JS::Value(plural_rules.min_integer_digits()), seen_objects)); if (plural_rules.has_min_fraction_digits()) { diff --git a/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp b/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp index 12a2c10dc04..8faf661a255 100644 --- a/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp +++ b/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp @@ -111,20 +111,28 @@ Unicode::RoundingOptions NumberFormatBase::rounding_options() const }; } +Unicode::DisplayOptions NumberFormatBase::display_options() const +{ + Unicode::DisplayOptions options; + options.notation = m_notation; + options.compact_display = m_compact_display; + + return options; +} + Unicode::DisplayOptions NumberFormat::display_options() const { - return { - .style = m_style, - .sign_display = m_sign_display, - .notation = m_notation, - .compact_display = m_compact_display, - .grouping = m_use_grouping, - .currency = m_currency, - .currency_display = m_currency_display, - .currency_sign = m_currency_sign, - .unit = m_unit, - .unit_display = m_unit_display, - }; + auto options = Base::display_options(); + options.style = m_style; + options.sign_display = m_sign_display; + options.grouping = m_use_grouping; + options.currency = m_currency; + options.currency_display = m_currency_display; + options.currency_sign = m_currency_sign; + options.unit = m_unit; + options.unit_display = m_unit_display; + + return options; } // 16.5.1 CurrencyDigits ( currency ), https://tc39.es/ecma402/#sec-currencydigits diff --git a/Libraries/LibJS/Runtime/Intl/NumberFormat.h b/Libraries/LibJS/Runtime/Intl/NumberFormat.h index 8e1fb845205..8d1ab74515b 100644 --- a/Libraries/LibJS/Runtime/Intl/NumberFormat.h +++ b/Libraries/LibJS/Runtime/Intl/NumberFormat.h @@ -52,6 +52,15 @@ public: int max_significant_digits() const { return *m_max_significant_digits; } void set_max_significant_digits(int max_significant_digits) { m_max_significant_digits = max_significant_digits; } + Unicode::Notation notation() const { return m_notation; } + StringView notation_string() const { return Unicode::notation_to_string(m_notation); } + void set_notation(StringView notation) { m_notation = Unicode::notation_from_string(notation); } + + bool has_compact_display() const { return m_compact_display.has_value(); } + Unicode::CompactDisplay compact_display() const { return *m_compact_display; } + StringView compact_display_string() const { return Unicode::compact_display_to_string(*m_compact_display); } + void set_compact_display(StringView compact_display) { m_compact_display = Unicode::compact_display_from_string(compact_display); } + Unicode::RoundingType rounding_type() const { return m_rounding_type; } StringView rounding_type_string() const { return Unicode::rounding_type_to_string(m_rounding_type); } void set_rounding_type(Unicode::RoundingType rounding_type) { m_rounding_type = rounding_type; } @@ -71,6 +80,7 @@ public: StringView trailing_zero_display_string() const { return Unicode::trailing_zero_display_to_string(m_trailing_zero_display); } void set_trailing_zero_display(StringView trailing_zero_display) { m_trailing_zero_display = Unicode::trailing_zero_display_from_string(trailing_zero_display); } + virtual Unicode::DisplayOptions display_options() const; Unicode::RoundingOptions rounding_options() const; Unicode::NumberFormat const& formatter() const { return *m_formatter; } @@ -86,6 +96,8 @@ private: Optional m_max_fraction_digits {}; // [[MaximumFractionDigits]] Optional m_min_significant_digits {}; // [[MinimumSignificantDigits]] Optional m_max_significant_digits {}; // [[MaximumSignificantDigits]] + Unicode::Notation m_notation; // [[Notation]] + Optional m_compact_display; // [[CompactDisplay]] Unicode::RoundingType m_rounding_type; // [[RoundingType]] ComputedRoundingPriority m_computed_rounding_priority { ComputedRoundingPriority::Invalid }; // [[ComputedRoundingPriority]] Unicode::RoundingMode m_rounding_mode; // [[RoundingMode]] @@ -140,15 +152,6 @@ public: Value use_grouping_to_value(VM&) const; void set_use_grouping(StringOrBoolean const& use_grouping); - Unicode::Notation notation() const { return m_notation; } - StringView notation_string() const { return Unicode::notation_to_string(m_notation); } - void set_notation(StringView notation) { m_notation = Unicode::notation_from_string(notation); } - - bool has_compact_display() const { return m_compact_display.has_value(); } - Unicode::CompactDisplay compact_display() const { return *m_compact_display; } - StringView compact_display_string() const { return Unicode::compact_display_to_string(*m_compact_display); } - void set_compact_display(StringView compact_display) { m_compact_display = Unicode::compact_display_from_string(compact_display); } - Unicode::SignDisplay sign_display() const { return m_sign_display; } StringView sign_display_string() const { return Unicode::sign_display_to_string(m_sign_display); } void set_sign_display(StringView sign_display) { m_sign_display = Unicode::sign_display_from_string(sign_display); } @@ -156,7 +159,7 @@ public: NativeFunction* bound_format() const { return m_bound_format; } void set_bound_format(NativeFunction* bound_format) { m_bound_format = bound_format; } - Unicode::DisplayOptions display_options() const; + Unicode::DisplayOptions display_options() const override; private: explicit NumberFormat(Object& prototype); @@ -172,8 +175,6 @@ private: Optional m_unit; // [[Unit]] Optional m_unit_display; // [[UnitDisplay]] Unicode::Grouping m_use_grouping { Unicode::Grouping::False }; // [[UseGrouping]] - Unicode::Notation m_notation; // [[Notation]] - Optional m_compact_display; // [[CompactDisplay]] Unicode::SignDisplay m_sign_display; // [[SignDisplay]] GC::Ptr m_bound_format; // [[BoundFormat]] }; diff --git a/Libraries/LibJS/Runtime/Intl/PluralRules.cpp b/Libraries/LibJS/Runtime/Intl/PluralRules.cpp index 0276522b50a..addd67a23f4 100644 --- a/Libraries/LibJS/Runtime/Intl/PluralRules.cpp +++ b/Libraries/LibJS/Runtime/Intl/PluralRules.cpp @@ -45,8 +45,9 @@ Unicode::PluralCategory resolve_plural(PluralRules const& plural_rules, Value nu // 3. Let s be res.[[FormattedString]]. // 4. Let locale be pluralRules.[[Locale]]. // 5. Let type be pluralRules.[[Type]]. - // 6. Let p be PluralRuleSelect(locale, type, s). - // 7. Return the Record { [[PluralCategory]]: p, [[FormattedString]]: s }. + // 6. Let notation be pluralRules.[[Notation]]. + // 7. Let p be PluralRuleSelect(locale, type, notation, s). + // 8. Return the Record { [[PluralCategory]]: p, [[FormattedString]]: s }. return plural_rules.formatter().select_plural(number.as_double()); } @@ -65,7 +66,8 @@ ThrowCompletionOr resolve_plural_range(VM& vm, PluralRu // a. Return xp.[[PluralCategory]]. // 5. Let locale be pluralRules.[[Locale]]. // 6. Let type be pluralRules.[[Type]]. - // 7. Return PluralRuleSelectRange(locale, type, xp.[[PluralCategory]], yp.[[PluralCategory]]). + // 7. Let notation be pluralRules.[[Notation]]. + // 8. Return PluralRuleSelectRange(locale, type, notation, xp.[[PluralCategory]], yp.[[PluralCategory]]). return plural_rules.formatter().select_plural_range(start.as_double(), end.as_double()); } diff --git a/Libraries/LibJS/Runtime/Intl/PluralRulesConstructor.cpp b/Libraries/LibJS/Runtime/Intl/PluralRulesConstructor.cpp index d099f2df6eb..3e704d4437e 100644 --- a/Libraries/LibJS/Runtime/Intl/PluralRulesConstructor.cpp +++ b/Libraries/LibJS/Runtime/Intl/PluralRulesConstructor.cpp @@ -52,7 +52,7 @@ ThrowCompletionOr> PluralRulesConstructor::construct(FunctionObj auto locales_value = vm.argument(0); auto options_value = vm.argument(1); - // 2. Let pluralRules be ? OrdinaryCreateFromConstructor(NewTarget, "%Intl.PluralRules.prototype%", « [[InitializedPluralRules]], [[Locale]], [[Type]], [[MinimumIntegerDigits]], [[MinimumFractionDigits]], [[MaximumFractionDigits]], [[MinimumSignificantDigits]], [[MaximumSignificantDigits]], [[RoundingType]], [[RoundingIncrement]], [[RoundingMode]], [[ComputedRoundingPriority]], [[TrailingZeroDisplay]] »). + // 2. Let pluralRules be ? OrdinaryCreateFromConstructor(NewTarget, "%Intl.PluralRules.prototype%", « [[InitializedPluralRules]], [[Locale]], [[Type]], [[Notation]], [[MinimumIntegerDigits]], [[MinimumFractionDigits]], [[MaximumFractionDigits]], [[MinimumSignificantDigits]], [[MaximumSignificantDigits]], [[RoundingType]], [[RoundingIncrement]], [[RoundingMode]], [[ComputedRoundingPriority]], [[TrailingZeroDisplay]] »). auto plural_rules = TRY(ordinary_create_from_constructor(vm, new_target, &Intrinsics::intl_plural_rules_prototype)); // 3. Let optionsResolution be ? ResolveOptions(%Intl.PluralRules%, %Intl.PluralRules%.[[LocaleData]], locales, options, « COERCE-OPTIONS »). @@ -69,13 +69,26 @@ ThrowCompletionOr> PluralRulesConstructor::construct(FunctionObj // 8. Set pluralRules.[[Type]] to t. plural_rules->set_type(type.as_string().utf8_string_view()); + // 9. Let notation be ? GetOption(options, "notation", string, « "standard", "scientific", "engineering", "compact" », "standard"). + auto notation = TRY(get_option(vm, *options, vm.names.notation, OptionType::String, { "standard"sv, "scientific"sv, "engineering"sv, "compact"sv }, "standard"sv)); + + // 10. Set pluralRules.[[Notation]] to notation. + plural_rules->set_notation(notation.as_string().utf8_string_view()); + // 9. Perform ? SetNumberFormatDigitOptions(pluralRules, options, 0, 3, "standard"). TRY(set_number_format_digit_options(vm, plural_rules, *options, 0, 3, Unicode::Notation::Standard)); + // FIXME: Spec issue: The patch which added `notation` to Intl.PluralRules neglected to also add `compactDisplay` + // for when the notation is compact. TC39 is planning to add this option soon. We default to `short` for now + // to match the Intl.NumberFormat default, as LibUnicode requires the compact display to be set. See: + // https://github.com/tc39/ecma402/pull/989#issuecomment-2906752480 + if (plural_rules->notation() == Unicode::Notation::Compact) + plural_rules->set_compact_display("short"sv); + // Non-standard, create an ICU number formatter for this Intl object. auto formatter = Unicode::NumberFormat::create( result.icu_locale, - {}, + plural_rules->display_options(), plural_rules->rounding_options()); formatter->create_plural_rules(plural_rules->type()); diff --git a/Libraries/LibJS/Runtime/Intl/PluralRulesPrototype.cpp b/Libraries/LibJS/Runtime/Intl/PluralRulesPrototype.cpp index 30e54c66e24..aff4fdbbaad 100644 --- a/Libraries/LibJS/Runtime/Intl/PluralRulesPrototype.cpp +++ b/Libraries/LibJS/Runtime/Intl/PluralRulesPrototype.cpp @@ -66,6 +66,7 @@ JS_DEFINE_NATIVE_FUNCTION(PluralRulesPrototype::resolved_options) // i. Perform ! CreateDataPropertyOrThrow(options, p, v). MUST(options->create_data_property_or_throw(vm.names.locale, PrimitiveString::create(vm, plural_rules->locale()))); MUST(options->create_data_property_or_throw(vm.names.type, PrimitiveString::create(vm, plural_rules->type_string()))); + MUST(options->create_data_property_or_throw(vm.names.notation, PrimitiveString::create(vm, plural_rules->notation_string()))); MUST(options->create_data_property_or_throw(vm.names.minimumIntegerDigits, Value(plural_rules->min_integer_digits()))); if (plural_rules->has_min_fraction_digits()) MUST(options->create_data_property_or_throw(vm.names.minimumFractionDigits, Value(plural_rules->min_fraction_digits()))); diff --git a/Libraries/LibJS/Tests/builtins/Intl/PluralRules/PluralRules.js b/Libraries/LibJS/Tests/builtins/Intl/PluralRules/PluralRules.js index cfa55577271..6125594068d 100644 --- a/Libraries/LibJS/Tests/builtins/Intl/PluralRules/PluralRules.js +++ b/Libraries/LibJS/Tests/builtins/Intl/PluralRules/PluralRules.js @@ -117,6 +117,12 @@ describe("errors", () => { }).toThrowWithMessage(RangeError, "Value 22 is NaN or is not between 1 and 21"); }); + test("notation option is invalid ", () => { + expect(() => { + new Intl.PluralRules("en", { notation: "hello!" }); + }).toThrowWithMessage(RangeError, "hello! is not a valid value for option notation"); + }); + test("roundingPriority option is invalid", () => { expect(() => { new Intl.PluralRules("en", { roundingPriority: "hello!" }); @@ -239,6 +245,14 @@ describe("normal behavior", () => { } }); + test("all valid notation options", () => { + ["standard", "scientific", "engineering", "compact"].forEach(notation => { + expect(() => { + new Intl.PluralRules("en", { notation: notation }); + }).not.toThrow(); + }); + }); + test("all valid roundingPriority options", () => { ["auto", "morePrecision", "lessPrecision"].forEach(roundingPriority => { expect(() => { diff --git a/Libraries/LibJS/Tests/builtins/Intl/PluralRules/PluralRules.prototype.resolvedOptions.js b/Libraries/LibJS/Tests/builtins/Intl/PluralRules/PluralRules.prototype.resolvedOptions.js index 204251ac628..5062354bd20 100644 --- a/Libraries/LibJS/Tests/builtins/Intl/PluralRules/PluralRules.prototype.resolvedOptions.js +++ b/Libraries/LibJS/Tests/builtins/Intl/PluralRules/PluralRules.prototype.resolvedOptions.js @@ -78,6 +78,16 @@ describe("correct behavior", () => { expect(en3.resolvedOptions().maximumSignificantDigits).toBe(10); }); + test("notation", () => { + const en1 = new Intl.PluralRules("en"); + expect(en1.resolvedOptions().notation).toBe("standard"); + + ["standard", "scientific", "engineering", "compact"].forEach(notation => { + const en2 = new Intl.PluralRules("en", { notation: notation }); + expect(en2.resolvedOptions().notation).toBe(notation); + }); + }); + test("plural categories", () => { const enCardinal = new Intl.PluralRules("en", { type: "cardinal" }).resolvedOptions(); expect(enCardinal.pluralCategories).toEqual(["one", "other"]); diff --git a/Libraries/LibJS/Tests/builtins/Intl/PluralRules/PluralRules.prototype.select.js b/Libraries/LibJS/Tests/builtins/Intl/PluralRules/PluralRules.prototype.select.js index 06099aba503..f9a61da9803 100644 --- a/Libraries/LibJS/Tests/builtins/Intl/PluralRules/PluralRules.prototype.select.js +++ b/Libraries/LibJS/Tests/builtins/Intl/PluralRules/PluralRules.prototype.select.js @@ -139,4 +139,25 @@ describe("correct behavior", () => { expect(mk.select(27)).toBe("many"); expect(mk.select(28)).toBe("many"); }); + + test("notation", () => { + const standard = new Intl.PluralRules("fr", { notation: "standard" }); + const engineering = new Intl.PluralRules("fr", { notation: "engineering" }); + const scientific = new Intl.PluralRules("fr", { notation: "scientific" }); + const compact = new Intl.PluralRules("fr", { notation: "compact" }); + + // prettier-ignore + const data = [ + { value: 1e6, standard: "many", engineering: "many", scientific: "many", compact: "many" }, + { value: 1.5e6, standard: "other", engineering: "many", scientific: "many", compact: "many" }, + { value: 1e-6, standard: "one", engineering: "many", scientific: "many", compact: "one" }, + ]; + + data.forEach(d => { + expect(standard.select(d.value)).toBe(d.standard); + expect(engineering.select(d.value)).toBe(d.engineering); + expect(scientific.select(d.value)).toBe(d.scientific); + expect(compact.select(d.value)).toBe(d.compact); + }); + }); });