next-intl-scanner
Version:
A tool to extract and manage translations from Next.js projects using next-intl
370 lines (369 loc) • 20.3 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractTranslationsFromSource = void 0;
const parser_1 = require("@babel/parser");
const traverse_1 = __importDefault(require("@babel/traverse"));
const t = __importStar(require("@babel/types"));
// Helper function to collect enum declarations
function collectEnums(ast) {
const enumMap = new Map();
(0, traverse_1.default)(ast, {
TSEnumDeclaration(path) {
const enumName = path.node.id.name;
const enumMembers = new Map();
path.node.members.forEach((member) => {
if (t.isTSEnumMember(member) && t.isIdentifier(member.id)) {
const memberName = member.id.name;
let memberValue = memberName; // Default to member name
if (member.initializer) {
if (t.isStringLiteral(member.initializer)) {
memberValue = member.initializer.value;
}
else if (t.isNumericLiteral(member.initializer)) {
memberValue = member.initializer.value.toString();
}
}
enumMembers.set(memberName, memberValue);
}
});
enumMap.set(enumName, enumMembers);
},
EnumDeclaration(path) {
var _a;
const enumName = path.node.id.name;
const enumMembers = new Map();
(_a = path.node.members) === null || _a === void 0 ? void 0 : _a.forEach((member) => {
if (t.isEnumMember(member) && t.isIdentifier(member.id)) {
const memberName = member.id.name;
let memberValue = memberName; // Default to member name
if (member.initializer) {
const initializer = member.initializer;
if (t.isStringLiteral(initializer)) {
memberValue = initializer.value;
}
else if (t.isNumericLiteral(initializer)) {
memberValue = initializer.value.toString();
}
}
enumMembers.set(memberName, memberValue);
}
});
enumMap.set(enumName, enumMembers);
},
});
return enumMap;
}
// Helper function to resolve enum references
function resolveEnumValue(node, enumMap) {
if (t.isMemberExpression(node)) {
const object = node.object;
const property = node.property;
if (t.isIdentifier(object) && t.isIdentifier(property)) {
const enumName = object.name;
const memberName = property.name;
const enumMembers = enumMap.get(enumName);
if (enumMembers && enumMembers.has(memberName)) {
return enumMembers.get(memberName) || null;
}
}
}
return null;
}
function extractTranslationsFromSource(source, file, config) {
const namespaceMap = new Map();
const translations = [];
let ast;
try {
ast = (0, parser_1.parse)(source, {
sourceType: "module",
plugins: [
"jsx",
"typescript",
"topLevelAwait",
"asyncGenerators",
"classProperties",
"decorators-legacy",
],
allowAwaitOutsideFunction: true,
errorRecovery: true, // Add error recovery
});
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.warn(`Failed to parse file ${file}: ${errorMessage}`);
return translations; // Return empty array instead of crashing
}
// Collect all enum declarations first
const enumMap = collectEnums(ast);
// Type assertion to handle version compatibility between @babel/parser and @babel/traverse
(0, traverse_1.default)(ast, {
VariableDeclarator(path) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
const id = path.node.id;
const init = path.node.init;
// const t = useTranslations('namespace')
if (t.isIdentifier(id) &&
t.isCallExpression(init) &&
t.isIdentifier(init.callee, { name: "useTranslations" })) {
const ns = init.arguments[0];
const namespace = t.isStringLiteral(ns) || t.isTemplateLiteral(ns)
? ns.value ||
((_c = (_b = (_a = ns.quasis) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.value) === null || _c === void 0 ? void 0 : _c.raw) ||
""
: "";
namespaceMap.set(id.name, namespace || "");
}
// const t = await getTranslations('en', 'namespace')
if (t.isIdentifier(id) &&
t.isAwaitExpression(init) &&
t.isCallExpression(init.argument) &&
t.isIdentifier(init.argument.callee, { name: "getTranslations" })) {
const args = init.argument.arguments;
const nsArg = args[1];
const namespace = t.isStringLiteral(nsArg) || t.isTemplateLiteral(nsArg)
? nsArg.value ||
((_f = (_e = (_d = nsArg.quasis) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.value) === null || _f === void 0 ? void 0 : _f.raw) ||
""
: "";
namespaceMap.set(id.name, namespace || "");
}
// const t = useNextTranslations('namespace')
if (t.isIdentifier(id) &&
t.isCallExpression(init) &&
t.isIdentifier(init.callee, { name: "useNextTranslations" })) {
const ns = init.arguments[0];
const namespace = t.isStringLiteral(ns) || t.isTemplateLiteral(ns)
? ns.value ||
((_j = (_h = (_g = ns.quasis) === null || _g === void 0 ? void 0 : _g[0]) === null || _h === void 0 ? void 0 : _h.value) === null || _j === void 0 ? void 0 : _j.raw) ||
""
: "";
namespaceMap.set(id.name, namespace || "");
}
},
CallExpression(path) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
const callee = path.node.callee;
const args = path.node.arguments;
// Match: t('key', ..., 'message') or any mapped variable like paymentT('key')
if (t.isIdentifier(callee)) {
const variableName = callee.name;
const namespace = namespaceMap.get(variableName);
if (namespace !== undefined) {
const keyArg = args[0];
const msgArg = args[2]; // optional
// Handle string literals and template literals
if (t.isStringLiteral(keyArg) || t.isTemplateLiteral(keyArg)) {
const key = t.isStringLiteral(keyArg)
? keyArg.value
: ((_c = (_b = (_a = keyArg.quasis) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.value) === null || _c === void 0 ? void 0 : _c.raw) || "";
// Get the message from the third argument if it exists and is a string
let message = key; // Default to key
if (args.length >= 3 && msgArg) {
if (t.isStringLiteral(msgArg)) {
message = msgArg.value;
}
else if (t.isTemplateLiteral(msgArg)) {
message = ((_f = (_e = (_d = msgArg.quasis) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.value) === null || _f === void 0 ? void 0 : _f.raw) || key;
}
}
// For keys with dots, use the message if available, otherwise use the key with underscores
// to avoid breaking next-intl namespace resolution
const safeValue = key.includes(".")
? message !== key
? message
: key.replace(/\./g, "_")
: message;
translations.push({
nameSpace: namespace,
messageKey: key,
string: safeValue,
file,
});
}
// Handle enum references
const resolvedEnumValue = resolveEnumValue(keyArg, enumMap);
if (resolvedEnumValue) {
translations.push({
nameSpace: namespace,
messageKey: resolvedEnumValue,
string: resolvedEnumValue,
file,
});
}
}
}
// Match: tVar.t('key')
if (t.isMemberExpression(callee) &&
t.isIdentifier(callee.property, { name: "t" }) &&
t.isIdentifier(callee.object)) {
const variable = callee.object.name;
const ns = namespaceMap.get(variable) || "";
const keyArg = args[0];
if (t.isStringLiteral(keyArg) || t.isTemplateLiteral(keyArg)) {
const key = t.isStringLiteral(keyArg)
? keyArg.value
: ((_j = (_h = (_g = keyArg.quasis) === null || _g === void 0 ? void 0 : _g[0]) === null || _h === void 0 ? void 0 : _h.value) === null || _j === void 0 ? void 0 : _j.raw) || "";
// For keys with dots, replace dots with underscores in the value
// to avoid breaking next-intl namespace resolution
const safeValue = key.includes(".") ? key.replace(/\./g, "_") : key;
translations.push({
nameSpace: ns,
messageKey: key,
string: safeValue,
file,
});
}
// Handle enum references
const resolvedEnumValue = resolveEnumValue(keyArg, enumMap);
if (resolvedEnumValue) {
translations.push({
nameSpace: ns,
messageKey: resolvedEnumValue,
string: resolvedEnumValue,
file,
});
}
}
// Match: t.rich('key', ...) and t.markup('key', ...)
if (t.isMemberExpression(callee) &&
t.isIdentifier(callee.object) &&
t.isIdentifier(callee.property) &&
["rich", "markup"].includes(callee.property.name)) {
const variable = callee.object.name;
const ns = namespaceMap.get(variable) || "";
const keyArg = args[0];
if (t.isStringLiteral(keyArg) || t.isTemplateLiteral(keyArg)) {
const key = t.isStringLiteral(keyArg)
? keyArg.value
: ((_m = (_l = (_k = keyArg.quasis) === null || _k === void 0 ? void 0 : _k[0]) === null || _l === void 0 ? void 0 : _l.value) === null || _m === void 0 ? void 0 : _m.raw) || "";
// For keys with dots, replace dots with underscores in the value
// to avoid breaking next-intl namespace resolution
const safeValue = key.includes(".") ? key.replace(/\./g, "_") : key;
translations.push({
nameSpace: ns,
messageKey: key,
string: safeValue,
file,
});
}
// Handle enum references
const resolvedEnumValue = resolveEnumValue(keyArg, enumMap);
if (resolvedEnumValue) {
translations.push({
nameSpace: ns,
messageKey: resolvedEnumValue,
string: resolvedEnumValue,
file,
});
}
}
},
JSXElement(path) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
if (!(config === null || config === void 0 ? void 0 : config.customJSXPattern))
return;
const openingElement = path.node.openingElement;
if (!t.isJSXIdentifier(openingElement.name))
return;
const elementName = openingElement.name.name;
for (const pattern of config.customJSXPattern) {
if (pattern.element === elementName) {
const namespaceAttr = openingElement.attributes.find((attr) => t.isJSXAttribute(attr) &&
attr.name.name === pattern.attributes.namespace);
const stringAttr = openingElement.attributes.find((attr) => t.isJSXAttribute(attr) &&
attr.name.name === pattern.attributes.string);
const messageKeyAttr = openingElement.attributes.find((attr) => t.isJSXAttribute(attr) &&
attr.name.name === pattern.attributes.messageKey);
if (namespaceAttr && stringAttr && messageKeyAttr) {
const namespace = t.isJSXAttribute(namespaceAttr) &&
(t.isStringLiteral(namespaceAttr.value) ||
t.isTemplateLiteral(namespaceAttr.value) ||
(t.isJSXExpressionContainer(namespaceAttr.value) &&
(t.isStringLiteral(namespaceAttr.value.expression) ||
t.isTemplateLiteral(namespaceAttr.value.expression))))
? t.isStringLiteral(namespaceAttr.value)
? namespaceAttr.value.value
: t.isTemplateLiteral(namespaceAttr.value)
? ((_c = (_b = (_a = namespaceAttr.value.quasis) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.value) === null || _c === void 0 ? void 0 : _c.raw) || ""
: t.isStringLiteral(namespaceAttr.value.expression)
? namespaceAttr.value.expression.value
: ((_f = (_e = (_d = namespaceAttr.value.expression
.quasis) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.value) === null || _f === void 0 ? void 0 : _f.raw) || ""
: "";
const string = t.isJSXAttribute(stringAttr) &&
(t.isStringLiteral(stringAttr.value) ||
t.isTemplateLiteral(stringAttr.value) ||
(t.isJSXExpressionContainer(stringAttr.value) &&
(t.isStringLiteral(stringAttr.value.expression) ||
t.isTemplateLiteral(stringAttr.value.expression))))
? t.isStringLiteral(stringAttr.value)
? stringAttr.value.value
: t.isTemplateLiteral(stringAttr.value)
? ((_j = (_h = (_g = stringAttr.value.quasis) === null || _g === void 0 ? void 0 : _g[0]) === null || _h === void 0 ? void 0 : _h.value) === null || _j === void 0 ? void 0 : _j.raw) || ""
: t.isStringLiteral(stringAttr.value.expression)
? stringAttr.value.expression.value
: ((_m = (_l = (_k = stringAttr.value.expression
.quasis) === null || _k === void 0 ? void 0 : _k[0]) === null || _l === void 0 ? void 0 : _l.value) === null || _m === void 0 ? void 0 : _m.raw) || ""
: "";
const messageKey = t.isJSXAttribute(messageKeyAttr) &&
(t.isStringLiteral(messageKeyAttr.value) ||
t.isTemplateLiteral(messageKeyAttr.value) ||
(t.isJSXExpressionContainer(messageKeyAttr.value) &&
(t.isStringLiteral(messageKeyAttr.value.expression) ||
t.isTemplateLiteral(messageKeyAttr.value.expression))))
? t.isStringLiteral(messageKeyAttr.value)
? messageKeyAttr.value.value
: t.isTemplateLiteral(messageKeyAttr.value)
? ((_q = (_p = (_o = messageKeyAttr.value.quasis) === null || _o === void 0 ? void 0 : _o[0]) === null || _p === void 0 ? void 0 : _p.value) === null || _q === void 0 ? void 0 : _q.raw) || ""
: t.isStringLiteral(messageKeyAttr.value.expression)
? messageKeyAttr.value.expression.value
: ((_t = (_s = (_r = messageKeyAttr.value.expression
.quasis) === null || _r === void 0 ? void 0 : _r[0]) === null || _s === void 0 ? void 0 : _s.value) === null || _t === void 0 ? void 0 : _t.raw) || ""
: "";
if (namespace && string && messageKey) {
// For keys with dots, replace dots with underscores in the value
// to avoid breaking next-intl namespace resolution
const safeValue = messageKey.includes(".")
? messageKey.replace(/\./g, "_")
: string;
translations.push({
nameSpace: namespace,
messageKey: messageKey,
string: safeValue,
file,
});
}
}
}
}
},
});
return translations;
}
exports.extractTranslationsFromSource = extractTranslationsFromSource;