ts-transform-react-intl
Version:
Extracts string messages for translation from modules that use React Intl.
125 lines (124 loc) • 4.49 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
const ts = require("typescript");
const macro_1 = require("./macro");
const loader_utils_1 = require("loader-utils");
const DEFAULT_OPTS = {
macroImportName: macro_1._.name,
macroModuleSpecifier: require("../package.json").name,
baseUrl: "",
onMsgExtracted: () => undefined
};
/**
* Trim the trailing & beginning ': 'asd' -> asd
*
* @param {string} txt text
* @returns trimmed string
*/
function trimSingleQuote(txt) {
return txt.replace(/["']/g, "");
}
/**
* Extract the object literal in TS AST into MessageDescriptor
*
* @param {ts.ObjectLiteralExpression} node object literal
* @returns {Messages}
*/
function extractMessageDescriptor(node, sf, interpolateNameFnOrPattern, baseUrl = "") {
const msg = {
id: "",
description: "",
defaultMessage: ""
};
// Go thru each property
ts.forEachChild(node, (p) => {
switch (p.name.getText(sf)) {
case "id":
const id = trimSingleQuote(p.initializer.getText(sf));
msg.id = id;
break;
case "description":
msg.description = trimSingleQuote(p.initializer.getText(sf));
break;
case "defaultMessage":
msg.defaultMessage = trimSingleQuote(p.initializer.getText(sf));
break;
}
});
switch (typeof interpolateNameFnOrPattern) {
case "string":
msg.id = loader_utils_1.interpolateName({ sourcePath: sf.fileName.replace(baseUrl, "") }, interpolateNameFnOrPattern, { content: JSON.stringify(msg) });
break;
case "function":
msg.id = interpolateNameFnOrPattern(sf.fileName, msg);
break;
}
return msg;
}
function findMacroHook(node, sf, macroImportName, macroModuleSpecifier) {
let hook = "";
if (ts.isImportDeclaration(node) &&
trimSingleQuote(node.moduleSpecifier.getText(sf)) === macroModuleSpecifier) {
const { namedBindings } = node.importClause;
// Search through named bindings to find our macro
ts.forEachChild(namedBindings, p => {
if (!ts.isImportSpecifier(p)) {
return;
}
// This is a alias, like `import {_ as foo}`
if (p.propertyName) {
if (p.propertyName.getText(sf) === macroImportName) {
hook = p.name.getText(sf);
}
}
else if (p.name.getText(sf) === macroImportName) {
hook = p.name.getText(sf);
}
});
}
return hook;
}
function isMacroExpression(node, sf, hook) {
// Make sure it's a function call
return (hook &&
ts.isCallExpression(node) &&
// Make sure the fn name matches our hook
node.expression.getText(sf) === hook &&
// Make sure we got only 1 arg
node.arguments.length === 1 &&
node.arguments[0] &&
// Make sure it's a object literal
ts.isObjectLiteralExpression(node.arguments[0]));
}
function transform(opts) {
opts = Object.assign({}, DEFAULT_OPTS, opts);
return (ctx) => {
function getVisitor(sf) {
let hook = "";
const visitor = (node) => {
if (!hook) {
hook = findMacroHook(node, sf, opts.macroImportName, opts.macroModuleSpecifier);
if (hook) {
return null;
}
}
// If it's not our macro, skip
if (!isMacroExpression(node, sf, hook)) {
return ts.visitEachChild(node, visitor, ctx);
}
const msgObjLiteral = node.arguments[0];
const msg = extractMessageDescriptor(msgObjLiteral, sf, opts.interpolateName, opts.baseUrl);
if (typeof opts.onMsgExtracted === "function") {
opts.onMsgExtracted(msg.id, msg, sf.fileName);
}
return ts.createObjectLiteral([
ts.createPropertyAssignment("id", ts.createStringLiteral(msg.id)),
ts.createPropertyAssignment("defaultMessage", ts.createStringLiteral(msg.defaultMessage))
]);
};
return visitor;
}
return (sf) => ts.visitNode(sf, getVisitor(sf));
};
}
exports.transform = transform;
;