// 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" #include "src/isolate-inl.h" #include "src/objects/intl-objects.h" #include "src/objects/js-plural-rules-inl.h" #include "unicode/decimfmt.h" #include "unicode/locid.h" #include "unicode/numfmt.h" #include "unicode/plurrule.h" #include "unicode/strenum.h" namespace v8 { namespace internal { namespace { bool CreateICUPluralRules(Isolate* isolate, const icu::Locale& icu_locale, const char* type_string, std::unique_ptr* pl, std::unique_ptr* nf) { // 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; UPluralType type = UPLURAL_TYPE_CARDINAL; if (strcmp(type_string, "ordinal") == 0) { type = UPLURAL_TYPE_ORDINAL; } else { CHECK_EQ(0, strcmp(type_string, "cardinal")); } std::unique_ptr plural_rules( icu::PluralRules::forLocale(icu_locale, type, status)); if (U_FAILURE(status)) { return false; } CHECK_NOT_NULL(plural_rules.get()); std::unique_ptr number_format( static_cast( icu::NumberFormat::createInstance(icu_locale, UNUM_DECIMAL, status))); if (U_FAILURE(status)) { return false; } CHECK_NOT_NULL(number_format.get()); *pl = std::move(plural_rules); *nf = std::move(number_format); return true; } void InitializeICUPluralRules( Isolate* isolate, Handle locale, const char* type, std::unique_ptr* plural_rules, std::unique_ptr* number_format) { icu::Locale icu_locale = Intl::CreateICULocale(isolate, locale); DCHECK(!icu_locale.isBogus()); bool success = CreateICUPluralRules(isolate, icu_locale, type, plural_rules, number_format); if (!success) { // Remove extensions and try again. icu::Locale no_extension_locale(icu_locale.getBaseName()); success = CreateICUPluralRules(isolate, no_extension_locale, type, plural_rules, number_format); if (!success) { FATAL("Failed to create ICU PluralRules, are ICU data files missing?"); } } CHECK_NOT_NULL((*plural_rules).get()); CHECK_NOT_NULL((*number_format).get()); } } // namespace // static MaybeHandle JSPluralRules::InitializePluralRules( Isolate* isolate, Handle plural_rules, Handle locales, Handle options_obj) { // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales). // TODO(jkummerow): Port ResolveLocale, then use the C++ version of // CanonicalizeLocaleList here. Handle requested_locales; ASSIGN_RETURN_ON_EXCEPTION(isolate, requested_locales, Intl::CanonicalizeLocaleListJS(isolate, locales), JSPluralRules); // 2. If options is undefined, then if (options_obj->IsUndefined(isolate)) { // 2. a. Let options be ObjectCreate(null). options_obj = isolate->factory()->NewJSObjectWithNullProto(); } else { // 3. Else // 3. a. Let options be ? ToObject(options). ASSIGN_RETURN_ON_EXCEPTION( isolate, options_obj, Object::ToObject(isolate, options_obj, "Intl.PluralRules"), JSPluralRules); } // At this point, options_obj can either be a JSObject or a JSProxy only. Handle options = Handle::cast(options_obj); // TODO(gsathya): This is currently done as part of the // Intl::ResolveLocale call below. Fix this once resolveLocale is // changed to not do the lookup. // // 5. Let matcher be ? GetOption(options, "localeMatcher", "string", // « "lookup", "best fit" », "best fit"). // 6. Set opt.[[localeMatcher]] to matcher. // 7. Let t be ? GetOption(options, "type", "string", « "cardinal", // "ordinal" », "cardinal"). std::vector values = {"cardinal", "ordinal"}; std::unique_ptr type_str = nullptr; const char* type_cstr = "cardinal"; Maybe found = Intl::GetStringOption(isolate, options, "type", values, "Intl.PluralRules", &type_str); MAYBE_RETURN(found, MaybeHandle()); if (found.FromJust()) { type_cstr = type_str.get(); } // 8. Set pluralRules.[[Type]] to t. Handle type = isolate->factory()->NewStringFromAsciiChecked(type_cstr); plural_rules->set_type(*type); // 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). Handle r; ASSIGN_RETURN_ON_EXCEPTION( isolate, r, Intl::ResolveLocale(isolate, "pluralrules", requested_locales, options), JSPluralRules); Handle locale_str = isolate->factory()->locale_string(); Handle locale_obj = JSObject::GetDataProperty(r, locale_str); // The locale has to be a string. Either a user provided // canonicalized string or the default locale. CHECK(locale_obj->IsString()); Handle locale = Handle::cast(locale_obj); // 12. Set pluralRules.[[Locale]] to the value of r.[[locale]]. plural_rules->set_locale(*locale); std::unique_ptr icu_plural_rules; std::unique_ptr icu_decimal_format; InitializeICUPluralRules(isolate, locale, type_cstr, &icu_plural_rules, &icu_decimal_format); CHECK_NOT_NULL(icu_plural_rules.get()); CHECK_NOT_NULL(icu_decimal_format.get()); // 9. Perform ? SetNumberFormatDigitOptions(pluralRules, options, 0, 3). Maybe done = Intl::SetNumberFormatDigitOptions( isolate, icu_decimal_format.get(), options, 0, 3); MAYBE_RETURN(done, MaybeHandle()); Handle> managed_plural_rules = Managed::FromUniquePtr(isolate, 0, std::move(icu_plural_rules)); plural_rules->set_icu_plural_rules(*managed_plural_rules); Handle> managed_decimal_format = Managed::FromUniquePtr(isolate, 0, std::move(icu_decimal_format)); plural_rules->set_icu_decimal_format(*managed_decimal_format); // 13. Return pluralRules. return plural_rules; } MaybeHandle JSPluralRules::ResolvePlural( Isolate* isolate, Handle plural_rules, Handle number) { icu::PluralRules* icu_plural_rules = plural_rules->icu_plural_rules()->raw(); CHECK_NOT_NULL(icu_plural_rules); icu::DecimalFormat* icu_decimal_format = plural_rules->icu_decimal_format()->raw(); CHECK_NOT_NULL(icu_decimal_format); // Currently, PluralRules doesn't implement all the options for rounding that // the Intl spec provides; format and parse the number to round to the // appropriate amount, then apply PluralRules. // // TODO(littledan): If a future ICU version supports an extended API to avoid // this step, then switch to that API. Bug thread: // http://bugs.icu-project.org/trac/ticket/12763 icu::UnicodeString rounded_string; icu_decimal_format->format(number->Number(), rounded_string); icu::Formattable formattable; UErrorCode status = U_ZERO_ERROR; icu_decimal_format->parse(rounded_string, formattable, status); CHECK(U_SUCCESS(status)); double rounded = formattable.getDouble(status); CHECK(U_SUCCESS(status)); icu::UnicodeString result = icu_plural_rules->select(rounded); return isolate->factory()->NewStringFromTwoByte(Vector( reinterpret_cast(result.getBuffer()), result.length())); } namespace { void CreateDataPropertyForOptions(Isolate* isolate, Handle options, Handle value, const char* key) { Handle 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. CHECK(JSReceiver::CreateDataProperty(isolate, options, key_str, value, kDontThrow) .FromJust()); } void CreateDataPropertyForOptions(Isolate* isolate, Handle options, int value, const char* key) { Handle value_smi(Smi::FromInt(value), isolate); CreateDataPropertyForOptions(isolate, options, value_smi, key); } } // namespace Handle JSPluralRules::ResolvedOptions( Isolate* isolate, Handle plural_rules) { Handle options = isolate->factory()->NewJSObject(isolate->object_function()); Handle locale_value(plural_rules->locale(), isolate); CreateDataPropertyForOptions(isolate, options, locale_value, "locale"); Handle type_value(plural_rules->type(), isolate); CreateDataPropertyForOptions(isolate, options, type_value, "type"); icu::DecimalFormat* icu_decimal_format = plural_rules->icu_decimal_format()->raw(); CHECK_NOT_NULL(icu_decimal_format); // This is a safe upcast as icu::DecimalFormat inherits from // icu::NumberFormat. icu::NumberFormat* icu_number_format = static_cast(icu_decimal_format); int min_int_digits = icu_number_format->getMinimumIntegerDigits(); CreateDataPropertyForOptions(isolate, options, min_int_digits, "minimumIntegerDigits"); int min_fraction_digits = icu_number_format->getMinimumFractionDigits(); CreateDataPropertyForOptions(isolate, options, min_fraction_digits, "minimumFractionDigits"); int max_fraction_digits = icu_number_format->getMaximumFractionDigits(); CreateDataPropertyForOptions(isolate, options, max_fraction_digits, "maximumFractionDigits"); if (icu_decimal_format->areSignificantDigitsUsed()) { int min_significant_digits = icu_decimal_format->getMinimumSignificantDigits(); CreateDataPropertyForOptions(isolate, options, min_significant_digits, "minimumSignificantDigits"); int max_significant_digits = icu_decimal_format->getMaximumSignificantDigits(); CreateDataPropertyForOptions(isolate, options, max_significant_digits, "maximumSignificantDigits"); } // 6. Let pluralCategories be a List of Strings representing the // possible results of PluralRuleSelect for the selected locale pr. icu::PluralRules* icu_plural_rules = plural_rules->icu_plural_rules()->raw(); CHECK_NOT_NULL(icu_plural_rules); UErrorCode status = U_ZERO_ERROR; std::unique_ptr categories( icu_plural_rules->getKeywords(status)); CHECK(U_SUCCESS(status)); int32_t count = categories->count(status); CHECK(U_SUCCESS(status)); Handle plural_categories = isolate->factory()->NewFixedArray(count); for (int32_t i = 0; i < count; i++) { const icu::UnicodeString* category = categories->snext(status); CHECK(U_SUCCESS(status)); if (category == nullptr) break; std::string keyword; Handle value = isolate->factory()->NewStringFromAsciiChecked( category->toUTF8String(keyword).data()); plural_categories->set(i, *value); } // 7. Perform ! CreateDataProperty(options, "pluralCategories", // CreateArrayFromList(pluralCategories)). Handle plural_categories_value = isolate->factory()->NewJSArrayWithElements(plural_categories); CreateDataPropertyForOptions(isolate, options, plural_categories_value, "pluralCategories"); return options; } } // namespace internal } // namespace v8