diff options
author | Timothy Flynn <trflynn89@pm.me> | 2021-11-16 13:56:52 -0500 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2021-11-16 23:14:09 +0000 |
commit | a1d5849e675616b12ebc62ed76e1c565f72d5c1a (patch) | |
tree | 986c351c49675124b1a2dbae70691aa00c8d1415 | |
parent | cafb717486eed8e24d9f1d6b0a845b270674562c (diff) | |
download | serenity-a1d5849e675616b12ebc62ed76e1c565f72d5c1a.zip |
LibJS: Implement unit number formatting
4 files changed, 358 insertions, 17 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp index 9373105fa4..5f3d36394b 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp @@ -617,8 +617,10 @@ Vector<PatternPartition> partition_number_pattern(NumberFormat& number_format, d number = format_number_result.rounded_number; } + Unicode::NumberFormat found_pattern {}; + // 5. Let pattern be GetNumberFormatPattern(numberFormat, x). - auto pattern = get_number_format_pattern(number_format, number); + auto pattern = get_number_format_pattern(number_format, number, found_pattern); if (!pattern.has_value()) return {}; @@ -672,23 +674,20 @@ Vector<PatternPartition> partition_number_pattern(NumberFormat& number_format, d } // g. Else if p is equal to "unitPrefix" and numberFormat.[[Style]] is "unit", then - else if ((part == "unitPrefix"sv) && (number_format.style() == NumberFormat::Style::Unit)) { - // i. Let unit be numberFormat.[[Unit]]. - // ii. Let unitDisplay be numberFormat.[[UnitDisplay]]. - // iii. Let mu be an ILD String value representing unit before x in unitDisplay form, which may depend on x in languages having different plural forms. - // iv. Append a new Record { [[Type]]: "unit", [[Value]]: mu } as the last element of result. + // h. Else if p is equal to "unitSuffix" and numberFormat.[[Style]] is "unit", then + else if ((part.starts_with("unitIdentifier:"sv)) && (number_format.style() == NumberFormat::Style::Unit)) { + // Note: Our implementation combines "unitPrefix" and "unitSuffix" into one field, "unitIdentifier". - // FIXME: LibUnicode will need to parse the cldr-units package. - } + auto identifier_index = part.substring_view("unitIdentifier:"sv.length()).to_uint(); + VERIFY(identifier_index.has_value()); - // h. Else if p is equal to "unitSuffix" and numberFormat.[[Style]] is "unit", then - else if ((part == "unitSuffix"sv) && (number_format.style() == NumberFormat::Style::Unit)) { // i. Let unit be numberFormat.[[Unit]]. // ii. Let unitDisplay be numberFormat.[[UnitDisplay]]. - // iii. Let mu be an ILD String value representing unit after x in unitDisplay form, which may depend on x in languages having different plural forms. - // iv. Append a new Record { [[Type]]: "unit", [[Value]]: mu } as the last element of result. + // iii. Let mu be an ILD String value representing unit before x in unitDisplay form, which may depend on x in languages having different plural forms. + auto unit_identifier = found_pattern.identifiers[*identifier_index]; - // FIXME: LibUnicode will need to parse the cldr-units package. + // iv. Append a new Record { [[Type]]: "unit", [[Value]]: mu } as the last element of result. + result.append({ "unit"sv, unit_identifier }); } // i. Else if p is equal to "currencyCode" and numberFormat.[[Style]] is "currency", then @@ -1329,7 +1328,7 @@ ThrowCompletionOr<void> set_number_format_unit_options(GlobalObject& global_obje } // 15.1.14 GetNumberFormatPattern ( numberFormat, x ), https://tc39.es/ecma402/#sec-getnumberformatpattern -Optional<Variant<StringView, String>> get_number_format_pattern(NumberFormat& number_format, double number) +Optional<Variant<StringView, String>> get_number_format_pattern(NumberFormat& number_format, double number, Unicode::NumberFormat& found_pattern) { // 1. Let localeData be %NumberFormat%.[[LocaleData]]. // 2. Let dataLocale be numberFormat.[[DataLocale]]. @@ -1347,7 +1346,7 @@ Optional<Variant<StringView, String>> get_number_format_pattern(NumberFormat& nu break; // 8. Else if style is "unit", then - case NumberFormat::Style::Unit: + case NumberFormat::Style::Unit: { // a. Let unit be numberFormat.[[Unit]]. // b. Let unitDisplay be numberFormat.[[UnitDisplay]]. // c. Let patterns be patterns.[[unit]]. @@ -1355,9 +1354,23 @@ Optional<Variant<StringView, String>> get_number_format_pattern(NumberFormat& nu // i. Let unit be "fallback". // e. Let patterns be patterns.[[<unit>]]. // f. Let patterns be patterns.[[<unitDisplay>]]. + Vector<Unicode::NumberFormat> formats; - // FIXME: LibUnicode will need to parse the cldr-units package. + switch (number_format.unit_display()) { + case NumberFormat::UnitDisplay::Long: + formats = Unicode::get_unit_formats(number_format.data_locale(), number_format.unit(), Unicode::Style::Long); + break; + case NumberFormat::UnitDisplay::Short: + formats = Unicode::get_unit_formats(number_format.data_locale(), number_format.unit(), Unicode::Style::Short); + break; + case NumberFormat::UnitDisplay::Narrow: + formats = Unicode::get_unit_formats(number_format.data_locale(), number_format.unit(), Unicode::Style::Narrow); + break; + } + + patterns = Unicode::select_pattern_with_plurality(formats, number); break; + } // 9. Else if style is "currency", then case NumberFormat::Style::Currency: @@ -1474,6 +1487,8 @@ Optional<Variant<StringView, String>> get_number_format_pattern(NumberFormat& nu VERIFY_NOT_REACHED(); } + found_pattern = patterns.release_value(); + // Handling of steps 9b/9g: Depending on the currency display and the format pattern found above, // we might need to mutate the format pattern to inject a space between the currency display and // the currency number. diff --git a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.h b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.h index 0f676d6a01..c3088efe7a 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.h +++ b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.h @@ -211,7 +211,7 @@ Array* format_numeric_to_parts(GlobalObject& global_object, NumberFormat& number RawFormatResult to_raw_precision(double number, int min_precision, int max_precision); RawFormatResult to_raw_fixed(double number, int min_fraction, int max_fraction); ThrowCompletionOr<void> set_number_format_unit_options(GlobalObject& global_object, NumberFormat& intl_object, Object const& options); -Optional<Variant<StringView, String>> get_number_format_pattern(NumberFormat& number_format, double number); +Optional<Variant<StringView, String>> get_number_format_pattern(NumberFormat& number_format, double number, Unicode::NumberFormat& found_pattern); Optional<StringView> get_notation_sub_pattern(NumberFormat& number_format, int exponent); int compute_exponent(NumberFormat& number_format, double number); int compute_exponent_for_magniude(NumberFormat& number_format, int magnitude); 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 cc580da8ab..77a4df82d6 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 @@ -889,3 +889,119 @@ describe("style=currency", () => { expect(ar2.format(-1)).toBe("\u061c-\u0661\u066b\u0660\u0660\u00a0US$"); }); }); + +describe("style=unit", () => { + test("unitDisplay=long", () => { + const en1 = new Intl.NumberFormat("en", { + style: "unit", + unit: "gigabit", + unitDisplay: "long", + }); + expect(en1.format(1)).toBe("1 gigabit"); + expect(en1.format(1.2)).toBe("1.2 gigabits"); + expect(en1.format(123)).toBe("123 gigabits"); + + const en2 = new Intl.NumberFormat("en", { + style: "unit", + unit: "kilometer-per-hour", + unitDisplay: "long", + }); + expect(en2.format(1)).toBe("1 kilometer per hour"); + expect(en2.format(1.2)).toBe("1.2 kilometers per hour"); + expect(en2.format(123)).toBe("123 kilometers per hour"); + + const ar = new Intl.NumberFormat("ar", { + style: "unit", + unit: "foot", + unitDisplay: "long", + }); + expect(ar.format(1)).toBe("ูุฏู
"); + expect(ar.format(1.2)).toBe("\u0661\u066b\u0662 ูุฏู
"); + expect(ar.format(123)).toBe("\u0661\u0662\u0663 ูุฏู
"); + + const ja = new Intl.NumberFormat("ja", { + style: "unit", + unit: "kilometer-per-hour", + unitDisplay: "long", + }); + expect(ja.format(1)).toBe("ๆ้ 1 ใญใญใกใผใใซ"); + expect(ja.format(1.2)).toBe("ๆ้ 1.2 ใญใญใกใผใใซ"); + expect(ja.format(123)).toBe("ๆ้ 123 ใญใญใกใผใใซ"); + }); + + test("unitDisplay=short", () => { + const en1 = new Intl.NumberFormat("en", { + style: "unit", + unit: "gigabit", + unitDisplay: "short", + }); + expect(en1.format(1)).toBe("1 Gb"); + expect(en1.format(1.2)).toBe("1.2 Gb"); + expect(en1.format(123)).toBe("123 Gb"); + + const en2 = new Intl.NumberFormat("en", { + style: "unit", + unit: "kilometer-per-hour", + unitDisplay: "short", + }); + expect(en2.format(1)).toBe("1 km/h"); + expect(en2.format(1.2)).toBe("1.2 km/h"); + expect(en2.format(123)).toBe("123 km/h"); + + const ar = new Intl.NumberFormat("ar", { + style: "unit", + unit: "foot", + unitDisplay: "short", + }); + expect(ar.format(1)).toBe("ูุฏู
"); + expect(ar.format(1.2)).toBe("\u0661\u066b\u0662 ูุฏู
"); + expect(ar.format(123)).toBe("\u0661\u0662\u0663 ูุฏู
"); + + const ja = new Intl.NumberFormat("ja", { + style: "unit", + unit: "kilometer-per-hour", + unitDisplay: "short", + }); + expect(ja.format(1)).toBe("1 km/h"); + expect(ja.format(1.2)).toBe("1.2 km/h"); + expect(ja.format(123)).toBe("123 km/h"); + }); + + test("unitDisplay=narrow", () => { + const en1 = new Intl.NumberFormat("en", { + style: "unit", + unit: "gigabit", + unitDisplay: "narrow", + }); + expect(en1.format(1)).toBe("1Gb"); + expect(en1.format(1.2)).toBe("1.2Gb"); + expect(en1.format(123)).toBe("123Gb"); + + const en2 = new Intl.NumberFormat("en", { + style: "unit", + unit: "kilometer-per-hour", + unitDisplay: "narrow", + }); + expect(en2.format(1)).toBe("1km/h"); + expect(en2.format(1.2)).toBe("1.2km/h"); + expect(en2.format(123)).toBe("123km/h"); + + const ar = new Intl.NumberFormat("ar", { + style: "unit", + unit: "foot", + unitDisplay: "narrow", + }); + expect(ar.format(1)).toBe("ูุฏู
"); + expect(ar.format(1.2)).toBe("\u0661\u066b\u0662 ูุฏู
"); + expect(ar.format(123)).toBe("\u0661\u0662\u0663 ูุฏู
ูุง"); + + const ja = new Intl.NumberFormat("ja", { + style: "unit", + unit: "kilometer-per-hour", + unitDisplay: "narrow", + }); + expect(ja.format(1)).toBe("1km/h"); + expect(ja.format(1.2)).toBe("1.2km/h"); + expect(ja.format(123)).toBe("123km/h"); + }); +}); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.formatToParts.js b/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.formatToParts.js index d787e5ba4b..5a144e1ebc 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.formatToParts.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.formatToParts.js @@ -1100,3 +1100,213 @@ describe("style=currency", () => { ]); }); }); + +describe("style=unit", () => { + test("unitDisplay=long", () => { + const en1 = new Intl.NumberFormat("en", { + style: "unit", + unit: "gigabit", + unitDisplay: "long", + }); + expect(en1.formatToParts(1)).toEqual([ + { type: "integer", value: "1" }, + { type: "literal", value: " " }, + { type: "unit", value: "gigabit" }, + ]); + expect(en1.formatToParts(1.2)).toEqual([ + { type: "integer", value: "1" }, + { type: "decimal", value: "." }, + { type: "fraction", value: "2" }, + { type: "literal", value: " " }, + { type: "unit", value: "gigabits" }, + ]); + + const en2 = new Intl.NumberFormat("en", { + style: "unit", + unit: "kilometer-per-hour", + unitDisplay: "long", + }); + expect(en2.formatToParts(1)).toEqual([ + { type: "integer", value: "1" }, + { type: "literal", value: " " }, + { type: "unit", value: "kilometer per hour" }, + ]); + expect(en2.formatToParts(1.2)).toEqual([ + { type: "integer", value: "1" }, + { type: "decimal", value: "." }, + { type: "fraction", value: "2" }, + { type: "literal", value: " " }, + { type: "unit", value: "kilometers per hour" }, + ]); + + const ar = new Intl.NumberFormat("ar", { + style: "unit", + unit: "foot", + unitDisplay: "long", + }); + expect(ar.formatToParts(1)).toEqual([{ type: "unit", value: "ูุฏู
" }]); + expect(ar.formatToParts(1.2)).toEqual([ + { type: "integer", value: "\u0661" }, + { type: "decimal", value: "\u066b" }, + { type: "fraction", value: "\u0662" }, + { type: "literal", value: " " }, + { type: "unit", value: "ูุฏู
" }, + ]); + + const ja = new Intl.NumberFormat("ja", { + style: "unit", + unit: "kilometer-per-hour", + unitDisplay: "long", + }); + expect(ja.formatToParts(1)).toEqual([ + { type: "unit", value: "ๆ้" }, + { type: "literal", value: " " }, + { type: "integer", value: "1" }, + { type: "literal", value: " " }, + { type: "unit", value: "ใญใญใกใผใใซ" }, + ]); + expect(ja.formatToParts(1.2)).toEqual([ + { type: "unit", value: "ๆ้" }, + { type: "literal", value: " " }, + { type: "integer", value: "1" }, + { type: "decimal", value: "." }, + { type: "fraction", value: "2" }, + { type: "literal", value: " " }, + { type: "unit", value: "ใญใญใกใผใใซ" }, + ]); + }); + + test("unitDisplay=short", () => { + const en1 = new Intl.NumberFormat("en", { + style: "unit", + unit: "gigabit", + unitDisplay: "short", + }); + expect(en1.formatToParts(1)).toEqual([ + { type: "integer", value: "1" }, + { type: "literal", value: " " }, + { type: "unit", value: "Gb" }, + ]); + expect(en1.formatToParts(1.2)).toEqual([ + { type: "integer", value: "1" }, + { type: "decimal", value: "." }, + { type: "fraction", value: "2" }, + { type: "literal", value: " " }, + { type: "unit", value: "Gb" }, + ]); + + const en2 = new Intl.NumberFormat("en", { + style: "unit", + unit: "kilometer-per-hour", + unitDisplay: "short", + }); + expect(en2.formatToParts(1)).toEqual([ + { type: "integer", value: "1" }, + { type: "literal", value: " " }, + { type: "unit", value: "km/h" }, + ]); + expect(en2.formatToParts(1.2)).toEqual([ + { type: "integer", value: "1" }, + { type: "decimal", value: "." }, + { type: "fraction", value: "2" }, + { type: "literal", value: " " }, + { type: "unit", value: "km/h" }, + ]); + + const ar = new Intl.NumberFormat("ar", { + style: "unit", + unit: "foot", + unitDisplay: "short", + }); + expect(ar.formatToParts(1)).toEqual([{ type: "unit", value: "ูุฏู
" }]); + expect(ar.formatToParts(1.2)).toEqual([ + { type: "integer", value: "\u0661" }, + { type: "decimal", value: "\u066b" }, + { type: "fraction", value: "\u0662" }, + { type: "literal", value: " " }, + { type: "unit", value: "ูุฏู
" }, + ]); + + const ja = new Intl.NumberFormat("ja", { + style: "unit", + unit: "kilometer-per-hour", + unitDisplay: "short", + }); + expect(ja.formatToParts(1)).toEqual([ + { type: "integer", value: "1" }, + { type: "literal", value: " " }, + { type: "unit", value: "km/h" }, + ]); + expect(ja.formatToParts(1.2)).toEqual([ + { type: "integer", value: "1" }, + { type: "decimal", value: "." }, + { type: "fraction", value: "2" }, + { type: "literal", value: " " }, + { type: "unit", value: "km/h" }, + ]); + }); + + test("unitDisplay=narrow", () => { + const en1 = new Intl.NumberFormat("en", { + style: "unit", + unit: "gigabit", + unitDisplay: "narrow", + }); + expect(en1.formatToParts(1)).toEqual([ + { type: "integer", value: "1" }, + { type: "unit", value: "Gb" }, + ]); + expect(en1.formatToParts(1.2)).toEqual([ + { type: "integer", value: "1" }, + { type: "decimal", value: "." }, + { type: "fraction", value: "2" }, + { type: "unit", value: "Gb" }, + ]); + + const en2 = new Intl.NumberFormat("en", { + style: "unit", + unit: "kilometer-per-hour", + unitDisplay: "narrow", + }); + expect(en2.formatToParts(1)).toEqual([ + { type: "integer", value: "1" }, + { type: "unit", value: "km/h" }, + ]); + expect(en2.formatToParts(1.2)).toEqual([ + { type: "integer", value: "1" }, + { type: "decimal", value: "." }, + { type: "fraction", value: "2" }, + { type: "unit", value: "km/h" }, + ]); + + const ar = new Intl.NumberFormat("ar", { + style: "unit", + unit: "foot", + unitDisplay: "narrow", + }); + expect(ar.formatToParts(1)).toEqual([{ type: "unit", value: "ูุฏู
" }]); + expect(ar.formatToParts(1.2)).toEqual([ + { type: "integer", value: "\u0661" }, + { type: "decimal", value: "\u066b" }, + { type: "fraction", value: "\u0662" }, + { type: "literal", value: " " }, + { type: "unit", value: "ูุฏู
" }, + ]); + + const ja = new Intl.NumberFormat("ja", { + style: "unit", + unit: "kilometer-per-hour", + unitDisplay: "narrow", + }); + expect(ja.formatToParts(1)).toEqual([ + { type: "integer", value: "1" }, + { type: "unit", value: "km/h" }, + ]); + expect(ja.formatToParts(1.2)).toEqual([ + { type: "integer", value: "1" }, + { type: "decimal", value: "." }, + { type: "fraction", value: "2" }, + { type: "unit", value: "km/h" }, + ]); + }); +}); |