var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __spreadArrays = (this && this.__spreadArrays) || function () { for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; for (var r = Array(s), k = 0, i = 0; i < il; i++) for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) r[k] = a[j]; return r; }; import { convertNumberSkeletonToNumberFormatOptions, isArgumentElement, isDateElement, isDateTimeSkeleton, isLiteralElement, isNumberElement, isNumberSkeleton, isPluralElement, isPoundElement, isSelectElement, isTimeElement, parseDateTimeSkeleton, } from 'intl-messageformat-parser'; var FormatError = /** @class */ (function (_super) { __extends(FormatError, _super); function FormatError(msg, variableId) { var _this = _super.call(this, msg) || this; _this.variableId = variableId; return _this; } return FormatError; }(Error)); function mergeLiteral(parts) { if (parts.length < 2) { return parts; } return parts.reduce(function (all, part) { var lastPart = all[all.length - 1]; if (!lastPart || lastPart.type !== 0 /* literal */ || part.type !== 0 /* literal */) { all.push(part); } else { lastPart.value += part.value; } return all; }, []); } // TODO(skeleton): add skeleton support export function formatToParts(els, locales, formatters, formats, values, currentPluralValue, // For debugging originalMessage) { // Hot path for straight simple msg translations if (els.length === 1 && isLiteralElement(els[0])) { return [ { type: 0 /* literal */, value: els[0].value, }, ]; } var result = []; for (var _i = 0, els_1 = els; _i < els_1.length; _i++) { var el = els_1[_i]; // Exit early for string parts. if (isLiteralElement(el)) { result.push({ type: 0 /* literal */, value: el.value, }); continue; } // TODO: should this part be literal type? // Replace `#` in plural rules with the actual numeric value. if (isPoundElement(el)) { if (typeof currentPluralValue === 'number') { result.push({ type: 0 /* literal */, value: formatters.getNumberFormat(locales).format(currentPluralValue), }); } continue; } var varName = el.value; // Enforce that all required values are provided by the caller. if (!(values && varName in values)) { throw new FormatError("The intl string context variable \"" + varName + "\" was not provided to the string \"" + originalMessage + "\""); } var value = values[varName]; if (isArgumentElement(el)) { if (!value || typeof value === 'string' || typeof value === 'number') { value = typeof value === 'string' || typeof value === 'number' ? String(value) : ''; } result.push({ type: 1 /* argument */, value: value, }); continue; } // Recursively format plural and select parts' option — which can be a // nested pattern structure. The choosing of the option to use is // abstracted-by and delegated-to the part helper object. if (isDateElement(el)) { var style = typeof el.style === 'string' ? formats.date[el.style] : undefined; result.push({ type: 0 /* literal */, value: formatters .getDateTimeFormat(locales, style) .format(value), }); continue; } if (isTimeElement(el)) { var style = typeof el.style === 'string' ? formats.time[el.style] : isDateTimeSkeleton(el.style) ? parseDateTimeSkeleton(el.style.pattern) : undefined; result.push({ type: 0 /* literal */, value: formatters .getDateTimeFormat(locales, style) .format(value), }); continue; } if (isNumberElement(el)) { var style = typeof el.style === 'string' ? formats.number[el.style] : isNumberSkeleton(el.style) ? convertNumberSkeletonToNumberFormatOptions(el.style.tokens) : undefined; result.push({ type: 0 /* literal */, value: formatters .getNumberFormat(locales, style) .format(value), }); continue; } if (isSelectElement(el)) { var opt = el.options[value] || el.options.other; if (!opt) { throw new RangeError("Invalid values for \"" + el.value + "\": \"" + value + "\". Options are \"" + Object.keys(el.options).join('", "') + "\""); } result.push.apply(result, formatToParts(opt.value, locales, formatters, formats, values)); continue; } if (isPluralElement(el)) { var opt = el.options["=" + value]; if (!opt) { if (!Intl.PluralRules) { throw new FormatError("Intl.PluralRules is not available in this environment.\nTry polyfilling it using \"@formatjs/intl-pluralrules\"\n"); } var rule = formatters .getPluralRules(locales, { type: el.pluralType }) .select(value - (el.offset || 0)); opt = el.options[rule] || el.options.other; } if (!opt) { throw new RangeError("Invalid values for \"" + el.value + "\": \"" + value + "\". Options are \"" + Object.keys(el.options).join('", "') + "\""); } result.push.apply(result, formatToParts(opt.value, locales, formatters, formats, values, value - (el.offset || 0))); continue; } } return mergeLiteral(result); } export function formatToString(els, locales, formatters, formats, values, // For debugging originalMessage) { var parts = formatToParts(els, locales, formatters, formats, values, undefined, originalMessage); // Hot path for straight simple msg translations if (parts.length === 1) { return parts[0].value; } return parts.reduce(function (all, part) { return (all += part.value); }, ''); } // Singleton var domParser; var TOKEN_DELIMITER = '@@'; var TOKEN_REGEX = /@@(\d+_\d+)@@/g; var counter = 0; function generateId() { return Date.now() + "_" + ++counter; } function restoreRichPlaceholderMessage(text, objectParts) { return text .split(TOKEN_REGEX) .filter(Boolean) .map(function (c) { return (objectParts[c] != null ? objectParts[c] : c); }) .reduce(function (all, c) { if (!all.length) { all.push(c); } else if (typeof c === 'string' && typeof all[all.length - 1] === 'string') { all[all.length - 1] += c; } else { all.push(c); } return all; }, []); } /** * Not exhaustive, just for sanity check */ var SIMPLE_XML_REGEX = /(<([0-9a-zA-Z-_]*?)>(.*?)<\/([0-9a-zA-Z-_]*?)>)|(<[0-9a-zA-Z-_]*?\/>)/; var TEMPLATE_ID = Date.now() + '@@'; var VOID_ELEMENTS = [ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr', ]; function formatHTMLElement(el, objectParts, values) { var tagName = el.tagName; var outerHTML = el.outerHTML, textContent = el.textContent, childNodes = el.childNodes; // Regular text if (!tagName) { return restoreRichPlaceholderMessage(textContent || '', objectParts); } tagName = tagName.toLowerCase(); var isVoidElement = ~VOID_ELEMENTS.indexOf(tagName); var formatFnOrValue = values[tagName]; if (formatFnOrValue && isVoidElement) { throw new FormatError(tagName + " is a self-closing tag and can not be used, please use another tag name."); } if (!childNodes.length) { return [outerHTML]; } var chunks = Array.prototype.slice.call(childNodes).reduce(function (all, child) { return all.concat(formatHTMLElement(child, objectParts, values)); }, []); // Legacy HTML if (!formatFnOrValue) { return __spreadArrays(["<" + tagName + ">"], chunks, [""]); } // HTML Tag replacement if (typeof formatFnOrValue === 'function') { return [formatFnOrValue.apply(void 0, chunks)]; } return [formatFnOrValue]; } export function formatHTMLMessage(els, locales, formatters, formats, values, // For debugging originalMessage) { var parts = formatToParts(els, locales, formatters, formats, values, undefined, originalMessage); var objectParts = {}; var formattedMessage = parts.reduce(function (all, part) { if (part.type === 0 /* literal */) { return (all += part.value); } var id = generateId(); objectParts[id] = part.value; return (all += "" + TOKEN_DELIMITER + id + TOKEN_DELIMITER); }, ''); // Not designed to filter out aggressively if (!SIMPLE_XML_REGEX.test(formattedMessage)) { return restoreRichPlaceholderMessage(formattedMessage, objectParts); } if (!values) { throw new FormatError('Message has placeholders but no values was given'); } if (typeof DOMParser === 'undefined') { throw new FormatError('Cannot format XML message without DOMParser'); } if (!domParser) { domParser = new DOMParser(); } var content = domParser .parseFromString("" + formattedMessage + "", 'text/html') .getElementById(TEMPLATE_ID); if (!content) { throw new FormatError("Malformed HTML message " + formattedMessage); } var tagsToFormat = Object.keys(values).filter(function (varName) { return !!content.getElementsByTagName(varName).length; }); // No tags to format if (!tagsToFormat.length) { return restoreRichPlaceholderMessage(formattedMessage, objectParts); } var caseSensitiveTags = tagsToFormat.filter(function (tagName) { return tagName !== tagName.toLowerCase(); }); if (caseSensitiveTags.length) { throw new FormatError("HTML tag must be lowercased but the following tags are not: " + caseSensitiveTags.join(', ')); } // We're doing this since top node is `` which does not have a formatter return Array.prototype.slice .call(content.childNodes) .reduce(function (all, child) { return all.concat(formatHTMLElement(child, objectParts, values)); }, []); }