intl-relative-time-format
Version:
A fully spec-compliant polyfill for 'Intl.RelativeTimeFormat'
1,346 lines (1,006 loc) • 57.7 kB
JavaScript
(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