mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-28 19:59:17 +00:00
LibJS: Add notation to Intl.PluralRules
This is a normative change in the ECMA-402 spec. See:
a7ff535
This commit is contained in:
parent
b16f34767e
commit
8e5cc74eb1
Notes:
github-actions[bot]
2025-05-27 14:40:25 +00:00
Author: https://github.com/trflynn89
Commit: 8e5cc74eb1
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/4894
9 changed files with 101 additions and 29 deletions
|
@ -717,6 +717,8 @@ ErrorOr<void> 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()) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<int> m_max_fraction_digits {}; // [[MaximumFractionDigits]]
|
||||
Optional<int> m_min_significant_digits {}; // [[MinimumSignificantDigits]]
|
||||
Optional<int> m_max_significant_digits {}; // [[MaximumSignificantDigits]]
|
||||
Unicode::Notation m_notation; // [[Notation]]
|
||||
Optional<Unicode::CompactDisplay> 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<String> m_unit; // [[Unit]]
|
||||
Optional<Unicode::Style> m_unit_display; // [[UnitDisplay]]
|
||||
Unicode::Grouping m_use_grouping { Unicode::Grouping::False }; // [[UseGrouping]]
|
||||
Unicode::Notation m_notation; // [[Notation]]
|
||||
Optional<Unicode::CompactDisplay> m_compact_display; // [[CompactDisplay]]
|
||||
Unicode::SignDisplay m_sign_display; // [[SignDisplay]]
|
||||
GC::Ptr<NativeFunction> m_bound_format; // [[BoundFormat]]
|
||||
};
|
||||
|
|
|
@ -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<Unicode::PluralCategory> 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());
|
||||
}
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ ThrowCompletionOr<GC::Ref<Object>> 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<PluralRules>(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<GC::Ref<Object>> 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());
|
||||
|
|
|
@ -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())));
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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"]);
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue