blob: 35b1919f3fb085c9881c9b2f905c500a10330f75 [file]
// 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-locale.h"
#include <algorithm>
#include <cctype>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "src/api/api.h"
#include "src/execution/isolate.h"
#include "src/heap/factory.h"
#include "src/objects/intl-objects.h"
#include "src/objects/js-locale-inl.h"
#include "src/objects/managed-inl.h"
#include "src/objects/objects-inl.h"
#include "src/objects/option-utils.h"
#include "unicode/calendar.h"
#include "unicode/char16ptr.h"
#include "unicode/coll.h"
#include "unicode/dtptngen.h"
#include "unicode/localebuilder.h"
#include "unicode/locid.h"
#include "unicode/ucal.h"
#include "unicode/uloc.h"
#include "unicode/ulocdata.h"
#include "unicode/unistr.h"
namespace v8 {
namespace internal {
namespace {
struct OptionData {
Handle<InternalizedString> (Factory::*object_key)();
const char* key;
const std::span<const std::string_view> possible_values;
bool is_bool_value;
};
struct ValueAndType {
const char* value;
const char* type;
};
// Inserts tags from options into locale string.
Maybe<bool> InsertOptionsIntoLocale(Isolate* isolate,
DirectHandle<JSReceiver> options,
icu::LocaleBuilder* builder) {
DCHECK(isolate);
static const auto hour_cycle_values =
std::to_array<const std::string_view>({"h11", "h12", "h23", "h24"});
static const auto case_first_values =
std::to_array<const std::string_view>({"upper", "lower", "false"});
const auto empty_values = std::span<std::string_view>();
const std::array<OptionData, 7> kOptionToUnicodeTagMap = {
{{&Factory::calendar_string, "ca", empty_values, false},
{&Factory::collation_string, "co", empty_values, false},
{&Factory::firstDayOfWeek_string, "fw", empty_values, false},
{&Factory::hourCycle_string, "hc", hour_cycle_values, false},
{&Factory::caseFirst_string, "kf", case_first_values, false},
{&Factory::numeric_string, "kn", empty_values, true},
{&Factory::numberingSystem_string, "nu", empty_values, false}}};
// TODO(cira): Pass in values as per the spec to make this to be
// spec compliant.
for (const auto& option_to_bcp47 : kOptionToUnicodeTagMap) {
bool value_bool = false;
DirectHandle<String> name =
(isolate->factory()->*option_to_bcp47.object_key)();
bool found = false;
std::string_view value_str;
std::string owned;
if (option_to_bcp47.is_bool_value) {
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, found,
GetBoolOption(isolate, options, name, "locale", &value_bool), {});
} else if (option_to_bcp47.possible_values.empty()) {
// We just wish to fetch the string
DirectHandle<String> output;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, found,
GetStringOption(isolate, options, name, "locale", &output), {});
if (found) {
owned = output->ToStdString();
value_str = owned;
}
} else {
// The string is expected to be in a particular set.
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, value_str,
GetStringOption<std::string_view>(
isolate, options, name, "locale", option_to_bcp47.possible_values,
option_to_bcp47.possible_values, std::string_view()),
{});
if (!value_str.empty()) {
found = true;
}
}
// TODO(cira): Use fallback value if value is not found to make
// this spec compliant.
if (!found) continue;
const char* type = value_str.data();
if (strcmp(option_to_bcp47.key, "fw") == 0) {
const std::array<ValueAndType, 8> kFirstDayValuesAndTypes = {
{{"0", "sun"},
{"1", "mon"},
{"2", "tue"},
{"3", "wed"},
{"4", "thu"},
{"5", "fri"},
{"6", "sat"},
{"7", "sun"}}};
for (const auto& value_to_type : kFirstDayValuesAndTypes) {
if (strcmp(type, value_to_type.value) == 0) {
type = value_to_type.type;
break;
}
}
} else if (option_to_bcp47.is_bool_value) {
type = value_bool ? "true" : "false";
}
DCHECK_NOT_NULL(type);
// Overwrite existing, or insert new key-value to the locale string.
if (!uloc_toLegacyType(uloc_toLegacyKey(option_to_bcp47.key), type)) {
return Just(false);
}
builder->setUnicodeLocaleKeyword(option_to_bcp47.key, type);
}
return Just(true);
}
DirectHandle<Object> UnicodeKeywordValue(Isolate* isolate,
DirectHandle<JSLocale> locale,
const char* key) {
Managed<icu::Locale>::Ptr icu_locale = locale->icu_locale()->ptr();
UErrorCode status = U_ZERO_ERROR;
std::string value =
icu_locale->getUnicodeKeywordValue<std::string>(key, status);
if (status == U_ILLEGAL_ARGUMENT_ERROR || value.empty()) {
return isolate->factory()->undefined_value();
}
if (value == "yes") {
value = "true";
}
if (value == "true" && strcmp(key, "kf") == 0) {
return isolate->factory()->NewStringFromStaticChars("");
}
return isolate->factory()->NewStringFromAsciiChecked(value.c_str());
}
bool IsCheckRange(std::string_view str, size_t min, size_t max,
bool(range_check_func)(char)) {
if (!base::IsInRange(str.length(), min, max)) return false;
for (size_t i = 0; i < str.length(); i++) {
if (!range_check_func(str[i])) return false;
}
return true;
}
bool IsAlpha(std::string_view str, size_t min, size_t max) {
return IsCheckRange(str, min, max, [](char c) -> bool {
return base::IsInRange(c, 'a', 'z') || base::IsInRange(c, 'A', 'Z');
});
}
bool IsDigit(std::string_view str, size_t min, size_t max) {
return IsCheckRange(str, min, max, [](char c) -> bool {
return base::IsInRange(c, '0', '9');
});
}
bool IsAlphanum(std::string_view str, size_t min, size_t max) {
return IsCheckRange(str, min, max, [](char c) -> bool {
return base::IsInRange(c, 'a', 'z') || base::IsInRange(c, 'A', 'Z') ||
base::IsInRange(c, '0', '9');
});
}
bool IsUnicodeLanguageSubtag(std::string_view value) {
// unicode_language_subtag = alpha{2,3} | alpha{5,8};
return IsAlpha(value, 2, 3) || IsAlpha(value, 5, 8);
}
bool IsUnicodeScriptSubtag(std::string_view value) {
// unicode_script_subtag = alpha{4} ;
return IsAlpha(value, 4, 4);
}
bool IsUnicodeRegionSubtag(std::string_view value) {
// unicode_region_subtag = (alpha{2} | digit{3});
return IsAlpha(value, 2, 2) || IsDigit(value, 3, 3);
}
bool IsDigitAlphanum3(std::string_view value) {
return value.length() == 4 && base::IsInRange(value[0], '0', '9') &&
IsAlphanum(value.substr(1), 3, 3);
}
bool IsUnicodeVariantSubtag(std::string_view value) {
// unicode_variant_subtag = (alphanum{5,8} | digit alphanum{3}) ;
return IsAlphanum(value, 5, 8) || IsDigitAlphanum3(value);
}
bool IsExtensionSingleton(std::string_view value) {
return IsAlphanum(value, 1, 1);
}
int32_t weekdayFromEDaysOfWeek(icu::Calendar::EDaysOfWeek eDaysOfWeek) {
return (eDaysOfWeek == icu::Calendar::SUNDAY) ? 7 : eDaysOfWeek - 1;
}
} // namespace
// Implemented as iteration instead of recursion to avoid stack overflow for
// very long input strings.
bool JSLocale::Is38AlphaNumList(std::string_view in) {
std::string_view value = in;
while (true) {
std::size_t found_dash = value.find('-');
if (found_dash == std::string::npos) {
return IsAlphanum(value, 3, 8);
}
if (!IsAlphanum(value.substr(0, found_dash), 3, 8)) return false;
value = value.substr(found_dash + 1);
}
}
bool JSLocale::Is3Alpha(std::string_view value) { return IsAlpha(value, 3, 3); }
// TODO(ftang) Replace the following check w/ icu::LocaleBuilder
// once ICU64 land in March 2019.
bool JSLocale::StartsWithUnicodeLanguageId(std::string_view value) {
// unicode_language_id =
// unicode_language_subtag (sep unicode_script_subtag)?
// (sep unicode_region_subtag)? (sep unicode_variant_subtag)* ;
if (value.empty()) return false;
std::vector<std::string_view> tokens;
size_t token_start = 0;
size_t token_end;
for (token_end = 0; token_end < value.size(); ++token_end) {
if (value[token_end] == '-') {
tokens.emplace_back(&value[token_start], token_end - token_start);
token_start = token_end + 1;
}
}
if (token_start != token_end) {
tokens.emplace_back(&value[token_start], token_end - token_start);
}
DCHECK(!tokens.empty());
// length >= 1
if (!IsUnicodeLanguageSubtag(tokens[0])) return false;
if (tokens.size() == 1) return true;
// length >= 2
if (IsExtensionSingleton(tokens[1])) return true;
size_t index = 1;
if (IsUnicodeScriptSubtag(tokens[index])) {
index++;
if (index == tokens.size()) return true;
}
if (IsUnicodeRegionSubtag(tokens[index])) {
index++;
}
while (index < tokens.size()) {
if (IsExtensionSingleton(tokens[index])) return true;
if (!IsUnicodeVariantSubtag(tokens[index])) return false;
index++;
}
return true;
}
namespace {
// Return false if variants contain duplicate elements or invalid delimiters.
bool IsValidVariants(std::string_view variants) {
// The length of one unicode_variant_subtag is between 4-8. To have
// duplicate or multiple subtags, the length of the variants need to be >=
// 4+1+4 = 9.
if (variants.length() >= 9) {
std::set<std::string> set;
size_t start = 0;
for (size_t i = 0; i <= variants.length(); ++i) {
if (i == variants.length() || variants[i] == '-') {
std::string subtag(variants.substr(start, i - start));
std::transform(
subtag.begin(), subtag.end(), subtag.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
if (!set.insert(subtag).second) {
return false;
}
start = i + 1;
} else if (variants[i] == '_') {
return false;
}
}
}
return true;
}
Maybe<bool> ApplyOptionsToTag(Isolate* isolate, DirectHandle<String> tag,
DirectHandle<JSReceiver> options,
icu::LocaleBuilder* builder) {
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
if (tag->length() == 0) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kLocaleNotEmpty));
}
v8::String::Utf8Value bcp47_tag(v8_isolate, v8::Utils::ToLocal(tag));
builder->setLanguageTag(
{*bcp47_tag, static_cast<int32_t>(bcp47_tag.length())});
DCHECK_LT(0, bcp47_tag.length());
DCHECK_NOT_NULL(*bcp47_tag);
// 2. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError
// exception.
if (!JSLocale::StartsWithUnicodeLanguageId(*bcp47_tag)) {
return Just(false);
}
UErrorCode status = U_ZERO_ERROR;
icu::Locale canonicalized = builder->build(status);
canonicalized.canonicalize(status);
if (U_FAILURE(status)) {
return Just(false);
}
builder->setLocale(canonicalized);
// 3. Let language be ? GetOption(options, "language", "string", undefined,
// undefined).
DirectHandle<String> language_str;
Maybe<bool> maybe_language =
GetStringOption(isolate, options, isolate->factory()->language_string(),
"ApplyOptionsToTag", &language_str);
MAYBE_RETURN(maybe_language, Nothing<bool>());
// 4. If language is not undefined, then
if (maybe_language.FromJust()) {
std::string language_stdstr = language_str->ToStdString();
builder->setLanguage(language_stdstr);
builder->build(status);
// a. If language does not match the unicode_language_subtag production,
// throw a RangeError exception.
if (U_FAILURE(status) || language_stdstr.empty() ||
IsAlpha(language_stdstr, 4, 4)) {
return Just(false);
}
}
// 5. Let script be ? GetOption(options, "script", "string", undefined,
// undefined).
DirectHandle<String> script_str;
Maybe<bool> maybe_script =
GetStringOption(isolate, options, isolate->factory()->script_string(),
"ApplyOptionsToTag", &script_str);
MAYBE_RETURN(maybe_script, Nothing<bool>());
// 6. If script is not undefined, then
if (maybe_script.FromJust()) {
std::string script_stdstr = script_str->ToStdString();
builder->setScript(script_stdstr);
builder->build(status);
// a. If script does not match the unicode_script_subtag production, throw
// a RangeError exception.
if (U_FAILURE(status) || script_stdstr.empty()) {
return Just(false);
}
}
// 7. Let region be ? GetOption(options, "region", "string", undefined,
// undefined).
DirectHandle<String> region_str;
Maybe<bool> maybe_region =
GetStringOption(isolate, options, isolate->factory()->region_string(),
"ApplyOptionsToTag", &region_str);
MAYBE_RETURN(maybe_region, Nothing<bool>());
// 8. If region is not undefined, then
if (maybe_region.FromJust()) {
std::string region_stdstr = region_str->ToStdString();
// a. If region does not match the region production, throw a RangeError
// exception.
builder->setRegion(region_stdstr);
builder->build(status);
if (U_FAILURE(status) || region_stdstr.empty()) {
return Just(false);
}
}
// 8. Let variants be ? GetOption(options, "variants", string, empty,
// GetLocaleVariants(baseName)).
DirectHandle<String> variants_str;
Maybe<bool> maybe_variants =
GetStringOption(isolate, options, isolate->factory()->variants_string(),
"ApplyOptionsToTag", &variants_str);
MAYBE_RETURN(maybe_variants, Nothing<bool>());
// 9. If variants is not undefined, then
if (maybe_variants.FromJust()) {
// a. If variants is the empty String, throw a RangeError exception.
// b. Let lowerVariants be the ASCII-lowercase of variants.
std::string variants_stdstr = variants_str->ToStdString();
// c. Let variantSubtags be StringSplitToList(lowerVariants, "-").
// d. For each element variant of variantSubtags, do
// i. If variant cannot be matched by the unicode_variant_subtag Unicode
// locale nonterminal, throw a RangeError exception.
builder->setVariant(variants_stdstr);
builder->build(status);
if (U_FAILURE(status) || variants_stdstr.empty()) {
return Just(false);
}
// e. If variantSubtags contains any duplicate elements, throw a
// RangeError exception.
if (!IsValidVariants(variants_stdstr)) {
return Just(false);
}
}
// 9. Set tag to CanonicalizeLanguageTag(tag).
// 10. If language is not undefined,
// a. Assert: tag matches the unicode_locale_id production.
// b. Set tag to tag with the substring corresponding to the
// unicode_language_subtag production replaced by the string language.
// 11. If script is not undefined, then
// a. If tag does not contain a unicode_script_subtag production, then
// i. Set tag to the concatenation of the unicode_language_subtag
// production of tag, "-", script, and the rest of tag.
// b. Else,
// i. Set tag to tag with the substring corresponding to the
// unicode_script_subtag production replaced by the string script.
// 12. If region is not undefined, then
// a. If tag does not contain a unicode_region_subtag production, then
// i. Set tag to the concatenation of the unicode_language_subtag
// production of tag, the substring corresponding to the "-"
// unicode_script_subtag production if present, "-", region, and
// the rest of tag.
// b. Else,
// i. Set tag to tag with the substring corresponding to the
// unicode_region_subtag production replaced by the string region.
// 13. Return CanonicalizeLanguageTag(tag).
return Just(true);
}
} // namespace
MaybeDirectHandle<JSLocale> JSLocale::New(Isolate* isolate,
DirectHandle<Map> map,
DirectHandle<String> locale_str,
DirectHandle<JSReceiver> options) {
icu::LocaleBuilder builder;
Maybe<bool> maybe_apply =
ApplyOptionsToTag(isolate, locale_str, options, &builder);
MAYBE_RETURN(maybe_apply, MaybeDirectHandle<JSLocale>());
if (!maybe_apply.FromJust()) {
THROW_NEW_ERROR(isolate,
NewRangeError(MessageTemplate::kLocaleBadParameters));
}
Maybe<bool> maybe_insert =
InsertOptionsIntoLocale(isolate, options, &builder);
MAYBE_RETURN(maybe_insert, MaybeDirectHandle<JSLocale>());
UErrorCode status = U_ZERO_ERROR;
icu::Locale icu_locale = builder.build(status);
icu_locale.canonicalize(status);
if (!maybe_insert.FromJust() || U_FAILURE(status)) {
THROW_NEW_ERROR(isolate,
NewRangeError(MessageTemplate::kLocaleBadParameters));
}
// 31. Set locale.[[Locale]] to r.[[locale]].
DirectHandle<Managed<icu::Locale>> managed_locale =
Managed<icu::Locale>::From(
isolate, 0, std::shared_ptr<icu::Locale>{icu_locale.clone()});
// Now all properties are ready, so we can allocate the result object.
DirectHandle<JSLocale> locale =
Cast<JSLocale>(isolate->factory()->NewFastOrSlowJSObjectFromMap(map));
DisallowGarbageCollection no_gc;
locale->set_icu_locale(*managed_locale);
return locale;
}
namespace {
MaybeDirectHandle<JSLocale> Construct(Isolate* isolate,
const icu::Locale& icu_locale) {
DirectHandle<Managed<icu::Locale>> managed_locale =
Managed<icu::Locale>::From(
isolate, 0, std::shared_ptr<icu::Locale>{icu_locale.clone()});
DirectHandle<JSFunction> constructor(
isolate->native_context()->intl_locale_function(), isolate);
DirectHandle<Map> map;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, map,
JSFunction::GetDerivedMap(isolate, constructor, constructor));
DirectHandle<JSLocale> locale =
Cast<JSLocale>(isolate->factory()->NewFastOrSlowJSObjectFromMap(map));
DisallowGarbageCollection no_gc;
locale->set_icu_locale(*managed_locale);
return locale;
}
} // namespace
MaybeDirectHandle<JSLocale> JSLocale::Maximize(Isolate* isolate,
DirectHandle<JSLocale> locale) {
// ICU has limitation on the length of the locale while addLikelySubtags
// is called. Work around the issue by only perform addLikelySubtags
// on the base locale and merge the extension if needed.
icu::Locale source(*(locale->icu_locale()->ptr()));
icu::Locale result = icu::Locale::createFromName(source.getBaseName());
UErrorCode status = U_ZERO_ERROR;
result.addLikelySubtags(status);
if (strlen(source.getBaseName()) != strlen(result.getBaseName())) {
// Base name is changed
if (strlen(source.getBaseName()) != strlen(source.getName())) {
// the source has extensions, get the extensions from the source.
result = icu::LocaleBuilder()
.setLocale(source)
.setLanguage(result.getLanguage())
.setRegion(result.getCountry())
.setScript(result.getScript())
.setVariant(result.getVariant())
.build(status);
}
} else {
// Base name is not changed
result = source;
}
if (U_FAILURE(status) || result.isBogus()) {
// Due to https://unicode-org.atlassian.net/browse/ICU-21639
// Valid but super long locale will fail. Just throw here for now.
THROW_NEW_ERROR(isolate,
NewRangeError(MessageTemplate::kLocaleBadParameters));
}
return Construct(isolate, result);
}
MaybeDirectHandle<JSLocale> JSLocale::Minimize(Isolate* isolate,
DirectHandle<JSLocale> locale) {
// ICU has limitation on the length of the locale while minimizeSubtags
// is called. Work around the issue by only perform addLikelySubtags
// on the base locale and merge the extension if needed.
icu::Locale source(*(locale->icu_locale()->ptr()));
icu::Locale result = icu::Locale::createFromName(source.getBaseName());
UErrorCode status = U_ZERO_ERROR;
result.minimizeSubtags(status);
if (strcmp(source.getBaseName(), result.getBaseName()) != 0) {
// Base name is changed
if (strlen(source.getBaseName()) != strlen(source.getName())) {
// the source has extensions, get the extensions from the source.
result = icu::LocaleBuilder()
.setLocale(source)
.setLanguage(result.getLanguage())
.setRegion(result.getCountry())
.setScript(result.getScript())
.setVariant(result.getVariant())
.build(status);
}
} else {
// Base name is not changed
result = source;
}
if (U_FAILURE(status) || result.isBogus()) {
// Due to https://unicode-org.atlassian.net/browse/ICU-21639
// Valid but super long locale will fail. Just throw here for now.
THROW_NEW_ERROR(isolate,
NewRangeError(MessageTemplate::kLocaleBadParameters));
}
return Construct(isolate, result);
}
template <typename T>
MaybeDirectHandle<JSArray> GetKeywordValuesFromLocale(
Isolate* isolate, const char* key, const char* unicode_key,
const icu::Locale& locale, bool (*removes)(const char*), bool commonly_used,
bool sort) {
Factory* factory = isolate->factory();
UErrorCode status = U_ZERO_ERROR;
std::string ext =
locale.getUnicodeKeywordValue<std::string>(unicode_key, status);
if (!ext.empty()) {
DirectHandle<FixedArray> fixed_array = factory->NewFixedArray(1);
DirectHandle<String> str = factory->NewStringFromAsciiChecked(ext.c_str());
fixed_array->set(0, *str);
return factory->NewJSArrayWithElements(fixed_array);
}
status = U_ZERO_ERROR;
std::unique_ptr<icu::StringEnumeration> enumeration(
T::getKeywordValuesForLocale(key, locale, commonly_used, status));
if (U_FAILURE(status)) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError));
}
return Intl::ToJSArray(
isolate,
[unicode_key](const char* value) {
return std::string(uloc_toUnicodeLocaleType(unicode_key, value));
},
enumeration.get(), removes, sort);
}
namespace {
// https://tc39.es/ecma402/#sec-canonicalunicodesubdivision
std::string CanonicalUnicodeSubdivision(const icu::Locale& icu_locale,
const char* key) {
UErrorCode status = U_ZERO_ERROR;
// 1. Let subdivision be UnicodeExtensionValue(locale, key).
std::string subdivision =
icu_locale.getUnicodeKeywordValue<std::string>(key, status);
// 2. If subdivision is ~empty~, return undefined.
if (U_FAILURE(status) || subdivision.empty()) return "";
// 3. If subdivision cannot be matched by the unicode_subdivision_id Unicode
// locale nonterminal, return undefined.
// unicode_subdivision_id = unicode_region_subtag unicode_subdivision_suffix
// unicode_region_subtag = (alpha{2} | digit{3})
// unicode_subdivision_suffix = alphanum{1,4}
//
// 4. Let region be the longest prefix of subdivision matched by the
// unicode_region_subtag Unicode locale nonterminal.
// Real examples: "gbeng" -> region "GB", "usca" -> region "US", "001abc" ->
// region "001".
std::string_view sub_view(subdivision);
std::string_view region;
if (sub_view.length() >= 4 && IsDigit(sub_view.substr(0, 3), 3, 3)) {
if (!IsAlphanum(sub_view.substr(3), 1, 4)) return "";
region = sub_view.substr(0, 3);
} else {
if (sub_view.length() < 3 || !IsAlpha(sub_view.substr(0, 2), 2, 2) ||
!IsAlphanum(sub_view.substr(2), 1, 4)) {
return "";
}
region = sub_view.substr(0, 2);
}
// 5. Let regionLocale be the string-concatenation of "und-" and region.
// 6. Set regionLocale to CanonicalizeUnicodeLocaleId(regionLocale).
// 7. Return GetLocaleRegion(regionLocale).
std::string result(region);
std::transform(result.begin(), result.end(), result.begin(),
[](unsigned char c) { return std::toupper(c); });
return result;
}
struct RegionPreferenceRecord {
std::string region;
std::string region_override;
};
// https://tc39.es/ecma402/#sec-regionpreference
RegionPreferenceRecord RegionPreference(const icu::Locale& icu_locale) {
// 1. Let region be GetLocaleRegion(locale).
const char* country = icu_locale.getCountry();
std::string region = (country != nullptr) ? country : "";
// 2. If region is undefined, then
if (region.empty()) {
// a. Set region to CanonicalUnicodeSubdivision(locale, "sd").
region = CanonicalUnicodeSubdivision(icu_locale, "sd");
// b. If region is undefined, then
if (region.empty()) {
// i. Let maximal be the result of the Add Likely Subtags algorithm
// applied to locale. If an error is signaled, set maximal to locale.
// ii. Set maximal to CanonicalizeUnicodeLocaleId(maximal).
UErrorCode status = U_ZERO_ERROR;
icu::Locale maximal = icu_locale;
maximal.addLikelySubtags(status);
if (U_FAILURE(status)) maximal = icu_locale;
// iii. Set region to GetLocaleRegion(maximal).
const char* max_country = maximal.getCountry();
region = (max_country != nullptr) ? max_country : "";
// iv. If region is undefined, then
if (region.empty()) {
// 1. Set region to "001".
region = "001";
}
}
}
// 3. Let regionOverride be CanonicalUnicodeSubdivision(locale, "rg").
std::string region_override = CanonicalUnicodeSubdivision(icu_locale, "rg");
// 4. Return { [[Region]]: region, [[RegionOverride]]: regionOverride }.
return {region, region_override};
}
// Applies RegionPreference(icu_locale) to construct an icu::Locale with the
// preferred lookup region. Implements region resolution for:
// - (CalendarsOfLocale) 2-3. Let preference be
// RegionPreference(loc.[[Locale]]).
// If preference.[[RegionOverride]] is not undefined, let preferredRegions be
// « preference.[[RegionOverride]], preference.[[Region]] »; else ...
// - (HourCyclesOfLocale) 2-3. Let preference be
// RegionPreference(loc.[[Locale]]).
// If preference.[[RegionOverride]] is not undefined, let preferredRegions be
// « preference.[[RegionOverride]], preference.[[Region]] »; else ...
// - (WeekInfoOfLocale) 1-5. Let preference be RegionPreference(loc.[[Locale]]).
// Select lookupRegion based on regionOverride or region.
icu::Locale ApplyRegionPreference(const icu::Locale& icu_locale) {
RegionPreferenceRecord pref = RegionPreference(icu_locale);
std::string lookup_region =
!pref.region_override.empty() ? pref.region_override : pref.region;
UErrorCode status = U_ZERO_ERROR;
icu::Locale res = icu::LocaleBuilder()
.setLocale(icu_locale)
.setRegion(lookup_region)
.build(status);
return U_SUCCESS(status) ? res : icu_locale;
}
MaybeDirectHandle<JSArray> CalendarsForLocale(Isolate* isolate,
const icu::Locale& icu_locale,
bool commonly_used, bool sort) {
return GetKeywordValuesFromLocale<icu::Calendar>(
isolate, "calendar", "ca", icu_locale, nullptr, commonly_used, sort);
}
} // namespace
MaybeDirectHandle<JSArray> JSLocale::GetCalendars(
Isolate* isolate, DirectHandle<JSLocale> locale) {
icu::Locale icu_locale(*(locale->icu_locale()->ptr()));
return CalendarsForLocale(isolate, ApplyRegionPreference(icu_locale), true,
false);
}
MaybeDirectHandle<JSArray> Intl::AvailableCalendars(Isolate* isolate) {
icu::Locale icu_locale("und");
return CalendarsForLocale(isolate, icu_locale, false, true);
}
MaybeDirectHandle<JSArray> JSLocale::GetCollations(
Isolate* isolate, DirectHandle<JSLocale> locale) {
icu::Locale icu_locale(*(locale->icu_locale()->ptr()));
return GetKeywordValuesFromLocale<icu::Collator>(
isolate, "collations", "co", icu_locale, Intl::RemoveCollation, true,
true);
}
MaybeDirectHandle<JSArray> JSLocale::GetHourCycles(
Isolate* isolate, DirectHandle<JSLocale> locale) {
// Let preferred be loc.[[HourCycle]].
// Let locale be loc.[[Locale]].
icu::Locale icu_locale(*(locale->icu_locale()->ptr()));
Factory* factory = isolate->factory();
// Assert: locale matches the unicode_locale_id production.
// Let list be a List of 1 or more hour cycle identifiers, which must be
// String values indicating either the 12-hour format ("h11", "h12") or the
// 24-hour format ("h23", "h24"), sorted in descending preference of those in
// common use in the locale for date and time formatting.
// Return CreateArrayFromListAndPreferred( list, preferred ).
DirectHandle<FixedArray> fixed_array = factory->NewFixedArray(1);
UErrorCode status = U_ZERO_ERROR;
std::string ext =
icu_locale.getUnicodeKeywordValue<std::string>("hc", status);
if (!ext.empty()) {
DirectHandle<String> str = factory->NewStringFromAsciiChecked(ext.c_str());
fixed_array->set(0, *str);
return factory->NewJSArrayWithElements(fixed_array);
}
status = U_ZERO_ERROR;
std::unique_ptr<icu::DateTimePatternGenerator> generator(
icu::DateTimePatternGenerator::createInstance(
ApplyRegionPreference(icu_locale), status));
if (U_FAILURE(status)) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError));
}
UDateFormatHourCycle hc = generator->getDefaultHourCycle(status);
if (U_FAILURE(status)) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError));
}
DirectHandle<String> hour_cycle;
switch (hc) {
case UDAT_HOUR_CYCLE_11:
hour_cycle = factory->h11_string();
break;
case UDAT_HOUR_CYCLE_12:
hour_cycle = factory->h12_string();
break;
case UDAT_HOUR_CYCLE_23:
hour_cycle = factory->h23_string();
break;
case UDAT_HOUR_CYCLE_24:
hour_cycle = factory->h24_string();
break;
default:
break;
}
fixed_array->set(0, *hour_cycle);
return factory->NewJSArrayWithElements(fixed_array);
}
MaybeDirectHandle<JSArray> JSLocale::GetNumberingSystems(
Isolate* isolate, DirectHandle<JSLocale> locale) {
// Let preferred be loc.[[NumberingSystem]].
// Let locale be loc.[[Locale]].
icu::Locale icu_locale(*(locale->icu_locale()->ptr()));
Factory* factory = isolate->factory();
// Assert: locale matches the unicode_locale_id production.
// Let list be a List of 1 or more numbering system identifiers, which must be
// String values conforming to the type sequence from UTS 35 Unicode Locale
// Identifier, section 3.2, sorted in descending preference of those in common
// use in the locale for formatting numeric values.
// Return CreateArrayFromListAndPreferred( list, preferred ).
UErrorCode status = U_ZERO_ERROR;
DirectHandle<FixedArray> fixed_array = factory->NewFixedArray(1);
std::string numbering_system =
icu_locale.getUnicodeKeywordValue<std::string>("nu", status);
if (numbering_system.empty()) {
numbering_system = Intl::GetNumberingSystem(icu_locale);
}
DirectHandle<String> str =
factory->NewStringFromAsciiChecked(numbering_system.c_str());
fixed_array->set(0, *str);
return factory->NewJSArrayWithElements(fixed_array);
}
MaybeDirectHandle<Object> JSLocale::GetTimeZones(
Isolate* isolate, DirectHandle<JSLocale> locale) {
// Let loc be the this value.
// Perform ? RequireInternalSlot(loc, [[InitializedLocale]])
// Let locale be loc.[[Locale]].
icu::Locale icu_locale(*(locale->icu_locale()->ptr()));
Factory* factory = isolate->factory();
// If the unicode_language_id production of locale does not contain the
// ["-" unicode_region_subtag] sequence, return undefined.
const char* region = icu_locale.getCountry();
if (region == nullptr || strlen(region) == 0) {
return factory->undefined_value();
}
// Return TimeZonesOfLocale(loc).
// Let locale be loc.[[Locale]].
// Assert: locale matches the unicode_locale_id production.
// Let region be the substring of locale corresponding to the
// unicode_region_subtag production of the unicode_language_id.
// Let list be a List of 1 or more time zone identifiers, which must be String
// values indicating a Zone or Link name of the IANA Time Zone Database,
// sorted in descending preference of those in common use in region.
UErrorCode status = U_ZERO_ERROR;
std::unique_ptr<icu::StringEnumeration> enumeration(
icu::TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL,
region, nullptr, status));
if (U_FAILURE(status)) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError));
}
return Intl::ToJSArray(isolate, nullptr, enumeration.get(), nullptr, true);
}
MaybeDirectHandle<JSObject> JSLocale::GetTextInfo(
Isolate* isolate, DirectHandle<JSLocale> locale) {
// Let loc be the this value.
// Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
// Let locale be loc.[[Locale]].
// Assert: locale matches the unicode_locale_id production.
Factory* factory = isolate->factory();
// Let info be ! ObjectCreate(%Object.prototype%).
DirectHandle<JSObject> info =
factory->NewJSObject(isolate->object_function());
// Let dir be "ltr".
DirectHandle<String> dir = locale->icu_locale()->ptr()->isRightToLeft()
? factory->rtl_string()
: factory->ltr_string();
// Perform ! CreateDataPropertyOrThrow(info, "direction", dir).
CHECK(JSReceiver::CreateDataProperty(
isolate, info, factory->direction_string(), dir, Just(kDontThrow))
.FromJust());
// Return info.
return info;
}
MaybeDirectHandle<JSObject> JSLocale::GetWeekInfo(
Isolate* isolate, DirectHandle<JSLocale> locale) {
// Let loc be the this value.
// Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
// Let locale be loc.[[Locale]].
// Assert: locale matches the unicode_locale_id production.
Factory* factory = isolate->factory();
// Let info be ! ObjectCreate(%Object.prototype%).
DirectHandle<JSObject> info =
factory->NewJSObject(isolate->object_function());
UErrorCode status = U_ZERO_ERROR;
std::unique_ptr<icu::Calendar> calendar(icu::Calendar::createInstance(
ApplyRegionPreference(*(locale->icu_locale()->ptr())), status));
if (U_FAILURE(status)) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError));
}
// Let fd be the weekday value indicating which day of the week is considered
// the 'first' day, for calendar purposes, in the locale.
int32_t fd = weekdayFromEDaysOfWeek(calendar->getFirstDayOfWeek());
// Let wi be ! WeekInfoOfLocale(loc).
// Let we be ! CreateArrayFromList( wi.[[Weekend]] ).
Handle<FixedArray> wi = Cast<FixedArray>(factory->NewFixedArray(2));
int32_t length = 0;
for (int32_t i = 1; i <= 7; i++) {
UCalendarDaysOfWeek day =
(i == 7) ? UCAL_SUNDAY : static_cast<UCalendarDaysOfWeek>(i + 1);
if (UCAL_WEEKDAY != calendar->getDayOfWeekType(day, status)) {
wi->set(length++, Smi::FromInt(i));
CHECK_LE(length, 2);
}
}
if (length != 2) {
wi = wi->RightTrimOrEmpty(isolate, wi, length);
}
DirectHandle<JSArray> we = factory->NewJSArrayWithElements(wi);
if (U_FAILURE(status)) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError));
}
// Perform ! CreateDataPropertyOrThrow(info, "firstDay", fd).
CHECK(JSReceiver::CreateDataProperty(
isolate, info, factory->firstDay_string(),
factory->NewNumberFromInt(fd), Just(kDontThrow))
.FromJust());
// Perform ! CreateDataPropertyOrThrow(info, "weekend", we).
CHECK(JSReceiver::CreateDataProperty(isolate, info, factory->weekend_string(),
we, Just(kDontThrow))
.FromJust());
// Return info.
return info;
}
DirectHandle<Object> JSLocale::Language(Isolate* isolate,
DirectHandle<JSLocale> locale) {
Factory* factory = isolate->factory();
Managed<icu::Locale>::Ptr icu_locale = locale->icu_locale()->ptr();
const char* language = icu_locale->getLanguage();
constexpr const char kUnd[] = "und";
if (strlen(language) == 0) {
language = kUnd;
}
return factory->NewStringFromAsciiChecked(language);
}
DirectHandle<Object> JSLocale::Script(Isolate* isolate,
DirectHandle<JSLocale> locale) {
Factory* factory = isolate->factory();
Managed<icu::Locale>::Ptr icu_locale = locale->icu_locale()->ptr();
const char* script = icu_locale->getScript();
if (strlen(script) == 0) return factory->undefined_value();
return factory->NewStringFromAsciiChecked(script);
}
DirectHandle<Object> JSLocale::Variants(Isolate* isolate,
DirectHandle<JSLocale> locale) {
Factory* factory = isolate->factory();
std::string variants = locale->icu_locale()->ptr()->getVariant();
if (variants.length() == 0) return factory->undefined_value();
// icu::Locale::getVariants() return the variants in upper case characters
// with '_', we need to convert it to lower case and '-' before return.
std::transform(variants.begin(), variants.end(), variants.begin(),
[](unsigned char c) {
if (c == '_') return '-';
return static_cast<char>(std::tolower(c));
});
return factory->NewStringFromAsciiChecked(variants.c_str());
}
DirectHandle<Object> JSLocale::Region(Isolate* isolate,
DirectHandle<JSLocale> locale) {
Factory* factory = isolate->factory();
Managed<icu::Locale>::Ptr icu_locale = locale->icu_locale()->ptr();
const char* region = icu_locale->getCountry();
if (strlen(region) == 0) return factory->undefined_value();
return factory->NewStringFromAsciiChecked(region);
}
DirectHandle<String> JSLocale::BaseName(Isolate* isolate,
DirectHandle<JSLocale> locale) {
icu::Locale icu_locale =
icu::Locale::createFromName(locale->icu_locale()->ptr()->getBaseName());
std::string base_name = Intl::ToLanguageTag(icu_locale).FromJust();
return isolate->factory()->NewStringFromAsciiChecked(base_name.c_str());
}
DirectHandle<Object> JSLocale::Calendar(Isolate* isolate,
DirectHandle<JSLocale> locale) {
return UnicodeKeywordValue(isolate, locale, "ca");
}
DirectHandle<Object> JSLocale::CaseFirst(Isolate* isolate,
DirectHandle<JSLocale> locale) {
return UnicodeKeywordValue(isolate, locale, "kf");
}
DirectHandle<Object> JSLocale::Collation(Isolate* isolate,
DirectHandle<JSLocale> locale) {
return UnicodeKeywordValue(isolate, locale, "co");
}
DirectHandle<Object> JSLocale::FirstDayOfWeek(Isolate* isolate,
DirectHandle<JSLocale> locale) {
return UnicodeKeywordValue(isolate, locale, "fw");
}
DirectHandle<Object> JSLocale::HourCycle(Isolate* isolate,
DirectHandle<JSLocale> locale) {
return UnicodeKeywordValue(isolate, locale, "hc");
}
DirectHandle<Object> JSLocale::Numeric(Isolate* isolate,
DirectHandle<JSLocale> locale) {
Factory* factory = isolate->factory();
Managed<icu::Locale>::Ptr icu_locale = locale->icu_locale()->ptr();
UErrorCode status = U_ZERO_ERROR;
std::string numeric =
icu_locale->getUnicodeKeywordValue<std::string>("kn", status);
return factory->ToBoolean(numeric == "true");
}
DirectHandle<Object> JSLocale::NumberingSystem(Isolate* isolate,
DirectHandle<JSLocale> locale) {
return UnicodeKeywordValue(isolate, locale, "nu");
}
std::string JSLocale::ToString(DirectHandle<JSLocale> locale) {
Managed<icu::Locale>::Ptr icu_locale = locale->icu_locale()->ptr();
return Intl::ToLanguageTag(*icu_locale).FromJust();
}
DirectHandle<String> JSLocale::ToString(Isolate* isolate,
DirectHandle<JSLocale> locale) {
std::string locale_str = JSLocale::ToString(locale);
return isolate->factory()->NewStringFromAsciiChecked(locale_str.c_str());
}
} // namespace internal
} // namespace v8