UNPKG

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
"use strict"; 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;