@lingui/babel-plugin-lingui-macro
Version:
Babel plugin for transforming Lingui Macros
486 lines (476 loc) • 15.6 kB
JavaScript
;
const t = require('@babel/types');
const generateMessageId = require('@lingui/message-utils/generateMessageId');
function _interopNamespaceCompat(e) {
if (e && typeof e === 'object' && 'default' in e) return e;
const n = Object.create(null);
if (e) {
for (const k in e) {
n[k] = e[k];
}
}
n.default = e;
return n;
}
const t__namespace = /*#__PURE__*/_interopNamespaceCompat(t);
const makeCounter = (index = 0) => () => index++;
const EXTRACT_MARK = "i18n";
var MsgDescriptorPropKey = /* @__PURE__ */ ((MsgDescriptorPropKey2) => {
MsgDescriptorPropKey2["id"] = "id";
MsgDescriptorPropKey2["message"] = "message";
MsgDescriptorPropKey2["comment"] = "comment";
MsgDescriptorPropKey2["values"] = "values";
MsgDescriptorPropKey2["components"] = "components";
MsgDescriptorPropKey2["context"] = "context";
return MsgDescriptorPropKey2;
})(MsgDescriptorPropKey || {});
var JsMacroName = /* @__PURE__ */ ((JsMacroName2) => {
JsMacroName2["t"] = "t";
JsMacroName2["plural"] = "plural";
JsMacroName2["select"] = "select";
JsMacroName2["selectOrdinal"] = "selectOrdinal";
JsMacroName2["msg"] = "msg";
JsMacroName2["defineMessage"] = "defineMessage";
JsMacroName2["arg"] = "arg";
JsMacroName2["useLingui"] = "useLingui";
JsMacroName2["ph"] = "ph";
return JsMacroName2;
})(JsMacroName || {});
var JsxMacroName = /* @__PURE__ */ ((JsxMacroName2) => {
JsxMacroName2["Trans"] = "Trans";
JsxMacroName2["Plural"] = "Plural";
JsxMacroName2["Select"] = "Select";
JsxMacroName2["SelectOrdinal"] = "SelectOrdinal";
return JsxMacroName2;
})(JsxMacroName || {});
const metaOptions = ["id", "comment", "props"];
const escapedMetaOptionsRe = new RegExp(`^_(${metaOptions.join("|")})$`);
class ICUMessageFormat {
fromTokens(tokens) {
return (Array.isArray(tokens) ? tokens : [tokens]).map((token) => this.processToken(token)).filter(Boolean).reduce(
(props, message) => ({
...message,
message: props.message + message.message,
values: { ...props.values, ...message.values },
elements: { ...props.elements, ...message.elements }
}),
{
message: "",
values: {},
elements: {}
}
);
}
processToken(token) {
const jsxElements = {};
if (token.type === "text") {
return {
message: token.value
};
} else if (token.type === "arg") {
if (token.value !== void 0 && t.isJSXEmptyExpression(token.value)) {
return null;
}
const values = token.value !== void 0 ? { [token.name]: token.value } : {};
switch (token.format) {
case "plural":
case "select":
case "selectordinal": {
const formatOptions = Object.keys(token.options).filter((key) => token.options[key] != null).map((key) => {
let value = token.options[key];
key = key.replace(escapedMetaOptionsRe, "$1");
if (key === "offset") {
return `offset:${value}`;
}
if (typeof value !== "string") {
const {
message,
values: childValues,
elements: childJsxElements
} = this.fromTokens(value);
Object.assign(values, childValues);
Object.assign(jsxElements, childJsxElements);
value = message;
}
return `${key} {${value}}`;
}).join(" ");
return {
message: `{${token.name}, ${token.format}, ${formatOptions}}`,
values,
elements: jsxElements
};
}
default:
return {
message: token.raw ? `${token.name}` : `{${token.name}}`,
values
};
}
} else if (token.type === "element") {
let message = "";
const elementValues = {};
Object.assign(jsxElements, { [token.name]: token.value });
token.children.forEach((child) => {
const {
message: childMessage,
values: childValues,
elements: childJsxElements
} = this.fromTokens(child);
message += childMessage;
Object.assign(elementValues, childValues);
Object.assign(jsxElements, childJsxElements);
});
return {
message: token.children.length ? `<${token.name}>${message}</${token.name}>` : `<${token.name}/>`,
values: elementValues,
elements: jsxElements
};
}
throw new Error(`Unknown token type ${token.type}`);
}
}
function buildICUFromTokens(tokens) {
const messageFormat = new ICUMessageFormat();
return messageFormat.fromTokens(tokens);
}
function isObjectProperty(node) {
return "type" in node;
}
function createMessageDescriptorFromTokens(tokens, oldLoc, stripNonEssentialProps, stripMessageProp, defaults = {}) {
return createMessageDescriptor(
buildICUFromTokens(tokens),
oldLoc,
stripNonEssentialProps,
stripMessageProp,
defaults
);
}
function createMessageDescriptor(result, oldLoc, stripNonEssentialProps, stripMessageProp, defaults = {}) {
const { message, values, elements } = result;
const properties = [];
properties.push(
defaults.id ? isObjectProperty(defaults.id) ? defaults.id : createStringObjectProperty(
MsgDescriptorPropKey.id,
defaults.id.text,
defaults.id.loc
) : createIdProperty(
message,
defaults.context ? isObjectProperty(defaults.context) ? getTextFromExpression(defaults.context.value) : defaults.context.text : null
)
);
if (!stripMessageProp) {
if (message) {
properties.push(
createStringObjectProperty(MsgDescriptorPropKey.message, message)
);
}
}
if (!stripNonEssentialProps) {
if (defaults.comment) {
properties.push(
isObjectProperty(defaults.comment) ? defaults.comment : createStringObjectProperty(
MsgDescriptorPropKey.comment,
defaults.comment.text,
defaults.comment.loc
)
);
}
if (defaults.context) {
properties.push(
isObjectProperty(defaults.context) ? defaults.context : createStringObjectProperty(
MsgDescriptorPropKey.context,
defaults.context.text,
defaults.context.loc
)
);
}
}
if (values) {
properties.push(createValuesProperty(MsgDescriptorPropKey.values, values));
}
if (elements) {
properties.push(
createValuesProperty(MsgDescriptorPropKey.components, elements)
);
}
return createMessageDescriptorObjectExpression(
properties,
// preserve line numbers for extractor
oldLoc
);
}
function createIdProperty(message, context) {
return createStringObjectProperty(
MsgDescriptorPropKey.id,
generateMessageId.generateMessageId(message, context)
);
}
function createValuesProperty(key, values) {
const valuesObject = Object.keys(values).map(
(key2) => t__namespace.objectProperty(t__namespace.identifier(key2), values[key2])
);
if (!valuesObject.length)
return;
return t__namespace.objectProperty(
t__namespace.identifier(key),
t__namespace.objectExpression(valuesObject)
);
}
function createStringObjectProperty(key, value, oldLoc) {
const property = t__namespace.objectProperty(
t__namespace.identifier(key),
t__namespace.stringLiteral(value)
);
if (oldLoc) {
property.loc = oldLoc;
}
return property;
}
function getTextFromExpression(exp) {
if (t__namespace.isStringLiteral(exp)) {
return exp.value;
}
if (t__namespace.isTemplateLiteral(exp)) {
if (exp?.quasis.length === 1) {
return exp.quasis[0]?.value?.cooked;
}
}
}
function createMessageDescriptorObjectExpression(properties, oldLoc) {
const newDescriptor = t__namespace.objectExpression(properties.filter(Boolean));
t__namespace.addComment(newDescriptor, "leading", EXTRACT_MARK);
if (oldLoc) {
newDescriptor.loc = oldLoc;
}
return newDescriptor;
}
function createMacroJsContext(isLinguiIdentifier2, stripNonEssentialProps, stripMessageProp) {
return {
getExpressionIndex: makeCounter(),
isLinguiIdentifier: isLinguiIdentifier2,
stripNonEssentialProps,
stripMessageProp
};
}
function processDescriptor(descriptor, ctx) {
const messageProperty = getObjectPropertyByKey(
descriptor,
MsgDescriptorPropKey.message
);
const idProperty = getObjectPropertyByKey(descriptor, MsgDescriptorPropKey.id);
const contextProperty = getObjectPropertyByKey(
descriptor,
MsgDescriptorPropKey.context
);
const commentProperty = getObjectPropertyByKey(
descriptor,
MsgDescriptorPropKey.comment
);
let tokens = [];
if (messageProperty) {
const messageValue = messageProperty.value;
tokens = t__namespace.isTemplateLiteral(messageValue) ? tokenizeTemplateLiteral(messageValue, ctx) : tokenizeNode(messageValue, true, ctx);
}
return createMessageDescriptorFromTokens(
tokens,
descriptor.loc,
ctx.stripNonEssentialProps,
ctx.stripMessageProp,
{
id: idProperty,
context: contextProperty,
comment: commentProperty
}
);
}
function tokenizeNode(node, ignoreExpression = false, ctx) {
if (isI18nMethod(node, ctx)) {
return tokenizeTemplateLiteral(node, ctx);
}
if (t__namespace.isCallExpression(node) && isArgDecorator(node, ctx)) {
return [tokenizeArg(node, ctx)];
}
const choiceMethod = isChoiceMethod(node, ctx);
if (choiceMethod) {
return [tokenizeChoiceComponent(node, choiceMethod, ctx)];
}
if (t__namespace.isStringLiteral(node)) {
return [
{
type: "text",
value: node.value
}
];
}
if (!ignoreExpression) {
return [tokenizeExpression(node, ctx)];
}
}
function tokenizeTemplateLiteral(node, ctx) {
const tpl = t__namespace.isTaggedTemplateExpression(node) ? node.quasi : node;
const expressions = tpl.expressions;
return tpl.quasis.flatMap((text, i) => {
const value = text.value.cooked;
let argTokens = [];
const currExp = expressions[i];
if (currExp) {
argTokens = t__namespace.isCallExpression(currExp) ? tokenizeNode(currExp, false, ctx) : [tokenizeExpression(currExp, ctx)];
}
const textToken = {
type: "text",
value
};
return [...value ? [textToken] : [], ...argTokens];
});
}
function tokenizeChoiceComponent(node, componentName, ctx) {
const format = componentName.toLowerCase();
const token = {
...tokenizeExpression(node.arguments[0], ctx),
format,
options: {
offset: void 0
}
};
const props = node.arguments[1].properties;
for (const attr of props) {
if (!t__namespace.isObjectProperty(attr)) {
throw new Error("Expected an ObjectProperty");
}
const key = attr.key;
const attrValue = attr.value;
const name = t__namespace.isNumericLiteral(key) ? `=${key.value}` : key.name || key.value;
if (format !== "select" && name === "offset") {
token.options.offset = attrValue.value;
} else {
let value;
if (t__namespace.isTemplateLiteral(attrValue)) {
value = tokenizeTemplateLiteral(attrValue, ctx);
} else if (t__namespace.isCallExpression(attrValue)) {
value = tokenizeNode(attrValue, false, ctx);
} else if (t__namespace.isStringLiteral(attrValue)) {
value = attrValue.value;
} else if (t__namespace.isExpression(attrValue)) {
value = tokenizeExpression(attrValue, ctx);
} else {
value = attrValue.value;
}
token.options[name] = value;
}
}
return token;
}
function tokenizeLabeledExpression(node, ctx) {
if (node.properties.length > 1) {
throw new Error(
"Incorrect usage, expected exactly one property as `{variableName: variableValue}`"
);
}
const property = node.properties[0];
if (t__namespace.isProperty(property) && t__namespace.isIdentifier(property.key)) {
return {
type: "arg",
name: expressionToArgument(property.key, ctx),
value: property.value
};
} else {
throw new Error(
"Incorrect usage of a labeled expression. Expected to have one object property with property key as identifier"
);
}
}
function tokenizeExpression(node, ctx) {
if (t__namespace.isTSAsExpression(node)) {
return tokenizeExpression(node.expression, ctx);
}
if (t__namespace.isObjectExpression(node)) {
return tokenizeLabeledExpression(node, ctx);
} else if (t__namespace.isCallExpression(node) && isLinguiIdentifier(node.callee, JsMacroName.ph, ctx) && node.arguments.length > 0) {
if (!t__namespace.isObjectExpression(node.arguments[0])) {
throw new Error(
"Incorrect usage of `ph` macro. First argument should be an ObjectExpression"
);
}
return tokenizeLabeledExpression(node.arguments[0], ctx);
}
return {
type: "arg",
name: expressionToArgument(node, ctx),
value: node
};
}
function tokenizeArg(node, ctx) {
const arg = node.arguments[0];
return {
type: "arg",
name: expressionToArgument(arg, ctx),
raw: true,
value: arg
};
}
function expressionToArgument(exp, ctx) {
if (t__namespace.isIdentifier(exp)) {
return exp.name;
}
return String(ctx.getExpressionIndex());
}
function isArgDecorator(node, ctx) {
return t__namespace.isCallExpression(node) && isLinguiIdentifier(node.callee, JsMacroName.arg, ctx);
}
function isDefineMessage(node, ctx) {
return isLinguiIdentifier(node, JsMacroName.defineMessage, ctx) || isLinguiIdentifier(node, JsMacroName.msg, ctx);
}
function isI18nMethod(node, ctx) {
if (!t__namespace.isTaggedTemplateExpression(node)) {
return;
}
const tag = node.tag;
return isLinguiIdentifier(tag, JsMacroName.t, ctx) || t__namespace.isCallExpression(tag) && isLinguiIdentifier(tag.callee, JsMacroName.t, ctx);
}
function isLinguiIdentifier(node, name, ctx) {
if (!t__namespace.isIdentifier(node)) {
return false;
}
return ctx.isLinguiIdentifier(node, name);
}
function isChoiceMethod(node, ctx) {
if (!t__namespace.isCallExpression(node)) {
return;
}
if (isLinguiIdentifier(node.callee, JsMacroName.plural, ctx)) {
return JsMacroName.plural;
}
if (isLinguiIdentifier(node.callee, JsMacroName.select, ctx)) {
return JsMacroName.select;
}
if (isLinguiIdentifier(node.callee, JsMacroName.selectOrdinal, ctx)) {
return JsMacroName.selectOrdinal;
}
}
function getObjectPropertyByKey(objectExp, key) {
return objectExp.properties.find(
(property) => t__namespace.isObjectProperty(property) && t__namespace.isIdentifier(property.key, {
name: key
})
);
}
exports.ICUMessageFormat = ICUMessageFormat;
exports.JsMacroName = JsMacroName;
exports.JsxMacroName = JsxMacroName;
exports.MsgDescriptorPropKey = MsgDescriptorPropKey;
exports.createMacroJsContext = createMacroJsContext;
exports.createMessageDescriptor = createMessageDescriptor;
exports.createMessageDescriptorFromTokens = createMessageDescriptorFromTokens;
exports.createStringObjectProperty = createStringObjectProperty;
exports.isArgDecorator = isArgDecorator;
exports.isChoiceMethod = isChoiceMethod;
exports.isDefineMessage = isDefineMessage;
exports.isI18nMethod = isI18nMethod;
exports.isLinguiIdentifier = isLinguiIdentifier;
exports.makeCounter = makeCounter;
exports.processDescriptor = processDescriptor;
exports.tokenizeArg = tokenizeArg;
exports.tokenizeChoiceComponent = tokenizeChoiceComponent;
exports.tokenizeExpression = tokenizeExpression;
exports.tokenizeNode = tokenizeNode;
exports.tokenizeTemplateLiteral = tokenizeTemplateLiteral;