Commit 339f08d2 authored by jshin's avatar jshin Committed by Commit bot

Support language tag extensions with multiple subtags for a key

Language tags with Unicode extensions can have multiple subtags
for a key (e.g. -ca-ismalic-civil has 'islamic-civi' for 'ca').

BUG=v8:4749
TEST=intl/date-format/calendar-with-multiple-type-subtags.js

Review-Url: https://codereview.chromium.org/2248563003
Cr-Commit-Position: refs/heads/master@{#38692}
parent 95e0ba65
...@@ -157,6 +157,9 @@ void SetResolvedDateSettings(Isolate* isolate, ...@@ -157,6 +157,9 @@ void SetResolvedDateSettings(Isolate* isolate,
// Set time zone and calendar. // Set time zone and calendar.
const icu::Calendar* calendar = date_format->getCalendar(); const icu::Calendar* calendar = date_format->getCalendar();
// getType() returns legacy calendar type name instead of LDML/BCP47 calendar
// key values. i18n.js maps them to BCP47 values for key "ca".
// TODO(jshin): Consider doing it here, instead.
const char* calendar_name = calendar->getType(); const char* calendar_name = calendar->getType();
JSObject::SetProperty(resolved, factory->NewStringFromStaticChars("calendar"), JSObject::SetProperty(resolved, factory->NewStringFromStaticChars("calendar"),
factory->NewStringFromAsciiChecked(calendar_name), factory->NewStringFromAsciiChecked(calendar_name),
......
...@@ -384,6 +384,9 @@ function getGetOption(options, caller) { ...@@ -384,6 +384,9 @@ function getGetOption(options, caller) {
/** /**
* Ecma 402 9.2.5
* TODO(jshin): relevantExtensionKeys and localeData need to be taken into
* account per spec.
* Compares a BCP 47 language priority list requestedLocales against the locales * Compares a BCP 47 language priority list requestedLocales against the locales
* in availableLocales and determines the best available language to meet the * in availableLocales and determines the best available language to meet the
* request. Two algorithms are available to match the locales: the Lookup * request. Two algorithms are available to match the locales: the Lookup
...@@ -467,6 +470,11 @@ function bestFitMatcher(service, requestedLocales) { ...@@ -467,6 +470,11 @@ function bestFitMatcher(service, requestedLocales) {
* Parses Unicode extension into key - value map. * Parses Unicode extension into key - value map.
* Returns empty object if the extension string is invalid. * Returns empty object if the extension string is invalid.
* We are not concerned with the validity of the values at this point. * We are not concerned with the validity of the values at this point.
* 'attribute' in RFC 6047 is not supported. Keys without explicit
* values are assigned UNDEFINED.
* TODO(jshin): Fix the handling of 'attribute' (in RFC 6047, but none
* has been defined so that it's not used) and boolean keys without
* an explicit value.
*/ */
function parseExtension(extension) { function parseExtension(extension) {
var extensionSplit = %StringSplit(extension, '-', kMaxUint32); var extensionSplit = %StringSplit(extension, '-', kMaxUint32);
...@@ -480,21 +488,33 @@ function parseExtension(extension) { ...@@ -480,21 +488,33 @@ function parseExtension(extension) {
// Key is {2}alphanum, value is {3,8}alphanum. // Key is {2}alphanum, value is {3,8}alphanum.
// Some keys may not have explicit values (booleans). // Some keys may not have explicit values (booleans).
var extensionMap = {}; var extensionMap = {};
var previousKey = UNDEFINED; var key = UNDEFINED;
var value = UNDEFINED;
for (var i = 2; i < extensionSplit.length; ++i) { for (var i = 2; i < extensionSplit.length; ++i) {
var length = extensionSplit[i].length; var length = extensionSplit[i].length;
var element = extensionSplit[i]; var element = extensionSplit[i];
if (length === 2) { if (length === 2) {
extensionMap[element] = UNDEFINED; if (!IS_UNDEFINED(key)) {
previousKey = element; if (!(key in extensionMap)) {
} else if (length >= 3 && length <=8 && !IS_UNDEFINED(previousKey)) { extensionMap[key] = value;
extensionMap[previousKey] = element; }
previousKey = UNDEFINED; value = UNDEFINED;
}
key = element;
} else if (length >= 3 && length <= 8 && !IS_UNDEFINED(key)) {
if (IS_UNDEFINED(value)) {
value = element;
} else {
value = value + "-" + element;
}
} else { } else {
// There is a value that's too long, or that doesn't have a key. // There is a value that's too long, or that doesn't have a key.
return {}; return {};
} }
} }
if (!IS_UNDEFINED(key) && !(key in extensionMap)) {
extensionMap[key] = value;
}
return extensionMap; return extensionMap;
} }
...@@ -917,6 +937,9 @@ function initializeCollator(collator, locales, options) { ...@@ -917,6 +937,9 @@ function initializeCollator(collator, locales, options) {
var locale = resolveLocale('collator', locales, options); var locale = resolveLocale('collator', locales, options);
// TODO(jshin): ICU now can take kb, kc, etc. Switch over to using ICU
// directly. See Collator::InitializeCollator and
// Collator::CreateICUCollator in src/i18n.cc
// ICU can't take kb, kc... parameters through localeID, so we need to pass // ICU can't take kb, kc... parameters through localeID, so we need to pass
// them as options. // them as options.
// One exception is -co- which has to be part of the extension, but only for // One exception is -co- which has to be part of the extension, but only for
...@@ -1664,21 +1687,13 @@ InstallFunction(Intl.DateTimeFormat.prototype, 'resolvedOptions', function() { ...@@ -1664,21 +1687,13 @@ InstallFunction(Intl.DateTimeFormat.prototype, 'resolvedOptions', function() {
} }
/** /**
* Maps ICU calendar names into LDML type. * Maps ICU calendar names to LDML/BCP47 types for key 'ca'.
* See typeMap section in third_party/icu/source/data/misc/keyTypeData.txt
* and
* http://www.unicode.org/repos/cldr/tags/latest/common/bcp47/calendar.xml
*/ */
var ICU_CALENDAR_MAP = { var ICU_CALENDAR_MAP = {
'gregorian': 'gregory', 'gregorian': 'gregory',
'japanese': 'japanese',
'buddhist': 'buddhist',
'roc': 'roc',
'persian': 'persian',
'islamic-civil': 'islamicc',
'islamic': 'islamic',
'hebrew': 'hebrew',
'chinese': 'chinese',
'indian': 'indian',
'coptic': 'coptic',
'ethiopic': 'ethiopic',
'ethiopic-amete-alem': 'ethioaa' 'ethiopic-amete-alem': 'ethioaa'
}; };
...@@ -1686,8 +1701,7 @@ InstallFunction(Intl.DateTimeFormat.prototype, 'resolvedOptions', function() { ...@@ -1686,8 +1701,7 @@ InstallFunction(Intl.DateTimeFormat.prototype, 'resolvedOptions', function() {
var fromPattern = fromLDMLString(format[resolvedSymbol][patternSymbol]); var fromPattern = fromLDMLString(format[resolvedSymbol][patternSymbol]);
var userCalendar = ICU_CALENDAR_MAP[format[resolvedSymbol].calendar]; var userCalendar = ICU_CALENDAR_MAP[format[resolvedSymbol].calendar];
if (IS_UNDEFINED(userCalendar)) { if (IS_UNDEFINED(userCalendar)) {
// Use ICU name if we don't have a match. It shouldn't happen, but // No match means that ICU's legacy name is identical to LDML/BCP type.
// it would be too strict to throw for this.
userCalendar = format[resolvedSymbol].calendar; userCalendar = format[resolvedSymbol].calendar;
} }
......
// Copyright 2016 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.
var options = Intl.DateTimeFormat("ar-u-ca-islamic-civil").resolvedOptions();
assertEquals(options.calendar, "islamic-civil");
options = Intl.DateTimeFormat("ar-u-ca-islamic-umalqura").resolvedOptions();
assertEquals(options.calendar, "islamic-umalqura");
var options = Intl.DateTimeFormat("ar-u-ca-islamic-civil").resolvedOptions();
assertEquals(options.calendar, "islamic-civil");
options =
Intl.DateTimeFormat("ar-u-ca-islamic-civil-nu-arab").resolvedOptions();
assertEquals(options.calendar, "islamic-civil");
assertEquals(options.numberingSystem, "arab");
// The default numberingSystem is 'arab' for 'ar' locale. Set it to 'latn'
// to check that 'nu-latn' keyword is parsed correctly.
options =
Intl.DateTimeFormat("ar-u-ca-islamic-civil-nu-latn").resolvedOptions();
assertEquals(options.calendar, "islamic-civil");
assertEquals(options.numberingSystem, "latn");
// ethioaa is the canonical LDML/BCP 47 name.
options = Intl.DateTimeFormat("am-u-ca-ethiopic-amete-alem").resolvedOptions();
assertEquals(options.calendar, "ethioaa");
// Invalid calendar type "foo-bar". Fall back to the default.
options = Intl.DateTimeFormat("ar-u-ca-foo-bar").resolvedOptions();
assertEquals(options.calendar, "gregory");
// No type subtag for ca. Fall back to the default.
options = Intl.DateTimeFormat("ar-u-ca-nu-arab").resolvedOptions();
assertEquals(options.calendar, "gregory");
// Too long a type subtag for ca.
assertThrows(() => Intl.DateTimeFormat("ar-u-ca-foobarbaz"), RangeError);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment