js-plural-rules.cc 13.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_INTL_SUPPORT
#error Internationalization is expected to be enabled.
#endif  // V8_INTL_SUPPORT

#include "src/objects/js-plural-rules.h"

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 24 25 26 27 28

namespace v8 {
namespace internal {

namespace {

bool CreateICUPluralRules(Isolate* isolate, const icu::Locale& icu_locale,
29
                          JSPluralRules::Type type,
30
                          std::unique_ptr<icu::PluralRules>* pl) {
31 32 33 34
  // Make formatter from options. Numbering system is added
  // to the locale as Unicode extension (if it was specified at all).
  UErrorCode status = U_ZERO_ERROR;

35 36 37
  UPluralType icu_type = UPLURAL_TYPE_CARDINAL;
  if (type == JSPluralRules::Type::ORDINAL) {
    icu_type = UPLURAL_TYPE_ORDINAL;
38
  } else {
Frank Tang's avatar
Frank Tang committed
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 45 46
  if (U_FAILURE(status)) {
    return false;
  }
Frank Tang's avatar
Frank Tang committed
47
  DCHECK_NOT_NULL(plural_rules.get());
48 49 50 51 52 53 54

  *pl = std::move(plural_rules);
  return true;
}

}  // namespace

55 56 57 58 59 60 61
Handle<String> JSPluralRules::TypeAsString() const {
  switch (type()) {
    case Type::CARDINAL:
      return GetReadOnlyRoots().cardinal_string_handle();
    case Type::ORDINAL:
      return GetReadOnlyRoots().ordinal_string_handle();
  }
62
  UNREACHABLE();
63 64
}

65
// static
66 67 68
MaybeHandle<JSPluralRules> JSPluralRules::New(Isolate* isolate, Handle<Map> map,
                                              Handle<Object> locales,
                                              Handle<Object> options_obj) {
69
  // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales).
70 71 72 73 74
  Maybe<std::vector<std::string>> maybe_requested_locales =
      Intl::CanonicalizeLocaleList(isolate, locales);
  MAYBE_RETURN(maybe_requested_locales, Handle<JSPluralRules>());
  std::vector<std::string> requested_locales =
      maybe_requested_locales.FromJust();
75

76 77 78 79
  // 2. Set options to ? CoerceOptionsToObject(options).
  Handle<JSReceiver> options;
  const char* service = "Intl.PluralRules";
  ASSIGN_RETURN_ON_EXCEPTION(
80
      isolate, options, CoerceOptionsToObject(isolate, options_obj, service),
81
      JSPluralRules);
82 83 84 85

  // 5. Let matcher be ? GetOption(options, "localeMatcher", "string",
  // « "lookup", "best fit" », "best fit").
  // 6. Set opt.[[localeMatcher]] to matcher.
86
  Maybe<Intl::MatcherOption> maybe_locale_matcher =
87
      Intl::GetLocaleMatcher(isolate, options, service);
88 89
  MAYBE_RETURN(maybe_locale_matcher, MaybeHandle<JSPluralRules>());
  Intl::MatcherOption matcher = maybe_locale_matcher.FromJust();
90 91 92

  // 7. Let t be ? GetOption(options, "type", "string", « "cardinal",
  // "ordinal" », "cardinal").
93
  Maybe<Type> maybe_type = GetStringOption<Type>(
94
      isolate, options, "type", service, {"cardinal", "ordinal"},
95 96 97
      {Type::CARDINAL, Type::ORDINAL}, Type::CARDINAL);
  MAYBE_RETURN(maybe_type, MaybeHandle<JSPluralRules>());
  Type type = maybe_type.FromJust();
98 99 100 101 102 103 104 105 106 107

  // Note: The spec says we should do ResolveLocale after performing
  // SetNumberFormatDigitOptions but we need the locale to create all
  // the ICU data structures.
  //
  // This isn't observable so we aren't violating the spec.

  // 11. Let r be ResolveLocale(%PluralRules%.[[AvailableLocales]],
  // requestedLocales, opt, %PluralRules%.[[RelevantExtensionKeys]],
  // localeData).
108
  Maybe<Intl::ResolvedLocale> maybe_resolve_locale =
109 110
      Intl::ResolveLocale(isolate, JSPluralRules::GetAvailableLocales(),
                          requested_locales, matcher, {});
111 112 113 114 115
  if (maybe_resolve_locale.IsNothing()) {
    THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
                    JSPluralRules);
  }
  Intl::ResolvedLocale r = maybe_resolve_locale.FromJust();
116 117
  Handle<String> locale_str =
      isolate->factory()->NewStringFromAsciiChecked(r.locale.c_str());
118

119 120 121
  icu::Locale icu_locale = r.icu_locale;
  icu::number::UnlocalizedNumberFormatter settings =
      icu::number::UnlocalizedNumberFormatter().roundingMode(UNUM_ROUND_HALFUP);
122

123
  std::unique_ptr<icu::PluralRules> icu_plural_rules;
124 125
  bool success =
      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 130
    success = CreateICUPluralRules(isolate, no_extension_locale, type,
                                   &icu_plural_rules);
131
    icu_locale = no_extension_locale;
132

133 134 135
    if (!success || icu_plural_rules.get() == nullptr) {
      THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
                      JSPluralRules);
136 137 138
    }
  }

139
  // 9. Perform ? SetNumberFormatDigitOptions(pluralRules, options, 0, 3).
140
  Maybe<Intl::NumberFormatDigitOptions> maybe_digit_options =
Frank Tang's avatar
Frank Tang committed
141
      Intl::SetNumberFormatDigitOptions(isolate, options, 0, 3, false);
142 143
  MAYBE_RETURN(maybe_digit_options, MaybeHandle<JSPluralRules>());
  Intl::NumberFormatDigitOptions digit_options = maybe_digit_options.FromJust();
144 145 146 147 148
  settings = JSNumberFormat::SetDigitOptionsToFormatter(
      settings, digit_options, 1, JSNumberFormat::ShowTrailingZeros::kShow);

