You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

304 lines
12 KiB

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, ["</" + tagName + ">"]);
}
// 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("<formatted-message id=\"" + TEMPLATE_ID + "\">" + formattedMessage + "</formatted-message>", '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 `<formatted-message/>` which does not have a formatter
return Array.prototype.slice
.call(content.childNodes)
.reduce(function (all, child) { return all.concat(formatHTMLElement(child, objectParts, values)); }, []);
}