From ca0d926036d9e530dc21950f5b1b3bfe68a28c2c Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Mon, 4 Sep 2023 09:15:02 -0400 Subject: [PATCH] LibJS: Use decimal compact patterns for currency style sub-patterns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When formatting a currency style pattern with compact notation, we were (trying to) doubly insert the currency symbol into the formatted string. We would first look up the currency pattern in GetNumberFormatPattern (for the en locale, this is "¤#,##0.00", which our generator transforms to "{currency}{number}"). When we hit the "{number}" field, NumberFormat will do a second lookup for the compact pattern to use for the number being formatted. By using the currency compact patterns, we receive a second pattern that also has the currency symbol (for the en locale, if formatting the number 1000, this is "¤0K", which our generator transforms to "{currency}{number}{compactIdentifier:0}". This second lookup is not supposed to have currency symbols (or any other symbols), thus we hit a VERIFY_NOT_REACHED(). Instead, we are meant to use the decimal compact pattern, and allow the currency symbol to be handled by only the outer currency pattern. --- .../LibJS/Runtime/Intl/NumberFormat.cpp | 12 +- .../NumberFormat.prototype.format.js | 140 ++++++++++++++++++ 2 files changed, 144 insertions(+), 8 deletions(-) diff --git a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp index 7f5bad9690..94b91bf3b5 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp @@ -1634,15 +1634,11 @@ int compute_exponent_for_magnitude(NumberFormat& number_format, int magnitude) // b. Let exponent be an implementation- and locale-dependent (ILD) integer by which to scale a number of the given magnitude in compact notation for the current locale. // c. Return exponent. - Vector<::Locale::NumberFormat> format_rules; - - if (number_format.style() == NumberFormat::Style::Currency) - format_rules = ::Locale::get_compact_number_system_formats(number_format.data_locale(), number_format.numbering_system(), ::Locale::CompactNumberFormatType::CurrencyShort); - else if (number_format.compact_display() == NumberFormat::CompactDisplay::Long) - format_rules = ::Locale::get_compact_number_system_formats(number_format.data_locale(), number_format.numbering_system(), ::Locale::CompactNumberFormatType::DecimalLong); - else - format_rules = ::Locale::get_compact_number_system_formats(number_format.data_locale(), number_format.numbering_system(), ::Locale::CompactNumberFormatType::DecimalShort); + auto compact_format_type = number_format.compact_display() == NumberFormat::CompactDisplay::Short || number_format.style() == NumberFormat::Style::Currency + ? ::Locale::CompactNumberFormatType::DecimalShort + : ::Locale::CompactNumberFormatType::DecimalLong; + auto format_rules = ::Locale::get_compact_number_system_formats(number_format.data_locale(), number_format.numbering_system(), compact_format_type); ::Locale::NumberFormat const* best_number_format = nullptr; for (auto const& format_rule : format_rules) { diff --git a/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.format.js b/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.format.js index cd9db5bfbe..9152b43b69 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.format.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.format.js @@ -1298,6 +1298,146 @@ describe("style=currency", () => { expect(ar2.format(1.23)).toBe("1.23 دولار أمريكي"); }); + test("notation=compact, compactDisplay=long", () => { + const en = new Intl.NumberFormat("en", { + style: "currency", + currency: "USD", + notation: "compact", + compactDisplay: "long", + }); + expect(en.format(1)).toBe("$1"); + expect(en.format(1200)).toBe("$1.2K"); + expect(en.format(1290)).toBe("$1.3K"); + expect(en.format(12000)).toBe("$12K"); + expect(en.format(12900)).toBe("$13K"); + expect(en.format(1200000)).toBe("$1.2M"); + expect(en.format(1290000)).toBe("$1.3M"); + expect(en.format(12000000)).toBe("$12M"); + expect(en.format(12900000)).toBe("$13M"); + + const ar = new Intl.NumberFormat("ar", { + style: "currency", + currency: "USD", + notation: "compact", + compactDisplay: "long", + }); + expect(ar.format(1)).toBe("‏١ US$"); + expect(ar.format(1200)).toBe("‏١٫٢ ألف US$"); + expect(ar.format(1290)).toBe("‏١٫٣ ألف US$"); + expect(ar.format(12000)).toBe("‏١٢ ألف US$"); + expect(ar.format(12900)).toBe("‏١٣ ألف US$"); + expect(ar.format(1200000)).toBe("‏١٫٢ مليون US$"); + expect(ar.format(1290000)).toBe("‏١٫٣ مليون US$"); + expect(ar.format(12000000)).toBe("‏١٢ مليون US$"); + expect(ar.format(12900000)).toBe("‏١٣ مليون US$"); + + const ja = new Intl.NumberFormat("ja", { + style: "currency", + currency: "JPY", + notation: "compact", + compactDisplay: "long", + }); + expect(ja.format(1)).toBe("¥1"); + expect(ja.format(1200)).toBe("¥1200"); + expect(ja.format(1290)).toBe("¥1290"); + expect(ja.format(12000)).toBe("¥1.2万"); + expect(ja.format(12900)).toBe("¥1.3万"); + expect(ja.format(1200000)).toBe("¥120万"); + expect(ja.format(1290000)).toBe("¥129万"); + expect(ja.format(12000000)).toBe("¥1200万"); + expect(ja.format(12900000)).toBe("¥1290万"); + expect(ja.format(120000000)).toBe("¥1.2億"); + expect(ja.format(129000000)).toBe("¥1.3億"); + expect(ja.format(12000000000)).toBe("¥120億"); + expect(ja.format(12900000000)).toBe("¥129億"); + + const de = new Intl.NumberFormat("de", { + style: "currency", + currency: "EUR", + notation: "compact", + compactDisplay: "long", + }); + expect(de.format(1)).toBe("1 €"); + expect(de.format(1200)).toBe("1200 €"); + expect(de.format(1290)).toBe("1290 €"); + expect(de.format(12000)).toBe("12.000 €"); + expect(de.format(12900)).toBe("12.900 €"); + expect(de.format(1200000)).toBe("1,2 Mio. €"); + expect(de.format(1290000)).toBe("1,3 Mio. €"); + expect(de.format(12000000)).toBe("12 Mio. €"); + expect(de.format(12900000)).toBe("13 Mio. €"); + }); + + test("notation=compact, compactDisplay=short", () => { + const en = new Intl.NumberFormat("en", { + style: "currency", + currency: "USD", + notation: "compact", + compactDisplay: "short", + }); + expect(en.format(1)).toBe("$1"); + expect(en.format(1200)).toBe("$1.2K"); + expect(en.format(1290)).toBe("$1.3K"); + expect(en.format(12000)).toBe("$12K"); + expect(en.format(12900)).toBe("$13K"); + expect(en.format(1200000)).toBe("$1.2M"); + expect(en.format(1290000)).toBe("$1.3M"); + expect(en.format(12000000)).toBe("$12M"); + expect(en.format(12900000)).toBe("$13M"); + + const ar = new Intl.NumberFormat("ar", { + style: "currency", + currency: "USD", + notation: "compact", + compactDisplay: "short", + }); + expect(ar.format(1)).toBe("‏١ US$"); + expect(ar.format(1200)).toBe("‏١٫٢ ألف US$"); + expect(ar.format(1290)).toBe("‏١٫٣ ألف US$"); + expect(ar.format(12000)).toBe("‏١٢ ألف US$"); + expect(ar.format(12900)).toBe("‏١٣ ألف US$"); + expect(ar.format(1200000)).toBe("‏١٫٢ مليون US$"); + expect(ar.format(1290000)).toBe("‏١٫٣ مليون US$"); + expect(ar.format(12000000)).toBe("‏١٢ مليون US$"); + expect(ar.format(12900000)).toBe("‏١٣ مليون US$"); + + const ja = new Intl.NumberFormat("ja", { + style: "currency", + currency: "JPY", + notation: "compact", + compactDisplay: "short", + }); + expect(ja.format(1)).toBe("¥1"); + expect(ja.format(1200)).toBe("¥1200"); + expect(ja.format(1290)).toBe("¥1290"); + expect(ja.format(12000)).toBe("¥1.2万"); + expect(ja.format(12900)).toBe("¥1.3万"); + expect(ja.format(1200000)).toBe("¥120万"); + expect(ja.format(1290000)).toBe("¥129万"); + expect(ja.format(12000000)).toBe("¥1200万"); + expect(ja.format(12900000)).toBe("¥1290万"); + expect(ja.format(120000000)).toBe("¥1.2億"); + expect(ja.format(129000000)).toBe("¥1.3億"); + expect(ja.format(12000000000)).toBe("¥120億"); + expect(ja.format(12900000000)).toBe("¥129億"); + + const de = new Intl.NumberFormat("de", { + style: "currency", + currency: "EUR", + notation: "compact", + compactDisplay: "short", + }); + expect(de.format(1)).toBe("1 €"); + expect(de.format(1200)).toBe("1200 €"); + expect(de.format(1290)).toBe("1290 €"); + expect(de.format(12000)).toBe("12.000 €"); + expect(de.format(12900)).toBe("12.900 €"); + expect(de.format(1200000)).toBe("1,2 Mio. €"); + expect(de.format(1290000)).toBe("1,3 Mio. €"); + expect(de.format(12000000)).toBe("12 Mio. €"); + expect(de.format(12900000)).toBe("13 Mio. €"); + }); + test("signDisplay=never", () => { const en1 = new Intl.NumberFormat("en", { style: "currency",