UNPKG

relative-time-format

Version:
466 lines (376 loc) 17 kB
function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { 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]); }); } return target; } function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } 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; } 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; } import { getDefaultLocale, setDefaultLocale, getLocaleData, addLocaleData } from './LocaleDataStore'; import resolveLocale from './resolveLocale'; import PluralRules from './PluralRules'; // Importing `PluralRule` polyfill from a separate package // results in a bundle that is larger by 1kB for some reason. // import PluralRules from 'intl-plural-rules-polyfill/cardinal' // Valid time units. export var UNITS = ["second", "minute", "hour", "day", "week", "month", "quarter", "year"]; // Valid values for the `numeric` option. var NUMERIC_VALUES = ["auto", "always"]; // Valid values for the `style` option. var STYLE_VALUES = ["long", "short", "narrow"]; // Valid values for the `localeMatcher` option. var LOCALE_MATCHER_VALUES = ["lookup", "best fit"]; /** * Polyfill for `Intl.RelativeTimeFormat` proposal. * https://github.com/tc39/proposal-intl-relative-time * https://github.com/tc39/proposal-intl-relative-time/issues/55 */ var RelativeTimeFormat = /*#__PURE__*/ function () { /** * @param {(string|string[])} [locales] - Preferred locales (or locale). * @param {Object} [options] - Formatting options. * @param {string} [options.style="long"] - One of: "long", "short", "narrow". * @param {string} [options.numeric="always"] - (Version >= 2) One of: "always", "auto". * @param {string} [options.localeMatcher="lookup"] - One of: "lookup", "best fit". Currently only "lookup" is supported. */ function RelativeTimeFormat() { var locales = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; _classCallCheck(this, RelativeTimeFormat); _defineProperty(this, "numeric", "always"); _defineProperty(this, "style", "long"); _defineProperty(this, "localeMatcher", "lookup"); var numeric = options.numeric, style = options.style, localeMatcher = options.localeMatcher; // Set `numeric` option. if (numeric !== undefined) { if (NUMERIC_VALUES.indexOf(numeric) < 0) { throw new RangeError("Invalid \"numeric\" option: ".concat(numeric)); } this.numeric = numeric; } // Set `style` option. if (style !== undefined) { if (STYLE_VALUES.indexOf(style) < 0) { throw new RangeError("Invalid \"style\" option: ".concat(style)); } this.style = style; } // Set `localeMatcher` option. if (localeMatcher !== undefined) { if (LOCALE_MATCHER_VALUES.indexOf(localeMatcher) < 0) { throw new RangeError("Invalid \"localeMatcher\" option: ".concat(localeMatcher)); } this.localeMatcher = localeMatcher; } // Set `locale`. // Convert `locales` to an array. if (typeof locales === 'string') { locales = [locales]; } // Add default locale. locales.push(getDefaultLocale()); // Choose the most appropriate locale. this.locale = RelativeTimeFormat.supportedLocalesOf(locales, { localeMatcher: this.localeMatcher })[0]; if (!this.locale) { throw new Error("No supported locale was found"); } // Construct an `Intl.PluralRules` instance (polyfill). if (PluralRules.supportedLocalesOf(this.locale).length > 0) { this.pluralRules = new PluralRules(this.locale); } else { console.warn("\"".concat(this.locale, "\" locale is not supported")); } // Use `Intl.NumberFormat` for formatting numbers (when available). if (typeof Intl !== 'undefined' && Intl.NumberFormat) { this.numberFormat = new Intl.NumberFormat(this.locale); this.numberingSystem = this.numberFormat.resolvedOptions().numberingSystem; } else { this.numberingSystem = 'latn'; } this.locale = resolveLocale(this.locale, { localeMatcher: this.localeMatcher }); } /** * Formats time `number` in `units` (either in past or in future). * @param {number} number - Time interval value. * @param {string} unit - Time interval measurement unit. * @return {string} * @throws {RangeError} If unit is not one of "second", "minute", "hour", "day", "week", "month", "quarter". * @example * // Returns "2 days ago" * rtf.format(-2, "day") * // Returns "in 5 minutes" * rtf.format(5, "minute") */ _createClass(RelativeTimeFormat, [{ key: "format", value: function format() { var _parseFormatArgs = parseFormatArgs(arguments), _parseFormatArgs2 = _slicedToArray(_parseFormatArgs, 2), number = _parseFormatArgs2[0], unit = _parseFormatArgs2[1]; return this.getRule(number, unit).replace('{0}', this.formatNumber(Math.abs(number))); } /** * Formats time `number` in `units` (either in past or in future). * @param {number} number - Time interval value. * @param {string} unit - Time interval measurement unit. * @return {Object[]} The parts (`{ type, value }`). * @throws {RangeError} If unit is not one of "second", "minute", "hour", "day", "week", "month", "quarter". * @example * // Version 1. * // Returns [ * // { type: "literal", value: "in " }, * // { type: "day", value: "100" }, * // { type: "literal", value: " days" } * // ] * rtf.formatToParts(100, "day") * // * // Version 2. * // Returns [ * // { type: "literal", value: "in " }, * // { type: "integer", value: "100", unit: "day" }, * // { type: "literal", value: " days" } * // ] * rtf.formatToParts(100, "day") */ }, { key: "formatToParts", value: function formatToParts() { var _parseFormatArgs3 = parseFormatArgs(arguments), _parseFormatArgs4 = _slicedToArray(_parseFormatArgs3, 2), number = _parseFormatArgs4[0], unit = _parseFormatArgs4[1]; var rule = this.getRule(number, unit); var valueIndex = rule.indexOf("{0}"); // "yesterday"/"today"/"tomorrow". if (valueIndex < 0) { return [{ type: "literal", value: rule }]; } var parts = []; if (valueIndex > 0) { parts.push({ type: "literal", value: rule.slice(0, valueIndex) }); } parts = parts.concat(this.formatNumberToParts(Math.abs(number)).map(function (part) { return _objectSpread({}, part, { unit: unit }); })); if (valueIndex + "{0}".length < rule.length - 1) { parts.push({ type: "literal", value: rule.slice(valueIndex + "{0}".length) }); } return parts; } /** * Returns formatting rule for `value` in `units` (either in past or in future). * @param {number} value - Time interval value. * @param {string} unit - Time interval measurement unit. * @return {string} * @throws {RangeError} If unit is not one of "second", "minute", "hour", "day", "week", "month", "quarter". * @example * // Returns "{0} days ago" * getRule(-2, "day") */ }, { key: "getRule", value: function getRule(value, unit) { // Get locale-specific time interval formatting rules // of a given `style` for the given value of measurement `unit`. // // E.g.: // // ```json // { // "past": { // "one": "a second ago", // "other": "{0} seconds ago" // }, // "future": { // "one": "in a second", // "other": "in {0} seconds" // } // } // ``` // var unitMessages = getLocaleData(this.locale)[this.style][unit]; // Special case for "yesterday"/"today"/"tomorrow". if (this.numeric === "auto") { // "yesterday", "the day before yesterday", etc. if (value === -2 || value === -1) { var message = unitMessages["previous".concat(value === -1 ? '' : '-' + Math.abs(value))]; if (message) { return message; } } // "tomorrow", "the day after tomorrow", etc. else if (value === 1 || value === 2) { var _message = unitMessages["next".concat(value === 1 ? '' : '-' + Math.abs(value))]; if (_message) { return _message; } } // "today" else if (value === 0) { if (unitMessages.current) { return unitMessages.current; } } } // Choose either "past" or "future" based on time `value` sign. // If there's only "other" then it's being collapsed. // (the resulting bundle size optimization technique) var pluralizedMessages = unitMessages[isNegative(value) ? "past" : "future"]; // Bundle size optimization technique. if (typeof pluralizedMessages === "string") { return pluralizedMessages; } // Quantify `value`. // There seems to be no such locale in CLDR // for which "plural rules" function is missing. var quantifier = this.pluralRules && this.pluralRules.select(Math.abs(value)) || 'other'; // "other" rule is supposed to be always present. // If only "other" rule is present then "rules" is not an object and is a string. return pluralizedMessages[quantifier] || pluralizedMessages.other; } /** * Formats a number into a string. * Uses `Intl.NumberFormat` when available. * @param {number} number * @return {string} */ }, { key: "formatNumber", value: function formatNumber(number) { return this.numberFormat ? this.numberFormat.format(number) : String(number); } /** * Formats a number into a list of parts. * Uses `Intl.NumberFormat` when available. * @param {number} number * @return {object[]} */ }, { key: "formatNumberToParts", value: function formatNumberToParts(number) { // `Intl.NumberFormat.formatToParts()` is not present, for example, // in Node.js 8.x while `Intl.NumberFormat` itself is present. return this.numberFormat && this.numberFormat.formatToParts ? this.numberFormat.formatToParts(number) : [{ type: "integer", value: this.formatNumber(number) }]; } /** * Returns a new object with properties reflecting the locale and date and time formatting options computed during initialization of this DateTimeFormat object. * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat/resolvedOptions * @return {Object} */ }, { key: "resolvedOptions", value: function resolvedOptions() { return { locale: this.locale, style: this.style, numeric: this.numeric, numberingSystem: this.numberingSystem }; } }]); return RelativeTimeFormat; }(); /** * Returns an array containing those of the provided locales * that are supported in collation without having to fall back * to the runtime's default locale. * @param {(string|string[])} locale - A string with a BCP 47 language tag, or an array of such strings. For the general form of the locales argument, see the Intl page. * @param {Object} [options] - An object that may have the following property: * @param {string} [options.localeMatcher="lookup"] - The locale matching algorithm to use. Possible values are "lookup" and "best fit". Currently only "lookup" is supported. * @return {string[]} An array of strings representing a subset of the given locale tags that are supported in collation without having to fall back to the runtime's default locale. * @example * var locales = ['ban', 'id-u-co-pinyin', 'es-PY'] * var options = { localeMatcher: 'lookup' } * // Returns ["id", "es-PY"] * Intl.RelativeTimeFormat.supportedLocalesOf(locales, options) */ export { RelativeTimeFormat as default }; RelativeTimeFormat.supportedLocalesOf = function (locales) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; // Convert `locales` to an array. if (typeof locales === 'string') { locales = [locales]; } else if (!Array.isArray(locales)) { throw new TypeError('Invalid "locales" argument'); } return locales.filter(function (locale) { return resolveLocale(locale, options); }); }; /** * Adds locale data for a specific locale. * @param {Object} localeData */ RelativeTimeFormat.addLocale = addLocaleData; /** * Sets default locale. * @param {string} locale */ RelativeTimeFormat.setDefaultLocale = setDefaultLocale; /** * Gets default locale. * @return {string} locale */ RelativeTimeFormat.getDefaultLocale = getDefaultLocale; /** * Export `Intl.PluralRules` just in case it's used somewhere else. */ RelativeTimeFormat.PluralRules = PluralRules; // The specification allows units to be in plural form. // Convert plural to singular. // Example: "seconds" -> "second". var UNIT_ERROR = 'Invalid "unit" argument'; function parseUnit(unit) { if (_typeof(unit) === 'symbol') { throw new TypeError(UNIT_ERROR); } if (typeof unit !== 'string') { throw new RangeError("".concat(UNIT_ERROR, ": ").concat(unit)); } if (unit[unit.length - 1] === 's') { unit = unit.slice(0, unit.length - 1); } if (UNITS.indexOf(unit) < 0) { throw new RangeError("".concat(UNIT_ERROR, ": ").concat(unit)); } return unit; } // Converts `value` to a `Number`. // The specification allows value to be a non-number. // For example, "-0" is supposed to be treated as `-0`. // Also checks if `value` is a finite number. var NUMBER_ERROR = 'Invalid "number" argument'; function parseNumber(value) { value = Number(value); if (Number.isFinite) { if (!Number.isFinite(value)) { throw new RangeError("".concat(NUMBER_ERROR, ": ").concat(value)); } } return value; } /** * Tells `0` from `-0`. * https://stackoverflow.com/questions/7223359/are-0-and-0-the-same * @param {number} number * @return {Boolean} * @example * isNegativeZero(0); // false * isNegativeZero(-0); // true */ function isNegativeZero(number) { return 1 / number === -Infinity; } function isNegative(number) { return number < 0 || number === 0 && isNegativeZero(number); } function parseFormatArgs(args) { if (args.length < 2) { throw new TypeError("\"unit\" argument is required"); } return [parseNumber(args[0]), parseUnit(args[1])]; } //# sourceMappingURL=RelativeTimeFormat.js.map