#ifndef V8_INTL_SUPPORT
#error Internationalization is expected to be enabled.
#endif
#include "src/objects/js-plural-rules.h"
#include "src/execution/isolate-inl.h"
#include "src/objects/intl-objects.h"
#include "src/objects/js-number-format.h"
#include "src/objects/js-plural-rules-inl.h"
#include "src/objects/managed-inl.h"
#include "src/objects/option-utils.h"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wshadow"
#include "unicode/locid.h"
#include "unicode/numberformatter.h"
#include "unicode/numberrangeformatter.h"
#include "unicode/plurrule.h"
#include "unicode/unumberformatter.h"
#pragma GCC diagnostic pop
namespace v8 {
namespace internal {
namespace {
bool CreateICUPluralRules(Isolate* isolate, const icu::Locale& icu_locale,
JSPluralRules::Type type,
std::unique_ptr<icu::PluralRules>* pl) {
UErrorCode status = U_ZERO_ERROR;
UPluralType icu_type = UPLURAL_TYPE_CARDINAL;
if (type == JSPluralRules::Type::ORDINAL) {
icu_type = UPLURAL_TYPE_ORDINAL;
} else {
DCHECK_EQ(JSPluralRules::Type::CARDINAL, type);
}
std::unique_ptr<icu::PluralRules> plural_rules(
icu::PluralRules::forLocale(icu_locale, icu_type, status));
if (U_FAILURE(status)) {
return false;
}
DCHECK_NOT_NULL(plural_rules.get());
*pl = std::move(plural_rules);
return true;
}
}
Handle<String> JSPluralRules::TypeAsString(Isolate* isolate) const {
switch (type()) {
case Type::CARDINAL:
return isolate->factory()->cardinal_string();
case Type::ORDINAL:
return isolate->factory()->ordinal_string();
}
UNREACHABLE();
}
MaybeDirectHandle<JSPluralRules> JSPluralRules::New(
Isolate* isolate, DirectHandle<Map> map, DirectHandle<Object> locales,
DirectHandle<Object> options_obj, const char* service) {
Maybe<std::vector<std::string>> maybe_requested_locales =
Intl::CanonicalizeLocaleList(isolate, locales);
MAYBE_RETURN(maybe_requested_locales, DirectHandle<JSPluralRules>());
std::vector<std::string> requested_locales =
maybe_requested_locales.FromJust();
DirectHandle<JSReceiver> options;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, options, CoerceOptionsToObject(isolate, options_obj, service));
Maybe<Intl::MatcherOption> maybe_locale_matcher =
Intl::GetLocaleMatcher(isolate, options, service);
MAYBE_RETURN(maybe_locale_matcher, MaybeDirectHandle<JSPluralRules>());
Intl::MatcherOption matcher = maybe_locale_matcher.FromJust();
Maybe<Type> maybe_type = GetStringOption<Type>(
isolate, options, isolate->factory()->type_string(), service,
std::to_array<const std::string_view>({"cardinal", "ordinal"}),
std::array{Type::CARDINAL, Type::ORDINAL}, Type::CARDINAL);
MAYBE_RETURN(maybe_type, MaybeDirectHandle<JSPluralRules>());
Type type = maybe_type.FromJust();
Intl::Notation notation = Intl::Notation::STANDARD;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, notation,
GetStringOption<Intl::Notation>(
isolate, options, isolate->factory()->notation_string(), service,
std::to_array<const std::string_view>(
{"standard", "scientific", "engineering", "compact"}),
std::array{Intl::Notation::STANDARD, Intl::Notation::SCIENTIFIC,
Intl::Notation::ENGINEERING, Intl::Notation::COMPACT},
Intl::Notation::STANDARD));
Maybe<Intl::CompactDisplay> maybe_compact_display =
GetStringOption<Intl::CompactDisplay>(
isolate, options, isolate->factory()->compactDisplay_string(),
service, std::to_array<const std::string_view>({"short", "long"}),
std::array{Intl::CompactDisplay::SHORT, Intl::CompactDisplay::LONG},
Intl::CompactDisplay::SHORT);
MAYBE_RETURN(maybe_compact_display, MaybeDirectHandle<JSPluralRules>());
Intl::CompactDisplay compact_display = maybe_compact_display.FromJust();
Intl::ResolvedLocale r;
if (!Intl::ResolveLocale(isolate, JSPluralRules::GetAvailableLocales(),
requested_locales, matcher, {})
.To(&r)) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError));
}
DirectHandle<String> locale_str =
isolate->factory()->NewStringFromAsciiChecked(r.locale.c_str());
icu::Locale icu_locale = r.icu_locale;
icu::number::UnlocalizedNumberFormatter settings =
icu::number::UnlocalizedNumberFormatter().roundingMode(UNUM_ROUND_HALFUP);
std::unique_ptr<icu::PluralRules> icu_plural_rules;
bool success =
CreateICUPluralRules(isolate, r.icu_locale, type, &icu_plural_rules);
if (!success || icu_plural_rules == nullptr) {
icu::Locale no_extension_locale(icu_locale.getBaseName());
success = CreateICUPluralRules(isolate, no_extension_locale, type,
&icu_plural_rules);
icu_locale = no_extension_locale;
if (!success || icu_plural_rules == nullptr) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError));
}
}
if (notation != Intl::Notation::STANDARD) {
settings =
settings.notation(Intl::ToICUNotation(notation, compact_display));
}
Maybe<Intl::NumberFormatDigitOptions> maybe_digit_options =
Intl::SetNumberFormatDigitOptions(
isolate, options, 0, 3, notation == Intl::Notation::COMPACT, service);
MAYBE_RETURN(maybe_digit_options, MaybeDirectHandle<JSPluralRules>());
Intl::NumberFormatDigitOptions digit_options = maybe_digit_options.FromJust();
settings =
JSNumberFormat::SetDigitOptionsToFormatter(settings, digit_options);
icu::number::LocalizedNumberFormatter icu_number_formatter =
settings.locale(icu_locale);
DirectHandle<Managed<icu::PluralRules>> managed_plural_rules =
Managed<icu::PluralRules>::From(isolate, 0, std::move(icu_plural_rules));
DirectHandle<Managed<icu::number::LocalizedNumberFormatter>>
managed_number_formatter =
Managed<icu::number::LocalizedNumberFormatter>::From(
isolate, 0,
std::make_shared<icu::number::LocalizedNumberFormatter>(
icu_number_formatter));
DirectHandle<JSPluralRules> plural_rules = Cast<JSPluralRules>(
isolate->factory()->NewFastOrSlowJSObjectFromMap(map));
DisallowGarbageCollection no_gc;
plural_rules->set_flags(0);
plural_rules->set_type(type);
plural_rules->set_locale(*locale_str);
plural_rules->set_icu_plural_rules(*managed_plural_rules);
plural_rules->set_icu_number_formatter(*managed_number_formatter);
return plural_rules;
}
MaybeDirectHandle<String> JSPluralRules::ResolvePlural(
Isolate* isolate, DirectHandle<JSPluralRules> plural_rules, double number) {
icu::PluralRules* icu_plural_rules = plural_rules->icu_plural_rules()->raw();
DCHECK_NOT_NULL(icu_plural_rules);
icu::number::LocalizedNumberFormatter* fmt =
plural_rules->icu_number_formatter()->raw();
DCHECK_NOT_NULL(fmt);
UErrorCode status = U_ZERO_ERROR;
icu::number::FormattedNumber formatted_number =
fmt->formatDouble(number, status);
DCHECK(U_SUCCESS(status));
icu::UnicodeString result =
icu_plural_rules->select(formatted_number, status);
DCHECK(U_SUCCESS(status));
return Intl::ToString(isolate, result);
}
MaybeDirectHandle<String> JSPluralRules::ResolvePluralRange(
Isolate* isolate, DirectHandle<JSPluralRules> plural_rules, double x,
double y) {
icu::PluralRules* icu_plural_rules = plural_rules->icu_plural_rules()->raw();
DCHECK_NOT_NULL(icu_plural_rules);
Maybe<icu::number::LocalizedNumberRangeFormatter> maybe_range_formatter =
JSNumberFormat::GetRangeFormatter(
isolate, plural_rules->locale(),
*plural_rules->icu_number_formatter()->raw());
MAYBE_RETURN(maybe_range_formatter, MaybeDirectHandle<String>());
icu::number::LocalizedNumberRangeFormatter nrfmt =
maybe_range_formatter.FromJust();
UErrorCode status = U_ZERO_ERROR;
icu::number::FormattedNumberRange formatted = nrfmt.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);
}
namespace {
void CreateDataPropertyForOptions(Isolate* isolate,
DirectHandle<JSObject> options,
DirectHandle<Object> value, const char* key) {
DirectHandle<String> key_str =
isolate->factory()->NewStringFromAsciiChecked(key);
Maybe<bool> maybe = JSReceiver::CreateDataProperty(isolate, options, key_str,
value, Just(kDontThrow));
DCHECK(maybe.FromJust());
USE(maybe);
}
void CreateDataPropertyForOptions(Isolate* isolate,
DirectHandle<JSObject> options, int value,
const char* key) {
DirectHandle<Smi> value_smi(Smi::FromInt(value), isolate);
CreateDataPropertyForOptions(isolate, options, value_smi, key);
}
}
DirectHandle<JSObject> JSPluralRules::ResolvedOptions(
Isolate* isolate, DirectHandle<JSPluralRules> plural_rules) {
DirectHandle<JSObject> options =
isolate->factory()->NewJSObject(isolate->object_function());
DirectHandle<String> locale_value(plural_rules->locale(), isolate);
CreateDataPropertyForOptions(isolate, options, locale_value, "locale");
CreateDataPropertyForOptions(isolate, options,
plural_rules->TypeAsString(isolate), "type");
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);
DCHECK(U_SUCCESS(status));
Factory* factory = isolate->factory();
Intl::Notation notation = Intl::NotationFromSkeleton(skeleton);
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->notation_string(),
Intl::NotationAsString(isolate, notation), Just(kDontThrow))
.FromJust());
if (notation == Intl::Notation::COMPACT) {
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->compactDisplay_string(),
Intl::CompactDisplayString(isolate, skeleton), Just(kDontThrow))
.FromJust());
}
CreateDataPropertyForOptions(
isolate, options,
JSNumberFormat::MinimumIntegerDigitsFromSkeleton(skeleton),
"minimumIntegerDigits");
int32_t min = 0, max = 0;
if (JSNumberFormat::SignificantDigitsFromSkeleton(skeleton, &min, &max)) {
CreateDataPropertyForOptions(isolate, options, min,
"minimumSignificantDigits");
CreateDataPropertyForOptions(isolate, options, max,
"maximumSignificantDigits");
} else {
JSNumberFormat::FractionDigitsFromSkeleton(skeleton, &min, &max);
CreateDataPropertyForOptions(isolate, options, min,
"minimumFractionDigits");
CreateDataPropertyForOptions(isolate, options, max,
"maximumFractionDigits");
}
icu::PluralRules* icu_plural_rules = plural_rules->icu_plural_rules()->raw();
DCHECK_NOT_NULL(icu_plural_rules);
std::unique_ptr<icu::StringEnumeration> categories(
icu_plural_rules->getKeywords(status));
DCHECK(U_SUCCESS(status));
int32_t count = categories->count(status);
DCHECK(U_SUCCESS(status));
DirectHandle<FixedArray> plural_categories = factory->NewFixedArray(count);
const std::vector<const char*> kCategories = {"zero", "one", "two",
"few", "many", "other"};
int32_t index = 0;
std::for_each(kCategories.cbegin(), kCategories.cend(), [&](const char* val) {
categories->reset(status);
DCHECK(U_SUCCESS(status));
for (int32_t i = 0; i < count; i++) {
int32_t len;
const char* cat = categories->next(&len, status);
DCHECK(U_SUCCESS(status));
if (cat == nullptr) break;
if (std::strcmp(val, cat) == 0) {
DirectHandle<String> value_string =
factory->NewStringFromAsciiChecked(val);
plural_categories->set(index++, *value_string);
break;
}
}
});
DCHECK(count == index);
DirectHandle<JSArray> plural_categories_value =
factory->NewJSArrayWithElements(plural_categories);
CreateDataPropertyForOptions(isolate, options, plural_categories_value,
"pluralCategories");
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->roundingIncrement_string(),
JSNumberFormat::RoundingIncrement(isolate, skeleton),
Just(kDontThrow))
.FromJust());
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->roundingMode_string(),
JSNumberFormat::RoundingModeString(isolate, skeleton),
Just(kDontThrow))
.FromJust());
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->roundingPriority_string(),
JSNumberFormat::RoundingPriorityString(isolate, skeleton),
Just(kDontThrow))
.FromJust());
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->trailingZeroDisplay_string(),
JSNumberFormat::TrailingZeroDisplayString(isolate, skeleton),
Just(kDontThrow))
.FromJust());
return options;
}
namespace {
class PluralRulesAvailableLocales {
public:
PluralRulesAvailableLocales() {
UErrorCode status = U_ZERO_ERROR;
std::unique_ptr<icu::StringEnumeration> locales(
icu::PluralRules::getAvailableLocales(status));
DCHECK(U_SUCCESS(status));
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_;
};
}
const std::set<std::string>& JSPluralRules::GetAvailableLocales() {
static base::LazyInstance<PluralRulesAvailableLocales>::type
available_locales = LAZY_INSTANCE_INITIALIZER;
return available_locales.Pointer()->Get();
}
}
}