  icu::number::LocalizedNumberFormatter icu_number_formatter =
      settings.locale(icu_locale);
149 150 151 152
  icu::number::LocalizedNumberRangeFormatter icu_number_range_formatter =
      icu::number::UnlocalizedNumberRangeFormatter()
          .numberFormatterBoth(settings)
          .locale(icu_locale);
153 154 155 156 157

  Handle<Managed<icu::PluralRules>> managed_plural_rules =
      Managed<icu::PluralRules>::FromUniquePtr(isolate, 0,
                                               std::move(icu_plural_rules));

158 159 160 161 162
  Handle<Managed<icu::number::LocalizedNumberFormatter>>
      managed_number_formatter =
          Managed<icu::number::LocalizedNumberFormatter>::FromRawPtr(
              isolate, 0,
              new icu::number::LocalizedNumberFormatter(icu_number_formatter));
163 164 165 166 167 168
  Handle<Managed<icu::number::LocalizedNumberRangeFormatter>>
      managed_number_range_formatter =
          Managed<icu::number::LocalizedNumberRangeFormatter>::FromRawPtr(
              isolate, 0,
              new icu::number::LocalizedNumberRangeFormatter(
                  icu_number_range_formatter));
169 170 171 172

  // Now all properties are ready, so we can allocate the result object.
  Handle<JSPluralRules> plural_rules = Handle<JSPluralRules>::cast(
      isolate->factory()->NewFastOrSlowJSObjectFromMap(map));
173
  DisallowGarbageCollection no_gc;
174 175 176 177 178 179 180 181 182
  plural_rules->set_flags(0);

  // 8. Set pluralRules.[[Type]] to t.
  plural_rules->set_type(type);

