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
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)); }, []);
|
|
}
|