@intlayer/babel 8.6.4 → 8.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/extractContent/babelProcessor.cjs +53 -28
- package/dist/cjs/extractContent/babelProcessor.cjs.map +1 -1
- package/dist/cjs/extractContent/extractContent.cjs +1 -1
- package/dist/cjs/extractContent/extractContent.cjs.map +1 -1
- package/dist/cjs/extractContent/processTsxFile.cjs +12 -1
- package/dist/cjs/extractContent/processTsxFile.cjs.map +1 -1
- package/dist/cjs/extractContent/utils/generateKey.cjs +1 -0
- package/dist/cjs/extractContent/utils/generateKey.cjs.map +1 -1
- package/dist/cjs/extractContent/utils/shouldExtract.cjs +5 -4
- package/dist/cjs/extractContent/utils/shouldExtract.cjs.map +1 -1
- package/dist/esm/extractContent/babelProcessor.mjs +53 -28
- package/dist/esm/extractContent/babelProcessor.mjs.map +1 -1
- package/dist/esm/extractContent/extractContent.mjs +1 -1
- package/dist/esm/extractContent/extractContent.mjs.map +1 -1
- package/dist/esm/extractContent/processTsxFile.mjs +12 -1
- package/dist/esm/extractContent/processTsxFile.mjs.map +1 -1
- package/dist/esm/extractContent/utils/generateKey.mjs +1 -0
- package/dist/esm/extractContent/utils/generateKey.mjs.map +1 -1
- package/dist/esm/extractContent/utils/shouldExtract.mjs +5 -4
- package/dist/esm/extractContent/utils/shouldExtract.mjs.map +1 -1
- package/dist/types/extractContent/babelProcessor.d.ts.map +1 -1
- package/dist/types/extractContent/utils/shouldExtract.d.ts +2 -2
- package/package.json +9 -9
|
@@ -101,6 +101,9 @@ const extractBabelContentForComponents = (ast, fileCode, existingKeys, defaultKe
|
|
|
101
101
|
let globalFileKey;
|
|
102
102
|
const componentPaths = [];
|
|
103
103
|
traverse(ast, {
|
|
104
|
+
Program(path) {
|
|
105
|
+
componentPaths.push(path);
|
|
106
|
+
},
|
|
104
107
|
FunctionDeclaration(path) {
|
|
105
108
|
componentPaths.push(path);
|
|
106
109
|
},
|
|
@@ -117,6 +120,13 @@ const extractBabelContentForComponents = (ast, fileCode, existingKeys, defaultKe
|
|
|
117
120
|
componentKeyMap.set(path.node, existingInfo.key);
|
|
118
121
|
usedKeysInFile.add(existingInfo.key);
|
|
119
122
|
hookMap.set(path.node, existingInfo.hook);
|
|
123
|
+
} else if (path.isProgram()) {
|
|
124
|
+
if (!globalFileKey) {
|
|
125
|
+
globalFileKey = require_extractContent_utils_resolveDictionaryKey.resolveDictionaryKey(defaultKey, filePath, configuration, unmergedDictionaries, usedKeysInFile);
|
|
126
|
+
usedKeysInFile.add(globalFileKey);
|
|
127
|
+
}
|
|
128
|
+
componentKeyMap.set(path.node, globalFileKey);
|
|
129
|
+
hookMap.set(path.node, "getIntlayer");
|
|
120
130
|
} else {
|
|
121
131
|
if (!globalFileKey) {
|
|
122
132
|
globalFileKey = require_extractContent_utils_resolveDictionaryKey.resolveDictionaryKey(defaultKey, filePath, configuration, unmergedDictionaries, usedKeysInFile);
|
|
@@ -125,7 +135,8 @@ const extractBabelContentForComponents = (ast, fileCode, existingKeys, defaultKe
|
|
|
125
135
|
componentKeyMap.set(path.node, globalFileKey);
|
|
126
136
|
const compName = require_extractContent_utils_getComponentName.getComponentName(path);
|
|
127
137
|
const isComponent = compName ? /^[A-Z]/.test(compName) : false;
|
|
128
|
-
|
|
138
|
+
const isHook = compName ? /^use[A-Z]/.test(compName) : false;
|
|
139
|
+
hookMap.set(path.node, isComponent || isHook ? "useIntlayer" : "getIntlayer");
|
|
129
140
|
}
|
|
130
141
|
}
|
|
131
142
|
const getComponentKeyForPath = (path) => {
|
|
@@ -198,39 +209,53 @@ const extractBabelContentForComponents = (ast, fileCode, existingKeys, defaultKe
|
|
|
198
209
|
}
|
|
199
210
|
});
|
|
200
211
|
const componentsNeedingHooks = /* @__PURE__ */ new Set();
|
|
201
|
-
for (const componentPath of componentPaths)
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
212
|
+
for (const componentPath of componentPaths) {
|
|
213
|
+
if (componentPath.isProgram()) {
|
|
214
|
+
if (replacements.some((replacement) => {
|
|
215
|
+
let current = replacement.path;
|
|
216
|
+
while (current) {
|
|
217
|
+
if (current.node === componentPath.node) return true;
|
|
218
|
+
if (componentPaths.some((p) => p !== componentPath && p.node === current?.node)) return false;
|
|
219
|
+
current = current.parentPath;
|
|
220
|
+
}
|
|
221
|
+
return false;
|
|
222
|
+
})) componentsNeedingHooks.add(componentPath);
|
|
223
|
+
continue;
|
|
206
224
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
225
|
+
if (replacements.some((replacement) => {
|
|
226
|
+
let current = replacement.path;
|
|
227
|
+
while (current) {
|
|
228
|
+
if (current.node === componentPath.node) return true;
|
|
229
|
+
current = current.parentPath;
|
|
230
|
+
}
|
|
231
|
+
return false;
|
|
232
|
+
})) {
|
|
233
|
+
const key = componentKeyMap.get(componentPath.node);
|
|
234
|
+
let ancestorProvidesKey = false;
|
|
235
|
+
let currentPath = componentPath.parentPath;
|
|
236
|
+
while (currentPath) {
|
|
237
|
+
const ancestorPath = componentPaths.find((path) => path.node === currentPath?.node);
|
|
238
|
+
if (ancestorPath && !ancestorPath.isProgram()) {
|
|
239
|
+
if (componentKeyMap.get(ancestorPath.node) === key) {
|
|
240
|
+
const ancestorHasReplacements = replacements.some((replacement) => {
|
|
241
|
+
let rPath = replacement.path;
|
|
242
|
+
while (rPath) {
|
|
243
|
+
if (rPath.node === ancestorPath.node) return true;
|
|
244
|
+
rPath = rPath.parentPath;
|
|
245
|
+
}
|
|
246
|
+
return false;
|
|
247
|
+
});
|
|
248
|
+
const existingInfo = require_extractContent_utils_getExistingIntlayerInfo.getExistingIntlayerInfo(ancestorPath);
|
|
249
|
+
if (ancestorHasReplacements || existingInfo) {
|
|
250
|
+
ancestorProvidesKey = true;
|
|
251
|
+
break;
|
|
221
252
|
}
|
|
222
|
-
return false;
|
|
223
|
-
});
|
|
224
|
-
const existingInfo = require_extractContent_utils_getExistingIntlayerInfo.getExistingIntlayerInfo(ancestorPath);
|
|
225
|
-
if (ancestorHasReplacements || existingInfo) {
|
|
226
|
-
ancestorProvidesKey = true;
|
|
227
|
-
break;
|
|
228
253
|
}
|
|
229
254
|
}
|
|
255
|
+
currentPath = currentPath.parentPath;
|
|
230
256
|
}
|
|
231
|
-
|
|
257
|
+
if (!ancestorProvidesKey) componentsNeedingHooks.add(componentPath);
|
|
232
258
|
}
|
|
233
|
-
if (!ancestorProvidesKey) componentsNeedingHooks.add(componentPath);
|
|
234
259
|
}
|
|
235
260
|
return {
|
|
236
261
|
extractedContent,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"babelProcessor.cjs","names":["_traverse","t","shouldExtract","getOrGenerateKey","getExistingIntlayerInfo","resolveDictionaryKey","getComponentName","ATTRIBUTES_TO_EXTRACT"],"sources":["../../../src/extractContent/babelProcessor.ts"],"sourcesContent":["import _traverse, { type NodePath } from '@babel/traverse';\nimport * as t from '@babel/types';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { resolveDictionaryKey } from '../extractContent/utils';\nimport {\n ATTRIBUTES_TO_EXTRACT,\n getComponentName,\n getExistingIntlayerInfo,\n getOrGenerateKey,\n shouldExtract,\n} from './utils';\n\nexport type BabelReplacement = {\n path: NodePath;\n key: string;\n type:\n | 'jsx-text'\n | 'jsx-attribute'\n | 'string-literal'\n | 'jsx-insertion'\n | 'jsx-text-combined';\n componentKey: string;\n childrenToReplace?: t.Node[];\n variables?: string[];\n};\n\n// CJS/ESM interop: @babel/traverse exports its function as `.default` in CJS bundles\nconst traverse = (\n typeof _traverse === 'function' ? _traverse : (_traverse as any).default\n) as typeof _traverse;\n\n/**\n * Handles JSX insertions (elements with multiple children, including expressions).\n * Replaces complex JSX structures with variable-based translations.\n */\nexport const handleJsxInsertionBabel = (\n path: NodePath<t.JSXElement | t.JSXFragment>,\n fileCode: string,\n existingKeys: Set<string>,\n getComponentKeyForPath: (path: NodePath) => string,\n extractedContent: Record<string, Record<string, string>>,\n replacements: BabelReplacement[],\n handledNodes: Set<t.Node>\n): boolean => {\n const children = path.node.children;\n\n if (children.length <= 1) return false;\n\n const parts: {\n type: 'text' | 'var';\n value: string;\n originalExpr?: string;\n }[] = [];\n let hasSignificantText = false;\n let hasVariables = false;\n\n for (const child of children) {\n if (t.isJSXText(child)) {\n const text = child.value;\n\n if (text.trim().length > 0) hasSignificantText = true;\n\n parts.push({ type: 'text', value: text });\n } else if (t.isJSXExpressionContainer(child)) {\n if (t.isJSXEmptyExpression(child.expression)) {\n parts.push({ type: 'text', value: '' });\n } else {\n const expr = child.expression;\n\n if (t.isIdentifier(expr)) {\n parts.push({\n type: 'var',\n value: expr.name,\n originalExpr: expr.name,\n });\n hasVariables = true;\n } else if (t.isMemberExpression(expr)) {\n const code = fileCode.substring(expr.start!, expr.end!);\n\n const varName = t.isIdentifier(expr.property)\n ? expr.property.name\n : 'var';\n\n parts.push({ type: 'var', value: varName, originalExpr: code });\n\n hasVariables = true;\n } else {\n return false;\n }\n }\n } else {\n return false;\n }\n }\n\n if (!hasSignificantText) return false;\n\n let combinedString = '';\n for (const part of parts) {\n if (part.type === 'var') combinedString += `{{${part.value}}}`;\n else combinedString += part.value;\n }\n\n const cleanString = combinedString.replace(/\\s+/g, ' ').trim();\n\n if (shouldExtract(cleanString)) {\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n cleanString,\n componentKey,\n existingKeys,\n extractedContent\n );\n\n const varMap = parts\n .filter((part) => part.type === 'var')\n .map((part) => `${part.value}: ${part.originalExpr}`);\n const uniqueVars = Array.from(new Set(varMap));\n\n if (hasVariables) {\n replacements.push({\n path,\n key,\n type: 'jsx-insertion',\n componentKey,\n childrenToReplace: children,\n variables: uniqueVars,\n });\n } else {\n replacements.push({\n path,\n key,\n type: 'jsx-text-combined',\n componentKey,\n childrenToReplace: children,\n });\n }\n\n children.forEach((child) => {\n handledNodes.add(child);\n });\n return true;\n }\n\n return false;\n};\n\n/**\n * Traverses the AST to identify components and extract content.\n * Returns extraction results and metadata about which components need Intlayer hooks.\n */\nexport const extractBabelContentForComponents = (\n ast: t.File,\n fileCode: string,\n existingKeys: Set<string>,\n defaultKey: string = 'default',\n configuration: IntlayerConfig,\n filePath: string,\n unmergedDictionaries: Record<string, unknown> = {}\n): {\n extractedContent: Record<string, Record<string, string>>;\n replacements: BabelReplacement[];\n componentsNeedingHooks: Set<NodePath>;\n componentKeyMap: Map<t.Node, string>;\n componentPaths: NodePath[];\n hookMap: Map<t.Node, 'useIntlayer' | 'getIntlayer'>;\n isSolid: boolean;\n} => {\n const extractedContent: Record<string, Record<string, string>> = {};\n const replacements: BabelReplacement[] = [];\n const handledNodes = new Set<t.Node>();\n const componentKeyMap = new Map<t.Node, string>();\n const hookMap = new Map<t.Node, 'useIntlayer' | 'getIntlayer'>();\n const usedKeysInFile = new Set<string>();\n let globalFileKey: string | undefined;\n\n const componentPaths: NodePath[] = [];\n\n traverse(ast, {\n FunctionDeclaration(path) {\n componentPaths.push(path);\n },\n ArrowFunctionExpression(path) {\n componentPaths.push(path);\n },\n FunctionExpression(path) {\n componentPaths.push(path);\n },\n });\n\n for (const path of componentPaths) {\n const existingInfo = getExistingIntlayerInfo(path);\n\n if (existingInfo) {\n componentKeyMap.set(path.node, existingInfo.key);\n usedKeysInFile.add(existingInfo.key);\n hookMap.set(path.node, existingInfo.hook);\n } else {\n if (!globalFileKey) {\n globalFileKey = resolveDictionaryKey(\n defaultKey,\n filePath,\n configuration,\n unmergedDictionaries,\n usedKeysInFile\n );\n usedKeysInFile.add(globalFileKey);\n }\n componentKeyMap.set(path.node, globalFileKey);\n\n const compName = getComponentName(path);\n const isComponent = compName ? /^[A-Z]/.test(compName) : false;\n hookMap.set(path.node, isComponent ? 'useIntlayer' : 'getIntlayer');\n }\n }\n\n const getComponentKeyForPath = (path: NodePath): string => {\n let current: NodePath | null = path;\n while (current) {\n if (componentKeyMap.has(current.node)) {\n return componentKeyMap.get(current.node)!;\n }\n current = current.parentPath;\n }\n return globalFileKey || defaultKey;\n };\n\n traverse(ast, {\n JSXElement(path) {\n if (handledNodes.has(path.node)) return;\n\n handleJsxInsertionBabel(\n path,\n fileCode,\n existingKeys,\n getComponentKeyForPath,\n extractedContent,\n replacements,\n handledNodes\n );\n },\n JSXFragment(path) {\n if (handledNodes.has(path.node)) return;\n\n handleJsxInsertionBabel(\n path,\n fileCode,\n existingKeys,\n getComponentKeyForPath,\n extractedContent,\n replacements,\n handledNodes\n );\n },\n JSXText(path) {\n if (handledNodes.has(path.node)) return;\n\n const text = path.node.value;\n\n if (shouldExtract(text)) {\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n text.replace(/\\s+/g, ' ').trim(),\n componentKey,\n existingKeys,\n extractedContent\n );\n replacements.push({ path, key, type: 'jsx-text', componentKey });\n }\n },\n JSXAttribute(path) {\n if (handledNodes.has(path.node)) return;\n\n const name = path.node.name.name;\n\n if (\n typeof name !== 'string' ||\n !ATTRIBUTES_TO_EXTRACT.includes(name as any)\n )\n return;\n const value = path.node.value;\n\n if (t.isStringLiteral(value) && shouldExtract(value.value)) {\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n value.value.trim(),\n componentKey,\n existingKeys,\n extractedContent\n );\n replacements.push({ path, key, type: 'jsx-attribute', componentKey });\n }\n },\n StringLiteral(path) {\n if (handledNodes.has(path.node)) return;\n\n const text = path.node.value;\n\n if (!shouldExtract(text)) return;\n\n const parent = path.parentPath;\n\n if (\n parent.isImportDeclaration() ||\n parent.isImportSpecifier() ||\n parent.isExportDeclaration()\n )\n return;\n\n if (parent.isJSXAttribute()) return;\n\n if (\n parent.isCallExpression() &&\n t.isMemberExpression(parent.node.callee)\n ) {\n if (\n t.isIdentifier(parent.node.callee.object) &&\n parent.node.callee.object.name === 'console' &&\n t.isIdentifier(parent.node.callee.property) &&\n parent.node.callee.property.name === 'log'\n ) {\n return;\n }\n }\n\n if (parent.isObjectProperty() && parent.node.key === path.node) return;\n\n if (parent.isMemberExpression() && parent.node.property === path.node)\n return;\n\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n text.trim(),\n componentKey,\n existingKeys,\n extractedContent\n );\n replacements.push({ path, key, type: 'string-literal', componentKey });\n },\n });\n\n const componentsNeedingHooks = new Set<NodePath>();\n for (const componentPath of componentPaths) {\n const hasReplacements = replacements.some((replacement) => {\n let current: NodePath | null = replacement.path;\n while (current) {\n if (current.node === componentPath.node) return true;\n\n current = current.parentPath;\n }\n return false;\n });\n\n if (hasReplacements) {\n const key = componentKeyMap.get(componentPath.node)!;\n let ancestorProvidesKey = false;\n let currentPath: NodePath | null = componentPath.parentPath;\n while (currentPath) {\n const ancestorPath = componentPaths.find(\n (path) => path.node === currentPath?.node\n );\n\n if (ancestorPath) {\n const ancestorKey = componentKeyMap.get(ancestorPath.node);\n\n if (ancestorKey === key) {\n const ancestorHasReplacements = replacements.some((replacement) => {\n let rPath: NodePath | null = replacement.path;\n while (rPath) {\n if (rPath.node === ancestorPath.node) return true;\n\n rPath = rPath.parentPath;\n }\n return false;\n });\n const existingInfo = getExistingIntlayerInfo(ancestorPath);\n\n if (ancestorHasReplacements || existingInfo) {\n ancestorProvidesKey = true;\n break;\n }\n }\n }\n currentPath = currentPath.parentPath;\n }\n\n if (!ancestorProvidesKey) {\n componentsNeedingHooks.add(componentPath);\n }\n }\n }\n\n return {\n extractedContent,\n replacements,\n componentsNeedingHooks,\n componentKeyMap,\n componentPaths,\n hookMap,\n isSolid: false,\n };\n};\n\n/**\n * High-level function to extract content from TS/JS/JSX/TSX AST.\n */\nexport const extractTsContent = (\n ast: t.File,\n fileCode: string,\n existingKeys: Set<string>,\n configuration: IntlayerConfig,\n filePath: string,\n unmergedDictionaries: Record<string, unknown> = {}\n): {\n extractedContent: Record<string, string>;\n replacements: BabelReplacement[];\n} => {\n const { extractedContent, replacements } = extractBabelContentForComponents(\n ast,\n fileCode,\n existingKeys,\n 'default',\n configuration,\n filePath,\n unmergedDictionaries\n );\n\n const flatContent: Record<string, string> = {};\n for (const group of Object.values(extractedContent)) {\n Object.assign(flatContent, group);\n }\n\n return { extractedContent: flatContent, replacements };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"babelProcessor.cjs","names":["_traverse","t","shouldExtract","getOrGenerateKey","getExistingIntlayerInfo","resolveDictionaryKey","getComponentName","ATTRIBUTES_TO_EXTRACT"],"sources":["../../../src/extractContent/babelProcessor.ts"],"sourcesContent":["import _traverse, { type NodePath } from '@babel/traverse';\nimport * as t from '@babel/types';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { resolveDictionaryKey } from '../extractContent/utils';\nimport {\n ATTRIBUTES_TO_EXTRACT,\n getComponentName,\n getExistingIntlayerInfo,\n getOrGenerateKey,\n shouldExtract,\n} from './utils';\n\nexport type BabelReplacement = {\n path: NodePath;\n key: string;\n type:\n | 'jsx-text'\n | 'jsx-attribute'\n | 'string-literal'\n | 'jsx-insertion'\n | 'jsx-text-combined';\n componentKey: string;\n childrenToReplace?: t.Node[];\n variables?: string[];\n};\n\n// CJS/ESM interop: @babel/traverse exports its function as `.default` in CJS bundles\nconst traverse = (\n typeof _traverse === 'function' ? _traverse : (_traverse as any).default\n) as typeof _traverse;\n\n/**\n * Handles JSX insertions (elements with multiple children, including expressions).\n * Replaces complex JSX structures with variable-based translations.\n */\nexport const handleJsxInsertionBabel = (\n path: NodePath<t.JSXElement | t.JSXFragment>,\n fileCode: string,\n existingKeys: Set<string>,\n getComponentKeyForPath: (path: NodePath) => string,\n extractedContent: Record<string, Record<string, string>>,\n replacements: BabelReplacement[],\n handledNodes: Set<t.Node>\n): boolean => {\n const children = path.node.children;\n\n if (children.length <= 1) return false;\n\n const parts: {\n type: 'text' | 'var';\n value: string;\n originalExpr?: string;\n }[] = [];\n let hasSignificantText = false;\n let hasVariables = false;\n\n for (const child of children) {\n if (t.isJSXText(child)) {\n const text = child.value;\n\n if (text.trim().length > 0) hasSignificantText = true;\n\n parts.push({ type: 'text', value: text });\n } else if (t.isJSXExpressionContainer(child)) {\n if (t.isJSXEmptyExpression(child.expression)) {\n parts.push({ type: 'text', value: '' });\n } else {\n const expr = child.expression;\n\n if (t.isIdentifier(expr)) {\n parts.push({\n type: 'var',\n value: expr.name,\n originalExpr: expr.name,\n });\n hasVariables = true;\n } else if (t.isMemberExpression(expr)) {\n const code = fileCode.substring(expr.start!, expr.end!);\n\n const varName = t.isIdentifier(expr.property)\n ? expr.property.name\n : 'var';\n\n parts.push({ type: 'var', value: varName, originalExpr: code });\n\n hasVariables = true;\n } else {\n return false;\n }\n }\n } else {\n return false;\n }\n }\n\n if (!hasSignificantText) return false;\n\n let combinedString = '';\n for (const part of parts) {\n if (part.type === 'var') combinedString += `{{${part.value}}}`;\n else combinedString += part.value;\n }\n\n const cleanString = combinedString.replace(/\\s+/g, ' ').trim();\n\n if (shouldExtract(cleanString)) {\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n cleanString,\n componentKey,\n existingKeys,\n extractedContent\n );\n\n const varMap = parts\n .filter((part) => part.type === 'var')\n .map((part) => `${part.value}: ${part.originalExpr}`);\n const uniqueVars = Array.from(new Set(varMap));\n\n if (hasVariables) {\n replacements.push({\n path,\n key,\n type: 'jsx-insertion',\n componentKey,\n childrenToReplace: children,\n variables: uniqueVars,\n });\n } else {\n replacements.push({\n path,\n key,\n type: 'jsx-text-combined',\n componentKey,\n childrenToReplace: children,\n });\n }\n\n children.forEach((child) => {\n handledNodes.add(child);\n });\n return true;\n }\n\n return false;\n};\n\n/**\n * Traverses the AST to identify components and extract content.\n * Returns extraction results and metadata about which components need Intlayer hooks.\n */\nexport const extractBabelContentForComponents = (\n ast: t.File,\n fileCode: string,\n existingKeys: Set<string>,\n defaultKey: string = 'default',\n configuration: IntlayerConfig,\n filePath: string,\n unmergedDictionaries: Record<string, unknown> = {}\n): {\n extractedContent: Record<string, Record<string, string>>;\n replacements: BabelReplacement[];\n componentsNeedingHooks: Set<NodePath>;\n componentKeyMap: Map<t.Node, string>;\n componentPaths: NodePath[];\n hookMap: Map<t.Node, 'useIntlayer' | 'getIntlayer'>;\n isSolid: boolean;\n} => {\n const extractedContent: Record<string, Record<string, string>> = {};\n const replacements: BabelReplacement[] = [];\n const handledNodes = new Set<t.Node>();\n const componentKeyMap = new Map<t.Node, string>();\n const hookMap = new Map<t.Node, 'useIntlayer' | 'getIntlayer'>();\n const usedKeysInFile = new Set<string>();\n let globalFileKey: string | undefined;\n\n const componentPaths: NodePath[] = [];\n\n traverse(ast, {\n Program(path) {\n componentPaths.push(path);\n },\n FunctionDeclaration(path) {\n componentPaths.push(path);\n },\n ArrowFunctionExpression(path) {\n componentPaths.push(path);\n },\n FunctionExpression(path) {\n componentPaths.push(path);\n },\n });\n\n for (const path of componentPaths) {\n const existingInfo = getExistingIntlayerInfo(path);\n\n if (existingInfo) {\n componentKeyMap.set(path.node, existingInfo.key);\n usedKeysInFile.add(existingInfo.key);\n hookMap.set(path.node, existingInfo.hook);\n } else {\n if (path.isProgram()) {\n if (!globalFileKey) {\n globalFileKey = resolveDictionaryKey(\n defaultKey,\n filePath,\n configuration,\n unmergedDictionaries,\n usedKeysInFile\n );\n usedKeysInFile.add(globalFileKey);\n }\n componentKeyMap.set(path.node, globalFileKey);\n hookMap.set(path.node, 'getIntlayer');\n } else {\n if (!globalFileKey) {\n globalFileKey = resolveDictionaryKey(\n defaultKey,\n filePath,\n configuration,\n unmergedDictionaries,\n usedKeysInFile\n );\n usedKeysInFile.add(globalFileKey);\n }\n componentKeyMap.set(path.node, globalFileKey);\n\n const compName = getComponentName(path);\n const isComponent = compName ? /^[A-Z]/.test(compName) : false;\n const isHook = compName ? /^use[A-Z]/.test(compName) : false;\n hookMap.set(\n path.node,\n isComponent || isHook ? 'useIntlayer' : 'getIntlayer'\n );\n }\n }\n }\n\n const getComponentKeyForPath = (path: NodePath): string => {\n let current: NodePath | null = path;\n while (current) {\n if (componentKeyMap.has(current.node)) {\n return componentKeyMap.get(current.node)!;\n }\n current = current.parentPath;\n }\n return globalFileKey || defaultKey;\n };\n\n traverse(ast, {\n JSXElement(path) {\n if (handledNodes.has(path.node)) return;\n\n handleJsxInsertionBabel(\n path,\n fileCode,\n existingKeys,\n getComponentKeyForPath,\n extractedContent,\n replacements,\n handledNodes\n );\n },\n JSXFragment(path) {\n if (handledNodes.has(path.node)) return;\n\n handleJsxInsertionBabel(\n path,\n fileCode,\n existingKeys,\n getComponentKeyForPath,\n extractedContent,\n replacements,\n handledNodes\n );\n },\n JSXText(path) {\n if (handledNodes.has(path.node)) return;\n\n const text = path.node.value;\n\n if (shouldExtract(text)) {\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n text.replace(/\\s+/g, ' ').trim(),\n componentKey,\n existingKeys,\n extractedContent\n );\n replacements.push({ path, key, type: 'jsx-text', componentKey });\n }\n },\n JSXAttribute(path) {\n if (handledNodes.has(path.node)) return;\n\n const name = path.node.name.name;\n\n if (\n typeof name !== 'string' ||\n !ATTRIBUTES_TO_EXTRACT.includes(name as any)\n )\n return;\n const value = path.node.value;\n\n if (t.isStringLiteral(value) && shouldExtract(value.value)) {\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n value.value.trim(),\n componentKey,\n existingKeys,\n extractedContent\n );\n replacements.push({ path, key, type: 'jsx-attribute', componentKey });\n }\n },\n StringLiteral(path) {\n if (handledNodes.has(path.node)) return;\n\n const text = path.node.value;\n\n if (!shouldExtract(text)) return;\n\n const parent = path.parentPath;\n\n if (\n parent.isImportDeclaration() ||\n parent.isImportSpecifier() ||\n parent.isExportDeclaration()\n )\n return;\n\n if (parent.isJSXAttribute()) return;\n\n if (\n parent.isCallExpression() &&\n t.isMemberExpression(parent.node.callee)\n ) {\n if (\n t.isIdentifier(parent.node.callee.object) &&\n parent.node.callee.object.name === 'console' &&\n t.isIdentifier(parent.node.callee.property) &&\n parent.node.callee.property.name === 'log'\n ) {\n return;\n }\n }\n\n if (parent.isObjectProperty() && parent.node.key === path.node) return;\n\n if (parent.isMemberExpression() && parent.node.property === path.node)\n return;\n\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n text.trim(),\n componentKey,\n existingKeys,\n extractedContent\n );\n replacements.push({ path, key, type: 'string-literal', componentKey });\n },\n });\n\n const componentsNeedingHooks = new Set<NodePath>();\n for (const componentPath of componentPaths) {\n if (componentPath.isProgram()) {\n const hasDirectReplacements = replacements.some((replacement) => {\n let current: NodePath | null = replacement.path;\n while (current) {\n if (current.node === componentPath.node) {\n return true;\n }\n const isOtherComponent = componentPaths.some(\n (p) => p !== componentPath && p.node === current?.node\n );\n if (isOtherComponent) {\n return false;\n }\n current = current.parentPath;\n }\n return false;\n });\n\n if (hasDirectReplacements) {\n componentsNeedingHooks.add(componentPath);\n }\n continue;\n }\n\n const hasReplacements = replacements.some((replacement) => {\n let current: NodePath | null = replacement.path;\n while (current) {\n if (current.node === componentPath.node) return true;\n\n current = current.parentPath;\n }\n return false;\n });\n\n if (hasReplacements) {\n const key = componentKeyMap.get(componentPath.node)!;\n let ancestorProvidesKey = false;\n let currentPath: NodePath | null = componentPath.parentPath;\n while (currentPath) {\n const ancestorPath = componentPaths.find(\n (path) => path.node === currentPath?.node\n );\n\n if (ancestorPath && !ancestorPath.isProgram()) {\n const ancestorKey = componentKeyMap.get(ancestorPath.node);\n\n if (ancestorKey === key) {\n const ancestorHasReplacements = replacements.some((replacement) => {\n let rPath: NodePath | null = replacement.path;\n while (rPath) {\n if (rPath.node === ancestorPath.node) return true;\n\n rPath = rPath.parentPath;\n }\n return false;\n });\n const existingInfo = getExistingIntlayerInfo(ancestorPath);\n\n if (ancestorHasReplacements || existingInfo) {\n ancestorProvidesKey = true;\n break;\n }\n }\n }\n currentPath = currentPath.parentPath;\n }\n\n if (!ancestorProvidesKey) {\n componentsNeedingHooks.add(componentPath);\n }\n }\n }\n\n return {\n extractedContent,\n replacements,\n componentsNeedingHooks,\n componentKeyMap,\n componentPaths,\n hookMap,\n isSolid: false,\n };\n};\n\n/**\n * High-level function to extract content from TS/JS/JSX/TSX AST.\n */\nexport const extractTsContent = (\n ast: t.File,\n fileCode: string,\n existingKeys: Set<string>,\n configuration: IntlayerConfig,\n filePath: string,\n unmergedDictionaries: Record<string, unknown> = {}\n): {\n extractedContent: Record<string, string>;\n replacements: BabelReplacement[];\n} => {\n const { extractedContent, replacements } = extractBabelContentForComponents(\n ast,\n fileCode,\n existingKeys,\n 'default',\n configuration,\n filePath,\n unmergedDictionaries\n );\n\n const flatContent: Record<string, string> = {};\n for (const group of Object.values(extractedContent)) {\n Object.assign(flatContent, group);\n }\n\n return { extractedContent: flatContent, replacements };\n};\n"],"mappings":"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"}
|
|
@@ -120,7 +120,7 @@ const extractContent = async (filePath, packageName, options) => {
|
|
|
120
120
|
unmergedDictionaries,
|
|
121
121
|
configuration
|
|
122
122
|
}, saveComponent, dictionaryKey);
|
|
123
|
-
if (!result
|
|
123
|
+
if (!result?.extractedContentMap) {
|
|
124
124
|
appLogger(`${(0, _intlayer_config_logger.colorize)("Compiler:", _intlayer_config_colors.GREY_DARK)} No extractable text found in ${(0, _intlayer_config_logger.colorizePath)((0, node_path.relative)(baseDir, filePath))}`, { isVerbose: true });
|
|
125
125
|
return;
|
|
126
126
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extractContent.cjs","names":["extractDictionaryKey","ATTRIBUTES_TO_EXTRACT","extractTsContent","ANSIColors","processTsxFile","writeContentHelper"],"sources":["../../../src/extractContent/extractContent.ts"],"sourcesContent":["import { execSync } from 'node:child_process';\nimport { readFileSync } from 'node:fs';\nimport { extname, relative } from 'node:path';\nimport type * as t from '@babel/types';\nimport { detectFormatCommand } from '@intlayer/chokidar/cli';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizePath, getAppLogger } from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport { getProjectRequire } from '@intlayer/config/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport { extractTsContent } from './babelProcessor';\nimport { writeContentHelper } from './contentWriter';\nimport { processTsxFile } from './processTsxFile';\nimport {\n ATTRIBUTES_TO_EXTRACT,\n extractDictionaryKeyFromPath,\n type PackageName,\n shouldExtract,\n} from './utils';\nimport { extractDictionaryKey } from './utils/extractDictionaryKey';\nimport { generateKey } from './utils/generateKey';\n\nexport type ExtractIntlayerOptions = {\n configOptions?: GetConfigurationOptions;\n codeOnly?: boolean;\n declarationOnly?: boolean;\n unmergedDictionaries?: Record<string, unknown>;\n configuration?: IntlayerConfig;\n code?: string;\n onExtract?: (result: {\n key: string;\n content: Record<string, string>;\n filePath: string;\n }) => void | Promise<void>;\n};\n\ntype ExternalCompilerResult = {\n extractedContent: Record<string, string>;\n code: string;\n};\n\ntype ExternalCompilerOptions = {\n generateKey: typeof generateKey;\n shouldExtract: typeof shouldExtract;\n attributesToExtract: typeof ATTRIBUTES_TO_EXTRACT;\n extractDictionaryKeyFromPath: typeof extractDictionaryKeyFromPath;\n extractTsContent: (\n ast: unknown,\n code: string,\n keys: Set<string>,\n config: IntlayerConfig,\n path: string\n ) => ReturnType<typeof extractTsContent>;\n};\n\ntype VueCompiler = typeof import('@intlayer/vue-compiler');\ntype SvelteCompiler = typeof import('@intlayer/svelte-compiler');\n\n// Module caches\nlet vueCompiler: VueCompiler | null = null;\nlet svelteCompiler: SvelteCompiler | null = null;\n\ntype InternalExtractResult = {\n extractedContentMap: Record<string, Record<string, string>> | null;\n transformedCode: string | null;\n};\n\nconst formatCompilerResult = (\n componentKey: string,\n res?: ExternalCompilerResult\n): InternalExtractResult | undefined => {\n if (!res) return undefined;\n\n return {\n extractedContentMap: { [componentKey]: res.extractedContent },\n transformedCode: res.code,\n };\n};\n\ntype Dependencies = {\n vueCompiler: VueCompiler | null;\n svelteCompiler: SvelteCompiler | null;\n unmergedDictionaries: Record<string, unknown>;\n configuration: IntlayerConfig;\n};\n\nconst processFileInternal = (\n filePath: string,\n packageName: PackageName,\n options: ExtractIntlayerOptions | undefined,\n dependencies: Dependencies,\n saveComponent: boolean,\n providedComponentKey?: string\n): InternalExtractResult | undefined => {\n const fileText = options?.code ?? readFileSync(filePath, 'utf-8');\n const componentKey =\n providedComponentKey ??\n extractDictionaryKey(\n filePath,\n fileText,\n dependencies.configuration.compiler.dictionaryKeyPrefix\n );\n const ext = extname(filePath);\n\n const { vueCompiler, svelteCompiler, unmergedDictionaries, configuration } =\n dependencies;\n\n const compilerCommonOptions: ExternalCompilerOptions = {\n generateKey,\n shouldExtract,\n attributesToExtract: ATTRIBUTES_TO_EXTRACT,\n extractDictionaryKeyFromPath,\n extractTsContent: (ast, code, keys, config, path) =>\n extractTsContent(\n ast as t.File,\n code,\n keys,\n config,\n path,\n unmergedDictionaries\n ),\n };\n\n if (ext === '.vue') {\n if (!vueCompiler) {\n throw new Error(\n `Please install ${colorizePath('@intlayer/vue-compiler', ANSIColors.YELLOW)} to process Vue files.`\n );\n }\n\n const res = vueCompiler.processVueFile(\n filePath,\n componentKey,\n packageName,\n compilerCommonOptions,\n saveComponent\n );\n\n if (res) {\n return formatCompilerResult(componentKey, res);\n }\n }\n\n if (ext === '.svelte') {\n if (!svelteCompiler) {\n throw new Error(\n `Please install ${colorizePath('@intlayer/svelte-compiler', ANSIColors.YELLOW)} to process Svelte files.`\n );\n }\n\n const res = svelteCompiler.processSvelteFile(\n filePath,\n componentKey,\n packageName,\n compilerCommonOptions,\n saveComponent\n );\n\n if (res) {\n return formatCompilerResult(componentKey, res);\n }\n }\n\n if (['.tsx', '.jsx', '.ts', '.js', '.cjs', '.mjs'].includes(ext)) {\n const result = processTsxFile(\n filePath,\n componentKey,\n packageName,\n configuration,\n saveComponent,\n unmergedDictionaries,\n fileText\n );\n\n if (result) {\n return {\n extractedContentMap: result.extractedContent,\n transformedCode: result.modifiedCode,\n };\n }\n }\n\n return undefined;\n};\n\nconst handleExtractionSideEffects = async (\n extractedContentMap: Record<string, Record<string, string>>,\n filePath: string,\n options: ExtractIntlayerOptions | undefined,\n {\n configuration,\n baseDir,\n appLogger,\n }: {\n configuration: IntlayerConfig;\n baseDir: string;\n appLogger: ReturnType<typeof getAppLogger>;\n },\n saveComponent: boolean\n) => {\n if (options?.onExtract) {\n for (const [key, content] of Object.entries(extractedContentMap)) {\n await options.onExtract({ key, content, filePath });\n }\n }\n\n const writeContent = !options?.codeOnly && !options?.onExtract;\n\n if (writeContent) {\n for (const [key, content] of Object.entries(extractedContentMap)) {\n const contentFilePath = await writeContentHelper(\n content,\n key,\n filePath,\n configuration\n );\n\n const relativeContentFilePath = relative(baseDir, contentFilePath);\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Created content file: ${colorizePath(relativeContentFilePath)}`\n );\n }\n }\n\n if (saveComponent) {\n const formatCommand = detectFormatCommand(configuration);\n\n if (formatCommand) {\n try {\n execSync(formatCommand.replace('{{file}}', filePath), {\n stdio: 'inherit',\n cwd: baseDir,\n });\n } catch (error) {\n console.error(error);\n }\n }\n\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Updated component: ${colorizePath(relative(baseDir, filePath))}`\n );\n }\n};\n\ntype ExtractResult = {\n transformedCode: string | null;\n extractedContentMap: Record<string, Record<string, string>>;\n};\n\ntype ExtractContext = {\n configuration: IntlayerConfig;\n appLogger: ReturnType<typeof getAppLogger>;\n baseDir: string;\n unmergedDictionaries: Record<string, unknown>;\n saveComponent: boolean;\n componentExtension: string;\n};\n\nconst buildContext = (\n filePath: string,\n options: ExtractIntlayerOptions | undefined\n): ExtractContext => {\n const configuration =\n options?.configuration ?? getConfiguration(options?.configOptions);\n const appLogger = getAppLogger(configuration);\n const { baseDir } = configuration.system;\n const unmergedDictionaries =\n options?.unmergedDictionaries ?? getUnmergedDictionaries(configuration);\n const saveComponent = !options?.declarationOnly;\n const componentExtension = extname(filePath);\n\n return {\n configuration,\n appLogger,\n baseDir,\n unmergedDictionaries,\n saveComponent,\n componentExtension,\n };\n};\n\nexport const extractContent = async (\n filePath: string,\n packageName: PackageName,\n options?: ExtractIntlayerOptions\n): Promise<ExtractResult | undefined> => {\n const {\n configuration,\n appLogger,\n baseDir,\n unmergedDictionaries,\n saveComponent,\n componentExtension,\n } = buildContext(filePath, options);\n\n if (componentExtension === '.vue' && !vueCompiler) {\n try {\n vueCompiler = await import('@intlayer/vue-compiler');\n } catch {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Install ${colorizePath('@intlayer/vue-compiler', ANSIColors.YELLOW)} to process Vue files.`\n );\n }\n }\n\n if (componentExtension === '.svelte' && !svelteCompiler) {\n try {\n svelteCompiler = await import('@intlayer/svelte-compiler');\n } catch {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Install ${colorizePath('@intlayer/svelte-compiler', ANSIColors.YELLOW)} to process Svelte files.`\n );\n }\n }\n\n const fileText = options?.code ?? readFileSync(filePath, 'utf-8');\n const dictionaryKey = extractDictionaryKey(\n filePath,\n fileText,\n configuration.compiler.dictionaryKeyPrefix\n );\n\n const result = processFileInternal(\n filePath,\n packageName,\n options,\n { vueCompiler, svelteCompiler, unmergedDictionaries, configuration },\n saveComponent,\n dictionaryKey\n );\n\n if (!result || !result.extractedContentMap) {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} No extractable text found in ${colorizePath(relative(baseDir, filePath))}`,\n { isVerbose: true }\n );\n return undefined;\n }\n\n await handleExtractionSideEffects(\n result.extractedContentMap,\n filePath,\n options,\n { configuration, baseDir, appLogger },\n saveComponent\n );\n\n return {\n transformedCode: result.transformedCode,\n extractedContentMap: result.extractedContentMap,\n };\n};\n\n/**\n * Synchronous variant of `extractContent` — used by the Babel plugin which\n * runs in a sync transform context (e.g. Next.js / Webpack).\n *\n * Differences from `extractContent`:\n * - Loads external compilers (Vue, Svelte) via `require()` instead of `import()`\n * - Fires `onExtract` callbacks as fire-and-forget (cannot await in sync context)\n * - Does NOT write dictionary files or run the code formatter; callers that\n * need persistence should supply an `onExtract` callback\n */\nexport const extractContentSync = (\n filePath: string,\n packageName: PackageName,\n options?: ExtractIntlayerOptions\n): ExtractResult | undefined => {\n const {\n configuration,\n appLogger,\n baseDir,\n unmergedDictionaries,\n saveComponent,\n componentExtension,\n } = buildContext(filePath, options);\n\n const requireFn = getProjectRequire();\n\n if (componentExtension === '.vue' && !vueCompiler) {\n try {\n vueCompiler = requireFn('@intlayer/vue-compiler') as VueCompiler;\n } catch {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Install ${colorizePath('@intlayer/vue-compiler', ANSIColors.YELLOW)} to process Vue files.`\n );\n }\n }\n\n if (componentExtension === '.svelte' && !svelteCompiler) {\n try {\n svelteCompiler = requireFn('@intlayer/svelte-compiler') as SvelteCompiler;\n } catch {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Install ${colorizePath('@intlayer/svelte-compiler', ANSIColors.YELLOW)} to process Svelte files.`\n );\n }\n }\n\n const result = processFileInternal(\n filePath,\n packageName,\n options,\n { vueCompiler, svelteCompiler, unmergedDictionaries, configuration },\n saveComponent\n );\n\n if (!result?.extractedContentMap) {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} No extractable text found in ${colorizePath(relative(baseDir, filePath))}`,\n { isVerbose: true }\n );\n return undefined;\n }\n\n const { extractedContentMap, transformedCode } = result;\n\n if (options?.onExtract) {\n for (const [key, content] of Object.entries(extractedContentMap)) {\n void options.onExtract({ key, content, filePath });\n }\n }\n\n return { transformedCode, extractedContentMap };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA+DA,IAAI,cAAkC;AACtC,IAAI,iBAAwC;AAO5C,MAAM,wBACJ,cACA,QACsC;AACtC,KAAI,CAAC,IAAK,QAAO;AAEjB,QAAO;EACL,qBAAqB,GAAG,eAAe,IAAI,kBAAkB;EAC7D,iBAAiB,IAAI;EACtB;;AAUH,MAAM,uBACJ,UACA,aACA,SACA,cACA,eACA,yBACsC;CACtC,MAAM,WAAW,SAAS,kCAAqB,UAAU,QAAQ;CACjE,MAAM,eACJ,wBACAA,uEACE,UACA,UACA,aAAa,cAAc,SAAS,oBACrC;CACH,MAAM,6BAAc,SAAS;CAE7B,MAAM,EAAE,aAAa,gBAAgB,sBAAsB,kBACzD;CAEF,MAAM,wBAAiD;EACrD;EACA;EACA,qBAAqBC;EACrB;EACA,mBAAmB,KAAK,MAAM,MAAM,QAAQ,SAC1CC,uDACE,KACA,MACA,MACA,QACA,MACA,qBACD;EACJ;AAED,KAAI,QAAQ,QAAQ;AAClB,MAAI,CAAC,YACH,OAAM,IAAI,MACR,4DAA+B,0BAA0BC,wBAAW,OAAO,CAAC,wBAC7E;EAGH,MAAM,MAAM,YAAY,eACtB,UACA,cACA,aACA,uBACA,cACD;AAED,MAAI,IACF,QAAO,qBAAqB,cAAc,IAAI;;AAIlD,KAAI,QAAQ,WAAW;AACrB,MAAI,CAAC,eACH,OAAM,IAAI,MACR,4DAA+B,6BAA6BA,wBAAW,OAAO,CAAC,2BAChF;EAGH,MAAM,MAAM,eAAe,kBACzB,UACA,cACA,aACA,uBACA,cACD;AAED,MAAI,IACF,QAAO,qBAAqB,cAAc,IAAI;;AAIlD,KAAI;EAAC;EAAQ;EAAQ;EAAO;EAAO;EAAQ;EAAO,CAAC,SAAS,IAAI,EAAE;EAChE,MAAM,SAASC,qDACb,UACA,cACA,aACA,eACA,eACA,sBACA,SACD;AAED,MAAI,OACF,QAAO;GACL,qBAAqB,OAAO;GAC5B,iBAAiB,OAAO;GACzB;;;AAOP,MAAM,8BAA8B,OAClC,qBACA,UACA,SACA,EACE,eACA,SACA,aAMF,kBACG;AACH,KAAI,SAAS,UACX,MAAK,MAAM,CAAC,KAAK,YAAY,OAAO,QAAQ,oBAAoB,CAC9D,OAAM,QAAQ,UAAU;EAAE;EAAK;EAAS;EAAU,CAAC;AAMvD,KAFqB,CAAC,SAAS,YAAY,CAAC,SAAS,UAGnD,MAAK,MAAM,CAAC,KAAK,YAAY,OAAO,QAAQ,oBAAoB,EAAE;EAQhE,MAAM,kDAAmC,SAPjB,MAAMC,wDAC5B,SACA,KACA,UACA,cACD,CAEiE;AAClE,YACE,yCAAY,aAAaF,wBAAW,UAAU,CAAC,mEAAsC,wBAAwB,GAC9G;;AAIL,KAAI,eAAe;EACjB,MAAM,gEAAoC,cAAc;AAExD,MAAI,cACF,KAAI;AACF,oCAAS,cAAc,QAAQ,YAAY,SAAS,EAAE;IACpD,OAAO;IACP,KAAK;IACN,CAAC;WACK,OAAO;AACd,WAAQ,MAAM,MAAM;;AAIxB,YACE,yCAAY,aAAaA,wBAAW,UAAU,CAAC,wFAA4C,SAAS,SAAS,CAAC,GAC/G;;;AAkBL,MAAM,gBACJ,UACA,YACmB;CACnB,MAAM,gBACJ,SAAS,6DAAkC,SAAS,cAAc;CACpE,MAAM,sDAAyB,cAAc;CAC7C,MAAM,EAAE,YAAY,cAAc;AAMlC,QAAO;EACL;EACA;EACA;EACA,sBARA,SAAS,2FAAgD,cAAc;EASvE,eARoB,CAAC,SAAS;EAS9B,2CARiC,SAAS;EAS3C;;AAGH,MAAa,iBAAiB,OAC5B,UACA,aACA,YACuC;CACvC,MAAM,EACJ,eACA,WACA,SACA,sBACA,eACA,uBACE,aAAa,UAAU,QAAQ;AAEnC,KAAI,uBAAuB,UAAU,CAAC,YACpC,KAAI;AACF,gBAAc,MAAM,OAAO;SACrB;AACN,YACE,yCAAY,aAAaA,wBAAW,UAAU,CAAC,qDAAwB,0BAA0BA,wBAAW,OAAO,CAAC,wBACrH;;AAIL,KAAI,uBAAuB,aAAa,CAAC,eACvC,KAAI;AACF,mBAAiB,MAAM,OAAO;SACxB;AACN,YACE,yCAAY,aAAaA,wBAAW,UAAU,CAAC,qDAAwB,6BAA6BA,wBAAW,OAAO,CAAC,2BACxH;;CAKL,MAAM,gBAAgBH,uEACpB,UAFe,SAAS,kCAAqB,UAAU,QAAQ,EAI/D,cAAc,SAAS,oBACxB;CAED,MAAM,SAAS,oBACb,UACA,aACA,SACA;EAAE;EAAa;EAAgB;EAAsB;EAAe,EACpE,eACA,cACD;AAED,KAAI,CAAC,UAAU,CAAC,OAAO,qBAAqB;AAC1C,YACE,yCAAY,aAAaG,wBAAW,UAAU,CAAC,kGAAsD,SAAS,SAAS,CAAC,IACxH,EAAE,WAAW,MAAM,CACpB;AACD;;AAGF,OAAM,4BACJ,OAAO,qBACP,UACA,SACA;EAAE;EAAe;EAAS;EAAW,EACrC,cACD;AAED,QAAO;EACL,iBAAiB,OAAO;EACxB,qBAAqB,OAAO;EAC7B;;;;;;;;;;;;AAaH,MAAa,sBACX,UACA,aACA,YAC8B;CAC9B,MAAM,EACJ,eACA,WACA,SACA,sBACA,eACA,uBACE,aAAa,UAAU,QAAQ;CAEnC,MAAM,2DAA+B;AAErC,KAAI,uBAAuB,UAAU,CAAC,YACpC,KAAI;AACF,gBAAc,UAAU,yBAAyB;SAC3C;AACN,YACE,yCAAY,aAAaA,wBAAW,UAAU,CAAC,qDAAwB,0BAA0BA,wBAAW,OAAO,CAAC,wBACrH;;AAIL,KAAI,uBAAuB,aAAa,CAAC,eACvC,KAAI;AACF,mBAAiB,UAAU,4BAA4B;SACjD;AACN,YACE,yCAAY,aAAaA,wBAAW,UAAU,CAAC,qDAAwB,6BAA6BA,wBAAW,OAAO,CAAC,2BACxH;;CAIL,MAAM,SAAS,oBACb,UACA,aACA,SACA;EAAE;EAAa;EAAgB;EAAsB;EAAe,EACpE,cACD;AAED,KAAI,CAAC,QAAQ,qBAAqB;AAChC,YACE,yCAAY,aAAaA,wBAAW,UAAU,CAAC,kGAAsD,SAAS,SAAS,CAAC,IACxH,EAAE,WAAW,MAAM,CACpB;AACD;;CAGF,MAAM,EAAE,qBAAqB,oBAAoB;AAEjD,KAAI,SAAS,UACX,MAAK,MAAM,CAAC,KAAK,YAAY,OAAO,QAAQ,oBAAoB,CAC9D,CAAK,QAAQ,UAAU;EAAE;EAAK;EAAS;EAAU,CAAC;AAItD,QAAO;EAAE;EAAiB;EAAqB"}
|
|
1
|
+
{"version":3,"file":"extractContent.cjs","names":["extractDictionaryKey","ATTRIBUTES_TO_EXTRACT","extractTsContent","ANSIColors","processTsxFile","writeContentHelper"],"sources":["../../../src/extractContent/extractContent.ts"],"sourcesContent":["import { execSync } from 'node:child_process';\nimport { readFileSync } from 'node:fs';\nimport { extname, relative } from 'node:path';\nimport type * as t from '@babel/types';\nimport { detectFormatCommand } from '@intlayer/chokidar/cli';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizePath, getAppLogger } from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport { getProjectRequire } from '@intlayer/config/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport { extractTsContent } from './babelProcessor';\nimport { writeContentHelper } from './contentWriter';\nimport { processTsxFile } from './processTsxFile';\nimport {\n ATTRIBUTES_TO_EXTRACT,\n extractDictionaryKeyFromPath,\n type PackageName,\n shouldExtract,\n} from './utils';\nimport { extractDictionaryKey } from './utils/extractDictionaryKey';\nimport { generateKey } from './utils/generateKey';\n\nexport type ExtractIntlayerOptions = {\n configOptions?: GetConfigurationOptions;\n codeOnly?: boolean;\n declarationOnly?: boolean;\n unmergedDictionaries?: Record<string, unknown>;\n configuration?: IntlayerConfig;\n code?: string;\n onExtract?: (result: {\n key: string;\n content: Record<string, string>;\n filePath: string;\n }) => void | Promise<void>;\n};\n\ntype ExternalCompilerResult = {\n extractedContent: Record<string, string>;\n code: string;\n};\n\ntype ExternalCompilerOptions = {\n generateKey: typeof generateKey;\n shouldExtract: typeof shouldExtract;\n attributesToExtract: typeof ATTRIBUTES_TO_EXTRACT;\n extractDictionaryKeyFromPath: typeof extractDictionaryKeyFromPath;\n extractTsContent: (\n ast: unknown,\n code: string,\n keys: Set<string>,\n config: IntlayerConfig,\n path: string\n ) => ReturnType<typeof extractTsContent>;\n};\n\ntype VueCompiler = typeof import('@intlayer/vue-compiler');\ntype SvelteCompiler = typeof import('@intlayer/svelte-compiler');\n\n// Module caches\nlet vueCompiler: VueCompiler | null = null;\nlet svelteCompiler: SvelteCompiler | null = null;\n\ntype InternalExtractResult = {\n extractedContentMap: Record<string, Record<string, string>> | null;\n transformedCode: string | null;\n};\n\nconst formatCompilerResult = (\n componentKey: string,\n res?: ExternalCompilerResult\n): InternalExtractResult | undefined => {\n if (!res) return undefined;\n\n return {\n extractedContentMap: { [componentKey]: res.extractedContent },\n transformedCode: res.code,\n };\n};\n\ntype Dependencies = {\n vueCompiler: VueCompiler | null;\n svelteCompiler: SvelteCompiler | null;\n unmergedDictionaries: Record<string, unknown>;\n configuration: IntlayerConfig;\n};\n\nconst processFileInternal = (\n filePath: string,\n packageName: PackageName,\n options: ExtractIntlayerOptions | undefined,\n dependencies: Dependencies,\n saveComponent: boolean,\n providedComponentKey?: string\n): InternalExtractResult | undefined => {\n const fileText = options?.code ?? readFileSync(filePath, 'utf-8');\n const componentKey =\n providedComponentKey ??\n extractDictionaryKey(\n filePath,\n fileText,\n dependencies.configuration.compiler.dictionaryKeyPrefix\n );\n const ext = extname(filePath);\n\n const { vueCompiler, svelteCompiler, unmergedDictionaries, configuration } =\n dependencies;\n\n const compilerCommonOptions: ExternalCompilerOptions = {\n generateKey,\n shouldExtract,\n attributesToExtract: ATTRIBUTES_TO_EXTRACT,\n extractDictionaryKeyFromPath,\n extractTsContent: (ast, code, keys, config, path) =>\n extractTsContent(\n ast as t.File,\n code,\n keys,\n config,\n path,\n unmergedDictionaries\n ),\n };\n\n if (ext === '.vue') {\n if (!vueCompiler) {\n throw new Error(\n `Please install ${colorizePath('@intlayer/vue-compiler', ANSIColors.YELLOW)} to process Vue files.`\n );\n }\n\n const res = vueCompiler.processVueFile(\n filePath,\n componentKey,\n packageName,\n compilerCommonOptions,\n saveComponent\n );\n\n if (res) {\n return formatCompilerResult(componentKey, res);\n }\n }\n\n if (ext === '.svelte') {\n if (!svelteCompiler) {\n throw new Error(\n `Please install ${colorizePath('@intlayer/svelte-compiler', ANSIColors.YELLOW)} to process Svelte files.`\n );\n }\n\n const res = svelteCompiler.processSvelteFile(\n filePath,\n componentKey,\n packageName,\n compilerCommonOptions,\n saveComponent\n );\n\n if (res) {\n return formatCompilerResult(componentKey, res);\n }\n }\n\n if (['.tsx', '.jsx', '.ts', '.js', '.cjs', '.mjs'].includes(ext)) {\n const result = processTsxFile(\n filePath,\n componentKey,\n packageName,\n configuration,\n saveComponent,\n unmergedDictionaries,\n fileText\n );\n\n if (result) {\n return {\n extractedContentMap: result.extractedContent,\n transformedCode: result.modifiedCode,\n };\n }\n }\n\n return undefined;\n};\n\nconst handleExtractionSideEffects = async (\n extractedContentMap: Record<string, Record<string, string>>,\n filePath: string,\n options: ExtractIntlayerOptions | undefined,\n {\n configuration,\n baseDir,\n appLogger,\n }: {\n configuration: IntlayerConfig;\n baseDir: string;\n appLogger: ReturnType<typeof getAppLogger>;\n },\n saveComponent: boolean\n) => {\n if (options?.onExtract) {\n for (const [key, content] of Object.entries(extractedContentMap)) {\n await options.onExtract({ key, content, filePath });\n }\n }\n\n const writeContent = !options?.codeOnly && !options?.onExtract;\n\n if (writeContent) {\n for (const [key, content] of Object.entries(extractedContentMap)) {\n const contentFilePath = await writeContentHelper(\n content,\n key,\n filePath,\n configuration\n );\n\n const relativeContentFilePath = relative(baseDir, contentFilePath);\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Created content file: ${colorizePath(relativeContentFilePath)}`\n );\n }\n }\n\n if (saveComponent) {\n const formatCommand = detectFormatCommand(configuration);\n\n if (formatCommand) {\n try {\n execSync(formatCommand.replace('{{file}}', filePath), {\n stdio: 'inherit',\n cwd: baseDir,\n });\n } catch (error) {\n console.error(error);\n }\n }\n\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Updated component: ${colorizePath(relative(baseDir, filePath))}`\n );\n }\n};\n\ntype ExtractResult = {\n transformedCode: string | null;\n extractedContentMap: Record<string, Record<string, string>>;\n};\n\ntype ExtractContext = {\n configuration: IntlayerConfig;\n appLogger: ReturnType<typeof getAppLogger>;\n baseDir: string;\n unmergedDictionaries: Record<string, unknown>;\n saveComponent: boolean;\n componentExtension: string;\n};\n\nconst buildContext = (\n filePath: string,\n options: ExtractIntlayerOptions | undefined\n): ExtractContext => {\n const configuration =\n options?.configuration ?? getConfiguration(options?.configOptions);\n const appLogger = getAppLogger(configuration);\n const { baseDir } = configuration.system;\n const unmergedDictionaries =\n options?.unmergedDictionaries ?? getUnmergedDictionaries(configuration);\n const saveComponent = !options?.declarationOnly;\n const componentExtension = extname(filePath);\n\n return {\n configuration,\n appLogger,\n baseDir,\n unmergedDictionaries,\n saveComponent,\n componentExtension,\n };\n};\n\nexport const extractContent = async (\n filePath: string,\n packageName: PackageName,\n options?: ExtractIntlayerOptions\n): Promise<ExtractResult | undefined> => {\n const {\n configuration,\n appLogger,\n baseDir,\n unmergedDictionaries,\n saveComponent,\n componentExtension,\n } = buildContext(filePath, options);\n\n if (componentExtension === '.vue' && !vueCompiler) {\n try {\n vueCompiler = await import('@intlayer/vue-compiler');\n } catch {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Install ${colorizePath('@intlayer/vue-compiler', ANSIColors.YELLOW)} to process Vue files.`\n );\n }\n }\n\n if (componentExtension === '.svelte' && !svelteCompiler) {\n try {\n svelteCompiler = await import('@intlayer/svelte-compiler');\n } catch {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Install ${colorizePath('@intlayer/svelte-compiler', ANSIColors.YELLOW)} to process Svelte files.`\n );\n }\n }\n\n const fileText = options?.code ?? readFileSync(filePath, 'utf-8');\n const dictionaryKey = extractDictionaryKey(\n filePath,\n fileText,\n configuration.compiler.dictionaryKeyPrefix\n );\n\n const result = processFileInternal(\n filePath,\n packageName,\n options,\n { vueCompiler, svelteCompiler, unmergedDictionaries, configuration },\n saveComponent,\n dictionaryKey\n );\n\n if (!result?.extractedContentMap) {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} No extractable text found in ${colorizePath(relative(baseDir, filePath))}`,\n { isVerbose: true }\n );\n return undefined;\n }\n\n await handleExtractionSideEffects(\n result.extractedContentMap,\n filePath,\n options,\n { configuration, baseDir, appLogger },\n saveComponent\n );\n\n return {\n transformedCode: result.transformedCode,\n extractedContentMap: result.extractedContentMap,\n };\n};\n\n/**\n * Synchronous variant of `extractContent` — used by the Babel plugin which\n * runs in a sync transform context (e.g. Next.js / Webpack).\n *\n * Differences from `extractContent`:\n * - Loads external compilers (Vue, Svelte) via `require()` instead of `import()`\n * - Fires `onExtract` callbacks as fire-and-forget (cannot await in sync context)\n * - Does NOT write dictionary files or run the code formatter; callers that\n * need persistence should supply an `onExtract` callback\n */\nexport const extractContentSync = (\n filePath: string,\n packageName: PackageName,\n options?: ExtractIntlayerOptions\n): ExtractResult | undefined => {\n const {\n configuration,\n appLogger,\n baseDir,\n unmergedDictionaries,\n saveComponent,\n componentExtension,\n } = buildContext(filePath, options);\n\n const requireFn = getProjectRequire();\n\n if (componentExtension === '.vue' && !vueCompiler) {\n try {\n vueCompiler = requireFn('@intlayer/vue-compiler') as VueCompiler;\n } catch {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Install ${colorizePath('@intlayer/vue-compiler', ANSIColors.YELLOW)} to process Vue files.`\n );\n }\n }\n\n if (componentExtension === '.svelte' && !svelteCompiler) {\n try {\n svelteCompiler = requireFn('@intlayer/svelte-compiler') as SvelteCompiler;\n } catch {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Install ${colorizePath('@intlayer/svelte-compiler', ANSIColors.YELLOW)} to process Svelte files.`\n );\n }\n }\n\n const result = processFileInternal(\n filePath,\n packageName,\n options,\n { vueCompiler, svelteCompiler, unmergedDictionaries, configuration },\n saveComponent\n );\n\n if (!result?.extractedContentMap) {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} No extractable text found in ${colorizePath(relative(baseDir, filePath))}`,\n { isVerbose: true }\n );\n return undefined;\n }\n\n const { extractedContentMap, transformedCode } = result;\n\n if (options?.onExtract) {\n for (const [key, content] of Object.entries(extractedContentMap)) {\n void options.onExtract({ key, content, filePath });\n }\n }\n\n return { transformedCode, extractedContentMap };\n};\n"],"mappings":"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"}
|
|
@@ -137,8 +137,19 @@ const processTsxFile = (filePath, componentKey, packageName, configuration, save
|
|
|
137
137
|
const hook = hookMap.get(componentPath.node) || "useIntlayer";
|
|
138
138
|
if (hook === "useIntlayer") needsUseIntlayer = true;
|
|
139
139
|
if (hook === "getIntlayer") needsGetIntlayer = true;
|
|
140
|
-
const bodyPath = componentPath.get("body");
|
|
141
140
|
const hookStatementStr = `\n const content = ${hook}('${finalKey}');\n`;
|
|
141
|
+
if (componentPath.isProgram()) {
|
|
142
|
+
let insertPos = 0;
|
|
143
|
+
if (componentPath.node.directives.length > 0) insertPos = componentPath.node.directives[componentPath.node.directives.length - 1].end;
|
|
144
|
+
for (const stmt of componentPath.node.body) if (_babel_types.isImportDeclaration(stmt)) insertPos = Math.max(insertPos, stmt.end);
|
|
145
|
+
textEdits.push({
|
|
146
|
+
start: insertPos,
|
|
147
|
+
end: insertPos,
|
|
148
|
+
replacement: `\n${hookStatementStr}`
|
|
149
|
+
});
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const bodyPath = componentPath.get("body");
|
|
142
153
|
if (bodyPath.isBlockStatement()) textEdits.push({
|
|
143
154
|
start: bodyPath.node.start + 1,
|
|
144
155
|
end: bodyPath.node.start + 1,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"processTsxFile.cjs","names":["_traverse","SERVER_CAPABLE_PACKAGES","extractBabelContentForComponents","getExistingIntlayerInfo","t"],"sources":["../../../src/extractContent/processTsxFile.ts"],"sourcesContent":["import { execSync } from 'node:child_process';\nimport { readFileSync, writeFileSync } from 'node:fs';\nimport { parse } from '@babel/parser';\nimport _traverse, { type NodePath } from '@babel/traverse';\nimport * as t from '@babel/types';\nimport { detectFormatCommand } from '@intlayer/chokidar/cli';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { extractBabelContentForComponents } from './babelProcessor';\nimport {\n type ExistingIntlayerInfo,\n getExistingIntlayerInfo,\n type PackageName,\n SERVER_CAPABLE_PACKAGES,\n} from './utils';\n\nexport type TextEdit = {\n start: number;\n end: number;\n replacement: string;\n};\n\nconst traverse = (\n typeof _traverse === 'function' ? _traverse : (_traverse as any).default\n) as typeof _traverse;\n\n/**\n * Processes a TSX/JSX/TS/JS file to extract content and inject Intlayer hooks/calls.\n */\nexport const processTsxFile = (\n filePath: string,\n componentKey: string,\n packageName: PackageName,\n configuration: IntlayerConfig,\n save: boolean = true,\n unmergedDictionaries: Record<string, unknown> = {},\n providedFileCode?: string\n): {\n extractedContent: Record<string, Record<string, string>>;\n modifiedCode: string;\n} | null => {\n const fileCode = providedFileCode ?? readFileSync(filePath, 'utf-8');\n\n const ast = parse(fileCode, {\n sourceType: 'module',\n plugins: ['jsx', 'typescript'],\n });\n\n const hasUseClient = ast.program.directives.some(\n (directive) => directive.value.value === 'use client'\n );\n\n const effectivePackageName =\n SERVER_CAPABLE_PACKAGES.has(packageName) && !hasUseClient\n ? `${packageName}/server`\n : packageName;\n\n // Both Solid and Angular expose content via a reactive/signal function —\n // access is `content().key` rather than `content.key`.\n const isSolid =\n packageName === 'solid-intlayer' || packageName === 'angular-intlayer';\n const existingKeys = new Set<string>();\n\n const {\n extractedContent,\n replacements,\n componentsNeedingHooks,\n componentKeyMap,\n componentPaths,\n hookMap,\n } = extractBabelContentForComponents(\n ast,\n fileCode,\n existingKeys,\n componentKey,\n configuration,\n filePath,\n unmergedDictionaries\n );\n\n if (Object.keys(extractedContent).length === 0) return null;\n\n const textEdits: TextEdit[] = [];\n\n // Build a lookup map: component AST node → NodePath (O(1) access)\n const componentNodeToPath = new Map<t.Node, NodePath>();\n for (const componentPath of componentPaths) {\n componentNodeToPath.set(componentPath.node, componentPath);\n }\n\n // Cache getExistingIntlayerInfo results for all component paths (avoids repeated traversals)\n const existingInfoCache = new Map<t.Node, ExistingIntlayerInfo | undefined>();\n for (const componentPath of componentPaths) {\n existingInfoCache.set(\n componentPath.node,\n getExistingIntlayerInfo(componentPath)\n );\n }\n\n /**\n * Walks up the ancestor chain to find the nearest enclosing component's\n * existing Intlayer info (if any).\n */\n const getExistingInfoForPath = (\n path: NodePath\n ): ExistingIntlayerInfo | undefined => {\n let current: NodePath | null = path;\n while (current) {\n if (componentNodeToPath.has(current.node)) {\n return existingInfoCache.get(current.node);\n }\n current = current.parentPath;\n }\n return undefined;\n };\n\n const getProvidingHookType = (\n path: NodePath\n ): 'useIntlayer' | 'getIntlayer' => {\n let current: NodePath | null = path;\n while (current) {\n const componentPath = componentNodeToPath.get(current.node);\n\n if (componentPath) {\n const existingInfo = existingInfoCache.get(componentPath.node);\n\n if (existingInfo) {\n return existingInfo.hook;\n }\n\n if (componentsNeedingHooks.has(componentPath)) {\n return hookMap.get(componentPath.node) || 'useIntlayer';\n }\n }\n current = current.parentPath;\n }\n return 'useIntlayer';\n };\n\n for (const {\n path,\n key,\n type,\n variables,\n childrenToReplace,\n } of replacements) {\n const existingInfo = getExistingInfoForPath(path);\n // When the existing call is destructured (e.g. `const { a } = getIntlayer(...)`),\n // new keys are added directly to that destructuring, so access them by name alone.\n const varName = existingInfo?.variableName ?? 'content';\n const contentAccessCode = existingInfo?.isDestructured\n ? key\n : isSolid\n ? `${varName}().${key}`\n : `${varName}.${key}`;\n const hookType = getProvidingHookType(path);\n const valueSuffix = hookType === 'getIntlayer' ? '' : '.value';\n\n if (type === 'jsx-text' && path.isJSXText()) {\n textEdits.push({\n start: path.node.start!,\n end: path.node.end!,\n replacement: `{${contentAccessCode}}`,\n });\n } else if (type === 'jsx-attribute' && path.isJSXAttribute()) {\n const valNode = path.node.value;\n\n if (valNode) {\n textEdits.push({\n start: valNode.start!,\n end: valNode.end!,\n replacement: `{${contentAccessCode}${valueSuffix}}`,\n });\n }\n } else if (type === 'string-literal' && path.isStringLiteral()) {\n textEdits.push({\n start: path.node.start!,\n end: path.node.end!,\n replacement: `${contentAccessCode}${valueSuffix}`,\n });\n } else if (\n type === 'jsx-text-combined' &&\n childrenToReplace &&\n childrenToReplace.length > 0\n ) {\n const accessStr = `{${contentAccessCode}}`;\n const start = childrenToReplace[0].start!;\n const end = childrenToReplace[childrenToReplace.length - 1].end!;\n textEdits.push({ start, end, replacement: accessStr });\n } else if (\n type === 'jsx-insertion' &&\n variables &&\n childrenToReplace &&\n childrenToReplace.length > 0\n ) {\n const objProps = variables\n .map((variableString) => {\n const [key, variableValue] = variableString\n .split(':')\n .map((part) => part.trim());\n return `${key}: ${variableValue}`;\n })\n .join(', ');\n\n const accessStr = `{${contentAccessCode}({ ${objProps} })}`;\n const start = childrenToReplace[0].start!;\n const end = childrenToReplace[childrenToReplace.length - 1].end!;\n textEdits.push({ start, end, replacement: accessStr });\n }\n }\n\n let needsUseIntlayer = false;\n let needsGetIntlayer = false;\n\n for (const componentPath of componentsNeedingHooks) {\n const finalKey = componentKeyMap.get(componentPath.node)!;\n const existingInfo = existingInfoCache.get(componentPath.node);\n\n if (existingInfo) {\n if (existingInfo.hook === 'useIntlayer') needsUseIntlayer = true;\n\n if (existingInfo.hook === 'getIntlayer') needsGetIntlayer = true;\n\n // When the existing call is destructured, inject any missing keys into\n // the destructuring pattern so they can be accessed by name directly.\n if (existingInfo.isDestructured && existingInfo.objectPatternNode) {\n const neededKeys = new Set<string>();\n\n for (const { path: rPath, key: rKey } of replacements) {\n let current: NodePath | null = rPath;\n\n while (current) {\n if (current.node === componentPath.node) {\n neededKeys.add(rKey);\n break;\n }\n current = current.parentPath;\n }\n }\n\n const missingKeys = [...neededKeys].filter(\n (key) => !existingInfo.existingDestructuredKeys.includes(key)\n );\n\n if (missingKeys.length > 0) {\n const { objectPatternNode } = existingInfo;\n // Insert right after the last property so the space/newline before\n // `}` is naturally preserved: `{ a }` → `{ a, b }`.\n const lastProp =\n objectPatternNode.properties[\n objectPatternNode.properties.length - 1\n ];\n textEdits.push({\n start: lastProp.end!,\n end: lastProp.end!,\n replacement: `, ${missingKeys.join(', ')}`,\n });\n }\n }\n } else {\n const hook = hookMap.get(componentPath.node) || 'useIntlayer';\n\n if (hook === 'useIntlayer') needsUseIntlayer = true;\n\n if (hook === 'getIntlayer') needsGetIntlayer = true;\n\n const bodyPath = componentPath.get('body') as NodePath;\n const hookStatementStr = `\\n const content = ${hook}('${finalKey}');\\n`;\n\n if (bodyPath.isBlockStatement()) {\n textEdits.push({\n start: bodyPath.node.start! + 1,\n end: bodyPath.node.start! + 1,\n replacement: hookStatementStr,\n });\n } else if (bodyPath.isExpression()) {\n const start = bodyPath.node.start!;\n const end = bodyPath.node.end!;\n\n // Detect wrapping parentheses: `() => (expr)` — scan backward from\n // the expression start and forward from its end for matching `(` / `)`.\n let parenStart = -1;\n let parenEnd = -1;\n\n for (let i = start - 1; i >= 0; i--) {\n const char = fileCode[i];\n if (char === '(') {\n parenStart = i;\n break;\n }\n if (char !== ' ' && char !== '\\n' && char !== '\\r' && char !== '\\t')\n break;\n }\n\n if (parenStart !== -1) {\n for (let i = end; i < fileCode.length; i++) {\n const char = fileCode[i];\n if (char === ')') {\n parenEnd = i;\n break;\n }\n if (char !== ' ' && char !== '\\n' && char !== '\\r' && char !== '\\t')\n break;\n }\n }\n\n if (parenStart !== -1 && parenEnd !== -1) {\n // Replace the surrounding `(` … `)` with a block statement. We keep\n // the parentheses as `return (…)` to prevent automatic semicolon\n // insertion when the returned expression starts on a new line.\n textEdits.push({\n start: parenEnd,\n end: parenEnd + 1,\n replacement: `)\\n}`,\n });\n textEdits.push({\n start: parenStart,\n end: parenStart + 1,\n replacement: `{\\n const content = ${hook}('${finalKey}');\\n return (`,\n });\n } else {\n textEdits.push({\n start: end,\n end: end,\n replacement: `\\n}`,\n });\n textEdits.push({\n start: start,\n end: start,\n replacement: `{\\n const content = ${hook}('${finalKey}');\\n return `,\n });\n }\n }\n }\n }\n\n const injectImport = (hookName: string, targetPackage: string) => {\n let existingImportPath: NodePath<t.ImportDeclaration> | undefined;\n\n traverse(ast, {\n ImportDeclaration(path) {\n if (path.node.source.value === targetPackage) {\n existingImportPath = path;\n path.stop();\n }\n },\n });\n\n if (!existingImportPath) {\n const newImportStr = `import { ${hookName} } from '${targetPackage}';\\n`;\n let insertPos = 0;\n\n if (ast.program.body.length > 0) {\n insertPos = ast.program.body[0].start!;\n } else if (ast.program.directives && ast.program.directives.length > 0) {\n insertPos =\n ast.program.directives[ast.program.directives.length - 1].end!;\n\n if (fileCode[insertPos] === ';') insertPos++;\n\n textEdits.push({\n start: insertPos,\n end: insertPos,\n replacement: `\\n${newImportStr}`,\n });\n return;\n }\n\n if (\n insertPos === 0 ||\n (ast.program.body.length > 0 &&\n insertPos === ast.program.body[0].start!)\n ) {\n textEdits.push({\n start: insertPos,\n end: insertPos,\n replacement: newImportStr,\n });\n }\n } else {\n const existingSpecifiers = existingImportPath.node.specifiers;\n const missingImport = !existingSpecifiers.some(\n (specifier) =>\n t.isImportSpecifier(specifier) &&\n t.isIdentifier(specifier.imported) &&\n specifier.imported.name === hookName\n );\n\n if (missingImport) {\n const importCode = fileCode.slice(\n existingImportPath.node.start!,\n existingImportPath.node.end!\n );\n const closingBraceIndex = importCode.lastIndexOf('}');\n\n if (closingBraceIndex !== -1) {\n const isCommaNeeded = !importCode\n .slice(0, closingBraceIndex)\n .trim()\n .endsWith(',');\n const absolutePos =\n existingImportPath.node.start! + closingBraceIndex;\n const prefix = isCommaNeeded ? ', ' : ' ';\n textEdits.push({\n start: absolutePos,\n end: absolutePos,\n replacement: `${prefix}${hookName} `,\n });\n }\n }\n }\n };\n\n if (needsUseIntlayer) injectImport('useIntlayer', effectivePackageName);\n\n if (needsGetIntlayer) injectImport('getIntlayer', 'intlayer');\n\n textEdits.sort((editA, editB) => {\n if (editB.start !== editA.start) return editB.start - editA.start;\n\n return editB.end - editA.end;\n });\n\n let formattedCode = fileCode;\n\n for (const edit of textEdits) {\n formattedCode =\n formattedCode.slice(0, edit.start) +\n edit.replacement +\n formattedCode.slice(edit.end);\n }\n\n if (save) {\n writeFileSync(filePath, formattedCode);\n\n const formatCommand = detectFormatCommand(configuration);\n\n if (formatCommand) {\n try {\n execSync(formatCommand.replace('{{file}}', filePath), {\n stdio: 'inherit',\n cwd: configuration.system.baseDir,\n });\n } catch (error) {\n console.error(error);\n }\n }\n }\n\n return { extractedContent, modifiedCode: formattedCode };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"processTsxFile.cjs","names":["_traverse","SERVER_CAPABLE_PACKAGES","extractBabelContentForComponents","getExistingIntlayerInfo","t"],"sources":["../../../src/extractContent/processTsxFile.ts"],"sourcesContent":["import { execSync } from 'node:child_process';\nimport { readFileSync, writeFileSync } from 'node:fs';\nimport { parse } from '@babel/parser';\nimport _traverse, { type NodePath } from '@babel/traverse';\nimport * as t from '@babel/types';\nimport { detectFormatCommand } from '@intlayer/chokidar/cli';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { extractBabelContentForComponents } from './babelProcessor';\nimport {\n type ExistingIntlayerInfo,\n getExistingIntlayerInfo,\n type PackageName,\n SERVER_CAPABLE_PACKAGES,\n} from './utils';\n\nexport type TextEdit = {\n start: number;\n end: number;\n replacement: string;\n};\n\nconst traverse = (\n typeof _traverse === 'function' ? _traverse : (_traverse as any).default\n) as typeof _traverse;\n\n/**\n * Processes a TSX/JSX/TS/JS file to extract content and inject Intlayer hooks/calls.\n */\nexport const processTsxFile = (\n filePath: string,\n componentKey: string,\n packageName: PackageName,\n configuration: IntlayerConfig,\n save: boolean = true,\n unmergedDictionaries: Record<string, unknown> = {},\n providedFileCode?: string\n): {\n extractedContent: Record<string, Record<string, string>>;\n modifiedCode: string;\n} | null => {\n const fileCode = providedFileCode ?? readFileSync(filePath, 'utf-8');\n\n const ast = parse(fileCode, {\n sourceType: 'module',\n plugins: ['jsx', 'typescript'],\n });\n\n const hasUseClient = ast.program.directives.some(\n (directive) => directive.value.value === 'use client'\n );\n\n const effectivePackageName =\n SERVER_CAPABLE_PACKAGES.has(packageName) && !hasUseClient\n ? `${packageName}/server`\n : packageName;\n\n // Both Solid and Angular expose content via a reactive/signal function —\n // access is `content().key` rather than `content.key`.\n const isSolid =\n packageName === 'solid-intlayer' || packageName === 'angular-intlayer';\n const existingKeys = new Set<string>();\n\n const {\n extractedContent,\n replacements,\n componentsNeedingHooks,\n componentKeyMap,\n componentPaths,\n hookMap,\n } = extractBabelContentForComponents(\n ast,\n fileCode,\n existingKeys,\n componentKey,\n configuration,\n filePath,\n unmergedDictionaries\n );\n\n if (Object.keys(extractedContent).length === 0) return null;\n\n const textEdits: TextEdit[] = [];\n\n // Build a lookup map: component AST node → NodePath (O(1) access)\n const componentNodeToPath = new Map<t.Node, NodePath>();\n for (const componentPath of componentPaths) {\n componentNodeToPath.set(componentPath.node, componentPath);\n }\n\n // Cache getExistingIntlayerInfo results for all component paths (avoids repeated traversals)\n const existingInfoCache = new Map<t.Node, ExistingIntlayerInfo | undefined>();\n for (const componentPath of componentPaths) {\n existingInfoCache.set(\n componentPath.node,\n getExistingIntlayerInfo(componentPath)\n );\n }\n\n /**\n * Walks up the ancestor chain to find the nearest enclosing component's\n * existing Intlayer info (if any).\n */\n const getExistingInfoForPath = (\n path: NodePath\n ): ExistingIntlayerInfo | undefined => {\n let current: NodePath | null = path;\n while (current) {\n if (componentNodeToPath.has(current.node)) {\n return existingInfoCache.get(current.node);\n }\n current = current.parentPath;\n }\n return undefined;\n };\n\n const getProvidingHookType = (\n path: NodePath\n ): 'useIntlayer' | 'getIntlayer' => {\n let current: NodePath | null = path;\n while (current) {\n const componentPath = componentNodeToPath.get(current.node);\n\n if (componentPath) {\n const existingInfo = existingInfoCache.get(componentPath.node);\n\n if (existingInfo) {\n return existingInfo.hook;\n }\n\n if (componentsNeedingHooks.has(componentPath)) {\n return hookMap.get(componentPath.node) || 'useIntlayer';\n }\n }\n current = current.parentPath;\n }\n return 'useIntlayer';\n };\n\n for (const {\n path,\n key,\n type,\n variables,\n childrenToReplace,\n } of replacements) {\n const existingInfo = getExistingInfoForPath(path);\n // When the existing call is destructured (e.g. `const { a } = getIntlayer(...)`),\n // new keys are added directly to that destructuring, so access them by name alone.\n const varName = existingInfo?.variableName ?? 'content';\n const contentAccessCode = existingInfo?.isDestructured\n ? key\n : isSolid\n ? `${varName}().${key}`\n : `${varName}.${key}`;\n const hookType = getProvidingHookType(path);\n const valueSuffix = hookType === 'getIntlayer' ? '' : '.value';\n\n if (type === 'jsx-text' && path.isJSXText()) {\n textEdits.push({\n start: path.node.start!,\n end: path.node.end!,\n replacement: `{${contentAccessCode}}`,\n });\n } else if (type === 'jsx-attribute' && path.isJSXAttribute()) {\n const valNode = path.node.value;\n\n if (valNode) {\n textEdits.push({\n start: valNode.start!,\n end: valNode.end!,\n replacement: `{${contentAccessCode}${valueSuffix}}`,\n });\n }\n } else if (type === 'string-literal' && path.isStringLiteral()) {\n textEdits.push({\n start: path.node.start!,\n end: path.node.end!,\n replacement: `${contentAccessCode}${valueSuffix}`,\n });\n } else if (\n type === 'jsx-text-combined' &&\n childrenToReplace &&\n childrenToReplace.length > 0\n ) {\n const accessStr = `{${contentAccessCode}}`;\n const start = childrenToReplace[0].start!;\n const end = childrenToReplace[childrenToReplace.length - 1].end!;\n textEdits.push({ start, end, replacement: accessStr });\n } else if (\n type === 'jsx-insertion' &&\n variables &&\n childrenToReplace &&\n childrenToReplace.length > 0\n ) {\n const objProps = variables\n .map((variableString) => {\n const [key, variableValue] = variableString\n .split(':')\n .map((part) => part.trim());\n return `${key}: ${variableValue}`;\n })\n .join(', ');\n\n const accessStr = `{${contentAccessCode}({ ${objProps} })}`;\n const start = childrenToReplace[0].start!;\n const end = childrenToReplace[childrenToReplace.length - 1].end!;\n textEdits.push({ start, end, replacement: accessStr });\n }\n }\n\n let needsUseIntlayer = false;\n let needsGetIntlayer = false;\n\n for (const componentPath of componentsNeedingHooks) {\n const finalKey = componentKeyMap.get(componentPath.node)!;\n const existingInfo = existingInfoCache.get(componentPath.node);\n\n if (existingInfo) {\n if (existingInfo.hook === 'useIntlayer') needsUseIntlayer = true;\n\n if (existingInfo.hook === 'getIntlayer') needsGetIntlayer = true;\n\n // When the existing call is destructured, inject any missing keys into\n // the destructuring pattern so they can be accessed by name directly.\n if (existingInfo.isDestructured && existingInfo.objectPatternNode) {\n const neededKeys = new Set<string>();\n\n for (const { path: rPath, key: rKey } of replacements) {\n let current: NodePath | null = rPath;\n\n while (current) {\n if (current.node === componentPath.node) {\n neededKeys.add(rKey);\n break;\n }\n current = current.parentPath;\n }\n }\n\n const missingKeys = [...neededKeys].filter(\n (key) => !existingInfo.existingDestructuredKeys.includes(key)\n );\n\n if (missingKeys.length > 0) {\n const { objectPatternNode } = existingInfo;\n // Insert right after the last property so the space/newline before\n // `}` is naturally preserved: `{ a }` → `{ a, b }`.\n const lastProp =\n objectPatternNode.properties[\n objectPatternNode.properties.length - 1\n ];\n textEdits.push({\n start: lastProp.end!,\n end: lastProp.end!,\n replacement: `, ${missingKeys.join(', ')}`,\n });\n }\n }\n } else {\n const hook = hookMap.get(componentPath.node) || 'useIntlayer';\n\n if (hook === 'useIntlayer') needsUseIntlayer = true;\n\n if (hook === 'getIntlayer') needsGetIntlayer = true;\n\n const hookStatementStr = `\\n const content = ${hook}('${finalKey}');\\n`;\n\n if (componentPath.isProgram()) {\n // Find the last import or directive to inject the getIntlayer call\n let insertPos = 0;\n if (componentPath.node.directives.length > 0) {\n insertPos =\n componentPath.node.directives[\n componentPath.node.directives.length - 1\n ].end!;\n }\n for (const stmt of componentPath.node.body) {\n if (t.isImportDeclaration(stmt)) {\n insertPos = Math.max(insertPos, stmt.end!);\n }\n }\n\n textEdits.push({\n start: insertPos,\n end: insertPos,\n replacement: `\\n${hookStatementStr}`,\n });\n continue;\n }\n\n const bodyPath = componentPath.get('body') as NodePath;\n\n if (bodyPath.isBlockStatement()) {\n textEdits.push({\n start: bodyPath.node.start! + 1,\n end: bodyPath.node.start! + 1,\n replacement: hookStatementStr,\n });\n } else if (bodyPath.isExpression()) {\n const start = bodyPath.node.start!;\n const end = bodyPath.node.end!;\n\n // Detect wrapping parentheses: `() => (expr)` — scan backward from\n // the expression start and forward from its end for matching `(` / `)`.\n let parenStart = -1;\n let parenEnd = -1;\n\n for (let i = start - 1; i >= 0; i--) {\n const char = fileCode[i];\n if (char === '(') {\n parenStart = i;\n break;\n }\n if (char !== ' ' && char !== '\\n' && char !== '\\r' && char !== '\\t')\n break;\n }\n\n if (parenStart !== -1) {\n for (let i = end; i < fileCode.length; i++) {\n const char = fileCode[i];\n if (char === ')') {\n parenEnd = i;\n break;\n }\n if (char !== ' ' && char !== '\\n' && char !== '\\r' && char !== '\\t')\n break;\n }\n }\n\n if (parenStart !== -1 && parenEnd !== -1) {\n // Replace the surrounding `(` … `)` with a block statement. We keep\n // the parentheses as `return (…)` to prevent automatic semicolon\n // insertion when the returned expression starts on a new line.\n textEdits.push({\n start: parenEnd,\n end: parenEnd + 1,\n replacement: `)\\n}`,\n });\n textEdits.push({\n start: parenStart,\n end: parenStart + 1,\n replacement: `{\\n const content = ${hook}('${finalKey}');\\n return (`,\n });\n } else {\n textEdits.push({\n start: end,\n end: end,\n replacement: `\\n}`,\n });\n textEdits.push({\n start: start,\n end: start,\n replacement: `{\\n const content = ${hook}('${finalKey}');\\n return `,\n });\n }\n }\n }\n }\n\n const injectImport = (hookName: string, targetPackage: string) => {\n let existingImportPath: NodePath<t.ImportDeclaration> | undefined;\n\n traverse(ast, {\n ImportDeclaration(path) {\n if (path.node.source.value === targetPackage) {\n existingImportPath = path;\n path.stop();\n }\n },\n });\n\n if (!existingImportPath) {\n const newImportStr = `import { ${hookName} } from '${targetPackage}';\\n`;\n let insertPos = 0;\n\n if (ast.program.body.length > 0) {\n insertPos = ast.program.body[0].start!;\n } else if (ast.program.directives && ast.program.directives.length > 0) {\n insertPos =\n ast.program.directives[ast.program.directives.length - 1].end!;\n\n if (fileCode[insertPos] === ';') insertPos++;\n\n textEdits.push({\n start: insertPos,\n end: insertPos,\n replacement: `\\n${newImportStr}`,\n });\n return;\n }\n\n if (\n insertPos === 0 ||\n (ast.program.body.length > 0 &&\n insertPos === ast.program.body[0].start!)\n ) {\n textEdits.push({\n start: insertPos,\n end: insertPos,\n replacement: newImportStr,\n });\n }\n } else {\n const existingSpecifiers = existingImportPath.node.specifiers;\n const missingImport = !existingSpecifiers.some(\n (specifier) =>\n t.isImportSpecifier(specifier) &&\n t.isIdentifier(specifier.imported) &&\n specifier.imported.name === hookName\n );\n\n if (missingImport) {\n const importCode = fileCode.slice(\n existingImportPath.node.start!,\n existingImportPath.node.end!\n );\n const closingBraceIndex = importCode.lastIndexOf('}');\n\n if (closingBraceIndex !== -1) {\n const isCommaNeeded = !importCode\n .slice(0, closingBraceIndex)\n .trim()\n .endsWith(',');\n const absolutePos =\n existingImportPath.node.start! + closingBraceIndex;\n const prefix = isCommaNeeded ? ', ' : ' ';\n textEdits.push({\n start: absolutePos,\n end: absolutePos,\n replacement: `${prefix}${hookName} `,\n });\n }\n }\n }\n };\n\n if (needsUseIntlayer) injectImport('useIntlayer', effectivePackageName);\n\n if (needsGetIntlayer) injectImport('getIntlayer', 'intlayer');\n\n textEdits.sort((editA, editB) => {\n if (editB.start !== editA.start) return editB.start - editA.start;\n\n return editB.end - editA.end;\n });\n\n let formattedCode = fileCode;\n\n for (const edit of textEdits) {\n formattedCode =\n formattedCode.slice(0, edit.start) +\n edit.replacement +\n formattedCode.slice(edit.end);\n }\n\n if (save) {\n writeFileSync(filePath, formattedCode);\n\n const formatCommand = detectFormatCommand(configuration);\n\n if (formatCommand) {\n try {\n execSync(formatCommand.replace('{{file}}', filePath), {\n stdio: 'inherit',\n cwd: configuration.system.baseDir,\n });\n } catch (error) {\n console.error(error);\n }\n }\n }\n\n return { extractedContent, modifiedCode: formattedCode };\n};\n"],"mappings":"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"}
|
|
@@ -4,6 +4,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
4
4
|
const generateKey = (text, existingKeys) => {
|
|
5
5
|
let key = text.normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[\s_-]+/g, " ").replace(/[^\p{L}\p{N} ]/gu, "").trim().split(" ").filter(Boolean).slice(0, 5).map((word, index) => index === 0 ? word.toLowerCase() : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
|
|
6
6
|
if (!key) key = "content";
|
|
7
|
+
if (/^[0-9]/.test(key)) key = `x${key}`;
|
|
7
8
|
if (existingKeys.has(key)) {
|
|
8
9
|
let i = 1;
|
|
9
10
|
while (existingKeys.has(`${key}${i}`)) i++;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generateKey.cjs","names":[],"sources":["../../../../src/extractContent/utils/generateKey.ts"],"sourcesContent":["export const generateKey = (\n text: string,\n existingKeys: Set<string>\n): string => {\n const maxWords = 5;\n let key = text\n .normalize('NFD') // Normalize to decomposes combined characters (e.g., é -> e + ´)\n .replace(/[\\u0300-\\u036f]/g, '') // Remove the accent characters\n .replace(/[\\s_-]+/g, ' ')\n .replace(/[^\\p{L}\\p{N} ]/gu, '')\n .trim()\n .split(' ')\n .filter(Boolean)\n .slice(0, maxWords)\n .map((word, index) =>\n index === 0\n ? word.toLowerCase()\n : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()\n )\n .join('');\n\n if (!key) key = 'content';\n if (existingKeys.has(key)) {\n let i = 1;\n while (existingKeys.has(`${key}${i}`)) i++;\n key = `${key}${i}`;\n }\n return key;\n};\n"],"mappings":";;;AAAA,MAAa,eACX,MACA,iBACW;CAEX,IAAI,MAAM,KACP,UAAU,MAAM,CAChB,QAAQ,oBAAoB,GAAG,CAC/B,QAAQ,YAAY,IAAI,CACxB,QAAQ,oBAAoB,GAAG,CAC/B,MAAM,CACN,MAAM,IAAI,CACV,OAAO,QAAQ,CACf,MAAM,GATQ,EASI,CAClB,KAAK,MAAM,UACV,UAAU,IACN,KAAK,aAAa,GAClB,KAAK,OAAO,EAAE,CAAC,aAAa,GAAG,KAAK,MAAM,EAAE,CAAC,aAAa,CAC/D,CACA,KAAK,GAAG;AAEX,KAAI,CAAC,IAAK,OAAM;
|
|
1
|
+
{"version":3,"file":"generateKey.cjs","names":[],"sources":["../../../../src/extractContent/utils/generateKey.ts"],"sourcesContent":["export const generateKey = (\n text: string,\n existingKeys: Set<string>\n): string => {\n const maxWords = 5;\n let key = text\n .normalize('NFD') // Normalize to decomposes combined characters (e.g., é -> e + ´)\n .replace(/[\\u0300-\\u036f]/g, '') // Remove the accent characters\n .replace(/[\\s_-]+/g, ' ')\n .replace(/[^\\p{L}\\p{N} ]/gu, '')\n .trim()\n .split(' ')\n .filter(Boolean)\n .slice(0, maxWords)\n .map((word, index) =>\n index === 0\n ? word.toLowerCase()\n : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()\n )\n .join('');\n\n if (!key) key = 'content';\n\n // If the key starts with a number, prepend 'x' to make it a valid JS identifier\n if (/^[0-9]/.test(key)) {\n key = `x${key}`;\n }\n\n if (existingKeys.has(key)) {\n let i = 1;\n while (existingKeys.has(`${key}${i}`)) i++;\n key = `${key}${i}`;\n }\n return key;\n};\n"],"mappings":";;;AAAA,MAAa,eACX,MACA,iBACW;CAEX,IAAI,MAAM,KACP,UAAU,MAAM,CAChB,QAAQ,oBAAoB,GAAG,CAC/B,QAAQ,YAAY,IAAI,CACxB,QAAQ,oBAAoB,GAAG,CAC/B,MAAM,CACN,MAAM,IAAI,CACV,OAAO,QAAQ,CACf,MAAM,GATQ,EASI,CAClB,KAAK,MAAM,UACV,UAAU,IACN,KAAK,aAAa,GAClB,KAAK,OAAO,EAAE,CAAC,aAAa,GAAG,KAAK,MAAM,EAAE,CAAC,aAAa,CAC/D,CACA,KAAK,GAAG;AAEX,KAAI,CAAC,IAAK,OAAM;AAGhB,KAAI,SAAS,KAAK,IAAI,CACpB,OAAM,IAAI;AAGZ,KAAI,aAAa,IAAI,IAAI,EAAE;EACzB,IAAI,IAAI;AACR,SAAO,aAAa,IAAI,GAAG,MAAM,IAAI,CAAE;AACvC,QAAM,GAAG,MAAM;;AAEjB,QAAO"}
|
|
@@ -6,16 +6,17 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
6
6
|
*
|
|
7
7
|
* Filters out:
|
|
8
8
|
* - Empty strings
|
|
9
|
-
* -
|
|
10
|
-
* -
|
|
9
|
+
* - Emails
|
|
10
|
+
* - Uncapitalized strings of 2 words or fewer (likely technical terms)
|
|
11
11
|
* - Dynamic content patterns like Vue bindings (`v-`) or object patterns (`{`)
|
|
12
12
|
*/
|
|
13
13
|
const shouldExtract = (text) => {
|
|
14
14
|
const trimmed = text.trim();
|
|
15
15
|
if (!trimmed) return false;
|
|
16
|
-
if (
|
|
17
|
-
if (!/^[A-Z]/.test(trimmed)) return false;
|
|
16
|
+
if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) return false;
|
|
18
17
|
if (trimmed.startsWith("{") || trimmed.startsWith("v-")) return false;
|
|
18
|
+
const wordCount = trimmed.split(/\s+/).length;
|
|
19
|
+
if (!/^[A-Z]/.test(trimmed) && wordCount <= 2) return false;
|
|
19
20
|
return true;
|
|
20
21
|
};
|
|
21
22
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shouldExtract.cjs","names":[],"sources":["../../../../src/extractContent/utils/shouldExtract.ts"],"sourcesContent":["/**\n * Checks whether the given text should be extracted as a translatable string.\n *\n * Filters out:\n * - Empty strings\n * -
|
|
1
|
+
{"version":3,"file":"shouldExtract.cjs","names":[],"sources":["../../../../src/extractContent/utils/shouldExtract.ts"],"sourcesContent":["/**\n * Checks whether the given text should be extracted as a translatable string.\n *\n * Filters out:\n * - Empty strings\n * - Emails\n * - Uncapitalized strings of 2 words or fewer (likely technical terms)\n * - Dynamic content patterns like Vue bindings (`v-`) or object patterns (`{`)\n */\nexport const shouldExtract = (text: string): boolean => {\n const trimmed = text.trim();\n\n if (!trimmed) return false;\n\n // Ignore emails\n if (/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(trimmed)) return false;\n\n // Ignore dynamic content patterns\n if (trimmed.startsWith('{') || trimmed.startsWith('v-')) return false;\n\n const wordCount = trimmed.split(/\\s+/).length;\n const isCapitalized = /^[A-Z]/.test(trimmed);\n\n // We usually want to extract full sentences or labels, not single/short technical words.\n // Extract if capitalized, or if it contains more than 2 words.\n if (!isCapitalized && wordCount <= 2) return false;\n\n return true;\n};\n"],"mappings":";;;;;;;;;;;;AASA,MAAa,iBAAiB,SAA0B;CACtD,MAAM,UAAU,KAAK,MAAM;AAE3B,KAAI,CAAC,QAAS,QAAO;AAGrB,KAAI,6BAA6B,KAAK,QAAQ,CAAE,QAAO;AAGvD,KAAI,QAAQ,WAAW,IAAI,IAAI,QAAQ,WAAW,KAAK,CAAE,QAAO;CAEhE,MAAM,YAAY,QAAQ,MAAM,MAAM,CAAC;AAKvC,KAAI,CAJkB,SAAS,KAAK,QAAQ,IAItB,aAAa,EAAG,QAAO;AAE7C,QAAO"}
|
|
@@ -97,6 +97,9 @@ const extractBabelContentForComponents = (ast, fileCode, existingKeys, defaultKe
|
|
|
97
97
|
let globalFileKey;
|
|
98
98
|
const componentPaths = [];
|
|
99
99
|
traverse(ast, {
|
|
100
|
+
Program(path) {
|
|
101
|
+
componentPaths.push(path);
|
|
102
|
+
},
|
|
100
103
|
FunctionDeclaration(path) {
|
|
101
104
|
componentPaths.push(path);
|
|
102
105
|
},
|
|
@@ -113,6 +116,13 @@ const extractBabelContentForComponents = (ast, fileCode, existingKeys, defaultKe
|
|
|
113
116
|
componentKeyMap.set(path.node, existingInfo.key);
|
|
114
117
|
usedKeysInFile.add(existingInfo.key);
|
|
115
118
|
hookMap.set(path.node, existingInfo.hook);
|
|
119
|
+
} else if (path.isProgram()) {
|
|
120
|
+
if (!globalFileKey) {
|
|
121
|
+
globalFileKey = resolveDictionaryKey(defaultKey, filePath, configuration, unmergedDictionaries, usedKeysInFile);
|
|
122
|
+
usedKeysInFile.add(globalFileKey);
|
|
123
|
+
}
|
|
124
|
+
componentKeyMap.set(path.node, globalFileKey);
|
|
125
|
+
hookMap.set(path.node, "getIntlayer");
|
|
116
126
|
} else {
|
|
117
127
|
if (!globalFileKey) {
|
|
118
128
|
globalFileKey = resolveDictionaryKey(defaultKey, filePath, configuration, unmergedDictionaries, usedKeysInFile);
|
|
@@ -121,7 +131,8 @@ const extractBabelContentForComponents = (ast, fileCode, existingKeys, defaultKe
|
|
|
121
131
|
componentKeyMap.set(path.node, globalFileKey);
|
|
122
132
|
const compName = getComponentName(path);
|
|
123
133
|
const isComponent = compName ? /^[A-Z]/.test(compName) : false;
|
|
124
|
-
|
|
134
|
+
const isHook = compName ? /^use[A-Z]/.test(compName) : false;
|
|
135
|
+
hookMap.set(path.node, isComponent || isHook ? "useIntlayer" : "getIntlayer");
|
|
125
136
|
}
|
|
126
137
|
}
|
|
127
138
|
const getComponentKeyForPath = (path) => {
|
|
@@ -194,39 +205,53 @@ const extractBabelContentForComponents = (ast, fileCode, existingKeys, defaultKe
|
|
|
194
205
|
}
|
|
195
206
|
});
|
|
196
207
|
const componentsNeedingHooks = /* @__PURE__ */ new Set();
|
|
197
|
-
for (const componentPath of componentPaths)
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
208
|
+
for (const componentPath of componentPaths) {
|
|
209
|
+
if (componentPath.isProgram()) {
|
|
210
|
+
if (replacements.some((replacement) => {
|
|
211
|
+
let current = replacement.path;
|
|
212
|
+
while (current) {
|
|
213
|
+
if (current.node === componentPath.node) return true;
|
|
214
|
+
if (componentPaths.some((p) => p !== componentPath && p.node === current?.node)) return false;
|
|
215
|
+
current = current.parentPath;
|
|
216
|
+
}
|
|
217
|
+
return false;
|
|
218
|
+
})) componentsNeedingHooks.add(componentPath);
|
|
219
|
+
continue;
|
|
202
220
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
221
|
+
if (replacements.some((replacement) => {
|
|
222
|
+
let current = replacement.path;
|
|
223
|
+
while (current) {
|
|
224
|
+
if (current.node === componentPath.node) return true;
|
|
225
|
+
current = current.parentPath;
|
|
226
|
+
}
|
|
227
|
+
return false;
|
|
228
|
+
})) {
|
|
229
|
+
const key = componentKeyMap.get(componentPath.node);
|
|
230
|
+
let ancestorProvidesKey = false;
|
|
231
|
+
let currentPath = componentPath.parentPath;
|
|
232
|
+
while (currentPath) {
|
|
233
|
+
const ancestorPath = componentPaths.find((path) => path.node === currentPath?.node);
|
|
234
|
+
if (ancestorPath && !ancestorPath.isProgram()) {
|
|
235
|
+
if (componentKeyMap.get(ancestorPath.node) === key) {
|
|
236
|
+
const ancestorHasReplacements = replacements.some((replacement) => {
|
|
237
|
+
let rPath = replacement.path;
|
|
238
|
+
while (rPath) {
|
|
239
|
+
if (rPath.node === ancestorPath.node) return true;
|
|
240
|
+
rPath = rPath.parentPath;
|
|
241
|
+
}
|
|
242
|
+
return false;
|
|
243
|
+
});
|
|
244
|
+
const existingInfo = getExistingIntlayerInfo(ancestorPath);
|
|
245
|
+
if (ancestorHasReplacements || existingInfo) {
|
|
246
|
+
ancestorProvidesKey = true;
|
|
247
|
+
break;
|
|
217
248
|
}
|
|
218
|
-
return false;
|
|
219
|
-
});
|
|
220
|
-
const existingInfo = getExistingIntlayerInfo(ancestorPath);
|
|
221
|
-
if (ancestorHasReplacements || existingInfo) {
|
|
222
|
-
ancestorProvidesKey = true;
|
|
223
|
-
break;
|
|
224
249
|
}
|
|
225
250
|
}
|
|
251
|
+
currentPath = currentPath.parentPath;
|
|
226
252
|
}
|
|
227
|
-
|
|
253
|
+
if (!ancestorProvidesKey) componentsNeedingHooks.add(componentPath);
|
|
228
254
|
}
|
|
229
|
-
if (!ancestorProvidesKey) componentsNeedingHooks.add(componentPath);
|
|
230
255
|
}
|
|
231
256
|
return {
|
|
232
257
|
extractedContent,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"babelProcessor.mjs","names":[],"sources":["../../../src/extractContent/babelProcessor.ts"],"sourcesContent":["import _traverse, { type NodePath } from '@babel/traverse';\nimport * as t from '@babel/types';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { resolveDictionaryKey } from '../extractContent/utils';\nimport {\n ATTRIBUTES_TO_EXTRACT,\n getComponentName,\n getExistingIntlayerInfo,\n getOrGenerateKey,\n shouldExtract,\n} from './utils';\n\nexport type BabelReplacement = {\n path: NodePath;\n key: string;\n type:\n | 'jsx-text'\n | 'jsx-attribute'\n | 'string-literal'\n | 'jsx-insertion'\n | 'jsx-text-combined';\n componentKey: string;\n childrenToReplace?: t.Node[];\n variables?: string[];\n};\n\n// CJS/ESM interop: @babel/traverse exports its function as `.default` in CJS bundles\nconst traverse = (\n typeof _traverse === 'function' ? _traverse : (_traverse as any).default\n) as typeof _traverse;\n\n/**\n * Handles JSX insertions (elements with multiple children, including expressions).\n * Replaces complex JSX structures with variable-based translations.\n */\nexport const handleJsxInsertionBabel = (\n path: NodePath<t.JSXElement | t.JSXFragment>,\n fileCode: string,\n existingKeys: Set<string>,\n getComponentKeyForPath: (path: NodePath) => string,\n extractedContent: Record<string, Record<string, string>>,\n replacements: BabelReplacement[],\n handledNodes: Set<t.Node>\n): boolean => {\n const children = path.node.children;\n\n if (children.length <= 1) return false;\n\n const parts: {\n type: 'text' | 'var';\n value: string;\n originalExpr?: string;\n }[] = [];\n let hasSignificantText = false;\n let hasVariables = false;\n\n for (const child of children) {\n if (t.isJSXText(child)) {\n const text = child.value;\n\n if (text.trim().length > 0) hasSignificantText = true;\n\n parts.push({ type: 'text', value: text });\n } else if (t.isJSXExpressionContainer(child)) {\n if (t.isJSXEmptyExpression(child.expression)) {\n parts.push({ type: 'text', value: '' });\n } else {\n const expr = child.expression;\n\n if (t.isIdentifier(expr)) {\n parts.push({\n type: 'var',\n value: expr.name,\n originalExpr: expr.name,\n });\n hasVariables = true;\n } else if (t.isMemberExpression(expr)) {\n const code = fileCode.substring(expr.start!, expr.end!);\n\n const varName = t.isIdentifier(expr.property)\n ? expr.property.name\n : 'var';\n\n parts.push({ type: 'var', value: varName, originalExpr: code });\n\n hasVariables = true;\n } else {\n return false;\n }\n }\n } else {\n return false;\n }\n }\n\n if (!hasSignificantText) return false;\n\n let combinedString = '';\n for (const part of parts) {\n if (part.type === 'var') combinedString += `{{${part.value}}}`;\n else combinedString += part.value;\n }\n\n const cleanString = combinedString.replace(/\\s+/g, ' ').trim();\n\n if (shouldExtract(cleanString)) {\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n cleanString,\n componentKey,\n existingKeys,\n extractedContent\n );\n\n const varMap = parts\n .filter((part) => part.type === 'var')\n .map((part) => `${part.value}: ${part.originalExpr}`);\n const uniqueVars = Array.from(new Set(varMap));\n\n if (hasVariables) {\n replacements.push({\n path,\n key,\n type: 'jsx-insertion',\n componentKey,\n childrenToReplace: children,\n variables: uniqueVars,\n });\n } else {\n replacements.push({\n path,\n key,\n type: 'jsx-text-combined',\n componentKey,\n childrenToReplace: children,\n });\n }\n\n children.forEach((child) => {\n handledNodes.add(child);\n });\n return true;\n }\n\n return false;\n};\n\n/**\n * Traverses the AST to identify components and extract content.\n * Returns extraction results and metadata about which components need Intlayer hooks.\n */\nexport const extractBabelContentForComponents = (\n ast: t.File,\n fileCode: string,\n existingKeys: Set<string>,\n defaultKey: string = 'default',\n configuration: IntlayerConfig,\n filePath: string,\n unmergedDictionaries: Record<string, unknown> = {}\n): {\n extractedContent: Record<string, Record<string, string>>;\n replacements: BabelReplacement[];\n componentsNeedingHooks: Set<NodePath>;\n componentKeyMap: Map<t.Node, string>;\n componentPaths: NodePath[];\n hookMap: Map<t.Node, 'useIntlayer' | 'getIntlayer'>;\n isSolid: boolean;\n} => {\n const extractedContent: Record<string, Record<string, string>> = {};\n const replacements: BabelReplacement[] = [];\n const handledNodes = new Set<t.Node>();\n const componentKeyMap = new Map<t.Node, string>();\n const hookMap = new Map<t.Node, 'useIntlayer' | 'getIntlayer'>();\n const usedKeysInFile = new Set<string>();\n let globalFileKey: string | undefined;\n\n const componentPaths: NodePath[] = [];\n\n traverse(ast, {\n FunctionDeclaration(path) {\n componentPaths.push(path);\n },\n ArrowFunctionExpression(path) {\n componentPaths.push(path);\n },\n FunctionExpression(path) {\n componentPaths.push(path);\n },\n });\n\n for (const path of componentPaths) {\n const existingInfo = getExistingIntlayerInfo(path);\n\n if (existingInfo) {\n componentKeyMap.set(path.node, existingInfo.key);\n usedKeysInFile.add(existingInfo.key);\n hookMap.set(path.node, existingInfo.hook);\n } else {\n if (!globalFileKey) {\n globalFileKey = resolveDictionaryKey(\n defaultKey,\n filePath,\n configuration,\n unmergedDictionaries,\n usedKeysInFile\n );\n usedKeysInFile.add(globalFileKey);\n }\n componentKeyMap.set(path.node, globalFileKey);\n\n const compName = getComponentName(path);\n const isComponent = compName ? /^[A-Z]/.test(compName) : false;\n hookMap.set(path.node, isComponent ? 'useIntlayer' : 'getIntlayer');\n }\n }\n\n const getComponentKeyForPath = (path: NodePath): string => {\n let current: NodePath | null = path;\n while (current) {\n if (componentKeyMap.has(current.node)) {\n return componentKeyMap.get(current.node)!;\n }\n current = current.parentPath;\n }\n return globalFileKey || defaultKey;\n };\n\n traverse(ast, {\n JSXElement(path) {\n if (handledNodes.has(path.node)) return;\n\n handleJsxInsertionBabel(\n path,\n fileCode,\n existingKeys,\n getComponentKeyForPath,\n extractedContent,\n replacements,\n handledNodes\n );\n },\n JSXFragment(path) {\n if (handledNodes.has(path.node)) return;\n\n handleJsxInsertionBabel(\n path,\n fileCode,\n existingKeys,\n getComponentKeyForPath,\n extractedContent,\n replacements,\n handledNodes\n );\n },\n JSXText(path) {\n if (handledNodes.has(path.node)) return;\n\n const text = path.node.value;\n\n if (shouldExtract(text)) {\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n text.replace(/\\s+/g, ' ').trim(),\n componentKey,\n existingKeys,\n extractedContent\n );\n replacements.push({ path, key, type: 'jsx-text', componentKey });\n }\n },\n JSXAttribute(path) {\n if (handledNodes.has(path.node)) return;\n\n const name = path.node.name.name;\n\n if (\n typeof name !== 'string' ||\n !ATTRIBUTES_TO_EXTRACT.includes(name as any)\n )\n return;\n const value = path.node.value;\n\n if (t.isStringLiteral(value) && shouldExtract(value.value)) {\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n value.value.trim(),\n componentKey,\n existingKeys,\n extractedContent\n );\n replacements.push({ path, key, type: 'jsx-attribute', componentKey });\n }\n },\n StringLiteral(path) {\n if (handledNodes.has(path.node)) return;\n\n const text = path.node.value;\n\n if (!shouldExtract(text)) return;\n\n const parent = path.parentPath;\n\n if (\n parent.isImportDeclaration() ||\n parent.isImportSpecifier() ||\n parent.isExportDeclaration()\n )\n return;\n\n if (parent.isJSXAttribute()) return;\n\n if (\n parent.isCallExpression() &&\n t.isMemberExpression(parent.node.callee)\n ) {\n if (\n t.isIdentifier(parent.node.callee.object) &&\n parent.node.callee.object.name === 'console' &&\n t.isIdentifier(parent.node.callee.property) &&\n parent.node.callee.property.name === 'log'\n ) {\n return;\n }\n }\n\n if (parent.isObjectProperty() && parent.node.key === path.node) return;\n\n if (parent.isMemberExpression() && parent.node.property === path.node)\n return;\n\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n text.trim(),\n componentKey,\n existingKeys,\n extractedContent\n );\n replacements.push({ path, key, type: 'string-literal', componentKey });\n },\n });\n\n const componentsNeedingHooks = new Set<NodePath>();\n for (const componentPath of componentPaths) {\n const hasReplacements = replacements.some((replacement) => {\n let current: NodePath | null = replacement.path;\n while (current) {\n if (current.node === componentPath.node) return true;\n\n current = current.parentPath;\n }\n return false;\n });\n\n if (hasReplacements) {\n const key = componentKeyMap.get(componentPath.node)!;\n let ancestorProvidesKey = false;\n let currentPath: NodePath | null = componentPath.parentPath;\n while (currentPath) {\n const ancestorPath = componentPaths.find(\n (path) => path.node === currentPath?.node\n );\n\n if (ancestorPath) {\n const ancestorKey = componentKeyMap.get(ancestorPath.node);\n\n if (ancestorKey === key) {\n const ancestorHasReplacements = replacements.some((replacement) => {\n let rPath: NodePath | null = replacement.path;\n while (rPath) {\n if (rPath.node === ancestorPath.node) return true;\n\n rPath = rPath.parentPath;\n }\n return false;\n });\n const existingInfo = getExistingIntlayerInfo(ancestorPath);\n\n if (ancestorHasReplacements || existingInfo) {\n ancestorProvidesKey = true;\n break;\n }\n }\n }\n currentPath = currentPath.parentPath;\n }\n\n if (!ancestorProvidesKey) {\n componentsNeedingHooks.add(componentPath);\n }\n }\n }\n\n return {\n extractedContent,\n replacements,\n componentsNeedingHooks,\n componentKeyMap,\n componentPaths,\n hookMap,\n isSolid: false,\n };\n};\n\n/**\n * High-level function to extract content from TS/JS/JSX/TSX AST.\n */\nexport const extractTsContent = (\n ast: t.File,\n fileCode: string,\n existingKeys: Set<string>,\n configuration: IntlayerConfig,\n filePath: string,\n unmergedDictionaries: Record<string, unknown> = {}\n): {\n extractedContent: Record<string, string>;\n replacements: BabelReplacement[];\n} => {\n const { extractedContent, replacements } = extractBabelContentForComponents(\n ast,\n fileCode,\n existingKeys,\n 'default',\n configuration,\n filePath,\n unmergedDictionaries\n );\n\n const flatContent: Record<string, string> = {};\n for (const group of Object.values(extractedContent)) {\n Object.assign(flatContent, group);\n }\n\n return { extractedContent: flatContent, replacements };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"babelProcessor.mjs","names":[],"sources":["../../../src/extractContent/babelProcessor.ts"],"sourcesContent":["import _traverse, { type NodePath } from '@babel/traverse';\nimport * as t from '@babel/types';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { resolveDictionaryKey } from '../extractContent/utils';\nimport {\n ATTRIBUTES_TO_EXTRACT,\n getComponentName,\n getExistingIntlayerInfo,\n getOrGenerateKey,\n shouldExtract,\n} from './utils';\n\nexport type BabelReplacement = {\n path: NodePath;\n key: string;\n type:\n | 'jsx-text'\n | 'jsx-attribute'\n | 'string-literal'\n | 'jsx-insertion'\n | 'jsx-text-combined';\n componentKey: string;\n childrenToReplace?: t.Node[];\n variables?: string[];\n};\n\n// CJS/ESM interop: @babel/traverse exports its function as `.default` in CJS bundles\nconst traverse = (\n typeof _traverse === 'function' ? _traverse : (_traverse as any).default\n) as typeof _traverse;\n\n/**\n * Handles JSX insertions (elements with multiple children, including expressions).\n * Replaces complex JSX structures with variable-based translations.\n */\nexport const handleJsxInsertionBabel = (\n path: NodePath<t.JSXElement | t.JSXFragment>,\n fileCode: string,\n existingKeys: Set<string>,\n getComponentKeyForPath: (path: NodePath) => string,\n extractedContent: Record<string, Record<string, string>>,\n replacements: BabelReplacement[],\n handledNodes: Set<t.Node>\n): boolean => {\n const children = path.node.children;\n\n if (children.length <= 1) return false;\n\n const parts: {\n type: 'text' | 'var';\n value: string;\n originalExpr?: string;\n }[] = [];\n let hasSignificantText = false;\n let hasVariables = false;\n\n for (const child of children) {\n if (t.isJSXText(child)) {\n const text = child.value;\n\n if (text.trim().length > 0) hasSignificantText = true;\n\n parts.push({ type: 'text', value: text });\n } else if (t.isJSXExpressionContainer(child)) {\n if (t.isJSXEmptyExpression(child.expression)) {\n parts.push({ type: 'text', value: '' });\n } else {\n const expr = child.expression;\n\n if (t.isIdentifier(expr)) {\n parts.push({\n type: 'var',\n value: expr.name,\n originalExpr: expr.name,\n });\n hasVariables = true;\n } else if (t.isMemberExpression(expr)) {\n const code = fileCode.substring(expr.start!, expr.end!);\n\n const varName = t.isIdentifier(expr.property)\n ? expr.property.name\n : 'var';\n\n parts.push({ type: 'var', value: varName, originalExpr: code });\n\n hasVariables = true;\n } else {\n return false;\n }\n }\n } else {\n return false;\n }\n }\n\n if (!hasSignificantText) return false;\n\n let combinedString = '';\n for (const part of parts) {\n if (part.type === 'var') combinedString += `{{${part.value}}}`;\n else combinedString += part.value;\n }\n\n const cleanString = combinedString.replace(/\\s+/g, ' ').trim();\n\n if (shouldExtract(cleanString)) {\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n cleanString,\n componentKey,\n existingKeys,\n extractedContent\n );\n\n const varMap = parts\n .filter((part) => part.type === 'var')\n .map((part) => `${part.value}: ${part.originalExpr}`);\n const uniqueVars = Array.from(new Set(varMap));\n\n if (hasVariables) {\n replacements.push({\n path,\n key,\n type: 'jsx-insertion',\n componentKey,\n childrenToReplace: children,\n variables: uniqueVars,\n });\n } else {\n replacements.push({\n path,\n key,\n type: 'jsx-text-combined',\n componentKey,\n childrenToReplace: children,\n });\n }\n\n children.forEach((child) => {\n handledNodes.add(child);\n });\n return true;\n }\n\n return false;\n};\n\n/**\n * Traverses the AST to identify components and extract content.\n * Returns extraction results and metadata about which components need Intlayer hooks.\n */\nexport const extractBabelContentForComponents = (\n ast: t.File,\n fileCode: string,\n existingKeys: Set<string>,\n defaultKey: string = 'default',\n configuration: IntlayerConfig,\n filePath: string,\n unmergedDictionaries: Record<string, unknown> = {}\n): {\n extractedContent: Record<string, Record<string, string>>;\n replacements: BabelReplacement[];\n componentsNeedingHooks: Set<NodePath>;\n componentKeyMap: Map<t.Node, string>;\n componentPaths: NodePath[];\n hookMap: Map<t.Node, 'useIntlayer' | 'getIntlayer'>;\n isSolid: boolean;\n} => {\n const extractedContent: Record<string, Record<string, string>> = {};\n const replacements: BabelReplacement[] = [];\n const handledNodes = new Set<t.Node>();\n const componentKeyMap = new Map<t.Node, string>();\n const hookMap = new Map<t.Node, 'useIntlayer' | 'getIntlayer'>();\n const usedKeysInFile = new Set<string>();\n let globalFileKey: string | undefined;\n\n const componentPaths: NodePath[] = [];\n\n traverse(ast, {\n Program(path) {\n componentPaths.push(path);\n },\n FunctionDeclaration(path) {\n componentPaths.push(path);\n },\n ArrowFunctionExpression(path) {\n componentPaths.push(path);\n },\n FunctionExpression(path) {\n componentPaths.push(path);\n },\n });\n\n for (const path of componentPaths) {\n const existingInfo = getExistingIntlayerInfo(path);\n\n if (existingInfo) {\n componentKeyMap.set(path.node, existingInfo.key);\n usedKeysInFile.add(existingInfo.key);\n hookMap.set(path.node, existingInfo.hook);\n } else {\n if (path.isProgram()) {\n if (!globalFileKey) {\n globalFileKey = resolveDictionaryKey(\n defaultKey,\n filePath,\n configuration,\n unmergedDictionaries,\n usedKeysInFile\n );\n usedKeysInFile.add(globalFileKey);\n }\n componentKeyMap.set(path.node, globalFileKey);\n hookMap.set(path.node, 'getIntlayer');\n } else {\n if (!globalFileKey) {\n globalFileKey = resolveDictionaryKey(\n defaultKey,\n filePath,\n configuration,\n unmergedDictionaries,\n usedKeysInFile\n );\n usedKeysInFile.add(globalFileKey);\n }\n componentKeyMap.set(path.node, globalFileKey);\n\n const compName = getComponentName(path);\n const isComponent = compName ? /^[A-Z]/.test(compName) : false;\n const isHook = compName ? /^use[A-Z]/.test(compName) : false;\n hookMap.set(\n path.node,\n isComponent || isHook ? 'useIntlayer' : 'getIntlayer'\n );\n }\n }\n }\n\n const getComponentKeyForPath = (path: NodePath): string => {\n let current: NodePath | null = path;\n while (current) {\n if (componentKeyMap.has(current.node)) {\n return componentKeyMap.get(current.node)!;\n }\n current = current.parentPath;\n }\n return globalFileKey || defaultKey;\n };\n\n traverse(ast, {\n JSXElement(path) {\n if (handledNodes.has(path.node)) return;\n\n handleJsxInsertionBabel(\n path,\n fileCode,\n existingKeys,\n getComponentKeyForPath,\n extractedContent,\n replacements,\n handledNodes\n );\n },\n JSXFragment(path) {\n if (handledNodes.has(path.node)) return;\n\n handleJsxInsertionBabel(\n path,\n fileCode,\n existingKeys,\n getComponentKeyForPath,\n extractedContent,\n replacements,\n handledNodes\n );\n },\n JSXText(path) {\n if (handledNodes.has(path.node)) return;\n\n const text = path.node.value;\n\n if (shouldExtract(text)) {\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n text.replace(/\\s+/g, ' ').trim(),\n componentKey,\n existingKeys,\n extractedContent\n );\n replacements.push({ path, key, type: 'jsx-text', componentKey });\n }\n },\n JSXAttribute(path) {\n if (handledNodes.has(path.node)) return;\n\n const name = path.node.name.name;\n\n if (\n typeof name !== 'string' ||\n !ATTRIBUTES_TO_EXTRACT.includes(name as any)\n )\n return;\n const value = path.node.value;\n\n if (t.isStringLiteral(value) && shouldExtract(value.value)) {\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n value.value.trim(),\n componentKey,\n existingKeys,\n extractedContent\n );\n replacements.push({ path, key, type: 'jsx-attribute', componentKey });\n }\n },\n StringLiteral(path) {\n if (handledNodes.has(path.node)) return;\n\n const text = path.node.value;\n\n if (!shouldExtract(text)) return;\n\n const parent = path.parentPath;\n\n if (\n parent.isImportDeclaration() ||\n parent.isImportSpecifier() ||\n parent.isExportDeclaration()\n )\n return;\n\n if (parent.isJSXAttribute()) return;\n\n if (\n parent.isCallExpression() &&\n t.isMemberExpression(parent.node.callee)\n ) {\n if (\n t.isIdentifier(parent.node.callee.object) &&\n parent.node.callee.object.name === 'console' &&\n t.isIdentifier(parent.node.callee.property) &&\n parent.node.callee.property.name === 'log'\n ) {\n return;\n }\n }\n\n if (parent.isObjectProperty() && parent.node.key === path.node) return;\n\n if (parent.isMemberExpression() && parent.node.property === path.node)\n return;\n\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n text.trim(),\n componentKey,\n existingKeys,\n extractedContent\n );\n replacements.push({ path, key, type: 'string-literal', componentKey });\n },\n });\n\n const componentsNeedingHooks = new Set<NodePath>();\n for (const componentPath of componentPaths) {\n if (componentPath.isProgram()) {\n const hasDirectReplacements = replacements.some((replacement) => {\n let current: NodePath | null = replacement.path;\n while (current) {\n if (current.node === componentPath.node) {\n return true;\n }\n const isOtherComponent = componentPaths.some(\n (p) => p !== componentPath && p.node === current?.node\n );\n if (isOtherComponent) {\n return false;\n }\n current = current.parentPath;\n }\n return false;\n });\n\n if (hasDirectReplacements) {\n componentsNeedingHooks.add(componentPath);\n }\n continue;\n }\n\n const hasReplacements = replacements.some((replacement) => {\n let current: NodePath | null = replacement.path;\n while (current) {\n if (current.node === componentPath.node) return true;\n\n current = current.parentPath;\n }\n return false;\n });\n\n if (hasReplacements) {\n const key = componentKeyMap.get(componentPath.node)!;\n let ancestorProvidesKey = false;\n let currentPath: NodePath | null = componentPath.parentPath;\n while (currentPath) {\n const ancestorPath = componentPaths.find(\n (path) => path.node === currentPath?.node\n );\n\n if (ancestorPath && !ancestorPath.isProgram()) {\n const ancestorKey = componentKeyMap.get(ancestorPath.node);\n\n if (ancestorKey === key) {\n const ancestorHasReplacements = replacements.some((replacement) => {\n let rPath: NodePath | null = replacement.path;\n while (rPath) {\n if (rPath.node === ancestorPath.node) return true;\n\n rPath = rPath.parentPath;\n }\n return false;\n });\n const existingInfo = getExistingIntlayerInfo(ancestorPath);\n\n if (ancestorHasReplacements || existingInfo) {\n ancestorProvidesKey = true;\n break;\n }\n }\n }\n currentPath = currentPath.parentPath;\n }\n\n if (!ancestorProvidesKey) {\n componentsNeedingHooks.add(componentPath);\n }\n }\n }\n\n return {\n extractedContent,\n replacements,\n componentsNeedingHooks,\n componentKeyMap,\n componentPaths,\n hookMap,\n isSolid: false,\n };\n};\n\n/**\n * High-level function to extract content from TS/JS/JSX/TSX AST.\n */\nexport const extractTsContent = (\n ast: t.File,\n fileCode: string,\n existingKeys: Set<string>,\n configuration: IntlayerConfig,\n filePath: string,\n unmergedDictionaries: Record<string, unknown> = {}\n): {\n extractedContent: Record<string, string>;\n replacements: BabelReplacement[];\n} => {\n const { extractedContent, replacements } = extractBabelContentForComponents(\n ast,\n fileCode,\n existingKeys,\n 'default',\n configuration,\n filePath,\n unmergedDictionaries\n );\n\n const flatContent: Record<string, string> = {};\n for (const group of Object.values(extractedContent)) {\n Object.assign(flatContent, group);\n }\n\n return { extractedContent: flatContent, replacements };\n};\n"],"mappings":"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"}
|
|
@@ -117,7 +117,7 @@ const extractContent = async (filePath, packageName, options) => {
|
|
|
117
117
|
unmergedDictionaries,
|
|
118
118
|
configuration
|
|
119
119
|
}, saveComponent, dictionaryKey);
|
|
120
|
-
if (!result
|
|
120
|
+
if (!result?.extractedContentMap) {
|
|
121
121
|
appLogger(`${colorize("Compiler:", ANSIColors.GREY_DARK)} No extractable text found in ${colorizePath(relative(baseDir, filePath))}`, { isVerbose: true });
|
|
122
122
|
return;
|
|
123
123
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extractContent.mjs","names":[],"sources":["../../../src/extractContent/extractContent.ts"],"sourcesContent":["import { execSync } from 'node:child_process';\nimport { readFileSync } from 'node:fs';\nimport { extname, relative } from 'node:path';\nimport type * as t from '@babel/types';\nimport { detectFormatCommand } from '@intlayer/chokidar/cli';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizePath, getAppLogger } from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport { getProjectRequire } from '@intlayer/config/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport { extractTsContent } from './babelProcessor';\nimport { writeContentHelper } from './contentWriter';\nimport { processTsxFile } from './processTsxFile';\nimport {\n ATTRIBUTES_TO_EXTRACT,\n extractDictionaryKeyFromPath,\n type PackageName,\n shouldExtract,\n} from './utils';\nimport { extractDictionaryKey } from './utils/extractDictionaryKey';\nimport { generateKey } from './utils/generateKey';\n\nexport type ExtractIntlayerOptions = {\n configOptions?: GetConfigurationOptions;\n codeOnly?: boolean;\n declarationOnly?: boolean;\n unmergedDictionaries?: Record<string, unknown>;\n configuration?: IntlayerConfig;\n code?: string;\n onExtract?: (result: {\n key: string;\n content: Record<string, string>;\n filePath: string;\n }) => void | Promise<void>;\n};\n\ntype ExternalCompilerResult = {\n extractedContent: Record<string, string>;\n code: string;\n};\n\ntype ExternalCompilerOptions = {\n generateKey: typeof generateKey;\n shouldExtract: typeof shouldExtract;\n attributesToExtract: typeof ATTRIBUTES_TO_EXTRACT;\n extractDictionaryKeyFromPath: typeof extractDictionaryKeyFromPath;\n extractTsContent: (\n ast: unknown,\n code: string,\n keys: Set<string>,\n config: IntlayerConfig,\n path: string\n ) => ReturnType<typeof extractTsContent>;\n};\n\ntype VueCompiler = typeof import('@intlayer/vue-compiler');\ntype SvelteCompiler = typeof import('@intlayer/svelte-compiler');\n\n// Module caches\nlet vueCompiler: VueCompiler | null = null;\nlet svelteCompiler: SvelteCompiler | null = null;\n\ntype InternalExtractResult = {\n extractedContentMap: Record<string, Record<string, string>> | null;\n transformedCode: string | null;\n};\n\nconst formatCompilerResult = (\n componentKey: string,\n res?: ExternalCompilerResult\n): InternalExtractResult | undefined => {\n if (!res) return undefined;\n\n return {\n extractedContentMap: { [componentKey]: res.extractedContent },\n transformedCode: res.code,\n };\n};\n\ntype Dependencies = {\n vueCompiler: VueCompiler | null;\n svelteCompiler: SvelteCompiler | null;\n unmergedDictionaries: Record<string, unknown>;\n configuration: IntlayerConfig;\n};\n\nconst processFileInternal = (\n filePath: string,\n packageName: PackageName,\n options: ExtractIntlayerOptions | undefined,\n dependencies: Dependencies,\n saveComponent: boolean,\n providedComponentKey?: string\n): InternalExtractResult | undefined => {\n const fileText = options?.code ?? readFileSync(filePath, 'utf-8');\n const componentKey =\n providedComponentKey ??\n extractDictionaryKey(\n filePath,\n fileText,\n dependencies.configuration.compiler.dictionaryKeyPrefix\n );\n const ext = extname(filePath);\n\n const { vueCompiler, svelteCompiler, unmergedDictionaries, configuration } =\n dependencies;\n\n const compilerCommonOptions: ExternalCompilerOptions = {\n generateKey,\n shouldExtract,\n attributesToExtract: ATTRIBUTES_TO_EXTRACT,\n extractDictionaryKeyFromPath,\n extractTsContent: (ast, code, keys, config, path) =>\n extractTsContent(\n ast as t.File,\n code,\n keys,\n config,\n path,\n unmergedDictionaries\n ),\n };\n\n if (ext === '.vue') {\n if (!vueCompiler) {\n throw new Error(\n `Please install ${colorizePath('@intlayer/vue-compiler', ANSIColors.YELLOW)} to process Vue files.`\n );\n }\n\n const res = vueCompiler.processVueFile(\n filePath,\n componentKey,\n packageName,\n compilerCommonOptions,\n saveComponent\n );\n\n if (res) {\n return formatCompilerResult(componentKey, res);\n }\n }\n\n if (ext === '.svelte') {\n if (!svelteCompiler) {\n throw new Error(\n `Please install ${colorizePath('@intlayer/svelte-compiler', ANSIColors.YELLOW)} to process Svelte files.`\n );\n }\n\n const res = svelteCompiler.processSvelteFile(\n filePath,\n componentKey,\n packageName,\n compilerCommonOptions,\n saveComponent\n );\n\n if (res) {\n return formatCompilerResult(componentKey, res);\n }\n }\n\n if (['.tsx', '.jsx', '.ts', '.js', '.cjs', '.mjs'].includes(ext)) {\n const result = processTsxFile(\n filePath,\n componentKey,\n packageName,\n configuration,\n saveComponent,\n unmergedDictionaries,\n fileText\n );\n\n if (result) {\n return {\n extractedContentMap: result.extractedContent,\n transformedCode: result.modifiedCode,\n };\n }\n }\n\n return undefined;\n};\n\nconst handleExtractionSideEffects = async (\n extractedContentMap: Record<string, Record<string, string>>,\n filePath: string,\n options: ExtractIntlayerOptions | undefined,\n {\n configuration,\n baseDir,\n appLogger,\n }: {\n configuration: IntlayerConfig;\n baseDir: string;\n appLogger: ReturnType<typeof getAppLogger>;\n },\n saveComponent: boolean\n) => {\n if (options?.onExtract) {\n for (const [key, content] of Object.entries(extractedContentMap)) {\n await options.onExtract({ key, content, filePath });\n }\n }\n\n const writeContent = !options?.codeOnly && !options?.onExtract;\n\n if (writeContent) {\n for (const [key, content] of Object.entries(extractedContentMap)) {\n const contentFilePath = await writeContentHelper(\n content,\n key,\n filePath,\n configuration\n );\n\n const relativeContentFilePath = relative(baseDir, contentFilePath);\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Created content file: ${colorizePath(relativeContentFilePath)}`\n );\n }\n }\n\n if (saveComponent) {\n const formatCommand = detectFormatCommand(configuration);\n\n if (formatCommand) {\n try {\n execSync(formatCommand.replace('{{file}}', filePath), {\n stdio: 'inherit',\n cwd: baseDir,\n });\n } catch (error) {\n console.error(error);\n }\n }\n\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Updated component: ${colorizePath(relative(baseDir, filePath))}`\n );\n }\n};\n\ntype ExtractResult = {\n transformedCode: string | null;\n extractedContentMap: Record<string, Record<string, string>>;\n};\n\ntype ExtractContext = {\n configuration: IntlayerConfig;\n appLogger: ReturnType<typeof getAppLogger>;\n baseDir: string;\n unmergedDictionaries: Record<string, unknown>;\n saveComponent: boolean;\n componentExtension: string;\n};\n\nconst buildContext = (\n filePath: string,\n options: ExtractIntlayerOptions | undefined\n): ExtractContext => {\n const configuration =\n options?.configuration ?? getConfiguration(options?.configOptions);\n const appLogger = getAppLogger(configuration);\n const { baseDir } = configuration.system;\n const unmergedDictionaries =\n options?.unmergedDictionaries ?? getUnmergedDictionaries(configuration);\n const saveComponent = !options?.declarationOnly;\n const componentExtension = extname(filePath);\n\n return {\n configuration,\n appLogger,\n baseDir,\n unmergedDictionaries,\n saveComponent,\n componentExtension,\n };\n};\n\nexport const extractContent = async (\n filePath: string,\n packageName: PackageName,\n options?: ExtractIntlayerOptions\n): Promise<ExtractResult | undefined> => {\n const {\n configuration,\n appLogger,\n baseDir,\n unmergedDictionaries,\n saveComponent,\n componentExtension,\n } = buildContext(filePath, options);\n\n if (componentExtension === '.vue' && !vueCompiler) {\n try {\n vueCompiler = await import('@intlayer/vue-compiler');\n } catch {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Install ${colorizePath('@intlayer/vue-compiler', ANSIColors.YELLOW)} to process Vue files.`\n );\n }\n }\n\n if (componentExtension === '.svelte' && !svelteCompiler) {\n try {\n svelteCompiler = await import('@intlayer/svelte-compiler');\n } catch {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Install ${colorizePath('@intlayer/svelte-compiler', ANSIColors.YELLOW)} to process Svelte files.`\n );\n }\n }\n\n const fileText = options?.code ?? readFileSync(filePath, 'utf-8');\n const dictionaryKey = extractDictionaryKey(\n filePath,\n fileText,\n configuration.compiler.dictionaryKeyPrefix\n );\n\n const result = processFileInternal(\n filePath,\n packageName,\n options,\n { vueCompiler, svelteCompiler, unmergedDictionaries, configuration },\n saveComponent,\n dictionaryKey\n );\n\n if (!result || !result.extractedContentMap) {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} No extractable text found in ${colorizePath(relative(baseDir, filePath))}`,\n { isVerbose: true }\n );\n return undefined;\n }\n\n await handleExtractionSideEffects(\n result.extractedContentMap,\n filePath,\n options,\n { configuration, baseDir, appLogger },\n saveComponent\n );\n\n return {\n transformedCode: result.transformedCode,\n extractedContentMap: result.extractedContentMap,\n };\n};\n\n/**\n * Synchronous variant of `extractContent` — used by the Babel plugin which\n * runs in a sync transform context (e.g. Next.js / Webpack).\n *\n * Differences from `extractContent`:\n * - Loads external compilers (Vue, Svelte) via `require()` instead of `import()`\n * - Fires `onExtract` callbacks as fire-and-forget (cannot await in sync context)\n * - Does NOT write dictionary files or run the code formatter; callers that\n * need persistence should supply an `onExtract` callback\n */\nexport const extractContentSync = (\n filePath: string,\n packageName: PackageName,\n options?: ExtractIntlayerOptions\n): ExtractResult | undefined => {\n const {\n configuration,\n appLogger,\n baseDir,\n unmergedDictionaries,\n saveComponent,\n componentExtension,\n } = buildContext(filePath, options);\n\n const requireFn = getProjectRequire();\n\n if (componentExtension === '.vue' && !vueCompiler) {\n try {\n vueCompiler = requireFn('@intlayer/vue-compiler') as VueCompiler;\n } catch {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Install ${colorizePath('@intlayer/vue-compiler', ANSIColors.YELLOW)} to process Vue files.`\n );\n }\n }\n\n if (componentExtension === '.svelte' && !svelteCompiler) {\n try {\n svelteCompiler = requireFn('@intlayer/svelte-compiler') as SvelteCompiler;\n } catch {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Install ${colorizePath('@intlayer/svelte-compiler', ANSIColors.YELLOW)} to process Svelte files.`\n );\n }\n }\n\n const result = processFileInternal(\n filePath,\n packageName,\n options,\n { vueCompiler, svelteCompiler, unmergedDictionaries, configuration },\n saveComponent\n );\n\n if (!result?.extractedContentMap) {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} No extractable text found in ${colorizePath(relative(baseDir, filePath))}`,\n { isVerbose: true }\n );\n return undefined;\n }\n\n const { extractedContentMap, transformedCode } = result;\n\n if (options?.onExtract) {\n for (const [key, content] of Object.entries(extractedContentMap)) {\n void options.onExtract({ key, content, filePath });\n }\n }\n\n return { transformedCode, extractedContentMap };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AA+DA,IAAI,cAAkC;AACtC,IAAI,iBAAwC;AAO5C,MAAM,wBACJ,cACA,QACsC;AACtC,KAAI,CAAC,IAAK,QAAO;AAEjB,QAAO;EACL,qBAAqB,GAAG,eAAe,IAAI,kBAAkB;EAC7D,iBAAiB,IAAI;EACtB;;AAUH,MAAM,uBACJ,UACA,aACA,SACA,cACA,eACA,yBACsC;CACtC,MAAM,WAAW,SAAS,QAAQ,aAAa,UAAU,QAAQ;CACjE,MAAM,eACJ,wBACA,qBACE,UACA,UACA,aAAa,cAAc,SAAS,oBACrC;CACH,MAAM,MAAM,QAAQ,SAAS;CAE7B,MAAM,EAAE,aAAa,gBAAgB,sBAAsB,kBACzD;CAEF,MAAM,wBAAiD;EACrD;EACA;EACA,qBAAqB;EACrB;EACA,mBAAmB,KAAK,MAAM,MAAM,QAAQ,SAC1C,iBACE,KACA,MACA,MACA,QACA,MACA,qBACD;EACJ;AAED,KAAI,QAAQ,QAAQ;AAClB,MAAI,CAAC,YACH,OAAM,IAAI,MACR,kBAAkB,aAAa,0BAA0B,WAAW,OAAO,CAAC,wBAC7E;EAGH,MAAM,MAAM,YAAY,eACtB,UACA,cACA,aACA,uBACA,cACD;AAED,MAAI,IACF,QAAO,qBAAqB,cAAc,IAAI;;AAIlD,KAAI,QAAQ,WAAW;AACrB,MAAI,CAAC,eACH,OAAM,IAAI,MACR,kBAAkB,aAAa,6BAA6B,WAAW,OAAO,CAAC,2BAChF;EAGH,MAAM,MAAM,eAAe,kBACzB,UACA,cACA,aACA,uBACA,cACD;AAED,MAAI,IACF,QAAO,qBAAqB,cAAc,IAAI;;AAIlD,KAAI;EAAC;EAAQ;EAAQ;EAAO;EAAO;EAAQ;EAAO,CAAC,SAAS,IAAI,EAAE;EAChE,MAAM,SAAS,eACb,UACA,cACA,aACA,eACA,eACA,sBACA,SACD;AAED,MAAI,OACF,QAAO;GACL,qBAAqB,OAAO;GAC5B,iBAAiB,OAAO;GACzB;;;AAOP,MAAM,8BAA8B,OAClC,qBACA,UACA,SACA,EACE,eACA,SACA,aAMF,kBACG;AACH,KAAI,SAAS,UACX,MAAK,MAAM,CAAC,KAAK,YAAY,OAAO,QAAQ,oBAAoB,CAC9D,OAAM,QAAQ,UAAU;EAAE;EAAK;EAAS;EAAU,CAAC;AAMvD,KAFqB,CAAC,SAAS,YAAY,CAAC,SAAS,UAGnD,MAAK,MAAM,CAAC,KAAK,YAAY,OAAO,QAAQ,oBAAoB,EAAE;EAQhE,MAAM,0BAA0B,SAAS,SAPjB,MAAM,mBAC5B,SACA,KACA,UACA,cACD,CAEiE;AAClE,YACE,GAAG,SAAS,aAAa,WAAW,UAAU,CAAC,yBAAyB,aAAa,wBAAwB,GAC9G;;AAIL,KAAI,eAAe;EACjB,MAAM,gBAAgB,oBAAoB,cAAc;AAExD,MAAI,cACF,KAAI;AACF,YAAS,cAAc,QAAQ,YAAY,SAAS,EAAE;IACpD,OAAO;IACP,KAAK;IACN,CAAC;WACK,OAAO;AACd,WAAQ,MAAM,MAAM;;AAIxB,YACE,GAAG,SAAS,aAAa,WAAW,UAAU,CAAC,sBAAsB,aAAa,SAAS,SAAS,SAAS,CAAC,GAC/G;;;AAkBL,MAAM,gBACJ,UACA,YACmB;CACnB,MAAM,gBACJ,SAAS,iBAAiB,iBAAiB,SAAS,cAAc;CACpE,MAAM,YAAY,aAAa,cAAc;CAC7C,MAAM,EAAE,YAAY,cAAc;AAMlC,QAAO;EACL;EACA;EACA;EACA,sBARA,SAAS,wBAAwB,wBAAwB,cAAc;EASvE,eARoB,CAAC,SAAS;EAS9B,oBARyB,QAAQ,SAAS;EAS3C;;AAGH,MAAa,iBAAiB,OAC5B,UACA,aACA,YACuC;CACvC,MAAM,EACJ,eACA,WACA,SACA,sBACA,eACA,uBACE,aAAa,UAAU,QAAQ;AAEnC,KAAI,uBAAuB,UAAU,CAAC,YACpC,KAAI;AACF,gBAAc,MAAM,OAAO;SACrB;AACN,YACE,GAAG,SAAS,aAAa,WAAW,UAAU,CAAC,WAAW,aAAa,0BAA0B,WAAW,OAAO,CAAC,wBACrH;;AAIL,KAAI,uBAAuB,aAAa,CAAC,eACvC,KAAI;AACF,mBAAiB,MAAM,OAAO;SACxB;AACN,YACE,GAAG,SAAS,aAAa,WAAW,UAAU,CAAC,WAAW,aAAa,6BAA6B,WAAW,OAAO,CAAC,2BACxH;;CAKL,MAAM,gBAAgB,qBACpB,UAFe,SAAS,QAAQ,aAAa,UAAU,QAAQ,EAI/D,cAAc,SAAS,oBACxB;CAED,MAAM,SAAS,oBACb,UACA,aACA,SACA;EAAE;EAAa;EAAgB;EAAsB;EAAe,EACpE,eACA,cACD;AAED,KAAI,CAAC,UAAU,CAAC,OAAO,qBAAqB;AAC1C,YACE,GAAG,SAAS,aAAa,WAAW,UAAU,CAAC,gCAAgC,aAAa,SAAS,SAAS,SAAS,CAAC,IACxH,EAAE,WAAW,MAAM,CACpB;AACD;;AAGF,OAAM,4BACJ,OAAO,qBACP,UACA,SACA;EAAE;EAAe;EAAS;EAAW,EACrC,cACD;AAED,QAAO;EACL,iBAAiB,OAAO;EACxB,qBAAqB,OAAO;EAC7B;;;;;;;;;;;;AAaH,MAAa,sBACX,UACA,aACA,YAC8B;CAC9B,MAAM,EACJ,eACA,WACA,SACA,sBACA,eACA,uBACE,aAAa,UAAU,QAAQ;CAEnC,MAAM,YAAY,mBAAmB;AAErC,KAAI,uBAAuB,UAAU,CAAC,YACpC,KAAI;AACF,gBAAc,UAAU,yBAAyB;SAC3C;AACN,YACE,GAAG,SAAS,aAAa,WAAW,UAAU,CAAC,WAAW,aAAa,0BAA0B,WAAW,OAAO,CAAC,wBACrH;;AAIL,KAAI,uBAAuB,aAAa,CAAC,eACvC,KAAI;AACF,mBAAiB,UAAU,4BAA4B;SACjD;AACN,YACE,GAAG,SAAS,aAAa,WAAW,UAAU,CAAC,WAAW,aAAa,6BAA6B,WAAW,OAAO,CAAC,2BACxH;;CAIL,MAAM,SAAS,oBACb,UACA,aACA,SACA;EAAE;EAAa;EAAgB;EAAsB;EAAe,EACpE,cACD;AAED,KAAI,CAAC,QAAQ,qBAAqB;AAChC,YACE,GAAG,SAAS,aAAa,WAAW,UAAU,CAAC,gCAAgC,aAAa,SAAS,SAAS,SAAS,CAAC,IACxH,EAAE,WAAW,MAAM,CACpB;AACD;;CAGF,MAAM,EAAE,qBAAqB,oBAAoB;AAEjD,KAAI,SAAS,UACX,MAAK,MAAM,CAAC,KAAK,YAAY,OAAO,QAAQ,oBAAoB,CAC9D,CAAK,QAAQ,UAAU;EAAE;EAAK;EAAS;EAAU,CAAC;AAItD,QAAO;EAAE;EAAiB;EAAqB"}
|
|
1
|
+
{"version":3,"file":"extractContent.mjs","names":[],"sources":["../../../src/extractContent/extractContent.ts"],"sourcesContent":["import { execSync } from 'node:child_process';\nimport { readFileSync } from 'node:fs';\nimport { extname, relative } from 'node:path';\nimport type * as t from '@babel/types';\nimport { detectFormatCommand } from '@intlayer/chokidar/cli';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizePath, getAppLogger } from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport { getProjectRequire } from '@intlayer/config/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport { extractTsContent } from './babelProcessor';\nimport { writeContentHelper } from './contentWriter';\nimport { processTsxFile } from './processTsxFile';\nimport {\n ATTRIBUTES_TO_EXTRACT,\n extractDictionaryKeyFromPath,\n type PackageName,\n shouldExtract,\n} from './utils';\nimport { extractDictionaryKey } from './utils/extractDictionaryKey';\nimport { generateKey } from './utils/generateKey';\n\nexport type ExtractIntlayerOptions = {\n configOptions?: GetConfigurationOptions;\n codeOnly?: boolean;\n declarationOnly?: boolean;\n unmergedDictionaries?: Record<string, unknown>;\n configuration?: IntlayerConfig;\n code?: string;\n onExtract?: (result: {\n key: string;\n content: Record<string, string>;\n filePath: string;\n }) => void | Promise<void>;\n};\n\ntype ExternalCompilerResult = {\n extractedContent: Record<string, string>;\n code: string;\n};\n\ntype ExternalCompilerOptions = {\n generateKey: typeof generateKey;\n shouldExtract: typeof shouldExtract;\n attributesToExtract: typeof ATTRIBUTES_TO_EXTRACT;\n extractDictionaryKeyFromPath: typeof extractDictionaryKeyFromPath;\n extractTsContent: (\n ast: unknown,\n code: string,\n keys: Set<string>,\n config: IntlayerConfig,\n path: string\n ) => ReturnType<typeof extractTsContent>;\n};\n\ntype VueCompiler = typeof import('@intlayer/vue-compiler');\ntype SvelteCompiler = typeof import('@intlayer/svelte-compiler');\n\n// Module caches\nlet vueCompiler: VueCompiler | null = null;\nlet svelteCompiler: SvelteCompiler | null = null;\n\ntype InternalExtractResult = {\n extractedContentMap: Record<string, Record<string, string>> | null;\n transformedCode: string | null;\n};\n\nconst formatCompilerResult = (\n componentKey: string,\n res?: ExternalCompilerResult\n): InternalExtractResult | undefined => {\n if (!res) return undefined;\n\n return {\n extractedContentMap: { [componentKey]: res.extractedContent },\n transformedCode: res.code,\n };\n};\n\ntype Dependencies = {\n vueCompiler: VueCompiler | null;\n svelteCompiler: SvelteCompiler | null;\n unmergedDictionaries: Record<string, unknown>;\n configuration: IntlayerConfig;\n};\n\nconst processFileInternal = (\n filePath: string,\n packageName: PackageName,\n options: ExtractIntlayerOptions | undefined,\n dependencies: Dependencies,\n saveComponent: boolean,\n providedComponentKey?: string\n): InternalExtractResult | undefined => {\n const fileText = options?.code ?? readFileSync(filePath, 'utf-8');\n const componentKey =\n providedComponentKey ??\n extractDictionaryKey(\n filePath,\n fileText,\n dependencies.configuration.compiler.dictionaryKeyPrefix\n );\n const ext = extname(filePath);\n\n const { vueCompiler, svelteCompiler, unmergedDictionaries, configuration } =\n dependencies;\n\n const compilerCommonOptions: ExternalCompilerOptions = {\n generateKey,\n shouldExtract,\n attributesToExtract: ATTRIBUTES_TO_EXTRACT,\n extractDictionaryKeyFromPath,\n extractTsContent: (ast, code, keys, config, path) =>\n extractTsContent(\n ast as t.File,\n code,\n keys,\n config,\n path,\n unmergedDictionaries\n ),\n };\n\n if (ext === '.vue') {\n if (!vueCompiler) {\n throw new Error(\n `Please install ${colorizePath('@intlayer/vue-compiler', ANSIColors.YELLOW)} to process Vue files.`\n );\n }\n\n const res = vueCompiler.processVueFile(\n filePath,\n componentKey,\n packageName,\n compilerCommonOptions,\n saveComponent\n );\n\n if (res) {\n return formatCompilerResult(componentKey, res);\n }\n }\n\n if (ext === '.svelte') {\n if (!svelteCompiler) {\n throw new Error(\n `Please install ${colorizePath('@intlayer/svelte-compiler', ANSIColors.YELLOW)} to process Svelte files.`\n );\n }\n\n const res = svelteCompiler.processSvelteFile(\n filePath,\n componentKey,\n packageName,\n compilerCommonOptions,\n saveComponent\n );\n\n if (res) {\n return formatCompilerResult(componentKey, res);\n }\n }\n\n if (['.tsx', '.jsx', '.ts', '.js', '.cjs', '.mjs'].includes(ext)) {\n const result = processTsxFile(\n filePath,\n componentKey,\n packageName,\n configuration,\n saveComponent,\n unmergedDictionaries,\n fileText\n );\n\n if (result) {\n return {\n extractedContentMap: result.extractedContent,\n transformedCode: result.modifiedCode,\n };\n }\n }\n\n return undefined;\n};\n\nconst handleExtractionSideEffects = async (\n extractedContentMap: Record<string, Record<string, string>>,\n filePath: string,\n options: ExtractIntlayerOptions | undefined,\n {\n configuration,\n baseDir,\n appLogger,\n }: {\n configuration: IntlayerConfig;\n baseDir: string;\n appLogger: ReturnType<typeof getAppLogger>;\n },\n saveComponent: boolean\n) => {\n if (options?.onExtract) {\n for (const [key, content] of Object.entries(extractedContentMap)) {\n await options.onExtract({ key, content, filePath });\n }\n }\n\n const writeContent = !options?.codeOnly && !options?.onExtract;\n\n if (writeContent) {\n for (const [key, content] of Object.entries(extractedContentMap)) {\n const contentFilePath = await writeContentHelper(\n content,\n key,\n filePath,\n configuration\n );\n\n const relativeContentFilePath = relative(baseDir, contentFilePath);\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Created content file: ${colorizePath(relativeContentFilePath)}`\n );\n }\n }\n\n if (saveComponent) {\n const formatCommand = detectFormatCommand(configuration);\n\n if (formatCommand) {\n try {\n execSync(formatCommand.replace('{{file}}', filePath), {\n stdio: 'inherit',\n cwd: baseDir,\n });\n } catch (error) {\n console.error(error);\n }\n }\n\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Updated component: ${colorizePath(relative(baseDir, filePath))}`\n );\n }\n};\n\ntype ExtractResult = {\n transformedCode: string | null;\n extractedContentMap: Record<string, Record<string, string>>;\n};\n\ntype ExtractContext = {\n configuration: IntlayerConfig;\n appLogger: ReturnType<typeof getAppLogger>;\n baseDir: string;\n unmergedDictionaries: Record<string, unknown>;\n saveComponent: boolean;\n componentExtension: string;\n};\n\nconst buildContext = (\n filePath: string,\n options: ExtractIntlayerOptions | undefined\n): ExtractContext => {\n const configuration =\n options?.configuration ?? getConfiguration(options?.configOptions);\n const appLogger = getAppLogger(configuration);\n const { baseDir } = configuration.system;\n const unmergedDictionaries =\n options?.unmergedDictionaries ?? getUnmergedDictionaries(configuration);\n const saveComponent = !options?.declarationOnly;\n const componentExtension = extname(filePath);\n\n return {\n configuration,\n appLogger,\n baseDir,\n unmergedDictionaries,\n saveComponent,\n componentExtension,\n };\n};\n\nexport const extractContent = async (\n filePath: string,\n packageName: PackageName,\n options?: ExtractIntlayerOptions\n): Promise<ExtractResult | undefined> => {\n const {\n configuration,\n appLogger,\n baseDir,\n unmergedDictionaries,\n saveComponent,\n componentExtension,\n } = buildContext(filePath, options);\n\n if (componentExtension === '.vue' && !vueCompiler) {\n try {\n vueCompiler = await import('@intlayer/vue-compiler');\n } catch {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Install ${colorizePath('@intlayer/vue-compiler', ANSIColors.YELLOW)} to process Vue files.`\n );\n }\n }\n\n if (componentExtension === '.svelte' && !svelteCompiler) {\n try {\n svelteCompiler = await import('@intlayer/svelte-compiler');\n } catch {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Install ${colorizePath('@intlayer/svelte-compiler', ANSIColors.YELLOW)} to process Svelte files.`\n );\n }\n }\n\n const fileText = options?.code ?? readFileSync(filePath, 'utf-8');\n const dictionaryKey = extractDictionaryKey(\n filePath,\n fileText,\n configuration.compiler.dictionaryKeyPrefix\n );\n\n const result = processFileInternal(\n filePath,\n packageName,\n options,\n { vueCompiler, svelteCompiler, unmergedDictionaries, configuration },\n saveComponent,\n dictionaryKey\n );\n\n if (!result?.extractedContentMap) {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} No extractable text found in ${colorizePath(relative(baseDir, filePath))}`,\n { isVerbose: true }\n );\n return undefined;\n }\n\n await handleExtractionSideEffects(\n result.extractedContentMap,\n filePath,\n options,\n { configuration, baseDir, appLogger },\n saveComponent\n );\n\n return {\n transformedCode: result.transformedCode,\n extractedContentMap: result.extractedContentMap,\n };\n};\n\n/**\n * Synchronous variant of `extractContent` — used by the Babel plugin which\n * runs in a sync transform context (e.g. Next.js / Webpack).\n *\n * Differences from `extractContent`:\n * - Loads external compilers (Vue, Svelte) via `require()` instead of `import()`\n * - Fires `onExtract` callbacks as fire-and-forget (cannot await in sync context)\n * - Does NOT write dictionary files or run the code formatter; callers that\n * need persistence should supply an `onExtract` callback\n */\nexport const extractContentSync = (\n filePath: string,\n packageName: PackageName,\n options?: ExtractIntlayerOptions\n): ExtractResult | undefined => {\n const {\n configuration,\n appLogger,\n baseDir,\n unmergedDictionaries,\n saveComponent,\n componentExtension,\n } = buildContext(filePath, options);\n\n const requireFn = getProjectRequire();\n\n if (componentExtension === '.vue' && !vueCompiler) {\n try {\n vueCompiler = requireFn('@intlayer/vue-compiler') as VueCompiler;\n } catch {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Install ${colorizePath('@intlayer/vue-compiler', ANSIColors.YELLOW)} to process Vue files.`\n );\n }\n }\n\n if (componentExtension === '.svelte' && !svelteCompiler) {\n try {\n svelteCompiler = requireFn('@intlayer/svelte-compiler') as SvelteCompiler;\n } catch {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Install ${colorizePath('@intlayer/svelte-compiler', ANSIColors.YELLOW)} to process Svelte files.`\n );\n }\n }\n\n const result = processFileInternal(\n filePath,\n packageName,\n options,\n { vueCompiler, svelteCompiler, unmergedDictionaries, configuration },\n saveComponent\n );\n\n if (!result?.extractedContentMap) {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} No extractable text found in ${colorizePath(relative(baseDir, filePath))}`,\n { isVerbose: true }\n );\n return undefined;\n }\n\n const { extractedContentMap, transformedCode } = result;\n\n if (options?.onExtract) {\n for (const [key, content] of Object.entries(extractedContentMap)) {\n void options.onExtract({ key, content, filePath });\n }\n }\n\n return { transformedCode, extractedContentMap };\n};\n"],"mappings":"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"}
|
|
@@ -133,8 +133,19 @@ const processTsxFile = (filePath, componentKey, packageName, configuration, save
|
|
|
133
133
|
const hook = hookMap.get(componentPath.node) || "useIntlayer";
|
|
134
134
|
if (hook === "useIntlayer") needsUseIntlayer = true;
|
|
135
135
|
if (hook === "getIntlayer") needsGetIntlayer = true;
|
|
136
|
-
const bodyPath = componentPath.get("body");
|
|
137
136
|
const hookStatementStr = `\n const content = ${hook}('${finalKey}');\n`;
|
|
137
|
+
if (componentPath.isProgram()) {
|
|
138
|
+
let insertPos = 0;
|
|
139
|
+
if (componentPath.node.directives.length > 0) insertPos = componentPath.node.directives[componentPath.node.directives.length - 1].end;
|
|
140
|
+
for (const stmt of componentPath.node.body) if (t.isImportDeclaration(stmt)) insertPos = Math.max(insertPos, stmt.end);
|
|
141
|
+
textEdits.push({
|
|
142
|
+
start: insertPos,
|
|
143
|
+
end: insertPos,
|
|
144
|
+
replacement: `\n${hookStatementStr}`
|
|
145
|
+
});
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const bodyPath = componentPath.get("body");
|
|
138
149
|
if (bodyPath.isBlockStatement()) textEdits.push({
|
|
139
150
|
start: bodyPath.node.start + 1,
|
|
140
151
|
end: bodyPath.node.start + 1,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"processTsxFile.mjs","names":[],"sources":["../../../src/extractContent/processTsxFile.ts"],"sourcesContent":["import { execSync } from 'node:child_process';\nimport { readFileSync, writeFileSync } from 'node:fs';\nimport { parse } from '@babel/parser';\nimport _traverse, { type NodePath } from '@babel/traverse';\nimport * as t from '@babel/types';\nimport { detectFormatCommand } from '@intlayer/chokidar/cli';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { extractBabelContentForComponents } from './babelProcessor';\nimport {\n type ExistingIntlayerInfo,\n getExistingIntlayerInfo,\n type PackageName,\n SERVER_CAPABLE_PACKAGES,\n} from './utils';\n\nexport type TextEdit = {\n start: number;\n end: number;\n replacement: string;\n};\n\nconst traverse = (\n typeof _traverse === 'function' ? _traverse : (_traverse as any).default\n) as typeof _traverse;\n\n/**\n * Processes a TSX/JSX/TS/JS file to extract content and inject Intlayer hooks/calls.\n */\nexport const processTsxFile = (\n filePath: string,\n componentKey: string,\n packageName: PackageName,\n configuration: IntlayerConfig,\n save: boolean = true,\n unmergedDictionaries: Record<string, unknown> = {},\n providedFileCode?: string\n): {\n extractedContent: Record<string, Record<string, string>>;\n modifiedCode: string;\n} | null => {\n const fileCode = providedFileCode ?? readFileSync(filePath, 'utf-8');\n\n const ast = parse(fileCode, {\n sourceType: 'module',\n plugins: ['jsx', 'typescript'],\n });\n\n const hasUseClient = ast.program.directives.some(\n (directive) => directive.value.value === 'use client'\n );\n\n const effectivePackageName =\n SERVER_CAPABLE_PACKAGES.has(packageName) && !hasUseClient\n ? `${packageName}/server`\n : packageName;\n\n // Both Solid and Angular expose content via a reactive/signal function —\n // access is `content().key` rather than `content.key`.\n const isSolid =\n packageName === 'solid-intlayer' || packageName === 'angular-intlayer';\n const existingKeys = new Set<string>();\n\n const {\n extractedContent,\n replacements,\n componentsNeedingHooks,\n componentKeyMap,\n componentPaths,\n hookMap,\n } = extractBabelContentForComponents(\n ast,\n fileCode,\n existingKeys,\n componentKey,\n configuration,\n filePath,\n unmergedDictionaries\n );\n\n if (Object.keys(extractedContent).length === 0) return null;\n\n const textEdits: TextEdit[] = [];\n\n // Build a lookup map: component AST node → NodePath (O(1) access)\n const componentNodeToPath = new Map<t.Node, NodePath>();\n for (const componentPath of componentPaths) {\n componentNodeToPath.set(componentPath.node, componentPath);\n }\n\n // Cache getExistingIntlayerInfo results for all component paths (avoids repeated traversals)\n const existingInfoCache = new Map<t.Node, ExistingIntlayerInfo | undefined>();\n for (const componentPath of componentPaths) {\n existingInfoCache.set(\n componentPath.node,\n getExistingIntlayerInfo(componentPath)\n );\n }\n\n /**\n * Walks up the ancestor chain to find the nearest enclosing component's\n * existing Intlayer info (if any).\n */\n const getExistingInfoForPath = (\n path: NodePath\n ): ExistingIntlayerInfo | undefined => {\n let current: NodePath | null = path;\n while (current) {\n if (componentNodeToPath.has(current.node)) {\n return existingInfoCache.get(current.node);\n }\n current = current.parentPath;\n }\n return undefined;\n };\n\n const getProvidingHookType = (\n path: NodePath\n ): 'useIntlayer' | 'getIntlayer' => {\n let current: NodePath | null = path;\n while (current) {\n const componentPath = componentNodeToPath.get(current.node);\n\n if (componentPath) {\n const existingInfo = existingInfoCache.get(componentPath.node);\n\n if (existingInfo) {\n return existingInfo.hook;\n }\n\n if (componentsNeedingHooks.has(componentPath)) {\n return hookMap.get(componentPath.node) || 'useIntlayer';\n }\n }\n current = current.parentPath;\n }\n return 'useIntlayer';\n };\n\n for (const {\n path,\n key,\n type,\n variables,\n childrenToReplace,\n } of replacements) {\n const existingInfo = getExistingInfoForPath(path);\n // When the existing call is destructured (e.g. `const { a } = getIntlayer(...)`),\n // new keys are added directly to that destructuring, so access them by name alone.\n const varName = existingInfo?.variableName ?? 'content';\n const contentAccessCode = existingInfo?.isDestructured\n ? key\n : isSolid\n ? `${varName}().${key}`\n : `${varName}.${key}`;\n const hookType = getProvidingHookType(path);\n const valueSuffix = hookType === 'getIntlayer' ? '' : '.value';\n\n if (type === 'jsx-text' && path.isJSXText()) {\n textEdits.push({\n start: path.node.start!,\n end: path.node.end!,\n replacement: `{${contentAccessCode}}`,\n });\n } else if (type === 'jsx-attribute' && path.isJSXAttribute()) {\n const valNode = path.node.value;\n\n if (valNode) {\n textEdits.push({\n start: valNode.start!,\n end: valNode.end!,\n replacement: `{${contentAccessCode}${valueSuffix}}`,\n });\n }\n } else if (type === 'string-literal' && path.isStringLiteral()) {\n textEdits.push({\n start: path.node.start!,\n end: path.node.end!,\n replacement: `${contentAccessCode}${valueSuffix}`,\n });\n } else if (\n type === 'jsx-text-combined' &&\n childrenToReplace &&\n childrenToReplace.length > 0\n ) {\n const accessStr = `{${contentAccessCode}}`;\n const start = childrenToReplace[0].start!;\n const end = childrenToReplace[childrenToReplace.length - 1].end!;\n textEdits.push({ start, end, replacement: accessStr });\n } else if (\n type === 'jsx-insertion' &&\n variables &&\n childrenToReplace &&\n childrenToReplace.length > 0\n ) {\n const objProps = variables\n .map((variableString) => {\n const [key, variableValue] = variableString\n .split(':')\n .map((part) => part.trim());\n return `${key}: ${variableValue}`;\n })\n .join(', ');\n\n const accessStr = `{${contentAccessCode}({ ${objProps} })}`;\n const start = childrenToReplace[0].start!;\n const end = childrenToReplace[childrenToReplace.length - 1].end!;\n textEdits.push({ start, end, replacement: accessStr });\n }\n }\n\n let needsUseIntlayer = false;\n let needsGetIntlayer = false;\n\n for (const componentPath of componentsNeedingHooks) {\n const finalKey = componentKeyMap.get(componentPath.node)!;\n const existingInfo = existingInfoCache.get(componentPath.node);\n\n if (existingInfo) {\n if (existingInfo.hook === 'useIntlayer') needsUseIntlayer = true;\n\n if (existingInfo.hook === 'getIntlayer') needsGetIntlayer = true;\n\n // When the existing call is destructured, inject any missing keys into\n // the destructuring pattern so they can be accessed by name directly.\n if (existingInfo.isDestructured && existingInfo.objectPatternNode) {\n const neededKeys = new Set<string>();\n\n for (const { path: rPath, key: rKey } of replacements) {\n let current: NodePath | null = rPath;\n\n while (current) {\n if (current.node === componentPath.node) {\n neededKeys.add(rKey);\n break;\n }\n current = current.parentPath;\n }\n }\n\n const missingKeys = [...neededKeys].filter(\n (key) => !existingInfo.existingDestructuredKeys.includes(key)\n );\n\n if (missingKeys.length > 0) {\n const { objectPatternNode } = existingInfo;\n // Insert right after the last property so the space/newline before\n // `}` is naturally preserved: `{ a }` → `{ a, b }`.\n const lastProp =\n objectPatternNode.properties[\n objectPatternNode.properties.length - 1\n ];\n textEdits.push({\n start: lastProp.end!,\n end: lastProp.end!,\n replacement: `, ${missingKeys.join(', ')}`,\n });\n }\n }\n } else {\n const hook = hookMap.get(componentPath.node) || 'useIntlayer';\n\n if (hook === 'useIntlayer') needsUseIntlayer = true;\n\n if (hook === 'getIntlayer') needsGetIntlayer = true;\n\n const bodyPath = componentPath.get('body') as NodePath;\n const hookStatementStr = `\\n const content = ${hook}('${finalKey}');\\n`;\n\n if (bodyPath.isBlockStatement()) {\n textEdits.push({\n start: bodyPath.node.start! + 1,\n end: bodyPath.node.start! + 1,\n replacement: hookStatementStr,\n });\n } else if (bodyPath.isExpression()) {\n const start = bodyPath.node.start!;\n const end = bodyPath.node.end!;\n\n // Detect wrapping parentheses: `() => (expr)` — scan backward from\n // the expression start and forward from its end for matching `(` / `)`.\n let parenStart = -1;\n let parenEnd = -1;\n\n for (let i = start - 1; i >= 0; i--) {\n const char = fileCode[i];\n if (char === '(') {\n parenStart = i;\n break;\n }\n if (char !== ' ' && char !== '\\n' && char !== '\\r' && char !== '\\t')\n break;\n }\n\n if (parenStart !== -1) {\n for (let i = end; i < fileCode.length; i++) {\n const char = fileCode[i];\n if (char === ')') {\n parenEnd = i;\n break;\n }\n if (char !== ' ' && char !== '\\n' && char !== '\\r' && char !== '\\t')\n break;\n }\n }\n\n if (parenStart !== -1 && parenEnd !== -1) {\n // Replace the surrounding `(` … `)` with a block statement. We keep\n // the parentheses as `return (…)` to prevent automatic semicolon\n // insertion when the returned expression starts on a new line.\n textEdits.push({\n start: parenEnd,\n end: parenEnd + 1,\n replacement: `)\\n}`,\n });\n textEdits.push({\n start: parenStart,\n end: parenStart + 1,\n replacement: `{\\n const content = ${hook}('${finalKey}');\\n return (`,\n });\n } else {\n textEdits.push({\n start: end,\n end: end,\n replacement: `\\n}`,\n });\n textEdits.push({\n start: start,\n end: start,\n replacement: `{\\n const content = ${hook}('${finalKey}');\\n return `,\n });\n }\n }\n }\n }\n\n const injectImport = (hookName: string, targetPackage: string) => {\n let existingImportPath: NodePath<t.ImportDeclaration> | undefined;\n\n traverse(ast, {\n ImportDeclaration(path) {\n if (path.node.source.value === targetPackage) {\n existingImportPath = path;\n path.stop();\n }\n },\n });\n\n if (!existingImportPath) {\n const newImportStr = `import { ${hookName} } from '${targetPackage}';\\n`;\n let insertPos = 0;\n\n if (ast.program.body.length > 0) {\n insertPos = ast.program.body[0].start!;\n } else if (ast.program.directives && ast.program.directives.length > 0) {\n insertPos =\n ast.program.directives[ast.program.directives.length - 1].end!;\n\n if (fileCode[insertPos] === ';') insertPos++;\n\n textEdits.push({\n start: insertPos,\n end: insertPos,\n replacement: `\\n${newImportStr}`,\n });\n return;\n }\n\n if (\n insertPos === 0 ||\n (ast.program.body.length > 0 &&\n insertPos === ast.program.body[0].start!)\n ) {\n textEdits.push({\n start: insertPos,\n end: insertPos,\n replacement: newImportStr,\n });\n }\n } else {\n const existingSpecifiers = existingImportPath.node.specifiers;\n const missingImport = !existingSpecifiers.some(\n (specifier) =>\n t.isImportSpecifier(specifier) &&\n t.isIdentifier(specifier.imported) &&\n specifier.imported.name === hookName\n );\n\n if (missingImport) {\n const importCode = fileCode.slice(\n existingImportPath.node.start!,\n existingImportPath.node.end!\n );\n const closingBraceIndex = importCode.lastIndexOf('}');\n\n if (closingBraceIndex !== -1) {\n const isCommaNeeded = !importCode\n .slice(0, closingBraceIndex)\n .trim()\n .endsWith(',');\n const absolutePos =\n existingImportPath.node.start! + closingBraceIndex;\n const prefix = isCommaNeeded ? ', ' : ' ';\n textEdits.push({\n start: absolutePos,\n end: absolutePos,\n replacement: `${prefix}${hookName} `,\n });\n }\n }\n }\n };\n\n if (needsUseIntlayer) injectImport('useIntlayer', effectivePackageName);\n\n if (needsGetIntlayer) injectImport('getIntlayer', 'intlayer');\n\n textEdits.sort((editA, editB) => {\n if (editB.start !== editA.start) return editB.start - editA.start;\n\n return editB.end - editA.end;\n });\n\n let formattedCode = fileCode;\n\n for (const edit of textEdits) {\n formattedCode =\n formattedCode.slice(0, edit.start) +\n edit.replacement +\n formattedCode.slice(edit.end);\n }\n\n if (save) {\n writeFileSync(filePath, formattedCode);\n\n const formatCommand = detectFormatCommand(configuration);\n\n if (formatCommand) {\n try {\n execSync(formatCommand.replace('{{file}}', filePath), {\n stdio: 'inherit',\n cwd: configuration.system.baseDir,\n });\n } catch (error) {\n console.error(error);\n }\n }\n }\n\n return { extractedContent, modifiedCode: formattedCode };\n};\n"],"mappings":";;;;;;;;;;;AAqBA,MAAM,WACJ,OAAO,cAAc,aAAa,YAAa,UAAkB;;;;AAMnE,MAAa,kBACX,UACA,cACA,aACA,eACA,OAAgB,MAChB,uBAAgD,EAAE,EAClD,qBAIU;CACV,MAAM,WAAW,oBAAoB,aAAa,UAAU,QAAQ;CAEpE,MAAM,MAAM,MAAM,UAAU;EAC1B,YAAY;EACZ,SAAS,CAAC,OAAO,aAAa;EAC/B,CAAC;CAEF,MAAM,eAAe,IAAI,QAAQ,WAAW,MACzC,cAAc,UAAU,MAAM,UAAU,aAC1C;CAED,MAAM,uBACJ,wBAAwB,IAAI,YAAY,IAAI,CAAC,eACzC,GAAG,YAAY,WACf;CAIN,MAAM,UACJ,gBAAgB,oBAAoB,gBAAgB;CAGtD,MAAM,EACJ,kBACA,cACA,wBACA,iBACA,gBACA,YACE,iCACF,KACA,0BAXmB,IAAI,KAAa,EAapC,cACA,eACA,UACA,qBACD;AAED,KAAI,OAAO,KAAK,iBAAiB,CAAC,WAAW,EAAG,QAAO;CAEvD,MAAM,YAAwB,EAAE;CAGhC,MAAM,sCAAsB,IAAI,KAAuB;AACvD,MAAK,MAAM,iBAAiB,eAC1B,qBAAoB,IAAI,cAAc,MAAM,cAAc;CAI5D,MAAM,oCAAoB,IAAI,KAA+C;AAC7E,MAAK,MAAM,iBAAiB,eAC1B,mBAAkB,IAChB,cAAc,MACd,wBAAwB,cAAc,CACvC;;;;;CAOH,MAAM,0BACJ,SACqC;EACrC,IAAI,UAA2B;AAC/B,SAAO,SAAS;AACd,OAAI,oBAAoB,IAAI,QAAQ,KAAK,CACvC,QAAO,kBAAkB,IAAI,QAAQ,KAAK;AAE5C,aAAU,QAAQ;;;CAKtB,MAAM,wBACJ,SACkC;EAClC,IAAI,UAA2B;AAC/B,SAAO,SAAS;GACd,MAAM,gBAAgB,oBAAoB,IAAI,QAAQ,KAAK;AAE3D,OAAI,eAAe;IACjB,MAAM,eAAe,kBAAkB,IAAI,cAAc,KAAK;AAE9D,QAAI,aACF,QAAO,aAAa;AAGtB,QAAI,uBAAuB,IAAI,cAAc,CAC3C,QAAO,QAAQ,IAAI,cAAc,KAAK,IAAI;;AAG9C,aAAU,QAAQ;;AAEpB,SAAO;;AAGT,MAAK,MAAM,EACT,MACA,KACA,MACA,WACA,uBACG,cAAc;EACjB,MAAM,eAAe,uBAAuB,KAAK;EAGjD,MAAM,UAAU,cAAc,gBAAgB;EAC9C,MAAM,oBAAoB,cAAc,iBACpC,MACA,UACE,GAAG,QAAQ,KAAK,QAChB,GAAG,QAAQ,GAAG;EAEpB,MAAM,cADW,qBAAqB,KAAK,KACV,gBAAgB,KAAK;AAEtD,MAAI,SAAS,cAAc,KAAK,WAAW,CACzC,WAAU,KAAK;GACb,OAAO,KAAK,KAAK;GACjB,KAAK,KAAK,KAAK;GACf,aAAa,IAAI,kBAAkB;GACpC,CAAC;WACO,SAAS,mBAAmB,KAAK,gBAAgB,EAAE;GAC5D,MAAM,UAAU,KAAK,KAAK;AAE1B,OAAI,QACF,WAAU,KAAK;IACb,OAAO,QAAQ;IACf,KAAK,QAAQ;IACb,aAAa,IAAI,oBAAoB,YAAY;IAClD,CAAC;aAEK,SAAS,oBAAoB,KAAK,iBAAiB,CAC5D,WAAU,KAAK;GACb,OAAO,KAAK,KAAK;GACjB,KAAK,KAAK,KAAK;GACf,aAAa,GAAG,oBAAoB;GACrC,CAAC;WAEF,SAAS,uBACT,qBACA,kBAAkB,SAAS,GAC3B;GACA,MAAM,YAAY,IAAI,kBAAkB;GACxC,MAAM,QAAQ,kBAAkB,GAAG;GACnC,MAAM,MAAM,kBAAkB,kBAAkB,SAAS,GAAG;AAC5D,aAAU,KAAK;IAAE;IAAO;IAAK,aAAa;IAAW,CAAC;aAEtD,SAAS,mBACT,aACA,qBACA,kBAAkB,SAAS,GAC3B;GAUA,MAAM,YAAY,IAAI,kBAAkB,KATvB,UACd,KAAK,mBAAmB;IACvB,MAAM,CAAC,KAAK,iBAAiB,eAC1B,MAAM,IAAI,CACV,KAAK,SAAS,KAAK,MAAM,CAAC;AAC7B,WAAO,GAAG,IAAI,IAAI;KAClB,CACD,KAAK,KAAK,CAEyC;GACtD,MAAM,QAAQ,kBAAkB,GAAG;GACnC,MAAM,MAAM,kBAAkB,kBAAkB,SAAS,GAAG;AAC5D,aAAU,KAAK;IAAE;IAAO;IAAK,aAAa;IAAW,CAAC;;;CAI1D,IAAI,mBAAmB;CACvB,IAAI,mBAAmB;AAEvB,MAAK,MAAM,iBAAiB,wBAAwB;EAClD,MAAM,WAAW,gBAAgB,IAAI,cAAc,KAAK;EACxD,MAAM,eAAe,kBAAkB,IAAI,cAAc,KAAK;AAE9D,MAAI,cAAc;AAChB,OAAI,aAAa,SAAS,cAAe,oBAAmB;AAE5D,OAAI,aAAa,SAAS,cAAe,oBAAmB;AAI5D,OAAI,aAAa,kBAAkB,aAAa,mBAAmB;IACjE,MAAM,6BAAa,IAAI,KAAa;AAEpC,SAAK,MAAM,EAAE,MAAM,OAAO,KAAK,UAAU,cAAc;KACrD,IAAI,UAA2B;AAE/B,YAAO,SAAS;AACd,UAAI,QAAQ,SAAS,cAAc,MAAM;AACvC,kBAAW,IAAI,KAAK;AACpB;;AAEF,gBAAU,QAAQ;;;IAItB,MAAM,cAAc,CAAC,GAAG,WAAW,CAAC,QACjC,QAAQ,CAAC,aAAa,yBAAyB,SAAS,IAAI,CAC9D;AAED,QAAI,YAAY,SAAS,GAAG;KAC1B,MAAM,EAAE,sBAAsB;KAG9B,MAAM,WACJ,kBAAkB,WAChB,kBAAkB,WAAW,SAAS;AAE1C,eAAU,KAAK;MACb,OAAO,SAAS;MAChB,KAAK,SAAS;MACd,aAAa,KAAK,YAAY,KAAK,KAAK;MACzC,CAAC;;;SAGD;GACL,MAAM,OAAO,QAAQ,IAAI,cAAc,KAAK,IAAI;AAEhD,OAAI,SAAS,cAAe,oBAAmB;AAE/C,OAAI,SAAS,cAAe,oBAAmB;GAE/C,MAAM,WAAW,cAAc,IAAI,OAAO;GAC1C,MAAM,mBAAmB,uBAAuB,KAAK,IAAI,SAAS;AAElE,OAAI,SAAS,kBAAkB,CAC7B,WAAU,KAAK;IACb,OAAO,SAAS,KAAK,QAAS;IAC9B,KAAK,SAAS,KAAK,QAAS;IAC5B,aAAa;IACd,CAAC;YACO,SAAS,cAAc,EAAE;IAClC,MAAM,QAAQ,SAAS,KAAK;IAC5B,MAAM,MAAM,SAAS,KAAK;IAI1B,IAAI,aAAa;IACjB,IAAI,WAAW;AAEf,SAAK,IAAI,IAAI,QAAQ,GAAG,KAAK,GAAG,KAAK;KACnC,MAAM,OAAO,SAAS;AACtB,SAAI,SAAS,KAAK;AAChB,mBAAa;AACb;;AAEF,SAAI,SAAS,OAAO,SAAS,QAAQ,SAAS,QAAQ,SAAS,IAC7D;;AAGJ,QAAI,eAAe,GACjB,MAAK,IAAI,IAAI,KAAK,IAAI,SAAS,QAAQ,KAAK;KAC1C,MAAM,OAAO,SAAS;AACtB,SAAI,SAAS,KAAK;AAChB,iBAAW;AACX;;AAEF,SAAI,SAAS,OAAO,SAAS,QAAQ,SAAS,QAAQ,SAAS,IAC7D;;AAIN,QAAI,eAAe,MAAM,aAAa,IAAI;AAIxC,eAAU,KAAK;MACb,OAAO;MACP,KAAK,WAAW;MAChB,aAAa;MACd,CAAC;AACF,eAAU,KAAK;MACb,OAAO;MACP,KAAK,aAAa;MAClB,aAAa,wBAAwB,KAAK,IAAI,SAAS;MACxD,CAAC;WACG;AACL,eAAU,KAAK;MACb,OAAO;MACF;MACL,aAAa;MACd,CAAC;AACF,eAAU,KAAK;MACN;MACP,KAAK;MACL,aAAa,wBAAwB,KAAK,IAAI,SAAS;MACxD,CAAC;;;;;CAMV,MAAM,gBAAgB,UAAkB,kBAA0B;EAChE,IAAI;AAEJ,WAAS,KAAK,EACZ,kBAAkB,MAAM;AACtB,OAAI,KAAK,KAAK,OAAO,UAAU,eAAe;AAC5C,yBAAqB;AACrB,SAAK,MAAM;;KAGhB,CAAC;AAEF,MAAI,CAAC,oBAAoB;GACvB,MAAM,eAAe,YAAY,SAAS,WAAW,cAAc;GACnE,IAAI,YAAY;AAEhB,OAAI,IAAI,QAAQ,KAAK,SAAS,EAC5B,aAAY,IAAI,QAAQ,KAAK,GAAG;YACvB,IAAI,QAAQ,cAAc,IAAI,QAAQ,WAAW,SAAS,GAAG;AACtE,gBACE,IAAI,QAAQ,WAAW,IAAI,QAAQ,WAAW,SAAS,GAAG;AAE5D,QAAI,SAAS,eAAe,IAAK;AAEjC,cAAU,KAAK;KACb,OAAO;KACP,KAAK;KACL,aAAa,KAAK;KACnB,CAAC;AACF;;AAGF,OACE,cAAc,KACb,IAAI,QAAQ,KAAK,SAAS,KACzB,cAAc,IAAI,QAAQ,KAAK,GAAG,MAEpC,WAAU,KAAK;IACb,OAAO;IACP,KAAK;IACL,aAAa;IACd,CAAC;aAIkB,CADK,mBAAmB,KAAK,WACT,MACvC,cACC,EAAE,kBAAkB,UAAU,IAC9B,EAAE,aAAa,UAAU,SAAS,IAClC,UAAU,SAAS,SAAS,SAC/B,EAEkB;GACjB,MAAM,aAAa,SAAS,MAC1B,mBAAmB,KAAK,OACxB,mBAAmB,KAAK,IACzB;GACD,MAAM,oBAAoB,WAAW,YAAY,IAAI;AAErD,OAAI,sBAAsB,IAAI;IAC5B,MAAM,gBAAgB,CAAC,WACpB,MAAM,GAAG,kBAAkB,CAC3B,MAAM,CACN,SAAS,IAAI;IAChB,MAAM,cACJ,mBAAmB,KAAK,QAAS;IACnC,MAAM,SAAS,gBAAgB,OAAO;AACtC,cAAU,KAAK;KACb,OAAO;KACP,KAAK;KACL,aAAa,GAAG,SAAS,SAAS;KACnC,CAAC;;;;AAMV,KAAI,iBAAkB,cAAa,eAAe,qBAAqB;AAEvE,KAAI,iBAAkB,cAAa,eAAe,WAAW;AAE7D,WAAU,MAAM,OAAO,UAAU;AAC/B,MAAI,MAAM,UAAU,MAAM,MAAO,QAAO,MAAM,QAAQ,MAAM;AAE5D,SAAO,MAAM,MAAM,MAAM;GACzB;CAEF,IAAI,gBAAgB;AAEpB,MAAK,MAAM,QAAQ,UACjB,iBACE,cAAc,MAAM,GAAG,KAAK,MAAM,GAClC,KAAK,cACL,cAAc,MAAM,KAAK,IAAI;AAGjC,KAAI,MAAM;AACR,gBAAc,UAAU,cAAc;EAEtC,MAAM,gBAAgB,oBAAoB,cAAc;AAExD,MAAI,cACF,KAAI;AACF,YAAS,cAAc,QAAQ,YAAY,SAAS,EAAE;IACpD,OAAO;IACP,KAAK,cAAc,OAAO;IAC3B,CAAC;WACK,OAAO;AACd,WAAQ,MAAM,MAAM;;;AAK1B,QAAO;EAAE;EAAkB,cAAc;EAAe"}
|
|
1
|
+
{"version":3,"file":"processTsxFile.mjs","names":[],"sources":["../../../src/extractContent/processTsxFile.ts"],"sourcesContent":["import { execSync } from 'node:child_process';\nimport { readFileSync, writeFileSync } from 'node:fs';\nimport { parse } from '@babel/parser';\nimport _traverse, { type NodePath } from '@babel/traverse';\nimport * as t from '@babel/types';\nimport { detectFormatCommand } from '@intlayer/chokidar/cli';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { extractBabelContentForComponents } from './babelProcessor';\nimport {\n type ExistingIntlayerInfo,\n getExistingIntlayerInfo,\n type PackageName,\n SERVER_CAPABLE_PACKAGES,\n} from './utils';\n\nexport type TextEdit = {\n start: number;\n end: number;\n replacement: string;\n};\n\nconst traverse = (\n typeof _traverse === 'function' ? _traverse : (_traverse as any).default\n) as typeof _traverse;\n\n/**\n * Processes a TSX/JSX/TS/JS file to extract content and inject Intlayer hooks/calls.\n */\nexport const processTsxFile = (\n filePath: string,\n componentKey: string,\n packageName: PackageName,\n configuration: IntlayerConfig,\n save: boolean = true,\n unmergedDictionaries: Record<string, unknown> = {},\n providedFileCode?: string\n): {\n extractedContent: Record<string, Record<string, string>>;\n modifiedCode: string;\n} | null => {\n const fileCode = providedFileCode ?? readFileSync(filePath, 'utf-8');\n\n const ast = parse(fileCode, {\n sourceType: 'module',\n plugins: ['jsx', 'typescript'],\n });\n\n const hasUseClient = ast.program.directives.some(\n (directive) => directive.value.value === 'use client'\n );\n\n const effectivePackageName =\n SERVER_CAPABLE_PACKAGES.has(packageName) && !hasUseClient\n ? `${packageName}/server`\n : packageName;\n\n // Both Solid and Angular expose content via a reactive/signal function —\n // access is `content().key` rather than `content.key`.\n const isSolid =\n packageName === 'solid-intlayer' || packageName === 'angular-intlayer';\n const existingKeys = new Set<string>();\n\n const {\n extractedContent,\n replacements,\n componentsNeedingHooks,\n componentKeyMap,\n componentPaths,\n hookMap,\n } = extractBabelContentForComponents(\n ast,\n fileCode,\n existingKeys,\n componentKey,\n configuration,\n filePath,\n unmergedDictionaries\n );\n\n if (Object.keys(extractedContent).length === 0) return null;\n\n const textEdits: TextEdit[] = [];\n\n // Build a lookup map: component AST node → NodePath (O(1) access)\n const componentNodeToPath = new Map<t.Node, NodePath>();\n for (const componentPath of componentPaths) {\n componentNodeToPath.set(componentPath.node, componentPath);\n }\n\n // Cache getExistingIntlayerInfo results for all component paths (avoids repeated traversals)\n const existingInfoCache = new Map<t.Node, ExistingIntlayerInfo | undefined>();\n for (const componentPath of componentPaths) {\n existingInfoCache.set(\n componentPath.node,\n getExistingIntlayerInfo(componentPath)\n );\n }\n\n /**\n * Walks up the ancestor chain to find the nearest enclosing component's\n * existing Intlayer info (if any).\n */\n const getExistingInfoForPath = (\n path: NodePath\n ): ExistingIntlayerInfo | undefined => {\n let current: NodePath | null = path;\n while (current) {\n if (componentNodeToPath.has(current.node)) {\n return existingInfoCache.get(current.node);\n }\n current = current.parentPath;\n }\n return undefined;\n };\n\n const getProvidingHookType = (\n path: NodePath\n ): 'useIntlayer' | 'getIntlayer' => {\n let current: NodePath | null = path;\n while (current) {\n const componentPath = componentNodeToPath.get(current.node);\n\n if (componentPath) {\n const existingInfo = existingInfoCache.get(componentPath.node);\n\n if (existingInfo) {\n return existingInfo.hook;\n }\n\n if (componentsNeedingHooks.has(componentPath)) {\n return hookMap.get(componentPath.node) || 'useIntlayer';\n }\n }\n current = current.parentPath;\n }\n return 'useIntlayer';\n };\n\n for (const {\n path,\n key,\n type,\n variables,\n childrenToReplace,\n } of replacements) {\n const existingInfo = getExistingInfoForPath(path);\n // When the existing call is destructured (e.g. `const { a } = getIntlayer(...)`),\n // new keys are added directly to that destructuring, so access them by name alone.\n const varName = existingInfo?.variableName ?? 'content';\n const contentAccessCode = existingInfo?.isDestructured\n ? key\n : isSolid\n ? `${varName}().${key}`\n : `${varName}.${key}`;\n const hookType = getProvidingHookType(path);\n const valueSuffix = hookType === 'getIntlayer' ? '' : '.value';\n\n if (type === 'jsx-text' && path.isJSXText()) {\n textEdits.push({\n start: path.node.start!,\n end: path.node.end!,\n replacement: `{${contentAccessCode}}`,\n });\n } else if (type === 'jsx-attribute' && path.isJSXAttribute()) {\n const valNode = path.node.value;\n\n if (valNode) {\n textEdits.push({\n start: valNode.start!,\n end: valNode.end!,\n replacement: `{${contentAccessCode}${valueSuffix}}`,\n });\n }\n } else if (type === 'string-literal' && path.isStringLiteral()) {\n textEdits.push({\n start: path.node.start!,\n end: path.node.end!,\n replacement: `${contentAccessCode}${valueSuffix}`,\n });\n } else if (\n type === 'jsx-text-combined' &&\n childrenToReplace &&\n childrenToReplace.length > 0\n ) {\n const accessStr = `{${contentAccessCode}}`;\n const start = childrenToReplace[0].start!;\n const end = childrenToReplace[childrenToReplace.length - 1].end!;\n textEdits.push({ start, end, replacement: accessStr });\n } else if (\n type === 'jsx-insertion' &&\n variables &&\n childrenToReplace &&\n childrenToReplace.length > 0\n ) {\n const objProps = variables\n .map((variableString) => {\n const [key, variableValue] = variableString\n .split(':')\n .map((part) => part.trim());\n return `${key}: ${variableValue}`;\n })\n .join(', ');\n\n const accessStr = `{${contentAccessCode}({ ${objProps} })}`;\n const start = childrenToReplace[0].start!;\n const end = childrenToReplace[childrenToReplace.length - 1].end!;\n textEdits.push({ start, end, replacement: accessStr });\n }\n }\n\n let needsUseIntlayer = false;\n let needsGetIntlayer = false;\n\n for (const componentPath of componentsNeedingHooks) {\n const finalKey = componentKeyMap.get(componentPath.node)!;\n const existingInfo = existingInfoCache.get(componentPath.node);\n\n if (existingInfo) {\n if (existingInfo.hook === 'useIntlayer') needsUseIntlayer = true;\n\n if (existingInfo.hook === 'getIntlayer') needsGetIntlayer = true;\n\n // When the existing call is destructured, inject any missing keys into\n // the destructuring pattern so they can be accessed by name directly.\n if (existingInfo.isDestructured && existingInfo.objectPatternNode) {\n const neededKeys = new Set<string>();\n\n for (const { path: rPath, key: rKey } of replacements) {\n let current: NodePath | null = rPath;\n\n while (current) {\n if (current.node === componentPath.node) {\n neededKeys.add(rKey);\n break;\n }\n current = current.parentPath;\n }\n }\n\n const missingKeys = [...neededKeys].filter(\n (key) => !existingInfo.existingDestructuredKeys.includes(key)\n );\n\n if (missingKeys.length > 0) {\n const { objectPatternNode } = existingInfo;\n // Insert right after the last property so the space/newline before\n // `}` is naturally preserved: `{ a }` → `{ a, b }`.\n const lastProp =\n objectPatternNode.properties[\n objectPatternNode.properties.length - 1\n ];\n textEdits.push({\n start: lastProp.end!,\n end: lastProp.end!,\n replacement: `, ${missingKeys.join(', ')}`,\n });\n }\n }\n } else {\n const hook = hookMap.get(componentPath.node) || 'useIntlayer';\n\n if (hook === 'useIntlayer') needsUseIntlayer = true;\n\n if (hook === 'getIntlayer') needsGetIntlayer = true;\n\n const hookStatementStr = `\\n const content = ${hook}('${finalKey}');\\n`;\n\n if (componentPath.isProgram()) {\n // Find the last import or directive to inject the getIntlayer call\n let insertPos = 0;\n if (componentPath.node.directives.length > 0) {\n insertPos =\n componentPath.node.directives[\n componentPath.node.directives.length - 1\n ].end!;\n }\n for (const stmt of componentPath.node.body) {\n if (t.isImportDeclaration(stmt)) {\n insertPos = Math.max(insertPos, stmt.end!);\n }\n }\n\n textEdits.push({\n start: insertPos,\n end: insertPos,\n replacement: `\\n${hookStatementStr}`,\n });\n continue;\n }\n\n const bodyPath = componentPath.get('body') as NodePath;\n\n if (bodyPath.isBlockStatement()) {\n textEdits.push({\n start: bodyPath.node.start! + 1,\n end: bodyPath.node.start! + 1,\n replacement: hookStatementStr,\n });\n } else if (bodyPath.isExpression()) {\n const start = bodyPath.node.start!;\n const end = bodyPath.node.end!;\n\n // Detect wrapping parentheses: `() => (expr)` — scan backward from\n // the expression start and forward from its end for matching `(` / `)`.\n let parenStart = -1;\n let parenEnd = -1;\n\n for (let i = start - 1; i >= 0; i--) {\n const char = fileCode[i];\n if (char === '(') {\n parenStart = i;\n break;\n }\n if (char !== ' ' && char !== '\\n' && char !== '\\r' && char !== '\\t')\n break;\n }\n\n if (parenStart !== -1) {\n for (let i = end; i < fileCode.length; i++) {\n const char = fileCode[i];\n if (char === ')') {\n parenEnd = i;\n break;\n }\n if (char !== ' ' && char !== '\\n' && char !== '\\r' && char !== '\\t')\n break;\n }\n }\n\n if (parenStart !== -1 && parenEnd !== -1) {\n // Replace the surrounding `(` … `)` with a block statement. We keep\n // the parentheses as `return (…)` to prevent automatic semicolon\n // insertion when the returned expression starts on a new line.\n textEdits.push({\n start: parenEnd,\n end: parenEnd + 1,\n replacement: `)\\n}`,\n });\n textEdits.push({\n start: parenStart,\n end: parenStart + 1,\n replacement: `{\\n const content = ${hook}('${finalKey}');\\n return (`,\n });\n } else {\n textEdits.push({\n start: end,\n end: end,\n replacement: `\\n}`,\n });\n textEdits.push({\n start: start,\n end: start,\n replacement: `{\\n const content = ${hook}('${finalKey}');\\n return `,\n });\n }\n }\n }\n }\n\n const injectImport = (hookName: string, targetPackage: string) => {\n let existingImportPath: NodePath<t.ImportDeclaration> | undefined;\n\n traverse(ast, {\n ImportDeclaration(path) {\n if (path.node.source.value === targetPackage) {\n existingImportPath = path;\n path.stop();\n }\n },\n });\n\n if (!existingImportPath) {\n const newImportStr = `import { ${hookName} } from '${targetPackage}';\\n`;\n let insertPos = 0;\n\n if (ast.program.body.length > 0) {\n insertPos = ast.program.body[0].start!;\n } else if (ast.program.directives && ast.program.directives.length > 0) {\n insertPos =\n ast.program.directives[ast.program.directives.length - 1].end!;\n\n if (fileCode[insertPos] === ';') insertPos++;\n\n textEdits.push({\n start: insertPos,\n end: insertPos,\n replacement: `\\n${newImportStr}`,\n });\n return;\n }\n\n if (\n insertPos === 0 ||\n (ast.program.body.length > 0 &&\n insertPos === ast.program.body[0].start!)\n ) {\n textEdits.push({\n start: insertPos,\n end: insertPos,\n replacement: newImportStr,\n });\n }\n } else {\n const existingSpecifiers = existingImportPath.node.specifiers;\n const missingImport = !existingSpecifiers.some(\n (specifier) =>\n t.isImportSpecifier(specifier) &&\n t.isIdentifier(specifier.imported) &&\n specifier.imported.name === hookName\n );\n\n if (missingImport) {\n const importCode = fileCode.slice(\n existingImportPath.node.start!,\n existingImportPath.node.end!\n );\n const closingBraceIndex = importCode.lastIndexOf('}');\n\n if (closingBraceIndex !== -1) {\n const isCommaNeeded = !importCode\n .slice(0, closingBraceIndex)\n .trim()\n .endsWith(',');\n const absolutePos =\n existingImportPath.node.start! + closingBraceIndex;\n const prefix = isCommaNeeded ? ', ' : ' ';\n textEdits.push({\n start: absolutePos,\n end: absolutePos,\n replacement: `${prefix}${hookName} `,\n });\n }\n }\n }\n };\n\n if (needsUseIntlayer) injectImport('useIntlayer', effectivePackageName);\n\n if (needsGetIntlayer) injectImport('getIntlayer', 'intlayer');\n\n textEdits.sort((editA, editB) => {\n if (editB.start !== editA.start) return editB.start - editA.start;\n\n return editB.end - editA.end;\n });\n\n let formattedCode = fileCode;\n\n for (const edit of textEdits) {\n formattedCode =\n formattedCode.slice(0, edit.start) +\n edit.replacement +\n formattedCode.slice(edit.end);\n }\n\n if (save) {\n writeFileSync(filePath, formattedCode);\n\n const formatCommand = detectFormatCommand(configuration);\n\n if (formatCommand) {\n try {\n execSync(formatCommand.replace('{{file}}', filePath), {\n stdio: 'inherit',\n cwd: configuration.system.baseDir,\n });\n } catch (error) {\n console.error(error);\n }\n }\n }\n\n return { extractedContent, modifiedCode: formattedCode };\n};\n"],"mappings":"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"}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
const generateKey = (text, existingKeys) => {
|
|
3
3
|
let key = text.normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[\s_-]+/g, " ").replace(/[^\p{L}\p{N} ]/gu, "").trim().split(" ").filter(Boolean).slice(0, 5).map((word, index) => index === 0 ? word.toLowerCase() : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
|
|
4
4
|
if (!key) key = "content";
|
|
5
|
+
if (/^[0-9]/.test(key)) key = `x${key}`;
|
|
5
6
|
if (existingKeys.has(key)) {
|
|
6
7
|
let i = 1;
|
|
7
8
|
while (existingKeys.has(`${key}${i}`)) i++;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generateKey.mjs","names":[],"sources":["../../../../src/extractContent/utils/generateKey.ts"],"sourcesContent":["export const generateKey = (\n text: string,\n existingKeys: Set<string>\n): string => {\n const maxWords = 5;\n let key = text\n .normalize('NFD') // Normalize to decomposes combined characters (e.g., é -> e + ´)\n .replace(/[\\u0300-\\u036f]/g, '') // Remove the accent characters\n .replace(/[\\s_-]+/g, ' ')\n .replace(/[^\\p{L}\\p{N} ]/gu, '')\n .trim()\n .split(' ')\n .filter(Boolean)\n .slice(0, maxWords)\n .map((word, index) =>\n index === 0\n ? word.toLowerCase()\n : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()\n )\n .join('');\n\n if (!key) key = 'content';\n if (existingKeys.has(key)) {\n let i = 1;\n while (existingKeys.has(`${key}${i}`)) i++;\n key = `${key}${i}`;\n }\n return key;\n};\n"],"mappings":";AAAA,MAAa,eACX,MACA,iBACW;CAEX,IAAI,MAAM,KACP,UAAU,MAAM,CAChB,QAAQ,oBAAoB,GAAG,CAC/B,QAAQ,YAAY,IAAI,CACxB,QAAQ,oBAAoB,GAAG,CAC/B,MAAM,CACN,MAAM,IAAI,CACV,OAAO,QAAQ,CACf,MAAM,GATQ,EASI,CAClB,KAAK,MAAM,UACV,UAAU,IACN,KAAK,aAAa,GAClB,KAAK,OAAO,EAAE,CAAC,aAAa,GAAG,KAAK,MAAM,EAAE,CAAC,aAAa,CAC/D,CACA,KAAK,GAAG;AAEX,KAAI,CAAC,IAAK,OAAM;
|
|
1
|
+
{"version":3,"file":"generateKey.mjs","names":[],"sources":["../../../../src/extractContent/utils/generateKey.ts"],"sourcesContent":["export const generateKey = (\n text: string,\n existingKeys: Set<string>\n): string => {\n const maxWords = 5;\n let key = text\n .normalize('NFD') // Normalize to decomposes combined characters (e.g., é -> e + ´)\n .replace(/[\\u0300-\\u036f]/g, '') // Remove the accent characters\n .replace(/[\\s_-]+/g, ' ')\n .replace(/[^\\p{L}\\p{N} ]/gu, '')\n .trim()\n .split(' ')\n .filter(Boolean)\n .slice(0, maxWords)\n .map((word, index) =>\n index === 0\n ? word.toLowerCase()\n : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()\n )\n .join('');\n\n if (!key) key = 'content';\n\n // If the key starts with a number, prepend 'x' to make it a valid JS identifier\n if (/^[0-9]/.test(key)) {\n key = `x${key}`;\n }\n\n if (existingKeys.has(key)) {\n let i = 1;\n while (existingKeys.has(`${key}${i}`)) i++;\n key = `${key}${i}`;\n }\n return key;\n};\n"],"mappings":";AAAA,MAAa,eACX,MACA,iBACW;CAEX,IAAI,MAAM,KACP,UAAU,MAAM,CAChB,QAAQ,oBAAoB,GAAG,CAC/B,QAAQ,YAAY,IAAI,CACxB,QAAQ,oBAAoB,GAAG,CAC/B,MAAM,CACN,MAAM,IAAI,CACV,OAAO,QAAQ,CACf,MAAM,GATQ,EASI,CAClB,KAAK,MAAM,UACV,UAAU,IACN,KAAK,aAAa,GAClB,KAAK,OAAO,EAAE,CAAC,aAAa,GAAG,KAAK,MAAM,EAAE,CAAC,aAAa,CAC/D,CACA,KAAK,GAAG;AAEX,KAAI,CAAC,IAAK,OAAM;AAGhB,KAAI,SAAS,KAAK,IAAI,CACpB,OAAM,IAAI;AAGZ,KAAI,aAAa,IAAI,IAAI,EAAE;EACzB,IAAI,IAAI;AACR,SAAO,aAAa,IAAI,GAAG,MAAM,IAAI,CAAE;AACvC,QAAM,GAAG,MAAM;;AAEjB,QAAO"}
|
|
@@ -4,16 +4,17 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Filters out:
|
|
6
6
|
* - Empty strings
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
7
|
+
* - Emails
|
|
8
|
+
* - Uncapitalized strings of 2 words or fewer (likely technical terms)
|
|
9
9
|
* - Dynamic content patterns like Vue bindings (`v-`) or object patterns (`{`)
|
|
10
10
|
*/
|
|
11
11
|
const shouldExtract = (text) => {
|
|
12
12
|
const trimmed = text.trim();
|
|
13
13
|
if (!trimmed) return false;
|
|
14
|
-
if (
|
|
15
|
-
if (!/^[A-Z]/.test(trimmed)) return false;
|
|
14
|
+
if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) return false;
|
|
16
15
|
if (trimmed.startsWith("{") || trimmed.startsWith("v-")) return false;
|
|
16
|
+
const wordCount = trimmed.split(/\s+/).length;
|
|
17
|
+
if (!/^[A-Z]/.test(trimmed) && wordCount <= 2) return false;
|
|
17
18
|
return true;
|
|
18
19
|
};
|
|
19
20
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shouldExtract.mjs","names":[],"sources":["../../../../src/extractContent/utils/shouldExtract.ts"],"sourcesContent":["/**\n * Checks whether the given text should be extracted as a translatable string.\n *\n * Filters out:\n * - Empty strings\n * -
|
|
1
|
+
{"version":3,"file":"shouldExtract.mjs","names":[],"sources":["../../../../src/extractContent/utils/shouldExtract.ts"],"sourcesContent":["/**\n * Checks whether the given text should be extracted as a translatable string.\n *\n * Filters out:\n * - Empty strings\n * - Emails\n * - Uncapitalized strings of 2 words or fewer (likely technical terms)\n * - Dynamic content patterns like Vue bindings (`v-`) or object patterns (`{`)\n */\nexport const shouldExtract = (text: string): boolean => {\n const trimmed = text.trim();\n\n if (!trimmed) return false;\n\n // Ignore emails\n if (/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(trimmed)) return false;\n\n // Ignore dynamic content patterns\n if (trimmed.startsWith('{') || trimmed.startsWith('v-')) return false;\n\n const wordCount = trimmed.split(/\\s+/).length;\n const isCapitalized = /^[A-Z]/.test(trimmed);\n\n // We usually want to extract full sentences or labels, not single/short technical words.\n // Extract if capitalized, or if it contains more than 2 words.\n if (!isCapitalized && wordCount <= 2) return false;\n\n return true;\n};\n"],"mappings":";;;;;;;;;;AASA,MAAa,iBAAiB,SAA0B;CACtD,MAAM,UAAU,KAAK,MAAM;AAE3B,KAAI,CAAC,QAAS,QAAO;AAGrB,KAAI,6BAA6B,KAAK,QAAQ,CAAE,QAAO;AAGvD,KAAI,QAAQ,WAAW,IAAI,IAAI,QAAQ,WAAW,KAAK,CAAE,QAAO;CAEhE,MAAM,YAAY,QAAQ,MAAM,MAAM,CAAC;AAKvC,KAAI,CAJkB,SAAS,KAAK,QAAQ,IAItB,aAAa,EAAG,QAAO;AAE7C,QAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"babelProcessor.d.ts","names":[],"sources":["../../../src/extractContent/babelProcessor.ts"],"mappings":";;;;;KAYY,gBAAA;EACV,IAAA,EAAM,QAAA;EACN,GAAA;EACA,IAAA;EAMA,YAAA;EACA,iBAAA,GAAoB,CAAA,CAAE,IAAA;EACtB,SAAA;AAAA;;;;;cAYW,uBAAA,GACX,IAAA,EAAM,QAAA,CAAS,CAAA,CAAE,UAAA,GAAa,CAAA,CAAE,WAAA,GAChC,QAAA,UACA,YAAA,EAAc,GAAA,UACd,sBAAA,GAAyB,IAAA,EAAM,QAAA,aAC/B,gBAAA,EAAkB,MAAA,SAAe,MAAA,mBACjC,YAAA,EAAc,gBAAA,IACd,YAAA,EAAc,GAAA,CAAI,CAAA,CAAE,IAAA;;;;;cA6GT,gCAAA,GACX,GAAA,EAAK,CAAA,CAAE,IAAA,EACP,QAAA,UACA,YAAA,EAAc,GAAA,UACd,UAAA,UACA,aAAA,EAAe,cAAA,EACf,QAAA,UACA,oBAAA,GAAsB,MAAA;EAEtB,gBAAA,EAAkB,MAAA,SAAe,MAAA;EACjC,YAAA,EAAc,gBAAA;EACd,sBAAA,EAAwB,GAAA,CAAI,QAAA;EAC5B,eAAA,EAAiB,GAAA,CAAI,CAAA,CAAE,IAAA;EACvB,cAAA,EAAgB,QAAA;EAChB,OAAA,EAAS,GAAA,CAAI,CAAA,CAAE,IAAA;EACf,OAAA;AAAA;;;;
|
|
1
|
+
{"version":3,"file":"babelProcessor.d.ts","names":[],"sources":["../../../src/extractContent/babelProcessor.ts"],"mappings":";;;;;KAYY,gBAAA;EACV,IAAA,EAAM,QAAA;EACN,GAAA;EACA,IAAA;EAMA,YAAA;EACA,iBAAA,GAAoB,CAAA,CAAE,IAAA;EACtB,SAAA;AAAA;;;;;cAYW,uBAAA,GACX,IAAA,EAAM,QAAA,CAAS,CAAA,CAAE,UAAA,GAAa,CAAA,CAAE,WAAA,GAChC,QAAA,UACA,YAAA,EAAc,GAAA,UACd,sBAAA,GAAyB,IAAA,EAAM,QAAA,aAC/B,gBAAA,EAAkB,MAAA,SAAe,MAAA,mBACjC,YAAA,EAAc,gBAAA,IACd,YAAA,EAAc,GAAA,CAAI,CAAA,CAAE,IAAA;;;;;cA6GT,gCAAA,GACX,GAAA,EAAK,CAAA,CAAE,IAAA,EACP,QAAA,UACA,YAAA,EAAc,GAAA,UACd,UAAA,UACA,aAAA,EAAe,cAAA,EACf,QAAA,UACA,oBAAA,GAAsB,MAAA;EAEtB,gBAAA,EAAkB,MAAA,SAAe,MAAA;EACjC,YAAA,EAAc,gBAAA;EACd,sBAAA,EAAwB,GAAA,CAAI,QAAA;EAC5B,eAAA,EAAiB,GAAA,CAAI,CAAA,CAAE,IAAA;EACvB,cAAA,EAAgB,QAAA;EAChB,OAAA,EAAS,GAAA,CAAI,CAAA,CAAE,IAAA;EACf,OAAA;AAAA;;;;cA8RW,gBAAA,GACX,GAAA,EAAK,CAAA,CAAE,IAAA,EACP,QAAA,UACA,YAAA,EAAc,GAAA,UACd,aAAA,EAAe,cAAA,EACf,QAAA,UACA,oBAAA,GAAsB,MAAA;EAEtB,gBAAA,EAAkB,MAAA;EAClB,YAAA,EAAc,gBAAA;AAAA"}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Filters out:
|
|
6
6
|
* - Empty strings
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
7
|
+
* - Emails
|
|
8
|
+
* - Uncapitalized strings of 2 words or fewer (likely technical terms)
|
|
9
9
|
* - Dynamic content patterns like Vue bindings (`v-`) or object patterns (`{`)
|
|
10
10
|
*/
|
|
11
11
|
declare const shouldExtract: (text: string) => boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intlayer/babel",
|
|
3
|
-
"version": "8.6.
|
|
3
|
+
"version": "8.6.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A Babel plugin for Intlayer that transforms declaration files and provides internationalization features during the build process according to the Intlayer configuration.",
|
|
6
6
|
"keywords": [
|
|
@@ -81,12 +81,12 @@
|
|
|
81
81
|
"@babel/plugin-syntax-jsx": "7.28.6",
|
|
82
82
|
"@babel/traverse": "7.29.0",
|
|
83
83
|
"@babel/types": "7.29.0",
|
|
84
|
-
"@intlayer/chokidar": "8.6.
|
|
85
|
-
"@intlayer/config": "8.6.
|
|
86
|
-
"@intlayer/core": "8.6.
|
|
87
|
-
"@intlayer/dictionaries-entry": "8.6.
|
|
88
|
-
"@intlayer/types": "8.6.
|
|
89
|
-
"@intlayer/unmerged-dictionaries-entry": "8.6.
|
|
84
|
+
"@intlayer/chokidar": "8.6.6",
|
|
85
|
+
"@intlayer/config": "8.6.6",
|
|
86
|
+
"@intlayer/core": "8.6.6",
|
|
87
|
+
"@intlayer/dictionaries-entry": "8.6.6",
|
|
88
|
+
"@intlayer/types": "8.6.6",
|
|
89
|
+
"@intlayer/unmerged-dictionaries-entry": "8.6.6",
|
|
90
90
|
"@types/babel__core": "7.20.5",
|
|
91
91
|
"@types/babel__generator": "7.27.0",
|
|
92
92
|
"@types/babel__traverse": "7.28.0"
|
|
@@ -104,8 +104,8 @@
|
|
|
104
104
|
"vitest": "4.1.2"
|
|
105
105
|
},
|
|
106
106
|
"peerDependencies": {
|
|
107
|
-
"@intlayer/svelte-compiler": "8.6.
|
|
108
|
-
"@intlayer/vue-compiler": "8.6.
|
|
107
|
+
"@intlayer/svelte-compiler": "8.6.6",
|
|
108
|
+
"@intlayer/vue-compiler": "8.6.6"
|
|
109
109
|
},
|
|
110
110
|
"peerDependenciesMeta": {
|
|
111
111
|
"@intlayer/svelte-compiler": {
|