"use strict"; /** * A collection of runtime utility functions * * @remarks * This package should be marked as a dependency for any package that publishes the output of {@link @messageformat/core#compileModule}, * as it may be included in its ES module source output as a dependency. * * For applications that bundle their output using e.g. Webpack this is not necessary. * * The `Messages` accessor class is a completely optional addition. * See also {@link @messageformat/react# | @messageformat/react} for a React-specific solution. * * @packageDocumentation */ Object.defineProperty(exports, "__esModule", { value: true }); /** * Accessor class for compiled message functions generated by * {@link @messageformat/core#compileModule} * * @public * @remarks * ```js * import Messages from '@messageformat/runtime/messages' * ``` * * @example * ```js * // build.js * import { writeFileSync } from 'fs'; * import MessageFormat from '@messageformat/core'; * import compileModule from '@messageformat/core/compile-module' * * const mf = new MessageFormat(['en', 'fi']); * const msgSet = { * en: { * a: 'A {TYPE} example.', * b: 'This has {COUNT, plural, one{one user} other{# users}}.', * c: { * d: 'We have {P, number, percent} code coverage.' * } * }, * fi: { * b: 'Tällä on {COUNT, plural, one{yksi käyttäjä} other{# käyttäjää}}.', * e: 'Minä puhun vain suomea.' * } * }; * writeFileSync('messages.js', compileModule(mf, msgSet)); * ``` * * ```js * // runtime.js * import Messages from '@messageformat/runtime/messages'; * import msgData from './messages'; * * const messages = new Messages(msgData, 'en'); * * messages.hasMessage('a') // true * messages.hasObject('c') // true * messages.get('b', { COUNT: 3 }) // 'This has 3 users.' * messages.get(['c', 'd'], { P: 0.314 }) // 'We have 31% code coverage.' * * messages.get('e') // 'e' * messages.setFallback('en', ['foo', 'fi']) * messages.get('e') // 'Minä puhun vain suomea.' * * messages.locale = 'fi' * messages.hasMessage('a') // false * messages.hasMessage('a', 'en') // true * messages.hasMessage('a', null, true) // true * messages.hasObject('c') // false * messages.get('b', { COUNT: 3 }) // 'Tällä on 3 käyttäjää.' * messages.get('c').d({ P: 0.628 }) // 'We have 63% code coverage.' * ``` */ var Messages = /** @class */ (function () { /** * @param msgData - A map of locale codes to their function objects * @param defaultLocale - If not defined, default and initial locale is the first key of `msgData` */ function Messages(msgData, defaultLocale) { var _this = this; /** @internal */ this._data = {}; /** @internal */ this._fallback = {}; /** @internal */ this._defaultLocale = null; /** @internal */ this._locale = null; Object.keys(msgData).forEach(function (lc) { if (lc !== 'toString') { _this._data[lc] = msgData[lc]; if (defaultLocale === undefined) defaultLocale = lc; } }); this.locale = defaultLocale || null; this._defaultLocale = this.locale; } Object.defineProperty(Messages.prototype, "availableLocales", { /** Read-only list of available locales */ get: function () { return Object.keys(this._data); }, enumerable: false, configurable: true }); Object.defineProperty(Messages.prototype, "locale", { /** * Current locale * * @remarks * One of {@link Messages.availableLocales} or `null`. * Partial matches of language tags are supported, so e.g. with an `en` locale defined, it will be selected by `messages.locale = 'en-US'` and vice versa. */ get: function () { return this._locale; }, set: function (locale) { this._locale = this.resolveLocale(locale); }, enumerable: false, configurable: true }); Object.defineProperty(Messages.prototype, "defaultLocale", { /** * Default fallback locale * * @remarks * One of {@link Messages.availableLocales} or `null`. * Partial matches of language tags are supported, so e.g. with an `en` locale defined, it will be selected by `messages.defaultLocale = 'en-US'` and vice versa. */ get: function () { return this._defaultLocale; }, set: function (locale) { this._defaultLocale = this.resolveLocale(locale); }, enumerable: false, configurable: true }); /** * Add new messages to the accessor; useful if loading data dynamically * * @remarks * The locale code `lc` should be an exact match for the locale being updated, or empty to default to the current locale. * Use {@link Messages.resolveLocale} for resolving partial locale strings. * * If `keypath` is empty, adds or sets the complete message object for the corresponding locale. * If any keys in `keypath` do not exist, a new object will be created at that key. * * @param data - Hierarchical map of keys to functions, or a single message function * @param locale - If empty or undefined, defaults to `this.locale` * @param keypath - The keypath being added */ Messages.prototype.addMessages = function (data, locale, keypath) { var lc = locale || String(this.locale); if (typeof data !== 'function') { data = Object.keys(data).reduce(function (map, key) { if (key !== 'toString') map[key] = data[key]; return map; }, {}); } if (Array.isArray(keypath) && keypath.length > 0) { var parent_1 = this._data[lc]; for (var i = 0; i < keypath.length - 1; ++i) { var key = keypath[i]; if (!parent_1[key]) parent_1[key] = {}; parent_1 = parent_1[key]; } parent_1[keypath[keypath.length - 1]] = data; } else { this._data[lc] = data; } return this; }; /** * Resolve `lc` to the key of an available locale or `null`, allowing for partial matches. * * @remarks * For example, with an `en` locale defined, it will be selected by `messages.defaultLocale = 'en-US'` and vice versa. */ Messages.prototype.resolveLocale = function (locale) { var lc = String(locale); if (this._data[lc]) return locale; if (locale) { while ((lc = lc.replace(/[-_]?[^-_]*$/, ''))) { if (this._data[lc]) return lc; } var ll = this.availableLocales; var re = new RegExp('^' + locale + '[-_]'); for (var i = 0; i < ll.length; ++i) { if (re.test(ll[i])) return ll[i]; } } return null; }; /** * Get the list of fallback locales * * @param locale - If empty or undefined, defaults to `this.locale` */ Messages.prototype.getFallback = function (locale) { var lc = locale || String(this.locale); return (this._fallback[lc] || (lc === this.defaultLocale || !this.defaultLocale ? [] : [this.defaultLocale])); }; /** * Set the fallback locale or locales for `lc` * * @remarks * To disable fallback for the locale, use `setFallback(lc, [])`. * To use the default fallback, use `setFallback(lc, null)`. */ Messages.prototype.setFallback = function (lc, fallback) { this._fallback[lc] = Array.isArray(fallback) ? fallback : null; return this; }; /** * Check if `key` is a message function for the locale * * @remarks * `key` may be a `string` for functions at the root level, or `string[]` for * accessing hierarchical objects. If an exact match is not found and * `fallback` is true, the fallback locales are checked for the first match. * * @param key - The key or keypath being sought * @param locale - If empty or undefined, defaults to `this.locale` * @param fallback - If true, also checks fallback locales */ Messages.prototype.hasMessage = function (key, locale, fallback) { var lc = locale || String(this.locale); var fb = fallback ? this.getFallback(lc) : null; return _has(this._data, lc, key, fb, 'function'); }; /** * Check if `key` is a message object for the locale * * @remarks * `key` may be a `string` for functions at the root level, or `string[]` for * accessing hierarchical objects. If an exact match is not found and * `fallback` is true, the fallback locales are checked for the first match. * * @param key - The key or keypath being sought * @param locale - If empty or undefined, defaults to `this.locale` * @param fallback - If true, also checks fallback locales */ Messages.prototype.hasObject = function (key, locale, fallback) { var lc = locale || String(this.locale); var fb = fallback ? this.getFallback(lc) : null; return _has(this._data, lc, key, fb, 'object'); }; /** * Get the message or object corresponding to `key` * * @remarks * `key` may be a `string` for functions at the root level, or `string[]` for accessing hierarchical objects. * If an exact match is not found, the fallback locales are checked for the first match. * * If `key` maps to a message function, the returned value will be the result of calling it with `props`. * If it maps to an object, the object is returned directly. * If nothing is found, `key` is returned. * * @param key - The key or keypath being sought * @param props - Optional properties passed to the function * @param lc - If empty or undefined, defaults to `this.locale` */ Messages.prototype.get = function (key, props, locale) { var lc = locale || String(this.locale); var msg = _get(this._data[lc], key); if (msg) return typeof msg == 'function' ? msg(props) : msg; var fb = this.getFallback(lc); for (var i = 0; i < fb.length; ++i) { msg = _get(this._data[fb[i]], key); if (msg) return typeof msg == 'function' ? msg(props) : msg; } return key; }; return Messages; }()); exports.default = Messages; function _get(obj, key) { if (!obj) return null; var res = obj; if (Array.isArray(key)) { for (var i = 0; i < key.length; ++i) { if (typeof res !== 'object') return null; res = res[key[i]]; if (!res) return null; } return res; } return typeof res === 'object' ? res[key] : null; } function _has(data, lc, key, fallback, type) { var msg = _get(data[lc], key); if (msg) return typeof msg === type; if (fallback) { for (var i = 0; i < fallback.length; ++i) { msg = _get(data[fallback[i]], key); if (msg) return typeof msg === type; } } return false; }