blob: 48280ea152e15526ef0fcbcf37a8c1eb28e80101 [file] [log] [blame]
// Copyright 2019 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-display-names.h"
#include <memory>
#include <vector>
#include "src/execution/isolate.h"
#include "src/heap/factory.h"
#include "src/objects/intl-objects.h"
#include "src/objects/js-display-names-inl.h"
#include "src/objects/js-locale.h"
#include "src/objects/managed-inl.h"
#include "src/objects/objects-inl.h"
#include "src/objects/option-utils.h"
#include "unicode/dtfmtsym.h"
#include "unicode/dtptngen.h"
#include "unicode/localebuilder.h"
#include "unicode/locdspnm.h"
#include "unicode/measfmt.h"
#include "unicode/timezone.h"
#include "unicode/tznames.h"
#include "unicode/uloc.h"
#include "unicode/unistr.h"
#include "unicode/uscript.h"
namespace v8 {
namespace internal {
namespace {
// Type: identifying the types of the display names.
//
// ecma402/#sec-properties-of-intl-displaynames-instances
enum class Type {
kUndefined,
kLanguage,
kRegion,
kScript,
kCurrency,
kCalendar,
kDateTimeField
};
bool IsUnicodeScriptSubtag(const std::string& value) {
UErrorCode status = U_ZERO_ERROR;
icu::LocaleBuilder builder;
builder.setScript(value).build(status);
return U_SUCCESS(status);
}
bool IsUnicodeRegionSubtag(const std::string& value) {
if (value.empty()) return false;
UErrorCode status = U_ZERO_ERROR;
icu::LocaleBuilder builder;
builder.setRegion(value).build(status);
return U_SUCCESS(status);
}
UDisplayContext ToUDisplayContext(JSDisplayNames::Style style) {
switch (style) {
case JSDisplayNames::Style::kLong:
return UDISPCTX_LENGTH_FULL;
case JSDisplayNames::Style::kShort:
case JSDisplayNames::Style::kNarrow:
return UDISPCTX_LENGTH_SHORT;
}
}
} // anonymous namespace
// Abstract class for all different types.
class DisplayNamesInternal {
public:
static constexpr ExternalPointerTag kManagedTag = kDisplayNamesInternalTag;
DisplayNamesInternal() = default;
virtual ~DisplayNamesInternal() = default;
virtual const char* type() const = 0;
virtual icu::Locale locale() const = 0;
virtual Maybe<icu::UnicodeString> of(Isolate* isolate,
const char* code) const = 0;
};
namespace {
class LocaleDisplayNamesCommon : public DisplayNamesInternal {
public:
LocaleDisplayNamesCommon(const icu::Locale& locale,
JSDisplayNames::Style style, bool fallback,
bool dialect)
: style_(style) {
UDisplayContext sub =
fallback ? UDISPCTX_SUBSTITUTE : UDISPCTX_NO_SUBSTITUTE;
UDisplayContext dialect_context =
dialect ? UDISPCTX_DIALECT_NAMES : UDISPCTX_STANDARD_NAMES;
UDisplayContext display_context[] = {ToUDisplayContext(style_),
dialect_context,
UDISPCTX_CAPITALIZATION_NONE, sub};
ldn_.reset(
icu::LocaleDisplayNames::createInstance(locale, display_context, 4));
}
~LocaleDisplayNamesCommon() override = default;
icu::Locale locale() const override { return ldn_->getLocale(); }
protected:
icu::LocaleDisplayNames* locale_display_names() const { return ldn_.get(); }
private:
std::unique_ptr<icu::LocaleDisplayNames> ldn_;
JSDisplayNames::Style style_;
};
class LanguageNames : public LocaleDisplayNamesCommon {
public:
LanguageNames(const icu::Locale& locale, JSDisplayNames::Style style,
bool fallback, bool dialect)
: LocaleDisplayNamesCommon(locale, style, fallback, dialect) {}
~LanguageNames() override = default;
const char* type() const override { return "language"; }
Maybe<icu::UnicodeString> of(Isolate* isolate,
const char* code) const override {
UErrorCode status = U_ZERO_ERROR;
// 1.a If code does not match the unicode_language_id production, throw a
// RangeError exception.
icu::Locale tagLocale = icu::Locale::forLanguageTag(code, status);
icu::Locale l(tagLocale.getBaseName());
if (U_FAILURE(status) || tagLocale != l ||
!JSLocale::StartsWithUnicodeLanguageId(code)) {
THROW_NEW_ERROR_RETURN_VALUE(
isolate, NewRangeError(MessageTemplate::kInvalidArgument),
Nothing<icu::UnicodeString>());
}
// 1.b If IsStructurallyValidLanguageTag(code) is false, throw a RangeError
// exception.
// 1.c Set code to CanonicalizeUnicodeLocaleId(code).
l.canonicalize(status);
std::string checked = l.toLanguageTag<std::string>(status);
if (U_FAILURE(status)) {
THROW_NEW_ERROR_RETURN_VALUE(
isolate, NewRangeError(MessageTemplate::kInvalidArgument),
Nothing<icu::UnicodeString>());
}
icu::UnicodeString result;
locale_display_names()->localeDisplayName(checked.c_str(), result);
return Just(result);
}
};
class RegionNames : public LocaleDisplayNamesCommon {
public:
RegionNames(const icu::Locale& locale, JSDisplayNames::Style style,
bool fallback, bool dialect)
: LocaleDisplayNamesCommon(locale, style, fallback, dialect) {}
~RegionNames() override = default;
const char* type() const override { return "region"; }
Maybe<icu::UnicodeString> of(Isolate* isolate,
const char* code) const override {
std::string code_str(code);
if (!IsUnicodeRegionSubtag(code_str)) {
THROW_NEW_ERROR_RETURN_VALUE(
isolate, NewRangeError(MessageTemplate::kInvalidArgument),
Nothing<icu::UnicodeString>());
}
icu::UnicodeString result;
locale_display_names()->regionDisplayName(code_str.c_str(), result);
return Just(result);
}
};
class ScriptNames : public LocaleDisplayNamesCommon {
public:
ScriptNames(const icu::Locale& locale, JSDisplayNames::Style style,
bool fallback, bool dialect)
: LocaleDisplayNamesCommon(locale, style, fallback, dialect) {}
~ScriptNames() override = default;
const char* type() const override { return "script"; }
Maybe<icu::UnicodeString> of(Isolate* isolate,
const char* code) const override {
std::string code_str(code);
if (!IsUnicodeScriptSubtag(code_str)) {
THROW_NEW_ERROR_RETURN_VALUE(
isolate, NewRangeError(MessageTemplate::kInvalidArgument),
Nothing<icu::UnicodeString>());
}
icu::UnicodeString result;
locale_display_names()->scriptDisplayName(code_str.c_str(), result);
return Just(result);
}
};
class KeyValueDisplayNames : public LocaleDisplayNamesCommon {
public:
KeyValueDisplayNames(const icu::Locale& locale, JSDisplayNames::Style style,
bool fallback, bool dialect, const char* key,
bool prevent_fallback)
: LocaleDisplayNamesCommon(locale, style, fallback, dialect),
key_(key),
prevent_fallback_(prevent_fallback) {}
~KeyValueDisplayNames() override = default;
const char* type() const override { return key_.c_str(); }
Maybe<icu::UnicodeString> of(Isolate* isolate,
const char* code) const override {
std::string code_str(code);
icu::UnicodeString result;
locale_display_names()->keyValueDisplayName(key_.c_str(), code_str.c_str(),
result);
// Work around the issue that the keyValueDisplayNames ignore no
// substituion and always fallback.
if (prevent_fallback_ && (result.length() == 3) &&
(code_str.length() == 3) &&
(result == icu::UnicodeString(code_str.c_str(), -1, US_INV))) {
result.setToBogus();
}
return Just(result);
}
private:
std::string key_;
bool prevent_fallback_;
};
class CurrencyNames : public KeyValueDisplayNames {
public:
CurrencyNames(const icu::Locale& locale, JSDisplayNames::Style style,
bool fallback, bool dialect)
: KeyValueDisplayNames(locale, style, fallback, dialect, "currency",
fallback == false) {}
~CurrencyNames() override = default;
Maybe<icu::UnicodeString> of(Isolate* isolate,
const char* code) const override {
std::string code_str(code);
if (!Intl::IsWellFormedCurrency(code_str)) {
THROW_NEW_ERROR_RETURN_VALUE(
isolate, NewRangeError(MessageTemplate::kInvalidArgument),
Nothing<icu::UnicodeString>());
}
return KeyValueDisplayNames::of(isolate, code);
}
};
class CalendarNames : public KeyValueDisplayNames {
public:
CalendarNames(const icu::Locale& locale, JSDisplayNames::Style style,
bool fallback, bool dialect)
: KeyValueDisplayNames(locale, style, fallback, dialect, "calendar",
false) {}
~CalendarNames() override = default;
Maybe<icu::UnicodeString> of(Isolate* isolate,
const char* code) const override {
std::string code_str(code);
if (!Intl::IsWellFormedCalendar(code_str)) {
THROW_NEW_ERROR_RETURN_VALUE(
isolate, NewRangeError(MessageTemplate::kInvalidArgument),
Nothing<icu::UnicodeString>());
}
return KeyValueDisplayNames::of(isolate, strcmp(code, "gregory") == 0
? "gregorian"
: strcmp(code, "ethioaa") == 0
? "ethiopic-amete-alem"
: code);
}
};
UDateTimePGDisplayWidth StyleToUDateTimePGDisplayWidth(
JSDisplayNames::Style style) {
switch (style) {
case JSDisplayNames::Style::kLong:
return UDATPG_WIDE;
case JSDisplayNames::Style::kShort:
return UDATPG_ABBREVIATED;
case JSDisplayNames::Style::kNarrow:
return UDATPG_NARROW;
}
}
UDateTimePatternField StringToUDateTimePatternField(const char* code) {
switch (code[0]) {
case 'd':
if (strcmp(code, "day") == 0) return UDATPG_DAY_FIELD;
if (strcmp(code, "dayPeriod") == 0) return UDATPG_DAYPERIOD_FIELD;
break;
case 'e':
if (strcmp(code, "era") == 0) return UDATPG_ERA_FIELD;
break;
case 'h':
if (strcmp(code, "hour") == 0) return UDATPG_HOUR_FIELD;
break;
case 'm':
if (strcmp(code, "minute") == 0) return UDATPG_MINUTE_FIELD;
if (strcmp(code, "month") == 0) return UDATPG_MONTH_FIELD;
break;
case 'q':
if (strcmp(code, "quarter") == 0) return UDATPG_QUARTER_FIELD;
break;
case 's':
if (strcmp(code, "second") == 0) return UDATPG_SECOND_FIELD;
break;
case 't':
if (strcmp(code, "timeZoneName") == 0) return UDATPG_ZONE_FIELD;
break;
case 'w':
if (strcmp(code, "weekOfYear") == 0) return UDATPG_WEEK_OF_YEAR_FIELD;
if (strcmp(code, "weekday") == 0) return UDATPG_WEEKDAY_FIELD;
break;
case 'y':
if (strcmp(code, "year") == 0) return UDATPG_YEAR_FIELD;
break;
default:
break;
}
return UDATPG_FIELD_COUNT;
}
class DateTimeFieldNames : public DisplayNamesInternal {
public:
DateTimeFieldNames(const icu::Locale& locale, JSDisplayNames::Style style,
bool fallback)
: locale_(locale), width_(StyleToUDateTimePGDisplayWidth(style)) {
UErrorCode status = U_ZERO_ERROR;
generator_.reset(
icu::DateTimePatternGenerator::createInstance(locale_, status));
DCHECK(U_SUCCESS(status));
}
~DateTimeFieldNames() override = default;
const char* type() const override { return "dateTimeField"; }
icu::Locale locale() const override { return locale_; }
Maybe<icu::UnicodeString> of(Isolate* isolate,
const char* code) const override {
UDateTimePatternField field = StringToUDateTimePatternField(code);
if (field == UDATPG_FIELD_COUNT) {
THROW_NEW_ERROR_RETURN_VALUE(
isolate, NewRangeError(MessageTemplate::kInvalidArgument),
Nothing<icu::UnicodeString>());
}
return Just(generator_->getFieldDisplayName(field, width_));
}
private:
icu::Locale locale_;
UDateTimePGDisplayWidth width_;
std::unique_ptr<icu::DateTimePatternGenerator> generator_;
};
DisplayNamesInternal* CreateInternal(const icu::Locale& locale,
JSDisplayNames::Style style, Type type,
bool fallback, bool dialect) {
switch (type) {
case Type::kLanguage:
return new LanguageNames(locale, style, fallback, dialect);
case Type::kRegion:
return new RegionNames(locale, style, fallback, false);
case Type::kScript:
return new ScriptNames(locale, style, fallback, false);
case Type::kCurrency:
return new CurrencyNames(locale, style, fallback, false);
case Type::kCalendar:
return new CalendarNames(locale, style, fallback, false);
case Type::kDateTimeField:
return new DateTimeFieldNames(locale, style, fallback);
default:
UNREACHABLE();
}
}
} // anonymous namespace
// ecma402 #sec-Intl.DisplayNames
MaybeHandle<JSDisplayNames> JSDisplayNames::New(Isolate* isolate,
Handle<Map> map,
Handle<Object> locales,
Handle<Object> input_options) {
const char* service = "Intl.DisplayNames";
Factory* factory = isolate->factory();
Handle<JSReceiver> options;
// 3. Let requestedLocales be ? CanonicalizeLocaleList(locales).
Maybe<std::vector<std::string>> maybe_requested_locales =
Intl::CanonicalizeLocaleList(isolate, locales);
MAYBE_RETURN(maybe_requested_locales, Handle<JSDisplayNames>());
std::vector<std::string> requested_locales =
maybe_requested_locales.FromJust();
// 4. Let options be ? GetOptionsObject(options).
ASSIGN_RETURN_ON_EXCEPTION(isolate, options,
GetOptionsObject(isolate, input_options, service),
JSDisplayNames);
// Note: No need to create a record. It's not observable.
// 5. Let opt be a new Record.
// 6. Let localeData be %DisplayNames%.[[LocaleData]].
// 7. Let matcher be ? GetOption(options, "localeMatcher", "string", «
// "lookup", "best fit" », "best fit").
Maybe<Intl::MatcherOption> maybe_locale_matcher =
Intl::GetLocaleMatcher(isolate, options, service);
MAYBE_RETURN(maybe_locale_matcher, MaybeHandle<JSDisplayNames>());
// 8. Set opt.[[localeMatcher]] to matcher.
Intl::MatcherOption matcher = maybe_locale_matcher.FromJust();
// ecma402/#sec-Intl.DisplayNames-internal-slots
// The value of the [[RelevantExtensionKeys]] internal slot is
// « ».
std::set<std::string> relevant_extension_keys = {};
// 9. Let r be ResolveLocale(%DisplayNames%.[[AvailableLocales]],
// requestedLocales, opt, %DisplayNames%.[[RelevantExtensionKeys]]).
Maybe<Intl::ResolvedLocale> maybe_resolve_locale =
Intl::ResolveLocale(isolate, JSDisplayNames::GetAvailableLocales(),
requested_locales, matcher, relevant_extension_keys);
if (maybe_resolve_locale.IsNothing()) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
JSDisplayNames);
}
Intl::ResolvedLocale r = maybe_resolve_locale.FromJust();
icu::Locale icu_locale = r.icu_locale;
// 10. Let s be ? GetOption(options, "style", "string",
// «"long", "short", "narrow"», "long").
Maybe<Style> maybe_style = GetStringOption<Style>(
isolate, options, "style", service, {"long", "short", "narrow"},
{Style::kLong, Style::kShort, Style::kNarrow}, Style::kLong);
MAYBE_RETURN(maybe_style, MaybeHandle<JSDisplayNames>());
Style style_enum = maybe_style.FromJust();
// 11. Set displayNames.[[Style]] to style.
// 12. Let type be ? GetOption(options, "type", "string", « "language",
// "region", "script", "currency" , "calendar", "dateTimeField", "unit"»,
// undefined).
Maybe<Type> maybe_type = GetStringOption<Type>(
isolate, options, "type", service,
{"language", "region", "script", "currency", "calendar", "dateTimeField"},
{Type::kLanguage, Type::kRegion, Type::kScript, Type::kCurrency,
Type::kCalendar, Type::kDateTimeField},
Type::kUndefined);
MAYBE_RETURN(maybe_type, MaybeHandle<JSDisplayNames>());
Type type_enum = maybe_type.FromJust();
// 13. If type is undefined, throw a TypeError exception.
if (type_enum == Type::kUndefined) {
THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kInvalidArgument),
JSDisplayNames);
}
// 14. Set displayNames.[[Type]] to type.
// 15. Let fallback be ? GetOption(options, "fallback", "string",
// « "code", "none" », "code").
Maybe<Fallback> maybe_fallback = GetStringOption<Fallback>(
isolate, options, "fallback", service, {"code", "none"},
{Fallback::kCode, Fallback::kNone}, Fallback::kCode);
MAYBE_RETURN(maybe_fallback, MaybeHandle<JSDisplayNames>());
Fallback fallback_enum = maybe_fallback.FromJust();
// 16. Set displayNames.[[Fallback]] to fallback.
LanguageDisplay language_display_enum = LanguageDisplay::kDialect;
// 24. Let languageDisplay be ? GetOption(options, "languageDisplay",
// "string", « "dialect", "standard" », "dialect").
Maybe<LanguageDisplay> maybe_language_display =
GetStringOption<LanguageDisplay>(
isolate, options, "languageDisplay", service, {"dialect", "standard"},
{LanguageDisplay::kDialect, LanguageDisplay::kStandard},
LanguageDisplay::kDialect);
MAYBE_RETURN(maybe_language_display, MaybeHandle<JSDisplayNames>());
// 25. If type is "language", then
if (type_enum == Type::kLanguage) {
// a. Set displayNames.[[LanguageDisplay]] to languageDisplay.
language_display_enum = maybe_language_display.FromJust();
}
// Set displayNames.[[Fallback]] to fallback.
// 17. Set displayNames.[[Locale]] to the value of r.[[Locale]].
// Let dataLocale be r.[[dataLocale]].
// Let dataLocaleData be localeData.[[<dataLocale>]].
// Let types be dataLocaleData.[[types]].
// Assert: types is a Record (see 1.3.3).
// Let typeFields be types.[[<type>]].
// Assert: typeFields is a Record (see 1.3.3).
// Let styleFields be typeFields.[[<style>]].
// Assert: styleFields is a Record (see 1.3.3).
// Set displayNames.[[Fields]] to styleFields.
DisplayNamesInternal* internal = CreateInternal(
icu_locale, style_enum, type_enum, fallback_enum == Fallback::kCode,
language_display_enum == LanguageDisplay::kDialect);
if (internal == nullptr) {
THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError),
JSDisplayNames);
}
Handle<Managed<DisplayNamesInternal>> managed_internal =
Managed<DisplayNamesInternal>::FromRawPtr(isolate, 0, internal);
Handle<JSDisplayNames> display_names =
Handle<JSDisplayNames>::cast(factory->NewFastOrSlowJSObjectFromMap(map));
display_names->set_flags(0);
display_names->set_style(style_enum);
display_names->set_fallback(fallback_enum);
display_names->set_language_display(language_display_enum);
DisallowGarbageCollection no_gc;
display_names->set_internal(*managed_internal);
// Return displayNames.
return display_names;
}
// ecma402 #sec-Intl.DisplayNames.prototype.resolvedOptions
Handle<JSObject> JSDisplayNames::ResolvedOptions(
Isolate* isolate, Handle<JSDisplayNames> display_names) {
Factory* factory = isolate->factory();
// 4. Let options be ! ObjectCreate(%ObjectPrototype%).
Handle<JSObject> options = factory->NewJSObject(isolate->object_function());
DisplayNamesInternal* internal = display_names->internal()->raw();
Maybe<std::string> maybe_locale = Intl::ToLanguageTag(internal->locale());
DCHECK(maybe_locale.IsJust());
Handle<String> locale = isolate->factory()->NewStringFromAsciiChecked(
maybe_locale.FromJust().c_str());
Handle<String> style = display_names->StyleAsString();
Handle<String> type = factory->NewStringFromAsciiChecked(internal->type());
Handle<String> fallback = display_names->FallbackAsString();
Handle<String> language_display = display_names->LanguageDisplayAsString();
Maybe<bool> maybe_create_locale = JSReceiver::CreateDataProperty(
isolate, options, factory->locale_string(), locale, Just(kDontThrow));
DCHECK(maybe_create_locale.FromJust());
USE(maybe_create_locale);
Maybe<bool> maybe_create_style = JSReceiver::CreateDataProperty(
isolate, options, factory->style_string(), style, Just(kDontThrow));
DCHECK(maybe_create_style.FromJust());
USE(maybe_create_style);
Maybe<bool> maybe_create_type = JSReceiver::CreateDataProperty(
isolate, options, factory->type_string(), type, Just(kDontThrow));
DCHECK(maybe_create_type.FromJust());
USE(maybe_create_type);
Maybe<bool> maybe_create_fallback = JSReceiver::CreateDataProperty(
isolate, options, factory->fallback_string(), fallback, Just(kDontThrow));
DCHECK(maybe_create_fallback.FromJust());
USE(maybe_create_fallback);
if (std::strcmp("language", internal->type()) == 0) {
Maybe<bool> maybe_create_language_display =
JSReceiver::CreateDataProperty(isolate, options,
factory->languageDisplay_string(),
language_display, Just(kDontThrow));
DCHECK(maybe_create_language_display.FromJust());
USE(maybe_create_language_display);
}
return options;
}
// ecma402 #sec-Intl.DisplayNames.prototype.of
MaybeHandle<Object> JSDisplayNames::Of(Isolate* isolate,
Handle<JSDisplayNames> display_names,
Handle<Object> code_obj) {
Handle<String> code;
ASSIGN_RETURN_ON_EXCEPTION(isolate, code, Object::ToString(isolate, code_obj),
Object);
DisplayNamesInternal* internal = display_names->internal()->raw();
Maybe<icu::UnicodeString> maybe_result =
internal->of(isolate, code->ToCString().get());
MAYBE_RETURN(maybe_result, Handle<Object>());
icu::UnicodeString result = maybe_result.FromJust();
if (result.isBogus()) {
return isolate->factory()->undefined_value();
}
return Intl::ToString(isolate, result).ToHandleChecked();
}
namespace {
struct CheckCalendar {
static const char* key() { return "calendar"; }
static const char* path() { return nullptr; }
};
} // namespace
const std::set<std::string>& JSDisplayNames::GetAvailableLocales() {
static base::LazyInstance<Intl::AvailableLocales<CheckCalendar>>::type
available_locales = LAZY_INSTANCE_INITIALIZER;
return available_locales.Pointer()->Get();
}
Handle<String> JSDisplayNames::StyleAsString() const {
switch (style()) {
case Style::kLong:
return GetReadOnlyRoots().long_string_handle();
case Style::kShort:
return GetReadOnlyRoots().short_string_handle();
case Style::kNarrow:
return GetReadOnlyRoots().narrow_string_handle();
}
UNREACHABLE();
}
Handle<String> JSDisplayNames::FallbackAsString() const {
switch (fallback()) {
case Fallback::kCode:
return GetReadOnlyRoots().code_string_handle();
case Fallback::kNone:
return GetReadOnlyRoots().none_string_handle();
}
UNREACHABLE();
}
Handle<String> JSDisplayNames::LanguageDisplayAsString() const {
switch (language_display()) {
case LanguageDisplay::kDialect:
return GetReadOnlyRoots().dialect_string_handle();
case LanguageDisplay::kStandard:
return GetReadOnlyRoots().standard_string_handle();
}
UNREACHABLE();
}
} // namespace internal
} // namespace v8