• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/execution/isolate-inl.h"
12 #include "src/objects/intl-objects.h"
13 #include "src/objects/js-number-format.h"
14 #include "src/objects/js-plural-rules-inl.h"
15 #include "src/objects/managed-inl.h"
16 #include "src/objects/option-utils.h"
17 #include "unicode/locid.h"
18 #include "unicode/numberformatter.h"
19 #include "unicode/numberrangeformatter.h"
20 #include "unicode/plurrule.h"
21 #include "unicode/unumberformatter.h"
22 
23 namespace v8 {
24 namespace internal {
25 
26 namespace {
27 
CreateICUPluralRules(Isolate * isolate,const icu::Locale & icu_locale,JSPluralRules::Type type,std::unique_ptr<icu::PluralRules> * pl)28 bool CreateICUPluralRules(Isolate* isolate, const icu::Locale& icu_locale,
29                           JSPluralRules::Type type,
30                           std::unique_ptr<icu::PluralRules>* pl) {
31   // Make formatter from options. Numbering system is added
32   // to the locale as Unicode extension (if it was specified at all).
33   UErrorCode status = U_ZERO_ERROR;
34 
35   UPluralType icu_type = UPLURAL_TYPE_CARDINAL;
36   if (type == JSPluralRules::Type::ORDINAL) {
37     icu_type = UPLURAL_TYPE_ORDINAL;
38   } else {
39     DCHECK_EQ(JSPluralRules::Type::CARDINAL, type);
40   }
41 
42   std::unique_ptr<icu::PluralRules> plural_rules(
43       icu::PluralRules::forLocale(icu_locale, icu_type, status));
44   if (U_FAILURE(status)) {
45     return false;
46   }
47   DCHECK_NOT_NULL(plural_rules.get());
48 
49   *pl = std::move(plural_rules);
50   return true;
51 }
52 
53 }  // namespace
54 
TypeAsString() const55 Handle<String> JSPluralRules::TypeAsString() const {
56   switch (type()) {
57     case Type::CARDINAL:
58       return GetReadOnlyRoots().cardinal_string_handle();
59     case Type::ORDINAL:
60       return GetReadOnlyRoots().ordinal_string_handle();
61   }
62   UNREACHABLE();
63 }
64 
65 // static
New(Isolate * isolate,Handle<Map> map,Handle<Object> locales,Handle<Object> options_obj)66 MaybeHandle<JSPluralRules> JSPluralRules::New(Isolate* isolate, Handle<Map> map,
67                                               Handle<Object> locales,
68                                               Handle<Object> options_obj) {
69   // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales).
70   Maybe<std::vector<std::string>> maybe_requested_locales =
71       Intl::CanonicalizeLocaleList(isolate, locales);
72   MAYBE_RETURN(maybe_requested_locales, Handle<JSPluralRules>());
73   std::vector<std::string> requested_locales =
74       maybe_requested_locales.FromJust();
75 
76   // 2. Set options to ? CoerceOptionsToObject(options).
77   Handle<JSReceiver> options;
78   const char* service = "Intl.PluralRules";
79   ASSIGN_RETURN_ON_EXCEPTION(
80       isolate, options, CoerceOptionsToObject(isolate, options_obj, service),
81       JSPluralRules);
82 
83   // 5. Let matcher be ? GetOption(options, "localeMatcher", "string",
84   // « "lookup", "best fit" », "best fit").
85   // 6. Set opt.[[localeMatcher]] to matcher.
86   Maybe<Intl::MatcherOption> maybe_locale_matcher =
87       Intl::GetLocaleMatcher(isolate, options, service);
88   MAYBE_RETURN(maybe_locale_matcher, MaybeHandle<JSPluralRules>());
89   Intl::MatcherOption matcher = maybe_locale_matcher.FromJust();
90 
91   // 7. Let t be ? GetOption(options, "type", "string", « "cardinal",
92   // "ordinal" », "cardinal").
93   Maybe<Type> maybe_type = GetStringOption<Type>(
94       isolate, options, "type", service, {"cardinal", "ordinal"},
95       {Type::CARDINAL, Type::ORDINAL}, Type::CARDINAL);
96   MAYBE_RETURN(maybe_type, MaybeHandle<JSPluralRules>());
97   Type type = maybe_type.FromJust();
98 
99   // Note: The spec says we should do ResolveLocale after performing
100   // SetNumberFormatDigitOptions but we need the locale to create all
101   // the ICU data structures.
102   //
103   // This isn't observable so we aren't violating the spec.
104 
105   // 11. Let r be ResolveLocale(%PluralRules%.[[AvailableLocales]],
106   // requestedLocales, opt, %PluralRules%.[[RelevantExtensionKeys]],
107   // localeData).
108   Maybe<Intl::ResolvedLocale> maybe_resolve_locale =
109       Intl::ResolveLocale(isolate, JSPluralRules::GetAvailableLocales(),
110                           requested_locales, matcher, {});
111   if (maybe_resolve_locale.IsNothing()) {
112     THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
113                     JSPluralRules);
114   }
115   Intl::ResolvedLocale r = maybe_resolve_locale.FromJust();
116   Handle<String> locale_str =
117       isolate->factory()->NewStringFromAsciiChecked(r.locale.c_str());
118 
119   icu::Locale icu_locale = r.icu_locale;
120   icu::number::UnlocalizedNumberFormatter settings =
121       icu::number::UnlocalizedNumberFormatter().roundingMode(UNUM_ROUND_HALFUP);
122 
123   std::unique_ptr<icu::PluralRules> icu_plural_rules;
124   bool success =
125       CreateICUPluralRules(isolate, r.icu_locale, type, &icu_plural_rules);
126   if (!success || icu_plural_rules.get() == nullptr) {
127     // Remove extensions and try again.
128     icu::Locale no_extension_locale(icu_locale.getBaseName());
129     success = CreateICUPluralRules(isolate, no_extension_locale, type,
130                                    &icu_plural_rules);
131     icu_locale = no_extension_locale;
132 
133     if (!success || icu_plural_rules.get() == nullptr) {
134       THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
135                       JSPluralRules);
136     }
137   }
138 
139   // 9. Perform ? SetNumberFormatDigitOptions(pluralRules, options, 0, 3).
140   Maybe<Intl::NumberFormatDigitOptions> maybe_digit_options =
141       Intl::SetNumberFormatDigitOptions(isolate, options, 0, 3, false);
142   MAYBE_RETURN(maybe_digit_options, MaybeHandle<JSPluralRules>());
143   Intl::NumberFormatDigitOptions digit_options = maybe_digit_options.FromJust();
144   settings = JSNumberFormat::SetDigitOptionsToFormatter(
145       settings, digit_options, 1, JSNumberFormat::ShowTrailingZeros::kShow);
146 
147   icu::number::LocalizedNumberFormatter icu_number_formatter =
148       settings.locale(icu_locale);
149   icu::number::LocalizedNumberRangeFormatter icu_number_range_formatter =
150       icu::number::UnlocalizedNumberRangeFormatter()
151           .numberFormatterBoth(settings)
152           .locale(icu_locale);
153 
154   Handle<Managed<icu::PluralRules>> managed_plural_rules =
155       Managed<icu::PluralRules>::FromUniquePtr(isolate, 0,
156                                                std::move(icu_plural_rules));
157 
158   Handle<Managed<icu::number::LocalizedNumberFormatter>>
159       managed_number_formatter =
160           Managed<icu::number::LocalizedNumberFormatter>::FromRawPtr(
161               isolate, 0,
162               new icu::number::LocalizedNumberFormatter(icu_number_formatter));
163   Handle<Managed<icu::number::LocalizedNumberRangeFormatter>>
164       managed_number_range_formatter =
165           Managed<icu::number::LocalizedNumberRangeFormatter>::FromRawPtr(
166               isolate, 0,
167               new icu::number::LocalizedNumberRangeFormatter(
168                   icu_number_range_formatter));
169 
170   // Now all properties are ready, so we can allocate the result object.
171   Handle<JSPluralRules> plural_rules = Handle<JSPluralRules>::cast(
172       isolate->factory()->NewFastOrSlowJSObjectFromMap(map));
173   DisallowGarbageCollection no_gc;
174   plural_rules->set_flags(0);
175 
176   // 8. Set pluralRules.[[Type]] to t.
177   plural_rules->set_type(type);
178 
179   // 12. Set pluralRules.[[Locale]] to the value of r.[[locale]].
180   plural_rules->set_locale(*locale_str);
181 
182   plural_rules->set_icu_plural_rules(*managed_plural_rules);
183   plural_rules->set_icu_number_formatter(*managed_number_formatter);
184   plural_rules->set_icu_number_range_formatter(*managed_number_range_formatter);
185 
186   // 13. Return pluralRules.
187   return plural_rules;
188 }
189 
ResolvePlural(Isolate * isolate,Handle<JSPluralRules> plural_rules,double number)190 MaybeHandle<String> JSPluralRules::ResolvePlural(
191     Isolate* isolate, Handle<JSPluralRules> plural_rules, double number) {
192   icu::PluralRules* icu_plural_rules = plural_rules->icu_plural_rules().raw();
193   DCHECK_NOT_NULL(icu_plural_rules);
194 
195   icu::number::LocalizedNumberFormatter* fmt =
196       plural_rules->icu_number_formatter().raw();
197   DCHECK_NOT_NULL(fmt);
198 
199   UErrorCode status = U_ZERO_ERROR;
200   icu::number::FormattedNumber formatted_number =
201       fmt->formatDouble(number, status);
202   DCHECK(U_SUCCESS(status));
203 
204   icu::UnicodeString result =
205       icu_plural_rules->select(formatted_number, status);
206   DCHECK(U_SUCCESS(status));
207 
208   return Intl::ToString(isolate, result);
209 }
210 
ResolvePluralRange(Isolate * isolate,Handle<JSPluralRules> plural_rules,double x,double y)211 MaybeHandle<String> JSPluralRules::ResolvePluralRange(
212     Isolate* isolate, Handle<JSPluralRules> plural_rules, double x, double y) {
213   icu::PluralRules* icu_plural_rules = plural_rules->icu_plural_rules().raw();
214   DCHECK_NOT_NULL(icu_plural_rules);
215 
216   icu::number::LocalizedNumberRangeFormatter* fmt =
217       plural_rules->icu_number_range_formatter().raw();
218   DCHECK_NOT_NULL(fmt);
219 
220   UErrorCode status = U_ZERO_ERROR;
221   icu::number::FormattedNumberRange formatted = fmt->formatFormattableRange(
222       icu::Formattable(x), icu::Formattable(y), status);
223 
224   DCHECK(U_SUCCESS(status));
225   icu::UnicodeString result = icu_plural_rules->select(formatted, status);
226   DCHECK(U_SUCCESS(status));
227 
228   return Intl::ToString(isolate, result);
229 }
230 
231 namespace {
232 
CreateDataPropertyForOptions(Isolate * isolate,Handle<JSObject> options,Handle<Object> value,const char * key)233 void CreateDataPropertyForOptions(Isolate* isolate, Handle<JSObject> options,
234                                   Handle<Object> value, const char* key) {
235   Handle<String> key_str = isolate->factory()->NewStringFromAsciiChecked(key);
236 
237   // This is a brand new JSObject that shouldn't already have the same
238   // key so this shouldn't fail.
239   Maybe<bool> maybe = JSReceiver::CreateDataProperty(isolate, options, key_str,
240                                                      value, Just(kDontThrow));
241   DCHECK(maybe.FromJust());
242   USE(maybe);
243 }
244 
CreateDataPropertyForOptions(Isolate * isolate,Handle<JSObject> options,int value,const char * key)245 void CreateDataPropertyForOptions(Isolate* isolate, Handle<JSObject> options,
246                                   int value, const char* key) {
247   Handle<Smi> value_smi(Smi::FromInt(value), isolate);
248   CreateDataPropertyForOptions(isolate, options, value_smi, key);
249 }
250 
251 }  // namespace
252 
ResolvedOptions(Isolate * isolate,Handle<JSPluralRules> plural_rules)253 Handle<JSObject> JSPluralRules::ResolvedOptions(
254     Isolate* isolate, Handle<JSPluralRules> plural_rules) {
255   Handle<JSObject> options =
256       isolate->factory()->NewJSObject(isolate->object_function());
257 
258   Handle<String> locale_value(plural_rules->locale(), isolate);
259   CreateDataPropertyForOptions(isolate, options, locale_value, "locale");
260 
261   CreateDataPropertyForOptions(isolate, options, plural_rules->TypeAsString(),
262                                "type");
263 
264   UErrorCode status = U_ZERO_ERROR;
265   icu::number::LocalizedNumberFormatter* icu_number_formatter =
266       plural_rules->icu_number_formatter().raw();
267   icu::UnicodeString skeleton = icu_number_formatter->toSkeleton(status);
268   DCHECK(U_SUCCESS(status));
269 
270   CreateDataPropertyForOptions(
271       isolate, options,
272       JSNumberFormat::MinimumIntegerDigitsFromSkeleton(skeleton),
273       "minimumIntegerDigits");
274   int32_t min = 0, max = 0;
275 
276   if (JSNumberFormat::SignificantDigitsFromSkeleton(skeleton, &min, &max)) {
277     CreateDataPropertyForOptions(isolate, options, min,
278                                  "minimumSignificantDigits");
279     CreateDataPropertyForOptions(isolate, options, max,
280                                  "maximumSignificantDigits");
281   } else {
282     JSNumberFormat::FractionDigitsFromSkeleton(skeleton, &min, &max);
283     CreateDataPropertyForOptions(isolate, options, min,
284                                  "minimumFractionDigits");
285     CreateDataPropertyForOptions(isolate, options, max,
286                                  "maximumFractionDigits");
287   }
288 
289   // 6. Let pluralCategories be a List of Strings representing the
290   // possible results of PluralRuleSelect for the selected locale pr.
291   icu::PluralRules* icu_plural_rules = plural_rules->icu_plural_rules().raw();
292   DCHECK_NOT_NULL(icu_plural_rules);
293 
294   std::unique_ptr<icu::StringEnumeration> categories(
295       icu_plural_rules->getKeywords(status));
296   DCHECK(U_SUCCESS(status));
297   int32_t count = categories->count(status);
298   DCHECK(U_SUCCESS(status));
299 
300   Handle<FixedArray> plural_categories =
301       isolate->factory()->NewFixedArray(count);
302   for (int32_t i = 0; i < count; i++) {
303     const icu::UnicodeString* category = categories->snext(status);
304     DCHECK(U_SUCCESS(status));
305     if (category == nullptr) break;
306 
307     std::string keyword;
308     Handle<String> value = isolate->factory()->NewStringFromAsciiChecked(
309         category->toUTF8String(keyword).data());
310     plural_categories->set(i, *value);
311   }
312 
313   // 7. Perform ! CreateDataProperty(options, "pluralCategories",
314   // CreateArrayFromList(pluralCategories)).
315   Handle<JSArray> plural_categories_value =
316       isolate->factory()->NewJSArrayWithElements(plural_categories);
317   CreateDataPropertyForOptions(isolate, options, plural_categories_value,
318                                "pluralCategories");
319 
320   return options;
321 }
322 
323 namespace {
324 
325 class PluralRulesAvailableLocales {
326  public:
PluralRulesAvailableLocales()327   PluralRulesAvailableLocales() {
328     UErrorCode status = U_ZERO_ERROR;
329     std::unique_ptr<icu::StringEnumeration> locales(
330         icu::PluralRules::getAvailableLocales(status));
331     DCHECK(U_SUCCESS(status));
332     int32_t len = 0;
333     const char* locale = nullptr;
334     while ((locale = locales->next(&len, status)) != nullptr &&
335            U_SUCCESS(status)) {
336       std::string str(locale);
337       if (len > 3) {
338         std::replace(str.begin(), str.end(), '_', '-');
339       }
340       set_.insert(std::move(str));
341     }
342   }
Get() const343   const std::set<std::string>& Get() const { return set_; }
344 
345  private:
346   std::set<std::string> set_;
347 };
348 
349 }  // namespace
350 
GetAvailableLocales()351 const std::set<std::string>& JSPluralRules::GetAvailableLocales() {
352   static base::LazyInstance<PluralRulesAvailableLocales>::type
353       available_locales = LAZY_INSTANCE_INITIALIZER;
354   return available_locales.Pointer()->Get();
355 }
356 
357 }  // namespace internal
358 }  // namespace v8
359