/src/serenity/Userland/Libraries/LibJS/Runtime/Intl/DisplayNames.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2021-2022, Tim Flynn <trflynn89@serenityos.org> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <LibJS/Runtime/GlobalObject.h> |
8 | | #include <LibJS/Runtime/Intl/AbstractOperations.h> |
9 | | #include <LibJS/Runtime/Intl/DisplayNames.h> |
10 | | |
11 | | namespace JS::Intl { |
12 | | |
13 | | JS_DEFINE_ALLOCATOR(DisplayNames); |
14 | | |
15 | | // 12 DisplayNames Objects, https://tc39.es/ecma402/#intl-displaynames-objects |
16 | | DisplayNames::DisplayNames(Object& prototype) |
17 | 0 | : Object(ConstructWithPrototypeTag::Tag, prototype) |
18 | 0 | { |
19 | 0 | } |
20 | | |
21 | | void DisplayNames::set_type(StringView type) |
22 | 0 | { |
23 | 0 | if (type == "language"sv) |
24 | 0 | m_type = Type::Language; |
25 | 0 | else if (type == "region"sv) |
26 | 0 | m_type = Type::Region; |
27 | 0 | else if (type == "script"sv) |
28 | 0 | m_type = Type::Script; |
29 | 0 | else if (type == "currency"sv) |
30 | 0 | m_type = Type::Currency; |
31 | 0 | else if (type == "calendar"sv) |
32 | 0 | m_type = Type::Calendar; |
33 | 0 | else if (type == "dateTimeField"sv) |
34 | 0 | m_type = Type::DateTimeField; |
35 | 0 | else |
36 | 0 | VERIFY_NOT_REACHED(); |
37 | 0 | } |
38 | | |
39 | | StringView DisplayNames::type_string() const |
40 | 0 | { |
41 | 0 | switch (m_type) { |
42 | 0 | case Type::Language: |
43 | 0 | return "language"sv; |
44 | 0 | case Type::Region: |
45 | 0 | return "region"sv; |
46 | 0 | case Type::Script: |
47 | 0 | return "script"sv; |
48 | 0 | case Type::Currency: |
49 | 0 | return "currency"sv; |
50 | 0 | case Type::Calendar: |
51 | 0 | return "calendar"sv; |
52 | 0 | case Type::DateTimeField: |
53 | 0 | return "dateTimeField"sv; |
54 | 0 | default: |
55 | 0 | VERIFY_NOT_REACHED(); |
56 | 0 | } |
57 | 0 | } |
58 | | |
59 | | void DisplayNames::set_fallback(StringView fallback) |
60 | 0 | { |
61 | 0 | if (fallback == "none"sv) |
62 | 0 | m_fallback = Fallback::None; |
63 | 0 | else if (fallback == "code"sv) |
64 | 0 | m_fallback = Fallback::Code; |
65 | 0 | else |
66 | 0 | VERIFY_NOT_REACHED(); |
67 | 0 | } |
68 | | |
69 | | StringView DisplayNames::fallback_string() const |
70 | 0 | { |
71 | 0 | switch (m_fallback) { |
72 | 0 | case Fallback::None: |
73 | 0 | return "none"sv; |
74 | 0 | case Fallback::Code: |
75 | 0 | return "code"sv; |
76 | 0 | default: |
77 | 0 | VERIFY_NOT_REACHED(); |
78 | 0 | } |
79 | 0 | } |
80 | | |
81 | | void DisplayNames::set_language_display(StringView language_display) |
82 | 0 | { |
83 | 0 | if (language_display == "dialect"sv) |
84 | 0 | m_language_display = LanguageDisplay::Dialect; |
85 | 0 | else if (language_display == "standard"sv) |
86 | 0 | m_language_display = LanguageDisplay::Standard; |
87 | 0 | else |
88 | 0 | VERIFY_NOT_REACHED(); |
89 | 0 | } |
90 | | |
91 | | StringView DisplayNames::language_display_string() const |
92 | 0 | { |
93 | 0 | VERIFY(m_language_display.has_value()); |
94 | | |
95 | 0 | switch (*m_language_display) { |
96 | 0 | case LanguageDisplay::Dialect: |
97 | 0 | return "dialect"sv; |
98 | 0 | case LanguageDisplay::Standard: |
99 | 0 | return "standard"sv; |
100 | 0 | default: |
101 | 0 | VERIFY_NOT_REACHED(); |
102 | 0 | } |
103 | 0 | } |
104 | | |
105 | | // 12.5.1 CanonicalCodeForDisplayNames ( type, code ), https://tc39.es/ecma402/#sec-canonicalcodefordisplaynames |
106 | | ThrowCompletionOr<Value> canonical_code_for_display_names(VM& vm, DisplayNames::Type type, StringView code) |
107 | 0 | { |
108 | | // 1. If type is "language", then |
109 | 0 | if (type == DisplayNames::Type::Language) { |
110 | | // a. If code does not match the unicode_language_id production, throw a RangeError exception. |
111 | 0 | if (!::Locale::parse_unicode_language_id(code).has_value()) |
112 | 0 | return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "language"sv); |
113 | | |
114 | | // b. If IsStructurallyValidLanguageTag(code) is false, throw a RangeError exception. |
115 | 0 | auto locale_id = is_structurally_valid_language_tag(code); |
116 | 0 | if (!locale_id.has_value()) |
117 | 0 | return vm.throw_completion<RangeError>(ErrorType::IntlInvalidLanguageTag, code); |
118 | | |
119 | | // c. Return ! CanonicalizeUnicodeLocaleId(code). |
120 | 0 | auto canonicalized_tag = JS::Intl::canonicalize_unicode_locale_id(*locale_id); |
121 | 0 | return PrimitiveString::create(vm, move(canonicalized_tag)); |
122 | 0 | } |
123 | | |
124 | | // 2. If type is "region", then |
125 | 0 | if (type == DisplayNames::Type::Region) { |
126 | | // a. If code does not match the unicode_region_subtag production, throw a RangeError exception. |
127 | 0 | if (!::Locale::is_unicode_region_subtag(code)) |
128 | 0 | return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "region"sv); |
129 | | |
130 | | // b. Return the ASCII-uppercase of code. |
131 | 0 | return PrimitiveString::create(vm, code.to_uppercase_string()); |
132 | 0 | } |
133 | | |
134 | | // 3. If type is "script", then |
135 | 0 | if (type == DisplayNames::Type::Script) { |
136 | | // a. If code does not match the unicode_script_subtag production, throw a RangeError exception. |
137 | 0 | if (!::Locale::is_unicode_script_subtag(code)) |
138 | 0 | return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "script"sv); |
139 | | |
140 | | // Assert: The length of code is 4, and every code unit of code represents an ASCII letter (0x0041 through 0x005A and 0x0061 through 0x007A, both inclusive). |
141 | 0 | VERIFY(code.length() == 4); |
142 | 0 | VERIFY(all_of(code, is_ascii_alpha)); |
143 | | |
144 | | // c. Let first be the ASCII-uppercase of the substring of code from 0 to 1. |
145 | | // d. Let rest be the ASCII-lowercase of the substring of code from 1. |
146 | | // e. Return the string-concatenation of first and rest. |
147 | 0 | return PrimitiveString::create(vm, code.to_titlecase_string()); |
148 | 0 | } |
149 | | |
150 | | // 4. If type is "calendar", then |
151 | 0 | if (type == DisplayNames::Type::Calendar) { |
152 | | // a. If code does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception. |
153 | 0 | if (!::Locale::is_type_identifier(code)) |
154 | 0 | return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "calendar"sv); |
155 | | |
156 | | // b. If code uses any of the backwards compatibility syntax described in Unicode Technical Standard #35 LDML ยง 3.3 BCP 47 Conformance, throw a RangeError exception. |
157 | 0 | if (code.contains('_')) |
158 | 0 | return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "calendar"sv); |
159 | | |
160 | | // c. Return the ASCII-lowercase of code. |
161 | 0 | return PrimitiveString::create(vm, code.to_lowercase_string()); |
162 | 0 | } |
163 | | |
164 | | // 5. If type is "dateTimeField", then |
165 | 0 | if (type == DisplayNames::Type::DateTimeField) { |
166 | | // a. If the result of IsValidDateTimeFieldCode(code) is false, throw a RangeError exception. |
167 | 0 | if (!is_valid_date_time_field_code(code)) |
168 | 0 | return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "dateTimeField"sv); |
169 | | |
170 | | // b. Return code. |
171 | 0 | return PrimitiveString::create(vm, code); |
172 | 0 | } |
173 | | |
174 | | // 6. Assert: type is "currency". |
175 | 0 | VERIFY(type == DisplayNames::Type::Currency); |
176 | | |
177 | | // 7. If ! IsWellFormedCurrencyCode(code) is false, throw a RangeError exception. |
178 | 0 | if (!is_well_formed_currency_code(code)) |
179 | 0 | return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "currency"sv); |
180 | | |
181 | | // 8. Return the ASCII-uppercase of code. |
182 | 0 | return PrimitiveString::create(vm, code.to_uppercase_string()); |
183 | 0 | } |
184 | | |
185 | | // 12.5.2 IsValidDateTimeFieldCode ( field ), https://tc39.es/ecma402/#sec-isvaliddatetimefieldcode |
186 | | bool is_valid_date_time_field_code(StringView field) |
187 | 0 | { |
188 | | // 1. If field is listed in the Code column of Table 9, return true. |
189 | | // 2. Return false. |
190 | 0 | 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); |
191 | 0 | } |
192 | | |
193 | | } |