// 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 #include #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/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) { 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: DisplayNamesInternal() = default; virtual ~DisplayNamesInternal() = default; virtual const char* type() const = 0; virtual icu::Locale locale() const = 0; virtual Maybe 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 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 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. // 1.b If IsStructurallyValidLanguageTag(code) is false, throw a RangeError // exception. icu::Locale l = icu::Locale(icu::Locale::forLanguageTag(code, status).getBaseName()); // 1.c Set code to CanonicalizeUnicodeLocaleId(code). l.canonicalize(status); std::string checked = l.toLanguageTag(status); if (U_FAILURE(status)) { THROW_NEW_ERROR_RETURN_VALUE( isolate, NewRangeError(MessageTemplate::kInvalidArgument), Nothing()); } 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 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 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 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 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 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 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()); } 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 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()); } 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 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()); } return Just(generator_->getFieldDisplayName(field, width_)); } private: icu::Locale locale_; UDateTimePGDisplayWidth width_; std::unique_ptr 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::New(Isolate* isolate, Handle map, Handle locales, Handle input_options) { const char* service = "Intl.DisplayNames"; Factory* factory = isolate->factory(); Handle options; // 3. Let requestedLocales be ? CanonicalizeLocaleList(locales). Maybe> maybe_requested_locales = Intl::CanonicalizeLocaleList(isolate, locales); MAYBE_RETURN(maybe_requested_locales, Handle()); std::vector 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 maybe_locale_matcher = Intl::GetLocaleMatcher(isolate, options, service); MAYBE_RETURN(maybe_locale_matcher, MaybeHandle()); // 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 relevant_extension_keys = {}; // 9. Let r be ResolveLocale(%DisplayNames%.[[AvailableLocales]], // requestedLocales, opt, %DisplayNames%.[[RelevantExtensionKeys]]). Maybe 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