@intlayer/babel 9.7.3 → 9.8.0-canary.1

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.
Files changed (25) hide show
  1. package/dist/cjs/extractContent/babelProcessor.cjs +32 -0
  2. package/dist/cjs/extractContent/babelProcessor.cjs.map +1 -1
  3. package/dist/cjs/extractContent/contentWriter.cjs +6 -2
  4. package/dist/cjs/extractContent/contentWriter.cjs.map +1 -1
  5. package/dist/cjs/extractContent/extractContent.cjs +1 -0
  6. package/dist/cjs/extractContent/extractContent.cjs.map +1 -1
  7. package/dist/cjs/extractContent/utils/index.cjs +1 -0
  8. package/dist/cjs/extractContent/utils/resolveDictionaryKey.cjs +19 -2
  9. package/dist/cjs/extractContent/utils/resolveDictionaryKey.cjs.map +1 -1
  10. package/dist/esm/extractContent/babelProcessor.mjs +33 -1
  11. package/dist/esm/extractContent/babelProcessor.mjs.map +1 -1
  12. package/dist/esm/extractContent/contentWriter.mjs +6 -2
  13. package/dist/esm/extractContent/contentWriter.mjs.map +1 -1
  14. package/dist/esm/extractContent/extractContent.mjs +2 -1
  15. package/dist/esm/extractContent/extractContent.mjs.map +1 -1
  16. package/dist/esm/extractContent/utils/index.mjs +2 -2
  17. package/dist/esm/extractContent/utils/resolveDictionaryKey.mjs +21 -5
  18. package/dist/esm/extractContent/utils/resolveDictionaryKey.mjs.map +1 -1
  19. package/dist/types/extractContent/babelProcessor.d.ts.map +1 -1
  20. package/dist/types/extractContent/contentWriter.d.ts.map +1 -1
  21. package/dist/types/extractContent/extractContent.d.ts.map +1 -1
  22. package/dist/types/extractContent/utils/index.d.ts +2 -2
  23. package/dist/types/extractContent/utils/resolveDictionaryKey.d.ts +7 -2
  24. package/dist/types/extractContent/utils/resolveDictionaryKey.d.ts.map +1 -1
  25. package/package.json +8 -8
@@ -320,17 +320,46 @@ const extractBabelContentForComponents = (ast, fileCode, existingKeys, defaultKe
320
320
  }
321
321
  return globalFileKey || defaultKey;
322
322
  };
