@hi18n/core
Version:
Message internationalization meets immutability and type-safety - core runtime
208 lines (166 loc) • 7.29 kB
JavaScript
import { defaultErrorHandler } from "./error-handling.mjs";
import { ArgumentTypeError, MessageEvaluationError, MissingArgumentError } from "./errors.mjs";
export function evaluateMessage(msg, options, numberValue) {
var _msg$argType;
if (typeof msg === "string") {
return msg;
} else if (Array.isArray(msg)) {
const reduced = reduceSubmessages(msg.map(part => evaluateMessage(part, options, numberValue)));
if (typeof reduced === "string") {
return reduced;
}
const {
collect
} = options;
if (!collect) throw new MessageEvaluationError("Invalid message: not a default-collectable message");
return collect(reduced);
} else if (msg.type === "Var") {
var _options$params;
const value = ((_options$params = options.params) !== null && _options$params !== void 0 ? _options$params : {})[msg.name];
if (value === undefined) throw new MissingArgumentError({
argName: msg.name
});
switch (msg.argType) {
case undefined:
if (typeof value !== "string") throw new MessageEvaluationError( // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
"Invalid argument ".concat(msg.name, ": expected string, got ").concat(value));
return value;
case "number":
{
if (typeof value !== "number" && typeof value !== "bigint") {
throw new ArgumentTypeError({
argName: msg.name,
expectedType: "number",
got: value
});
}
const formatOptions = {};
let modifiedValue = value;
switch (msg.argStyle) {
case "integer":
// https://github.com/openjdk/jdk/blob/739769c8fc4b496f08a92225a12d07414537b6c0/src/java.base/share/classes/sun/util/locale/provider/NumberFormatProviderImpl.java#L196-L198
formatOptions.maximumFractionDigits = 0;
if (typeof value === "number") modifiedValue = Math.round(value);
break;
case "currency":
// Need to provide an appropriate currency from somewhere
throw new Error("Unimplemented: argStyle=currency");
case "percent":
formatOptions.style = msg.argStyle;
break;
}
if ((typeof Intl === "undefined" || !Intl.NumberFormat) && (msg.argStyle === undefined || msg.argStyle === "integer")) {
var _options$handleError;
((_options$handleError = options.handleError) !== null && _options$handleError !== void 0 ? _options$handleError : defaultErrorHandler)(new Error("Missing Intl.NumberFormat"), "warn");
return "".concat(modifiedValue);
} // TODO: allow injecting polyfill
return new Intl.NumberFormat(options.locale, formatOptions).format(modifiedValue);
}
case "date":
case "time":
{
if (!isDateLike(value)) {
throw new ArgumentTypeError({
argName: msg.name,
expectedType: "Date",
got: value
});
}
if (typeof options.timeZone !== "string") {
throw new MissingArgumentError({
argName: "timeZone"
});
}
const formatOptions = {
timeZone: options.timeZone
};
if (typeof msg.argStyle === "object") {
// parsed object from the skeleton
Object.assign(formatOptions, msg.argStyle);
} else {
if (msg.argType === "date") {
var _msg$argStyle;
formatOptions.dateStyle = (_msg$argStyle = msg.argStyle) !== null && _msg$argStyle !== void 0 ? _msg$argStyle : "medium";
} else {
var _msg$argStyle2;
formatOptions.timeStyle = (_msg$argStyle2 = msg.argStyle) !== null && _msg$argStyle2 !== void 0 ? _msg$argStyle2 : "medium";
}
} // TODO: allow injecting polyfill
return new Intl.DateTimeFormat(options.locale, formatOptions).format(value);
}
default:
throw new Error("Unimplemented: argType=".concat((_msg$argType = msg.argType) !== null && _msg$argType !== void 0 ? _msg$argType : "string"));
}
} else if (msg.type === "Plural") {
var _options$params2;
const value = ((_options$params2 = options.params) !== null && _options$params2 !== void 0 ? _options$params2 : {})[msg.name];
let relativeValue;
if (value === undefined) {
throw new MissingArgumentError({
argName: msg.name
});
}
if (typeof value === "number") {
var _msg$offset;
relativeValue = value - ((_msg$offset = msg.offset) !== null && _msg$offset !== void 0 ? _msg$offset : 0);
} else if (typeof value === "bigint") {
var _msg$offset2;
relativeValue = value - BigInt((_msg$offset2 = msg.offset) !== null && _msg$offset2 !== void 0 ? _msg$offset2 : 0);
} else {
throw new ArgumentTypeError({
argName: msg.name,
expectedType: "number",
got: value
});
}
const rule = (() => {
if (typeof Intl === "undefined" || !Intl.PluralRules) {
var _options$handleError2;
((_options$handleError2 = options.handleError) !== null && _options$handleError2 !== void 0 ? _options$handleError2 : defaultErrorHandler)(new Error("Missing Intl.PluralRules"), "warn");
return "other";
} // TODO: allow injecting polyfill
const pluralRules = new Intl.PluralRules(options.locale);
return pluralRules.select(Number(relativeValue));
})();
for (const branch of msg.branches) {
if (branch.selector === Number(value) || branch.selector === rule || branch.selector === "other") {
return evaluateMessage(branch.message, options, relativeValue);
}
}
throw new MessageEvaluationError("Non-exhaustive plural branches for ".concat(value));
} else if (msg.type === "Number" && numberValue !== undefined) {
// TODO: allow injecting polyfill
return new Intl.NumberFormat(options.locale).format(numberValue);
} else if (msg.type === "Element") {
var _options$params3;
const {
wrap
} = options;
if (!wrap) throw new MessageEvaluationError("Invalid message: unexpected elementArg");
const value = ((_options$params3 = options.params) !== null && _options$params3 !== void 0 ? _options$params3 : {})[msg.name];
if (value === undefined) throw new MissingArgumentError({
argName: msg.name
});
return wrap(value, msg.message !== undefined ? evaluateMessage(msg.message, options, numberValue) : undefined);
}
throw new MessageEvaluationError("Invalid message");
}
function reduceSubmessages(submessages) {
if (submessages.every(x => typeof x === "string")) {
return submessages.join("");
}
const reduced = [];
for (const x of submessages) {
if (x === "") continue;
if (typeof x === "string" && typeof reduced[reduced.length - 1] === "string") {
reduced[reduced.length - 1] += x;
} else {
reduced.push(x);
}
}
return reduced;
}
function isDateLike(obj) {
return typeof obj === "object" && typeof obj.getFullYear === "function";
}
//# sourceMappingURL=msgfmt-eval.mjs.map