Line data Source code
1 : // Copyright 2018 the V8 project authors. All rights reserved.
2 : // Use of this source code is governed by a BSD-style license that can be
3 : // found in the LICENSE file.
4 :
5 : #ifndef V8_INTL_SUPPORT
6 : #error Internationalization is expected to be enabled.
7 : #endif // V8_INTL_SUPPORT
8 :
9 : #include "src/objects/js-plural-rules.h"
10 :
11 : #include "src/isolate-inl.h"
12 : #include "src/objects/intl-objects.h"
13 : #include "src/objects/js-plural-rules-inl.h"
14 : #include "unicode/decimfmt.h"
15 : #include "unicode/locid.h"
16 : #include "unicode/numfmt.h"
17 : #include "unicode/plurrule.h"
18 :
19 : namespace v8 {
20 : namespace internal {
21 :
22 : namespace {
23 :
24 45 : bool CreateICUPluralRules(Isolate* isolate, const icu::Locale& icu_locale,
25 : JSPluralRules::Type type,
26 : std::unique_ptr<icu::PluralRules>* pl,
27 : std::unique_ptr<icu::DecimalFormat>* nf) {
28 : // Make formatter from options. Numbering system is added
29 : // to the locale as Unicode extension (if it was specified at all).
30 45 : UErrorCode status = U_ZERO_ERROR;
31 :
32 : UPluralType icu_type = UPLURAL_TYPE_CARDINAL;
33 45 : if (type == JSPluralRules::Type::ORDINAL) {
34 : icu_type = UPLURAL_TYPE_ORDINAL;
35 : } else {
36 36 : CHECK_EQ(JSPluralRules::Type::CARDINAL, type);
37 : }
38 :
39 : std::unique_ptr<icu::PluralRules> plural_rules(
40 45 : icu::PluralRules::forLocale(icu_locale, icu_type, status));
41 45 : if (U_FAILURE(status)) {
42 : return false;
43 : }
44 45 : CHECK_NOT_NULL(plural_rules.get());
45 :
46 : std::unique_ptr<icu::DecimalFormat> number_format(
47 : static_cast<icu::DecimalFormat*>(
48 45 : icu::NumberFormat::createInstance(icu_locale, UNUM_DECIMAL, status)));
49 45 : if (U_FAILURE(status)) {
50 : return false;
51 : }
52 45 : CHECK_NOT_NULL(number_format.get());
53 :
54 : *pl = std::move(plural_rules);
55 : *nf = std::move(number_format);
56 :
57 : return true;
58 : }
59 :
60 45 : void InitializeICUPluralRules(
61 : Isolate* isolate, const icu::Locale& icu_locale, JSPluralRules::Type type,
62 : std::unique_ptr<icu::PluralRules>* plural_rules,
63 : std::unique_ptr<icu::DecimalFormat>* number_format) {
64 : bool success = CreateICUPluralRules(isolate, icu_locale, type, plural_rules,
65 45 : number_format);
66 45 : if (!success) {
67 : // Remove extensions and try again.
68 0 : icu::Locale no_extension_locale(icu_locale.getBaseName());
69 : success = CreateICUPluralRules(isolate, no_extension_locale, type,
70 0 : plural_rules, number_format);
71 :
72 0 : if (!success) {
73 0 : FATAL("Failed to create ICU PluralRules, are ICU data files missing?");
74 : }
75 : }
76 :
77 45 : CHECK_NOT_NULL((*plural_rules).get());
78 45 : CHECK_NOT_NULL((*number_format).get());
79 45 : }
80 :
81 : } // namespace
82 :
83 0 : Handle<String> JSPluralRules::TypeAsString() const {
84 0 : switch (type()) {
85 : case Type::CARDINAL:
86 : return GetReadOnlyRoots().cardinal_string_handle();
87 : case Type::ORDINAL:
88 : return GetReadOnlyRoots().ordinal_string_handle();
89 : case Type::COUNT:
90 0 : UNREACHABLE();
91 : }
92 0 : }
93 :
94 : // static
95 45 : MaybeHandle<JSPluralRules> JSPluralRules::Initialize(
96 : Isolate* isolate, Handle<JSPluralRules> plural_rules,
97 : Handle<Object> locales, Handle<Object> options_obj) {
98 : plural_rules->set_flags(0);
99 : // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales).
100 : Maybe<std::vector<std::string>> maybe_requested_locales =
101 45 : Intl::CanonicalizeLocaleList(isolate, locales);
102 45 : MAYBE_RETURN(maybe_requested_locales, Handle<JSPluralRules>());
103 : std::vector<std::string> requested_locales =
104 45 : maybe_requested_locales.FromJust();
105 :
106 : // 2. If options is undefined, then
107 45 : if (options_obj->IsUndefined(isolate)) {
108 : // 2. a. Let options be ObjectCreate(null).
109 27 : options_obj = isolate->factory()->NewJSObjectWithNullProto();
110 : } else {
111 : // 3. Else
112 : // 3. a. Let options be ? ToObject(options).
113 36 : ASSIGN_RETURN_ON_EXCEPTION(
114 : isolate, options_obj,
115 : Object::ToObject(isolate, options_obj, "Intl.PluralRules"),
116 : JSPluralRules);
117 : }
118 :
119 : // At this point, options_obj can either be a JSObject or a JSProxy only.
120 45 : Handle<JSReceiver> options = Handle<JSReceiver>::cast(options_obj);
121 :
122 : // 5. Let matcher be ? GetOption(options, "localeMatcher", "string",
123 : // « "lookup", "best fit" », "best fit").
124 : // 6. Set opt.[[localeMatcher]] to matcher.
125 : Maybe<Intl::MatcherOption> maybe_locale_matcher =
126 45 : Intl::GetLocaleMatcher(isolate, options, "Intl.PluralRules");
127 45 : MAYBE_RETURN(maybe_locale_matcher, MaybeHandle<JSPluralRules>());
128 : Intl::MatcherOption matcher = maybe_locale_matcher.FromJust();
129 :
130 : // 7. Let t be ? GetOption(options, "type", "string", « "cardinal",
131 : // "ordinal" », "cardinal").
132 : Maybe<Type> maybe_type = Intl::GetStringOption<Type>(
133 : isolate, options, "type", "Intl.PluralRules", {"cardinal", "ordinal"},
134 135 : {Type::CARDINAL, Type::ORDINAL}, Type::CARDINAL);
135 45 : MAYBE_RETURN(maybe_type, MaybeHandle<JSPluralRules>());
136 : Type type = maybe_type.FromJust();
137 :
138 : // 8. Set pluralRules.[[Type]] to t.
139 45 : plural_rules->set_type(type);
140 :
141 : // Note: The spec says we should do ResolveLocale after performing
142 : // SetNumberFormatDigitOptions but we need the locale to create all
143 : // the ICU data structures.
144 : //
145 : // This isn't observable so we aren't violating the spec.
146 :
147 : // 11. Let r be ResolveLocale(%PluralRules%.[[AvailableLocales]],
148 : // requestedLocales, opt, %PluralRules%.[[RelevantExtensionKeys]],
149 : // localeData).
150 : Intl::ResolvedLocale r =
151 : Intl::ResolveLocale(isolate, JSPluralRules::GetAvailableLocales(),
152 135 : requested_locales, matcher, {});
153 :
154 : // 12. Set pluralRules.[[Locale]] to the value of r.[[locale]].
155 : Handle<String> locale_str =
156 45 : isolate->factory()->NewStringFromAsciiChecked(r.locale.c_str());
157 45 : plural_rules->set_locale(*locale_str);
158 :
159 45 : std::unique_ptr<icu::PluralRules> icu_plural_rules;
160 45 : std::unique_ptr<icu::DecimalFormat> icu_decimal_format;
161 : InitializeICUPluralRules(isolate, r.icu_locale, type, &icu_plural_rules,
162 45 : &icu_decimal_format);
163 45 : CHECK_NOT_NULL(icu_plural_rules.get());
164 45 : CHECK_NOT_NULL(icu_decimal_format.get());
165 :
166 : // 9. Perform ? SetNumberFormatDigitOptions(pluralRules, options, 0, 3).
167 : Maybe<bool> done = Intl::SetNumberFormatDigitOptions(
168 45 : isolate, icu_decimal_format.get(), options, 0, 3);
169 45 : MAYBE_RETURN(done, MaybeHandle<JSPluralRules>());
170 :
171 : Handle<Managed<icu::PluralRules>> managed_plural_rules =
172 : Managed<icu::PluralRules>::FromUniquePtr(isolate, 0,
173 90 : std::move(icu_plural_rules));
174 45 : plural_rules->set_icu_plural_rules(*managed_plural_rules);
175 :
176 : Handle<Managed<icu::DecimalFormat>> managed_decimal_format =
177 : Managed<icu::DecimalFormat>::FromUniquePtr(isolate, 0,
178 90 : std::move(icu_decimal_format));
179 45 : plural_rules->set_icu_decimal_format(*managed_decimal_format);
180 :
181 : // 13. Return pluralRules.
182 45 : return plural_rules;
183 : }
184 :
185 378 : MaybeHandle<String> JSPluralRules::ResolvePlural(
186 : Isolate* isolate, Handle<JSPluralRules> plural_rules, double number) {
187 : icu::PluralRules* icu_plural_rules = plural_rules->icu_plural_rules()->raw();
188 378 : CHECK_NOT_NULL(icu_plural_rules);
189 :
190 : icu::DecimalFormat* icu_decimal_format =
191 : plural_rules->icu_decimal_format()->raw();
192 378 : CHECK_NOT_NULL(icu_decimal_format);
193 :
194 : // Currently, PluralRules doesn't implement all the options for rounding that
195 : // the Intl spec provides; format and parse the number to round to the
196 : // appropriate amount, then apply PluralRules.
197 : //
198 : // TODO(littledan): If a future ICU version supports an extended API to avoid
199 : // this step, then switch to that API. Bug thread:
200 : // http://bugs.icu-project.org/trac/ticket/12763
201 378 : icu::UnicodeString rounded_string;
202 378 : icu_decimal_format->format(number, rounded_string);
203 :
204 756 : icu::Formattable formattable;
205 378 : UErrorCode status = U_ZERO_ERROR;
206 378 : icu_decimal_format->parse(rounded_string, formattable, status);
207 378 : CHECK(U_SUCCESS(status));
208 :
209 378 : double rounded = formattable.getDouble(status);
210 378 : CHECK(U_SUCCESS(status));
211 :
212 756 : icu::UnicodeString result = icu_plural_rules->select(rounded);
213 : return isolate->factory()->NewStringFromTwoByte(Vector<const uint16_t>(
214 1134 : reinterpret_cast<const uint16_t*>(result.getBuffer()), result.length()));
215 : }
216 :
217 : namespace {
218 :
219 0 : void CreateDataPropertyForOptions(Isolate* isolate, Handle<JSObject> options,
220 : Handle<Object> value, const char* key) {
221 0 : Handle<String> key_str = isolate->factory()->NewStringFromAsciiChecked(key);
222 :
223 : // This is a brand new JSObject that shouldn't already have the same
224 : // key so this shouldn't fail.
225 0 : CHECK(JSReceiver::CreateDataProperty(isolate, options, key_str, value,
226 : Just(kDontThrow))
227 : .FromJust());
228 0 : }
229 :
230 0 : void CreateDataPropertyForOptions(Isolate* isolate, Handle<JSObject> options,
231 : int value, const char* key) {
232 : Handle<Smi> value_smi(Smi::FromInt(value), isolate);
233 0 : CreateDataPropertyForOptions(isolate, options, value_smi, key);
234 0 : }
235 :
236 : } // namespace
237 :
238 0 : Handle<JSObject> JSPluralRules::ResolvedOptions(
239 : Isolate* isolate, Handle<JSPluralRules> plural_rules) {
240 : Handle<JSObject> options =
241 0 : isolate->factory()->NewJSObject(isolate->object_function());
242 :
243 : Handle<String> locale_value(plural_rules->locale(), isolate);
244 0 : CreateDataPropertyForOptions(isolate, options, locale_value, "locale");
245 :
246 0 : CreateDataPropertyForOptions(isolate, options, plural_rules->TypeAsString(),
247 0 : "type");
248 :
249 : icu::DecimalFormat* icu_decimal_format =
250 : plural_rules->icu_decimal_format()->raw();
251 0 : CHECK_NOT_NULL(icu_decimal_format);
252 :
253 : // This is a safe upcast as icu::DecimalFormat inherits from
254 : // icu::NumberFormat.
255 : icu::NumberFormat* icu_number_format =
256 : static_cast<icu::NumberFormat*>(icu_decimal_format);
257 :
258 0 : int min_int_digits = icu_number_format->getMinimumIntegerDigits();
259 : CreateDataPropertyForOptions(isolate, options, min_int_digits,
260 0 : "minimumIntegerDigits");
261 :
262 0 : int min_fraction_digits = icu_number_format->getMinimumFractionDigits();
263 : CreateDataPropertyForOptions(isolate, options, min_fraction_digits,
264 0 : "minimumFractionDigits");
265 :
266 0 : int max_fraction_digits = icu_number_format->getMaximumFractionDigits();
267 : CreateDataPropertyForOptions(isolate, options, max_fraction_digits,
268 0 : "maximumFractionDigits");
269 :
270 0 : if (icu_decimal_format->areSignificantDigitsUsed()) {
271 : int min_significant_digits =
272 0 : icu_decimal_format->getMinimumSignificantDigits();
273 : CreateDataPropertyForOptions(isolate, options, min_significant_digits,
274 0 : "minimumSignificantDigits");
275 :
276 : int max_significant_digits =
277 0 : icu_decimal_format->getMaximumSignificantDigits();
278 : CreateDataPropertyForOptions(isolate, options, max_significant_digits,
279 0 : "maximumSignificantDigits");
280 : }
281 :
282 : // 6. Let pluralCategories be a List of Strings representing the
283 : // possible results of PluralRuleSelect for the selected locale pr.
284 : icu::PluralRules* icu_plural_rules = plural_rules->icu_plural_rules()->raw();
285 0 : CHECK_NOT_NULL(icu_plural_rules);
286 :
287 0 : UErrorCode status = U_ZERO_ERROR;
288 : std::unique_ptr<icu::StringEnumeration> categories(
289 0 : icu_plural_rules->getKeywords(status));
290 0 : CHECK(U_SUCCESS(status));
291 0 : int32_t count = categories->count(status);
292 0 : CHECK(U_SUCCESS(status));
293 :
294 : Handle<FixedArray> plural_categories =
295 0 : isolate->factory()->NewFixedArray(count);
296 0 : for (int32_t i = 0; i < count; i++) {
297 0 : const icu::UnicodeString* category = categories->snext(status);
298 0 : CHECK(U_SUCCESS(status));
299 0 : if (category == nullptr) break;
300 :
301 : std::string keyword;
302 : Handle<String> value = isolate->factory()->NewStringFromAsciiChecked(
303 0 : category->toUTF8String(keyword).data());
304 0 : plural_categories->set(i, *value);
305 : }
306 :
307 : // 7. Perform ! CreateDataProperty(options, "pluralCategories",
308 : // CreateArrayFromList(pluralCategories)).
309 : Handle<JSArray> plural_categories_value =
310 : isolate->factory()->NewJSArrayWithElements(plural_categories);
311 0 : CreateDataPropertyForOptions(isolate, options, plural_categories_value,
312 0 : "pluralCategories");
313 :
314 0 : return options;
315 : }
316 :
317 131 : const std::set<std::string>& JSPluralRules::GetAvailableLocales() {
318 : // TODO(ftang): For PluralRules, filter out locales that
319 : // don't support PluralRules.
320 : // PluralRules is missing an appropriate getAvailableLocales method,
321 : // so we should filter from all locales, but it's not clear how; see
322 : // https://ssl.icu-project.org/trac/ticket/12756
323 176 : return Intl::GetAvailableLocalesForLocale();
324 : }
325 :
326 : } // namespace internal
327 122036 : } // namespace v8
|