mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 08:47:34 +00:00
LibJS: Implement number grouping for Intl.NumberFormat
For example, in en-US, the number 123456 should be formatted as the string "123,456". In en-IN, it should be formatted as "1,23,456".
This commit is contained in:
parent
3b7f5af042
commit
15c5fbd9e9
3 changed files with 171 additions and 12 deletions
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
#include <AK/Array.h>
|
||||
#include <AK/Utf8View.h>
|
||||
#include <LibJS/Runtime/Array.h>
|
||||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/Intl/NumberFormat.h>
|
||||
|
@ -807,12 +808,43 @@ static String replace_digits_for_number_format(NumberFormat& number_format, Stri
|
|||
return builder.build();
|
||||
}
|
||||
|
||||
static Vector<StringView> separate_integer_into_groups(Unicode::NumberGroupings const& grouping_sizes, StringView integer)
|
||||
{
|
||||
Utf8View utf8_integer { integer };
|
||||
Vector<StringView> groups;
|
||||
|
||||
auto add_group = [&](size_t index, size_t length) {
|
||||
groups.prepend(utf8_integer.unicode_substring_view(index, length).as_string());
|
||||
};
|
||||
|
||||
if (utf8_integer.length() > grouping_sizes.primary_grouping_size) {
|
||||
size_t index = utf8_integer.length() - grouping_sizes.primary_grouping_size;
|
||||
add_group(index, grouping_sizes.primary_grouping_size);
|
||||
|
||||
while (index > grouping_sizes.secondary_grouping_size) {
|
||||
index -= grouping_sizes.secondary_grouping_size;
|
||||
add_group(index, grouping_sizes.secondary_grouping_size);
|
||||
}
|
||||
|
||||
if (index > 0)
|
||||
add_group(0, index);
|
||||
} else {
|
||||
groups.append(integer);
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
// 15.1.7 PartitionNotationSubPattern ( numberFormat, x, n, exponent ), https://tc39.es/ecma402/#sec-partitionnotationsubpattern
|
||||
Vector<PatternPartition> partition_notation_sub_pattern(NumberFormat& number_format, double number, String formatted_string, int exponent)
|
||||
{
|
||||
// 1. Let result be a new empty List.
|
||||
Vector<PatternPartition> result;
|
||||
|
||||
auto grouping_sizes = Unicode::get_number_system_groupings(number_format.data_locale(), number_format.numbering_system());
|
||||
if (!grouping_sizes.has_value())
|
||||
return {};
|
||||
|
||||
// 2. If x is NaN, then
|
||||
if (Value(number).is_nan()) {
|
||||
// a. Append a new Record { [[Type]]: "nan", [[Value]]: n } as the last element of result.
|
||||
|
@ -870,19 +902,36 @@ Vector<PatternPartition> partition_notation_sub_pattern(NumberFormat& number_for
|
|||
}
|
||||
|
||||
// 6. If the numberFormat.[[UseGrouping]] is true, then
|
||||
if (number_format.use_grouping()) {
|
||||
// a. Let groupSepSymbol be the implementation-, locale-, and numbering system-dependent (ILND) String representing the grouping separator.
|
||||
// b. Let groups be a List whose elements are, in left to right order, the substrings defined by ILND set of locations within the integer.
|
||||
// c. Assert: The number of elements in groups List is greater than 0.
|
||||
// d. Repeat, while groups List is not empty,
|
||||
// i. Remove the first element from groups and let integerGroup be the value of that element.
|
||||
// ii. Append a new Record { [[Type]]: "integer", [[Value]]: integerGroup } as the last element of result.
|
||||
// iii. If groups List is not empty, then
|
||||
// i. Append a new Record { [[Type]]: "group", [[Value]]: groupSepSymbol } as the last element of result.
|
||||
// 7. Else,
|
||||
// a. Append a new Record { [[Type]]: "integer", [[Value]]: integer } as the last element of result.
|
||||
auto group_sep_symbol = Unicode::get_number_system_symbol(number_format.data_locale(), number_format.numbering_system(), "group"sv).value_or(","sv);
|
||||
|
||||
// FIXME: Implement grouping.
|
||||
// b. Let groups be a List whose elements are, in left to right order, the substrings defined by ILND set of locations within the integer.
|
||||
auto groups = separate_integer_into_groups(*grouping_sizes, integer);
|
||||
|
||||
// c. Assert: The number of elements in groups List is greater than 0.
|
||||
VERIFY(!groups.is_empty());
|
||||
|
||||
// d. Repeat, while groups List is not empty,
|
||||
while (!groups.is_empty()) {
|
||||
// i. Remove the first element from groups and let integerGroup be the value of that element.
|
||||
auto integer_group = groups.take_first();
|
||||
|
||||
// ii. Append a new Record { [[Type]]: "integer", [[Value]]: integerGroup } as the last element of result.
|
||||
result.append({ "integer"sv, integer_group });
|
||||
|
||||
// iii. If groups List is not empty, then
|
||||
if (!groups.is_empty()) {
|
||||
// i. Append a new Record { [[Type]]: "group", [[Value]]: groupSepSymbol } as the last element of result.
|
||||
result.append({ "group"sv, group_sep_symbol });
|
||||
}
|
||||
}
|
||||
}
|
||||
// 7. Else,
|
||||
else {
|
||||
// a. Append a new Record { [[Type]]: "integer", [[Value]]: integer } as the last element of result.
|
||||
result.append({ "integer"sv, integer });
|
||||
}
|
||||
|
||||
// 8. If fraction is not undefined, then
|
||||
if (fraction.has_value()) {
|
||||
|
|
|
@ -204,6 +204,52 @@ describe("style=decimal", () => {
|
|||
expect(ar.format(-0)).toBe("\u0660");
|
||||
expect(ar.format(-1)).toBe("\u061c-\u0661");
|
||||
});
|
||||
|
||||
test("useGrouping=true", () => {
|
||||
const en = new Intl.NumberFormat("en", { useGrouping: true });
|
||||
expect(en.format(123)).toBe("123");
|
||||
expect(en.format(1234)).toBe("1,234");
|
||||
expect(en.format(12345)).toBe("12,345");
|
||||
expect(en.format(123456)).toBe("123,456");
|
||||
expect(en.format(1234567)).toBe("1,234,567");
|
||||
|
||||
const enIn = new Intl.NumberFormat("en-IN", { useGrouping: true });
|
||||
expect(enIn.format(123)).toBe("123");
|
||||
expect(enIn.format(1234)).toBe("1,234");
|
||||
expect(enIn.format(12345)).toBe("12,345");
|
||||
expect(enIn.format(123456)).toBe("1,23,456");
|
||||
expect(enIn.format(1234567)).toBe("12,34,567");
|
||||
|
||||
const ar = new Intl.NumberFormat("ar", { useGrouping: true });
|
||||
expect(ar.format(123)).toBe("\u0661\u0662\u0663");
|
||||
expect(ar.format(1234)).toBe("\u0661\u066c\u0662\u0663\u0664");
|
||||
expect(ar.format(12345)).toBe("\u0661\u0662\u066c\u0663\u0664\u0665");
|
||||
expect(ar.format(123456)).toBe("\u0661\u0662\u0663\u066c\u0664\u0665\u0666");
|
||||
expect(ar.format(1234567)).toBe("\u0661\u066c\u0662\u0663\u0664\u066c\u0665\u0666\u0667");
|
||||
});
|
||||
|
||||
test("useGrouping=false", () => {
|
||||
const en = new Intl.NumberFormat("en", { useGrouping: false });
|
||||
expect(en.format(123)).toBe("123");
|
||||
expect(en.format(1234)).toBe("1234");
|
||||
expect(en.format(12345)).toBe("12345");
|
||||
expect(en.format(123456)).toBe("123456");
|
||||
expect(en.format(1234567)).toBe("1234567");
|
||||
|
||||
const enIn = new Intl.NumberFormat("en-IN", { useGrouping: false });
|
||||
expect(enIn.format(123)).toBe("123");
|
||||
expect(enIn.format(1234)).toBe("1234");
|
||||
expect(enIn.format(12345)).toBe("12345");
|
||||
expect(enIn.format(123456)).toBe("123456");
|
||||
expect(enIn.format(1234567)).toBe("1234567");
|
||||
|
||||
const ar = new Intl.NumberFormat("ar", { useGrouping: false });
|
||||
expect(ar.format(123)).toBe("\u0661\u0662\u0663");
|
||||
expect(ar.format(1234)).toBe("\u0661\u0662\u0663\u0664");
|
||||
expect(ar.format(12345)).toBe("\u0661\u0662\u0663\u0664\u0665");
|
||||
expect(ar.format(123456)).toBe("\u0661\u0662\u0663\u0664\u0665\u0666");
|
||||
expect(ar.format(1234567)).toBe("\u0661\u0662\u0663\u0664\u0665\u0666\u0667");
|
||||
});
|
||||
});
|
||||
|
||||
describe("style=percent", () => {
|
||||
|
|
|
@ -180,6 +180,70 @@ describe("style=decimal", () => {
|
|||
{ type: "integer", value: "\u0661" },
|
||||
]);
|
||||
});
|
||||
|
||||
test("useGrouping=true", () => {
|
||||
const en = new Intl.NumberFormat("en", { useGrouping: true });
|
||||
expect(en.formatToParts(123456)).toEqual([
|
||||
{ type: "integer", value: "123" },
|
||||
{ type: "group", value: "," },
|
||||
{ type: "integer", value: "456" },
|
||||
]);
|
||||
expect(en.formatToParts(1234567)).toEqual([
|
||||
{ type: "integer", value: "1" },
|
||||
{ type: "group", value: "," },
|
||||
{ type: "integer", value: "234" },
|
||||
{ type: "group", value: "," },
|
||||
{ type: "integer", value: "567" },
|
||||
]);
|
||||
|
||||
const enIn = new Intl.NumberFormat("en-IN", { useGrouping: true });
|
||||
expect(enIn.formatToParts(123456)).toEqual([
|
||||
{ type: "integer", value: "1" },
|
||||
{ type: "group", value: "," },
|
||||
{ type: "integer", value: "23" },
|
||||
{ type: "group", value: "," },
|
||||
{ type: "integer", value: "456" },
|
||||
]);
|
||||
expect(enIn.formatToParts(1234567)).toEqual([
|
||||
{ type: "integer", value: "12" },
|
||||
{ type: "group", value: "," },
|
||||
{ type: "integer", value: "34" },
|
||||
{ type: "group", value: "," },
|
||||
{ type: "integer", value: "567" },
|
||||
]);
|
||||
|
||||
const ar = new Intl.NumberFormat("ar", { useGrouping: true });
|
||||
expect(ar.formatToParts(123456)).toEqual([
|
||||
{ type: "integer", value: "\u0661\u0662\u0663" },
|
||||
{ type: "group", value: "\u066c" },
|
||||
{ type: "integer", value: "\u0664\u0665\u0666" },
|
||||
]);
|
||||
expect(ar.formatToParts(1234567)).toEqual([
|
||||
{ type: "integer", value: "\u0661" },
|
||||
{ type: "group", value: "\u066c" },
|
||||
{ type: "integer", value: "\u0662\u0663\u0664" },
|
||||
{ type: "group", value: "\u066c" },
|
||||
{ type: "integer", value: "\u0665\u0666\u0667" },
|
||||
]);
|
||||
});
|
||||
|
||||
test("useGrouping=false", () => {
|
||||
const en = new Intl.NumberFormat("en", { useGrouping: false });
|
||||
expect(en.formatToParts(123456)).toEqual([{ type: "integer", value: "123456" }]);
|
||||
expect(en.formatToParts(1234567)).toEqual([{ type: "integer", value: "1234567" }]);
|
||||
|
||||
const enIn = new Intl.NumberFormat("en-IN", { useGrouping: false });
|
||||
expect(enIn.formatToParts(123456)).toEqual([{ type: "integer", value: "123456" }]);
|
||||
expect(enIn.formatToParts(1234567)).toEqual([{ type: "integer", value: "1234567" }]);
|
||||
|
||||
const ar = new Intl.NumberFormat("ar", { useGrouping: false });
|
||||
expect(ar.formatToParts(123456)).toEqual([
|
||||
{ type: "integer", value: "\u0661\u0662\u0663\u0664\u0665\u0666" },
|
||||
]);
|
||||
expect(ar.formatToParts(1234567)).toEqual([
|
||||
{ type: "integer", value: "\u0661\u0662\u0663\u0664\u0665\u0666\u0667" },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("style=percent", () => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue