UNPKG

intl-relative-time-format

Version:

A fully spec-compliant polyfill for 'Intl.RelativeTimeFormat'

1,346 lines (1,006 loc) 57.7 kB
(function () { 'use strict'; var SUPPORTS_RELATIVE_TIME_FORMAT = "RelativeTimeFormat" in Intl; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } var LOCALE_MATCHER = ["lookup", "best fit"]; var STYLE = ["long", "short", "narrow"]; var NUMERIC = ["always", "auto"]; var VALID_SINGULAR_RELATIVE_TIME_UNIT_VALUES = ["second", "minute", "hour", "day", "week", "month", "quarter", "year"]; /** * Sanitizes a RelativeTimeUnit into a SingularRelativeTimeUnit * * http://tc39.github.io/proposal-intl-relative-time/#sec-singularrelativetimeunit * @param {RelativeTimeUnit} unit * @return {SingularRelativeTimeUnit} */ function singularRelativeTimeUnit(unit) { // Assert: Type(unit) is String. if (typeof unit !== "string") { throw new TypeError("unit: '".concat(unit, "' must be a string")); } // If unit is "seconds", return "second". if (unit === "seconds") return "second"; // If unit is "minutes", return "minute". if (unit === "minutes") return "minute"; // If unit is "hours", return "hour". if (unit === "hours") return "hour"; // If unit is "days", return "day". if (unit === "days") return "day"; // If unit is "weeks", return "week". if (unit === "weeks") return "week"; // If unit is "months", return "month". if (unit === "months") return "month"; // If unit is "quarters", return "quarter". if (unit === "quarters") return "quarter"; // If unit is "years", return "year". if (unit === "years") return "year"; // If unit is not one of "second", "minute", "hour", "day", "week", "month", "quarter", or "year", throw a RangeError exception. if (!VALID_SINGULAR_RELATIVE_TIME_UNIT_VALUES.some(function (validUnit) { return validUnit === unit; })) { throw new RangeError("Unit: '".concat(unit, "' must be one of: ").concat(VALID_SINGULAR_RELATIVE_TIME_UNIT_VALUES.map(function (val) { return "\"".concat(val, "\""); }).join(", "))); } // Return unit. return unit; } function _typeof2(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof2 = function _typeof2(obj) { return typeof obj; }; } else { _typeof2 = function _typeof2(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof2(obj); } function _typeof(obj) { if (typeof Symbol === "function" && _typeof2(Symbol.iterator) === "symbol") { _typeof = function _typeof(obj) { return _typeof2(obj); }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : _typeof2(obj); }; } return _typeof(obj); } /* tslint:disable:use-primitive-type no-construct no-any */ /** * The abstract operation ToObject converts argument to a value of type Object. * * https://tc39.github.io/ecma262/#sec-toobject * @param {T} argument * @return {T extends boolean ? Boolean : T extends number ? Number : T extends string ? String : T extends symbol ? symbol : T} */ function toObject(argument) { if (argument == null) { throw new TypeError("Argument ".concat(argument, " cannot be converted to an Object")); } if (typeof argument === "boolean") { return new Boolean(argument); } if (typeof argument === "number") { return new Number(argument); } if (typeof argument === "string") { return new String(argument); } if (_typeof(argument) === "symbol") { return new Object(argument); } return argument; } /** * The abstract operation ToString converts argument to a value of type String * https://tc39.es/ecma262/#sec-tostring * @param {*} argument * @returns {boolean} */ function toString(argument) { return argument + ""; } /** * A Regular Expression that matches Unicode extension sequences * @type {RegExp} */ var UNICODE_EXTENSION_SEQUENCE_REGEXP = /-u(?:-[0-9a-z]{2,8})+/gi; /** * Removes all Unicode characters from the given string * @param {string} str * @return {string} */ function removeUnicodeExtensionSequences(str) { return str.replace(UNICODE_EXTENSION_SEQUENCE_REGEXP, ""); } /** * The abstract operation UnicodeExtensionValue is called with extension, which must be a Unicode locale extension sequence, * and String key. This operation returns the type subtags for key. * @param {string} extension * @param {string} key * @returns {string?} */ function unicodeExtensionValue(extension, key) { // Assert: The number of elements in key is 2. if (key.length !== 2) { throw new TypeError("Could not get UnicodeExtensionValue: The given key: '".concat(key, "' must have a length of 2")); } // Let size be the number of elements in extension. var size = key.length; // Let searchValue be the concatenation of "-", key, and "-". var searchValue = "-".concat(key, "-"); // Let pos be Call(%StringProto_indexOf%, extension, « searchValue »). var pos = String.prototype.indexOf.call(extension, searchValue); // If pos ≠ -1, then if (pos !== -1) { // Let start be pos + 4. var start = pos + 4; // Let end be start. var end = start; // Let k be start. var k = start; // Let done be false. var done = false; // Repeat, while done is false while (!done) { // Let e be Call(%StringProto_indexOf%, extension, « "-", k »). var e = String.prototype.indexOf.call(extension, "-", k); // If e = -1, let len be size - k; else let len be e - k. var len = e === -1 ? size - k : e - k; // If len = 2, then if (len === 2) { // Let done be true. done = true; } // Else if e = -1, then else if (e === -1) { // Let end be size. end = size; // Let done be true. done = true; } // Else, else { // Let end be e. end = e; // Let k be e + 1. k = e + 1; } } // Return the String value equal to the substring of extension consisting of // the code units at indices start (inclusive) through end (exclusive). return extension.slice(start, end); } // Let searchValue be the concatenation of "-" and key. searchValue = "-".concat(key); // Let pos be Call(%StringProto_indexOf%, extension, « searchValue »). pos = String.prototype.indexOf.call(extension, searchValue); // If pos ≠ -1 and pos + 3 = size, then if (pos !== -1 && pos + 3 === size) { // Return the empty String. return ""; } // Return undefined. return undefined; } /** * The BestAvailableLocale abstract operation compares the provided argument locale, * which must be a String value with a structurally valid and canonicalized BCP 47 language tag, * against the locales in availableLocales and returns either the longest non-empty prefix of locale * that is an element of availableLocales, or undefined if there is no such element. It uses the fallback * mechanism of RFC 4647, section 3.4. * * https://tc39.github.io/ecma402/#sec-bestavailablelocale * @param {Locales} availableLocales * @param {Locale} locale * @return {string} */ function bestAvailableLocale(availableLocales, locale) { // Let candidate be locale. var candidate = locale; // Repeat while (true) { // If availableLocales contains an element equal to candidate, return candidate. if (availableLocales.includes(candidate)) { return candidate; } // Let pos be the character index of the last occurrence of "-" (U+002D) within candidate. var pos = candidate.lastIndexOf("-"); // If that character does not occur, return undefined. if (pos === -1) return undefined; // If pos ≥ 2 and the character "-" occurs at index pos-2 of candidate, decrease pos by 2. if (pos >= 2 && candidate.charAt(pos - 2) === "-") { pos -= 2; } // Let candidate be the substring of candidate from position 0, inclusive, to position pos, exclusive. candidate = candidate.slice(0, pos); } } /** * Must represent the structurally valid (6.2.2) and canonicalized (6.2.3) BCP 47 language tag for the host environment's current locale. * * https://tc39.github.io/ecma402/#sec-defaultlocale * @type {Locale?} */ var _defaultLocale; /** * Sets the default locale * @param {Locale} locale */ function setDefaultLocale(locale) { _defaultLocale = locale; } /** * The DefaultLocale abstract operation returns a String value representing the structurally valid (6.2.2) and canonicalized (6.2.3) BCP 47 language tag for the host environment's current locale. * https://tc39.github.io/ecma402/#sec-defaultlocale * @return {Locale | undefined} */ function getDefaultLocale() { return _defaultLocale; } /** * Retrieves the default locale if it is set, and throws otherwise * @return {Locale} */ function ensureDefaultLocale() { if (_defaultLocale == null) { throw new ReferenceError("Could not determine locale: No default locale has been configured"); } return _defaultLocale; } /** * The LookupMatcher abstract operation compares requestedLocales, which must be a List as returned by CanonicalizeLocaleList, * against the locales in availableLocales and determines the best available language to meet the request. * * https://tc39.github.io/ecma402/#sec-lookupmatcher * @param {MatcherOptions} options * @return {MatcherResult} */ function lookupMatcher(_ref) { var availableLocales = _ref.availableLocales, requestedLocales = _ref.requestedLocales; // Let result be a new Record. var result = {}; // For each element locale of requestedLocales in List order, do var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = requestedLocales[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var locale = _step.value; // Let noExtensionsLocale be the String value that is locale with all Unicode locale extension sequences removed. var noExtensionsLocale = removeUnicodeExtensionSequences(locale); // Let availableLocale be BestAvailableLocale(availableLocales, noExtensionsLocale). var availableLocale = bestAvailableLocale(availableLocales, noExtensionsLocale); // If availableLocale is not undefined, then if (availableLocale !== undefined) { // Set result.[[locale]] to availableLocale. result.locale = availableLocale; // If locale and noExtensionsLocale are not the same String value, then if (locale !== noExtensionsLocale) { // Let extension be the String value consisting of the first substring of local // that is a Unicode locale extension sequence. var extensionMatch = locale.match(UNICODE_EXTENSION_SEQUENCE_REGEXP); // Set result.[[extension]] to extension. result.extension = extensionMatch == null ? "" : extensionMatch[0]; } return result; } } // Let defLocale be DefaultLocale(). } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return != null) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } var defLocale = ensureDefaultLocale(); // Set result.[[locale]] to defLocale. result.locale = defLocale; // Return result. return result; } /** * The BestFitMatcher abstract operation compares requestedLocales, * which must be a List as returned by CanonicalizeLocaleList, * against the locales in availableLocales and determines the best available language to meet the request. * The algorithm is implementation dependent, but should produce results that a typical user of the requested * locales would perceive as at least as good as those produced by the LookupMatcher abstract operation. * RelativeTimeFormatOptions specified through Unicode locale extension sequences must be ignored by the algorithm. * Information about such subsequences is returned separately. The abstract operation returns a record * with a [[locale]] field, whose value is the language tag of the selected locale, * which must be an element of availableLocales. * If the language tag of the request locale that led to the selected locale contained a Unicode locale extension sequence, * then the returned record also contains an [[extension]] field whose value is the first Unicode locale extension sequence * within the request locale language tag. * * https://tc39.github.io/ecma402/#sec-bestfitmatcher * @param {MatcherOptions} options * @return {MatcherResult} */ function bestFitMatcher(options) { return lookupMatcher(options); } /** * Returns true if the given item is a record * @param {T} item * @return {item is T} */ function isRecord(item) { return Object.prototype.toString.call(item) === "[object Object]"; } /** * Returns true if the given item is a List * @param {T} item * @return {item is T} */ function isList(item) { return Array.isArray(item) || isRecord(item); } /** * The internal comparison abstract operation SameValueNonNumber(x, y), where neither x nor y are Number values, produces true or false. * * https://tc39.github.io/ecma262/#sec-samevaluenonnumber * @param {Exclude<*, number>} x * @param {Exclude<*, number>} y * @return {boolean} */ function sameValueNonNumber(x, y) { // Assert: Type(x) is not Number. if (typeof x === "number") { throw new TypeError("First argument 'x' must not be a number"); } // Assert: Type(x) is the same as Type(y). if (_typeof(x) !== _typeof(y)) { throw new TypeError("The given arguments must have the same type"); } // If Type(x) is Undefined, return true. if (x === undefined) return true; // If Type(x) is Null, return true. if (x === null) return true; // If Type(x) is String, then if (typeof x === "string") { // If x and y are exactly the same sequence of code units // (same length and same code units at corresponding indices), return true; otherwise, return false. return x === y; } // If Type(x) is Boolean, then if (typeof x === "boolean") { // If x and y are both true or both false, return true; otherwise, return false. return x === y; } // If Type(x) is Symbol, then if (_typeof(x) === "symbol") { // If x and y are both the same Symbol value, return true; otherwise, return false. return x.valueOf() === y.valueOf(); } // If x and y are the same Object value, return true. Otherwise, return false. return x === y; } /** * The internal comparison abstract operation SameValue(x, y), where x and y are ECMAScript language values, produces true or false. * * https://tc39.github.io/ecma262/#sec-samevalue * @param {*} x * @param {*} y * @return {boolean} */ function sameValue(x, y) { // If Type(x) is different from Type(y), return false. if (_typeof(x) !== _typeof(y)) return false; // If Type(x) is Number, then if (typeof x === "number") { // If x is NaN and y is NaN, return true. if (isNaN(x) && isNaN(y)) return true; // If x is +0 and y is -0, return false. if (Object.is(x, 0) && Object.is(y, -0)) return false; // If x is the same Number value as y, return true. if (x === y) return true; // Return false. return false; } // Return SameValueNonNumber(x, y). return sameValueNonNumber(x, y); } /** * The ResolveLocale abstract operation compares a BCP 47 language priority list * requestedLocales against the locales in availableLocales and determines the best available language to meet the request. * availableLocales, requestedLocales, and relevantExtensionKeys must be provided as List values, * options and localeData as Records. * * https://tc39.github.io/ecma402/#sec-resolvelocale * @param {Locales} availableLocales * @param {Locales} requestedLocales * @param {ResolveLocaleOptions} options * @param {RelevantExtensionKey[]} relevantExtensionKeys * @param {LocaleData} localeData * @returns {ResolveLocaleResult} */ function resolveLocale(availableLocales, requestedLocales, options, relevantExtensionKeys, localeData) { // Let matcher be options.[[localeMatcher]]. var matcher = options.localeMatcher; // If matcher is "lookup", then // (a) Let r be LookupMatcher(availableLocales, requestedLocales). // (b) Let r be BestFitMatcher(availableLocales, requestedLocales). var r = matcher === "lookup" ? lookupMatcher({ availableLocales: availableLocales, requestedLocales: requestedLocales }) : bestFitMatcher({ availableLocales: availableLocales, requestedLocales: requestedLocales }); // Let foundLocale be r.[[locale]]. var foundLocale = r.locale; // Let result be a new Record. var result = {}; // Set result.[[dataLocale]] to foundLocale. result.dataLocale = foundLocale; // Let supportedExtension be "-u" var supportedExtension = "-u"; // For each element key of relevantExtensionKeys in List order, do var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = relevantExtensionKeys[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var key = _step.value; // Let foundLocaleData be localeData.[[<foundLocale>]]. var foundLocaleData = localeData[foundLocale]; // Assert: Type(foundLocaleData) is Record. if (!isRecord(foundLocaleData)) { throw new TypeError("LocaleData for locale: '".concat(foundLocale, "' must be an object")); } // Let keyLocaleData be foundLocaleData.[[<key>]]. var keyLocaleData = foundLocaleData[key]; // Assert: Type(keyLocaleData) is List. if (!isList(keyLocaleData)) { throw new TypeError("key: '".concat(key, "' in LocaleData for locale: '").concat(foundLocale, "' must be indexable")); } // Let value be keyLocaleData[0]. var value = keyLocaleData[0]; // Assert: Type(value) is either String or Null. if (typeof value !== "string" && value !== null) { throw new TypeError("value: '".concat(value, "' for key: '").concat(key, "' in LocaleData for locale: '").concat(foundLocale, "' must be a string or null")); } // Let supportedExtensionAddition be "". var supportedExtensionAddition = ""; // If r has an [[extension]] field, then if ("extension" in r) { // Let requestedValue be UnicodeExtensionValue(r.[[extension]], key). var requestedValue = unicodeExtensionValue(r.extension, key); // If requestedValue is not undefined, then if (requestedValue !== undefined) { // If requestedValue is not the empty String, then if (requestedValue !== "") { // If keyLocaleData contains requestedValue, then if (keyLocaleData.includes(requestedValue)) { // Let value be requestedValue. value = requestedValue; // Let supportedExtensionAddition be the concatenation of "-", key, "-", and value. supportedExtensionAddition = "-".concat(key, "-").concat(value); } } // Else if keyLocaleData contains "true", then else if (keyLocaleData.includes("true")) { // Let value be "true". value = "true"; } } } // If options has a field [[<key>]], then if ("key" in options) { // Let optionsValue be options.[[<key>]]. var optionsValue = options.key; // Assert: Type(optionsValue) is either String, Undefined, or Null. if (typeof optionsValue !== "string" && optionsValue != null) { throw new TypeError("options value: '".concat(optionsValue, "' must be a string, undefined, or null")); } // If keyLocaleData contains optionsValue, then if (optionsValue !== undefined && keyLocaleData.includes(optionsValue)) { // If SameValue(optionsValue, value) is false, then // tslint:disable-next-line:no-collapsible-if if (!sameValue(optionsValue, value)) { // Let value be optionsValue. value = optionsValue; // Let supportedExtensionAddition be "". supportedExtensionAddition = ""; } } } // Set result.[[<key>]] to value. result[key] = value; // Append supportedExtensionAddition to supportedExtension. supportedExtension += supportedExtensionAddition; } // If the number of elements in supportedExtension is greater than 2, then } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return != null) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } if (supportedExtension.length > 2) { // Let privateIndex be Call(%StringProto_indexOf%, foundLocale, « "-x-" »). var privateIndex = String.prototype.indexOf.call(foundLocale, "-x-"); // If privateIndex = -1, then if (privateIndex === -1) { // Let foundLocale be the concatenation of foundLocale and supportedExtension. foundLocale = "".concat(foundLocale).concat(supportedExtension); } // Else, else { // Let preExtension be the substring of foundLocale from position 0, inclusive, to position privateIndex, exclusive. var preExtension = foundLocale.slice(0, privateIndex); // Let postExtension be the substring of foundLocale from position privateIndex to the end of the string. var postExtension = foundLocale.slice(privateIndex); // Let foundLocale be the concatenation of preExtension, supportedExtension, and postExtension. foundLocale = "".concat(preExtension).concat(supportedExtension).concat(postExtension); } // Assert: IsStructurallyValidLanguageTag(foundLocale) is true. // Let foundLocale be CanonicalizeLanguageTag(foundLocale). // Intl.getCanonicalLocales will throw a TypeError if the locale isn't structurally valid foundLocale = Intl.getCanonicalLocales(foundLocale)[0]; } // Set result.[[locale]] to foundLocale. result.locale = foundLocale; // Return result. return result; } /** * The LookupSupportedLocales abstract operation returns the subset of the provided BCP 47 language priority list * requestedLocales for which availableLocales has a matching locale when using the BCP 47 Lookup algorithm. * Locales appear in the same order in the returned list as in requestedLocales. * * https://tc39.github.io/ecma402/#sec-bestfitsupportedlocales * @param {Locales} availableLocales * @param {Locales} requestedLocales * @return {Locales} */ function lookupSupportedLocales(availableLocales, requestedLocales) { // Let subset be a new empty List. var subset = []; // For each element locale of requestedLocales in List order, do var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = requestedLocales[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var locale = _step.value; // Let noExtensionsLocale be the String value that is locale with all Unicode locale extension sequences removed. var noExtensionsLocale = removeUnicodeExtensionSequences(locale); // Let availableLocale be BestAvailableLocale(availableLocales, noExtensionsLocale). var availableLocale = bestAvailableLocale(availableLocales, noExtensionsLocale); // If availableLocale is not undefined, append locale to the end of subset. if (availableLocale !== undefined) { subset.push(locale); } } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return != null) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } return subset; } /** * The BestFitSupportedLocales abstract operation returns the subset of the provided BCP 47 language priority list * requestedLocales for which availableLocales has a matching locale when using the Best Fit Matcher algorithm. * Locales appear in the same order in the returned list as in requestedLocales. * * https://tc39.github.io/ecma402/#sec-bestfitsupportedlocales * @param {Locales} availableLocales * @param {Locales} requestedLocales * @return {Locales} */ function bestFitSupportedLocales(availableLocales, requestedLocales) { return lookupSupportedLocales(availableLocales, requestedLocales); } /** * The abstract operation IsPropertyKey determines if argument, which must be an ECMAScript language value, is a value that may be used as a property key. * https://tc39.es/ecma262/#sec-ispropertykey * @param {*} argument * @returns {boolean} */ function isPropertyKey(argument) { // If Type(argument) is String, return true. if (typeof argument === "string") return true; // If Type(argument) is Symbol, return true. if (_typeof(argument) === "symbol") return true; // Return false. return false; } /** * The abstract operation Get is used to retrieve the value of a specific property of an object. The operation is called with arguments O and P where O is the object and P is the property key. * https://tc39.es/ecma262/#sec-get-o-p * @param {O} o * @param {P} p * @returns {O[P]} */ function get(o, p) { // Assert: Type(O) is Object. if (_typeof(o) !== "object") { throw new TypeError("Given argument ".concat(o, " must be of type Object")); } // Assert: IsPropertyKey(P) is true. if (!isPropertyKey(p)) { throw new TypeError("Given argument ".concat(p, " must be a PropertyKey")); } return o[p]; } /** * The abstract operation ToBoolean converts argument to a value of type Boolean * https://tc39.es/ecma262/#sec-toboolean * @param {*} argument * @returns {boolean} */ function toBoolean(argument) { return Boolean(argument); } /** * https://tc39.es/ecma402/#sec-getoption * @param {Options} options * @param {Property} property * @param {Type} type * @param {Values} values * @param {Fallback} fallback * @returns {Return} */ function getOption(options, property, type, values, fallback) { // Let value be ? Get(options, property). var value = get(options, property); // If value is not undefined, then if (value !== undefined) { // Assert: type is "boolean" or "string". if (type !== "boolean" && type !== "string") { throw new TypeError("Expected type ".concat(type, " to be 'boolean' or 'string")); } // If type is "boolean", then if (type === "boolean") { // Let value be ToBoolean(value). value = toBoolean(value); } // If type is "string", then if (type === "string") { // Let value be ? ToString(value). value = toString(value); } // If values is not undefined, then if (values !== undefined) { // If values does not contain an element equal to value, throw a RangeError exception. // tslint:disable-next-line:no-collapsible-if if (!values.includes(value)) { throw new RangeError("Value ".concat(value, " out of range for options property ").concat(property)); } } // Return value. return value; } // Else, return fallback. else { return fallback; } } /** * The SupportedLocales abstract operation returns the subset of the provided BCP 47 language priority list * requestedLocales for which availableLocales has a matching locale. Two algorithms are available to match * the locales: the Lookup algorithm described in RFC 4647 section 3.4, and an implementation dependent * best-fit algorithm. Locales appear in the same order in the returned list as in requestedLocales. * * https://tc39.github.io/ecma402/#sec-supportedlocales * @param {Locales} availableLocales * @param {Locales} requestedLocales * @param {SupportedLocalesOptions} [options] * @return {Locales} */ function supportedLocales(availableLocales, requestedLocales, options) { var matcher; // If options is not undefined, then if (options !== undefined) { // Let options be ? ToObject(options). options = toObject(options); // Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit"). matcher = getOption(options, "localeMatcher", "string", LOCALE_MATCHER, "best fit"); } // Else, let matcher be "best fit". else { matcher = "best fit"; } // If matcher is "best fit", then let supportedLocales be BestFitSupportedLocales(availableLocales, requestedLocales). // Else let supportedLocales be LookupSupportedLocales(availableLocales, requestedLocales). // Return CreateArrayFromList(supportedLocales). return matcher === "best fit" ? bestFitSupportedLocales(availableLocales, requestedLocales) : lookupSupportedLocales(availableLocales, requestedLocales); } /** * A WeakMap between RelativeTimeFormat instances and their internal slot members * @type {WeakMap<RelativeTimeFormat, RelativeTimeFormatInstanceInternals>} */ var RELATIVE_TIME_FORMAT_INSTANCE_INTERNAL_MAP = new WeakMap(); /** * Contains the internal static for RelativeTimeFormat * @type {RelativeTimeFormatStaticInternals} */ var RELATIVE_TIME_FORMAT_STATIC_INTERNALS = { /** * The value of the [[RelevantExtensionKeys]] internal slot is « "nu" ». * http://tc39.github.io/proposal-intl-relative-time/#sec-Intl.RelativeTimeFormat-internal-slots */ relevantExtensionKeys: ["nu"], /** * The value of the [[LocaleData]] internal slot is implementation defined within the constraints described in 9.1 * http://tc39.github.io/proposal-intl-relative-time/#sec-Intl.RelativeTimeFormat-internal-slots */ localeData: {}, /** * The value of the [[AvailableLocales]] internal slot is implementation defined within the constraints described in 9.1 * http://tc39.github.io/proposal-intl-relative-time/#sec-Intl.RelativeTimeFormat-internal-slots */ availableLocales: [] }; /** * Sets the value for a property in an internal slot for an instance of RelativeTimeFormat * @param {RelativeTimeFormat} instance * @param {T} property * @param {RelativeTimeFormatInstanceInternals[T]} value */ function setInternalSlot(instance, property, value) { var record = RELATIVE_TIME_FORMAT_INSTANCE_INTERNAL_MAP.get(instance); if (record == null) { record = Object.create(null); RELATIVE_TIME_FORMAT_INSTANCE_INTERNAL_MAP.set(instance, record); } // Update the property with the given value record[property] = value; } /** * Gets the value associated with the given property on the internal slots of the given instance of RelativeTimeFormat * @param {RelativeTimeFormat} instance * @param {T} property * @return {RelativeTimeFormatInstanceInternals[T]} */ function getInternalSlot(instance, property) { var record = RELATIVE_TIME_FORMAT_INSTANCE_INTERNAL_MAP.get(instance); if (record == null) { throw new ReferenceError("No internal slots has been allocated for the given instance of RelativeTimeFormat"); } return record[property]; } /** * Returns true if the given property on the internal slots of the given instance of RelativeTimeFormat exists * @param {RelativeTimeFormat} instance * @param {T} property * @return {RelativeTimeFormatInstanceInternals[T]} */ function hasInternalSlot(instance, property) { var record = RELATIVE_TIME_FORMAT_INSTANCE_INTERNAL_MAP.get(instance); return record != null && property in record; } /** * When the ResolvePlural abstract operation is called with arguments pluralRules (which must be an object initialized as a PluralRules) and n (which must be a Number value), it returns a String value representing the plural form of n according to the effective locale and the options of pluralRules. * * https://tc39.github.io/ecma402/#sec-resolveplural * @param {RelativeTimeFormat} relativeTimeFormat - needed to get internal slots * @param {number} n */ function resolvePlural(relativeTimeFormat, n) { // Assert: Type(pluralRules) is Object. // Assert: pluralRules has an [[InitializedPluralRules]] internal slot. if (!hasInternalSlot(relativeTimeFormat, "pluralRules")) { throw new TypeError("Given instance of of Intl.RelativeTimeFormat must have an [[InitializedPluralRules]] internal slot"); } // Assert: Type(n) is Number. if (typeof n !== "number") { throw new TypeError("Argument 'n' must be a number"); } // If n is not a finite Number, then if (!isFinite(n)) { // Return "other". return "other"; } // Let locale be pluralRules.[[Locale]]. // Let type be pluralRules.[[Type]]. var pluralRules = getInternalSlot(relativeTimeFormat, "pluralRules"); // Return ? PluralRuleSelect(locale, type, n, operands). return pluralRules.select(n); } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _objectSpread2(target) { for (var i = 1; i < arguments.length; i++) { if (i % 2) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } else { Object.defineProperties(target, Object.getOwnPropertyDescriptors(arguments[i])); } } return target; } /** * The MakePartsList abstract operation is called with arguments pattern, * a pattern String, unit, a String, and parts, a List of Records representing a formatted Number. * * http://tc39.github.io/proposal-intl-relative-time/#sec-makepartslist * @param {string} pattern * @param {SingularRelativeTimeUnit} unit * @param {Intl.NumberFormatPart[]} parts * @returns {RelativeTimeFormatPart} */ function makePartsList(pattern, unit, parts) { // Let result be a new empty List. var result = []; // Let beginIndex be ! Call(%StringProto_indexOf%, pattern, « "{", 0 »). var beginIndex = String.prototype.indexOf.call(pattern, "{", 0); // Let endIndex be 0. var endIndex = 0; // Let nextIndex be 0. var nextIndex = 0; // Let length be the number of elements in pattern. var length = pattern.length; // Repeat, while beginIndex is an integer index into pattern while (pattern[beginIndex] !== undefined) { // Set endIndex to ! Call(%StringProto_indexOf%, pattern, « "}", beginIndex »). endIndex = String.prototype.indexOf.call(pattern, "}", beginIndex); // Assert: endIndex is not -1, otherwise the pattern would be malformed. if (endIndex === -1) { throw new RangeError("The pattern: '".concat(pattern, "' is malformed")); } // If beginIndex is greater than nextIndex, then if (beginIndex > nextIndex) { // Let literal be a substring of pattern from position nextIndex, inclusive, to position beginIndex, exclusive. var literal = pattern.slice(nextIndex, beginIndex); // Add new part Record { [[Type]]: "literal", [[Value]]: literal } as a new element of the list result. result.push({ type: "literal", value: literal }); } // Let p be the substring of pattern from position beginIndex, exclusive, to position endIndex, exclusive. var p = pattern.slice(beginIndex + 1, endIndex); // Assert: p is "0". if (p !== "0") { throw new TypeError("Expected ".concat(p, " to be \"0\"")); } // For each part in parts, do var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = parts[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var part = _step.value; // Add new part Record { [[Type]]: part.[[Type]], [[Value]]: part.[[Value]], [[Unit]]: unit } as a new element on the List result. if (part.type === "literal") { result.push(_objectSpread2({}, part, { type: part.type })); } else { result.push(_objectSpread2({}, part, { unit: unit })); } } // Set nextIndex to endIndex + 1. } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return != null) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } nextIndex = endIndex + 1; // Set beginIndex to Call(%StringProto_indexOf%, pattern, « "{", nextIndex »). beginIndex = String.prototype.indexOf.call(pattern, "{", nextIndex); } // If nextIndex is less than length, then if (nextIndex < length) { // Let literal be the substring of pattern from position nextIndex, exclusive, to position length, exclusive. // CORRECTION: It should actually be from nextIndex, inclusive, to correctly partition text var _literal = pattern.slice(nextIndex, length); // Add new part Record { [[Type]]: "literal", [[Value]]: literal } as a new element of the list result. result.push({ type: "literal", value: _literal }); } return result; } /** * When the FormatRelativeTime abstract operation is called with arguments relativeTimeFormat, * value, and unit it returns a String value representing value (interpreted as a time value as specified in ES2016, 20.3.1.1) * according to the effective locale and the formatting options of relativeTimeFormat. * @param {RelativeTimeFormat} relativeTimeFormat * @param {number} value * @param {RelativeTimeUnit} unit * @returns {RelativeTimeFormatPart[]} */ function partitionRelativeTimePattern(relativeTimeFormat, value, unit) { // Assert: relativeTimeFormat has an [[InitializedRelativeTimeFormat]] internal slot. if (!hasInternalSlot(relativeTimeFormat, "initializedRelativeTimeFormat")) { throw new TypeError("Internal function called on incompatible receiver ".concat(relativeTimeFormat.toString())); } // Assert: Type(value) is Number. if (typeof value !== "number") { throw new TypeError("Argument: 'value' must be a number"); } // Assert: Type(unit) is String. if (typeof unit !== "string") { throw new TypeError("Argument: 'unit' must be a string"); } // If value is NaN, +∞, or -∞, throw a RangeError exception. if (isNaN(value) || value === Infinity || value === -Infinity) { throw new RangeError("Value need to be finite number"); } // Let unit be ? SingularRelativeTimeUnit(unit). unit = singularRelativeTimeUnit(unit); // Let fields be relativeTimeFormat.[[Fields]]. var fields = getInternalSlot(relativeTimeFormat, "fields"); // Let style be relativeTimeFormat.[[Style]]. var style = getInternalSlot(relativeTimeFormat, "style"); // If style is equal to "short", then let entry be the string-concatenation of unit and "-short". // Else if style is equal to "narrow", then let entry be the string-concatenation of unit and "-narrow". // Else let entry be unit. var entry = style === "short" ? "".concat(unit, "-short") : style === "narrow" ? "".concat(unit, "-narrow") : unit; // Let exists be ! HasProperty(fields, entry). var exists = entry in fields; // If exists is false, then if (!exists) { // Let entry be unit. entry = unit; } // Let patterns be ! Get(fields, entry). var patterns = fields[entry]; // Make sure that the patterns are defined if (patterns == null) { throw new TypeError("Could not match entry: '".concat(entry, "' inside fields for locale: '").concat(getInternalSlot(relativeTimeFormat, "locale"), "'")); } // Let numeric be relativeTimeFormat.[[Numeric]]. var numeric = getInternalSlot(relativeTimeFormat, "numeric"); // If numeric is equal to "auto", then if (numeric === "auto") { // Let exists be ! HasProperty(patterns, ! ToString(value)). exists = toString(value) in patterns; // If exists is true, then if (exists) { // Let result be ! Get(patterns, ! ToString(value)). var result = patterns[toString(value)]; // Return a List containing the Record { [[Type]]: "literal", [[Value]]: result }. return [{ type: "literal", value: result }]; } } // If value is -0 or if value is less than 0, then let tl be "past". // Else let tl be "future". var tl = Object.is(value, -0) || value < 0 ? "past" : "future"; // Let po be ! Get(patterns, tl). var po = patterns[tl]; // Let fv be ! PartitionNumberPattern(relativeTimeFormat.[[NumberFormat]], value). var fv = getInternalSlot(relativeTimeFormat, "numberFormat").formatToParts(Math.abs(value)); // Let pr be ! ResolvePlural(relativeTimeFormat.[[PluralRules]], value). var pr = resolvePlural(relativeTimeFormat, value); // Let pattern be ! Get(po, pr). var pattern = po[pr]; // Return ! MakePartsList(pattern, unit, fv). return makePartsList(pattern, unit, fv); } /** * The FormatRelativeTime abstract operation is called with arguments relativeTimeFormat * (which must be an object initialized as a RelativeTimeFormat), value (which must be a Number value), * and unit (which must be a String denoting the value unit) and performs the following steps: * * http://tc39.github.io/proposal-intl-relative-time/#sec-FormatRelativeTime * @param {RelativeTimeFormat} relativeTimeFormat * @param {number} value * @param {RelativeTimeUnit} unit * @return {string} */ function formatRelativeTime(relativeTimeFormat, value, unit) { // Let parts be ? PartitionRelativeTimePattern(relativeTimeFormat, value, unit). var parts = partitionRelativeTimePattern(relativeTimeFormat, value, unit); // Let result be an empty String. var result = ""; // For each part in parts, do var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = parts[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var part = _step.value; // Set result to the string-concatenation of result and part.[[Value]]. result += part.value; } // Return result. } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return != null) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } return result; } /** * The FormatRelativeTimeToParts abstract operation is called with arguments relativeTimeFormat * (which must be an object initialized as a RelativeTimeFormat), value (which must be a Number value), * and unit (which must be a String denoting the value unit) * * http://tc39.github.io/proposal-intl-relative-time/#sec-FormatRelativeTimeToParts * @param {RelativeTimeFormat} relativeTimeFormat * @param {number} value * @param {RelativeTimeUnit} unit * @return {RelativeTimeFormatPart[]} */ function formatRelativeTimeToParts(relativeTimeFormat, value, unit) { return partitionRelativeTimePattern(relativeTimeFormat, value, unit); } /** * The abstract operation ToNumber converts argument to a value of type Number * https://tc39.es/ecma262/#sec-tonumber * @param {*} argument * @returns {boolean} */ function toNumber(argument) { return Number(argument); } /** * The RelativeTimeFormat constructor is the %RelativeTimeFormat% intrinsic object and a standard built-in property of the Intl object. * Behaviour common to all service constructor properties of the Intl object is specified in 9.1. * * http://tc39.github.io/proposal-intl-relative-time/#sec-intl-relativetimeformat-constructor */ var RelativeTimeFormat = /*#__PURE__*/ function () { // The spec states that the constructor must have a length of 0 and therefore be parameter-less function RelativeTimeFormat() { _classCallCheck(this, RelativeTimeFormat); var locales = arguments[0]; var options = arguments[1]; // If NewTarget is undefined, throw a TypeError exception. if ((this instanceof RelativeTimeFormat ? this.constructor : void 0) === undefined) { throw new TypeError("Constructor Intl.RelativeTimeFormat requires 'new'"); } // The following operations comes from the 'InitializeRelativeFormat' abstract operation (http://tc39.github.io/proposal-intl-relative-time/#sec-InitializeRelativeTimeFormat) // Let requestedLocales be ? CanonicalizeLocaleList(locales). var requestedLocales = Intl.getCanonicalLocales(locales); // If options is undefined, then (a) Let options be ObjectCreate(null). // Else (b) Let options be ? ToObject(options). options = options === undefined ? Object.create(null) : toObject(options); // Let opt be a new Record (that doesn't derive from Object.prototype). var opt = Object.create(null); // Let matcher be ? GetOption(options, "localeMatcher", "string", «"lookup", "best fit"», "best fit"). var matcher = getOption(options, "localeMatcher", "string", LOCALE_MATCHER, "best fit"); // Set opt.[[LocaleMatcher]] to matcher. opt.localeMatcher = matcher; // Let localeData be %RelativeTimeFormat%.[[LocaleData]]. var localeData = RELATIVE_TIME_FORMAT_STATIC_INTERNALS.localeData; // Let r be ResolveLocale(%RelativeTimeFormat%.[[AvailableLocales]], requestedLocales, opt, %RelativeTimeFormat%.[[RelevantExtensionKeys]], localeData). var r = resolveLocale(RELATIVE_TIME_FORMAT_STATIC_INTERNALS.availableLocales, requestedLocales, opt, RELATIVE_TIME_FORMAT_STATIC_INTERNALS.relevantExtensionKeys, localeData); // Let locale be r.[[Locale]]. var locale = r.locale; // Set relativeTimeFormat.[[Locale]] to locale. setInternalSlot(this, "locale", locale); // Set relativeTimeFormat.[[NumberingSystem]] to r_.[[nu]]. setInternalSlot(this, "numberingSystem", r.nu); // Let dataLocale be r.[[DataLocale]]. var dataLocale = r.dataLocale; // Let s be ? GetOption(options, "style", "string", «"long", "short", "narrow"», "long"). var s = getOption(options, "style", "string", STYLE, "long"); // Set relativeTimeFormat.[[Style]] to s. setInternalSlot(this, "style", s); // Let numeric be ? GetOption(options, "numeric", "string", «"always", "auto"», "always"). var numeric = getOption(options, "numeric", "string", NUMERIC, "always"); // Set relativeTimeFormat.[[Numeric]] to numeric. setInternalSlot(this, "numeric", numeric); // Let fields be ! Get(localeData, dataLocale). var fields = localeData[dataLocale]; // Assert: fields is an object (see 1.3.3). if (!(fields instanceof Object)) { throw new TypeError("Expected the LocaleDataEntry for locale: '".concat(dataLocale, "' to b