323
+ const ignoreDirectives = (0, _intlayer_config_extract.getExtractionIgnoreDirectives)(fileCode);
324
+ /**
325
+ * Start offset of the JSX element a string directly belongs to: its text,
326
+ * a `{'literal'}` child, or one of its attributes.
327
+ */
328
+ const getEnclosingJsxElementStart = (path) => {
329
+ let current = path.parentPath;
330
+ if (current?.isJSXExpressionContainer()) current = current.parentPath;
331
+ if (current?.isJSXAttribute()) current = current.parentPath;
332
+ if (current?.isJSXOpeningElement()) current = current.parentPath;
333
+ return current?.isJSXElement() || current?.isJSXFragment() ? current.node.start ?? void 0 : void 0;
334
+ };
335
+ /**
336
+ * Whether an `intlayer-ignore-line` / `intlayer-ignore-next-line` directive
337
+ * covers the string: on the line where its text starts, or on the opening
338
+ * tag of its JSX element.
339
+ */
340
+ const isIgnoredPath = (path) => {
341
+ const { node } = path;
342
+ if (typeof node.start === "number") {
343
+ const text = _babel_types.isJSXText(node) ? node.value : "";
344
+ const leadingWhitespaceLength = text.length - text.trimStart().length;
345
+ if (ignoreDirectives.isOffsetIgnored(node.start + leadingWhitespaceLength)) return true;
346
+ }
347
+ return ignoreDirectives.isOffsetIgnored(getEnclosingJsxElementStart(path));
348
+ };
323
349
  traverse(ast, {
324
350
  JSXElement(path) {
325
351
  if (handledNodes.has(path.node)) return;
352
+ if (isIgnoredPath(path)) return;
326
353
  handleJsxInsertionBabel(path, fileCode, existingKeys, getComponentKeyForPath, extractedContent, replacements, handledNodes);
327
354
  },
328
355
  JSXFragment(path) {
329
356
  if (handledNodes.has(path.node)) return;
357
+ if (isIgnoredPath(path)) return;
330
358
  handleJsxInsertionBabel(path, fileCode, existingKeys, getComponentKeyForPath, extractedContent, replacements, handledNodes);
331
359
  },
332
360
  JSXText(path) {
333
361
  if (handledNodes.has(path.node)) return;
362
+ if (isIgnoredPath(path)) return;
334
363
  const text = path.node.value;
335
364
  if ((0, _intlayer_config_extract.shouldExtract)(text)) {
336
365
  const componentKey = getComponentKeyForPath(path);
@@ -345,6 +374,7 @@ const extractBabelContentForComponents = (ast, fileCode, existingKeys, defaultKe
345
374
  },
346
375
  JSXAttribute(path) {
347
376
  if (handledNodes.has(path.node)) return;
377
+ if (isIgnoredPath(path)) return;
348
378
  const name = path.node.name.name;
349
379
  if (typeof name !== "string" || !_intlayer_config_extract.ATTRIBUTES_TO_EXTRACT.includes(name)) return;
350
380
  const value = path.node.value;
@@ -361,6 +391,7 @@ const extractBabelContentForComponents = (ast, fileCode, existingKeys, defaultKe
361
391
  },
362
392
  StringLiteral(path) {
363
393
  if (handledNodes.has(path.node)) return;
394
+ if (isIgnoredPath(path)) return;
364
395
  const text = path.node.value;
365
396
  if (!(0, _intlayer_config_extract.shouldExtract)(text)) return;
366
397
  const parent = path.parentPath;
@@ -391,6 +422,7 @@ const extractBabelContentForComponents = (ast, fileCode, existingKeys, defaultKe
391
422
  },
392
423
  TemplateLiteral(path) {
393
424
  if (handledNodes.has(path.node)) return;
425
+ if (isIgnoredPath(path)) return;
394
426
  const { quasis, expressions } = path.node;
395
427
  let combinedString = "";
396
428
  const variables = [];
@@ -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 { ATTRIBUTES_TO_EXTRACT, shouldExtract } from '@intlayer/config/extract';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { resolveDictionaryKey } from '../extractContent/utils';\nimport {\n type ExistingIntlayerInfo,\n getComponentName,\n getExistingIntlayerInfo,\n getOrGenerateKey,\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 | 'template-literal';\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 * Method names whose string arguments are technical operands — lookups,\n * comparisons, DOM queries, storage keys, event names — rather than translatable\n * display text. A string literal passed to one of these (e.g.\n * `message.includes('Loading chunk')`) is code, not content, and is skipped.\n */\n/**\n * Binary operators whose string operands are code constants / enum values\n * (e.g. `msg.step === 'ERROR'`) rather than translatable display text.\n */\nconst COMPARISON_OPERATORS = new Set(['===', '!==', '==', '!=']);\n\n/**\n * Object property names whose string values are technical/non-translatable\n * (e.g. `icon: 'Globe'`, `variant: 'primary'`). String values under other\n * property names (e.g. `label: 'Language'`) are still extracted.\n */\nconst TECHNICAL_KEYS = new Set<string>([\n 'icon',\n 'className',\n 'class',\n 'id',\n 'type',\n 'variant',\n 'color',\n 'theme',\n 'size',\n 'align',\n 'placement',\n 'target',\n 'rel',\n 'method',\n 'mode',\n 'direction',\n 'orientation',\n 'scope',\n 'role',\n 'lang',\n 'locale',\n 'href',\n 'src',\n 'width',\n 'height',\n 'as',\n 'to',\n 'key',\n 'value',\n 'defaultValue',\n 'prop',\n 'property',\n 'state',\n 'action',\n 'event',\n 'handler',\n 'callback',\n 'url',\n 'uri',\n 'path',\n 'route',\n 'slug',\n 'endpoint',\n 'headers',\n 'contentType',\n 'toolName',\n 'toolDescription',\n 'toolParamDescription',\n]);\n\nconst NON_TRANSLATABLE_CALL_METHODS = new Set<string>([\n 'includes',\n 'startsWith',\n 'endsWith',\n 'indexOf',\n 'lastIndexOf',\n 'match',\n 'matchAll',\n 'search',\n 'localeCompare',\n 'split',\n 'getAttribute',\n 'setAttribute',\n 'hasAttribute',\n 'removeAttribute',\n 'querySelector',\n 'querySelectorAll',\n 'getElementById',\n 'getElementsByClassName',\n 'getElementsByTagName',\n 'addEventListener',\n 'removeEventListener',\n 'dispatchEvent',\n 'matchMedia',\n 'getItem',\n 'setItem',\n 'removeItem',\n 'getPropertyValue',\n 'setProperty',\n]);\n\n/**\n * When `path` is a string-literal argument of a member/optional-member call\n * expression (e.g. `foo.includes('bar')` or `foo?.bar?.includes('baz')`),\n * returns the called method name and whether the call targets the `console`\n * object. Returns `undefined` when the parent is not such a call, or the string\n * is not one of its arguments.\n */\nconst getEnclosingMethodCallInfo = (\n path: NodePath<t.StringLiteral>\n): { methodName: string | undefined; isConsoleCall: boolean } | undefined => {\n const parent = path.parentPath;\n if (!parent.isCallExpression() && !parent.isOptionalCallExpression()) {\n return undefined;\n }\n\n const callNode = parent.node as t.CallExpression | t.OptionalCallExpression;\n if (!callNode.arguments.includes(path.node)) return undefined;\n\n const callee = callNode.callee;\n if (!t.isMemberExpression(callee) && !t.isOptionalMemberExpression(callee)) {\n return undefined;\n }\n\n const methodName = t.isIdentifier(callee.property)\n ? callee.property.name\n : undefined;\n const isConsoleCall =\n t.isIdentifier(callee.object) && callee.object.name === 'console';\n\n return { methodName, isConsoleCall };\n};\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 if (t.isTemplateLiteral(expr)) {\n for (let i = 0; i < expr.quasis.length; i++) {\n parts.push({\n type: 'text',\n value: expr.quasis[i]?.value.raw ?? '',\n });\n if (i < expr.expressions.length) {\n const subExpr = expr.expressions[i];\n if (t.isIdentifier(subExpr)) {\n parts.push({\n type: 'var',\n value: subExpr.name,\n originalExpr: subExpr.name,\n });\n hasVariables = true;\n } else if (t.isMemberExpression(subExpr)) {\n const code = fileCode.substring(subExpr.start!, subExpr.end!);\n const varName = t.isIdentifier(subExpr.property)\n ? subExpr.property.name\n : 'var';\n parts.push({ type: 'var', value: varName, originalExpr: code });\n hasVariables = true;\n } else {\n return false;\n }\n }\n }\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 /** Cached `getExistingIntlayerInfo` result per component node, so callers do not re-traverse. */\n existingInfoByNode: Map<t.Node, ExistingIntlayerInfo | undefined>;\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 // `getExistingIntlayerInfo` traverses the component body, so resolve it a\n // single time per component and share the cache with every later phase\n // (including the caller, via the returned `existingInfoByNode`).\n const existingInfoByNode = new Map<\n t.Node,\n ExistingIntlayerInfo | undefined\n >();\n const componentNodeToPath = new Map<t.Node, NodePath>();\n for (const path of componentPaths) {\n existingInfoByNode.set(path.node, getExistingIntlayerInfo(path));\n componentNodeToPath.set(path.node, path);\n }\n\n // Pre-scan non-Program paths to collect their existing dictionary keys before the\n // Program scope is assigned. Without this, Program is processed first (depth-first\n // traversal) and creates a new file-path-derived key even when a child component\n // already declares a specific dictionary (e.g. useIntlayer('dashboard-sidebar')).\n for (const path of componentPaths) {\n if (path.isProgram()) continue;\n const existingInfo = existingInfoByNode.get(path.node);\n if (existingInfo) {\n usedKeysInFile.add(existingInfo.key);\n }\n }\n\n for (const path of componentPaths) {\n const existingInfo = existingInfoByNode.get(path.node);\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 // Reuse the dominant existing key from child components so that\n // module-level strings join the same dictionary rather than\n // creating a new file-path-derived one (e.g. 'route').\n const dominantKey =\n usedKeysInFile.size > 0 ? [...usedKeysInFile][0] : undefined;\n if (dominantKey) {\n globalFileKey = dominantKey;\n } else {\n globalFileKey = resolveDictionaryKey(\n defaultKey,\n filePath,\n configuration,\n unmergedDictionaries,\n usedKeysInFile\n );\n usedKeysInFile.add(globalFileKey);\n }\n }\n componentKeyMap.set(path.node, globalFileKey);\n hookMap.set(path.node, 'getIntlayer');\n } else {\n let inheritedKey: string | undefined;\n let parent: NodePath | null = path.parentPath;\n while (parent) {\n if (componentKeyMap.has(parent.node)) {\n inheritedKey = componentKeyMap.get(parent.node);\n break;\n }\n parent = parent.parentPath;\n }\n\n if (!inheritedKey) {\n if (!globalFileKey) {\n globalFileKey = resolveDictionaryKey(\n defaultKey,\n filePath,\n configuration,\n unmergedDictionaries,\n usedKeysInFile\n );\n usedKeysInFile.add(globalFileKey);\n }\n inheritedKey = globalFileKey;\n }\n\n componentKeyMap.set(path.node, inheritedKey);\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 // A literal wrapped in braces (`title={\"Hello\"}`) follows the same\n // attribute allowlist as the plain form handled by `JSXAttribute`.\n if (parent.isJSXExpressionContainer()) {\n const attributePath = parent.parentPath;\n if (attributePath?.isJSXAttribute()) {\n const attributeName = attributePath.node.name.name;\n if (\n typeof attributeName !== 'string' ||\n !ATTRIBUTES_TO_EXTRACT.includes(\n attributeName as (typeof ATTRIBUTES_TO_EXTRACT)[number]\n )\n ) {\n return;\n }\n }\n }\n\n // Skip string literals used as comparison operands (e.g. `msg.step === 'ERROR'`).\n // These are code constants / enum values, not translatable display text.\n if (\n parent.isBinaryExpression() &&\n COMPARISON_OPERATORS.has(parent.node.operator)\n ) {\n return;\n }\n\n // Skip string literals used as `switch` case tests (e.g. `case 'ERROR':`).\n // Like comparisons, these are code constants, not display text.\n if (parent.isSwitchCase() && parent.node.test === path.node) return;\n\n // Skip string literals passed to technical method calls, e.g.\n // `message.includes('Loading chunk')`, `el.getAttribute('data-id')`, or\n // `console.log('...')`. These are code operands, not display text.\n const methodCallInfo = getEnclosingMethodCallInfo(path);\n if (\n methodCallInfo &&\n (methodCallInfo.isConsoleCall ||\n (methodCallInfo.methodName !== undefined &&\n NON_TRANSLATABLE_CALL_METHODS.has(methodCallInfo.methodName)))\n ) {\n return;\n }\n\n if (parent.isObjectProperty() && parent.node.key === path.node) return;\n\n // Skip string values in known technical/non-translatable object properties (e.g. `icon: 'Globe'`).\n // String values in translatable object properties (e.g. `label: 'Language'`) are still extracted.\n if (\n parent.isObjectProperty() &&\n t.isIdentifier(parent.node.key) &&\n TECHNICAL_KEYS.has(parent.node.key.name)\n ) {\n return;\n }\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 TemplateLiteral(path) {\n if (handledNodes.has(path.node)) return;\n\n const { quasis, expressions } = path.node;\n\n // Build the combined string with placeholders\n let combinedString = '';\n const variables: string[] = [];\n let hasSignificantText = false;\n\n for (let i = 0; i < quasis.length; i++) {\n const text = quasis[i]?.value.raw ?? '';\n combinedString += text;\n if (text.trim().length > 0) hasSignificantText = true;\n\n if (i < expressions.length) {\n const expr = expressions[i];\n if (t.isIdentifier(expr)) {\n combinedString += `{{${expr.name}}}`;\n variables.push(`${expr.name}: ${expr.name}`);\n } else if (t.isMemberExpression(expr)) {\n const code = fileCode.substring(expr.start!, expr.end!);\n const varName = t.isIdentifier(expr.property)\n ? expr.property.name\n : 'var';\n combinedString += `{{${varName}}}`;\n variables.push(`${varName}: ${code}`);\n } else {\n // Complex expression in template literal, skip\n return;\n }\n }\n }\n\n if (!hasSignificantText) return;\n\n const cleanString = combinedString.replace(/\\s+/g, ' ').trim();\n\n if (!shouldExtract(cleanString)) return;\n\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n cleanString,\n componentKey,\n existingKeys,\n extractedContent\n );\n\n const uniqueVars = Array.from(new Set(variables));\n\n replacements.push({\n path,\n key,\n type: 'template-literal',\n componentKey,\n variables: uniqueVars,\n });\n },\n });\n\n /** Returns true when `componentNode` encloses the replacement's path. */\n const replacementIsInside = (\n replacement: BabelReplacement,\n componentNode: t.Node\n ): boolean => {\n let current: NodePath | null = replacement.path;\n while (current) {\n if (current.node === componentNode) return true;\n current = current.parentPath;\n }\n return false;\n };\n\n const componentsNeedingHooks = new Set<NodePath>();\n for (const componentPath of componentPaths) {\n if (componentPath.isProgram()) {\n // Only count replacements that belong to the Program scope itself, not\n // to a nested component (which provides its own hook).\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 if (componentNodeToPath.has(current.node)) {\n return false; // reached an enclosing component first\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 replacementIsInside(replacement, componentPath.node)\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 = componentNodeToPath.get(currentPath.node);\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 replacementIsInside(replacement, ancestorPath.node)\n );\n const existingInfo = existingInfoByNode.get(ancestorPath.node);\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 existingInfoByNode,\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","getExtractionIgnoreDirectives","ATTRIBUTES_TO_EXTRACT"],"sources":["../../../src/extractContent/babelProcessor.ts"],"sourcesContent":["import _traverse, { type NodePath } from '@babel/traverse';\nimport * as t from '@babel/types';\nimport {\n ATTRIBUTES_TO_EXTRACT,\n getExtractionIgnoreDirectives,\n shouldExtract,\n} from '@intlayer/config/extract';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { resolveDictionaryKey } from '../extractContent/utils';\nimport {\n type ExistingIntlayerInfo,\n getComponentName,\n getExistingIntlayerInfo,\n getOrGenerateKey,\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 | 'template-literal';\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 * Method names whose string arguments are technical operands — lookups,\n * comparisons, DOM queries, storage keys, event names — rather than translatable\n * display text. A string literal passed to one of these (e.g.\n * `message.includes('Loading chunk')`) is code, not content, and is skipped.\n */\n/**\n * Binary operators whose string operands are code constants / enum values\n * (e.g. `msg.step === 'ERROR'`) rather than translatable display text.\n */\nconst COMPARISON_OPERATORS = new Set(['===', '!==', '==', '!=']);\n\n/**\n * Object property names whose string values are technical/non-translatable\n * (e.g. `icon: 'Globe'`, `variant: 'primary'`). String values under other\n * property names (e.g. `label: 'Language'`) are still extracted.\n */\nconst TECHNICAL_KEYS = new Set<string>([\n 'icon',\n 'className',\n 'class',\n 'id',\n 'type',\n 'variant',\n 'color',\n 'theme',\n 'size',\n 'align',\n 'placement',\n 'target',\n 'rel',\n 'method',\n 'mode',\n 'direction',\n 'orientation',\n 'scope',\n 'role',\n 'lang',\n 'locale',\n 'href',\n 'src',\n 'width',\n 'height',\n 'as',\n 'to',\n 'key',\n 'value',\n 'defaultValue',\n 'prop',\n 'property',\n 'state',\n 'action',\n 'event',\n 'handler',\n 'callback',\n 'url',\n 'uri',\n 'path',\n 'route',\n 'slug',\n 'endpoint',\n 'headers',\n 'contentType',\n 'toolName',\n 'toolDescription',\n 'toolParamDescription',\n]);\n\nconst NON_TRANSLATABLE_CALL_METHODS = new Set<string>([\n 'includes',\n 'startsWith',\n 'endsWith',\n 'indexOf',\n 'lastIndexOf',\n 'match',\n 'matchAll',\n 'search',\n 'localeCompare',\n 'split',\n 'getAttribute',\n 'setAttribute',\n 'hasAttribute',\n 'removeAttribute',\n 'querySelector',\n 'querySelectorAll',\n 'getElementById',\n 'getElementsByClassName',\n 'getElementsByTagName',\n 'addEventListener',\n 'removeEventListener',\n 'dispatchEvent',\n 'matchMedia',\n 'getItem',\n 'setItem',\n 'removeItem',\n 'getPropertyValue',\n 'setProperty',\n]);\n\n/**\n * When `path` is a string-literal argument of a member/optional-member call\n * expression (e.g. `foo.includes('bar')` or `foo?.bar?.includes('baz')`),\n * returns the called method name and whether the call targets the `console`\n * object. Returns `undefined` when the parent is not such a call, or the string\n * is not one of its arguments.\n */\nconst getEnclosingMethodCallInfo = (\n path: NodePath<t.StringLiteral>\n): { methodName: string | undefined; isConsoleCall: boolean } | undefined => {\n const parent = path.parentPath;\n if (!parent.isCallExpression() && !parent.isOptionalCallExpression()) {\n return undefined;\n }\n\n const callNode = parent.node as t.CallExpression | t.OptionalCallExpression;\n if (!callNode.arguments.includes(path.node)) return undefined;\n\n const callee = callNode.callee;\n if (!t.isMemberExpression(callee) && !t.isOptionalMemberExpression(callee)) {\n return undefined;\n }\n\n const methodName = t.isIdentifier(callee.property)\n ? callee.property.name\n : undefined;\n const isConsoleCall =\n t.isIdentifier(callee.object) && callee.object.name === 'console';\n\n return { methodName, isConsoleCall };\n};\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 if (t.isTemplateLiteral(expr)) {\n for (let i = 0; i < expr.quasis.length; i++) {\n parts.push({\n type: 'text',\n value: expr.quasis[i]?.value.raw ?? '',\n });\n if (i < expr.expressions.length) {\n const subExpr = expr.expressions[i];\n if (t.isIdentifier(subExpr)) {\n parts.push({\n type: 'var',\n value: subExpr.name,\n originalExpr: subExpr.name,\n });\n hasVariables = true;\n } else if (t.isMemberExpression(subExpr)) {\n const code = fileCode.substring(subExpr.start!, subExpr.end!);\n const varName = t.isIdentifier(subExpr.property)\n ? subExpr.property.name\n : 'var';\n parts.push({ type: 'var', value: varName, originalExpr: code });\n hasVariables = true;\n } else {\n return false;\n }\n }\n }\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 /** Cached `getExistingIntlayerInfo` result per component node, so callers do not re-traverse. */\n existingInfoByNode: Map<t.Node, ExistingIntlayerInfo | undefined>;\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 // `getExistingIntlayerInfo` traverses the component body, so resolve it a\n // single time per component and share the cache with every later phase\n // (including the caller, via the returned `existingInfoByNode`).\n const existingInfoByNode = new Map<\n t.Node,\n ExistingIntlayerInfo | undefined\n >();\n const componentNodeToPath = new Map<t.Node, NodePath>();\n for (const path of componentPaths) {\n existingInfoByNode.set(path.node, getExistingIntlayerInfo(path));\n componentNodeToPath.set(path.node, path);\n }\n\n // Pre-scan non-Program paths to collect their existing dictionary keys before the\n // Program scope is assigned. Without this, Program is processed first (depth-first\n // traversal) and creates a new file-path-derived key even when a child component\n // already declares a specific dictionary (e.g. useIntlayer('dashboard-sidebar')).\n for (const path of componentPaths) {\n if (path.isProgram()) continue;\n const existingInfo = existingInfoByNode.get(path.node);\n if (existingInfo) {\n usedKeysInFile.add(existingInfo.key);\n }\n }\n\n for (const path of componentPaths) {\n const existingInfo = existingInfoByNode.get(path.node);\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 // Reuse the dominant existing key from child components so that\n // module-level strings join the same dictionary rather than\n // creating a new file-path-derived one (e.g. 'route').\n const dominantKey =\n usedKeysInFile.size > 0 ? [...usedKeysInFile][0] : undefined;\n if (dominantKey) {\n globalFileKey = dominantKey;\n } else {\n globalFileKey = resolveDictionaryKey(\n defaultKey,\n filePath,\n configuration,\n unmergedDictionaries,\n usedKeysInFile\n );\n usedKeysInFile.add(globalFileKey);\n }\n }\n componentKeyMap.set(path.node, globalFileKey);\n hookMap.set(path.node, 'getIntlayer');\n } else {\n let inheritedKey: string | undefined;\n let parent: NodePath | null = path.parentPath;\n while (parent) {\n if (componentKeyMap.has(parent.node)) {\n inheritedKey = componentKeyMap.get(parent.node);\n break;\n }\n parent = parent.parentPath;\n }\n\n if (!inheritedKey) {\n if (!globalFileKey) {\n globalFileKey = resolveDictionaryKey(\n defaultKey,\n filePath,\n configuration,\n unmergedDictionaries,\n usedKeysInFile\n );\n usedKeysInFile.add(globalFileKey);\n }\n inheritedKey = globalFileKey;\n }\n\n componentKeyMap.set(path.node, inheritedKey);\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 const ignoreDirectives = getExtractionIgnoreDirectives(fileCode);\n\n /**\n * Start offset of the JSX element a string directly belongs to: its text,\n * a `{'literal'}` child, or one of its attributes.\n */\n const getEnclosingJsxElementStart = (path: NodePath): number | undefined => {\n let current: NodePath | null = path.parentPath;\n\n if (current?.isJSXExpressionContainer()) current = current.parentPath;\n if (current?.isJSXAttribute()) current = current.parentPath;\n if (current?.isJSXOpeningElement()) current = current.parentPath;\n\n return current?.isJSXElement() || current?.isJSXFragment()\n ? (current.node.start ?? undefined)\n : undefined;\n };\n\n /**\n * Whether an `intlayer-ignore-line` / `intlayer-ignore-next-line` directive\n * covers the string: on the line where its text starts, or on the opening\n * tag of its JSX element.\n */\n const isIgnoredPath = (path: NodePath): boolean => {\n const { node } = path;\n\n if (typeof node.start === 'number') {\n const text = t.isJSXText(node) ? node.value : '';\n const leadingWhitespaceLength = text.length - text.trimStart().length;\n\n if (\n ignoreDirectives.isOffsetIgnored(node.start + leadingWhitespaceLength)\n )\n return true;\n }\n\n return ignoreDirectives.isOffsetIgnored(getEnclosingJsxElementStart(path));\n };\n\n traverse(ast, {\n JSXElement(path) {\n if (handledNodes.has(path.node)) return;\n if (isIgnoredPath(path)) 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 if (isIgnoredPath(path)) 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 if (isIgnoredPath(path)) 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 if (isIgnoredPath(path)) 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 if (isIgnoredPath(path)) 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 // A literal wrapped in braces (`title={\"Hello\"}`) follows the same\n // attribute allowlist as the plain form handled by `JSXAttribute`.\n if (parent.isJSXExpressionContainer()) {\n const attributePath = parent.parentPath;\n if (attributePath?.isJSXAttribute()) {\n const attributeName = attributePath.node.name.name;\n if (\n typeof attributeName !== 'string' ||\n !ATTRIBUTES_TO_EXTRACT.includes(\n attributeName as (typeof ATTRIBUTES_TO_EXTRACT)[number]\n )\n ) {\n return;\n }\n }\n }\n\n // Skip string literals used as comparison operands (e.g. `msg.step === 'ERROR'`).\n // These are code constants / enum values, not translatable display text.\n if (\n parent.isBinaryExpression() &&\n COMPARISON_OPERATORS.has(parent.node.operator)\n ) {\n return;\n }\n\n // Skip string literals used as `switch` case tests (e.g. `case 'ERROR':`).\n // Like comparisons, these are code constants, not display text.\n if (parent.isSwitchCase() && parent.node.test === path.node) return;\n\n // Skip string literals passed to technical method calls, e.g.\n // `message.includes('Loading chunk')`, `el.getAttribute('data-id')`, or\n // `console.log('...')`. These are code operands, not display text.\n const methodCallInfo = getEnclosingMethodCallInfo(path);\n if (\n methodCallInfo &&\n (methodCallInfo.isConsoleCall ||\n (methodCallInfo.methodName !== undefined &&\n NON_TRANSLATABLE_CALL_METHODS.has(methodCallInfo.methodName)))\n ) {\n return;\n }\n\n if (parent.isObjectProperty() && parent.node.key === path.node) return;\n\n // Skip string values in known technical/non-translatable object properties (e.g. `icon: 'Globe'`).\n // String values in translatable object properties (e.g. `label: 'Language'`) are still extracted.\n if (\n parent.isObjectProperty() &&\n t.isIdentifier(parent.node.key) &&\n TECHNICAL_KEYS.has(parent.node.key.name)\n ) {\n return;\n }\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 TemplateLiteral(path) {\n if (handledNodes.has(path.node)) return;\n if (isIgnoredPath(path)) return;\n\n const { quasis, expressions } = path.node;\n\n // Build the combined string with placeholders\n let combinedString = '';\n const variables: string[] = [];\n let hasSignificantText = false;\n\n for (let i = 0; i < quasis.length; i++) {\n const text = quasis[i]?.value.raw ?? '';\n combinedString += text;\n if (text.trim().length > 0) hasSignificantText = true;\n\n if (i < expressions.length) {\n const expr = expressions[i];\n if (t.isIdentifier(expr)) {\n combinedString += `{{${expr.name}}}`;\n variables.push(`${expr.name}: ${expr.name}`);\n } else if (t.isMemberExpression(expr)) {\n const code = fileCode.substring(expr.start!, expr.end!);\n const varName = t.isIdentifier(expr.property)\n ? expr.property.name\n : 'var';\n combinedString += `{{${varName}}}`;\n variables.push(`${varName}: ${code}`);\n } else {\n // Complex expression in template literal, skip\n return;\n }\n }\n }\n\n if (!hasSignificantText) return;\n\n const cleanString = combinedString.replace(/\\s+/g, ' ').trim();\n\n if (!shouldExtract(cleanString)) return;\n\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n cleanString,\n componentKey,\n existingKeys,\n extractedContent\n );\n\n const uniqueVars = Array.from(new Set(variables));\n\n replacements.push({\n path,\n key,\n type: 'template-literal',\n componentKey,\n variables: uniqueVars,\n });\n },\n });\n\n /** Returns true when `componentNode` encloses the replacement's path. */\n const replacementIsInside = (\n replacement: BabelReplacement,\n componentNode: t.Node\n ): boolean => {\n let current: NodePath | null = replacement.path;\n while (current) {\n if (current.node === componentNode) return true;\n current = current.parentPath;\n }\n return false;\n };\n\n const componentsNeedingHooks = new Set<NodePath>();\n for (const componentPath of componentPaths) {\n if (componentPath.isProgram()) {\n // Only count replacements that belong to the Program scope itself, not\n // to a nested component (which provides its own hook).\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 if (componentNodeToPath.has(current.node)) {\n return false; // reached an enclosing component first\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 replacementIsInside(replacement, componentPath.node)\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 = componentNodeToPath.get(currentPath.node);\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 replacementIsInside(replacement, ancestorPath.node)\n );\n const existingInfo = existingInfoByNode.get(ancestorPath.node);\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 existingInfoByNode,\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,6 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
3
3
  const require_extractContent_utils_extractDictionaryInfo = require("./utils/extractDictionaryInfo.cjs");
4
+ const require_extractContent_utils_resolveDictionaryKey = require("./utils/resolveDictionaryKey.cjs");
4
5
  let node_fs = require("node:fs");
5
6
  let node_fs_promises = require("node:fs/promises");
6
7
  let node_path = require("node:path");
@@ -101,6 +102,7 @@ const writeContentHelper = async (extractedContent, dictionaryKey, filePath, con
101
102
  console.error(error);
102
103
  }
103
104
  const relativeContentFilePath = (0, node_path.relative)(baseDir, absolutePath);
105
+ const componentFilePath = require_extractContent_utils_resolveDictionaryKey.getComponentFilePath(filePath, configuration);
104
106
  let mergedDictionary;
105
107
  if (isPerLocale) {
106
108
  const mergedContent = mergeWithExistingPerLocaleDictionary(extractedContent, existingDictionary);
@@ -109,7 +111,8 @@ const writeContentHelper = async (extractedContent, dictionaryKey, filePath, con
109
111
  key: dictionaryKey,
110
112
  content: mergedContent,
111
113
  locale: defaultLocale,
112
- filePath: relativeContentFilePath
114
+ filePath: relativeContentFilePath,
115
+ componentFilePath
113
116
  };
114
117
  } else {
115
118
  const mergedContent = mergeWithExistingMultilingualDictionary(extractedContent, existingDictionary, defaultLocale);
@@ -117,7 +120,8 @@ const writeContentHelper = async (extractedContent, dictionaryKey, filePath, con
117
120
  ...existingDictionary,
118
121
  key: dictionaryKey,
119
122
  content: mergedContent,
120
- filePath: relativeContentFilePath
123
+ filePath: relativeContentFilePath,
124
+ componentFilePath
121
125
  };
122
126
  }
123
127
  const relativeDir = (0, node_path.relative)(baseDir, outputDir);
@@ -1 +1 @@
1
- {"version":3,"file":"contentWriter.cjs","names":["insertContentInDictionary","NodeTypes","resolveContentFilePaths","ensureIntlayerBundle","dirname","mkdir","existsSync","loadContentDeclaration","relative","writeContentDeclaration","buildDictionary"],"sources":["../../../src/extractContent/contentWriter.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { mkdir } from 'node:fs/promises';\nimport { dirname, relative } from 'node:path';\nimport { insertContentInDictionary } from '@intlayer/core/plugins';\nimport {\n buildDictionary,\n ensureIntlayerBundle,\n loadContentDeclaration,\n writeContentDeclaration,\n} from '@intlayer/engine/build';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Dictionary, DictionaryKey } from '@intlayer/types/dictionary';\nimport * as NodeTypes from '@intlayer/types/nodeType';\nimport { resolveContentFilePaths } from './utils/extractDictionaryInfo';\n\nconst hasInsertionVars = (str: string): boolean => /\\{\\{[^}]+\\}\\}/.test(str);\n\nconst getInsertionFields = (template: string): string[] => {\n const fields = new Set<string>();\n const regex = /\\{\\{([^}]+)\\}\\}/g;\n for (\n let match = regex.exec(template);\n match !== null;\n match = regex.exec(template)\n ) {\n const field = match[1]?.trim();\n if (field) fields.add(field);\n }\n return [...fields];\n};\n\n/**\n * Translation node structure used in dictionaries\n */\ntype TranslationNode = {\n nodeType: 'translation';\n translation: Record<string, string>;\n};\n\n/**\n * Dictionary content structure - map of keys to translation nodes\n */\ntype DictionaryContentMap = Record<string, TranslationNode>;\n\n/**\n * Cached bundle file path to optimize performance\n */\nlet cachedBundleFilePath: string | undefined;\n\n/**\n * Merge extracted content with existing dictionary for multilingual format.\n * - Keys in extracted but not in existing: added with default locale only\n * - Keys in both: preserve existing translations, update default locale value\n * - Keys in existing but not in extracted: removed (no longer in source)\n */\nexport const mergeWithExistingMultilingualDictionary = (\n extractedContent: Record<string, string>,\n existingDictionary: Dictionary | null,\n defaultLocale: string\n): DictionaryContentMap => {\n const dictionary: Dictionary =\n existingDictionary ??\n ({\n key: '',\n content: {},\n filePath: '',\n } as Dictionary);\n\n const mergedDictionary = insertContentInDictionary(\n dictionary,\n extractedContent,\n defaultLocale as Locale\n );\n\n const mergedContent = mergedDictionary.content as DictionaryContentMap;\n\n // Pruning: remove keys not in extractedContent\n const finalContent: DictionaryContentMap = {};\n for (const key in extractedContent) {\n const contentNode = mergedContent[key];\n if (contentNode) {\n finalContent[key] = contentNode;\n }\n }\n\n // Promote any key whose source text contains {{vars}} to an insertion node\n for (const key in extractedContent) {\n const rawValue = extractedContent[key];\n if (typeof rawValue === 'string' && hasInsertionVars(rawValue)) {\n const node = finalContent[key] as any;\n if (node && node.nodeType === NodeTypes.TRANSLATION) {\n (finalContent as any)[key] = {\n nodeType: NodeTypes.INSERTION,\n [NodeTypes.INSERTION]: node,\n fields: getInsertionFields(rawValue),\n };\n }\n }\n }\n\n return finalContent;\n};\n\n/**\n * Merge extracted content with existing dictionary for per-locale format.\n * - Keys in extracted but not in existing: added\n * - Keys in both: update value\n * - Keys in existing but not in extracted: removed (no longer in source)\n */\nexport const mergeWithExistingPerLocaleDictionary = (\n extractedContent: Record<string, string>,\n existingDictionary: Dictionary | null\n): Record<string, string> => {\n const dictionary: Dictionary =\n existingDictionary ??\n ({\n key: '',\n content: {},\n filePath: '',\n } as Dictionary);\n\n const mergedDictionary = insertContentInDictionary(\n dictionary,\n extractedContent\n );\n\n const mergedContent = mergedDictionary.content as Record<string, string>;\n\n // Pruning: remove keys not in extractedContent\n const finalContent: Record<string, string> = {};\n for (const key in extractedContent) {\n const contentStr = mergedContent[key];\n if (contentStr) {\n finalContent[key] = contentStr;\n }\n }\n\n // Promote any key whose source text contains {{vars}} to an insertion node\n for (const key in extractedContent) {\n const rawValue = extractedContent[key];\n if (typeof rawValue === 'string' && hasInsertionVars(rawValue)) {\n const currentVal = finalContent[key];\n if (typeof currentVal === 'string') {\n (finalContent as any)[key] = {\n nodeType: NodeTypes.INSERTION,\n [NodeTypes.INSERTION]: currentVal,\n fields: getInsertionFields(rawValue),\n };\n }\n }\n }\n\n return finalContent;\n};\n\n/**\n * Helper to write extracted content to dictionary file(s).\n */\nexport const writeContentHelper = async (\n extractedContent: Record<string, string>,\n dictionaryKey: DictionaryKey,\n filePath: string,\n configuration: IntlayerConfig\n): Promise<string> => {\n const { absolutePath, isPerLocale } = await resolveContentFilePaths(\n filePath,\n dictionaryKey,\n configuration\n );\n\n const { defaultLocale } = configuration.internationalization;\n const { baseDir } = configuration.system;\n\n if (!cachedBundleFilePath) {\n cachedBundleFilePath = await ensureIntlayerBundle(configuration);\n }\n\n const outputDir = dirname(absolutePath);\n\n // Ensure output directory exists\n await mkdir(outputDir, { recursive: true });\n\n // Read existing dictionary to preserve translations and metadata\n let existingDictionary: Dictionary | null = null;\n\n if (existsSync(absolutePath)) {\n try {\n const dictionary = await loadContentDeclaration(\n absolutePath,\n configuration,\n cachedBundleFilePath\n );\n\n existingDictionary = dictionary ?? null;\n } catch (error) {\n console.error(error);\n }\n }\n\n const relativeContentFilePath = relative(baseDir, absolutePath);\n\n let mergedDictionary: Dictionary;\n\n if (isPerLocale) {\n // Per-locale format: simple string content for a single locale\n const mergedContent = mergeWithExistingPerLocaleDictionary(\n extractedContent,\n existingDictionary\n );\n\n mergedDictionary = {\n // Preserve existing metadata\n ...existingDictionary,\n key: dictionaryKey,\n content: mergedContent,\n locale: defaultLocale,\n filePath: relativeContentFilePath,\n };\n } else {\n // Multilingual format: content wrapped in translation nodes for multiple locales\n const mergedContent = mergeWithExistingMultilingualDictionary(\n extractedContent,\n existingDictionary,\n defaultLocale\n );\n\n mergedDictionary = {\n // Preserve existing metadata\n ...existingDictionary,\n key: dictionaryKey,\n content: mergedContent,\n filePath: relativeContentFilePath,\n };\n }\n\n const relativeDir = relative(baseDir, outputDir);\n\n const writeResult = await writeContentDeclaration(\n mergedDictionary,\n configuration,\n {\n newDictionariesPath: relativeDir,\n localeList: [defaultLocale],\n }\n );\n\n // Build the dictionary immediately\n const dictionaryToBuild: Dictionary = {\n ...mergedDictionary,\n filePath: relative(baseDir, writeResult?.path ?? absolutePath),\n };\n\n await buildDictionary([dictionaryToBuild], configuration);\n\n return absolutePath;\n};\n"],"mappings":";;;;;;;;;;;;AAgBA,MAAM,oBAAoB,QAAyB,gBAAgB,KAAK,GAAG;AAE3E,MAAM,sBAAsB,aAA+B;CACzD,MAAM,yBAAS,IAAI,IAAY;CAC/B,MAAM,QAAQ;CACd,KACE,IAAI,QAAQ,MAAM,KAAK,QAAQ,GAC/B,UAAU,MACV,QAAQ,MAAM,KAAK,QAAQ,GAC3B;EACA,MAAM,QAAQ,MAAM,EAAE,EAAE,KAAK;EAC7B,IAAI,OAAO,OAAO,IAAI,KAAK;CAC7B;CACA,OAAO,CAAC,GAAG,MAAM;AACnB;;;;AAkBA,IAAI;;;;;;;AAQJ,MAAa,2CACX,kBACA,oBACA,kBACyB;CAezB,MAAM,oBANmBA,kDAPvB,sBACC;EACC,KAAK;EACL,SAAS,CAAC;EACV,UAAU;CACZ,GAIA,kBACA,aAGmC,CAAC,CAAC;CAGvC,MAAM,eAAqC,CAAC;CAC5C,KAAK,MAAM,OAAO,kBAAkB;EAClC,MAAM,cAAc,cAAc;EAClC,IAAI,aACF,aAAa,OAAO;CAExB;CAGA,KAAK,MAAM,OAAO,kBAAkB;EAClC,MAAM,WAAW,iBAAiB;EAClC,IAAI,OAAO,aAAa,YAAY,iBAAiB,QAAQ,GAAG;GAC9D,MAAM,OAAO,aAAa;GAC1B,IAAI,QAAQ,KAAK,aAAaC,yBAAU,aACtC,AAAC,aAAqB,OAAO;IAC3B,UAAUA,yBAAU;KACnBA,yBAAU,YAAY;IACvB,QAAQ,mBAAmB,QAAQ;GACrC;EAEJ;CACF;CAEA,OAAO;AACT;;;;;;;AAQA,MAAa,wCACX,kBACA,uBAC2B;CAc3B,MAAM,oBALmBD,kDAPvB,sBACC;EACC,KAAK;EACL,SAAS,CAAC;EACV,UAAU;CACZ,GAIA,gBAGmC,CAAC,CAAC;CAGvC,MAAM,eAAuC,CAAC;CAC9C,KAAK,MAAM,OAAO,kBAAkB;EAClC,MAAM,aAAa,cAAc;EACjC,IAAI,YACF,aAAa,OAAO;CAExB;CAGA,KAAK,MAAM,OAAO,kBAAkB;EAClC,MAAM,WAAW,iBAAiB;EAClC,IAAI,OAAO,aAAa,YAAY,iBAAiB,QAAQ,GAAG;GAC9D,MAAM,aAAa,aAAa;GAChC,IAAI,OAAO,eAAe,UACxB,AAAC,aAAqB,OAAO;IAC3B,UAAUC,yBAAU;KACnBA,yBAAU,YAAY;IACvB,QAAQ,mBAAmB,QAAQ;GACrC;EAEJ;CACF;CAEA,OAAO;AACT;;;;AAKA,MAAa,qBAAqB,OAChC,kBACA,eACA,UACA,kBACoB;CACpB,MAAM,EAAE,cAAc,gBAAgB,MAAMC,2EAC1C,UACA,eACA,aACF;CAEA,MAAM,EAAE,kBAAkB,cAAc;CACxC,MAAM,EAAE,YAAY,cAAc;CAElC,IAAI,CAAC,sBACH,uBAAuB,UAAMC,6CAAqB,aAAa;CAGjE,MAAM,gBAAYC,mBAAQ,YAAY;CAGtC,UAAMC,wBAAM,WAAW,EAAE,WAAW,KAAK,CAAC;CAG1C,IAAI,qBAAwC;CAE5C,QAAIC,oBAAW,YAAY,GACzB,IAAI;EAOF,qBAAqB,UANIC,+CACvB,cACA,eACA,oBACF,KAEmC;CACrC,SAAS,OAAO;EACd,QAAQ,MAAM,KAAK;CACrB;CAGF,MAAM,8BAA0BC,oBAAS,SAAS,YAAY;CAE9D,IAAI;CAEJ,IAAI,aAAa;EAEf,MAAM,gBAAgB,qCACpB,kBACA,kBACF;EAEA,mBAAmB;GAEjB,GAAG;GACH,KAAK;GACL,SAAS;GACT,QAAQ;GACR,UAAU;EACZ;CACF,OAAO;EAEL,MAAM,gBAAgB,wCACpB,kBACA,oBACA,aACF;EAEA,mBAAmB;GAEjB,GAAG;GACH,KAAK;GACL,SAAS;GACT,UAAU;EACZ;CACF;CAEA,MAAM,kBAAcA,oBAAS,SAAS,SAAS;CAE/C,MAAM,cAAc,UAAMC,gDACxB,kBACA,eACA;EACE,qBAAqB;EACrB,YAAY,CAAC,aAAa;CAC5B,CACF;CAGA,MAAM,oBAAgC;EACpC,GAAG;EACH,cAAUD,oBAAS,SAAS,aAAa,QAAQ,YAAY;CAC/D;CAEA,UAAME,wCAAgB,CAAC,iBAAiB,GAAG,aAAa;CAExD,OAAO;AACT"}
1
+ {"version":3,"file":"contentWriter.cjs","names":["insertContentInDictionary","NodeTypes","resolveContentFilePaths","ensureIntlayerBundle","dirname","mkdir","existsSync","loadContentDeclaration","relative","getComponentFilePath","writeContentDeclaration","buildDictionary"],"sources":["../../../src/extractContent/contentWriter.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { mkdir } from 'node:fs/promises';\nimport { dirname, relative } from 'node:path';\nimport { insertContentInDictionary } from '@intlayer/core/plugins';\nimport {\n buildDictionary,\n ensureIntlayerBundle,\n loadContentDeclaration,\n writeContentDeclaration,\n} from '@intlayer/engine/build';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Dictionary, DictionaryKey } from '@intlayer/types/dictionary';\nimport * as NodeTypes from '@intlayer/types/nodeType';\nimport { resolveContentFilePaths } from './utils/extractDictionaryInfo';\nimport { getComponentFilePath } from './utils/resolveDictionaryKey';\n\nconst hasInsertionVars = (str: string): boolean => /\\{\\{[^}]+\\}\\}/.test(str);\n\nconst getInsertionFields = (template: string): string[] => {\n const fields = new Set<string>();\n const regex = /\\{\\{([^}]+)\\}\\}/g;\n for (\n let match = regex.exec(template);\n match !== null;\n match = regex.exec(template)\n ) {\n const field = match[1]?.trim();\n if (field) fields.add(field);\n }\n return [...fields];\n};\n\n/**\n * Translation node structure used in dictionaries\n */\ntype TranslationNode = {\n nodeType: 'translation';\n translation: Record<string, string>;\n};\n\n/**\n * Dictionary content structure - map of keys to translation nodes\n */\ntype DictionaryContentMap = Record<string, TranslationNode>;\n\n/**\n * Cached bundle file path to optimize performance\n */\nlet cachedBundleFilePath: string | undefined;\n\n/**\n * Merge extracted content with existing dictionary for multilingual format.\n * - Keys in extracted but not in existing: added with default locale only\n * - Keys in both: preserve existing translations, update default locale value\n * - Keys in existing but not in extracted: removed (no longer in source)\n */\nexport const mergeWithExistingMultilingualDictionary = (\n extractedContent: Record<string, string>,\n existingDictionary: Dictionary | null,\n defaultLocale: string\n): DictionaryContentMap => {\n const dictionary: Dictionary =\n existingDictionary ??\n ({\n key: '',\n content: {},\n filePath: '',\n } as Dictionary);\n\n const mergedDictionary = insertContentInDictionary(\n dictionary,\n extractedContent,\n defaultLocale as Locale\n );\n\n const mergedContent = mergedDictionary.content as DictionaryContentMap;\n\n // Pruning: remove keys not in extractedContent\n const finalContent: DictionaryContentMap = {};\n for (const key in extractedContent) {\n const contentNode = mergedContent[key];\n if (contentNode) {\n finalContent[key] = contentNode;\n }\n }\n\n // Promote any key whose source text contains {{vars}} to an insertion node\n for (const key in extractedContent) {\n const rawValue = extractedContent[key];\n if (typeof rawValue === 'string' && hasInsertionVars(rawValue)) {\n const node = finalContent[key] as any;\n if (node && node.nodeType === NodeTypes.TRANSLATION) {\n (finalContent as any)[key] = {\n nodeType: NodeTypes.INSERTION,\n [NodeTypes.INSERTION]: node,\n fields: getInsertionFields(rawValue),\n };\n }\n }\n }\n\n return finalContent;\n};\n\n/**\n * Merge extracted content with existing dictionary for per-locale format.\n * - Keys in extracted but not in existing: added\n * - Keys in both: update value\n * - Keys in existing but not in extracted: removed (no longer in source)\n */\nexport const mergeWithExistingPerLocaleDictionary = (\n extractedContent: Record<string, string>,\n existingDictionary: Dictionary | null\n): Record<string, string> => {\n const dictionary: Dictionary =\n existingDictionary ??\n ({\n key: '',\n content: {},\n filePath: '',\n } as Dictionary);\n\n const mergedDictionary = insertContentInDictionary(\n dictionary,\n extractedContent\n );\n\n const mergedContent = mergedDictionary.content as Record<string, string>;\n\n // Pruning: remove keys not in extractedContent\n const finalContent: Record<string, string> = {};\n for (const key in extractedContent) {\n const contentStr = mergedContent[key];\n if (contentStr) {\n finalContent[key] = contentStr;\n }\n }\n\n // Promote any key whose source text contains {{vars}} to an insertion node\n for (const key in extractedContent) {\n const rawValue = extractedContent[key];\n if (typeof rawValue === 'string' && hasInsertionVars(rawValue)) {\n const currentVal = finalContent[key];\n if (typeof currentVal === 'string') {\n (finalContent as any)[key] = {\n nodeType: NodeTypes.INSERTION,\n [NodeTypes.INSERTION]: currentVal,\n fields: getInsertionFields(rawValue),\n };\n }\n }\n }\n\n return finalContent;\n};\n\n/**\n * Helper to write extracted content to dictionary file(s).\n */\nexport const writeContentHelper = async (\n extractedContent: Record<string, string>,\n dictionaryKey: DictionaryKey,\n filePath: string,\n configuration: IntlayerConfig\n): Promise<string> => {\n const { absolutePath, isPerLocale } = await resolveContentFilePaths(\n filePath,\n dictionaryKey,\n configuration\n );\n\n const { defaultLocale } = configuration.internationalization;\n const { baseDir } = configuration.system;\n\n if (!cachedBundleFilePath) {\n cachedBundleFilePath = await ensureIntlayerBundle(configuration);\n }\n\n const outputDir = dirname(absolutePath);\n\n // Ensure output directory exists\n await mkdir(outputDir, { recursive: true });\n\n // Read existing dictionary to preserve translations and metadata\n let existingDictionary: Dictionary | null = null;\n\n if (existsSync(absolutePath)) {\n try {\n const dictionary = await loadContentDeclaration(\n absolutePath,\n configuration,\n cachedBundleFilePath\n );\n\n existingDictionary = dictionary ?? null;\n } catch (error) {\n console.error(error);\n }\n }\n\n const relativeContentFilePath = relative(baseDir, absolutePath);\n const componentFilePath = getComponentFilePath(filePath, configuration);\n\n let mergedDictionary: Dictionary;\n\n if (isPerLocale) {\n // Per-locale format: simple string content for a single locale\n const mergedContent = mergeWithExistingPerLocaleDictionary(\n extractedContent,\n existingDictionary\n );\n\n mergedDictionary = {\n // Preserve existing metadata\n ...existingDictionary,\n key: dictionaryKey,\n content: mergedContent,\n locale: defaultLocale,\n filePath: relativeContentFilePath,\n componentFilePath,\n };\n } else {\n // Multilingual format: content wrapped in translation nodes for multiple locales\n const mergedContent = mergeWithExistingMultilingualDictionary(\n extractedContent,\n existingDictionary,\n defaultLocale\n );\n\n mergedDictionary = {\n // Preserve existing metadata\n ...existingDictionary,\n key: dictionaryKey,\n content: mergedContent,\n filePath: relativeContentFilePath,\n componentFilePath,\n };\n }\n\n const relativeDir = relative(baseDir, outputDir);\n\n const writeResult = await writeContentDeclaration(\n mergedDictionary,\n configuration,\n {\n newDictionariesPath: relativeDir,\n localeList: [defaultLocale],\n }\n );\n\n // Build the dictionary immediately\n const dictionaryToBuild: Dictionary = {\n ...mergedDictionary,\n filePath: relative(baseDir, writeResult?.path ?? absolutePath),\n };\n\n await buildDictionary([dictionaryToBuild], configuration);\n\n return absolutePath;\n};\n"],"mappings":"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"}
@@ -29,6 +29,7 @@ const formatCompilerResult = (componentKey, res) => {
29
29
  };
30
30
  const processFileInternal = (filePath, packageName, options, dependencies, saveComponent, providedComponentKey) => {
31
31
  const fileText = options?.code ?? (0, node_fs.readFileSync)(filePath, "utf-8");
32
+ if ((0, _intlayer_config_extract.getExtractionIgnoreDirectives)(fileText).isFileIgnored) return void 0;
32
33
  const componentKey = providedComponentKey ?? require_extractContent_utils_extractDictionaryKey.extractDictionaryKey(filePath, fileText, dependencies.configuration.compiler.dictionaryKeyPrefix);
33
34
  const ext = (0, node_path.extname)(filePath);
34
35
  const { vueCompiler, svelteCompiler, unmergedDictionaries, configuration } = dependencies;
@@ -1 +1 @@
1
- {"version":3,"file":"extractContent.cjs","names":["readFileSync","extractDictionaryKey","extname","ATTRIBUTES_TO_EXTRACT","extractTsContent","colorizePath","ANSIColors","processTsxFile","detectFormatCommand","writeContentHelper","relative","colorize","getConfiguration","getAppLogger","getUnmergedDictionaries","getProjectRequire"],"sources":["../../../src/extractContent/extractContent.ts"],"sourcesContent":["import { execSync } from 'node:child_process';\nimport { readFileSync, writeFileSync } from 'node:fs';\nimport { extname, relative } from 'node:path';\nimport type * as t from '@babel/types';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { ATTRIBUTES_TO_EXTRACT, shouldExtract } from '@intlayer/config/extract';\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 { getUnmergedDictionaries } from '@intlayer/dictionaries-entry/unmerged';\nimport { detectFormatCommand } from '@intlayer/engine/cli';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { extractTsContent } from './babelProcessor';\nimport { writeContentHelper } from './contentWriter';\nimport { processTsxFile } from './processTsxFile';\nimport { extractDictionaryKeyFromPath, type PackageName } 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 fileText\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 fileText\n );\n\n if (res) {\n return formatCompilerResult(componentKey, res);\n }\n }\n\n if (ext === '.astro') {\n const frontmatterMatch = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---/.exec(fileText);\n let fakeTsxCode = fileText;\n let hasFrontmatter = false;\n\n if (frontmatterMatch) {\n hasFrontmatter = true;\n const matchStart = frontmatterMatch.index;\n const matchEnd = matchStart + frontmatterMatch[0].length;\n\n const before = fileText.substring(0, matchStart);\n const matchText = frontmatterMatch[0];\n const after = fileText.substring(matchEnd);\n\n const newMatchText = matchText\n .replace(/^---/, '///')\n .replace(/---$/, '///');\n fakeTsxCode = before + newMatchText + after;\n } else {\n // Add empty frontmatter with a dummy statement so imports get injected properly\n fakeTsxCode = `///\\n;\\n///\\n${fileText}`;\n }\n\n const result = processTsxFile(\n filePath,\n componentKey,\n packageName,\n configuration,\n false, // don't let processTsxFile write — we reconstruct the full file below\n unmergedDictionaries,\n fakeTsxCode\n );\n\n if (!result) return undefined;\n\n let modifiedFullCode = result.modifiedCode\n .replace(/^\\s*\\/\\/\\//, '---')\n .replace(/\\n\\/\\/\\//, '\\n---');\n\n if (!hasFrontmatter) {\n modifiedFullCode = modifiedFullCode.replace(/\\n;\\n---\\n/, '\\n---\\n');\n }\n\n if (saveComponent) {\n writeFileSync(filePath, modifiedFullCode);\n\n const formatCommand = detectFormatCommand(configuration);\n if (formatCommand) {\n try {\n execSync(formatCommand.replace('{{file}}', filePath), {\n stdio: 'inherit',\n cwd: dependencies.configuration.system.baseDir,\n });\n } catch (error) {\n console.error(error);\n }\n }\n }\n\n return {\n extractedContentMap: result.extractedContent,\n transformedCode: modifiedFullCode,\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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"}
1
+ {"version":3,"file":"extractContent.cjs","names":["readFileSync","getExtractionIgnoreDirectives","extractDictionaryKey","extname","ATTRIBUTES_TO_EXTRACT","extractTsContent","colorizePath","ANSIColors","processTsxFile","detectFormatCommand","writeContentHelper","relative","colorize","getConfiguration","getAppLogger","getUnmergedDictionaries","getProjectRequire"],"sources":["../../../src/extractContent/extractContent.ts"],"sourcesContent":["import { execSync } from 'node:child_process';\nimport { readFileSync, writeFileSync } from 'node:fs';\nimport { extname, relative } from 'node:path';\nimport type * as t from '@babel/types';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport {\n ATTRIBUTES_TO_EXTRACT,\n getExtractionIgnoreDirectives,\n shouldExtract,\n} from '@intlayer/config/extract';\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 { getUnmergedDictionaries } from '@intlayer/dictionaries-entry/unmerged';\nimport { detectFormatCommand } from '@intlayer/engine/cli';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { extractTsContent } from './babelProcessor';\nimport { writeContentHelper } from './contentWriter';\nimport { processTsxFile } from './processTsxFile';\nimport { extractDictionaryKeyFromPath, type PackageName } 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\n // `// intlayer-ignore-file` keeps the whole file out of extraction.\n if (getExtractionIgnoreDirectives(fileText).isFileIgnored) return undefined;\n\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 fileText\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 fileText\n );\n\n if (res) {\n return formatCompilerResult(componentKey, res);\n }\n }\n\n if (ext === '.astro') {\n const frontmatterMatch = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---/.exec(fileText);\n let fakeTsxCode = fileText;\n let hasFrontmatter = false;\n\n if (frontmatterMatch) {\n hasFrontmatter = true;\n const matchStart = frontmatterMatch.index;\n const matchEnd = matchStart + frontmatterMatch[0].length;\n\n const before = fileText.substring(0, matchStart);\n const matchText = frontmatterMatch[0];\n const after = fileText.substring(matchEnd);\n\n const newMatchText = matchText\n .replace(/^---/, '///')\n .replace(/---$/, '///');\n fakeTsxCode = before + newMatchText + after;\n } else {\n // Add empty frontmatter with a dummy statement so imports get injected properly\n fakeTsxCode = `///\\n;\\n///\\n${fileText}`;\n }\n\n const result = processTsxFile(\n filePath,\n componentKey,\n packageName,\n configuration,\n false, // don't let processTsxFile write — we reconstruct the full file below\n unmergedDictionaries,\n fakeTsxCode\n );\n\n if (!result) return undefined;\n\n let modifiedFullCode = result.modifiedCode\n .replace(/^\\s*\\/\\/\\//, '---')\n .replace(/\\n\\/\\/\\//, '\\n---');\n\n if (!hasFrontmatter) {\n modifiedFullCode = modifiedFullCode.replace(/\\n;\\n---\\n/, '\\n---\\n');\n }\n\n if (saveComponent) {\n writeFileSync(filePath, modifiedFullCode);\n\n const formatCommand = detectFormatCommand(configuration);\n if (formatCommand) {\n try {\n execSync(formatCommand.replace('{{file}}', filePath), {\n stdio: 'inherit',\n cwd: dependencies.configuration.system.baseDir,\n });\n } catch (error) {\n console.error(error);\n }\n }\n }\n\n return {\n extractedContentMap: result.extractedContent,\n transformedCode: modifiedFullCode,\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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"}
@@ -15,6 +15,7 @@ exports.extractDictionaryInfo = require_extractContent_utils_extractDictionaryIn
15
15
  exports.extractDictionaryKey = require_extractContent_utils_extractDictionaryKey.extractDictionaryKey;
16
16
  exports.extractDictionaryKeyFromPath = require_extractContent_utils_extractDictionaryKey.extractDictionaryKeyFromPath;
17
17
  exports.generateKey = require_extractContent_utils_generateKey.generateKey;
18
+ exports.getComponentFilePath = require_extractContent_utils_resolveDictionaryKey.getComponentFilePath;
18
19
  exports.getComponentName = require_extractContent_utils_getComponentName.getComponentName;
19
20
  exports.getExistingIntlayerInfo = require_extractContent_utils_getExistingIntlayerInfo.getExistingIntlayerInfo;
20
21
  exports.getOrGenerateKey = require_extractContent_utils_getOrGenerateKey.getOrGenerateKey;
@@ -6,13 +6,28 @@ let _intlayer_dictionaries_entry_unmerged = require("@intlayer/dictionaries-entr
6
6
 
7
7
  // src/extractContent/utils/resolveDictionaryKey.ts
8
8
  /**
9
+ * Returns the component path stored in `componentFilePath`: relative to the
10
+ * project root, POSIX separators, so it is stable across machines.
11
+ */
12
+ const getComponentFilePath = (filePath, configuration) => (0, _intlayer_config_utils.normalizePath)((0, node_path.relative)(configuration.system.baseDir, (0, node_path.resolve)(filePath)));
13
+ /**
14
+ * Whether the dictionaries already declared under a key were extracted from
15
+ * this component. Dictionaries without `componentFilePath` (hand-written,
16
+ * remote, or translations added afterwards) do not claim the key.
17
+ */
18
+ const isOwnedByComponent = (dictionaries, componentFilePath) => {
19
+ const owners = dictionaries.map((dictionary) => dictionary.componentFilePath).filter((owner) => Boolean(owner));
20
+ return owners.length > 0 && owners.every((owner) => owner === componentFilePath);
21
+ };
22
+ /**
9
23
  * Resolves a unique dictionary key, checking for existing dictionaries and files.
10
- * Note: this chokidar-specific variant fetches unmergedDictionaries internally
11
- * from the configuration (unlike the `@intlayer/babel` version which takes it as a parameter).
24
+ * A key already used by a dictionary extracted from the same component is
25
+ * reused, so the key stays stable when the component is not rewritten.
12
26
  */
13
27
  const resolveDictionaryKey = (initialKey, filePath, configuration, unmergedDictionaries, usedKeys = /* @__PURE__ */ new Set()) => {
14
28
  const dicts = unmergedDictionaries ?? (0, _intlayer_dictionaries_entry_unmerged.getUnmergedDictionaries)(configuration) ?? {};
15
29
  const { fileExtensions } = configuration.content;
30
+ const componentFilePath = getComponentFilePath(filePath, configuration);
16
31
  const dirName = (0, node_path.dirname)(filePath);
17
32
  const firstExtension = fileExtensions[0] ?? ".ts";
18
33
  const extension = firstExtension.startsWith(".") ? firstExtension : `.${firstExtension}`;
@@ -24,6 +39,7 @@ const resolveDictionaryKey = (initialKey, filePath, configuration, unmergedDicti
24
39
  (0, _intlayer_config_utils.assertPathWithin)((0, node_path.resolve)(contentFilePath), dirName);
25
40
  const isKeyUsed = usedKeys.has(keyToTest);
26
41
  const hasDictionaries = dictionaries && dictionaries.length > 0;
42
+ if (!isKeyUsed && hasDictionaries && isOwnedByComponent(dictionaries, componentFilePath)) return keyToTest;
27
43
  const fileExists = (0, node_fs.existsSync)(contentFilePath);
28
44
  if (!isKeyUsed && !hasDictionaries && !fileExists) return keyToTest;
29
45
  index++;
@@ -31,5 +47,6 @@ const resolveDictionaryKey = (initialKey, filePath, configuration, unmergedDicti
31
47
  return initialKey;
32
48
  };
33
49
 
50
+ exports.getComponentFilePath = getComponentFilePath;
34
51
  exports.resolveDictionaryKey = resolveDictionaryKey;
35
52
  //# sourceMappingURL=resolveDictionaryKey.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"resolveDictionaryKey.cjs","names":["getUnmergedDictionaries","dirname","join","resolve","existsSync"],"sources":["../../../../src/extractContent/utils/resolveDictionaryKey.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { assertPathWithin } from '@intlayer/config/utils';\nimport { getUnmergedDictionaries } from '@intlayer/dictionaries-entry/unmerged';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Dictionary } from '@intlayer/types/dictionary';\n\n/**\n * Resolves a unique dictionary key, checking for existing dictionaries and files.\n * Note: this chokidar-specific variant fetches unmergedDictionaries internally\n * from the configuration (unlike the `@intlayer/babel` version which takes it as a parameter).\n */\nexport const resolveDictionaryKey = (\n initialKey: string,\n filePath: string,\n configuration: IntlayerConfig,\n unmergedDictionaries?: Record<string, unknown>,\n usedKeys: Set<string> = new Set()\n): string => {\n const dicts =\n unmergedDictionaries ?? getUnmergedDictionaries(configuration) ?? {};\n\n const { fileExtensions } = configuration.content;\n\n const dirName = dirname(filePath);\n const firstExtension = fileExtensions[0] ?? '.ts';\n const extension = firstExtension.startsWith('.')\n ? firstExtension\n : `.${firstExtension}`;\n\n let index = 0;\n\n while (index < 100) {\n const keyToTest = index === 0 ? initialKey : `${initialKey}${index}`;\n const dictionaries = dicts[keyToTest] as Dictionary[] | undefined;\n const contentFilePath = join(dirName, `${keyToTest}${extension}`);\n\n assertPathWithin(resolve(contentFilePath), dirName);\n\n const isKeyUsed = usedKeys.has(keyToTest);\n const hasDictionaries = dictionaries && dictionaries.length > 0;\n const fileExists = existsSync(contentFilePath);\n\n if (!isKeyUsed && !hasDictionaries && !fileExists) {\n return keyToTest;\n }\n index++;\n }\n\n return initialKey;\n};\n"],"mappings":";;;;;;;;;;;;AAYA,MAAa,wBACX,YACA,UACA,eACA,sBACA,2BAAwB,IAAI,IAAI,MACrB;CACX,MAAM,QACJ,4BAAwBA,+DAAwB,aAAa,KAAK,CAAC;CAErE,MAAM,EAAE,mBAAmB,cAAc;CAEzC,MAAM,cAAUC,mBAAQ,QAAQ;CAChC,MAAM,iBAAiB,eAAe,MAAM;CAC5C,MAAM,YAAY,eAAe,WAAW,GAAG,IAC3C,iBACA,IAAI;CAER,IAAI,QAAQ;CAEZ,OAAO,QAAQ,KAAK;EAClB,MAAM,YAAY,UAAU,IAAI,aAAa,GAAG,aAAa;EAC7D,MAAM,eAAe,MAAM;EAC3B,MAAM,sBAAkBC,gBAAK,SAAS,GAAG,YAAY,WAAW;EAEhE,iDAAiBC,mBAAQ,eAAe,GAAG,OAAO;EAElD,MAAM,YAAY,SAAS,IAAI,SAAS;EACxC,MAAM,kBAAkB,gBAAgB,aAAa,SAAS;EAC9D,MAAM,iBAAaC,oBAAW,eAAe;EAE7C,IAAI,CAAC,aAAa,CAAC,mBAAmB,CAAC,YACrC,OAAO;EAET;CACF;CAEA,OAAO;AACT"}
1
+ {"version":3,"file":"resolveDictionaryKey.cjs","names":["normalizePath","relative","resolve","getUnmergedDictionaries","dirname","join","existsSync"],"sources":["../../../../src/extractContent/utils/resolveDictionaryKey.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { dirname, join, relative, resolve } from 'node:path';\nimport { assertPathWithin, normalizePath } from '@intlayer/config/utils';\nimport { getUnmergedDictionaries } from '@intlayer/dictionaries-entry/unmerged';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Dictionary } from '@intlayer/types/dictionary';\n\n/**\n * Returns the component path stored in `componentFilePath`: relative to the\n * project root, POSIX separators, so it is stable across machines.\n */\nexport const getComponentFilePath = (\n filePath: string,\n configuration: IntlayerConfig\n): string =>\n normalizePath(relative(configuration.system.baseDir, resolve(filePath)));\n\n/**\n * Whether the dictionaries already declared under a key were extracted from\n * this component. Dictionaries without `componentFilePath` (hand-written,\n * remote, or translations added afterwards) do not claim the key.\n */\nconst isOwnedByComponent = (\n dictionaries: Dictionary[],\n componentFilePath: string\n): boolean => {\n const owners = dictionaries\n .map((dictionary) => dictionary.componentFilePath)\n .filter((owner): owner is string => Boolean(owner));\n\n return (\n owners.length > 0 && owners.every((owner) => owner === componentFilePath)\n );\n};\n\n/**\n * Resolves a unique dictionary key, checking for existing dictionaries and files.\n * A key already used by a dictionary extracted from the same component is\n * reused, so the key stays stable when the component is not rewritten.\n */\nexport const resolveDictionaryKey = (\n initialKey: string,\n filePath: string,\n configuration: IntlayerConfig,\n unmergedDictionaries?: Record<string, unknown>,\n usedKeys: Set<string> = new Set()\n): string => {\n const dicts =\n unmergedDictionaries ?? getUnmergedDictionaries(configuration) ?? {};\n\n const { fileExtensions } = configuration.content;\n const componentFilePath = getComponentFilePath(filePath, configuration);\n\n const dirName = dirname(filePath);\n const firstExtension = fileExtensions[0] ?? '.ts';\n const extension = firstExtension.startsWith('.')\n ? firstExtension\n : `.${firstExtension}`;\n\n let index = 0;\n\n while (index < 100) {\n const keyToTest = index === 0 ? initialKey : `${initialKey}${index}`;\n const dictionaries = dicts[keyToTest] as Dictionary[] | undefined;\n const contentFilePath = join(dirName, `${keyToTest}${extension}`);\n\n assertPathWithin(resolve(contentFilePath), dirName);\n\n const isKeyUsed = usedKeys.has(keyToTest);\n const hasDictionaries = dictionaries && dictionaries.length > 0;\n\n if (\n !isKeyUsed &&\n hasDictionaries &&\n isOwnedByComponent(dictionaries, componentFilePath)\n ) {\n return keyToTest;\n }\n\n const fileExists = existsSync(contentFilePath);\n\n if (!isKeyUsed && !hasDictionaries && !fileExists) {\n return keyToTest;\n }\n index++;\n }\n\n return initialKey;\n};\n"],"mappings":";;;;;;;;;;;AAWA,MAAa,wBACX,UACA,sBAEAA,0CAAcC,oBAAS,cAAc,OAAO,aAASC,mBAAQ,QAAQ,CAAC,CAAC;;;;;;AAOzE,MAAM,sBACJ,cACA,sBACY;CACZ,MAAM,SAAS,aACZ,KAAK,eAAe,WAAW,iBAAiB,CAAC,CACjD,QAAQ,UAA2B,QAAQ,KAAK,CAAC;CAEpD,OACE,OAAO,SAAS,KAAK,OAAO,OAAO,UAAU,UAAU,iBAAiB;AAE5E;;;;;;AAOA,MAAa,wBACX,YACA,UACA,eACA,sBACA,2BAAwB,IAAI,IAAI,MACrB;CACX,MAAM,QACJ,4BAAwBC,+DAAwB,aAAa,KAAK,CAAC;CAErE,MAAM,EAAE,mBAAmB,cAAc;CACzC,MAAM,oBAAoB,qBAAqB,UAAU,aAAa;CAEtE,MAAM,cAAUC,mBAAQ,QAAQ;CAChC,MAAM,iBAAiB,eAAe,MAAM;CAC5C,MAAM,YAAY,eAAe,WAAW,GAAG,IAC3C,iBACA,IAAI;CAER,IAAI,QAAQ;CAEZ,OAAO,QAAQ,KAAK;EAClB,MAAM,YAAY,UAAU,IAAI,aAAa,GAAG,aAAa;EAC7D,MAAM,eAAe,MAAM;EAC3B,MAAM,sBAAkBC,gBAAK,SAAS,GAAG,YAAY,WAAW;EAEhE,iDAAiBH,mBAAQ,eAAe,GAAG,OAAO;EAElD,MAAM,YAAY,SAAS,IAAI,SAAS;EACxC,MAAM,kBAAkB,gBAAgB,aAAa,SAAS;EAE9D,IACE,CAAC,aACD,mBACA,mBAAmB,cAAc,iBAAiB,GAElD,OAAO;EAGT,MAAM,iBAAaI,oBAAW,eAAe;EAE7C,IAAI,CAAC,aAAa,CAAC,mBAAmB,CAAC,YACrC,OAAO;EAET;CACF;CAEA,OAAO;AACT"}
@@ -2,7 +2,7 @@ import { resolveDictionaryKey } from "./utils/resolveDictionaryKey.mjs";
2
2
  import { getComponentName } from "./utils/getComponentName.mjs";
3
3
  import { getExistingIntlayerInfo } from "./utils/getExistingIntlayerInfo.mjs";
4
4
  import { getOrGenerateKey } from "./utils/getOrGenerateKey.mjs";
5
- import { ATTRIBUTES_TO_EXTRACT, shouldExtract } from "@intlayer/config/extract";
5
+ import { ATTRIBUTES_TO_EXTRACT, getExtractionIgnoreDirectives, shouldExtract } from "@intlayer/config/extract";
6
6
  import _traverse from "@babel/traverse";
7
7
  import * as t from "@babel/types";
8
8
 
@@ -316,17 +316,46 @@ const extractBabelContentForComponents = (ast, fileCode, existingKeys, defaultKe
316
316
  }
317
317
  return globalFileKey || defaultKey;
318
318
  };
319
+ const ignoreDirectives = getExtractionIgnoreDirectives(fileCode);
320
+ /**
321
+ * Start offset of the JSX element a string directly belongs to: its text,
322
+ * a `{'literal'}` child, or one of its attributes.
323
+ */
324
+ const getEnclosingJsxElementStart = (path) => {
325
+ let current = path.parentPath;
326
+ if (current?.isJSXExpressionContainer()) current = current.parentPath;
327
+ if (current?.isJSXAttribute()) current = current.parentPath;
328
+ if (current?.isJSXOpeningElement()) current = current.parentPath;
329
+ return current?.isJSXElement() || current?.isJSXFragment() ? current.node.start ?? void 0 : void 0;
330
+ };
331
+ /**
332
+ * Whether an `intlayer-ignore-line` / `intlayer-ignore-next-line` directive
333
+ * covers the string: on the line where its text starts, or on the opening
334
+ * tag of its JSX element.
335
+ */
336
+ const isIgnoredPath = (path) => {
337
+ const { node } = path;
338
+ if (typeof node.start === "number") {
339
+ const text = t.isJSXText(node) ? node.value : "";
340
+ const leadingWhitespaceLength = text.length - text.trimStart().length;
341
+ if (ignoreDirectives.isOffsetIgnored(node.start + leadingWhitespaceLength)) return true;
342
+ }
343
+ return ignoreDirectives.isOffsetIgnored(getEnclosingJsxElementStart(path));
344
+ };
319
345
  traverse(ast, {
320
346
  JSXElement(path) {
321
347
  if (handledNodes.has(path.node)) return;
348
+ if (isIgnoredPath(path)) return;
322
349
  handleJsxInsertionBabel(path, fileCode, existingKeys, getComponentKeyForPath, extractedContent, replacements, handledNodes);
323
350
  },
324
351
  JSXFragment(path) {
325
352
  if (handledNodes.has(path.node)) return;
353
+ if (isIgnoredPath(path)) return;
326
354
  handleJsxInsertionBabel(path, fileCode, existingKeys, getComponentKeyForPath, extractedContent, replacements, handledNodes);
327
355
  },
328
356
  JSXText(path) {
329
357
  if (handledNodes.has(path.node)) return;
358
+ if (isIgnoredPath(path)) return;
330
359
  const text = path.node.value;
331
360
  if (shouldExtract(text)) {
332
361
  const componentKey = getComponentKeyForPath(path);
@@ -341,6 +370,7 @@ const extractBabelContentForComponents = (ast, fileCode, existingKeys, defaultKe
341
370
  },
342
371
  JSXAttribute(path) {
343
372
  if (handledNodes.has(path.node)) return;
373
+ if (isIgnoredPath(path)) return;
344
374
  const name = path.node.name.name;
345
375
  if (typeof name !== "string" || !ATTRIBUTES_TO_EXTRACT.includes(name)) return;
346
376
  const value = path.node.value;
@@ -357,6 +387,7 @@ const extractBabelContentForComponents = (ast, fileCode, existingKeys, defaultKe
357
387
  },
358
388
  StringLiteral(path) {
359
389
  if (handledNodes.has(path.node)) return;
390
+ if (isIgnoredPath(path)) return;
360
391
  const text = path.node.value;
361
392
  if (!shouldExtract(text)) return;
362
393
  const parent = path.parentPath;
@@ -387,6 +418,7 @@ const extractBabelContentForComponents = (ast, fileCode, existingKeys, defaultKe
387
418
  },
388
419
  TemplateLiteral(path) {
389
420
  if (handledNodes.has(path.node)) return;
421
+ if (isIgnoredPath(path)) return;
390
422
  const { quasis, expressions } = path.node;
391
423
  let combinedString = "";
392
424
  const variables = [];
@@ -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 { ATTRIBUTES_TO_EXTRACT, shouldExtract } from '@intlayer/config/extract';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { resolveDictionaryKey } from '../extractContent/utils';\nimport {\n type ExistingIntlayerInfo,\n getComponentName,\n getExistingIntlayerInfo,\n getOrGenerateKey,\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 | 'template-literal';\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 * Method names whose string arguments are technical operands — lookups,\n * comparisons, DOM queries, storage keys, event names — rather than translatable\n * display text. A string literal passed to one of these (e.g.\n * `message.includes('Loading chunk')`) is code, not content, and is skipped.\n */\n/**\n * Binary operators whose string operands are code constants / enum values\n * (e.g. `msg.step === 'ERROR'`) rather than translatable display text.\n */\nconst COMPARISON_OPERATORS = new Set(['===', '!==', '==', '!=']);\n\n/**\n * Object property names whose string values are technical/non-translatable\n * (e.g. `icon: 'Globe'`, `variant: 'primary'`). String values under other\n * property names (e.g. `label: 'Language'`) are still extracted.\n */\nconst TECHNICAL_KEYS = new Set<string>([\n 'icon',\n 'className',\n 'class',\n 'id',\n 'type',\n 'variant',\n 'color',\n 'theme',\n 'size',\n 'align',\n 'placement',\n 'target',\n 'rel',\n 'method',\n 'mode',\n 'direction',\n 'orientation',\n 'scope',\n 'role',\n 'lang',\n 'locale',\n 'href',\n 'src',\n 'width',\n 'height',\n 'as',\n 'to',\n 'key',\n 'value',\n 'defaultValue',\n 'prop',\n 'property',\n 'state',\n 'action',\n 'event',\n 'handler',\n 'callback',\n 'url',\n 'uri',\n 'path',\n 'route',\n 'slug',\n 'endpoint',\n 'headers',\n 'contentType',\n 'toolName',\n 'toolDescription',\n 'toolParamDescription',\n]);\n\nconst NON_TRANSLATABLE_CALL_METHODS = new Set<string>([\n 'includes',\n 'startsWith',\n 'endsWith',\n 'indexOf',\n 'lastIndexOf',\n 'match',\n 'matchAll',\n 'search',\n 'localeCompare',\n 'split',\n 'getAttribute',\n 'setAttribute',\n 'hasAttribute',\n 'removeAttribute',\n 'querySelector',\n 'querySelectorAll',\n 'getElementById',\n 'getElementsByClassName',\n 'getElementsByTagName',\n 'addEventListener',\n 'removeEventListener',\n 'dispatchEvent',\n 'matchMedia',\n 'getItem',\n 'setItem',\n 'removeItem',\n 'getPropertyValue',\n 'setProperty',\n]);\n\n/**\n * When `path` is a string-literal argument of a member/optional-member call\n * expression (e.g. `foo.includes('bar')` or `foo?.bar?.includes('baz')`),\n * returns the called method name and whether the call targets the `console`\n * object. Returns `undefined` when the parent is not such a call, or the string\n * is not one of its arguments.\n */\nconst getEnclosingMethodCallInfo = (\n path: NodePath<t.StringLiteral>\n): { methodName: string | undefined; isConsoleCall: boolean } | undefined => {\n const parent = path.parentPath;\n if (!parent.isCallExpression() && !parent.isOptionalCallExpression()) {\n return undefined;\n }\n\n const callNode = parent.node as t.CallExpression | t.OptionalCallExpression;\n if (!callNode.arguments.includes(path.node)) return undefined;\n\n const callee = callNode.callee;\n if (!t.isMemberExpression(callee) && !t.isOptionalMemberExpression(callee)) {\n return undefined;\n }\n\n const methodName = t.isIdentifier(callee.property)\n ? callee.property.name\n : undefined;\n const isConsoleCall =\n t.isIdentifier(callee.object) && callee.object.name === 'console';\n\n return { methodName, isConsoleCall };\n};\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 if (t.isTemplateLiteral(expr)) {\n for (let i = 0; i < expr.quasis.length; i++) {\n parts.push({\n type: 'text',\n value: expr.quasis[i]?.value.raw ?? '',\n });\n if (i < expr.expressions.length) {\n const subExpr = expr.expressions[i];\n if (t.isIdentifier(subExpr)) {\n parts.push({\n type: 'var',\n value: subExpr.name,\n originalExpr: subExpr.name,\n });\n hasVariables = true;\n } else if (t.isMemberExpression(subExpr)) {\n const code = fileCode.substring(subExpr.start!, subExpr.end!);\n const varName = t.isIdentifier(subExpr.property)\n ? subExpr.property.name\n : 'var';\n parts.push({ type: 'var', value: varName, originalExpr: code });\n hasVariables = true;\n } else {\n return false;\n }\n }\n }\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 /** Cached `getExistingIntlayerInfo` result per component node, so callers do not re-traverse. */\n existingInfoByNode: Map<t.Node, ExistingIntlayerInfo | undefined>;\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 // `getExistingIntlayerInfo` traverses the component body, so resolve it a\n // single time per component and share the cache with every later phase\n // (including the caller, via the returned `existingInfoByNode`).\n const existingInfoByNode = new Map<\n t.Node,\n ExistingIntlayerInfo | undefined\n >();\n const componentNodeToPath = new Map<t.Node, NodePath>();\n for (const path of componentPaths) {\n existingInfoByNode.set(path.node, getExistingIntlayerInfo(path));\n componentNodeToPath.set(path.node, path);\n }\n\n // Pre-scan non-Program paths to collect their existing dictionary keys before the\n // Program scope is assigned. Without this, Program is processed first (depth-first\n // traversal) and creates a new file-path-derived key even when a child component\n // already declares a specific dictionary (e.g. useIntlayer('dashboard-sidebar')).\n for (const path of componentPaths) {\n if (path.isProgram()) continue;\n const existingInfo = existingInfoByNode.get(path.node);\n if (existingInfo) {\n usedKeysInFile.add(existingInfo.key);\n }\n }\n\n for (const path of componentPaths) {\n const existingInfo = existingInfoByNode.get(path.node);\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 // Reuse the dominant existing key from child components so that\n // module-level strings join the same dictionary rather than\n // creating a new file-path-derived one (e.g. 'route').\n const dominantKey =\n usedKeysInFile.size > 0 ? [...usedKeysInFile][0] : undefined;\n if (dominantKey) {\n globalFileKey = dominantKey;\n } else {\n globalFileKey = resolveDictionaryKey(\n defaultKey,\n filePath,\n configuration,\n unmergedDictionaries,\n usedKeysInFile\n );\n usedKeysInFile.add(globalFileKey);\n }\n }\n componentKeyMap.set(path.node, globalFileKey);\n hookMap.set(path.node, 'getIntlayer');\n } else {\n let inheritedKey: string | undefined;\n let parent: NodePath | null = path.parentPath;\n while (parent) {\n if (componentKeyMap.has(parent.node)) {\n inheritedKey = componentKeyMap.get(parent.node);\n break;\n }\n parent = parent.parentPath;\n }\n\n if (!inheritedKey) {\n if (!globalFileKey) {\n globalFileKey = resolveDictionaryKey(\n defaultKey,\n filePath,\n configuration,\n unmergedDictionaries,\n usedKeysInFile\n );\n usedKeysInFile.add(globalFileKey);\n }\n inheritedKey = globalFileKey;\n }\n\n componentKeyMap.set(path.node, inheritedKey);\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 // A literal wrapped in braces (`title={\"Hello\"}`) follows the same\n // attribute allowlist as the plain form handled by `JSXAttribute`.\n if (parent.isJSXExpressionContainer()) {\n const attributePath = parent.parentPath;\n if (attributePath?.isJSXAttribute()) {\n const attributeName = attributePath.node.name.name;\n if (\n typeof attributeName !== 'string' ||\n !ATTRIBUTES_TO_EXTRACT.includes(\n attributeName as (typeof ATTRIBUTES_TO_EXTRACT)[number]\n )\n ) {\n return;\n }\n }\n }\n\n // Skip string literals used as comparison operands (e.g. `msg.step === 'ERROR'`).\n // These are code constants / enum values, not translatable display text.\n if (\n parent.isBinaryExpression() &&\n COMPARISON_OPERATORS.has(parent.node.operator)\n ) {\n return;\n }\n\n // Skip string literals used as `switch` case tests (e.g. `case 'ERROR':`).\n // Like comparisons, these are code constants, not display text.\n if (parent.isSwitchCase() && parent.node.test === path.node) return;\n\n // Skip string literals passed to technical method calls, e.g.\n // `message.includes('Loading chunk')`, `el.getAttribute('data-id')`, or\n // `console.log('...')`. These are code operands, not display text.\n const methodCallInfo = getEnclosingMethodCallInfo(path);\n if (\n methodCallInfo &&\n (methodCallInfo.isConsoleCall ||\n (methodCallInfo.methodName !== undefined &&\n NON_TRANSLATABLE_CALL_METHODS.has(methodCallInfo.methodName)))\n ) {\n return;\n }\n\n if (parent.isObjectProperty() && parent.node.key === path.node) return;\n\n // Skip string values in known technical/non-translatable object properties (e.g. `icon: 'Globe'`).\n // String values in translatable object properties (e.g. `label: 'Language'`) are still extracted.\n if (\n parent.isObjectProperty() &&\n t.isIdentifier(parent.node.key) &&\n TECHNICAL_KEYS.has(parent.node.key.name)\n ) {\n return;\n }\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 TemplateLiteral(path) {\n if (handledNodes.has(path.node)) return;\n\n const { quasis, expressions } = path.node;\n\n // Build the combined string with placeholders\n let combinedString = '';\n const variables: string[] = [];\n let hasSignificantText = false;\n\n for (let i = 0; i < quasis.length; i++) {\n const text = quasis[i]?.value.raw ?? '';\n combinedString += text;\n if (text.trim().length > 0) hasSignificantText = true;\n\n if (i < expressions.length) {\n const expr = expressions[i];\n if (t.isIdentifier(expr)) {\n combinedString += `{{${expr.name}}}`;\n variables.push(`${expr.name}: ${expr.name}`);\n } else if (t.isMemberExpression(expr)) {\n const code = fileCode.substring(expr.start!, expr.end!);\n const varName = t.isIdentifier(expr.property)\n ? expr.property.name\n : 'var';\n combinedString += `{{${varName}}}`;\n variables.push(`${varName}: ${code}`);\n } else {\n // Complex expression in template literal, skip\n return;\n }\n }\n }\n\n if (!hasSignificantText) return;\n\n const cleanString = combinedString.replace(/\\s+/g, ' ').trim();\n\n if (!shouldExtract(cleanString)) return;\n\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n cleanString,\n componentKey,\n existingKeys,\n extractedContent\n );\n\n const uniqueVars = Array.from(new Set(variables));\n\n replacements.push({\n path,\n key,\n type: 'template-literal',\n componentKey,\n variables: uniqueVars,\n });\n },\n });\n\n /** Returns true when `componentNode` encloses the replacement's path. */\n const replacementIsInside = (\n replacement: BabelReplacement,\n componentNode: t.Node\n ): boolean => {\n let current: NodePath | null = replacement.path;\n while (current) {\n if (current.node === componentNode) return true;\n current = current.parentPath;\n }\n return false;\n };\n\n const componentsNeedingHooks = new Set<NodePath>();\n for (const componentPath of componentPaths) {\n if (componentPath.isProgram()) {\n // Only count replacements that belong to the Program scope itself, not\n // to a nested component (which provides its own hook).\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 if (componentNodeToPath.has(current.node)) {\n return false; // reached an enclosing component first\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 replacementIsInside(replacement, componentPath.node)\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 = componentNodeToPath.get(currentPath.node);\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 replacementIsInside(replacement, ancestorPath.node)\n );\n const existingInfo = existingInfoByNode.get(ancestorPath.node);\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 existingInfoByNode,\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":";;;;;;;;;AA4BA,MAAM,WACJ,OAAO,cAAc,aAAa,YAAa,UAAkB;;;;;;;;;;;AAanE,MAAM,uCAAuB,IAAI,IAAI;CAAC;CAAO;CAAO;CAAM;AAAI,CAAC;;;;;;AAO/D,MAAM,iCAAiB,IAAI,IAAY;CACrC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,MAAM,gDAAgC,IAAI,IAAY;CACpD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;;;;;;;AASD,MAAM,8BACJ,SAC2E;CAC3E,MAAM,SAAS,KAAK;CACpB,IAAI,CAAC,OAAO,iBAAiB,KAAK,CAAC,OAAO,yBAAyB,GACjE;CAGF,MAAM,WAAW,OAAO;CACxB,IAAI,CAAC,SAAS,UAAU,SAAS,KAAK,IAAI,GAAG,OAAO;CAEpD,MAAM,SAAS,SAAS;CACxB,IAAI,CAAC,EAAE,mBAAmB,MAAM,KAAK,CAAC,EAAE,2BAA2B,MAAM,GACvE;CASF,OAAO;EAAE,YANU,EAAE,aAAa,OAAO,QAAQ,IAC7C,OAAO,SAAS,OAChB;EAIiB,eAFnB,EAAE,aAAa,OAAO,MAAM,KAAK,OAAO,OAAO,SAAS;CAEvB;AACrC;;;;;AAMA,MAAa,2BACX,MACA,UACA,cACA,wBACA,kBACA,cACA,iBACY;CACZ,MAAM,WAAW,KAAK,KAAK;CAE3B,IAAI,SAAS,UAAU,GAAG,OAAO;CAEjC,MAAM,QAIA,CAAC;CACP,IAAI,qBAAqB;CACzB,IAAI,eAAe;CAEnB,KAAK,MAAM,SAAS,UAClB,IAAI,EAAE,UAAU,KAAK,GAAG;EACtB,MAAM,OAAO,MAAM;EAEnB,IAAI,KAAK,KAAK,CAAC,CAAC,SAAS,GAAG,qBAAqB;EAEjD,MAAM,KAAK;GAAE,MAAM;GAAQ,OAAO;EAAK,CAAC;CAC1C,OAAO,IAAI,EAAE,yBAAyB,KAAK,GAAG;EAC5C,IAAI,EAAE,qBAAqB,MAAM,UAAU,GACzC,MAAM,KAAK;GAAE,MAAM;GAAQ,OAAO;EAAG,CAAC;OACjC;GACL,MAAM,OAAO,MAAM;GAEnB,IAAI,EAAE,aAAa,IAAI,GAAG;IACxB,MAAM,KAAK;KACT,MAAM;KACN,OAAO,KAAK;KACZ,cAAc,KAAK;IACrB,CAAC;IACD,eAAe;GACjB,OAAO,IAAI,EAAE,mBAAmB,IAAI,GAAG;IACrC,MAAM,OAAO,SAAS,UAAU,KAAK,OAAQ,KAAK,GAAI;IAEtD,MAAM,UAAU,EAAE,aAAa,KAAK,QAAQ,IACxC,KAAK,SAAS,OACd;IAEJ,MAAM,KAAK;KAAE,MAAM;KAAO,OAAO;KAAS,cAAc;IAAK,CAAC;IAE9D,eAAe;GACjB,OAAO,IAAI,EAAE,kBAAkB,IAAI,GACjC,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,OAAO,QAAQ,KAAK;IAC3C,MAAM,KAAK;KACT,MAAM;KACN,OAAO,KAAK,OAAO,EAAE,EAAE,MAAM,OAAO;IACtC,CAAC;IACD,IAAI,IAAI,KAAK,YAAY,QAAQ;KAC/B,MAAM,UAAU,KAAK,YAAY;KACjC,IAAI,EAAE,aAAa,OAAO,GAAG;MAC3B,MAAM,KAAK;OACT,MAAM;OACN,OAAO,QAAQ;OACf,cAAc,QAAQ;MACxB,CAAC;MACD,eAAe;KACjB,OAAO,IAAI,EAAE,mBAAmB,OAAO,GAAG;MACxC,MAAM,OAAO,SAAS,UAAU,QAAQ,OAAQ,QAAQ,GAAI;MAC5D,MAAM,UAAU,EAAE,aAAa,QAAQ,QAAQ,IAC3C,QAAQ,SAAS,OACjB;MACJ,MAAM,KAAK;OAAE,MAAM;OAAO,OAAO;OAAS,cAAc;MAAK,CAAC;MAC9D,eAAe;KACjB,OACE,OAAO;IAEX;GACF;QAEA,OAAO;EAEX;CACF,OACE,OAAO;CAIX,IAAI,CAAC,oBAAoB,OAAO;CAEhC,IAAI,iBAAiB;CACrB,KAAK,MAAM,QAAQ,OACjB,IAAI,KAAK,SAAS,OAAO,kBAAkB,KAAK,KAAK,MAAM;MACtD,kBAAkB,KAAK;CAG9B,MAAM,cAAc,eAAe,QAAQ,QAAQ,GAAG,CAAC,CAAC,KAAK;CAE7D,IAAI,cAAc,WAAW,GAAG;EAC9B,MAAM,eAAe,uBAAuB,IAAI;EAChD,MAAM,MAAM,iBACV,aACA,cACA,cACA,gBACF;EAEA,MAAM,SAAS,MACZ,QAAQ,SAAS,KAAK,SAAS,KAAK,CAAC,CACrC,KAAK,SAAS,GAAG,KAAK,MAAM,IAAI,KAAK,cAAc;EACtD,MAAM,aAAa,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC;EAE7C,IAAI,cACF,aAAa,KAAK;GAChB;GACA;GACA,MAAM;GACN;GACA,mBAAmB;GACnB,WAAW;EACb,CAAC;OAED,aAAa,KAAK;GAChB;GACA;GACA,MAAM;GACN;GACA,mBAAmB;EACrB,CAAC;EAGH,SAAS,SAAS,UAAU;GAC1B,aAAa,IAAI,KAAK;EACxB,CAAC;EACD,OAAO;CACT;CAEA,OAAO;AACT;;;;;AAMA,MAAa,oCACX,KACA,UACA,cACA,aAAqB,WACrB,eACA,UACA,uBAAgD,CAAC,MAW9C;CACH,MAAM,mBAA2D,CAAC;CAClE,MAAM,eAAmC,CAAC;CAC1C,MAAM,+BAAe,IAAI,IAAY;CACrC,MAAM,kCAAkB,IAAI,IAAoB;CAChD,MAAM,0BAAU,IAAI,IAA2C;CAC/D,MAAM,iCAAiB,IAAI,IAAY;CACvC,IAAI;CAEJ,MAAM,iBAA6B,CAAC;CAEpC,SAAS,KAAK;EACZ,QAAQ,MAAM;GACZ,eAAe,KAAK,IAAI;EAC1B;EACA,oBAAoB,MAAM;GACxB,eAAe,KAAK,IAAI;EAC1B;EACA,wBAAwB,MAAM;GAC5B,eAAe,KAAK,IAAI;EAC1B;EACA,mBAAmB,MAAM;GACvB,eAAe,KAAK,IAAI;EAC1B;CACF,CAAC;CAKD,MAAM,qCAAqB,IAAI,IAG7B;CACF,MAAM,sCAAsB,IAAI,IAAsB;CACtD,KAAK,MAAM,QAAQ,gBAAgB;EACjC,mBAAmB,IAAI,KAAK,MAAM,wBAAwB,IAAI,CAAC;EAC/D,oBAAoB,IAAI,KAAK,MAAM,IAAI;CACzC;CAMA,KAAK,MAAM,QAAQ,gBAAgB;EACjC,IAAI,KAAK,UAAU,GAAG;EACtB,MAAM,eAAe,mBAAmB,IAAI,KAAK,IAAI;EACrD,IAAI,cACF,eAAe,IAAI,aAAa,GAAG;CAEvC;CAEA,KAAK,MAAM,QAAQ,gBAAgB;EACjC,MAAM,eAAe,mBAAmB,IAAI,KAAK,IAAI;EAErD,IAAI,cAAc;GAChB,gBAAgB,IAAI,KAAK,MAAM,aAAa,GAAG;GAC/C,eAAe,IAAI,aAAa,GAAG;GACnC,QAAQ,IAAI,KAAK,MAAM,aAAa,IAAI;EAC1C,OACE,IAAI,KAAK,UAAU,GAAG;GACpB,IAAI,CAAC,eAAe;IAIlB,MAAM,cACJ,eAAe,OAAO,IAAI,CAAC,GAAG,cAAc,CAAC,CAAC,KAAK;IACrD,IAAI,aACF,gBAAgB;SACX;KACL,gBAAgB,qBACd,YACA,UACA,eACA,sBACA,cACF;KACA,eAAe,IAAI,aAAa;IAClC;GACF;GACA,gBAAgB,IAAI,KAAK,MAAM,aAAa;GAC5C,QAAQ,IAAI,KAAK,MAAM,aAAa;EACtC,OAAO;GACL,IAAI;GACJ,IAAI,SAA0B,KAAK;GACnC,OAAO,QAAQ;IACb,IAAI,gBAAgB,IAAI,OAAO,IAAI,GAAG;KACpC,eAAe,gBAAgB,IAAI,OAAO,IAAI;KAC9C;IACF;IACA,SAAS,OAAO;GAClB;GAEA,IAAI,CAAC,cAAc;IACjB,IAAI,CAAC,eAAe;KAClB,gBAAgB,qBACd,YACA,UACA,eACA,sBACA,cACF;KACA,eAAe,IAAI,aAAa;IAClC;IACA,eAAe;GACjB;GAEA,gBAAgB,IAAI,KAAK,MAAM,YAAY;GAE3C,MAAM,WAAW,iBAAiB,IAAI;GACtC,MAAM,cAAc,WAAW,SAAS,KAAK,QAAQ,IAAI;GACzD,MAAM,SAAS,WAAW,YAAY,KAAK,QAAQ,IAAI;GACvD,QAAQ,IACN,KAAK,MACL,eAAe,SAAS,gBAAgB,aAC1C;EACF;CAEJ;CAEA,MAAM,0BAA0B,SAA2B;EACzD,IAAI,UAA2B;EAC/B,OAAO,SAAS;GACd,IAAI,gBAAgB,IAAI,QAAQ,IAAI,GAClC,OAAO,gBAAgB,IAAI,QAAQ,IAAI;GAEzC,UAAU,QAAQ;EACpB;EACA,OAAO,iBAAiB;CAC1B;CAEA,SAAS,KAAK;EACZ,WAAW,MAAM;GACf,IAAI,aAAa,IAAI,KAAK,IAAI,GAAG;GAEjC,wBACE,MACA,UACA,cACA,wBACA,kBACA,cACA,YACF;EACF;EACA,YAAY,MAAM;GAChB,IAAI,aAAa,IAAI,KAAK,IAAI,GAAG;GAEjC,wBACE,MACA,UACA,cACA,wBACA,kBACA,cACA,YACF;EACF;EACA,QAAQ,MAAM;GACZ,IAAI,aAAa,IAAI,KAAK,IAAI,GAAG;GAEjC,MAAM,OAAO,KAAK,KAAK;GAEvB,IAAI,cAAc,IAAI,GAAG;IACvB,MAAM,eAAe,uBAAuB,IAAI;IAChD,MAAM,MAAM,iBACV,KAAK,QAAQ,QAAQ,GAAG,CAAC,CAAC,KAAK,GAC/B,cACA,cACA,gBACF;IACA,aAAa,KAAK;KAAE;KAAM;KAAK,MAAM;KAAY;IAAa,CAAC;GACjE;EACF;EACA,aAAa,MAAM;GACjB,IAAI,aAAa,IAAI,KAAK,IAAI,GAAG;GAEjC,MAAM,OAAO,KAAK,KAAK,KAAK;GAE5B,IACE,OAAO,SAAS,YAChB,CAAC,sBAAsB,SAAS,IAAW,GAE3C;GACF,MAAM,QAAQ,KAAK,KAAK;GAExB,IAAI,EAAE,gBAAgB,KAAK,KAAK,cAAc,MAAM,KAAK,GAAG;IAC1D,MAAM,eAAe,uBAAuB,IAAI;IAChD,MAAM,MAAM,iBACV,MAAM,MAAM,KAAK,GACjB,cACA,cACA,gBACF;IACA,aAAa,KAAK;KAAE;KAAM;KAAK,MAAM;KAAiB;IAAa,CAAC;GACtE;EACF;EACA,cAAc,MAAM;GAClB,IAAI,aAAa,IAAI,KAAK,IAAI,GAAG;GAEjC,MAAM,OAAO,KAAK,KAAK;GAEvB,IAAI,CAAC,cAAc,IAAI,GAAG;GAE1B,MAAM,SAAS,KAAK;GAEpB,IACE,OAAO,oBAAoB,KAC3B,OAAO,kBAAkB,KACzB,OAAO,oBAAoB,GAE3B;GAEF,IAAI,OAAO,eAAe,GAAG;GAI7B,IAAI,OAAO,yBAAyB,GAAG;IACrC,MAAM,gBAAgB,OAAO;IAC7B,IAAI,eAAe,eAAe,GAAG;KACnC,MAAM,gBAAgB,cAAc,KAAK,KAAK;KAC9C,IACE,OAAO,kBAAkB,YACzB,CAAC,sBAAsB,SACrB,aACF,GAEA;IAEJ;GACF;GAIA,IACE,OAAO,mBAAmB,KAC1B,qBAAqB,IAAI,OAAO,KAAK,QAAQ,GAE7C;GAKF,IAAI,OAAO,aAAa,KAAK,OAAO,KAAK,SAAS,KAAK,MAAM;GAK7D,MAAM,iBAAiB,2BAA2B,IAAI;GACtD,IACE,mBACC,eAAe,iBACb,eAAe,eAAe,UAC7B,8BAA8B,IAAI,eAAe,UAAU,IAE/D;GAGF,IAAI,OAAO,iBAAiB,KAAK,OAAO,KAAK,QAAQ,KAAK,MAAM;GAIhE,IACE,OAAO,iBAAiB,KACxB,EAAE,aAAa,OAAO,KAAK,GAAG,KAC9B,eAAe,IAAI,OAAO,KAAK,IAAI,IAAI,GAEvC;GAGF,IAAI,OAAO,mBAAmB,KAAK,OAAO,KAAK,aAAa,KAAK,MAC/D;GAEF,MAAM,eAAe,uBAAuB,IAAI;GAChD,MAAM,MAAM,iBACV,KAAK,KAAK,GACV,cACA,cACA,gBACF;GACA,aAAa,KAAK;IAAE;IAAM;IAAK,MAAM;IAAkB;GAAa,CAAC;EACvE;EACA,gBAAgB,MAAM;GACpB,IAAI,aAAa,IAAI,KAAK,IAAI,GAAG;GAEjC,MAAM,EAAE,QAAQ,gBAAgB,KAAK;GAGrC,IAAI,iBAAiB;GACrB,MAAM,YAAsB,CAAC;GAC7B,IAAI,qBAAqB;GAEzB,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;IACtC,MAAM,OAAO,OAAO,EAAE,EAAE,MAAM,OAAO;IACrC,kBAAkB;IAClB,IAAI,KAAK,KAAK,CAAC,CAAC,SAAS,GAAG,qBAAqB;IAEjD,IAAI,IAAI,YAAY,QAAQ;KAC1B,MAAM,OAAO,YAAY;KACzB,IAAI,EAAE,aAAa,IAAI,GAAG;MACxB,kBAAkB,KAAK,KAAK,KAAK;MACjC,UAAU,KAAK,GAAG,KAAK,KAAK,IAAI,KAAK,MAAM;KAC7C,OAAO,IAAI,EAAE,mBAAmB,IAAI,GAAG;MACrC,MAAM,OAAO,SAAS,UAAU,KAAK,OAAQ,KAAK,GAAI;MACtD,MAAM,UAAU,EAAE,aAAa,KAAK,QAAQ,IACxC,KAAK,SAAS,OACd;MACJ,kBAAkB,KAAK,QAAQ;MAC/B,UAAU,KAAK,GAAG,QAAQ,IAAI,MAAM;KACtC,OAEE;IAEJ;GACF;GAEA,IAAI,CAAC,oBAAoB;GAEzB,MAAM,cAAc,eAAe,QAAQ,QAAQ,GAAG,CAAC,CAAC,KAAK;GAE7D,IAAI,CAAC,cAAc,WAAW,GAAG;GAEjC,MAAM,eAAe,uBAAuB,IAAI;GAChD,MAAM,MAAM,iBACV,aACA,cACA,cACA,gBACF;GAEA,MAAM,aAAa,MAAM,KAAK,IAAI,IAAI,SAAS,CAAC;GAEhD,aAAa,KAAK;IAChB;IACA;IACA,MAAM;IACN;IACA,WAAW;GACb,CAAC;EACH;CACF,CAAC;;CAGD,MAAM,uBACJ,aACA,kBACY;EACZ,IAAI,UAA2B,YAAY;EAC3C,OAAO,SAAS;GACd,IAAI,QAAQ,SAAS,eAAe,OAAO;GAC3C,UAAU,QAAQ;EACpB;EACA,OAAO;CACT;CAEA,MAAM,yCAAyB,IAAI,IAAc;CACjD,KAAK,MAAM,iBAAiB,gBAAgB;EAC1C,IAAI,cAAc,UAAU,GAAG;GAiB7B,IAd8B,aAAa,MAAM,gBAAgB;IAC/D,IAAI,UAA2B,YAAY;IAC3C,OAAO,SAAS;KACd,IAAI,QAAQ,SAAS,cAAc,MACjC,OAAO;KAET,IAAI,oBAAoB,IAAI,QAAQ,IAAI,GACtC,OAAO;KAET,UAAU,QAAQ;IACpB;IACA,OAAO;GACT,CAEwB,GACtB,uBAAuB,IAAI,aAAa;GAE1C;EACF;EAMA,IAJwB,aAAa,MAAM,gBACzC,oBAAoB,aAAa,cAAc,IAAI,CAGnC,GAAG;GACnB,MAAM,MAAM,gBAAgB,IAAI,cAAc,IAAI;GAClD,IAAI,sBAAsB;GAC1B,IAAI,cAA+B,cAAc;GACjD,OAAO,aAAa;IAClB,MAAM,eAAe,oBAAoB,IAAI,YAAY,IAAI;IAE7D,IAAI,gBAAgB,CAAC,aAAa,UAAU,GAG1C;SAFoB,gBAAgB,IAAI,aAAa,IAEvC,MAAM,KAAK;MACvB,MAAM,0BAA0B,aAAa,MAAM,gBACjD,oBAAoB,aAAa,aAAa,IAAI,CACpD;MACA,MAAM,eAAe,mBAAmB,IAAI,aAAa,IAAI;MAE7D,IAAI,2BAA2B,cAAc;OAC3C,sBAAsB;OACtB;MACF;KACF;;IAEF,cAAc,YAAY;GAC5B;GAEA,IAAI,CAAC,qBACH,uBAAuB,IAAI,aAAa;EAE5C;CACF;CAEA,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA,SAAS;CACX;AACF;;;;AAKA,MAAa,oBACX,KACA,UACA,cACA,eACA,UACA,uBAAgD,CAAC,MAI9C;CACH,MAAM,EAAE,kBAAkB,iBAAiB,iCACzC,KACA,UACA,cACA,WACA,eACA,UACA,oBACF;CAEA,MAAM,cAAsC,CAAC;CAC7C,KAAK,MAAM,SAAS,OAAO,OAAO,gBAAgB,GAChD,OAAO,OAAO,aAAa,KAAK;CAGlC,OAAO;EAAE,kBAAkB;EAAa;CAAa;AACvD"}
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 {\n ATTRIBUTES_TO_EXTRACT,\n getExtractionIgnoreDirectives,\n shouldExtract,\n} from '@intlayer/config/extract';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { resolveDictionaryKey } from '../extractContent/utils';\nimport {\n type ExistingIntlayerInfo,\n getComponentName,\n getExistingIntlayerInfo,\n getOrGenerateKey,\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 | 'template-literal';\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 * Method names whose string arguments are technical operands — lookups,\n * comparisons, DOM queries, storage keys, event names — rather than translatable\n * display text. A string literal passed to one of these (e.g.\n * `message.includes('Loading chunk')`) is code, not content, and is skipped.\n */\n/**\n * Binary operators whose string operands are code constants / enum values\n * (e.g. `msg.step === 'ERROR'`) rather than translatable display text.\n */\nconst COMPARISON_OPERATORS = new Set(['===', '!==', '==', '!=']);\n\n/**\n * Object property names whose string values are technical/non-translatable\n * (e.g. `icon: 'Globe'`, `variant: 'primary'`). String values under other\n * property names (e.g. `label: 'Language'`) are still extracted.\n */\nconst TECHNICAL_KEYS = new Set<string>([\n 'icon',\n 'className',\n 'class',\n 'id',\n 'type',\n 'variant',\n 'color',\n 'theme',\n 'size',\n 'align',\n 'placement',\n 'target',\n 'rel',\n 'method',\n 'mode',\n 'direction',\n 'orientation',\n 'scope',\n 'role',\n 'lang',\n 'locale',\n 'href',\n 'src',\n 'width',\n 'height',\n 'as',\n 'to',\n 'key',\n 'value',\n 'defaultValue',\n 'prop',\n 'property',\n 'state',\n 'action',\n 'event',\n 'handler',\n 'callback',\n 'url',\n 'uri',\n 'path',\n 'route',\n 'slug',\n 'endpoint',\n 'headers',\n 'contentType',\n 'toolName',\n 'toolDescription',\n 'toolParamDescription',\n]);\n\nconst NON_TRANSLATABLE_CALL_METHODS = new Set<string>([\n 'includes',\n 'startsWith',\n 'endsWith',\n 'indexOf',\n 'lastIndexOf',\n 'match',\n 'matchAll',\n 'search',\n 'localeCompare',\n 'split',\n 'getAttribute',\n 'setAttribute',\n 'hasAttribute',\n 'removeAttribute',\n 'querySelector',\n 'querySelectorAll',\n 'getElementById',\n 'getElementsByClassName',\n 'getElementsByTagName',\n 'addEventListener',\n 'removeEventListener',\n 'dispatchEvent',\n 'matchMedia',\n 'getItem',\n 'setItem',\n 'removeItem',\n 'getPropertyValue',\n 'setProperty',\n]);\n\n/**\n * When `path` is a string-literal argument of a member/optional-member call\n * expression (e.g. `foo.includes('bar')` or `foo?.bar?.includes('baz')`),\n * returns the called method name and whether the call targets the `console`\n * object. Returns `undefined` when the parent is not such a call, or the string\n * is not one of its arguments.\n */\nconst getEnclosingMethodCallInfo = (\n path: NodePath<t.StringLiteral>\n): { methodName: string | undefined; isConsoleCall: boolean } | undefined => {\n const parent = path.parentPath;\n if (!parent.isCallExpression() && !parent.isOptionalCallExpression()) {\n return undefined;\n }\n\n const callNode = parent.node as t.CallExpression | t.OptionalCallExpression;\n if (!callNode.arguments.includes(path.node)) return undefined;\n\n const callee = callNode.callee;\n if (!t.isMemberExpression(callee) && !t.isOptionalMemberExpression(callee)) {\n return undefined;\n }\n\n const methodName = t.isIdentifier(callee.property)\n ? callee.property.name\n : undefined;\n const isConsoleCall =\n t.isIdentifier(callee.object) && callee.object.name === 'console';\n\n return { methodName, isConsoleCall };\n};\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 if (t.isTemplateLiteral(expr)) {\n for (let i = 0; i < expr.quasis.length; i++) {\n parts.push({\n type: 'text',\n value: expr.quasis[i]?.value.raw ?? '',\n });\n if (i < expr.expressions.length) {\n const subExpr = expr.expressions[i];\n if (t.isIdentifier(subExpr)) {\n parts.push({\n type: 'var',\n value: subExpr.name,\n originalExpr: subExpr.name,\n });\n hasVariables = true;\n } else if (t.isMemberExpression(subExpr)) {\n const code = fileCode.substring(subExpr.start!, subExpr.end!);\n const varName = t.isIdentifier(subExpr.property)\n ? subExpr.property.name\n : 'var';\n parts.push({ type: 'var', value: varName, originalExpr: code });\n hasVariables = true;\n } else {\n return false;\n }\n }\n }\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 /** Cached `getExistingIntlayerInfo` result per component node, so callers do not re-traverse. */\n existingInfoByNode: Map<t.Node, ExistingIntlayerInfo | undefined>;\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 // `getExistingIntlayerInfo` traverses the component body, so resolve it a\n // single time per component and share the cache with every later phase\n // (including the caller, via the returned `existingInfoByNode`).\n const existingInfoByNode = new Map<\n t.Node,\n ExistingIntlayerInfo | undefined\n >();\n const componentNodeToPath = new Map<t.Node, NodePath>();\n for (const path of componentPaths) {\n existingInfoByNode.set(path.node, getExistingIntlayerInfo(path));\n componentNodeToPath.set(path.node, path);\n }\n\n // Pre-scan non-Program paths to collect their existing dictionary keys before the\n // Program scope is assigned. Without this, Program is processed first (depth-first\n // traversal) and creates a new file-path-derived key even when a child component\n // already declares a specific dictionary (e.g. useIntlayer('dashboard-sidebar')).\n for (const path of componentPaths) {\n if (path.isProgram()) continue;\n const existingInfo = existingInfoByNode.get(path.node);\n if (existingInfo) {\n usedKeysInFile.add(existingInfo.key);\n }\n }\n\n for (const path of componentPaths) {\n const existingInfo = existingInfoByNode.get(path.node);\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 // Reuse the dominant existing key from child components so that\n // module-level strings join the same dictionary rather than\n // creating a new file-path-derived one (e.g. 'route').\n const dominantKey =\n usedKeysInFile.size > 0 ? [...usedKeysInFile][0] : undefined;\n if (dominantKey) {\n globalFileKey = dominantKey;\n } else {\n globalFileKey = resolveDictionaryKey(\n defaultKey,\n filePath,\n configuration,\n unmergedDictionaries,\n usedKeysInFile\n );\n usedKeysInFile.add(globalFileKey);\n }\n }\n componentKeyMap.set(path.node, globalFileKey);\n hookMap.set(path.node, 'getIntlayer');\n } else {\n let inheritedKey: string | undefined;\n let parent: NodePath | null = path.parentPath;\n while (parent) {\n if (componentKeyMap.has(parent.node)) {\n inheritedKey = componentKeyMap.get(parent.node);\n break;\n }\n parent = parent.parentPath;\n }\n\n if (!inheritedKey) {\n if (!globalFileKey) {\n globalFileKey = resolveDictionaryKey(\n defaultKey,\n filePath,\n configuration,\n unmergedDictionaries,\n usedKeysInFile\n );\n usedKeysInFile.add(globalFileKey);\n }\n inheritedKey = globalFileKey;\n }\n\n componentKeyMap.set(path.node, inheritedKey);\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 const ignoreDirectives = getExtractionIgnoreDirectives(fileCode);\n\n /**\n * Start offset of the JSX element a string directly belongs to: its text,\n * a `{'literal'}` child, or one of its attributes.\n */\n const getEnclosingJsxElementStart = (path: NodePath): number | undefined => {\n let current: NodePath | null = path.parentPath;\n\n if (current?.isJSXExpressionContainer()) current = current.parentPath;\n if (current?.isJSXAttribute()) current = current.parentPath;\n if (current?.isJSXOpeningElement()) current = current.parentPath;\n\n return current?.isJSXElement() || current?.isJSXFragment()\n ? (current.node.start ?? undefined)\n : undefined;\n };\n\n /**\n * Whether an `intlayer-ignore-line` / `intlayer-ignore-next-line` directive\n * covers the string: on the line where its text starts, or on the opening\n * tag of its JSX element.\n */\n const isIgnoredPath = (path: NodePath): boolean => {\n const { node } = path;\n\n if (typeof node.start === 'number') {\n const text = t.isJSXText(node) ? node.value : '';\n const leadingWhitespaceLength = text.length - text.trimStart().length;\n\n if (\n ignoreDirectives.isOffsetIgnored(node.start + leadingWhitespaceLength)\n )\n return true;\n }\n\n return ignoreDirectives.isOffsetIgnored(getEnclosingJsxElementStart(path));\n };\n\n traverse(ast, {\n JSXElement(path) {\n if (handledNodes.has(path.node)) return;\n if (isIgnoredPath(path)) 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 if (isIgnoredPath(path)) 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 if (isIgnoredPath(path)) 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 if (isIgnoredPath(path)) 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 if (isIgnoredPath(path)) 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 // A literal wrapped in braces (`title={\"Hello\"}`) follows the same\n // attribute allowlist as the plain form handled by `JSXAttribute`.\n if (parent.isJSXExpressionContainer()) {\n const attributePath = parent.parentPath;\n if (attributePath?.isJSXAttribute()) {\n const attributeName = attributePath.node.name.name;\n if (\n typeof attributeName !== 'string' ||\n !ATTRIBUTES_TO_EXTRACT.includes(\n attributeName as (typeof ATTRIBUTES_TO_EXTRACT)[number]\n )\n ) {\n return;\n }\n }\n }\n\n // Skip string literals used as comparison operands (e.g. `msg.step === 'ERROR'`).\n // These are code constants / enum values, not translatable display text.\n if (\n parent.isBinaryExpression() &&\n COMPARISON_OPERATORS.has(parent.node.operator)\n ) {\n return;\n }\n\n // Skip string literals used as `switch` case tests (e.g. `case 'ERROR':`).\n // Like comparisons, these are code constants, not display text.\n if (parent.isSwitchCase() && parent.node.test === path.node) return;\n\n // Skip string literals passed to technical method calls, e.g.\n // `message.includes('Loading chunk')`, `el.getAttribute('data-id')`, or\n // `console.log('...')`. These are code operands, not display text.\n const methodCallInfo = getEnclosingMethodCallInfo(path);\n if (\n methodCallInfo &&\n (methodCallInfo.isConsoleCall ||\n (methodCallInfo.methodName !== undefined &&\n NON_TRANSLATABLE_CALL_METHODS.has(methodCallInfo.methodName)))\n ) {\n return;\n }\n\n if (parent.isObjectProperty() && parent.node.key === path.node) return;\n\n // Skip string values in known technical/non-translatable object properties (e.g. `icon: 'Globe'`).\n // String values in translatable object properties (e.g. `label: 'Language'`) are still extracted.\n if (\n parent.isObjectProperty() &&\n t.isIdentifier(parent.node.key) &&\n TECHNICAL_KEYS.has(parent.node.key.name)\n ) {\n return;\n }\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 TemplateLiteral(path) {\n if (handledNodes.has(path.node)) return;\n if (isIgnoredPath(path)) return;\n\n const { quasis, expressions } = path.node;\n\n // Build the combined string with placeholders\n let combinedString = '';\n const variables: string[] = [];\n let hasSignificantText = false;\n\n for (let i = 0; i < quasis.length; i++) {\n const text = quasis[i]?.value.raw ?? '';\n combinedString += text;\n if (text.trim().length > 0) hasSignificantText = true;\n\n if (i < expressions.length) {\n const expr = expressions[i];\n if (t.isIdentifier(expr)) {\n combinedString += `{{${expr.name}}}`;\n variables.push(`${expr.name}: ${expr.name}`);\n } else if (t.isMemberExpression(expr)) {\n const code = fileCode.substring(expr.start!, expr.end!);\n const varName = t.isIdentifier(expr.property)\n ? expr.property.name\n : 'var';\n combinedString += `{{${varName}}}`;\n variables.push(`${varName}: ${code}`);\n } else {\n // Complex expression in template literal, skip\n return;\n }\n }\n }\n\n if (!hasSignificantText) return;\n\n const cleanString = combinedString.replace(/\\s+/g, ' ').trim();\n\n if (!shouldExtract(cleanString)) return;\n\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n cleanString,\n componentKey,\n existingKeys,\n extractedContent\n );\n\n const uniqueVars = Array.from(new Set(variables));\n\n replacements.push({\n path,\n key,\n type: 'template-literal',\n componentKey,\n variables: uniqueVars,\n });\n },\n });\n\n /** Returns true when `componentNode` encloses the replacement's path. */\n const replacementIsInside = (\n replacement: BabelReplacement,\n componentNode: t.Node\n ): boolean => {\n let current: NodePath | null = replacement.path;\n while (current) {\n if (current.node === componentNode) return true;\n current = current.parentPath;\n }\n return false;\n };\n\n const componentsNeedingHooks = new Set<NodePath>();\n for (const componentPath of componentPaths) {\n if (componentPath.isProgram()) {\n // Only count replacements that belong to the Program scope itself, not\n // to a nested component (which provides its own hook).\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 if (componentNodeToPath.has(current.node)) {\n return false; // reached an enclosing component first\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 replacementIsInside(replacement, componentPath.node)\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 = componentNodeToPath.get(currentPath.node);\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 replacementIsInside(replacement, ancestorPath.node)\n );\n const existingInfo = existingInfoByNode.get(ancestorPath.node);\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 existingInfoByNode,\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,4 +1,5 @@
1
1
  import { resolveContentFilePaths } from "./utils/extractDictionaryInfo.mjs";
2
+ import { getComponentFilePath } from "./utils/resolveDictionaryKey.mjs";
2
3
  import { existsSync } from "node:fs";
3
4
  import { mkdir } from "node:fs/promises";
4
5
  import { dirname, relative } from "node:path";
@@ -98,6 +99,7 @@ const writeContentHelper = async (extractedContent, dictionaryKey, filePath, con
98
99
  console.error(error);
99
100
  }
100
101
  const relativeContentFilePath = relative(baseDir, absolutePath);
102
+ const componentFilePath = getComponentFilePath(filePath, configuration);
101
103
  let mergedDictionary;
102
104
  if (isPerLocale) {
103
105
  const mergedContent = mergeWithExistingPerLocaleDictionary(extractedContent, existingDictionary);
@@ -106,7 +108,8 @@ const writeContentHelper = async (extractedContent, dictionaryKey, filePath, con
106
108
  key: dictionaryKey,
107
109
  content: mergedContent,
108
110
  locale: defaultLocale,
109
- filePath: relativeContentFilePath
111
+ filePath: relativeContentFilePath,
112
+ componentFilePath
110
113
  };
111
114
  } else {
112
115
  const mergedContent = mergeWithExistingMultilingualDictionary(extractedContent, existingDictionary, defaultLocale);
@@ -114,7 +117,8 @@ const writeContentHelper = async (extractedContent, dictionaryKey, filePath, con
114
117
  ...existingDictionary,
115
118
  key: dictionaryKey,
116
119
  content: mergedContent,
117
- filePath: relativeContentFilePath
120
+ filePath: relativeContentFilePath,
121
+ componentFilePath
118
122
  };
119
123
  }
120
124
  const relativeDir = relative(baseDir, outputDir);
@@ -1 +1 @@
1
- {"version":3,"file":"contentWriter.mjs","names":[],"sources":["../../../src/extractContent/contentWriter.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { mkdir } from 'node:fs/promises';\nimport { dirname, relative } from 'node:path';\nimport { insertContentInDictionary } from '@intlayer/core/plugins';\nimport {\n buildDictionary,\n ensureIntlayerBundle,\n loadContentDeclaration,\n writeContentDeclaration,\n} from '@intlayer/engine/build';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Dictionary, DictionaryKey } from '@intlayer/types/dictionary';\nimport * as NodeTypes from '@intlayer/types/nodeType';\nimport { resolveContentFilePaths } from './utils/extractDictionaryInfo';\n\nconst hasInsertionVars = (str: string): boolean => /\\{\\{[^}]+\\}\\}/.test(str);\n\nconst getInsertionFields = (template: string): string[] => {\n const fields = new Set<string>();\n const regex = /\\{\\{([^}]+)\\}\\}/g;\n for (\n let match = regex.exec(template);\n match !== null;\n match = regex.exec(template)\n ) {\n const field = match[1]?.trim();\n if (field) fields.add(field);\n }\n return [...fields];\n};\n\n/**\n * Translation node structure used in dictionaries\n */\ntype TranslationNode = {\n nodeType: 'translation';\n translation: Record<string, string>;\n};\n\n/**\n * Dictionary content structure - map of keys to translation nodes\n */\ntype DictionaryContentMap = Record<string, TranslationNode>;\n\n/**\n * Cached bundle file path to optimize performance\n */\nlet cachedBundleFilePath: string | undefined;\n\n/**\n * Merge extracted content with existing dictionary for multilingual format.\n * - Keys in extracted but not in existing: added with default locale only\n * - Keys in both: preserve existing translations, update default locale value\n * - Keys in existing but not in extracted: removed (no longer in source)\n */\nexport const mergeWithExistingMultilingualDictionary = (\n extractedContent: Record<string, string>,\n existingDictionary: Dictionary | null,\n defaultLocale: string\n): DictionaryContentMap => {\n const dictionary: Dictionary =\n existingDictionary ??\n ({\n key: '',\n content: {},\n filePath: '',\n } as Dictionary);\n\n const mergedDictionary = insertContentInDictionary(\n dictionary,\n extractedContent,\n defaultLocale as Locale\n );\n\n const mergedContent = mergedDictionary.content as DictionaryContentMap;\n\n // Pruning: remove keys not in extractedContent\n const finalContent: DictionaryContentMap = {};\n for (const key in extractedContent) {\n const contentNode = mergedContent[key];\n if (contentNode) {\n finalContent[key] = contentNode;\n }\n }\n\n // Promote any key whose source text contains {{vars}} to an insertion node\n for (const key in extractedContent) {\n const rawValue = extractedContent[key];\n if (typeof rawValue === 'string' && hasInsertionVars(rawValue)) {\n const node = finalContent[key] as any;\n if (node && node.nodeType === NodeTypes.TRANSLATION) {\n (finalContent as any)[key] = {\n nodeType: NodeTypes.INSERTION,\n [NodeTypes.INSERTION]: node,\n fields: getInsertionFields(rawValue),\n };\n }\n }\n }\n\n return finalContent;\n};\n\n/**\n * Merge extracted content with existing dictionary for per-locale format.\n * - Keys in extracted but not in existing: added\n * - Keys in both: update value\n * - Keys in existing but not in extracted: removed (no longer in source)\n */\nexport const mergeWithExistingPerLocaleDictionary = (\n extractedContent: Record<string, string>,\n existingDictionary: Dictionary | null\n): Record<string, string> => {\n const dictionary: Dictionary =\n existingDictionary ??\n ({\n key: '',\n content: {},\n filePath: '',\n } as Dictionary);\n\n const mergedDictionary = insertContentInDictionary(\n dictionary,\n extractedContent\n );\n\n const mergedContent = mergedDictionary.content as Record<string, string>;\n\n // Pruning: remove keys not in extractedContent\n const finalContent: Record<string, string> = {};\n for (const key in extractedContent) {\n const contentStr = mergedContent[key];\n if (contentStr) {\n finalContent[key] = contentStr;\n }\n }\n\n // Promote any key whose source text contains {{vars}} to an insertion node\n for (const key in extractedContent) {\n const rawValue = extractedContent[key];\n if (typeof rawValue === 'string' && hasInsertionVars(rawValue)) {\n const currentVal = finalContent[key];\n if (typeof currentVal === 'string') {\n (finalContent as any)[key] = {\n nodeType: NodeTypes.INSERTION,\n [NodeTypes.INSERTION]: currentVal,\n fields: getInsertionFields(rawValue),\n };\n }\n }\n }\n\n return finalContent;\n};\n\n/**\n * Helper to write extracted content to dictionary file(s).\n */\nexport const writeContentHelper = async (\n extractedContent: Record<string, string>,\n dictionaryKey: DictionaryKey,\n filePath: string,\n configuration: IntlayerConfig\n): Promise<string> => {\n const { absolutePath, isPerLocale } = await resolveContentFilePaths(\n filePath,\n dictionaryKey,\n configuration\n );\n\n const { defaultLocale } = configuration.internationalization;\n const { baseDir } = configuration.system;\n\n if (!cachedBundleFilePath) {\n cachedBundleFilePath = await ensureIntlayerBundle(configuration);\n }\n\n const outputDir = dirname(absolutePath);\n\n // Ensure output directory exists\n await mkdir(outputDir, { recursive: true });\n\n // Read existing dictionary to preserve translations and metadata\n let existingDictionary: Dictionary | null = null;\n\n if (existsSync(absolutePath)) {\n try {\n const dictionary = await loadContentDeclaration(\n absolutePath,\n configuration,\n cachedBundleFilePath\n );\n\n existingDictionary = dictionary ?? null;\n } catch (error) {\n console.error(error);\n }\n }\n\n const relativeContentFilePath = relative(baseDir, absolutePath);\n\n let mergedDictionary: Dictionary;\n\n if (isPerLocale) {\n // Per-locale format: simple string content for a single locale\n const mergedContent = mergeWithExistingPerLocaleDictionary(\n extractedContent,\n existingDictionary\n );\n\n mergedDictionary = {\n // Preserve existing metadata\n ...existingDictionary,\n key: dictionaryKey,\n content: mergedContent,\n locale: defaultLocale,\n filePath: relativeContentFilePath,\n };\n } else {\n // Multilingual format: content wrapped in translation nodes for multiple locales\n const mergedContent = mergeWithExistingMultilingualDictionary(\n extractedContent,\n existingDictionary,\n defaultLocale\n );\n\n mergedDictionary = {\n // Preserve existing metadata\n ...existingDictionary,\n key: dictionaryKey,\n content: mergedContent,\n filePath: relativeContentFilePath,\n };\n }\n\n const relativeDir = relative(baseDir, outputDir);\n\n const writeResult = await writeContentDeclaration(\n mergedDictionary,\n configuration,\n {\n newDictionariesPath: relativeDir,\n localeList: [defaultLocale],\n }\n );\n\n // Build the dictionary immediately\n const dictionaryToBuild: Dictionary = {\n ...mergedDictionary,\n filePath: relative(baseDir, writeResult?.path ?? absolutePath),\n };\n\n await buildDictionary([dictionaryToBuild], configuration);\n\n return absolutePath;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"contentWriter.mjs","names":[],"sources":["../../../src/extractContent/contentWriter.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { mkdir } from 'node:fs/promises';\nimport { dirname, relative } from 'node:path';\nimport { insertContentInDictionary } from '@intlayer/core/plugins';\nimport {\n buildDictionary,\n ensureIntlayerBundle,\n loadContentDeclaration,\n writeContentDeclaration,\n} from '@intlayer/engine/build';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Dictionary, DictionaryKey } from '@intlayer/types/dictionary';\nimport * as NodeTypes from '@intlayer/types/nodeType';\nimport { resolveContentFilePaths } from './utils/extractDictionaryInfo';\nimport { getComponentFilePath } from './utils/resolveDictionaryKey';\n\nconst hasInsertionVars = (str: string): boolean => /\\{\\{[^}]+\\}\\}/.test(str);\n\nconst getInsertionFields = (template: string): string[] => {\n const fields = new Set<string>();\n const regex = /\\{\\{([^}]+)\\}\\}/g;\n for (\n let match = regex.exec(template);\n match !== null;\n match = regex.exec(template)\n ) {\n const field = match[1]?.trim();\n if (field) fields.add(field);\n }\n return [...fields];\n};\n\n/**\n * Translation node structure used in dictionaries\n */\ntype TranslationNode = {\n nodeType: 'translation';\n translation: Record<string, string>;\n};\n\n/**\n * Dictionary content structure - map of keys to translation nodes\n */\ntype DictionaryContentMap = Record<string, TranslationNode>;\n\n/**\n * Cached bundle file path to optimize performance\n */\nlet cachedBundleFilePath: string | undefined;\n\n/**\n * Merge extracted content with existing dictionary for multilingual format.\n * - Keys in extracted but not in existing: added with default locale only\n * - Keys in both: preserve existing translations, update default locale value\n * - Keys in existing but not in extracted: removed (no longer in source)\n */\nexport const mergeWithExistingMultilingualDictionary = (\n extractedContent: Record<string, string>,\n existingDictionary: Dictionary | null,\n defaultLocale: string\n): DictionaryContentMap => {\n const dictionary: Dictionary =\n existingDictionary ??\n ({\n key: '',\n content: {},\n filePath: '',\n } as Dictionary);\n\n const mergedDictionary = insertContentInDictionary(\n dictionary,\n extractedContent,\n defaultLocale as Locale\n );\n\n const mergedContent = mergedDictionary.content as DictionaryContentMap;\n\n // Pruning: remove keys not in extractedContent\n const finalContent: DictionaryContentMap = {};\n for (const key in extractedContent) {\n const contentNode = mergedContent[key];\n if (contentNode) {\n finalContent[key] = contentNode;\n }\n }\n\n // Promote any key whose source text contains {{vars}} to an insertion node\n for (const key in extractedContent) {\n const rawValue = extractedContent[key];\n if (typeof rawValue === 'string' && hasInsertionVars(rawValue)) {\n const node = finalContent[key] as any;\n if (node && node.nodeType === NodeTypes.TRANSLATION) {\n (finalContent as any)[key] = {\n nodeType: NodeTypes.INSERTION,\n [NodeTypes.INSERTION]: node,\n fields: getInsertionFields(rawValue),\n };\n }\n }\n }\n\n return finalContent;\n};\n\n/**\n * Merge extracted content with existing dictionary for per-locale format.\n * - Keys in extracted but not in existing: added\n * - Keys in both: update value\n * - Keys in existing but not in extracted: removed (no longer in source)\n */\nexport const mergeWithExistingPerLocaleDictionary = (\n extractedContent: Record<string, string>,\n existingDictionary: Dictionary | null\n): Record<string, string> => {\n const dictionary: Dictionary =\n existingDictionary ??\n ({\n key: '',\n content: {},\n filePath: '',\n } as Dictionary);\n\n const mergedDictionary = insertContentInDictionary(\n dictionary,\n extractedContent\n );\n\n const mergedContent = mergedDictionary.content as Record<string, string>;\n\n // Pruning: remove keys not in extractedContent\n const finalContent: Record<string, string> = {};\n for (const key in extractedContent) {\n const contentStr = mergedContent[key];\n if (contentStr) {\n finalContent[key] = contentStr;\n }\n }\n\n // Promote any key whose source text contains {{vars}} to an insertion node\n for (const key in extractedContent) {\n const rawValue = extractedContent[key];\n if (typeof rawValue === 'string' && hasInsertionVars(rawValue)) {\n const currentVal = finalContent[key];\n if (typeof currentVal === 'string') {\n (finalContent as any)[key] = {\n nodeType: NodeTypes.INSERTION,\n [NodeTypes.INSERTION]: currentVal,\n fields: getInsertionFields(rawValue),\n };\n }\n }\n }\n\n return finalContent;\n};\n\n/**\n * Helper to write extracted content to dictionary file(s).\n */\nexport const writeContentHelper = async (\n extractedContent: Record<string, string>,\n dictionaryKey: DictionaryKey,\n filePath: string,\n configuration: IntlayerConfig\n): Promise<string> => {\n const { absolutePath, isPerLocale } = await resolveContentFilePaths(\n filePath,\n dictionaryKey,\n configuration\n );\n\n const { defaultLocale } = configuration.internationalization;\n const { baseDir } = configuration.system;\n\n if (!cachedBundleFilePath) {\n cachedBundleFilePath = await ensureIntlayerBundle(configuration);\n }\n\n const outputDir = dirname(absolutePath);\n\n // Ensure output directory exists\n await mkdir(outputDir, { recursive: true });\n\n // Read existing dictionary to preserve translations and metadata\n let existingDictionary: Dictionary | null = null;\n\n if (existsSync(absolutePath)) {\n try {\n const dictionary = await loadContentDeclaration(\n absolutePath,\n configuration,\n cachedBundleFilePath\n );\n\n existingDictionary = dictionary ?? null;\n } catch (error) {\n console.error(error);\n }\n }\n\n const relativeContentFilePath = relative(baseDir, absolutePath);\n const componentFilePath = getComponentFilePath(filePath, configuration);\n\n let mergedDictionary: Dictionary;\n\n if (isPerLocale) {\n // Per-locale format: simple string content for a single locale\n const mergedContent = mergeWithExistingPerLocaleDictionary(\n extractedContent,\n existingDictionary\n );\n\n mergedDictionary = {\n // Preserve existing metadata\n ...existingDictionary,\n key: dictionaryKey,\n content: mergedContent,\n locale: defaultLocale,\n filePath: relativeContentFilePath,\n componentFilePath,\n };\n } else {\n // Multilingual format: content wrapped in translation nodes for multiple locales\n const mergedContent = mergeWithExistingMultilingualDictionary(\n extractedContent,\n existingDictionary,\n defaultLocale\n );\n\n mergedDictionary = {\n // Preserve existing metadata\n ...existingDictionary,\n key: dictionaryKey,\n content: mergedContent,\n filePath: relativeContentFilePath,\n componentFilePath,\n };\n }\n\n const relativeDir = relative(baseDir, outputDir);\n\n const writeResult = await writeContentDeclaration(\n mergedDictionary,\n configuration,\n {\n newDictionariesPath: relativeDir,\n localeList: [defaultLocale],\n }\n );\n\n // Build the dictionary immediately\n const dictionaryToBuild: Dictionary = {\n ...mergedDictionary,\n filePath: relative(baseDir, writeResult?.path ?? absolutePath),\n };\n\n await buildDictionary([dictionaryToBuild], configuration);\n\n return absolutePath;\n};\n"],"mappings":"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"}
@@ -12,7 +12,7 @@ import { getProjectRequire } from "@intlayer/config/utils";
12
12
  import { getUnmergedDictionaries } from "@intlayer/dictionaries-entry/unmerged";
13
13
  import { detectFormatCommand } from "@intlayer/engine/cli";
14
14
  import { execSync } from "node:child_process";
15
- import { ATTRIBUTES_TO_EXTRACT, shouldExtract } from "@intlayer/config/extract";
15
+ import { ATTRIBUTES_TO_EXTRACT, getExtractionIgnoreDirectives, shouldExtract } from "@intlayer/config/extract";
16
16
 
17
17
  // src/extractContent/extractContent.ts
18
18
  let vueCompiler = null;
@@ -26,6 +26,7 @@ const formatCompilerResult = (componentKey, res) => {
26
26
  };
27
27
  const processFileInternal = (filePath, packageName, options, dependencies, saveComponent, providedComponentKey) => {
28
28
  const fileText = options?.code ?? readFileSync(filePath, "utf-8");
29
+ if (getExtractionIgnoreDirectives(fileText).isFileIgnored) return void 0;
29
30
  const componentKey = providedComponentKey ?? extractDictionaryKey(filePath, fileText, dependencies.configuration.compiler.dictionaryKeyPrefix);
30
31
  const ext = extname(filePath);
31
32
  const { vueCompiler, svelteCompiler, unmergedDictionaries, configuration } = dependencies;
@@ -1 +1 @@
1
- {"version":3,"file":"extractContent.mjs","names":[],"sources":["../../../src/extractContent/extractContent.ts"],"sourcesContent":["import { execSync } from 'node:child_process';\nimport { readFileSync, writeFileSync } from 'node:fs';\nimport { extname, relative } from 'node:path';\nimport type * as t from '@babel/types';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { ATTRIBUTES_TO_EXTRACT, shouldExtract } from '@intlayer/config/extract';\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 { getUnmergedDictionaries } from '@intlayer/dictionaries-entry/unmerged';\nimport { detectFormatCommand } from '@intlayer/engine/cli';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { extractTsContent } from './babelProcessor';\nimport { writeContentHelper } from './contentWriter';\nimport { processTsxFile } from './processTsxFile';\nimport { extractDictionaryKeyFromPath, type PackageName } 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 fileText\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 fileText\n );\n\n if (res) {\n return formatCompilerResult(componentKey, res);\n }\n }\n\n if (ext === '.astro') {\n const frontmatterMatch = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---/.exec(fileText);\n let fakeTsxCode = fileText;\n let hasFrontmatter = false;\n\n if (frontmatterMatch) {\n hasFrontmatter = true;\n const matchStart = frontmatterMatch.index;\n const matchEnd = matchStart + frontmatterMatch[0].length;\n\n const before = fileText.substring(0, matchStart);\n const matchText = frontmatterMatch[0];\n const after = fileText.substring(matchEnd);\n\n const newMatchText = matchText\n .replace(/^---/, '///')\n .replace(/---$/, '///');\n fakeTsxCode = before + newMatchText + after;\n } else {\n // Add empty frontmatter with a dummy statement so imports get injected properly\n fakeTsxCode = `///\\n;\\n///\\n${fileText}`;\n }\n\n const result = processTsxFile(\n filePath,\n componentKey,\n packageName,\n configuration,\n false, // don't let processTsxFile write — we reconstruct the full file below\n unmergedDictionaries,\n fakeTsxCode\n );\n\n if (!result) return undefined;\n\n let modifiedFullCode = result.modifiedCode\n .replace(/^\\s*\\/\\/\\//, '---')\n .replace(/\\n\\/\\/\\//, '\\n---');\n\n if (!hasFrontmatter) {\n modifiedFullCode = modifiedFullCode.replace(/\\n;\\n---\\n/, '\\n---\\n');\n }\n\n if (saveComponent) {\n writeFileSync(filePath, modifiedFullCode);\n\n const formatCommand = detectFormatCommand(configuration);\n if (formatCommand) {\n try {\n execSync(formatCommand.replace('{{file}}', filePath), {\n stdio: 'inherit',\n cwd: dependencies.configuration.system.baseDir,\n });\n } catch (error) {\n console.error(error);\n }\n }\n }\n\n return {\n extractedContentMap: result.extractedContent,\n transformedCode: modifiedFullCode,\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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"}
1
+ {"version":3,"file":"extractContent.mjs","names":[],"sources":["../../../src/extractContent/extractContent.ts"],"sourcesContent":["import { execSync } from 'node:child_process';\nimport { readFileSync, writeFileSync } from 'node:fs';\nimport { extname, relative } from 'node:path';\nimport type * as t from '@babel/types';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport {\n ATTRIBUTES_TO_EXTRACT,\n getExtractionIgnoreDirectives,\n shouldExtract,\n} from '@intlayer/config/extract';\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 { getUnmergedDictionaries } from '@intlayer/dictionaries-entry/unmerged';\nimport { detectFormatCommand } from '@intlayer/engine/cli';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { extractTsContent } from './babelProcessor';\nimport { writeContentHelper } from './contentWriter';\nimport { processTsxFile } from './processTsxFile';\nimport { extractDictionaryKeyFromPath, type PackageName } 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\n // `// intlayer-ignore-file` keeps the whole file out of extraction.\n if (getExtractionIgnoreDirectives(fileText).isFileIgnored) return undefined;\n\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 fileText\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 fileText\n );\n\n if (res) {\n return formatCompilerResult(componentKey, res);\n }\n }\n\n if (ext === '.astro') {\n const frontmatterMatch = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---/.exec(fileText);\n let fakeTsxCode = fileText;\n let hasFrontmatter = false;\n\n if (frontmatterMatch) {\n hasFrontmatter = true;\n const matchStart = frontmatterMatch.index;\n const matchEnd = matchStart + frontmatterMatch[0].length;\n\n const before = fileText.substring(0, matchStart);\n const matchText = frontmatterMatch[0];\n const after = fileText.substring(matchEnd);\n\n const newMatchText = matchText\n .replace(/^---/, '///')\n .replace(/---$/, '///');\n fakeTsxCode = before + newMatchText + after;\n } else {\n // Add empty frontmatter with a dummy statement so imports get injected properly\n fakeTsxCode = `///\\n;\\n///\\n${fileText}`;\n }\n\n const result = processTsxFile(\n filePath,\n componentKey,\n packageName,\n configuration,\n false, // don't let processTsxFile write — we reconstruct the full file below\n unmergedDictionaries,\n fakeTsxCode\n );\n\n if (!result) return undefined;\n\n let modifiedFullCode = result.modifiedCode\n .replace(/^\\s*\\/\\/\\//, '---')\n .replace(/\\n\\/\\/\\//, '\\n---');\n\n if (!hasFrontmatter) {\n modifiedFullCode = modifiedFullCode.replace(/\\n;\\n---\\n/, '\\n---\\n');\n }\n\n if (saveComponent) {\n writeFileSync(filePath, modifiedFullCode);\n\n const formatCommand = detectFormatCommand(configuration);\n if (formatCommand) {\n try {\n execSync(formatCommand.replace('{{file}}', filePath), {\n stdio: 'inherit',\n cwd: dependencies.configuration.system.baseDir,\n });\n } catch (error) {\n console.error(error);\n }\n }\n }\n\n return {\n extractedContentMap: result.extractedContent,\n transformedCode: modifiedFullCode,\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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"}
@@ -6,6 +6,6 @@ import { generateKey } from "./generateKey.mjs";
6
6
  import { getComponentName } from "./getComponentName.mjs";
7
7
  import { getExistingIntlayerInfo } from "./getExistingIntlayerInfo.mjs";
8
8
  import { getOrGenerateKey } from "./getOrGenerateKey.mjs";
9
- import { resolveDictionaryKey } from "./resolveDictionaryKey.mjs";
9
+ import { getComponentFilePath, resolveDictionaryKey } from "./resolveDictionaryKey.mjs";
10
10
 
11
- export { SERVER_CAPABLE_PACKAGES, detectPackageName, extractDictionaryInfo, extractDictionaryKey, extractDictionaryKeyFromPath, generateKey, getComponentName, getExistingIntlayerInfo, getOrGenerateKey, getOutput, packageList, resolveContentFilePaths, resolveDictionaryKey };
11
+ export { SERVER_CAPABLE_PACKAGES, detectPackageName, extractDictionaryInfo, extractDictionaryKey, extractDictionaryKeyFromPath, generateKey, getComponentFilePath, getComponentName, getExistingIntlayerInfo, getOrGenerateKey, getOutput, packageList, resolveContentFilePaths, resolveDictionaryKey };
@@ -1,17 +1,32 @@
1
1
  import { existsSync } from "node:fs";
2
- import { dirname, join, resolve } from "node:path";
3
- import { assertPathWithin } from "@intlayer/config/utils";
2
+ import { dirname, join, relative, resolve } from "node:path";
3
+ import { assertPathWithin, normalizePath } from "@intlayer/config/utils";
4
4
  import { getUnmergedDictionaries } from "@intlayer/dictionaries-entry/unmerged";
5
5
 
6
6
  // src/extractContent/utils/resolveDictionaryKey.ts
7
7
  /**
8
+ * Returns the component path stored in `componentFilePath`: relative to the
9
+ * project root, POSIX separators, so it is stable across machines.
10
+ */
11
+ const getComponentFilePath = (filePath, configuration) => normalizePath(relative(configuration.system.baseDir, resolve(filePath)));
12
+ /**
13
+ * Whether the dictionaries already declared under a key were extracted from
14
+ * this component. Dictionaries without `componentFilePath` (hand-written,
15
+ * remote, or translations added afterwards) do not claim the key.
16
+ */
17
+ const isOwnedByComponent = (dictionaries, componentFilePath) => {
18
+ const owners = dictionaries.map((dictionary) => dictionary.componentFilePath).filter((owner) => Boolean(owner));
19
+ return owners.length > 0 && owners.every((owner) => owner === componentFilePath);
20
+ };
21
+ /**
8
22
  * Resolves a unique dictionary key, checking for existing dictionaries and files.
9
- * Note: this chokidar-specific variant fetches unmergedDictionaries internally
10
- * from the configuration (unlike the `@intlayer/babel` version which takes it as a parameter).
23
+ * A key already used by a dictionary extracted from the same component is
24
+ * reused, so the key stays stable when the component is not rewritten.
11
25
  */
12
26
  const resolveDictionaryKey = (initialKey, filePath, configuration, unmergedDictionaries, usedKeys = /* @__PURE__ */ new Set()) => {
13
27
  const dicts = unmergedDictionaries ?? getUnmergedDictionaries(configuration) ?? {};
14
28
  const { fileExtensions } = configuration.content;
29
+ const componentFilePath = getComponentFilePath(filePath, configuration);
15
30
  const dirName = dirname(filePath);
16
31
  const firstExtension = fileExtensions[0] ?? ".ts";
17
32
  const extension = firstExtension.startsWith(".") ? firstExtension : `.${firstExtension}`;
@@ -23,6 +38,7 @@ const resolveDictionaryKey = (initialKey, filePath, configuration, unmergedDicti
23
38
  assertPathWithin(resolve(contentFilePath), dirName);
24
39
  const isKeyUsed = usedKeys.has(keyToTest);
25
40
  const hasDictionaries = dictionaries && dictionaries.length > 0;
41
+ if (!isKeyUsed && hasDictionaries && isOwnedByComponent(dictionaries, componentFilePath)) return keyToTest;
26
42
  const fileExists = existsSync(contentFilePath);
27
43
  if (!isKeyUsed && !hasDictionaries && !fileExists) return keyToTest;
28
44
  index++;
@@ -30,5 +46,5 @@ const resolveDictionaryKey = (initialKey, filePath, configuration, unmergedDicti
30
46
  return initialKey;
31
47
  };
32
48
 
33
- export { resolveDictionaryKey };
49
+ export { getComponentFilePath, resolveDictionaryKey };
34
50
  //# sourceMappingURL=resolveDictionaryKey.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"resolveDictionaryKey.mjs","names":[],"sources":["../../../../src/extractContent/utils/resolveDictionaryKey.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { assertPathWithin } from '@intlayer/config/utils';\nimport { getUnmergedDictionaries } from '@intlayer/dictionaries-entry/unmerged';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Dictionary } from '@intlayer/types/dictionary';\n\n/**\n * Resolves a unique dictionary key, checking for existing dictionaries and files.\n * Note: this chokidar-specific variant fetches unmergedDictionaries internally\n * from the configuration (unlike the `@intlayer/babel` version which takes it as a parameter).\n */\nexport const resolveDictionaryKey = (\n initialKey: string,\n filePath: string,\n configuration: IntlayerConfig,\n unmergedDictionaries?: Record<string, unknown>,\n usedKeys: Set<string> = new Set()\n): string => {\n const dicts =\n unmergedDictionaries ?? getUnmergedDictionaries(configuration) ?? {};\n\n const { fileExtensions } = configuration.content;\n\n const dirName = dirname(filePath);\n const firstExtension = fileExtensions[0] ?? '.ts';\n const extension = firstExtension.startsWith('.')\n ? firstExtension\n : `.${firstExtension}`;\n\n let index = 0;\n\n while (index < 100) {\n const keyToTest = index === 0 ? initialKey : `${initialKey}${index}`;\n const dictionaries = dicts[keyToTest] as Dictionary[] | undefined;\n const contentFilePath = join(dirName, `${keyToTest}${extension}`);\n\n assertPathWithin(resolve(contentFilePath), dirName);\n\n const isKeyUsed = usedKeys.has(keyToTest);\n const hasDictionaries = dictionaries && dictionaries.length > 0;\n const fileExists = existsSync(contentFilePath);\n\n if (!isKeyUsed && !hasDictionaries && !fileExists) {\n return keyToTest;\n }\n index++;\n }\n\n return initialKey;\n};\n"],"mappings":";;;;;;;;;;;AAYA,MAAa,wBACX,YACA,UACA,eACA,sBACA,2BAAwB,IAAI,IAAI,MACrB;CACX,MAAM,QACJ,wBAAwB,wBAAwB,aAAa,KAAK,CAAC;CAErE,MAAM,EAAE,mBAAmB,cAAc;CAEzC,MAAM,UAAU,QAAQ,QAAQ;CAChC,MAAM,iBAAiB,eAAe,MAAM;CAC5C,MAAM,YAAY,eAAe,WAAW,GAAG,IAC3C,iBACA,IAAI;CAER,IAAI,QAAQ;CAEZ,OAAO,QAAQ,KAAK;EAClB,MAAM,YAAY,UAAU,IAAI,aAAa,GAAG,aAAa;EAC7D,MAAM,eAAe,MAAM;EAC3B,MAAM,kBAAkB,KAAK,SAAS,GAAG,YAAY,WAAW;EAEhE,iBAAiB,QAAQ,eAAe,GAAG,OAAO;EAElD,MAAM,YAAY,SAAS,IAAI,SAAS;EACxC,MAAM,kBAAkB,gBAAgB,aAAa,SAAS;EAC9D,MAAM,aAAa,WAAW,eAAe;EAE7C,IAAI,CAAC,aAAa,CAAC,mBAAmB,CAAC,YACrC,OAAO;EAET;CACF;CAEA,OAAO;AACT"}
1
+ {"version":3,"file":"resolveDictionaryKey.mjs","names":[],"sources":["../../../../src/extractContent/utils/resolveDictionaryKey.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { dirname, join, relative, resolve } from 'node:path';\nimport { assertPathWithin, normalizePath } from '@intlayer/config/utils';\nimport { getUnmergedDictionaries } from '@intlayer/dictionaries-entry/unmerged';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Dictionary } from '@intlayer/types/dictionary';\n\n/**\n * Returns the component path stored in `componentFilePath`: relative to the\n * project root, POSIX separators, so it is stable across machines.\n */\nexport const getComponentFilePath = (\n filePath: string,\n configuration: IntlayerConfig\n): string =>\n normalizePath(relative(configuration.system.baseDir, resolve(filePath)));\n\n/**\n * Whether the dictionaries already declared under a key were extracted from\n * this component. Dictionaries without `componentFilePath` (hand-written,\n * remote, or translations added afterwards) do not claim the key.\n */\nconst isOwnedByComponent = (\n dictionaries: Dictionary[],\n componentFilePath: string\n): boolean => {\n const owners = dictionaries\n .map((dictionary) => dictionary.componentFilePath)\n .filter((owner): owner is string => Boolean(owner));\n\n return (\n owners.length > 0 && owners.every((owner) => owner === componentFilePath)\n );\n};\n\n/**\n * Resolves a unique dictionary key, checking for existing dictionaries and files.\n * A key already used by a dictionary extracted from the same component is\n * reused, so the key stays stable when the component is not rewritten.\n */\nexport const resolveDictionaryKey = (\n initialKey: string,\n filePath: string,\n configuration: IntlayerConfig,\n unmergedDictionaries?: Record<string, unknown>,\n usedKeys: Set<string> = new Set()\n): string => {\n const dicts =\n unmergedDictionaries ?? getUnmergedDictionaries(configuration) ?? {};\n\n const { fileExtensions } = configuration.content;\n const componentFilePath = getComponentFilePath(filePath, configuration);\n\n const dirName = dirname(filePath);\n const firstExtension = fileExtensions[0] ?? '.ts';\n const extension = firstExtension.startsWith('.')\n ? firstExtension\n : `.${firstExtension}`;\n\n let index = 0;\n\n while (index < 100) {\n const keyToTest = index === 0 ? initialKey : `${initialKey}${index}`;\n const dictionaries = dicts[keyToTest] as Dictionary[] | undefined;\n const contentFilePath = join(dirName, `${keyToTest}${extension}`);\n\n assertPathWithin(resolve(contentFilePath), dirName);\n\n const isKeyUsed = usedKeys.has(keyToTest);\n const hasDictionaries = dictionaries && dictionaries.length > 0;\n\n if (\n !isKeyUsed &&\n hasDictionaries &&\n isOwnedByComponent(dictionaries, componentFilePath)\n ) {\n return keyToTest;\n }\n\n const fileExists = existsSync(contentFilePath);\n\n if (!isKeyUsed && !hasDictionaries && !fileExists) {\n return keyToTest;\n }\n index++;\n }\n\n return initialKey;\n};\n"],"mappings":";;;;;;;;;;AAWA,MAAa,wBACX,UACA,kBAEA,cAAc,SAAS,cAAc,OAAO,SAAS,QAAQ,QAAQ,CAAC,CAAC;;;;;;AAOzE,MAAM,sBACJ,cACA,sBACY;CACZ,MAAM,SAAS,aACZ,KAAK,eAAe,WAAW,iBAAiB,CAAC,CACjD,QAAQ,UAA2B,QAAQ,KAAK,CAAC;CAEpD,OACE,OAAO,SAAS,KAAK,OAAO,OAAO,UAAU,UAAU,iBAAiB;AAE5E;;;;;;AAOA,MAAa,wBACX,YACA,UACA,eACA,sBACA,2BAAwB,IAAI,IAAI,MACrB;CACX,MAAM,QACJ,wBAAwB,wBAAwB,aAAa,KAAK,CAAC;CAErE,MAAM,EAAE,mBAAmB,cAAc;CACzC,MAAM,oBAAoB,qBAAqB,UAAU,aAAa;CAEtE,MAAM,UAAU,QAAQ,QAAQ;CAChC,MAAM,iBAAiB,eAAe,MAAM;CAC5C,MAAM,YAAY,eAAe,WAAW,GAAG,IAC3C,iBACA,IAAI;CAER,IAAI,QAAQ;CAEZ,OAAO,QAAQ,KAAK;EAClB,MAAM,YAAY,UAAU,IAAI,aAAa,GAAG,aAAa;EAC7D,MAAM,eAAe,MAAM;EAC3B,MAAM,kBAAkB,KAAK,SAAS,GAAG,YAAY,WAAW;EAEhE,iBAAiB,QAAQ,eAAe,GAAG,OAAO;EAElD,MAAM,YAAY,SAAS,IAAI,SAAS;EACxC,MAAM,kBAAkB,gBAAgB,aAAa,SAAS;EAE9D,IACE,CAAC,aACD,mBACA,mBAAmB,cAAc,iBAAiB,GAElD,OAAO;EAGT,MAAM,aAAa,WAAW,eAAe;EAE7C,IAAI,CAAC,aAAa,CAAC,mBAAmB,CAAC,YACrC,OAAO;EAET;CACF;CAEA,OAAO;AACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"babelProcessor.d.ts","names":[],"sources":["../../../src/extractContent/babelProcessor.ts"],"mappings":";;;;;YAYY;EACV,MAAM;EACN;EACA;EAOA;EACA,oBAAoB,EAAE;EACtB;;;;;;qBA+IW,0BAAuB,MAC5B,SAAS,EAAE,aAAa,EAAE,cAAY,kBAC5B,cACF,aAAW,yBACA,MAAM,qBAAmB,kBAChC,eAAe,yBAAuB,cAC1C,oBAAkB,cAClB,IAAI,EAAE;;;;;qBAwIT,mCAAgC,KACtC,EAAE,MAAI,kBACK,cACF,aAAW,oBACP,eACH,gBAAc,kBACb,uBACM;EAEtB,kBAAkB,eAAe;EACjC,cAAc;EACd,wBAAwB,IAAI;EAC5B,iBAAiB,IAAI,EAAE;EACvB,gBAAgB;EAChB,SAAS,IAAI,EAAE;;EAEf,oBAAoB,IAAI,EAAE,MAAM;EAChC;;;;;qBA8aW,mBAAgB,KACtB,EAAE,MAAI,kBACK,cACF,aAAW,eACV,gBAAc,kBACb,uBACM;EAEtB,kBAAkB;EAClB,cAAc"}
1
+ {"version":3,"file":"babelProcessor.d.ts","names":[],"sources":["../../../src/extractContent/babelProcessor.ts"],"mappings":";;;;;YAgBY;EACV,MAAM;EACN;EACA;EAOA;EACA,oBAAoB,EAAE;EACtB;;;;;;qBA+IW,0BAAuB,MAC5B,SAAS,EAAE,aAAa,EAAE,cAAY,kBAC5B,cACF,aAAW,yBACA,MAAM,qBAAmB,kBAChC,eAAe,yBAAuB,cAC1C,oBAAkB,cAClB,IAAI,EAAE;;;;;qBAwIT,mCAAgC,KACtC,EAAE,MAAI,kBACK,cACF,aAAW,oBACP,eACH,gBAAc,kBACb,uBACM;EAEtB,kBAAkB,eAAe;EACjC,cAAc;EACd,wBAAwB,IAAI;EAC5B,iBAAiB,IAAI,EAAE;EACvB,gBAAgB;EAChB,SAAS,IAAI,EAAE;;EAEf,oBAAoB,IAAI,EAAE,MAAM;EAChC;;;;;qBA2dW,mBAAgB,KACtB,EAAE,MAAI,kBACK,cACF,aAAW,eACV,gBAAc,kBACb,uBACM;EAEtB,kBAAkB;EAClB,cAAc"}
@@ -1 +1 @@
1
- {"version":3,"file":"contentWriter.d.ts","names":[],"sources":["../../../src/extractContent/contentWriter.ts"],"mappings":";;;;;KAmCK;EACH;EACA,aAAa;;;;;KAMV,uBAAuB,eAAe;;;;;;;qBAa9B,0CAAuC,kBAChC,wBAAsB,oBACpB,mBAAiB,0BAEpC;;;;;;;qBAkDU,uCAAoC,kBAC7B,wBAAsB,oBACpB,sBACnB;;;;qBA8CU,qBAAkB,kBACX,wBAAsB,eACzB,eAAa,kBACZ,eACD,mBACd"}
1
+ {"version":3,"file":"contentWriter.d.ts","names":[],"sources":["../../../src/extractContent/contentWriter.ts"],"mappings":";;;;;KAoCK;EACH;EACA,aAAa;;;;;KAMV,uBAAuB,eAAe;;;;;;;qBAa9B,0CAAuC,kBAChC,wBAAsB,oBACpB,mBAAiB,0BAEpC;;;;;;;qBAkDU,uCAAoC,kBAC7B,wBAAsB,oBACpB,sBACnB;;;;qBA8CU,qBAAkB,kBACX,wBAAsB,eACzB,eAAa,kBACZ,eACD,mBACd"}
@@ -1 +1 @@
1
- {"version":3,"file":"extractContent.d.ts","names":[],"sources":["../../../src/extractContent/extractContent.ts"],"mappings":";;;;YAsBY;EACV,gBAAgB;EAChB;EACA;EACA,uBAAuB;EACvB,gBAAgB;EAChB;EACA,aAAa;IACX;IACA,SAAS;IACT;eACW;;KAsRV;EACH;EACA,qBAAqB,eAAe;;qBAmCzB,iBAAc,kBACT,aACH,aAAW,UACd,2BACT,QAAQ;;;;;;;;;;;qBA8EE,qBAAkB,kBACb,aACH,aAAW,UACd,2BACT"}
1
+ {"version":3,"file":"extractContent.d.ts","names":[],"sources":["../../../src/extractContent/extractContent.ts"],"mappings":";;;;YA0BY;EACV,gBAAgB;EAChB;EACA;EACA,uBAAuB;EACvB,gBAAgB;EAChB;EACA,aAAa;IACX;IACA,SAAS;IACT;eACW;;KA0RV;EACH;EACA,qBAAqB,eAAe;;qBAmCzB,iBAAc,kBACT,aACH,aAAW,UACd,2BACT,QAAQ;;;;;;;;;;;qBA8EE,qBAAkB,kBACb,aACH,aAAW,UACd,2BACT"}
@@ -6,5 +6,5 @@ import { generateKey } from "./generateKey.js";
6
6
  import { getComponentName } from "./getComponentName.js";
7
7
  import { ExistingIntlayerInfo, getExistingIntlayerInfo } from "./getExistingIntlayerInfo.js";
8
8
  import { getOrGenerateKey } from "./getOrGenerateKey.js";
9
- import { resolveDictionaryKey } from "./resolveDictionaryKey.js";
10
- export { ExistingIntlayerInfo, ExtractDictionaryInfoOptions, PackageName, ResolveContentFilePaths, SERVER_CAPABLE_PACKAGES, detectPackageName, extractDictionaryInfo, extractDictionaryKey, extractDictionaryKeyFromPath, generateKey, getComponentName, getExistingIntlayerInfo, getOrGenerateKey, getOutput, packageList, resolveContentFilePaths, resolveDictionaryKey };
9
+ import { getComponentFilePath, resolveDictionaryKey } from "./resolveDictionaryKey.js";
10
+ export { ExistingIntlayerInfo, ExtractDictionaryInfoOptions, PackageName, ResolveContentFilePaths, SERVER_CAPABLE_PACKAGES, detectPackageName, extractDictionaryInfo, extractDictionaryKey, extractDictionaryKeyFromPath, generateKey, getComponentFilePath, getComponentName, getExistingIntlayerInfo, getOrGenerateKey, getOutput, packageList, resolveContentFilePaths, resolveDictionaryKey };
@@ -1,8 +1,13 @@
1
1
  import { IntlayerConfig } from "@intlayer/types/config";
2
+ /**
3
+ * Returns the component path stored in `componentFilePath`: relative to the
4
+ * project root, POSIX separators, so it is stable across machines.
5
+ */
6
+ export declare const getComponentFilePath: (filePath: string, configuration: IntlayerConfig) => string;
2
7
  /**
3
8
  * Resolves a unique dictionary key, checking for existing dictionaries and files.
4
- * Note: this chokidar-specific variant fetches unmergedDictionaries internally
5
- * from the configuration (unlike the `@intlayer/babel` version which takes it as a parameter).
9
+ * A key already used by a dictionary extracted from the same component is
10
+ * reused, so the key stays stable when the component is not rewritten.
6
11
  */
7
12
  export declare const resolveDictionaryKey: (initialKey: string, filePath: string, configuration: IntlayerConfig, unmergedDictionaries?: Record<string, unknown>, usedKeys?: Set<string>) => string;
8
13
  //# sourceMappingURL=resolveDictionaryKey.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"resolveDictionaryKey.d.ts","names":[],"sources":["../../../../src/extractContent/utils/resolveDictionaryKey.ts"],"mappings":";;;;;;qBAYa,uBAAoB,oBACb,kBACF,eACD,gBAAc,uBACN,yBAAuB,WACpC"}
1
+ {"version":3,"file":"resolveDictionaryKey.d.ts","names":[],"sources":["../../../../src/extractContent/utils/resolveDictionaryKey.ts"],"mappings":";;;;;qBAWa,uBAAoB,kBACf,eACD;;;;;;qBA2BJ,uBAAoB,oBACb,kBACF,eACD,gBAAc,uBACN,yBAAuB,WACpC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intlayer/babel",
3
- "version": "9.7.3",
3
+ "version": "9.8.0-canary.1",
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": [
@@ -90,11 +90,11 @@
90
90
  "@babel/plugin-syntax-jsx": "8.0.1",
91
91
  "@babel/traverse": "8.1.0",
92
92
  "@babel/types": "8.1.0",
93
- "@intlayer/config": "9.7.3",
94
- "@intlayer/core": "9.7.3",
95
- "@intlayer/dictionaries-entry": "9.7.3",
96
- "@intlayer/engine": "9.7.3",
97
- "@intlayer/types": "9.7.3"
93
+ "@intlayer/config": "9.8.0-canary.1",
94
+ "@intlayer/core": "9.8.0-canary.1",
95
+ "@intlayer/dictionaries-entry": "9.8.0-canary.1",
96
+ "@intlayer/engine": "9.8.0-canary.1",
97
+ "@intlayer/types": "9.8.0-canary.1"
98
98
  },
99
99
  "devDependencies": {
100
100
  "@babel/plugin-syntax-jsx": "8.0.1",
@@ -112,8 +112,8 @@
112
112
  "vitest": "5.1.0"
113
113
  },
114
114
  "peerDependencies": {
115
- "@intlayer/svelte-compiler": "9.7.3",
116
- "@intlayer/vue-compiler": "9.7.3"
115
+ "@intlayer/svelte-compiler": "9.8.0-canary.1",
116
+ "@intlayer/vue-compiler": "9.8.0-canary.1"
117
117
  },
118
118
  "peerDependenciesMeta": {
119
119
  "@intlayer/svelte-compiler": {