summaryrefslogtreecommitdiff
path: root/Userland
diff options
context:
space:
mode:
authorTimothy Flynn <trflynn89@pm.me>2022-01-12 17:55:45 -0500
committerLinus Groh <mail@linusgroh.de>2022-01-13 13:43:57 +0100
commit4875ec26dd94ce5c062cb8d62072dda27db7c51c (patch)
tree3b0aa58db5305e5dc60d1279cd99b9a5cfa71a9c /Userland
parentadb762ee48c10f0f07d6a0f314b1d80ef74ec600 (diff)
downloadserenity-4875ec26dd94ce5c062cb8d62072dda27db7c51c.zip
LibJS: Implement per-locale display of calendars and date-time fields
Diffstat (limited to 'Userland')
-rw-r--r--Userland/Libraries/LibJS/Runtime/Intl/DisplayNames.cpp37
-rw-r--r--Userland/Libraries/LibJS/Runtime/Intl/DisplayNames.h1
-rw-r--r--Userland/Libraries/LibJS/Runtime/Intl/DisplayNamesPrototype.cpp14
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Intl/DisplayNames/DisplayNames.prototype.of.js130
4 files changed, 178 insertions, 4 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/Intl/DisplayNames.cpp b/Userland/Libraries/LibJS/Runtime/Intl/DisplayNames.cpp
index 2804f31c24..1fafd3f488 100644
--- a/Userland/Libraries/LibJS/Runtime/Intl/DisplayNames.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Intl/DisplayNames.cpp
@@ -171,16 +171,45 @@ ThrowCompletionOr<Value> canonical_code_for_display_names(GlobalObject& global_o
return js_string(vm, code.to_titlecase_string());
}
- // 4. Assert: type is "currency".
+ // 4. If type is "calendar", then
+ if (type == DisplayNames::Type::Calendar) {
+ // a. If code does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception.
+ if (!Unicode::is_type_identifier(code))
+ return vm.throw_completion<RangeError>(global_object, ErrorType::OptionIsNotValidValue, code, "calendar"sv);
+
+ // b. Let code be the result of mapping code to lower case as described in 6.1.
+ // c. Return code.
+ return js_string(vm, code.to_lowercase_string());
+ }
+
+ // 5. If type is "dateTimeField", then
+ if (type == DisplayNames::Type::DateTimeField) {
+ // a. If the result of IsValidDateTimeFieldCode(code) is false, throw a RangeError exception.
+ if (!is_valid_date_time_field_code(code))
+ return vm.throw_completion<RangeError>(global_object, ErrorType::OptionIsNotValidValue, code, "dateTimeField"sv);
+
+ // b. Return code.
+ return js_string(vm, code);
+ }
+
+ // 6. Assert: type is "currency".
VERIFY(type == DisplayNames::Type::Currency);
- // 5. If ! IsWellFormedCurrencyCode(code) is false, throw a RangeError exception.
+ // 7. If ! IsWellFormedCurrencyCode(code) is false, throw a RangeError exception.
if (!is_well_formed_currency_code(code))
return vm.throw_completion<RangeError>(global_object, ErrorType::OptionIsNotValidValue, code, "currency"sv);
- // 6. Let code be the result of mapping code to upper case as described in 6.1.
- // 7. Return code.
+ // 8. Let code be the result of mapping code to upper case as described in 6.1.
+ // 9. Return code.
return js_string(vm, code.to_uppercase_string());
}
+// 12.2 IsValidDateTimeFieldCode ( field ), https://tc39.es/ecma402/#sec-isvaliddatetimefieldcode
+bool is_valid_date_time_field_code(StringView field)
+{
+ // 1. If field is listed in the Code column of Table 8, return true.
+ // 2. Return false.
+ return field.is_one_of("era"sv, "year"sv, "quarter"sv, "month"sv, "weekOfYear"sv, "weekday"sv, "day"sv, "dayPeriod"sv, "hour"sv, "minute"sv, "second"sv, "timeZoneName"sv);
+}
+
}
diff --git a/Userland/Libraries/LibJS/Runtime/Intl/DisplayNames.h b/Userland/Libraries/LibJS/Runtime/Intl/DisplayNames.h
index 22fdbfa488..db59a242ce 100644
--- a/Userland/Libraries/LibJS/Runtime/Intl/DisplayNames.h
+++ b/Userland/Libraries/LibJS/Runtime/Intl/DisplayNames.h
@@ -77,5 +77,6 @@ private:
};
ThrowCompletionOr<Value> canonical_code_for_display_names(GlobalObject& global_object, DisplayNames::Type type, StringView code);
+bool is_valid_date_time_field_code(StringView field);
}
diff --git a/Userland/Libraries/LibJS/Runtime/Intl/DisplayNamesPrototype.cpp b/Userland/Libraries/LibJS/Runtime/Intl/DisplayNamesPrototype.cpp
index 0c51b3717b..2778b7c3f8 100644
--- a/Userland/Libraries/LibJS/Runtime/Intl/DisplayNamesPrototype.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Intl/DisplayNamesPrototype.cpp
@@ -79,8 +79,22 @@ JS_DEFINE_NATIVE_FUNCTION(DisplayNamesPrototype::of)
}
break;
case DisplayNames::Type::Calendar:
+ result = Unicode::get_locale_calendar_mapping(display_names->locale(), code.as_string().string());
break;
case DisplayNames::Type::DateTimeField:
+ switch (display_names->style()) {
+ case DisplayNames::Style::Long:
+ result = Unicode::get_locale_long_date_field_mapping(display_names->locale(), code.as_string().string());
+ break;
+ case DisplayNames::Style::Short:
+ result = Unicode::get_locale_short_date_field_mapping(display_names->locale(), code.as_string().string());
+ break;
+ case DisplayNames::Style::Narrow:
+ result = Unicode::get_locale_narrow_date_field_mapping(display_names->locale(), code.as_string().string());
+ break;
+ default:
+ VERIFY_NOT_REACHED();
+ }
break;
default:
VERIFY_NOT_REACHED();
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Intl/DisplayNames/DisplayNames.prototype.of.js b/Userland/Libraries/LibJS/Tests/builtins/Intl/DisplayNames/DisplayNames.prototype.of.js
index 3f85173188..a6a1f9726e 100644
--- a/Userland/Libraries/LibJS/Tests/builtins/Intl/DisplayNames/DisplayNames.prototype.of.js
+++ b/Userland/Libraries/LibJS/Tests/builtins/Intl/DisplayNames/DisplayNames.prototype.of.js
@@ -22,6 +22,18 @@ describe("errors", () => {
new Intl.DisplayNames("en", { type: "currency" }).of("hello!");
}).toThrowWithMessage(RangeError, "hello! is not a valid value for option currency");
});
+
+ test("invalid calendar", () => {
+ expect(() => {
+ new Intl.DisplayNames("en", { type: "calendar" }).of("hello!");
+ }).toThrowWithMessage(RangeError, "hello! is not a valid value for option calendar");
+ });
+
+ test("invalid dateTimeField", () => {
+ expect(() => {
+ new Intl.DisplayNames("en", { type: "dateTimeField" }).of("hello!");
+ }).toThrowWithMessage(RangeError, "hello! is not a valid value for option dateTimeField");
+ });
});
describe("correct behavior", () => {
@@ -118,4 +130,122 @@ describe("correct behavior", () => {
expect(es419.of("AAA")).toBe("AAA");
expect(zhHant.of("AAA")).toBe("AAA");
});
+
+ test("option type calendar", () => {
+ // prettier-ignore
+ const data = [
+ { calendar: "buddhist", en: "Buddhist Calendar", es419: "calendario budista", zhHant: "佛曆" },
+ { calendar: "chinese", en: "Chinese Calendar", es419: "calendario chino", zhHant: "農曆" },
+ { calendar: "coptic", en: "Coptic Calendar", es419: "calendario cóptico", zhHant: "科普特曆" },
+ { calendar: "dangi", en: "Dangi Calendar", es419: "calendario dangi", zhHant: "檀紀曆" },
+ { calendar: "ethioaa", en: "Ethiopic Amete Alem Calendar", es419: "calendario etíope Amete Alem", zhHant: "衣索比亞曆 (Amete Alem)" },
+ { calendar: "ethiopic", en: "Ethiopic Calendar", es419: "calendario etíope", zhHant: "衣索比亞曆" },
+ { calendar: "gregory", en: "Gregorian Calendar", es419: "calendario gregoriano", zhHant: "公曆" },
+ { calendar: "hebrew", en: "Hebrew Calendar", es419: "calendario hebreo", zhHant: "希伯來曆" },
+ { calendar: "indian", en: "Indian National Calendar", es419: "calendario nacional hindú", zhHant: "印度國曆" },
+ { calendar: "islamic", en: "Islamic Calendar", es419: "calendario islámico", zhHant: "伊斯蘭曆" },
+ { calendar: "islamic-civil", en: "Islamic Calendar (tabular, civil epoch)", es419: "calendario civil islámico", zhHant: "伊斯蘭民用曆" },
+ { calendar: "islamic-rgsa", en: "Islamic Calendar (Saudi Arabia, sighting)", es419: "calendario islámico (Arabia Saudita)", zhHant: "伊斯蘭新月曆" },
+ { calendar: "islamic-tbla", en: "Islamic Calendar (tabular, astronomical epoch)", es419: "calendario islámico tabular", zhHant: "伊斯蘭天文曆" },
+ { calendar: "islamic-umalqura", en: "Islamic Calendar (Umm al-Qura)", es419: "calendario islámico umalqura", zhHant: "烏姆庫拉曆" },
+ { calendar: "iso8601", en: "ISO-8601 Calendar", es419: "calendario ISO-8601", zhHant: "ISO 8601 國際曆法" },
+ { calendar: "japanese", en: "Japanese Calendar", es419: "calendario japonés", zhHant: "日本曆" },
+ { calendar: "persian", en: "Persian Calendar", es419: "calendario persa", zhHant: "波斯曆" },
+ { calendar: "roc", en: "Minguo Calendar", es419: "calendario de la República de China", zhHant: "國曆" },
+ ];
+
+ const en = new Intl.DisplayNames("en", { type: "calendar" });
+ const es419 = new Intl.DisplayNames("es-419", { type: "calendar" });
+ const zhHant = new Intl.DisplayNames("zh-Hant", { type: "calendar" });
+
+ data.forEach(d => {
+ expect(en.of(d.calendar)).toBe(d.en);
+ expect(es419.of(d.calendar)).toBe(d.es419);
+ expect(zhHant.of(d.calendar)).toBe(d.zhHant);
+ });
+ });
+
+ test("option type dateTimeField, style long", () => {
+ // prettier-ignore
+ const data = [
+ { dateTimeField: "era", en: "era", es419: "era", zhHant: "年代" },
+ { dateTimeField: "year", en: "year", es419: "año", zhHant: "年" },
+ { dateTimeField: "quarter", en: "quarter", es419: "trimestre", zhHant: "季" },
+ { dateTimeField: "month", en: "month", es419: "mes", zhHant: "月" },
+ { dateTimeField: "weekOfYear", en: "week", es419: "semana", zhHant: "週" },
+ { dateTimeField: "weekday", en: "day of the week", es419: "día de la semana", zhHant: "週天" },
+ { dateTimeField: "day", en: "day", es419: "día", zhHant: "日" },
+ { dateTimeField: "dayPeriod", en: "AM/PM", es419: "a. m./p. m.", zhHant: "上午/下午" },
+ { dateTimeField: "hour", en: "hour", es419: "hora", zhHant: "小時" },
+ { dateTimeField: "minute", en: "minute", es419: "minuto", zhHant: "分鐘" },
+ { dateTimeField: "second", en: "second", es419: "segundo", zhHant: "秒" },
+ { dateTimeField: "timeZoneName", en: "time zone", es419: "zona horaria", zhHant: "時區" },
+ ];
+
+ const en = new Intl.DisplayNames("en", { type: "dateTimeField", style: "long" });
+ const es419 = new Intl.DisplayNames("es-419", { type: "dateTimeField", style: "long" });
+ const zhHant = new Intl.DisplayNames("zh-Hant", { type: "dateTimeField", style: "long" });
+
+ data.forEach(d => {
+ expect(en.of(d.dateTimeField)).toBe(d.en);
+ expect(es419.of(d.dateTimeField)).toBe(d.es419);
+ expect(zhHant.of(d.dateTimeField)).toBe(d.zhHant);
+ });
+ });
+
+ test("option type dateTimeField, style short", () => {
+ // prettier-ignore
+ const data = [
+ { dateTimeField: "era", en: "era", es419: "era", zhHant: "年代" },
+ { dateTimeField: "year", en: "yr.", es419: "a", zhHant: "年" },
+ { dateTimeField: "quarter", en: "qtr.", es419: "trim.", zhHant: "季" },
+ { dateTimeField: "month", en: "mo.", es419: "m", zhHant: "月" },
+ { dateTimeField: "weekOfYear", en: "wk.", es419: "sem.", zhHant: "週" },
+ { dateTimeField: "weekday", en: "day of wk.", es419: "día de sem.", zhHant: "週天" },
+ { dateTimeField: "day", en: "day", es419: "d", zhHant: "日" },
+ { dateTimeField: "dayPeriod", en: "AM/PM", es419: "a.m./p.m.", zhHant: "上午/下午" },
+ { dateTimeField: "hour", en: "hr.", es419: "h", zhHant: "小時" },
+ { dateTimeField: "minute", en: "min.", es419: "min", zhHant: "分鐘" },
+ { dateTimeField: "second", en: "sec.", es419: "s", zhHant: "秒" },
+ { dateTimeField: "timeZoneName", en: "zone", es419: "zona", zhHant: "時區" },
+ ];
+
+ const en = new Intl.DisplayNames("en", { type: "dateTimeField", style: "short" });
+ const es419 = new Intl.DisplayNames("es-419", { type: "dateTimeField", style: "short" });
+ const zhHant = new Intl.DisplayNames("zh-Hant", { type: "dateTimeField", style: "short" });
+
+ data.forEach(d => {
+ expect(en.of(d.dateTimeField)).toBe(d.en);
+ expect(es419.of(d.dateTimeField)).toBe(d.es419);
+ expect(zhHant.of(d.dateTimeField)).toBe(d.zhHant);
+ });
+ });
+
+ test("option type dateTimeField, style narrow", () => {
+ // prettier-ignore
+ const data = [
+ { dateTimeField: "era", en: "era", es419: "era", zhHant: "年代" },
+ { dateTimeField: "year", en: "yr.", es419: "a", zhHant: "年" },
+ { dateTimeField: "quarter", en: "qtr.", es419: "trim.", zhHant: "季" },
+ { dateTimeField: "month", en: "mo.", es419: "m", zhHant: "月" },
+ { dateTimeField: "weekOfYear", en: "wk.", es419: "sem.", zhHant: "週" },
+ { dateTimeField: "weekday", en: "day of wk.", es419: "día de sem.", zhHant: "週天" },
+ { dateTimeField: "day", en: "day", es419: "d", zhHant: "日" },
+ { dateTimeField: "dayPeriod", en: "AM/PM", es419: "a.m./p.m.", zhHant: "上午/下午" },
+ { dateTimeField: "hour", en: "hr.", es419: "h", zhHant: "小時" },
+ { dateTimeField: "minute", en: "min.", es419: "min", zhHant: "分鐘" },
+ { dateTimeField: "second", en: "sec.", es419: "s", zhHant: "秒" },
+ { dateTimeField: "timeZoneName", en: "zone", es419: "zona", zhHant: "時區" },
+ ];
+
+ const en = new Intl.DisplayNames("en", { type: "dateTimeField", style: "narrow" });
+ const es419 = new Intl.DisplayNames("es-419", { type: "dateTimeField", style: "narrow" });
+ const zhHant = new Intl.DisplayNames("zh-Hant", { type: "dateTimeField", style: "narrow" });
+
+ data.forEach(d => {
+ expect(en.of(d.dateTimeField)).toBe(d.en);
+ expect(es419.of(d.dateTimeField)).toBe(d.es419);
+ expect(zhHant.of(d.dateTimeField)).toBe(d.zhHant);
+ });
+ });
});