summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwolfbeast <mcwerewolf@gmail.com>2017-08-13 14:44:53 +0200
committerwolfbeast <mcwerewolf@gmail.com>2018-02-07 10:33:18 +0100
commit7686bceecff17f91758c8c6547a78e71ff3a8c38 (patch)
tree96a6f5a35c3033fd7e2729c0c289c5772168d668
parent646d68be64b1c5ec8e12aff0d22a76433fcc5703 (diff)
downloadUXP-7686bceecff17f91758c8c6547a78e71ff3a8c38.tar
UXP-7686bceecff17f91758c8c6547a78e71ff3a8c38.tar.gz
UXP-7686bceecff17f91758c8c6547a78e71ff3a8c38.tar.lz
UXP-7686bceecff17f91758c8c6547a78e71ff3a8c38.tar.xz
UXP-7686bceecff17f91758c8c6547a78e71ff3a8c38.zip
Add pluralrules to JS Intl
-rw-r--r--js/src/builtin/Intl.js626
-rw-r--r--js/src/tests/Intl/PluralRules/resolvedOptions-overridden-species.js27
-rw-r--r--js/src/tests/Intl/getCanonicalLocales-overridden-species.js23
3 files changed, 542 insertions, 134 deletions
diff --git a/js/src/builtin/Intl.js b/js/src/builtin/Intl.js
index 493062c1c..37c87365b 100644
--- a/js/src/builtin/Intl.js
+++ b/js/src/builtin/Intl.js
@@ -9,7 +9,8 @@
JSMSG_INVALID_OPTION_VALUE: false, JSMSG_INVALID_DIGITS_VALUE: false,
JSMSG_INTL_OBJECT_REINITED: false, JSMSG_INVALID_CURRENCY_CODE: false,
JSMSG_UNDEFINED_CURRENCY: false, JSMSG_INVALID_TIME_ZONE: false,
- JSMSG_DATE_NOT_FINITE: false,
+ JSMSG_DATE_NOT_FINITE: false, JSMSG_INVALID_KEYS_TYPE: false,
+ JSMSG_INVALID_KEY: false,
intl_Collator_availableLocales: false,
intl_availableCollations: false,
intl_CompareStrings: false,
@@ -20,6 +21,9 @@
intl_availableCalendars: false,
intl_patternForSkeleton: false,
intl_FormatDateTime: false,
+ intl_SelectPluralRule: false,
+ intl_GetPluralCategories: false,
+ intl_GetCalendarInfo: false,
*/
/*
@@ -432,7 +436,7 @@ function CanonicalizeLanguageTag(locale) {
subtags[i] = subtag;
i++;
}
- var normal = callFunction(std_Array_join, callFunction(std_Array_slice, subtags, 0, i), "-");
+ var normal = ArrayJoinRange(subtags, "-", 0, i);
// Extension sequences are sorted by their singleton characters.
// "u-ca-chinese-t-zh-latn" -> "t-zh-latn-u-ca-chinese"
@@ -442,7 +446,7 @@ function CanonicalizeLanguageTag(locale) {
i++;
while (i < subtags.length && subtags[i].length > 1)
i++;
- var extension = callFunction(std_Array_join, callFunction(std_Array_slice, subtags, extensionStart, i), "-");
+ var extension = ArrayJoinRange(subtags, "-", extensionStart, i);
callFunction(std_Array_push, extensions, extension);
}
callFunction(std_Array_sort, extensions);
@@ -450,7 +454,7 @@ function CanonicalizeLanguageTag(locale) {
// Private use sequences are left as is. "x-private"
var privateUse = "";
if (i < subtags.length)
- privateUse = callFunction(std_Array_join, callFunction(std_Array_slice, subtags, i), "-");
+ privateUse = ArrayJoinRange(subtags, "-", i);
// Put everything back together.
var canonical = normal;
@@ -467,6 +471,24 @@ function CanonicalizeLanguageTag(locale) {
return canonical;
}
+/**
+ * Joins the array elements in the given range with the supplied separator.
+ */
+function ArrayJoinRange(array, separator, from, to = array.length) {
+ assert(typeof separator === "string", "|separator| is a string value");
+ assert(typeof from === "number", "|from| is a number value");
+ assert(typeof to === "number", "|to| is a number value");
+ assert(0 <= from && from <= to && to <= array.length, "|from| and |to| form a valid range");
+
+ if (from === to)
+ return "";
+
+ var result = array[from];
+ for (var i = from + 1; i < to; i++) {
+ result += separator + array[i];
+ }
+ return result;
+}
function localeContainsNoUnicodeExtensions(locale) {
// No "-u-", no possible Unicode extension.
@@ -838,6 +860,7 @@ function BestAvailableLocaleIgnoringDefault(availableLocales, locale) {
return BestAvailableLocaleHelper(availableLocales, locale, false);
}
+var noRelevantExtensionKeys = [];
/**
* Compares a BCP 47 language priority list against the set of locales in
@@ -1165,9 +1188,10 @@ function GetOption(options, property, type, values, fallback) {
* Spec: ECMAScript Internationalization API Specification, 9.2.10.
*/
function GetNumberOption(options, property, minimum, maximum, fallback) {
- assert(typeof minimum === "number", "GetNumberOption");
- assert(typeof maximum === "number", "GetNumberOption");
- assert(fallback === undefined || (fallback >= minimum && fallback <= maximum), "GetNumberOption");
+ assert(typeof minimum === "number" && (minimum | 0) === minimum, "GetNumberOption");
+ assert(typeof maximum === "number" && (maximum | 0) === maximum, "GetNumberOption");
+ assert(typeof fallback === "number" && (fallback | 0) === fallback, "GetNumberOption");
+ assert(minimum <= fallback && fallback <= maximum, "GetNumberOption");
// Step 1.
var value = options[property];
@@ -1177,7 +1201,10 @@ function GetNumberOption(options, property, minimum, maximum, fallback) {
value = ToNumber(value);
if (Number_isNaN(value) || value < minimum || value > maximum)
ThrowRangeError(JSMSG_INVALID_DIGITS_VALUE, value);
- return std_Math_floor(value);
+
+ // Apply bitwise-or to convert -0 to +0 per ES2017, 5.2 and to ensure
+ // the result is an int32 value.
+ return std_Math_floor(value) | 0;
}
// Step 3.
@@ -1251,7 +1278,9 @@ function initializeIntlObject(obj) {
function setLazyData(internals, type, lazyData)
{
assert(internals.type === "partial", "can't set lazy data for anything but a newborn");
- assert(type === "Collator" || type === "DateTimeFormat" || type == "NumberFormat", "bad type");
+ assert(type === "Collator" || type === "DateTimeFormat" ||
+ type == "NumberFormat" || type === "PluralRules",
+ "bad type");
assert(IsObject(lazyData), "non-object lazy data");
// Set in reverse order so that the .type change is a barrier.
@@ -1301,7 +1330,9 @@ function isInitializedIntlObject(obj) {
if (IsObject(internals)) {
assert(callFunction(std_Object_hasOwnProperty, internals, "type"), "missing type");
var type = internals.type;
- assert(type === "partial" || type === "Collator" || type === "DateTimeFormat" || type === "NumberFormat", "unexpected type");
+ assert(type === "partial" || type === "Collator" ||
+ type === "DateTimeFormat" || type === "NumberFormat" || type === "PluralRules",
+ "unexpected type");
assert(callFunction(std_Object_hasOwnProperty, internals, "lazyData"), "missing lazyData");
assert(callFunction(std_Object_hasOwnProperty, internals, "internalProps"), "missing internalProps");
} else {
@@ -1358,6 +1389,8 @@ function getInternals(obj)
internalProps = resolveCollatorInternals(lazyData)
else if (type === "DateTimeFormat")
internalProps = resolveDateTimeFormatInternals(lazyData)
+ else if (type === "PluralRules")
+ internalProps = resolvePluralRulesInternals(lazyData)
else
internalProps = resolveNumberFormatInternals(lazyData);
setInternalProperties(internals, internalProps);
@@ -1689,6 +1722,7 @@ function Intl_Collator_compare_get() {
// Step 2.
return internals.boundCompare;
}
+_SetCanonicalName(Intl_Collator_compare_get, "get compare");
/**
@@ -1757,45 +1791,37 @@ function resolveNumberFormatInternals(lazyNumberFormatData) {
// Step 6.
var opt = lazyNumberFormatData.opt;
- // Compute effective locale.
- // Step 9.
var NumberFormat = numberFormatInternalProperties;
- // Step 10.
+ // Step 9.
var localeData = NumberFormat.localeData;
- // Step 11.
+ // Step 10.
var r = ResolveLocale(callFunction(NumberFormat.availableLocales, NumberFormat),
lazyNumberFormatData.requestedLocales,
lazyNumberFormatData.opt,
NumberFormat.relevantExtensionKeys,
localeData);
- // Steps 12-13. (Step 14 is not relevant to our implementation.)
+ // Steps 11-12. (Step 13 is not relevant to our implementation.)
internalProps.locale = r.locale;
internalProps.numberingSystem = r.nu;
// Compute formatting options.
- // Step 16.
- var s = lazyNumberFormatData.style;
- internalProps.style = s;
+ // Step 15.
+ var style = lazyNumberFormatData.style;
+ internalProps.style = style;
- // Steps 20, 22.
- if (s === "currency") {
+ // Steps 19, 21.
+ if (style === "currency") {
internalProps.currency = lazyNumberFormatData.currency;
internalProps.currencyDisplay = lazyNumberFormatData.currencyDisplay;
}
- // Step 24.
internalProps.minimumIntegerDigits = lazyNumberFormatData.minimumIntegerDigits;
-
- // Steps 27.
internalProps.minimumFractionDigits = lazyNumberFormatData.minimumFractionDigits;
-
- // Step 30.
internalProps.maximumFractionDigits = lazyNumberFormatData.maximumFractionDigits;
- // Step 33.
if ("minimumSignificantDigits" in lazyNumberFormatData) {
// Note: Intl.NumberFormat.prototype.resolvedOptions() exposes the
// actual presence (versus undefined-ness) of these properties.
@@ -1804,10 +1830,10 @@ function resolveNumberFormatInternals(lazyNumberFormatData) {
internalProps.maximumSignificantDigits = lazyNumberFormatData.maximumSignificantDigits;
}
- // Step 35.
+ // Step 27.
internalProps.useGrouping = lazyNumberFormatData.useGrouping;
- // Step 42.
+ // Step 34.
internalProps.boundFormat = undefined;
// The caller is responsible for associating |internalProps| with the right
@@ -1835,6 +1861,44 @@ function getNumberFormatInternals(obj, methodName) {
return internalProps;
}
+/**
+ * Applies digit options used for number formatting onto the intl object.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 11.1.1.
+ */
+function SetNumberFormatDigitOptions(lazyData, options, mnfdDefault, mxfdDefault) {
+ // We skip Step 1 because we set the properties on a lazyData object.
+
+ // Step 2-3.
+ assert(IsObject(options), "SetNumberFormatDigitOptions");
+ assert(typeof mnfdDefault === "number", "SetNumberFormatDigitOptions");
+ assert(typeof mxfdDefault === "number", "SetNumberFormatDigitOptions");
+ assert(mnfdDefault <= mxfdDefault, "SetNumberFormatDigitOptions");
+
+ // Steps 4-6.
+ const mnid = GetNumberOption(options, "minimumIntegerDigits", 1, 21, 1);
+ const mnfd = GetNumberOption(options, "minimumFractionDigits", 0, 20, mnfdDefault);
+ const mxfdActualDefault = std_Math_max(mnfd, mxfdDefault);
+ const mxfd = GetNumberOption(options, "maximumFractionDigits", mnfd, 20, mxfdActualDefault);
+
+ // Steps 7-8.
+ let mnsd = options.minimumSignificantDigits;
+ let mxsd = options.maximumSignificantDigits;
+
+ // Steps 9-11.
+ lazyData.minimumIntegerDigits = mnid;
+ lazyData.minimumFractionDigits = mnfd;
+ lazyData.maximumFractionDigits = mxfd;
+
+ // Step 12.
+ if (mnsd !== undefined || mxsd !== undefined) {
+ mnsd = GetNumberOption(options, "minimumSignificantDigits", 1, 21, 1);
+ mxsd = GetNumberOption(options, "maximumSignificantDigits", mnsd, 21, 21);
+ lazyData.minimumSignificantDigits = mnsd;
+ lazyData.maximumSignificantDigits = mxsd;
+ }
+}
+
/**
* Initializes an object as a NumberFormat.
@@ -1884,7 +1948,7 @@ function InitializeNumberFormat(numberFormat, locales, options) {
// }
//
// Note that lazy data is only installed as a final step of initialization,
- // so every Collator lazy data object has *all* these properties, never a
+ // so every NumberFormat lazy data object has *all* these properties, never a
// subset of them.
var lazyNumberFormatData = std_Object_create(null);
@@ -1914,67 +1978,46 @@ function InitializeNumberFormat(numberFormat, locales, options) {
opt.localeMatcher = matcher;
// Compute formatting options.
- // Step 15.
- var s = GetOption(options, "style", "string", ["decimal", "percent", "currency"], "decimal");
- lazyNumberFormatData.style = s;
+ // Step 14.
+ var style = GetOption(options, "style", "string", ["decimal", "percent", "currency"], "decimal");
+ lazyNumberFormatData.style = style;
- // Steps 17-20.
+ // Steps 16-19.
var c = GetOption(options, "currency", "string", undefined, undefined);
if (c !== undefined && !IsWellFormedCurrencyCode(c))
ThrowRangeError(JSMSG_INVALID_CURRENCY_CODE, c);
var cDigits;
- if (s === "currency") {
+ if (style === "currency") {
if (c === undefined)
ThrowTypeError(JSMSG_UNDEFINED_CURRENCY);
- // Steps 20.a-c.
+ // Steps 19.a-c.
c = toASCIIUpperCase(c);
lazyNumberFormatData.currency = c;
cDigits = CurrencyDigits(c);
}
- // Step 21.
+ // Step 20.
var cd = GetOption(options, "currencyDisplay", "string", ["code", "symbol", "name"], "symbol");
- if (s === "currency")
+ if (style === "currency")
lazyNumberFormatData.currencyDisplay = cd;
- // Step 23.
- var mnid = GetNumberOption(options, "minimumIntegerDigits", 1, 21, 1);
- lazyNumberFormatData.minimumIntegerDigits = mnid;
-
- // Steps 25-26.
- var mnfdDefault = (s === "currency") ? cDigits : 0;
- var mnfd = GetNumberOption(options, "minimumFractionDigits", 0, 20, mnfdDefault);
- lazyNumberFormatData.minimumFractionDigits = mnfd;
-
- // Steps 28-29.
- var mxfdDefault;
- if (s === "currency")
- mxfdDefault = std_Math_max(mnfd, cDigits);
- else if (s === "percent")
- mxfdDefault = std_Math_max(mnfd, 0);
- else
- mxfdDefault = std_Math_max(mnfd, 3);
- var mxfd = GetNumberOption(options, "maximumFractionDigits", mnfd, 20, mxfdDefault);
- lazyNumberFormatData.maximumFractionDigits = mxfd;
-
- // Steps 31-32.
- var mnsd = options.minimumSignificantDigits;
- var mxsd = options.maximumSignificantDigits;
-
- // Step 33.
- if (mnsd !== undefined || mxsd !== undefined) {
- mnsd = GetNumberOption(options, "minimumSignificantDigits", 1, 21, 1);
- mxsd = GetNumberOption(options, "maximumSignificantDigits", mnsd, 21, 21);
- lazyNumberFormatData.minimumSignificantDigits = mnsd;
- lazyNumberFormatData.maximumSignificantDigits = mxsd;
+ // Steps 22-25.
+ var mnfdDefault, mxfdDefault;
+ if (style === "currency") {
+ mnfdDefault = cDigits;
+ mxfdDefault = cDigits;
+ } else {
+ mnfdDefault = 0;
+ mxfdDefault = style === "percent" ? 0 : 3;
}
+ SetNumberFormatDigitOptions(lazyNumberFormatData, options, mnfdDefault, mxfdDefault);
- // Step 34.
+ // Steps 26.
var g = GetOption(options, "useGrouping", "boolean", undefined, true);
lazyNumberFormatData.useGrouping = g;
- // Step 43.
+ // Steps 35-36.
//
// We've done everything that must be done now: mark the lazy data as fully
// computed and install it.
@@ -1983,43 +2026,6 @@ function InitializeNumberFormat(numberFormat, locales, options) {
/**
- * Mapping from currency codes to the number of decimal digits used for them.
- * Default is 2 digits.
- *
- * Spec: ISO 4217 Currency and Funds Code List.
- * http://www.currency-iso.org/en/home/tables/table-a1.html
- */
-var currencyDigits = {
- BHD: 3,
- BIF: 0,
- BYR: 0,
- CLF: 4,
- CLP: 0,
- DJF: 0,
- GNF: 0,
- IQD: 3,
- ISK: 0,
- JOD: 3,
- JPY: 0,
- KMF: 0,
- KRW: 0,
- KWD: 3,
- LYD: 3,
- OMR: 3,
- PYG: 0,
- RWF: 0,
- TND: 3,
- UGX: 0,
- UYI: 0,
- VND: 0,
- VUV: 0,
- XAF: 0,
- XOF: 0,
- XPF: 0
-};
-
-
-/**
* Returns the number of decimal digits to be used for the given currency.
*
* Spec: ECMAScript Internationalization API Specification, 11.1.1.
@@ -2100,7 +2106,7 @@ function numberFormatFormatToBind(value) {
// Step 1.a.ii-iii.
var x = ToNumber(value);
- return intl_FormatNumber(this, x);
+ return intl_FormatNumber(this, x, /* formatToParts = */ false);
}
@@ -2127,6 +2133,22 @@ function Intl_NumberFormat_format_get() {
// Step 2.
return internals.boundFormat;
}
+_SetCanonicalName(Intl_NumberFormat_format_get, "get format");
+
+
+function Intl_NumberFormat_formatToParts(value) {
+ // Step 1.
+ var nf = this;
+
+ // Steps 2-3.
+ getNumberFormatInternals(nf, "formatToParts");
+
+ // Step 4.
+ var x = ToNumber(value);
+
+ // Step 5.
+ return intl_FormatNumber(nf, x, /* formatToParts = */ true);
+}
/**
@@ -2268,26 +2290,6 @@ function getDateTimeFormatInternals(obj, methodName) {
return internalProps;
}
-/**
- * Components of date and time formats and their values.
- *
- * Spec: ECMAScript Internationalization API Specification, 12.1.1.
- */
-var dateTimeComponentValues = {
- weekday: ["narrow", "short", "long"],
- era: ["narrow", "short", "long"],
- year: ["2-digit", "numeric"],
- month: ["2-digit", "numeric", "narrow", "short", "long"],
- day: ["2-digit", "numeric"],
- hour: ["2-digit", "numeric"],
- minute: ["2-digit", "numeric"],
- second: ["2-digit", "numeric"],
- timeZoneName: ["short", "long"]
-};
-
-
-var dateTimeComponents = std_Object_getOwnPropertyNames(dateTimeComponentValues);
-
/**
* Initializes an object as a DateTimeFormat.
@@ -2377,12 +2379,19 @@ function InitializeDateTimeFormat(dateTimeFormat, locales, options) {
lazyDateTimeFormatData.formatOpt = formatOpt;
// Step 19.
- var i, prop;
- for (i = 0; i < dateTimeComponents.length; i++) {
- prop = dateTimeComponents[i];
- var value = GetOption(options, prop, "string", dateTimeComponentValues[prop], undefined);
- formatOpt[prop] = value;
- }
+ // 12.1, Table 4: Components of date and time formats.
+ formatOpt.weekday = GetOption(options, "weekday", "string", ["narrow", "short", "long"],
+ undefined);
+ formatOpt.era = GetOption(options, "era", "string", ["narrow", "short", "long"], undefined);
+ formatOpt.year = GetOption(options, "year", "string", ["2-digit", "numeric"], undefined);
+ formatOpt.month = GetOption(options, "month", "string",
+ ["2-digit", "numeric", "narrow", "short", "long"], undefined);
+ formatOpt.day = GetOption(options, "day", "string", ["2-digit", "numeric"], undefined);
+ formatOpt.hour = GetOption(options, "hour", "string", ["2-digit", "numeric"], undefined);
+ formatOpt.minute = GetOption(options, "minute", "string", ["2-digit", "numeric"], undefined);
+ formatOpt.second = GetOption(options, "second", "string", ["2-digit", "numeric"], undefined);
+ formatOpt.timeZoneName = GetOption(options, "timeZoneName", "string", ["short", "long"],
+ undefined);
// Steps 20-21 provided by ICU - see comment after this function.
@@ -2650,7 +2659,6 @@ function ToDateTimeOptions(options, required, defaults) {
return options;
}
-
/**
* Compares the date and time components requested by options with the available
* date and time formats in formats, and selects the best match according
@@ -2836,6 +2844,7 @@ function Intl_DateTimeFormat_format_get() {
// Step 2.
return internals.boundFormat;
}
+_SetCanonicalName(Intl_DateTimeFormat_format_get, "get format");
function Intl_DateTimeFormat_formatToParts() {
@@ -2971,6 +2980,232 @@ function resolveICUPattern(pattern, result) {
}
}
+/********** Intl.PluralRules **********/
+
+/**
+ * PluralRules internal properties.
+ *
+ * Spec: ECMAScript 402 API, PluralRules, 1.3.3.
+ */
+var pluralRulesInternalProperties = {
+ _availableLocales: null,
+ availableLocales: function()
+ {
+ var locales = this._availableLocales;
+ if (locales)
+ return locales;
+
+ locales = intl_PluralRules_availableLocales();
+ addSpecialMissingLanguageTags(locales);
+ return (this._availableLocales = locales);
+ }
+};
+
+/**
+ * Compute an internal properties object from |lazyPluralRulesData|.
+ */
+function resolvePluralRulesInternals(lazyPluralRulesData) {
+ assert(IsObject(lazyPluralRulesData), "lazy data not an object?");
+
+ var internalProps = std_Object_create(null);
+
+ var requestedLocales = lazyPluralRulesData.requestedLocales;
+
+ var PluralRules = pluralRulesInternalProperties;
+
+ // Step 13.
+ const r = ResolveLocale(callFunction(PluralRules.availableLocales, PluralRules),
+ lazyPluralRulesData.requestedLocales,
+ lazyPluralRulesData.opt,
+ noRelevantExtensionKeys, undefined);
+
+ // Step 14.
+ internalProps.locale = r.locale;
+ internalProps.type = lazyPluralRulesData.type;
+
+ internalProps.pluralCategories = intl_GetPluralCategories(
+ internalProps.locale,
+ internalProps.type);
+
+ internalProps.minimumIntegerDigits = lazyPluralRulesData.minimumIntegerDigits;
+ internalProps.minimumFractionDigits = lazyPluralRulesData.minimumFractionDigits;
+ internalProps.maximumFractionDigits = lazyPluralRulesData.maximumFractionDigits;
+
+ if ("minimumSignificantDigits" in lazyPluralRulesData) {
+ assert("maximumSignificantDigits" in lazyPluralRulesData, "min/max sig digits mismatch");
+ internalProps.minimumSignificantDigits = lazyPluralRulesData.minimumSignificantDigits;
+ internalProps.maximumSignificantDigits = lazyPluralRulesData.maximumSignificantDigits;
+ }
+
+ return internalProps;
+}
+
+/**
+ * Returns an object containing the PluralRules internal properties of |obj|,
+ * or throws a TypeError if |obj| isn't PluralRules-initialized.
+ */
+function getPluralRulesInternals(obj, methodName) {
+ var internals = getIntlObjectInternals(obj, "PluralRules", methodName);
+ assert(internals.type === "PluralRules", "bad type escaped getIntlObjectInternals");
+
+ var internalProps = maybeInternalProperties(internals);
+ if (internalProps)
+ return internalProps;
+
+ internalProps = resolvePluralRulesInternals(internals.lazyData);
+ setInternalProperties(internals, internalProps);
+ return internalProps;
+}
+
+/**
+ * Initializes an object as a PluralRules.
+ *
+ * This method is complicated a moderate bit by its implementing initialization
+ * as a *lazy* concept. Everything that must happen now, does -- but we defer
+ * all the work we can until the object is actually used as a PluralRules.
+ * This later work occurs in |resolvePluralRulesInternals|; steps not noted
+ * here occur there.
+ *
+ * Spec: ECMAScript 402 API, PluralRules, 1.1.1.
+ */
+function InitializePluralRules(pluralRules, locales, options) {
+ assert(IsObject(pluralRules), "InitializePluralRules");
+
+ // Step 1.
+ if (isInitializedIntlObject(pluralRules))
+ ThrowTypeError(JSMSG_INTL_OBJECT_REINITED);
+
+ let internals = initializeIntlObject(pluralRules);
+
+ // Lazy PluralRules data has the following structure:
+ //
+ // {
+ // requestedLocales: List of locales,
+ // type: "cardinal" / "ordinal",
+ //
+ // opt: // opt object computer in InitializePluralRules
+ // {
+ // localeMatcher: "lookup" / "best fit",
+ // }
+ //
+ // minimumIntegerDigits: integer ∈ [1, 21],
+ // minimumFractionDigits: integer ∈ [0, 20],
+ // maximumFractionDigits: integer ∈ [0, 20],
+ //
+ // // optional
+ // minimumSignificantDigits: integer ∈ [1, 21],
+ // maximumSignificantDigits: integer ∈ [1, 21],
+ // }
+ //
+ // Note that lazy data is only installed as a final step of initialization,
+ // so every PluralRules lazy data object has *all* these properties, never a
+ // subset of them.
+ const lazyPluralRulesData = std_Object_create(null);
+
+ // Step 3.
+ let requestedLocales = CanonicalizeLocaleList(locales);
+ lazyPluralRulesData.requestedLocales = requestedLocales;
+
+ // Steps 4-5.
+ if (options === undefined)
+ options = {};
+ else
+ options = ToObject(options);
+
+ // Step 6.
+ const type = GetOption(options, "type", "string", ["cardinal", "ordinal"], "cardinal");
+ lazyPluralRulesData.type = type;
+
+ // Step 8.
+ let opt = new Record();
+ lazyPluralRulesData.opt = opt;
+
+ // Steps 9-10.
+ let matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit");
+ opt.localeMatcher = matcher;
+
+ // Steps 11-12.
+ SetNumberFormatDigitOptions(lazyPluralRulesData, options, 0, 3);
+
+ setLazyData(internals, "PluralRules", lazyPluralRulesData)
+}
+
+/**
+ * Returns the subset of the given locale list for which this locale list has a
+ * matching (possibly fallback) locale. Locales appear in the same order in the
+ * returned list as in the input list.
+ *
+ * Spec: ECMAScript 402 API, PluralRules, 1.3.2.
+ */
+function Intl_PluralRules_supportedLocalesOf(locales /*, options*/) {
+ var options = arguments.length > 1 ? arguments[1] : undefined;
+
+ // Step 1.
+ var availableLocales = callFunction(pluralRulesInternalProperties.availableLocales,
+ pluralRulesInternalProperties);
+ // Step 2.
+ let requestedLocales = CanonicalizeLocaleList(locales);
+
+ // Step 3.
+ return SupportedLocales(availableLocales, requestedLocales, options);
+}
+
+/**
+ * Returns a String value representing the plural category matching
+ * the number passed as value according to the
+ * effective locale and the formatting options of this PluralRules.
+ *
+ * Spec: ECMAScript 402 API, PluralRules, 1.4.3.
+ */
+function Intl_PluralRules_select(value) {
+ // Step 1.
+ let pluralRules = this;
+ // Step 2.
+ let internals = getPluralRulesInternals(pluralRules, "select");
+
+ // Steps 3-4.
+ let n = ToNumber(value);
+
+ // Step 5.
+ return intl_SelectPluralRule(pluralRules, n);
+}
+
+/**
+ * Returns the resolved options for a PluralRules object.
+ *
+ * Spec: ECMAScript 402 API, PluralRules, 1.4.4.
+ */
+function Intl_PluralRules_resolvedOptions() {
+ var internals = getPluralRulesInternals(this, "resolvedOptions");
+
+ var internalsPluralCategories = internals.pluralCategories;
+ var pluralCategories = [];
+ for (var i = 0; i < internalsPluralCategories.length; i++)
+ _DefineDataProperty(pluralCategories, i, internalsPluralCategories[i]);
+
+ var result = {
+ locale: internals.locale,
+ type: internals.type,
+ pluralCategories,
+ minimumIntegerDigits: internals.minimumIntegerDigits,
+ minimumFractionDigits: internals.minimumFractionDigits,
+ maximumFractionDigits: internals.maximumFractionDigits,
+ };
+
+ var optionalProperties = [
+ "minimumSignificantDigits",
+ "maximumSignificantDigits"
+ ];
+
+ for (var i = 0; i < optionalProperties.length; i++) {
+ var p = optionalProperties[i];
+ if (callFunction(std_Object_hasOwnProperty, internals, p))
+ _DefineDataProperty(result, p, internals[p]);
+ }
+ return result;
+}
+
+
function Intl_getCanonicalLocales(locales) {
let codes = CanonicalizeLocaleList(locales);
let result = [];
@@ -3006,3 +3241,126 @@ function Intl_getCalendarInfo(locales) {
return result;
}
+
+/**
+ * This function is a custom method designed after Intl API, but currently
+ * not part of the spec or spec proposal.
+ * We want to use it internally to retrieve translated values from CLDR in
+ * order to ensure they're aligned with what Intl API returns.
+ *
+ * This API may one day be a foundation for an ECMA402 API spec proposal.
+ *
+ * The function takes two arguments - locales which is a list of locale strings
+ * and options which is an object with two optional properties:
+ *
+ * keys:
+ * an Array of string values that are paths to individual terms
+ *
+ * style:
+ * a String with a value "long", "short" or "narrow"
+ *
+ * It returns an object with properties:
+ *
+ * locale:
+ * a negotiated locale string
+ *
+ * style:
+ * negotiated style
+ *
+ * values:
+ * A key-value pair list of requested keys and corresponding
+ * translated values
+ *
+ */
+function Intl_getDisplayNames(locales, options) {
+ // 1. Let requestLocales be ? CanonicalizeLocaleList(locales).
+ const requestedLocales = CanonicalizeLocaleList(locales);
+
+ // 2. If options is undefined, then
+ if (options === undefined)
+ // a. Let options be ObjectCreate(%ObjectPrototype%).
+ options = {};
+ // 3. Else,
+ else
+ // a. Let options be ? ToObject(options).
+ options = ToObject(options);
+
+ const DateTimeFormat = dateTimeFormatInternalProperties;
+
+ // 4. Let localeData be %DateTimeFormat%.[[localeData]].
+ const localeData = DateTimeFormat.localeData;
+
+ // 5. Let opt be a new Record.
+ const localeOpt = new Record();
+ // 6. Set localeOpt.[[localeMatcher]] to "best fit".
+ localeOpt.localeMatcher = "best fit";
+
+ // 7. Let r be ResolveLocale(%DateTimeFormat%.[[availableLocales]], requestedLocales, localeOpt,
+ // %DateTimeFormat%.[[relevantExtensionKeys]], localeData).
+ const r = ResolveLocale(callFunction(DateTimeFormat.availableLocales, DateTimeFormat),
+ requestedLocales,
+ localeOpt,
+ DateTimeFormat.relevantExtensionKeys,
+ localeData);
+
+ // 8. Let style be ? GetOption(options, "style", "string", « "long", "short", "narrow" », "long").
+ const style = GetOption(options, "style", "string", ["long", "short", "narrow"], "long");
+ // 9. Let keys be ? Get(options, "keys").
+ let keys = options.keys;
+
+ // 10. If keys is undefined,
+ if (keys === undefined) {
+ // a. Let keys be ArrayCreate(0).
+ keys = [];
+ } else if (!IsObject(keys)) {
+ // 11. Else,
+ // a. If Type(keys) is not Object, throw a TypeError exception.
+ ThrowTypeError(JSMSG_INVALID_KEYS_TYPE);
+ }
+
+ // 12. Let processedKeys be ArrayCreate(0).
+ // (This really should be a List, but we use an Array here in order that
+ // |intl_ComputeDisplayNames| may infallibly access the list's length via
+ // |ArrayObject::length|.)
+ let processedKeys = [];
+ // 13. Let len be ? ToLength(? Get(keys, "length")).
+ let len = ToLength(keys.length);
+ // 14. Let i be 0.
+ // 15. Repeat, while i < len
+ for (let i = 0; i < len; i++) {
+ // a. Let processedKey be ? ToString(? Get(keys, i)).
+ // b. Perform ? CreateDataPropertyOrThrow(processedKeys, i, processedKey).
+ callFunction(std_Array_push, processedKeys, ToString(keys[i]));
+ }
+
+ // 16. Let names be ? ComputeDisplayNames(r.[[locale]], style, processedKeys).
+ const names = intl_ComputeDisplayNames(r.locale, style, processedKeys);
+
+ // 17. Let values be ObjectCreate(%ObjectPrototype%).
+ const values = {};
+
+ // 18. Set i to 0.
+ // 19. Repeat, while i < len
+ for (let i = 0; i < len; i++) {
+ // a. Let key be ? Get(processedKeys, i).
+ const key = processedKeys[i];
+ // b. Let name be ? Get(names, i).
+ const name = names[i];
+ // c. Assert: Type(name) is string.
+ assert(typeof name === "string", "unexpected non-string value");
+ // d. Assert: the length of name is greater than zero.
+ assert(name.length > 0, "empty string value");
+ // e. Perform ? DefinePropertyOrThrow(values, key, name).
+ _DefineDataProperty(values, key, name);
+ }
+
+ // 20. Let options be ObjectCreate(%ObjectPrototype%).
+ // 21. Perform ! DefinePropertyOrThrow(result, "locale", r.[[locale]]).
+ // 22. Perform ! DefinePropertyOrThrow(result, "style", style).
+ // 23. Perform ! DefinePropertyOrThrow(result, "values", values).
+ const result = { locale: r.locale, style, values };
+
+ // 24. Return result.
+ return result;
+}
+
diff --git a/js/src/tests/Intl/PluralRules/resolvedOptions-overridden-species.js b/js/src/tests/Intl/PluralRules/resolvedOptions-overridden-species.js
new file mode 100644
index 000000000..f5f5b62a8
--- /dev/null
+++ b/js/src/tests/Intl/PluralRules/resolvedOptions-overridden-species.js
@@ -0,0 +1,27 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl")||!this.hasOwnProperty("addIntlExtras"))
+
+// Tests the PluralRules.resolvedOptions function for overriden Array[Symbol.species].
+
+addIntlExtras(Intl);
+
+var pl = new Intl.PluralRules("de");
+
+Object.defineProperty(Array, Symbol.species, {
+ value: function() {
+ return new Proxy(["?"], {
+ get(t, pk, r) {
+ return Reflect.get(t, pk, r);
+ },
+ defineProperty(t, pk) {
+ return true;
+ }
+ });
+ }
+});
+
+var pluralCategories = pl.resolvedOptions().pluralCategories;
+
+assertEqArray(pluralCategories, ["one", "other"]);
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/Intl/getCanonicalLocales-overridden-species.js b/js/src/tests/Intl/getCanonicalLocales-overridden-species.js
new file mode 100644
index 000000000..858735b58
--- /dev/null
+++ b/js/src/tests/Intl/getCanonicalLocales-overridden-species.js
@@ -0,0 +1,23 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Tests the getCanonicalLocales function for overriden Array[Symbol.species].
+
+Object.defineProperty(Array, Symbol.species, {
+ value: function() {
+ return new Proxy(["?"], {
+ get(t, pk, r) {
+ return Reflect.get(t, pk, r);
+ },
+ defineProperty(t, pk) {
+ return true;
+ }
+ });
+ }
+});
+
+var arr = Intl.getCanonicalLocales("de-x-private");
+
+assertEqArray(arr, ["de-x-private"]);
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);