  // 12. Set pluralRules.[[Locale]] to the value of r.[[locale]].
  plural_rules->set_locale(*locale_str);

  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 187 188 189 190

  // 13. Return pluralRules.
  return plural_rules;
}

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();
Frank Tang's avatar
Frank Tang committed
193
  DCHECK_NOT_NULL(icu_plural_rules);
194

195 196
  icu::number::LocalizedNumberFormatter* fmt =
      plural_rules->icu_number_formatter().raw();
Frank Tang's avatar
Frank Tang committed
197
  DCHECK_NOT_NULL(fmt);
198 199

  UErrorCode status = U_ZERO_ERROR;
200 201
  icu::number::FormattedNumber formatted_number =
      fmt->formatDouble(number, status);
Frank Tang's avatar
Frank Tang committed
202
  DCHECK(U_SUCCESS(status));
203

204 205
  icu::UnicodeString result =
      icu_plural_rules->select(formatted_number, status);
Frank Tang's avatar
Frank Tang committed
206
  DCHECK(U_SUCCESS(status));
207

208
  return Intl::ToString(isolate, result);
209 210
}

211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
MaybeHandle<String> JSPluralRules::ResolvePluralRange(
    Isolate* isolate, Handle<JSPluralRules> plural_rules, double x, double y) {
  icu::PluralRules* icu_plural_rules = plural_rules->icu_plural_rules().raw();
  DCHECK_NOT_NULL(icu_plural_rules);

  icu::number::LocalizedNumberRangeFormatter* fmt =
      plural_rules->icu_number_range_formatter().raw();
  DCHECK_NOT_NULL(fmt);

  UErrorCode status = U_ZERO_ERROR;
  icu::number::FormattedNumberRange formatted = fmt->formatFormattableRange(
      icu::Formattable(x), icu::Formattable(y), status);

  DCHECK(U_SUCCESS(status));
  icu::UnicodeString result = icu_plural_rules->select(formatted, status);
  DCHECK(U_SUCCESS(status));

  return Intl::ToString(isolate, result);
}

231 232 233 234 235 236 237 238
namespace {

void CreateDataPropertyForOptions(Isolate* isolate, Handle<JSObject> options,
                                  Handle<Object> value, const char* key) {
  Handle<String> key_str = isolate->factory()->NewStringFromAsciiChecked(key);

  // This is a brand new JSObject that shouldn't already have the same
  // key so this shouldn't fail.
Frank Tang's avatar
Frank Tang committed
239 240 241 242
  Maybe<bool> maybe = JSReceiver::CreateDataProperty(isolate, options, key_str,
                                                     value, Just(kDontThrow));
  DCHECK(maybe.FromJust());
  USE(maybe);
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
}

void CreateDataPropertyForOptions(Isolate* isolate, Handle<JSObject> options,
                                  int value, const char* key) {
  Handle<Smi> value_smi(Smi::FromInt(value), isolate);
  CreateDataPropertyForOptions(isolate, options, value_smi, key);
}

}  // namespace

Handle<JSObject> JSPluralRules::ResolvedOptions(
    Isolate* isolate, Handle<JSPluralRules> plural_rules) {
  Handle<JSObject> options =
      isolate->factory()->NewJSObject(isolate->object_function());

  Handle<String> locale_value(plural_rules->locale(), isolate);
  CreateDataPropertyForOptions(isolate, options, locale_value, "locale");

261 262
  CreateDataPropertyForOptions(isolate, options, plural_rules->TypeAsString(),
                               "type");
263

264 265 266 267
  UErrorCode status = U_ZERO_ERROR;
  icu::number::LocalizedNumberFormatter* icu_number_formatter =
      plural_rules->icu_number_formatter().raw();
  icu::UnicodeString skeleton = icu_number_formatter->toSkeleton(status);
Frank Tang's avatar
Frank Tang committed
268
  DCHECK(U_SUCCESS(status));
269

270 271 272 273 274
  CreateDataPropertyForOptions(
      isolate, options,
      JSNumberFormat::MinimumIntegerDigitsFromSkeleton(skeleton),
      "minimumIntegerDigits");
  int32_t min = 0, max = 0;
275

276 277
  if (JSNumberFormat::SignificantDigitsFromSkeleton(skeleton, &min, &max)) {
    CreateDataPropertyForOptions(isolate, options, min,
278
                                 "minimumSignificantDigits");
279
    CreateDataPropertyForOptions(isolate, options, max,
280
                                 "maximumSignificantDigits");
281 282 283 284 285 286
  } else {
    JSNumberFormat::FractionDigitsFromSkeleton(skeleton, &min, &max);
    CreateDataPropertyForOptions(isolate, options, min,
                                 "minimumFractionDigits");
    CreateDataPropertyForOptions(isolate, options, max,
                                 "maximumFractionDigits");
287 288 289 290
  }

  // 6. Let pluralCategories be a List of Strings representing the
  // possible results of PluralRuleSelect for the selected locale pr.
291
  icu::PluralRules* icu_plural_rules = plural_rules->icu_plural_rules().raw();
Frank Tang's avatar
Frank Tang committed
292
  DCHECK_NOT_NULL(icu_plural_rules);
293 294 295

  std::unique_ptr<icu::StringEnumeration> categories(
      icu_plural_rules->getKeywords(status));
Frank Tang's avatar
Frank Tang committed
296
  DCHECK(U_SUCCESS(status));
297
  int32_t count = categories->count(status);
Frank Tang's avatar
Frank Tang committed
298
  DCHECK(U_SUCCESS(status));
299 300 301 302 303

  Handle<FixedArray> plural_categories =
      isolate->factory()->NewFixedArray(count);
  for (int32_t i = 0; i < count; i++) {
    const icu::UnicodeString* category = categories->snext(status);
Frank Tang's avatar
Frank Tang committed
304
    DCHECK(U_SUCCESS(status));
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
    if (category == nullptr) break;

    std::string keyword;
    Handle<String> value = isolate->factory()->NewStringFromAsciiChecked(
        category->toUTF8String(keyword).data());
    plural_categories->set(i, *value);
  }

  // 7. Perform ! CreateDataProperty(options, "pluralCategories",
  // CreateArrayFromList(pluralCategories)).
  Handle<JSArray> plural_categories_value =
      isolate->factory()->NewJSArrayWithElements(plural_categories);
  CreateDataPropertyForOptions(isolate, options, plural_categories_value,
                               "pluralCategories");

  return options;
}

323 324 325 326 327 328 329 330
namespace {

class PluralRulesAvailableLocales {
 public:
  PluralRulesAvailableLocales() {
    UErrorCode status = U_ZERO_ERROR;
    std::unique_ptr<icu::StringEnumeration> locales(
        icu::PluralRules::getAvailableLocales(status));
Frank Tang's avatar
Frank Tang committed
331
    DCHECK(U_SUCCESS(status));
332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350
    int32_t len = 0;
    const char* locale = nullptr;
    while ((locale = locales->next(&len, status)) != nullptr &&
           U_SUCCESS(status)) {
      std::string str(locale);
      if (len > 3) {
        std::replace(str.begin(), str.end(), '_', '-');
      }
      set_.insert(std::move(str));
    }
  }
  const std::set<std::string>& Get() const { return set_; }

 private:
  std::set<std::string> set_;
};

}  // namespace

351
const std::set<std::string>& JSPluralRules::GetAvailableLocales() {
352 353 354
  static base::LazyInstance<PluralRulesAvailableLocales>::type
      available_locales = LAZY_INSTANCE_INITIALIZER;
  return available_locales.Pointer()->Get();
355 356
}

357 358
}  // namespace internal
}  // namespace v8