@intlayer/babel 8.7.6 → 8.7.8-canary.0

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 (31) hide show
  1. package/dist/cjs/babel-plugin-intlayer-optimize.cjs +8 -0
  2. package/dist/cjs/babel-plugin-intlayer-optimize.cjs.map +1 -1
  3. package/dist/cjs/babel-plugin-intlayer-usage-analyzer.cjs.map +1 -1
  4. package/dist/cjs/extractContent/babelProcessor.cjs.map +1 -1
  5. package/dist/cjs/extractContent/contentWriter.cjs.map +1 -1
  6. package/dist/cjs/extractContent/extractContent.cjs.map +1 -1
  7. package/dist/cjs/extractContent/processTsxFile.cjs.map +1 -1
  8. package/dist/cjs/extractContent/utils/extractDictionaryInfo.cjs.map +1 -1
  9. package/dist/cjs/extractContent/utils/shouldExtract.cjs.map +1 -1
  10. package/dist/cjs/extractScriptBlocks.cjs +28 -3
  11. package/dist/cjs/extractScriptBlocks.cjs.map +1 -1
  12. package/dist/cjs/getOptimizePluginOptions.cjs.map +1 -1
  13. package/dist/cjs/transformers.cjs +1 -1
  14. package/dist/cjs/transformers.cjs.map +1 -1
  15. package/dist/esm/babel-plugin-intlayer-optimize.mjs +8 -0
  16. package/dist/esm/babel-plugin-intlayer-optimize.mjs.map +1 -1
  17. package/dist/esm/babel-plugin-intlayer-usage-analyzer.mjs.map +1 -1
  18. package/dist/esm/extractContent/babelProcessor.mjs.map +1 -1
  19. package/dist/esm/extractContent/contentWriter.mjs.map +1 -1
  20. package/dist/esm/extractContent/extractContent.mjs.map +1 -1
  21. package/dist/esm/extractContent/processTsxFile.mjs.map +1 -1
  22. package/dist/esm/extractContent/utils/extractDictionaryInfo.mjs.map +1 -1
  23. package/dist/esm/extractContent/utils/shouldExtract.mjs.map +1 -1
  24. package/dist/esm/extractScriptBlocks.mjs +28 -3
  25. package/dist/esm/extractScriptBlocks.mjs.map +1 -1
  26. package/dist/esm/getOptimizePluginOptions.mjs.map +1 -1
  27. package/dist/esm/transformers.mjs +1 -1
  28. package/dist/esm/transformers.mjs.map +1 -1
  29. package/dist/types/extractScriptBlocks.d.ts +5 -3
  30. package/dist/types/extractScriptBlocks.d.ts.map +1 -1
  31. package/package.json +11 -11
@@ -1 +1 @@
1
- {"version":3,"file":"babel-plugin-intlayer-usage-analyzer.mjs","names":[],"sources":["../../src/babel-plugin-intlayer-usage-analyzer.ts"],"sourcesContent":["import type { NodePath, PluginObj, PluginPass } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\n\n// ── PruneContext types ────────────────────────────────────────────────────────\n\n/**\n * Dictionary field usage result for a single dictionary key.\n *\n * 'all' → could not determine statically which fields are used;\n * keep every field (no pruning possible).\n * Set<string> → the exact top-level content field names that were accessed.\n */\nexport type DictionaryFieldUsage = Set<string> | 'all';\n\n/**\n * One node in the nested field-rename tree.\n *\n * shortName – the compact alias assigned to this field name.\n * children – rename table for the next level of user-defined keys inside\n * this field's value (empty when the value is a leaf / primitive).\n */\nexport type NestedRenameEntry = {\n shortName: string;\n children: NestedRenameMap;\n};\n\n/** A level of the field-rename tree, mapping original field names to entries. */\nexport type NestedRenameMap = Map<string, NestedRenameEntry>;\n\n/**\n * Shared mutable state created once by the vite plugin and passed by reference\n * to the usage-analyzer (writer) and the prune/minify plugins (readers).\n *\n * All mutations happen during the usage-analysis `buildStart` phase; readers\n * only access this state during the subsequent `transform` phase.\n */\nexport type PruneContext = {\n /**\n * Maps every dictionary key seen in source files to the set of top-level\n * content fields statically accessed, or `'all'` when the access pattern\n * could not be determined.\n */\n dictionaryKeyToFieldUsageMap: Map<string, DictionaryFieldUsage>;\n\n /**\n * Dictionary keys for which the prune/minify step must be skipped entirely\n * because an edge case was detected during analysis or structure recognition.\n */\n dictionariesWithEdgeCases: Set<string>;\n\n /**\n * True if at least one source file failed to parse during the analysis phase.\n * The prune plugin uses this flag conservatively: any dictionary key without\n * a usage entry might have been referenced by the unparsable file.\n */\n hasUnparsableSourceFiles: boolean;\n\n /**\n * Maps dictionary keys to the source file paths where the result of\n * `useIntlayer` / `getIntlayer` was assigned to a plain variable, making\n * static field analysis impossible.\n */\n dictionaryKeysWithUntrackedBindings: Map<string, string[]>;\n\n /**\n * Maps each dictionary key to a nested field-rename tree built after the\n * usage analysis phase (only populated when `build.minify` is active and\n * the field usage for that dictionary is a finite `Set<string>`).\n */\n dictionaryKeyToFieldRenameMap: Map<string, NestedRenameMap>;\n\n /**\n * Maps each dictionary key to a per-field list of source locations where\n * the field value is consumed \"opaquely\" (passed as-is to a child component\n * or function argument). When a field is opaque AND has nested user-defined\n * structure, its children must not be renamed.\n *\n * Structure: dictionaryKey → fieldName → [\"filePath:line\", …]\n */\n dictionaryKeysWithOpaqueTopLevelFields: Map<string, Map<string, string[]>>;\n\n /**\n * Dictionary keys for which field-key renaming must be skipped even if a\n * finite field-usage set was determined.\n *\n * Populated for dictionaries whose plain-variable bindings were resolved by\n * the framework-specific extractor (Vue / Svelte SFCs), because the Babel\n * rename plugin cannot update the source-code property accesses for those\n * indirect patterns (Vue `.value.field` / Svelte `$store.field`).\n *\n * Pruning and basic minification still apply; only field-key renaming is\n * suppressed.\n */\n dictionariesSkippingFieldRename: Set<string>;\n\n /**\n * Plain variable bindings that require a framework-specific secondary pass.\n *\n * Populated during the Babel analysis phase for `.vue` and `.svelte` source\n * files where direct field access is not visible to Babel scope analysis:\n * - Vue: `content.value.fieldName` – the `.value` ref-accessor is hidden\n * - Svelte: `$varName.fieldName` – the `$` prefix creates a new identifier\n *\n * Structure: filePath → [{variableName, dictionaryKey}, …]\n */\n pendingFrameworkAnalysis: Map<\n string,\n { variableName: string; dictionaryKey: string }[]\n >;\n};\n\nexport const createPruneContext = (): PruneContext => ({\n dictionaryKeyToFieldUsageMap: new Map(),\n dictionariesWithEdgeCases: new Set(),\n hasUnparsableSourceFiles: false,\n dictionaryKeysWithUntrackedBindings: new Map(),\n dictionaryKeyToFieldRenameMap: new Map(),\n dictionaryKeysWithOpaqueTopLevelFields: new Map<\n string,\n Map<string, string[]>\n >(),\n dictionariesSkippingFieldRename: new Set(),\n pendingFrameworkAnalysis: new Map(),\n});\n\n// ── Usage-analyzer Babel plugin ───────────────────────────────────────────────\n\n/** Canonical intlayer caller names that trigger usage analysis. */\nexport const INTLAYER_CALLER_NAMES = ['useIntlayer', 'getIntlayer'] as const;\nexport type IntlayerCallerName = (typeof INTLAYER_CALLER_NAMES)[number];\n\n/**\n * Records the usage of a specific dictionary key's fields into `pruneContext`.\n * Merges with any previously recorded usage for the same key.\n */\nconst recordFieldUsage = (\n pruneContext: PruneContext,\n dictionaryKey: string,\n fieldUsage: DictionaryFieldUsage\n): void => {\n const existingUsage =\n pruneContext.dictionaryKeyToFieldUsageMap.get(dictionaryKey);\n\n if (existingUsage === 'all') return; // already saturated\n\n if (fieldUsage === 'all') {\n pruneContext.dictionaryKeyToFieldUsageMap.set(dictionaryKey, 'all');\n return;\n }\n\n const mergedFieldSet =\n existingUsage instanceof Set\n ? new Set([...existingUsage, ...fieldUsage])\n : new Set(fieldUsage);\n\n pruneContext.dictionaryKeyToFieldUsageMap.set(dictionaryKey, mergedFieldSet);\n};\n\n/**\n * Analyses how the result of a single `useIntlayer('key')` / `getIntlayer('key')`\n * call expression is consumed, then records the field usage into `pruneContext`.\n *\n * Recognised patterns:\n * const { fieldA, fieldB } = useIntlayer('key') → records {fieldA, fieldB}\n * useIntlayer('key').fieldA → records {fieldA}\n * useIntlayer('key')['fieldA'] → records {fieldA}\n * const { ...rest } = useIntlayer('key') → records 'all' (spread)\n * const result = useIntlayer('key') → records 'all' (untracked binding)\n */\nconst analyzeCallExpressionUsage = (\n babelTypes: typeof BabelTypes,\n pruneContext: PruneContext,\n callExpressionPath: NodePath<BabelTypes.CallExpression>,\n dictionaryKey: string,\n currentSourceFilePath: string,\n isSfcFile: boolean\n): void => {\n const parentNode = callExpressionPath.parent;\n\n /** Mark the dictionary key as having an untracked binding in this file. */\n const markUntrackedBinding = (): void => {\n const existingPaths =\n pruneContext.dictionaryKeysWithUntrackedBindings.get(dictionaryKey) ?? [];\n if (!existingPaths.includes(currentSourceFilePath)) {\n pruneContext.dictionaryKeysWithUntrackedBindings.set(dictionaryKey, [\n ...existingPaths,\n currentSourceFilePath,\n ]);\n }\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n };\n\n /** Record that a field value is consumed opaquely (not further destructured). */\n const markOpaqueField = (\n fieldName: string,\n line: number | undefined\n ): void => {\n const fieldToLocations =\n pruneContext.dictionaryKeysWithOpaqueTopLevelFields.get(dictionaryKey) ??\n new Map<string, string[]>();\n const location =\n line !== undefined\n ? `${currentSourceFilePath}:${line}`\n : currentSourceFilePath;\n const locations = fieldToLocations.get(fieldName) ?? [];\n if (!locations.includes(location)) locations.push(location);\n fieldToLocations.set(fieldName, locations);\n pruneContext.dictionaryKeysWithOpaqueTopLevelFields.set(\n dictionaryKey,\n fieldToLocations\n );\n };\n\n /** Register a plain variable binding in an SFC file for a second-pass analysis. */\n const deferFrameworkAnalysis = (variableName: string): void => {\n const existing =\n pruneContext.pendingFrameworkAnalysis.get(currentSourceFilePath) ?? [];\n if (\n !existing.some(\n (e) =>\n e.variableName === variableName && e.dictionaryKey === dictionaryKey\n )\n ) {\n existing.push({ variableName, dictionaryKey });\n }\n pruneContext.pendingFrameworkAnalysis.set(currentSourceFilePath, existing);\n };\n\n /**\n * Analyses usage of a variable or member access to detect opaque\n * consumption (passing a dictionary field as-is to a prop or function).\n *\n * If a direct, non-chained consumption is found, it calls `markOpaqueField`.\n * Chained accesses (e.g. `field.sub`) are NOT considered opaque for `field`\n * because the renamer can safely track and update them.\n */\n const analyzeOpaqueUsage = (\n refPath: NodePath<BabelTypes.Node>,\n fieldName: string\n ): void => {\n const parentNode = refPath.parent;\n\n // 1. Chained member access (e.g. field.sub or field?.sub)\n if (\n (babelTypes.isMemberExpression(parentNode) ||\n babelTypes.isOptionalMemberExpression(parentNode)) &&\n (parentNode as BabelTypes.MemberExpression).object === refPath.node\n ) {\n // Chained access is safe: the renamer correctly updates it.\n return;\n }\n\n // 2. Destructuring (e.g. const { sub } = field)\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isObjectPattern(parentNode.id) &&\n parentNode.init === refPath.node\n ) {\n // Destructuring is analogous to member access: safe.\n return;\n }\n\n // 3. Ignored patterns (e.g. array literals [content])\n if (babelTypes.isArrayExpression(parentNode)) {\n return;\n }\n\n // 4. Opaque consumption (passed to prop, function, etc.)\n markOpaqueField(fieldName, refPath.node.loc?.start.line);\n };\n\n // ── Pattern 1: const { fieldA, fieldB } = useIntlayer('key') ──────────────\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isObjectPattern(parentNode.id)\n ) {\n const hasRestElement = parentNode.id.properties.some((prop) =>\n babelTypes.isRestElement(prop)\n );\n\n if (hasRestElement) {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n const accessedFieldNames = new Set<string>();\n for (const property of parentNode.id.properties) {\n let fieldName: string | undefined;\n\n if (\n babelTypes.isObjectProperty(property) &&\n babelTypes.isIdentifier(property.key)\n ) {\n fieldName = property.key.name;\n } else if (\n babelTypes.isObjectProperty(property) &&\n babelTypes.isStringLiteral(property.key)\n ) {\n fieldName = property.key.value;\n }\n\n if (fieldName) {\n accessedFieldNames.add(fieldName);\n\n // Check usage of the bound variable to look for opaque consumption\n if (\n babelTypes.isObjectProperty(property) &&\n babelTypes.isIdentifier(property.value)\n ) {\n const variableBinding = callExpressionPath.scope.getBinding(\n property.value.name\n );\n if (variableBinding) {\n for (const refPath of variableBinding.referencePaths) {\n analyzeOpaqueUsage(refPath, fieldName);\n }\n }\n }\n }\n }\n\n recordFieldUsage(pruneContext, dictionaryKey, accessedFieldNames);\n return;\n }\n\n // ── Pattern 2: useIntlayer('key').fieldA / useIntlayer('key')?.fieldA ──────\n if (\n (babelTypes.isMemberExpression(parentNode) ||\n babelTypes.isOptionalMemberExpression(parentNode)) &&\n (parentNode as BabelTypes.MemberExpression).object ===\n callExpressionPath.node\n ) {\n let fieldName: string | undefined;\n\n if (!parentNode.computed && babelTypes.isIdentifier(parentNode.property)) {\n fieldName = parentNode.property.name;\n } else if (\n parentNode.computed &&\n babelTypes.isStringLiteral(parentNode.property)\n ) {\n fieldName = parentNode.property.value;\n }\n\n if (fieldName) {\n recordFieldUsage(pruneContext, dictionaryKey, new Set([fieldName]));\n\n // Check for opaque usage (e.g. passed directly to a prop)\n const memberExprPath = callExpressionPath.parentPath;\n if (memberExprPath) {\n analyzeOpaqueUsage(memberExprPath, fieldName);\n }\n } else {\n markUntrackedBinding();\n }\n return;\n }\n\n // ── Pattern 3: const content = useIntlayer('key') ─────────────────────────\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isIdentifier(parentNode.id)\n ) {\n const variableName = parentNode.id.name;\n const variableBinding = callExpressionPath.scope.getBinding(variableName);\n\n if (!variableBinding) {\n markUntrackedBinding();\n return;\n }\n\n const accessedTopLevelFieldNames = new Set<string>();\n let hasUntrackedReferenceAccess = false;\n\n for (const variableReferencePath of variableBinding.referencePaths) {\n const referenceParentNode = variableReferencePath.parent;\n\n if (\n (babelTypes.isMemberExpression(referenceParentNode) ||\n babelTypes.isOptionalMemberExpression(referenceParentNode)) &&\n (referenceParentNode as BabelTypes.MemberExpression).object ===\n variableReferencePath.node\n ) {\n const memberExpressionNode =\n referenceParentNode as BabelTypes.MemberExpression;\n let fieldName: string | undefined;\n\n if (\n !memberExpressionNode.computed &&\n babelTypes.isIdentifier(memberExpressionNode.property)\n ) {\n fieldName = memberExpressionNode.property.name;\n } else if (\n memberExpressionNode.computed &&\n babelTypes.isStringLiteral(memberExpressionNode.property)\n ) {\n fieldName = memberExpressionNode.property.value;\n }\n\n if (fieldName) {\n accessedTopLevelFieldNames.add(fieldName);\n\n // Check usage of the field to look for opaque consumption\n const memberExprPath = variableReferencePath.parentPath;\n if (memberExprPath) {\n analyzeOpaqueUsage(memberExprPath, fieldName);\n }\n } else {\n // Dynamic computed access – cannot resolve statically\n hasUntrackedReferenceAccess = true;\n break;\n }\n } else if (babelTypes.isArrayExpression(referenceParentNode)) {\n // Ignore array literals (e.g. [content]) – uncommon but benign\n } else {\n // Variable used in a non-member-access context (spread, function arg, etc.)\n hasUntrackedReferenceAccess = true;\n break;\n }\n }\n\n if (hasUntrackedReferenceAccess) {\n markUntrackedBinding();\n } else if (isSfcFile) {\n // Vue / Svelte SFC: defer to the framework-specific extractor because\n // Babel scope analysis cannot see through `.value` or `$` indirection.\n deferFrameworkAnalysis(variableName);\n } else if (variableBinding.referencePaths.length === 0) {\n // Non-SFC file with no visible references – conservatively keep all fields.\n markUntrackedBinding();\n } else {\n recordFieldUsage(pruneContext, dictionaryKey, accessedTopLevelFieldNames);\n }\n return;\n }\n\n // ── Pattern 4: bare call – result is discarded ─────────────────────────────\n if (babelTypes.isExpressionStatement(parentNode)) {\n return; // no usage to record\n }\n\n // ── Fallback: result passed as argument, used in ternary, etc. ─────────────\n markUntrackedBinding();\n};\n\n/**\n * Creates a Babel plugin that traverses source files and records which\n * top-level dictionary fields each `useIntlayer` / `getIntlayer` call-site\n * accesses. Results are accumulated into `pruneContext`.\n *\n * This plugin is analysis-only: it does not transform the code (`code: false`\n * should be passed to `transformAsync` when using it).\n */\nexport const makeUsageAnalyzerBabelPlugin =\n (pruneContext: PruneContext) =>\n ({ types: babelTypes }: { types: typeof BabelTypes }): PluginObj => ({\n name: 'intlayer-usage-analyzer',\n visitor: {\n Program: {\n exit: (programPath, state: PluginPass) => {\n const currentSourceFilePath =\n state.file.opts.filename ?? 'unknown file';\n const isSfcFile =\n currentSourceFilePath.endsWith('.vue') ||\n currentSourceFilePath.endsWith('.svelte');\n\n // Phase 1: collect local aliases for useIntlayer / getIntlayer\n const intlayerCallerLocalNameMap = new Map<string, string>();\n\n programPath.traverse({\n ImportDeclaration: (importDeclarationPath) => {\n for (const importSpecifier of importDeclarationPath.node\n .specifiers) {\n if (!babelTypes.isImportSpecifier(importSpecifier)) continue;\n\n const importedName = babelTypes.isIdentifier(\n importSpecifier.imported\n )\n ? importSpecifier.imported.name\n : (importSpecifier.imported as BabelTypes.StringLiteral)\n .value;\n\n if (\n INTLAYER_CALLER_NAMES.includes(\n importedName as IntlayerCallerName\n )\n ) {\n intlayerCallerLocalNameMap.set(\n importSpecifier.local.name,\n importedName\n );\n }\n }\n },\n });\n\n if (intlayerCallerLocalNameMap.size === 0) return;\n\n // Phase 2: analyse each call-site\n programPath.traverse({\n CallExpression: (callExpressionPath) => {\n const calleeNode = callExpressionPath.node.callee;\n let localCallerName: string | undefined;\n\n if (babelTypes.isIdentifier(calleeNode)) {\n localCallerName = calleeNode.name;\n } else if (\n babelTypes.isMemberExpression(calleeNode) &&\n babelTypes.isIdentifier(calleeNode.property)\n ) {\n localCallerName = calleeNode.property.name;\n }\n\n if (\n !localCallerName ||\n !intlayerCallerLocalNameMap.has(localCallerName)\n )\n return;\n\n const callArguments = callExpressionPath.node.arguments;\n if (callArguments.length === 0) return;\n\n const firstArgument = callArguments[0];\n let dictionaryKey: string | undefined;\n\n if (babelTypes.isStringLiteral(firstArgument)) {\n dictionaryKey = firstArgument.value;\n } else if (\n babelTypes.isTemplateLiteral(firstArgument) &&\n firstArgument.expressions.length === 0 &&\n firstArgument.quasis.length === 1\n ) {\n dictionaryKey =\n firstArgument.quasis[0].value.cooked ??\n firstArgument.quasis[0].value.raw;\n }\n\n if (!dictionaryKey) return; // dynamic key – cannot resolve which dictionary\n\n analyzeCallExpressionUsage(\n babelTypes,\n pruneContext,\n callExpressionPath,\n dictionaryKey,\n currentSourceFilePath,\n isSfcFile\n );\n },\n });\n },\n },\n },\n });\n"],"mappings":"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"}
1
+ {"version":3,"file":"babel-plugin-intlayer-usage-analyzer.mjs","names":[],"sources":["../../src/babel-plugin-intlayer-usage-analyzer.ts"],"sourcesContent":["import type { NodePath, PluginObj, PluginPass } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\n\n// ── PruneContext types ────────────────────────────────────────────────────────\n\n/**\n * Dictionary field usage result for a single dictionary key.\n *\n * 'all' → could not determine statically which fields are used;\n * keep every field (no pruning possible).\n * Set<string> → the exact top-level content field names that were accessed.\n */\nexport type DictionaryFieldUsage = Set<string> | 'all';\n\n/**\n * One node in the nested field-rename tree.\n *\n * shortName – the compact alias assigned to this field name.\n * children – rename table for the next level of user-defined keys inside\n * this field's value (empty when the value is a leaf / primitive).\n */\nexport type NestedRenameEntry = {\n shortName: string;\n children: NestedRenameMap;\n};\n\n/** A level of the field-rename tree, mapping original field names to entries. */\nexport type NestedRenameMap = Map<string, NestedRenameEntry>;\n\n/**\n * Shared mutable state created once by the vite plugin and passed by reference\n * to the usage-analyzer (writer) and the prune/minify plugins (readers).\n *\n * All mutations happen during the usage-analysis `buildStart` phase; readers\n * only access this state during the subsequent `transform` phase.\n */\nexport type PruneContext = {\n /**\n * Maps every dictionary key seen in source files to the set of top-level\n * content fields statically accessed, or `'all'` when the access pattern\n * could not be determined.\n */\n dictionaryKeyToFieldUsageMap: Map<string, DictionaryFieldUsage>;\n\n /**\n * Dictionary keys for which the prune/minify step must be skipped entirely\n * because an edge case was detected during analysis or structure recognition.\n */\n dictionariesWithEdgeCases: Set<string>;\n\n /**\n * True if at least one source file failed to parse during the analysis phase.\n * The prune plugin uses this flag conservatively: any dictionary key without\n * a usage entry might have been referenced by the unparsable file.\n */\n hasUnparsableSourceFiles: boolean;\n\n /**\n * Maps dictionary keys to the source file paths where the result of\n * `useIntlayer` / `getIntlayer` was assigned to a plain variable, making\n * static field analysis impossible.\n */\n dictionaryKeysWithUntrackedBindings: Map<string, string[]>;\n\n /**\n * Maps each dictionary key to a nested field-rename tree built after the\n * usage analysis phase (only populated when `build.minify` is active and\n * the field usage for that dictionary is a finite `Set<string>`).\n */\n dictionaryKeyToFieldRenameMap: Map<string, NestedRenameMap>;\n\n /**\n * Maps each dictionary key to a per-field list of source locations where\n * the field value is consumed \"opaquely\" (passed as-is to a child component\n * or function argument). When a field is opaque AND has nested user-defined\n * structure, its children must not be renamed.\n *\n * Structure: dictionaryKey → fieldName → [\"filePath:line\", …]\n */\n dictionaryKeysWithOpaqueTopLevelFields: Map<string, Map<string, string[]>>;\n\n /**\n * Dictionary keys for which field-key renaming must be skipped even if a\n * finite field-usage set was determined.\n *\n * Populated for dictionaries whose plain-variable bindings were resolved by\n * the framework-specific extractor (Vue / Svelte SFCs), because the Babel\n * rename plugin cannot update the source-code property accesses for those\n * indirect patterns (Vue `.value.field` / Svelte `$store.field`).\n *\n * Pruning and basic minification still apply; only field-key renaming is\n * suppressed.\n */\n dictionariesSkippingFieldRename: Set<string>;\n\n /**\n * Plain variable bindings that require a framework-specific secondary pass.\n *\n * Populated during the Babel analysis phase for `.vue` and `.svelte` source\n * files where direct field access is not visible to Babel scope analysis:\n * - Vue: `content.value.fieldName` – the `.value` ref-accessor is hidden\n * - Svelte: `$varName.fieldName` – the `$` prefix creates a new identifier\n *\n * Structure: filePath → [{variableName, dictionaryKey}, …]\n */\n pendingFrameworkAnalysis: Map<\n string,\n { variableName: string; dictionaryKey: string }[]\n >;\n};\n\nexport const createPruneContext = (): PruneContext => ({\n dictionaryKeyToFieldUsageMap: new Map(),\n dictionariesWithEdgeCases: new Set(),\n hasUnparsableSourceFiles: false,\n dictionaryKeysWithUntrackedBindings: new Map(),\n dictionaryKeyToFieldRenameMap: new Map(),\n dictionaryKeysWithOpaqueTopLevelFields: new Map<\n string,\n Map<string, string[]>\n >(),\n dictionariesSkippingFieldRename: new Set(),\n pendingFrameworkAnalysis: new Map(),\n});\n\n// ── Usage-analyzer Babel plugin ───────────────────────────────────────────────\n\n/** Canonical intlayer caller names that trigger usage analysis. */\nexport const INTLAYER_CALLER_NAMES = ['useIntlayer', 'getIntlayer'] as const;\nexport type IntlayerCallerName = (typeof INTLAYER_CALLER_NAMES)[number];\n\n/**\n * Records the usage of a specific dictionary key's fields into `pruneContext`.\n * Merges with any previously recorded usage for the same key.\n */\nconst recordFieldUsage = (\n pruneContext: PruneContext,\n dictionaryKey: string,\n fieldUsage: DictionaryFieldUsage\n): void => {\n const existingUsage =\n pruneContext.dictionaryKeyToFieldUsageMap.get(dictionaryKey);\n\n if (existingUsage === 'all') return; // already saturated\n\n if (fieldUsage === 'all') {\n pruneContext.dictionaryKeyToFieldUsageMap.set(dictionaryKey, 'all');\n return;\n }\n\n const mergedFieldSet =\n existingUsage instanceof Set\n ? new Set([...existingUsage, ...fieldUsage])\n : new Set(fieldUsage);\n\n pruneContext.dictionaryKeyToFieldUsageMap.set(dictionaryKey, mergedFieldSet);\n};\n\n/**\n * Analyses how the result of a single `useIntlayer('key')` / `getIntlayer('key')`\n * call expression is consumed, then records the field usage into `pruneContext`.\n *\n * Recognised patterns:\n * const { fieldA, fieldB } = useIntlayer('key') → records {fieldA, fieldB}\n * useIntlayer('key').fieldA → records {fieldA}\n * useIntlayer('key')['fieldA'] → records {fieldA}\n * const { ...rest } = useIntlayer('key') → records 'all' (spread)\n * const result = useIntlayer('key') → records 'all' (untracked binding)\n */\nconst analyzeCallExpressionUsage = (\n babelTypes: typeof BabelTypes,\n pruneContext: PruneContext,\n callExpressionPath: NodePath<BabelTypes.CallExpression>,\n dictionaryKey: string,\n currentSourceFilePath: string,\n isSfcFile: boolean\n): void => {\n const parentNode = callExpressionPath.parent;\n\n /** Mark the dictionary key as having an untracked binding in this file. */\n const markUntrackedBinding = (): void => {\n const existingPaths =\n pruneContext.dictionaryKeysWithUntrackedBindings.get(dictionaryKey) ?? [];\n if (!existingPaths.includes(currentSourceFilePath)) {\n pruneContext.dictionaryKeysWithUntrackedBindings.set(dictionaryKey, [\n ...existingPaths,\n currentSourceFilePath,\n ]);\n }\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n };\n\n /** Record that a field value is consumed opaquely (not further destructured). */\n const markOpaqueField = (\n fieldName: string,\n line: number | undefined\n ): void => {\n const fieldToLocations =\n pruneContext.dictionaryKeysWithOpaqueTopLevelFields.get(dictionaryKey) ??\n new Map<string, string[]>();\n const location =\n line !== undefined\n ? `${currentSourceFilePath}:${line}`\n : currentSourceFilePath;\n const locations = fieldToLocations.get(fieldName) ?? [];\n if (!locations.includes(location)) locations.push(location);\n fieldToLocations.set(fieldName, locations);\n pruneContext.dictionaryKeysWithOpaqueTopLevelFields.set(\n dictionaryKey,\n fieldToLocations\n );\n };\n\n /** Register a plain variable binding in an SFC file for a second-pass analysis. */\n const deferFrameworkAnalysis = (variableName: string): void => {\n const existing =\n pruneContext.pendingFrameworkAnalysis.get(currentSourceFilePath) ?? [];\n if (\n !existing.some(\n (e) =>\n e.variableName === variableName && e.dictionaryKey === dictionaryKey\n )\n ) {\n existing.push({ variableName, dictionaryKey });\n }\n pruneContext.pendingFrameworkAnalysis.set(currentSourceFilePath, existing);\n };\n\n /**\n * Analyses usage of a variable or member access to detect opaque\n * consumption (passing a dictionary field as-is to a prop or function).\n *\n * If a direct, non-chained consumption is found, it calls `markOpaqueField`.\n * Chained accesses (e.g. `field.sub`) are NOT considered opaque for `field`\n * because the renamer can safely track and update them.\n */\n const analyzeOpaqueUsage = (\n refPath: NodePath<BabelTypes.Node>,\n fieldName: string\n ): void => {\n const parentNode = refPath.parent;\n\n // 1. Chained member access (e.g. field.sub or field?.sub)\n if (\n (babelTypes.isMemberExpression(parentNode) ||\n babelTypes.isOptionalMemberExpression(parentNode)) &&\n (parentNode as BabelTypes.MemberExpression).object === refPath.node\n ) {\n // Chained access is safe: the renamer correctly updates it.\n return;\n }\n\n // 2. Destructuring (e.g. const { sub } = field)\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isObjectPattern(parentNode.id) &&\n parentNode.init === refPath.node\n ) {\n // Destructuring is analogous to member access: safe.\n return;\n }\n\n // 3. Ignored patterns (e.g. array literals [content])\n if (babelTypes.isArrayExpression(parentNode)) {\n return;\n }\n\n // 4. Opaque consumption (passed to prop, function, etc.)\n markOpaqueField(fieldName, refPath.node.loc?.start.line);\n };\n\n // ── Pattern 1: const { fieldA, fieldB } = useIntlayer('key') ──────────────\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isObjectPattern(parentNode.id)\n ) {\n const hasRestElement = parentNode.id.properties.some((prop) =>\n babelTypes.isRestElement(prop)\n );\n\n if (hasRestElement) {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n const accessedFieldNames = new Set<string>();\n for (const property of parentNode.id.properties) {\n let fieldName: string | undefined;\n\n if (\n babelTypes.isObjectProperty(property) &&\n babelTypes.isIdentifier(property.key)\n ) {\n fieldName = property.key.name;\n } else if (\n babelTypes.isObjectProperty(property) &&\n babelTypes.isStringLiteral(property.key)\n ) {\n fieldName = property.key.value;\n }\n\n if (fieldName) {\n accessedFieldNames.add(fieldName);\n\n // Check usage of the bound variable to look for opaque consumption\n if (\n babelTypes.isObjectProperty(property) &&\n babelTypes.isIdentifier(property.value)\n ) {\n const variableBinding = callExpressionPath.scope.getBinding(\n property.value.name\n );\n if (variableBinding) {\n for (const refPath of variableBinding.referencePaths) {\n analyzeOpaqueUsage(refPath, fieldName);\n }\n }\n }\n }\n }\n\n recordFieldUsage(pruneContext, dictionaryKey, accessedFieldNames);\n return;\n }\n\n // ── Pattern 2: useIntlayer('key').fieldA / useIntlayer('key')?.fieldA ──────\n if (\n (babelTypes.isMemberExpression(parentNode) ||\n babelTypes.isOptionalMemberExpression(parentNode)) &&\n (parentNode as BabelTypes.MemberExpression).object ===\n callExpressionPath.node\n ) {\n let fieldName: string | undefined;\n\n if (!parentNode.computed && babelTypes.isIdentifier(parentNode.property)) {\n fieldName = parentNode.property.name;\n } else if (\n parentNode.computed &&\n babelTypes.isStringLiteral(parentNode.property)\n ) {\n fieldName = parentNode.property.value;\n }\n\n if (fieldName) {\n recordFieldUsage(pruneContext, dictionaryKey, new Set([fieldName]));\n\n // Check for opaque usage (e.g. passed directly to a prop)\n const memberExprPath = callExpressionPath.parentPath;\n if (memberExprPath) {\n analyzeOpaqueUsage(memberExprPath, fieldName);\n }\n } else {\n markUntrackedBinding();\n }\n return;\n }\n\n // ── Pattern 3: const content = useIntlayer('key') ─────────────────────────\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isIdentifier(parentNode.id)\n ) {\n const variableName = parentNode.id.name;\n const variableBinding = callExpressionPath.scope.getBinding(variableName);\n\n if (!variableBinding) {\n markUntrackedBinding();\n return;\n }\n\n const accessedTopLevelFieldNames = new Set<string>();\n let hasUntrackedReferenceAccess = false;\n\n for (const variableReferencePath of variableBinding.referencePaths) {\n const referenceParentNode = variableReferencePath.parent;\n\n if (\n (babelTypes.isMemberExpression(referenceParentNode) ||\n babelTypes.isOptionalMemberExpression(referenceParentNode)) &&\n (referenceParentNode as BabelTypes.MemberExpression).object ===\n variableReferencePath.node\n ) {\n const memberExpressionNode =\n referenceParentNode as BabelTypes.MemberExpression;\n let fieldName: string | undefined;\n\n if (\n !memberExpressionNode.computed &&\n babelTypes.isIdentifier(memberExpressionNode.property)\n ) {\n fieldName = memberExpressionNode.property.name;\n } else if (\n memberExpressionNode.computed &&\n babelTypes.isStringLiteral(memberExpressionNode.property)\n ) {\n fieldName = memberExpressionNode.property.value;\n }\n\n if (fieldName) {\n accessedTopLevelFieldNames.add(fieldName);\n\n // Check usage of the field to look for opaque consumption\n const memberExprPath = variableReferencePath.parentPath;\n if (memberExprPath) {\n analyzeOpaqueUsage(memberExprPath, fieldName);\n }\n } else {\n // Dynamic computed access – cannot resolve statically\n hasUntrackedReferenceAccess = true;\n break;\n }\n } else if (babelTypes.isArrayExpression(referenceParentNode)) {\n // Ignore array literals (e.g. [content]) – uncommon but benign\n } else {\n // Variable used in a non-member-access context (spread, function arg, etc.)\n hasUntrackedReferenceAccess = true;\n break;\n }\n }\n\n if (hasUntrackedReferenceAccess) {\n markUntrackedBinding();\n } else if (isSfcFile) {\n // Vue / Svelte SFC: defer to the framework-specific extractor because\n // Babel scope analysis cannot see through `.value` or `$` indirection.\n deferFrameworkAnalysis(variableName);\n } else if (variableBinding.referencePaths.length === 0) {\n // Non-SFC file with no visible references – conservatively keep all fields.\n markUntrackedBinding();\n } else {\n recordFieldUsage(pruneContext, dictionaryKey, accessedTopLevelFieldNames);\n }\n return;\n }\n\n // ── Pattern 4: bare call – result is discarded ─────────────────────────────\n if (babelTypes.isExpressionStatement(parentNode)) {\n return; // no usage to record\n }\n\n // ── Fallback: result passed as argument, used in ternary, etc. ─────────────\n markUntrackedBinding();\n};\n\n/**\n * Creates a Babel plugin that traverses source files and records which\n * top-level dictionary fields each `useIntlayer` / `getIntlayer` call-site\n * accesses. Results are accumulated into `pruneContext`.\n *\n * This plugin is analysis-only: it does not transform the code (`code: false`\n * should be passed to `transformAsync` when using it).\n */\nexport const makeUsageAnalyzerBabelPlugin =\n (pruneContext: PruneContext) =>\n ({ types: babelTypes }: { types: typeof BabelTypes }): PluginObj => ({\n name: 'intlayer-usage-analyzer',\n visitor: {\n Program: {\n exit: (programPath, state: PluginPass) => {\n const currentSourceFilePath =\n state.file.opts.filename ?? 'unknown file';\n const isSfcFile =\n currentSourceFilePath.endsWith('.vue') ||\n currentSourceFilePath.endsWith('.svelte');\n\n // Phase 1: collect local aliases for useIntlayer / getIntlayer\n const intlayerCallerLocalNameMap = new Map<string, string>();\n\n programPath.traverse({\n ImportDeclaration: (importDeclarationPath) => {\n for (const importSpecifier of importDeclarationPath.node\n .specifiers) {\n if (!babelTypes.isImportSpecifier(importSpecifier)) continue;\n\n const importedName = babelTypes.isIdentifier(\n importSpecifier.imported\n )\n ? importSpecifier.imported.name\n : (importSpecifier.imported as BabelTypes.StringLiteral)\n .value;\n\n if (\n INTLAYER_CALLER_NAMES.includes(\n importedName as IntlayerCallerName\n )\n ) {\n intlayerCallerLocalNameMap.set(\n importSpecifier.local.name,\n importedName\n );\n }\n }\n },\n });\n\n if (intlayerCallerLocalNameMap.size === 0) return;\n\n // Phase 2: analyse each call-site\n programPath.traverse({\n CallExpression: (callExpressionPath) => {\n const calleeNode = callExpressionPath.node.callee;\n let localCallerName: string | undefined;\n\n if (babelTypes.isIdentifier(calleeNode)) {\n localCallerName = calleeNode.name;\n } else if (\n babelTypes.isMemberExpression(calleeNode) &&\n babelTypes.isIdentifier(calleeNode.property)\n ) {\n localCallerName = calleeNode.property.name;\n }\n\n if (\n !localCallerName ||\n !intlayerCallerLocalNameMap.has(localCallerName)\n )\n return;\n\n const callArguments = callExpressionPath.node.arguments;\n if (callArguments.length === 0) return;\n\n const firstArgument = callArguments[0];\n let dictionaryKey: string | undefined;\n\n if (babelTypes.isStringLiteral(firstArgument)) {\n dictionaryKey = firstArgument.value;\n } else if (\n babelTypes.isTemplateLiteral(firstArgument) &&\n firstArgument.expressions.length === 0 &&\n firstArgument.quasis.length === 1\n ) {\n dictionaryKey =\n firstArgument.quasis[0].value.cooked ??\n firstArgument.quasis[0].value.raw;\n }\n\n if (!dictionaryKey) return; // dynamic key – cannot resolve which dictionary\n\n analyzeCallExpressionUsage(\n babelTypes,\n pruneContext,\n callExpressionPath,\n dictionaryKey,\n currentSourceFilePath,\n isSfcFile\n );\n },\n });\n },\n },\n },\n });\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"babelProcessor.mjs","names":[],"sources":["../../../src/extractContent/babelProcessor.ts"],"sourcesContent":["import _traverse, { type NodePath } from '@babel/traverse';\nimport * as t from '@babel/types';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { resolveDictionaryKey } from '../extractContent/utils';\nimport {\n ATTRIBUTES_TO_EXTRACT,\n getComponentName,\n getExistingIntlayerInfo,\n getOrGenerateKey,\n shouldExtract,\n} from './utils';\n\nexport type BabelReplacement = {\n path: NodePath;\n key: string;\n type:\n | 'jsx-text'\n | 'jsx-attribute'\n | 'string-literal'\n | 'jsx-insertion'\n | 'jsx-text-combined'\n | '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 * 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({ type: 'text', value: expr.quasis[i].value.raw });\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 isSolid: boolean;\n} => {\n const extractedContent: Record<string, Record<string, string>> = {};\n const replacements: BabelReplacement[] = [];\n const handledNodes = new Set<t.Node>();\n const componentKeyMap = new Map<t.Node, string>();\n const hookMap = new Map<t.Node, 'useIntlayer' | 'getIntlayer'>();\n const usedKeysInFile = new Set<string>();\n let globalFileKey: string | undefined;\n\n const componentPaths: NodePath[] = [];\n\n traverse(ast, {\n Program(path) {\n componentPaths.push(path);\n },\n FunctionDeclaration(path) {\n componentPaths.push(path);\n },\n ArrowFunctionExpression(path) {\n componentPaths.push(path);\n },\n FunctionExpression(path) {\n componentPaths.push(path);\n },\n });\n\n for (const path of componentPaths) {\n const existingInfo = getExistingIntlayerInfo(path);\n\n if (existingInfo) {\n componentKeyMap.set(path.node, existingInfo.key);\n usedKeysInFile.add(existingInfo.key);\n hookMap.set(path.node, existingInfo.hook);\n } else {\n if (path.isProgram()) {\n if (!globalFileKey) {\n globalFileKey = resolveDictionaryKey(\n defaultKey,\n filePath,\n configuration,\n unmergedDictionaries,\n usedKeysInFile\n );\n usedKeysInFile.add(globalFileKey);\n }\n componentKeyMap.set(path.node, globalFileKey);\n hookMap.set(path.node, 'getIntlayer');\n } else {\n if (!globalFileKey) {\n globalFileKey = resolveDictionaryKey(\n defaultKey,\n filePath,\n configuration,\n unmergedDictionaries,\n usedKeysInFile\n );\n usedKeysInFile.add(globalFileKey);\n }\n componentKeyMap.set(path.node, globalFileKey);\n\n const compName = getComponentName(path);\n const isComponent = compName ? /^[A-Z]/.test(compName) : false;\n const isHook = compName ? /^use[A-Z]/.test(compName) : false;\n hookMap.set(\n path.node,\n isComponent || isHook ? 'useIntlayer' : 'getIntlayer'\n );\n }\n }\n }\n\n const getComponentKeyForPath = (path: NodePath): string => {\n let current: NodePath | null = path;\n while (current) {\n if (componentKeyMap.has(current.node)) {\n return componentKeyMap.get(current.node)!;\n }\n current = current.parentPath;\n }\n return globalFileKey || defaultKey;\n };\n\n traverse(ast, {\n JSXElement(path) {\n if (handledNodes.has(path.node)) return;\n\n handleJsxInsertionBabel(\n path,\n fileCode,\n existingKeys,\n getComponentKeyForPath,\n extractedContent,\n replacements,\n handledNodes\n );\n },\n JSXFragment(path) {\n if (handledNodes.has(path.node)) return;\n\n handleJsxInsertionBabel(\n path,\n fileCode,\n existingKeys,\n getComponentKeyForPath,\n extractedContent,\n replacements,\n handledNodes\n );\n },\n JSXText(path) {\n if (handledNodes.has(path.node)) return;\n\n const text = path.node.value;\n\n if (shouldExtract(text)) {\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n text.replace(/\\s+/g, ' ').trim(),\n componentKey,\n existingKeys,\n extractedContent\n );\n replacements.push({ path, key, type: 'jsx-text', componentKey });\n }\n },\n JSXAttribute(path) {\n if (handledNodes.has(path.node)) return;\n\n const name = path.node.name.name;\n\n if (\n typeof name !== 'string' ||\n !ATTRIBUTES_TO_EXTRACT.includes(name as any)\n )\n return;\n const value = path.node.value;\n\n if (t.isStringLiteral(value) && shouldExtract(value.value)) {\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n value.value.trim(),\n componentKey,\n existingKeys,\n extractedContent\n );\n replacements.push({ path, key, type: 'jsx-attribute', componentKey });\n }\n },\n StringLiteral(path) {\n if (handledNodes.has(path.node)) return;\n\n const text = path.node.value;\n\n if (!shouldExtract(text)) return;\n\n const parent = path.parentPath;\n\n if (\n parent.isImportDeclaration() ||\n parent.isImportSpecifier() ||\n parent.isExportDeclaration()\n )\n return;\n\n if (parent.isJSXAttribute()) return;\n\n if (\n parent.isCallExpression() &&\n t.isMemberExpression(parent.node.callee)\n ) {\n if (\n t.isIdentifier(parent.node.callee.object) &&\n parent.node.callee.object.name === 'console' &&\n t.isIdentifier(parent.node.callee.property) &&\n parent.node.callee.property.name === 'log'\n ) {\n return;\n }\n }\n\n if (parent.isObjectProperty() && parent.node.key === path.node) return;\n\n if (parent.isMemberExpression() && parent.node.property === path.node)\n return;\n\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n text.trim(),\n componentKey,\n existingKeys,\n extractedContent\n );\n replacements.push({ path, key, type: 'string-literal', componentKey });\n },\n 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 const componentsNeedingHooks = new Set<NodePath>();\n for (const componentPath of componentPaths) {\n if (componentPath.isProgram()) {\n const hasDirectReplacements = replacements.some((replacement) => {\n let current: NodePath | null = replacement.path;\n while (current) {\n if (current.node === componentPath.node) {\n return true;\n }\n const isOtherComponent = componentPaths.some(\n (p) => p !== componentPath && p.node === current?.node\n );\n if (isOtherComponent) {\n return false;\n }\n current = current.parentPath;\n }\n return false;\n });\n\n if (hasDirectReplacements) {\n componentsNeedingHooks.add(componentPath);\n }\n continue;\n }\n\n const hasReplacements = replacements.some((replacement) => {\n let current: NodePath | null = replacement.path;\n while (current) {\n if (current.node === componentPath.node) return true;\n\n current = current.parentPath;\n }\n return false;\n });\n\n if (hasReplacements) {\n const key = componentKeyMap.get(componentPath.node)!;\n let ancestorProvidesKey = false;\n let currentPath: NodePath | null = componentPath.parentPath;\n while (currentPath) {\n const ancestorPath = componentPaths.find(\n (path) => path.node === currentPath?.node\n );\n\n if (ancestorPath && !ancestorPath.isProgram()) {\n const ancestorKey = componentKeyMap.get(ancestorPath.node);\n\n if (ancestorKey === key) {\n const ancestorHasReplacements = replacements.some((replacement) => {\n let rPath: NodePath | null = replacement.path;\n while (rPath) {\n if (rPath.node === ancestorPath.node) return true;\n\n rPath = rPath.parentPath;\n }\n return false;\n });\n const existingInfo = getExistingIntlayerInfo(ancestorPath);\n\n if (ancestorHasReplacements || existingInfo) {\n ancestorProvidesKey = true;\n break;\n }\n }\n }\n currentPath = currentPath.parentPath;\n }\n\n if (!ancestorProvidesKey) {\n componentsNeedingHooks.add(componentPath);\n }\n }\n }\n\n return {\n extractedContent,\n replacements,\n componentsNeedingHooks,\n componentKeyMap,\n componentPaths,\n hookMap,\n isSolid: false,\n };\n};\n\n/**\n * High-level function to extract content from TS/JS/JSX/TSX AST.\n */\nexport const extractTsContent = (\n ast: t.File,\n fileCode: string,\n existingKeys: Set<string>,\n configuration: IntlayerConfig,\n filePath: string,\n unmergedDictionaries: Record<string, unknown> = {}\n): {\n extractedContent: Record<string, string>;\n replacements: BabelReplacement[];\n} => {\n const { extractedContent, replacements } = extractBabelContentForComponents(\n ast,\n fileCode,\n existingKeys,\n 'default',\n configuration,\n filePath,\n unmergedDictionaries\n );\n\n const flatContent: Record<string, string> = {};\n for (const group of Object.values(extractedContent)) {\n Object.assign(flatContent, group);\n }\n\n return { extractedContent: flatContent, replacements };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"babelProcessor.mjs","names":[],"sources":["../../../src/extractContent/babelProcessor.ts"],"sourcesContent":["import _traverse, { type NodePath } from '@babel/traverse';\nimport * as t from '@babel/types';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { resolveDictionaryKey } from '../extractContent/utils';\nimport {\n ATTRIBUTES_TO_EXTRACT,\n getComponentName,\n getExistingIntlayerInfo,\n getOrGenerateKey,\n shouldExtract,\n} from './utils';\n\nexport type BabelReplacement = {\n path: NodePath;\n key: string;\n type:\n | 'jsx-text'\n | 'jsx-attribute'\n | 'string-literal'\n | 'jsx-insertion'\n | 'jsx-text-combined'\n | '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 * 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({ type: 'text', value: expr.quasis[i].value.raw });\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 isSolid: boolean;\n} => {\n const extractedContent: Record<string, Record<string, string>> = {};\n const replacements: BabelReplacement[] = [];\n const handledNodes = new Set<t.Node>();\n const componentKeyMap = new Map<t.Node, string>();\n const hookMap = new Map<t.Node, 'useIntlayer' | 'getIntlayer'>();\n const usedKeysInFile = new Set<string>();\n let globalFileKey: string | undefined;\n\n const componentPaths: NodePath[] = [];\n\n traverse(ast, {\n Program(path) {\n componentPaths.push(path);\n },\n FunctionDeclaration(path) {\n componentPaths.push(path);\n },\n ArrowFunctionExpression(path) {\n componentPaths.push(path);\n },\n FunctionExpression(path) {\n componentPaths.push(path);\n },\n });\n\n for (const path of componentPaths) {\n const existingInfo = getExistingIntlayerInfo(path);\n\n if (existingInfo) {\n componentKeyMap.set(path.node, existingInfo.key);\n usedKeysInFile.add(existingInfo.key);\n hookMap.set(path.node, existingInfo.hook);\n } else {\n if (path.isProgram()) {\n if (!globalFileKey) {\n globalFileKey = resolveDictionaryKey(\n defaultKey,\n filePath,\n configuration,\n unmergedDictionaries,\n usedKeysInFile\n );\n usedKeysInFile.add(globalFileKey);\n }\n componentKeyMap.set(path.node, globalFileKey);\n hookMap.set(path.node, 'getIntlayer');\n } else {\n if (!globalFileKey) {\n globalFileKey = resolveDictionaryKey(\n defaultKey,\n filePath,\n configuration,\n unmergedDictionaries,\n usedKeysInFile\n );\n usedKeysInFile.add(globalFileKey);\n }\n componentKeyMap.set(path.node, globalFileKey);\n\n const compName = getComponentName(path);\n const isComponent = compName ? /^[A-Z]/.test(compName) : false;\n const isHook = compName ? /^use[A-Z]/.test(compName) : false;\n hookMap.set(\n path.node,\n isComponent || isHook ? 'useIntlayer' : 'getIntlayer'\n );\n }\n }\n }\n\n const getComponentKeyForPath = (path: NodePath): string => {\n let current: NodePath | null = path;\n while (current) {\n if (componentKeyMap.has(current.node)) {\n return componentKeyMap.get(current.node)!;\n }\n current = current.parentPath;\n }\n return globalFileKey || defaultKey;\n };\n\n traverse(ast, {\n JSXElement(path) {\n if (handledNodes.has(path.node)) return;\n\n handleJsxInsertionBabel(\n path,\n fileCode,\n existingKeys,\n getComponentKeyForPath,\n extractedContent,\n replacements,\n handledNodes\n );\n },\n JSXFragment(path) {\n if (handledNodes.has(path.node)) return;\n\n handleJsxInsertionBabel(\n path,\n fileCode,\n existingKeys,\n getComponentKeyForPath,\n extractedContent,\n replacements,\n handledNodes\n );\n },\n JSXText(path) {\n if (handledNodes.has(path.node)) return;\n\n const text = path.node.value;\n\n if (shouldExtract(text)) {\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n text.replace(/\\s+/g, ' ').trim(),\n componentKey,\n existingKeys,\n extractedContent\n );\n replacements.push({ path, key, type: 'jsx-text', componentKey });\n }\n },\n JSXAttribute(path) {\n if (handledNodes.has(path.node)) return;\n\n const name = path.node.name.name;\n\n if (\n typeof name !== 'string' ||\n !ATTRIBUTES_TO_EXTRACT.includes(name as any)\n )\n return;\n const value = path.node.value;\n\n if (t.isStringLiteral(value) && shouldExtract(value.value)) {\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n value.value.trim(),\n componentKey,\n existingKeys,\n extractedContent\n );\n replacements.push({ path, key, type: 'jsx-attribute', componentKey });\n }\n },\n StringLiteral(path) {\n if (handledNodes.has(path.node)) return;\n\n const text = path.node.value;\n\n if (!shouldExtract(text)) return;\n\n const parent = path.parentPath;\n\n if (\n parent.isImportDeclaration() ||\n parent.isImportSpecifier() ||\n parent.isExportDeclaration()\n )\n return;\n\n if (parent.isJSXAttribute()) return;\n\n if (\n parent.isCallExpression() &&\n t.isMemberExpression(parent.node.callee)\n ) {\n if (\n t.isIdentifier(parent.node.callee.object) &&\n parent.node.callee.object.name === 'console' &&\n t.isIdentifier(parent.node.callee.property) &&\n parent.node.callee.property.name === 'log'\n ) {\n return;\n }\n }\n\n if (parent.isObjectProperty() && parent.node.key === path.node) return;\n\n if (parent.isMemberExpression() && parent.node.property === path.node)\n return;\n\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n text.trim(),\n componentKey,\n existingKeys,\n extractedContent\n );\n replacements.push({ path, key, type: 'string-literal', componentKey });\n },\n 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 const componentsNeedingHooks = new Set<NodePath>();\n for (const componentPath of componentPaths) {\n if (componentPath.isProgram()) {\n const hasDirectReplacements = replacements.some((replacement) => {\n let current: NodePath | null = replacement.path;\n while (current) {\n if (current.node === componentPath.node) {\n return true;\n }\n const isOtherComponent = componentPaths.some(\n (p) => p !== componentPath && p.node === current?.node\n );\n if (isOtherComponent) {\n return false;\n }\n current = current.parentPath;\n }\n return false;\n });\n\n if (hasDirectReplacements) {\n componentsNeedingHooks.add(componentPath);\n }\n continue;\n }\n\n const hasReplacements = replacements.some((replacement) => {\n let current: NodePath | null = replacement.path;\n while (current) {\n if (current.node === componentPath.node) return true;\n\n current = current.parentPath;\n }\n return false;\n });\n\n if (hasReplacements) {\n const key = componentKeyMap.get(componentPath.node)!;\n let ancestorProvidesKey = false;\n let currentPath: NodePath | null = componentPath.parentPath;\n while (currentPath) {\n const ancestorPath = componentPaths.find(\n (path) => path.node === currentPath?.node\n );\n\n if (ancestorPath && !ancestorPath.isProgram()) {\n const ancestorKey = componentKeyMap.get(ancestorPath.node);\n\n if (ancestorKey === key) {\n const ancestorHasReplacements = replacements.some((replacement) => {\n let rPath: NodePath | null = replacement.path;\n while (rPath) {\n if (rPath.node === ancestorPath.node) return true;\n\n rPath = rPath.parentPath;\n }\n return false;\n });\n const existingInfo = getExistingIntlayerInfo(ancestorPath);\n\n if (ancestorHasReplacements || existingInfo) {\n ancestorProvidesKey = true;\n break;\n }\n }\n }\n currentPath = currentPath.parentPath;\n }\n\n if (!ancestorProvidesKey) {\n componentsNeedingHooks.add(componentPath);\n }\n }\n }\n\n return {\n extractedContent,\n replacements,\n componentsNeedingHooks,\n componentKeyMap,\n componentPaths,\n hookMap,\n isSolid: false,\n };\n};\n\n/**\n * High-level function to extract content from TS/JS/JSX/TSX AST.\n */\nexport const extractTsContent = (\n ast: t.File,\n fileCode: string,\n existingKeys: Set<string>,\n configuration: IntlayerConfig,\n filePath: string,\n unmergedDictionaries: Record<string, unknown> = {}\n): {\n extractedContent: Record<string, string>;\n replacements: BabelReplacement[];\n} => {\n const { extractedContent, replacements } = extractBabelContentForComponents(\n ast,\n fileCode,\n existingKeys,\n 'default',\n configuration,\n filePath,\n unmergedDictionaries\n );\n\n const flatContent: Record<string, string> = {};\n for (const group of Object.values(extractedContent)) {\n Object.assign(flatContent, group);\n }\n\n return { extractedContent: flatContent, replacements };\n};\n"],"mappings":"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"}
@@ -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 {\n buildDictionary,\n ensureIntlayerBundle,\n loadContentDeclaration,\n writeContentDeclaration,\n} from '@intlayer/chokidar/build';\nimport { insertContentInDictionary } from '@intlayer/core/plugins';\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: string[] = [];\n const regex = /\\{\\{([^}]+)\\}\\}/g;\n let match: RegExpExecArray | null;\n for (\n let result = regex.exec(template);\n result !== null;\n result = regex.exec(template)\n ) {\n match = result;\n const field = match[1].trim();\n if (!fields.includes(field)) fields.push(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 finalContent[key] = mergedContent[key];\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 finalContent[key] = mergedContent[key];\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 {\n buildDictionary,\n ensureIntlayerBundle,\n loadContentDeclaration,\n writeContentDeclaration,\n} from '@intlayer/chokidar/build';\nimport { insertContentInDictionary } from '@intlayer/core/plugins';\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: string[] = [];\n const regex = /\\{\\{([^}]+)\\}\\}/g;\n let match: RegExpExecArray | null;\n for (\n let result = regex.exec(template);\n result !== null;\n result = regex.exec(template)\n ) {\n match = result;\n const field = match[1].trim();\n if (!fields.includes(field)) fields.push(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 finalContent[key] = mergedContent[key];\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 finalContent[key] = mergedContent[key];\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 +1 @@
1
- {"version":3,"file":"extractContent.mjs","names":[],"sources":["../../../src/extractContent/extractContent.ts"],"sourcesContent":["import { execSync } from 'node:child_process';\nimport { readFileSync } from 'node:fs';\nimport { extname, relative } from 'node:path';\nimport type * as t from '@babel/types';\nimport { detectFormatCommand } from '@intlayer/chokidar/cli';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizePath, getAppLogger } from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport { getProjectRequire } from '@intlayer/config/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport { extractTsContent } from './babelProcessor';\nimport { writeContentHelper } from './contentWriter';\nimport { processTsxFile } from './processTsxFile';\nimport {\n ATTRIBUTES_TO_EXTRACT,\n extractDictionaryKeyFromPath,\n type PackageName,\n shouldExtract,\n} from './utils';\nimport { extractDictionaryKey } from './utils/extractDictionaryKey';\nimport { generateKey } from './utils/generateKey';\n\nexport type ExtractIntlayerOptions = {\n configOptions?: GetConfigurationOptions;\n codeOnly?: boolean;\n declarationOnly?: boolean;\n unmergedDictionaries?: Record<string, unknown>;\n configuration?: IntlayerConfig;\n code?: string;\n onExtract?: (result: {\n key: string;\n content: Record<string, string>;\n filePath: string;\n }) => void | Promise<void>;\n};\n\ntype ExternalCompilerResult = {\n extractedContent: Record<string, string>;\n code: string;\n};\n\ntype ExternalCompilerOptions = {\n generateKey: typeof generateKey;\n shouldExtract: typeof shouldExtract;\n attributesToExtract: typeof ATTRIBUTES_TO_EXTRACT;\n extractDictionaryKeyFromPath: typeof extractDictionaryKeyFromPath;\n extractTsContent: (\n ast: unknown,\n code: string,\n keys: Set<string>,\n config: IntlayerConfig,\n path: string\n ) => ReturnType<typeof extractTsContent>;\n};\n\ntype VueCompiler = typeof import('@intlayer/vue-compiler');\ntype SvelteCompiler = typeof import('@intlayer/svelte-compiler');\n\n// Module caches\nlet vueCompiler: VueCompiler | null = null;\nlet svelteCompiler: SvelteCompiler | null = null;\n\ntype InternalExtractResult = {\n extractedContentMap: Record<string, Record<string, string>> | null;\n transformedCode: string | null;\n};\n\nconst formatCompilerResult = (\n componentKey: string,\n res?: ExternalCompilerResult\n): InternalExtractResult | undefined => {\n if (!res) return undefined;\n\n return {\n extractedContentMap: { [componentKey]: res.extractedContent },\n transformedCode: res.code,\n };\n};\n\ntype Dependencies = {\n vueCompiler: VueCompiler | null;\n svelteCompiler: SvelteCompiler | null;\n unmergedDictionaries: Record<string, unknown>;\n configuration: IntlayerConfig;\n};\n\nconst processFileInternal = (\n filePath: string,\n packageName: PackageName,\n options: ExtractIntlayerOptions | undefined,\n dependencies: Dependencies,\n saveComponent: boolean,\n providedComponentKey?: string\n): InternalExtractResult | undefined => {\n const fileText = options?.code ?? readFileSync(filePath, 'utf-8');\n const componentKey =\n providedComponentKey ??\n extractDictionaryKey(\n filePath,\n fileText,\n dependencies.configuration.compiler.dictionaryKeyPrefix\n );\n const ext = extname(filePath);\n\n const { vueCompiler, svelteCompiler, unmergedDictionaries, configuration } =\n dependencies;\n\n const compilerCommonOptions: ExternalCompilerOptions = {\n generateKey,\n shouldExtract,\n attributesToExtract: ATTRIBUTES_TO_EXTRACT,\n extractDictionaryKeyFromPath,\n extractTsContent: (ast, code, keys, config, path) =>\n extractTsContent(\n ast as t.File,\n code,\n keys,\n config,\n path,\n unmergedDictionaries\n ),\n };\n\n if (ext === '.vue') {\n if (!vueCompiler) {\n throw new Error(\n `Please install ${colorizePath('@intlayer/vue-compiler', ANSIColors.YELLOW)} to process Vue files.`\n );\n }\n\n const res = vueCompiler.processVueFile(\n filePath,\n componentKey,\n packageName,\n compilerCommonOptions,\n saveComponent\n );\n\n if (res) {\n return formatCompilerResult(componentKey, res);\n }\n }\n\n if (ext === '.svelte') {\n if (!svelteCompiler) {\n throw new Error(\n `Please install ${colorizePath('@intlayer/svelte-compiler', ANSIColors.YELLOW)} to process Svelte files.`\n );\n }\n\n const res = svelteCompiler.processSvelteFile(\n filePath,\n componentKey,\n packageName,\n compilerCommonOptions,\n saveComponent\n );\n\n if (res) {\n return formatCompilerResult(componentKey, res);\n }\n }\n\n if (['.tsx', '.jsx', '.ts', '.js', '.cjs', '.mjs'].includes(ext)) {\n const result = processTsxFile(\n filePath,\n componentKey,\n packageName,\n configuration,\n saveComponent,\n unmergedDictionaries,\n fileText\n );\n\n if (result) {\n return {\n extractedContentMap: result.extractedContent,\n transformedCode: result.modifiedCode,\n };\n }\n }\n\n return undefined;\n};\n\nconst handleExtractionSideEffects = async (\n extractedContentMap: Record<string, Record<string, string>>,\n filePath: string,\n options: ExtractIntlayerOptions | undefined,\n {\n configuration,\n baseDir,\n appLogger,\n }: {\n configuration: IntlayerConfig;\n baseDir: string;\n appLogger: ReturnType<typeof getAppLogger>;\n },\n saveComponent: boolean\n) => {\n if (options?.onExtract) {\n for (const [key, content] of Object.entries(extractedContentMap)) {\n await options.onExtract({ key, content, filePath });\n }\n }\n\n const writeContent = !options?.codeOnly && !options?.onExtract;\n\n if (writeContent) {\n for (const [key, content] of Object.entries(extractedContentMap)) {\n const contentFilePath = await writeContentHelper(\n content,\n key,\n filePath,\n configuration\n );\n\n const relativeContentFilePath = relative(baseDir, contentFilePath);\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Created content file: ${colorizePath(relativeContentFilePath)}`\n );\n }\n }\n\n if (saveComponent) {\n const formatCommand = detectFormatCommand(configuration);\n\n if (formatCommand) {\n try {\n execSync(formatCommand.replace('{{file}}', filePath), {\n stdio: 'inherit',\n cwd: baseDir,\n });\n } catch (error) {\n console.error(error);\n }\n }\n\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Updated component: ${colorizePath(relative(baseDir, filePath))}`\n );\n }\n};\n\ntype ExtractResult = {\n transformedCode: string | null;\n extractedContentMap: Record<string, Record<string, string>>;\n};\n\ntype ExtractContext = {\n configuration: IntlayerConfig;\n appLogger: ReturnType<typeof getAppLogger>;\n baseDir: string;\n unmergedDictionaries: Record<string, unknown>;\n saveComponent: boolean;\n componentExtension: string;\n};\n\nconst buildContext = (\n filePath: string,\n options: ExtractIntlayerOptions | undefined\n): ExtractContext => {\n const configuration =\n options?.configuration ?? getConfiguration(options?.configOptions);\n const appLogger = getAppLogger(configuration);\n const { baseDir } = configuration.system;\n const unmergedDictionaries =\n options?.unmergedDictionaries ?? getUnmergedDictionaries(configuration);\n const saveComponent = !options?.declarationOnly;\n const componentExtension = extname(filePath);\n\n return {\n configuration,\n appLogger,\n baseDir,\n unmergedDictionaries,\n saveComponent,\n componentExtension,\n };\n};\n\nexport const extractContent = async (\n filePath: string,\n packageName: PackageName,\n options?: ExtractIntlayerOptions\n): Promise<ExtractResult | undefined> => {\n const {\n configuration,\n appLogger,\n baseDir,\n unmergedDictionaries,\n saveComponent,\n componentExtension,\n } = buildContext(filePath, options);\n\n if (componentExtension === '.vue' && !vueCompiler) {\n try {\n vueCompiler = await import('@intlayer/vue-compiler');\n } catch {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Install ${colorizePath('@intlayer/vue-compiler', ANSIColors.YELLOW)} to process Vue files.`\n );\n }\n }\n\n if (componentExtension === '.svelte' && !svelteCompiler) {\n try {\n svelteCompiler = await import('@intlayer/svelte-compiler');\n } catch {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Install ${colorizePath('@intlayer/svelte-compiler', ANSIColors.YELLOW)} to process Svelte files.`\n );\n }\n }\n\n const fileText = options?.code ?? readFileSync(filePath, 'utf-8');\n const dictionaryKey = extractDictionaryKey(\n filePath,\n fileText,\n configuration.compiler.dictionaryKeyPrefix\n );\n\n const result = processFileInternal(\n filePath,\n packageName,\n options,\n { vueCompiler, svelteCompiler, unmergedDictionaries, configuration },\n saveComponent,\n dictionaryKey\n );\n\n if (!result?.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 } from 'node:fs';\nimport { extname, relative } from 'node:path';\nimport type * as t from '@babel/types';\nimport { detectFormatCommand } from '@intlayer/chokidar/cli';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizePath, getAppLogger } from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport { getProjectRequire } from '@intlayer/config/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport { extractTsContent } from './babelProcessor';\nimport { writeContentHelper } from './contentWriter';\nimport { processTsxFile } from './processTsxFile';\nimport {\n ATTRIBUTES_TO_EXTRACT,\n extractDictionaryKeyFromPath,\n type PackageName,\n shouldExtract,\n} from './utils';\nimport { extractDictionaryKey } from './utils/extractDictionaryKey';\nimport { generateKey } from './utils/generateKey';\n\nexport type ExtractIntlayerOptions = {\n configOptions?: GetConfigurationOptions;\n codeOnly?: boolean;\n declarationOnly?: boolean;\n unmergedDictionaries?: Record<string, unknown>;\n configuration?: IntlayerConfig;\n code?: string;\n onExtract?: (result: {\n key: string;\n content: Record<string, string>;\n filePath: string;\n }) => void | Promise<void>;\n};\n\ntype ExternalCompilerResult = {\n extractedContent: Record<string, string>;\n code: string;\n};\n\ntype ExternalCompilerOptions = {\n generateKey: typeof generateKey;\n shouldExtract: typeof shouldExtract;\n attributesToExtract: typeof ATTRIBUTES_TO_EXTRACT;\n extractDictionaryKeyFromPath: typeof extractDictionaryKeyFromPath;\n extractTsContent: (\n ast: unknown,\n code: string,\n keys: Set<string>,\n config: IntlayerConfig,\n path: string\n ) => ReturnType<typeof extractTsContent>;\n};\n\ntype VueCompiler = typeof import('@intlayer/vue-compiler');\ntype SvelteCompiler = typeof import('@intlayer/svelte-compiler');\n\n// Module caches\nlet vueCompiler: VueCompiler | null = null;\nlet svelteCompiler: SvelteCompiler | null = null;\n\ntype InternalExtractResult = {\n extractedContentMap: Record<string, Record<string, string>> | null;\n transformedCode: string | null;\n};\n\nconst formatCompilerResult = (\n componentKey: string,\n res?: ExternalCompilerResult\n): InternalExtractResult | undefined => {\n if (!res) return undefined;\n\n return {\n extractedContentMap: { [componentKey]: res.extractedContent },\n transformedCode: res.code,\n };\n};\n\ntype Dependencies = {\n vueCompiler: VueCompiler | null;\n svelteCompiler: SvelteCompiler | null;\n unmergedDictionaries: Record<string, unknown>;\n configuration: IntlayerConfig;\n};\n\nconst processFileInternal = (\n filePath: string,\n packageName: PackageName,\n options: ExtractIntlayerOptions | undefined,\n dependencies: Dependencies,\n saveComponent: boolean,\n providedComponentKey?: string\n): InternalExtractResult | undefined => {\n const fileText = options?.code ?? readFileSync(filePath, 'utf-8');\n const componentKey =\n providedComponentKey ??\n extractDictionaryKey(\n filePath,\n fileText,\n dependencies.configuration.compiler.dictionaryKeyPrefix\n );\n const ext = extname(filePath);\n\n const { vueCompiler, svelteCompiler, unmergedDictionaries, configuration } =\n dependencies;\n\n const compilerCommonOptions: ExternalCompilerOptions = {\n generateKey,\n shouldExtract,\n attributesToExtract: ATTRIBUTES_TO_EXTRACT,\n extractDictionaryKeyFromPath,\n extractTsContent: (ast, code, keys, config, path) =>\n extractTsContent(\n ast as t.File,\n code,\n keys,\n config,\n path,\n unmergedDictionaries\n ),\n };\n\n if (ext === '.vue') {\n if (!vueCompiler) {\n throw new Error(\n `Please install ${colorizePath('@intlayer/vue-compiler', ANSIColors.YELLOW)} to process Vue files.`\n );\n }\n\n const res = vueCompiler.processVueFile(\n filePath,\n componentKey,\n packageName,\n compilerCommonOptions,\n saveComponent\n );\n\n if (res) {\n return formatCompilerResult(componentKey, res);\n }\n }\n\n if (ext === '.svelte') {\n if (!svelteCompiler) {\n throw new Error(\n `Please install ${colorizePath('@intlayer/svelte-compiler', ANSIColors.YELLOW)} to process Svelte files.`\n );\n }\n\n const res = svelteCompiler.processSvelteFile(\n filePath,\n componentKey,\n packageName,\n compilerCommonOptions,\n saveComponent\n );\n\n if (res) {\n return formatCompilerResult(componentKey, res);\n }\n }\n\n if (['.tsx', '.jsx', '.ts', '.js', '.cjs', '.mjs'].includes(ext)) {\n const result = processTsxFile(\n filePath,\n componentKey,\n packageName,\n configuration,\n saveComponent,\n unmergedDictionaries,\n fileText\n );\n\n if (result) {\n return {\n extractedContentMap: result.extractedContent,\n transformedCode: result.modifiedCode,\n };\n }\n }\n\n return undefined;\n};\n\nconst handleExtractionSideEffects = async (\n extractedContentMap: Record<string, Record<string, string>>,\n filePath: string,\n options: ExtractIntlayerOptions | undefined,\n {\n configuration,\n baseDir,\n appLogger,\n }: {\n configuration: IntlayerConfig;\n baseDir: string;\n appLogger: ReturnType<typeof getAppLogger>;\n },\n saveComponent: boolean\n) => {\n if (options?.onExtract) {\n for (const [key, content] of Object.entries(extractedContentMap)) {\n await options.onExtract({ key, content, filePath });\n }\n }\n\n const writeContent = !options?.codeOnly && !options?.onExtract;\n\n if (writeContent) {\n for (const [key, content] of Object.entries(extractedContentMap)) {\n const contentFilePath = await writeContentHelper(\n content,\n key,\n filePath,\n configuration\n );\n\n const relativeContentFilePath = relative(baseDir, contentFilePath);\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Created content file: ${colorizePath(relativeContentFilePath)}`\n );\n }\n }\n\n if (saveComponent) {\n const formatCommand = detectFormatCommand(configuration);\n\n if (formatCommand) {\n try {\n execSync(formatCommand.replace('{{file}}', filePath), {\n stdio: 'inherit',\n cwd: baseDir,\n });\n } catch (error) {\n console.error(error);\n }\n }\n\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Updated component: ${colorizePath(relative(baseDir, filePath))}`\n );\n }\n};\n\ntype ExtractResult = {\n transformedCode: string | null;\n extractedContentMap: Record<string, Record<string, string>>;\n};\n\ntype ExtractContext = {\n configuration: IntlayerConfig;\n appLogger: ReturnType<typeof getAppLogger>;\n baseDir: string;\n unmergedDictionaries: Record<string, unknown>;\n saveComponent: boolean;\n componentExtension: string;\n};\n\nconst buildContext = (\n filePath: string,\n options: ExtractIntlayerOptions | undefined\n): ExtractContext => {\n const configuration =\n options?.configuration ?? getConfiguration(options?.configOptions);\n const appLogger = getAppLogger(configuration);\n const { baseDir } = configuration.system;\n const unmergedDictionaries =\n options?.unmergedDictionaries ?? getUnmergedDictionaries(configuration);\n const saveComponent = !options?.declarationOnly;\n const componentExtension = extname(filePath);\n\n return {\n configuration,\n appLogger,\n baseDir,\n unmergedDictionaries,\n saveComponent,\n componentExtension,\n };\n};\n\nexport const extractContent = async (\n filePath: string,\n packageName: PackageName,\n options?: ExtractIntlayerOptions\n): Promise<ExtractResult | undefined> => {\n const {\n configuration,\n appLogger,\n baseDir,\n unmergedDictionaries,\n saveComponent,\n componentExtension,\n } = buildContext(filePath, options);\n\n if (componentExtension === '.vue' && !vueCompiler) {\n try {\n vueCompiler = await import('@intlayer/vue-compiler');\n } catch {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Install ${colorizePath('@intlayer/vue-compiler', ANSIColors.YELLOW)} to process Vue files.`\n );\n }\n }\n\n if (componentExtension === '.svelte' && !svelteCompiler) {\n try {\n svelteCompiler = await import('@intlayer/svelte-compiler');\n } catch {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Install ${colorizePath('@intlayer/svelte-compiler', ANSIColors.YELLOW)} to process Svelte files.`\n );\n }\n }\n\n const fileText = options?.code ?? readFileSync(filePath, 'utf-8');\n const dictionaryKey = extractDictionaryKey(\n filePath,\n fileText,\n configuration.compiler.dictionaryKeyPrefix\n );\n\n const result = processFileInternal(\n filePath,\n packageName,\n options,\n { vueCompiler, svelteCompiler, unmergedDictionaries, configuration },\n saveComponent,\n dictionaryKey\n );\n\n if (!result?.extractedContentMap) {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} No extractable text found in ${colorizePath(relative(baseDir, filePath))}`,\n { isVerbose: true }\n );\n return undefined;\n }\n\n await handleExtractionSideEffects(\n result.extractedContentMap,\n filePath,\n options,\n { configuration, baseDir, appLogger },\n saveComponent\n );\n\n return {\n transformedCode: result.transformedCode,\n extractedContentMap: result.extractedContentMap,\n };\n};\n\n/**\n * Synchronous variant of `extractContent` — used by the Babel plugin which\n * runs in a sync transform context (e.g. Next.js / Webpack).\n *\n * Differences from `extractContent`:\n * - Loads external compilers (Vue, Svelte) via `require()` instead of `import()`\n * - Fires `onExtract` callbacks as fire-and-forget (cannot await in sync context)\n * - Does NOT write dictionary files or run the code formatter; callers that\n * need persistence should supply an `onExtract` callback\n */\nexport const extractContentSync = (\n filePath: string,\n packageName: PackageName,\n options?: ExtractIntlayerOptions\n): ExtractResult | undefined => {\n const {\n configuration,\n appLogger,\n baseDir,\n unmergedDictionaries,\n saveComponent,\n componentExtension,\n } = buildContext(filePath, options);\n\n const requireFn = getProjectRequire();\n\n if (componentExtension === '.vue' && !vueCompiler) {\n try {\n vueCompiler = requireFn('@intlayer/vue-compiler') as VueCompiler;\n } catch {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Install ${colorizePath('@intlayer/vue-compiler', ANSIColors.YELLOW)} to process Vue files.`\n );\n }\n }\n\n if (componentExtension === '.svelte' && !svelteCompiler) {\n try {\n svelteCompiler = requireFn('@intlayer/svelte-compiler') as SvelteCompiler;\n } catch {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Install ${colorizePath('@intlayer/svelte-compiler', ANSIColors.YELLOW)} to process Svelte files.`\n );\n }\n }\n\n const result = processFileInternal(\n filePath,\n packageName,\n options,\n { vueCompiler, svelteCompiler, unmergedDictionaries, configuration },\n saveComponent\n );\n\n if (!result?.extractedContentMap) {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} No extractable text found in ${colorizePath(relative(baseDir, filePath))}`,\n { isVerbose: true }\n );\n return undefined;\n }\n\n const { extractedContentMap, transformedCode } = result;\n\n if (options?.onExtract) {\n for (const [key, content] of Object.entries(extractedContentMap)) {\n void options.onExtract({ key, content, filePath });\n }\n }\n\n return { transformedCode, extractedContentMap };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AA+DA,IAAI,cAAkC;AACtC,IAAI,iBAAwC;AAO5C,MAAM,wBACJ,cACA,QACsC;AACtC,KAAI,CAAC,IAAK,QAAO;AAEjB,QAAO;EACL,qBAAqB,GAAG,eAAe,IAAI,kBAAkB;EAC7D,iBAAiB,IAAI;EACtB;;AAUH,MAAM,uBACJ,UACA,aACA,SACA,cACA,eACA,yBACsC;CACtC,MAAM,WAAW,SAAS,QAAQ,aAAa,UAAU,QAAQ;CACjE,MAAM,eACJ,wBACA,qBACE,UACA,UACA,aAAa,cAAc,SAAS,oBACrC;CACH,MAAM,MAAM,QAAQ,SAAS;CAE7B,MAAM,EAAE,aAAa,gBAAgB,sBAAsB,kBACzD;CAEF,MAAM,wBAAiD;EACrD;EACA;EACA,qBAAqB;EACrB;EACA,mBAAmB,KAAK,MAAM,MAAM,QAAQ,SAC1C,iBACE,KACA,MACA,MACA,QACA,MACA,qBACD;EACJ;AAED,KAAI,QAAQ,QAAQ;AAClB,MAAI,CAAC,YACH,OAAM,IAAI,MACR,kBAAkB,aAAa,0BAA0B,WAAW,OAAO,CAAC,wBAC7E;EAGH,MAAM,MAAM,YAAY,eACtB,UACA,cACA,aACA,uBACA,cACD;AAED,MAAI,IACF,QAAO,qBAAqB,cAAc,IAAI;;AAIlD,KAAI,QAAQ,WAAW;AACrB,MAAI,CAAC,eACH,OAAM,IAAI,MACR,kBAAkB,aAAa,6BAA6B,WAAW,OAAO,CAAC,2BAChF;EAGH,MAAM,MAAM,eAAe,kBACzB,UACA,cACA,aACA,uBACA,cACD;AAED,MAAI,IACF,QAAO,qBAAqB,cAAc,IAAI;;AAIlD,KAAI;EAAC;EAAQ;EAAQ;EAAO;EAAO;EAAQ;EAAO,CAAC,SAAS,IAAI,EAAE;EAChE,MAAM,SAAS,eACb,UACA,cACA,aACA,eACA,eACA,sBACA,SACD;AAED,MAAI,OACF,QAAO;GACL,qBAAqB,OAAO;GAC5B,iBAAiB,OAAO;GACzB;;;AAOP,MAAM,8BAA8B,OAClC,qBACA,UACA,SACA,EACE,eACA,SACA,aAMF,kBACG;AACH,KAAI,SAAS,UACX,MAAK,MAAM,CAAC,KAAK,YAAY,OAAO,QAAQ,oBAAoB,CAC9D,OAAM,QAAQ,UAAU;EAAE;EAAK;EAAS;EAAU,CAAC;AAMvD,KAFqB,CAAC,SAAS,YAAY,CAAC,SAAS,UAGnD,MAAK,MAAM,CAAC,KAAK,YAAY,OAAO,QAAQ,oBAAoB,EAAE;EAQhE,MAAM,0BAA0B,SAAS,SAAS,MAPpB,mBAC5B,SACA,KACA,UACA,cACD,CAEiE;AAClE,YACE,GAAG,SAAS,aAAa,WAAW,UAAU,CAAC,yBAAyB,aAAa,wBAAwB,GAC9G;;AAIL,KAAI,eAAe;EACjB,MAAM,gBAAgB,oBAAoB,cAAc;AAExD,MAAI,cACF,KAAI;AACF,YAAS,cAAc,QAAQ,YAAY,SAAS,EAAE;IACpD,OAAO;IACP,KAAK;IACN,CAAC;WACK,OAAO;AACd,WAAQ,MAAM,MAAM;;AAIxB,YACE,GAAG,SAAS,aAAa,WAAW,UAAU,CAAC,sBAAsB,aAAa,SAAS,SAAS,SAAS,CAAC,GAC/G;;;AAkBL,MAAM,gBACJ,UACA,YACmB;CACnB,MAAM,gBACJ,SAAS,iBAAiB,iBAAiB,SAAS,cAAc;CACpE,MAAM,YAAY,aAAa,cAAc;CAC7C,MAAM,EAAE,YAAY,cAAc;AAMlC,QAAO;EACL;EACA;EACA;EACA,sBARA,SAAS,wBAAwB,wBAAwB,cAAc;EASvE,gBARqB,SAAS;EAS9B,oBARyB,QAAQ,SAQf;EACnB;;AAGH,MAAa,iBAAiB,OAC5B,UACA,aACA,YACuC;CACvC,MAAM,EACJ,eACA,WACA,SACA,sBACA,eACA,uBACE,aAAa,UAAU,QAAQ;AAEnC,KAAI,uBAAuB,UAAU,CAAC,YACpC,KAAI;AACF,gBAAc,MAAM,OAAO;SACrB;AACN,YACE,GAAG,SAAS,aAAa,WAAW,UAAU,CAAC,WAAW,aAAa,0BAA0B,WAAW,OAAO,CAAC,wBACrH;;AAIL,KAAI,uBAAuB,aAAa,CAAC,eACvC,KAAI;AACF,mBAAiB,MAAM,OAAO;SACxB;AACN,YACE,GAAG,SAAS,aAAa,WAAW,UAAU,CAAC,WAAW,aAAa,6BAA6B,WAAW,OAAO,CAAC,2BACxH;;CAKL,MAAM,gBAAgB,qBACpB,UAFe,SAAS,QAAQ,aAAa,UAAU,QAAQ,EAI/D,cAAc,SAAS,oBACxB;CAED,MAAM,SAAS,oBACb,UACA,aACA,SACA;EAAE;EAAa;EAAgB;EAAsB;EAAe,EACpE,eACA,cACD;AAED,KAAI,CAAC,QAAQ,qBAAqB;AAChC,YACE,GAAG,SAAS,aAAa,WAAW,UAAU,CAAC,gCAAgC,aAAa,SAAS,SAAS,SAAS,CAAC,IACxH,EAAE,WAAW,MAAM,CACpB;AACD;;AAGF,OAAM,4BACJ,OAAO,qBACP,UACA,SACA;EAAE;EAAe;EAAS;EAAW,EACrC,cACD;AAED,QAAO;EACL,iBAAiB,OAAO;EACxB,qBAAqB,OAAO;EAC7B;;;;;;;;;;;;AAaH,MAAa,sBACX,UACA,aACA,YAC8B;CAC9B,MAAM,EACJ,eACA,WACA,SACA,sBACA,eACA,uBACE,aAAa,UAAU,QAAQ;CAEnC,MAAM,YAAY,mBAAmB;AAErC,KAAI,uBAAuB,UAAU,CAAC,YACpC,KAAI;AACF,gBAAc,UAAU,yBAAyB;SAC3C;AACN,YACE,GAAG,SAAS,aAAa,WAAW,UAAU,CAAC,WAAW,aAAa,0BAA0B,WAAW,OAAO,CAAC,wBACrH;;AAIL,KAAI,uBAAuB,aAAa,CAAC,eACvC,KAAI;AACF,mBAAiB,UAAU,4BAA4B;SACjD;AACN,YACE,GAAG,SAAS,aAAa,WAAW,UAAU,CAAC,WAAW,aAAa,6BAA6B,WAAW,OAAO,CAAC,2BACxH;;CAIL,MAAM,SAAS,oBACb,UACA,aACA,SACA;EAAE;EAAa;EAAgB;EAAsB;EAAe,EACpE,cACD;AAED,KAAI,CAAC,QAAQ,qBAAqB;AAChC,YACE,GAAG,SAAS,aAAa,WAAW,UAAU,CAAC,gCAAgC,aAAa,SAAS,SAAS,SAAS,CAAC,IACxH,EAAE,WAAW,MAAM,CACpB;AACD;;CAGF,MAAM,EAAE,qBAAqB,oBAAoB;AAEjD,KAAI,SAAS,UACX,MAAK,MAAM,CAAC,KAAK,YAAY,OAAO,QAAQ,oBAAoB,CAC9D,CAAK,QAAQ,UAAU;EAAE;EAAK;EAAS;EAAU,CAAC;AAItD,QAAO;EAAE;EAAiB;EAAqB"}
@@ -1 +1 @@
1
- {"version":3,"file":"processTsxFile.mjs","names":[],"sources":["../../../src/extractContent/processTsxFile.ts"],"sourcesContent":["import { execSync } from 'node:child_process';\nimport { readFileSync, writeFileSync } from 'node:fs';\nimport { parse } from '@babel/parser';\nimport _traverse, { type NodePath } from '@babel/traverse';\nimport * as t from '@babel/types';\nimport { detectFormatCommand } from '@intlayer/chokidar/cli';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { extractBabelContentForComponents } from './babelProcessor';\nimport {\n type ExistingIntlayerInfo,\n getExistingIntlayerInfo,\n type PackageName,\n SERVER_CAPABLE_PACKAGES,\n} from './utils';\n\nexport type TextEdit = {\n start: number;\n end: number;\n replacement: string;\n};\n\nconst traverse = (\n typeof _traverse === 'function' ? _traverse : (_traverse as any).default\n) as typeof _traverse;\n\n/**\n * Processes a TSX/JSX/TS/JS file to extract content and inject Intlayer hooks/calls.\n */\nexport const processTsxFile = (\n filePath: string,\n componentKey: string,\n packageName: PackageName,\n configuration: IntlayerConfig,\n save: boolean = true,\n unmergedDictionaries: Record<string, unknown> = {},\n providedFileCode?: string\n): {\n extractedContent: Record<string, Record<string, string>>;\n modifiedCode: string;\n} | null => {\n const fileCode = providedFileCode ?? readFileSync(filePath, 'utf-8');\n\n const ast = parse(fileCode, {\n sourceType: 'module',\n plugins: ['jsx', 'typescript'],\n });\n\n const hasUseClient = ast.program.directives.some(\n (directive) => directive.value.value === 'use client'\n );\n\n const effectivePackageName =\n SERVER_CAPABLE_PACKAGES.has(packageName) && !hasUseClient\n ? `${packageName}/server`\n : packageName;\n\n // Both Solid and Angular expose content via a reactive/signal function —\n // access is `content().key` rather than `content.key`.\n const isSolid =\n packageName === 'solid-intlayer' || packageName === 'angular-intlayer';\n const existingKeys = new Set<string>();\n\n const {\n extractedContent,\n replacements,\n componentsNeedingHooks,\n componentKeyMap,\n componentPaths,\n hookMap,\n } = extractBabelContentForComponents(\n ast,\n fileCode,\n existingKeys,\n componentKey,\n configuration,\n filePath,\n unmergedDictionaries\n );\n\n if (Object.keys(extractedContent).length === 0) return null;\n\n const textEdits: TextEdit[] = [];\n\n // Build a lookup map: component AST node → NodePath (O(1) access)\n const componentNodeToPath = new Map<t.Node, NodePath>();\n for (const componentPath of componentPaths) {\n componentNodeToPath.set(componentPath.node, componentPath);\n }\n\n // Cache getExistingIntlayerInfo results for all component paths (avoids repeated traversals)\n const existingInfoCache = new Map<t.Node, ExistingIntlayerInfo | undefined>();\n for (const componentPath of componentPaths) {\n existingInfoCache.set(\n componentPath.node,\n getExistingIntlayerInfo(componentPath)\n );\n }\n\n /**\n * Walks up the ancestor chain to find the nearest enclosing component's\n * existing Intlayer info (if any).\n */\n const getExistingInfoForPath = (\n path: NodePath\n ): ExistingIntlayerInfo | undefined => {\n let current: NodePath | null = path;\n while (current) {\n if (componentNodeToPath.has(current.node)) {\n return existingInfoCache.get(current.node);\n }\n current = current.parentPath;\n }\n return undefined;\n };\n\n const getProvidingHookType = (\n path: NodePath\n ): 'useIntlayer' | 'getIntlayer' => {\n let current: NodePath | null = path;\n while (current) {\n const componentPath = componentNodeToPath.get(current.node);\n\n if (componentPath) {\n const existingInfo = existingInfoCache.get(componentPath.node);\n\n if (existingInfo) {\n return existingInfo.hook;\n }\n\n if (componentsNeedingHooks.has(componentPath)) {\n return hookMap.get(componentPath.node) || 'useIntlayer';\n }\n }\n current = current.parentPath;\n }\n return 'useIntlayer';\n };\n\n for (const {\n path,\n key,\n type,\n variables,\n childrenToReplace,\n } of replacements) {\n const existingInfo = getExistingInfoForPath(path);\n // When the existing call is destructured (e.g. `const { a } = getIntlayer(...)`),\n // new keys are added directly to that destructuring, so access them by name alone.\n const varName = existingInfo?.variableName ?? 'content';\n const contentAccessCode = existingInfo?.isDestructured\n ? key\n : isSolid\n ? `${varName}().${key}`\n : `${varName}.${key}`;\n const hookType = getProvidingHookType(path);\n const valueSuffix = hookType === 'getIntlayer' ? '' : '.value';\n\n if (type === 'jsx-text' && path.isJSXText()) {\n textEdits.push({\n start: path.node.start!,\n end: path.node.end!,\n replacement: `{${contentAccessCode}}`,\n });\n } else if (type === 'jsx-attribute' && path.isJSXAttribute()) {\n const valNode = path.node.value;\n\n if (valNode) {\n textEdits.push({\n start: valNode.start!,\n end: valNode.end!,\n replacement: `{${contentAccessCode}${valueSuffix}}`,\n });\n }\n } else if (type === 'string-literal' && path.isStringLiteral()) {\n textEdits.push({\n start: path.node.start!,\n end: path.node.end!,\n replacement: `${contentAccessCode}${valueSuffix}`,\n });\n } else if (\n type === 'jsx-text-combined' &&\n childrenToReplace &&\n childrenToReplace.length > 0\n ) {\n const accessStr = `{${contentAccessCode}}`;\n const start = childrenToReplace[0].start!;\n const end = childrenToReplace[childrenToReplace.length - 1].end!;\n textEdits.push({ start, end, replacement: accessStr });\n } else if (\n type === 'jsx-insertion' &&\n variables &&\n childrenToReplace &&\n childrenToReplace.length > 0\n ) {\n const objProps = variables\n .map((variableString) => {\n const [key, variableValue] = variableString\n .split(':')\n .map((part) => part.trim());\n return `${key}: ${variableValue}`;\n })\n .join(', ');\n\n const accessStr = `{${contentAccessCode}({ ${objProps} })}`;\n const start = childrenToReplace[0].start!;\n const end = childrenToReplace[childrenToReplace.length - 1].end!;\n textEdits.push({ start, end, replacement: accessStr });\n } else if (type === 'template-literal' && path.isTemplateLiteral()) {\n let replacement = `${contentAccessCode}`;\n\n if (variables && variables.length > 0) {\n const objProps = variables\n .map((variableString) => {\n const [key, variableValue] = variableString\n .split(':')\n .map((part) => part.trim());\n return `${key}: ${variableValue}`;\n })\n .join(', ');\n replacement += `({ ${objProps} })`;\n } else {\n replacement += valueSuffix;\n }\n\n textEdits.push({\n start: path.node.start!,\n end: path.node.end!,\n replacement,\n });\n }\n }\n\n let needsUseIntlayer = false;\n let needsGetIntlayer = false;\n\n for (const componentPath of componentsNeedingHooks) {\n const finalKey = componentKeyMap.get(componentPath.node)!;\n const existingInfo = existingInfoCache.get(componentPath.node);\n\n if (existingInfo) {\n if (existingInfo.hook === 'useIntlayer') needsUseIntlayer = true;\n\n if (existingInfo.hook === 'getIntlayer') needsGetIntlayer = true;\n\n // When the existing call is destructured, inject any missing keys into\n // the destructuring pattern so they can be accessed by name directly.\n if (existingInfo.isDestructured && existingInfo.objectPatternNode) {\n const neededKeys = new Set<string>();\n\n for (const { path: rPath, key: rKey } of replacements) {\n let current: NodePath | null = rPath;\n\n while (current) {\n if (current.node === componentPath.node) {\n neededKeys.add(rKey);\n break;\n }\n current = current.parentPath;\n }\n }\n\n const missingKeys = [...neededKeys].filter(\n (key) => !existingInfo.existingDestructuredKeys.includes(key)\n );\n\n if (missingKeys.length > 0) {\n const { objectPatternNode } = existingInfo;\n // Insert right after the last property so the space/newline before\n // `}` is naturally preserved: `{ a }` → `{ a, b }`.\n const lastProp =\n objectPatternNode.properties[\n objectPatternNode.properties.length - 1\n ];\n textEdits.push({\n start: lastProp.end!,\n end: lastProp.end!,\n replacement: `, ${missingKeys.join(', ')}`,\n });\n }\n }\n } else {\n const hook = hookMap.get(componentPath.node) || 'useIntlayer';\n\n if (hook === 'useIntlayer') needsUseIntlayer = true;\n\n if (hook === 'getIntlayer') needsGetIntlayer = true;\n\n const hookStatementStr = `\\n const content = ${hook}('${finalKey}');\\n`;\n\n if (componentPath.isProgram()) {\n // Find the last import or directive to inject the getIntlayer call\n let insertPos = 0;\n if (componentPath.node.directives.length > 0) {\n insertPos =\n componentPath.node.directives[\n componentPath.node.directives.length - 1\n ].end!;\n }\n for (const stmt of componentPath.node.body) {\n if (t.isImportDeclaration(stmt)) {\n insertPos = Math.max(insertPos, stmt.end!);\n }\n }\n\n textEdits.push({\n start: insertPos,\n end: insertPos,\n replacement: `\\n${hookStatementStr}`,\n });\n continue;\n }\n\n const bodyPath = componentPath.get('body') as NodePath;\n\n if (bodyPath.isBlockStatement()) {\n textEdits.push({\n start: bodyPath.node.start! + 1,\n end: bodyPath.node.start! + 1,\n replacement: hookStatementStr,\n });\n } else if (bodyPath.isExpression()) {\n const start = bodyPath.node.start!;\n const end = bodyPath.node.end!;\n\n // Detect wrapping parentheses: `() => (expr)` — scan backward from\n // the expression start and forward from its end for matching `(` / `)`.\n let parenStart = -1;\n let parenEnd = -1;\n\n for (let i = start - 1; i >= 0; i--) {\n const char = fileCode[i];\n if (char === '(') {\n parenStart = i;\n break;\n }\n if (char !== ' ' && char !== '\\n' && char !== '\\r' && char !== '\\t')\n break;\n }\n\n if (parenStart !== -1) {\n for (let i = end; i < fileCode.length; i++) {\n const char = fileCode[i];\n if (char === ')') {\n parenEnd = i;\n break;\n }\n if (char !== ' ' && char !== '\\n' && char !== '\\r' && char !== '\\t')\n break;\n }\n }\n\n if (parenStart !== -1 && parenEnd !== -1) {\n // Replace the surrounding `(` … `)` with a block statement. We keep\n // the parentheses as `return (…)` to prevent automatic semicolon\n // insertion when the returned expression starts on a new line.\n textEdits.push({\n start: parenEnd,\n end: parenEnd + 1,\n replacement: `)\\n}`,\n });\n textEdits.push({\n start: parenStart,\n end: parenStart + 1,\n replacement: `{\\n const content = ${hook}('${finalKey}');\\n return (`,\n });\n } else {\n textEdits.push({\n start: end,\n end: end,\n replacement: `\\n}`,\n });\n textEdits.push({\n start: start,\n end: start,\n replacement: `{\\n const content = ${hook}('${finalKey}');\\n return `,\n });\n }\n }\n }\n }\n\n const injectImport = (hookName: string, targetPackage: string) => {\n let existingImportPath: NodePath<t.ImportDeclaration> | undefined;\n\n traverse(ast, {\n ImportDeclaration(path) {\n if (path.node.source.value === targetPackage) {\n existingImportPath = path;\n path.stop();\n }\n },\n });\n\n if (!existingImportPath) {\n const newImportStr = `import { ${hookName} } from '${targetPackage}';\\n`;\n let insertPos = 0;\n\n if (ast.program.body.length > 0) {\n insertPos = ast.program.body[0].start!;\n } else if (ast.program.directives && ast.program.directives.length > 0) {\n insertPos =\n ast.program.directives[ast.program.directives.length - 1].end!;\n\n if (fileCode[insertPos] === ';') insertPos++;\n\n textEdits.push({\n start: insertPos,\n end: insertPos,\n replacement: `\\n${newImportStr}`,\n });\n return;\n }\n\n if (\n insertPos === 0 ||\n (ast.program.body.length > 0 &&\n insertPos === ast.program.body[0].start!)\n ) {\n textEdits.push({\n start: insertPos,\n end: insertPos,\n replacement: newImportStr,\n });\n }\n } else {\n const existingSpecifiers = existingImportPath.node.specifiers;\n const missingImport = !existingSpecifiers.some(\n (specifier) =>\n t.isImportSpecifier(specifier) &&\n t.isIdentifier(specifier.imported) &&\n specifier.imported.name === hookName\n );\n\n if (missingImport) {\n const importCode = fileCode.slice(\n existingImportPath.node.start!,\n existingImportPath.node.end!\n );\n const closingBraceIndex = importCode.lastIndexOf('}');\n\n if (closingBraceIndex !== -1) {\n const isCommaNeeded = !importCode\n .slice(0, closingBraceIndex)\n .trim()\n .endsWith(',');\n const absolutePos =\n existingImportPath.node.start! + closingBraceIndex;\n const prefix = isCommaNeeded ? ', ' : ' ';\n textEdits.push({\n start: absolutePos,\n end: absolutePos,\n replacement: `${prefix}${hookName} `,\n });\n }\n }\n }\n };\n\n if (needsUseIntlayer) injectImport('useIntlayer', effectivePackageName);\n\n if (needsGetIntlayer) injectImport('getIntlayer', 'intlayer');\n\n textEdits.sort((editA, editB) => {\n if (editB.start !== editA.start) return editB.start - editA.start;\n\n return editB.end - editA.end;\n });\n\n let formattedCode = fileCode;\n\n for (const edit of textEdits) {\n formattedCode =\n formattedCode.slice(0, edit.start) +\n edit.replacement +\n formattedCode.slice(edit.end);\n }\n\n if (save) {\n writeFileSync(filePath, formattedCode);\n\n const formatCommand = detectFormatCommand(configuration);\n\n if (formatCommand) {\n try {\n execSync(formatCommand.replace('{{file}}', filePath), {\n stdio: 'inherit',\n cwd: configuration.system.baseDir,\n });\n } catch (error) {\n console.error(error);\n }\n }\n }\n\n return { extractedContent, modifiedCode: formattedCode };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"processTsxFile.mjs","names":[],"sources":["../../../src/extractContent/processTsxFile.ts"],"sourcesContent":["import { execSync } from 'node:child_process';\nimport { readFileSync, writeFileSync } from 'node:fs';\nimport { parse } from '@babel/parser';\nimport _traverse, { type NodePath } from '@babel/traverse';\nimport * as t from '@babel/types';\nimport { detectFormatCommand } from '@intlayer/chokidar/cli';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { extractBabelContentForComponents } from './babelProcessor';\nimport {\n type ExistingIntlayerInfo,\n getExistingIntlayerInfo,\n type PackageName,\n SERVER_CAPABLE_PACKAGES,\n} from './utils';\n\nexport type TextEdit = {\n start: number;\n end: number;\n replacement: string;\n};\n\nconst traverse = (\n typeof _traverse === 'function' ? _traverse : (_traverse as any).default\n) as typeof _traverse;\n\n/**\n * Processes a TSX/JSX/TS/JS file to extract content and inject Intlayer hooks/calls.\n */\nexport const processTsxFile = (\n filePath: string,\n componentKey: string,\n packageName: PackageName,\n configuration: IntlayerConfig,\n save: boolean = true,\n unmergedDictionaries: Record<string, unknown> = {},\n providedFileCode?: string\n): {\n extractedContent: Record<string, Record<string, string>>;\n modifiedCode: string;\n} | null => {\n const fileCode = providedFileCode ?? readFileSync(filePath, 'utf-8');\n\n const ast = parse(fileCode, {\n sourceType: 'module',\n plugins: ['jsx', 'typescript'],\n });\n\n const hasUseClient = ast.program.directives.some(\n (directive) => directive.value.value === 'use client'\n );\n\n const effectivePackageName =\n SERVER_CAPABLE_PACKAGES.has(packageName) && !hasUseClient\n ? `${packageName}/server`\n : packageName;\n\n // Both Solid and Angular expose content via a reactive/signal function —\n // access is `content().key` rather than `content.key`.\n const isSolid =\n packageName === 'solid-intlayer' || packageName === 'angular-intlayer';\n const existingKeys = new Set<string>();\n\n const {\n extractedContent,\n replacements,\n componentsNeedingHooks,\n componentKeyMap,\n componentPaths,\n hookMap,\n } = extractBabelContentForComponents(\n ast,\n fileCode,\n existingKeys,\n componentKey,\n configuration,\n filePath,\n unmergedDictionaries\n );\n\n if (Object.keys(extractedContent).length === 0) return null;\n\n const textEdits: TextEdit[] = [];\n\n // Build a lookup map: component AST node → NodePath (O(1) access)\n const componentNodeToPath = new Map<t.Node, NodePath>();\n for (const componentPath of componentPaths) {\n componentNodeToPath.set(componentPath.node, componentPath);\n }\n\n // Cache getExistingIntlayerInfo results for all component paths (avoids repeated traversals)\n const existingInfoCache = new Map<t.Node, ExistingIntlayerInfo | undefined>();\n for (const componentPath of componentPaths) {\n existingInfoCache.set(\n componentPath.node,\n getExistingIntlayerInfo(componentPath)\n );\n }\n\n /**\n * Walks up the ancestor chain to find the nearest enclosing component's\n * existing Intlayer info (if any).\n */\n const getExistingInfoForPath = (\n path: NodePath\n ): ExistingIntlayerInfo | undefined => {\n let current: NodePath | null = path;\n while (current) {\n if (componentNodeToPath.has(current.node)) {\n return existingInfoCache.get(current.node);\n }\n current = current.parentPath;\n }\n return undefined;\n };\n\n const getProvidingHookType = (\n path: NodePath\n ): 'useIntlayer' | 'getIntlayer' => {\n let current: NodePath | null = path;\n while (current) {\n const componentPath = componentNodeToPath.get(current.node);\n\n if (componentPath) {\n const existingInfo = existingInfoCache.get(componentPath.node);\n\n if (existingInfo) {\n return existingInfo.hook;\n }\n\n if (componentsNeedingHooks.has(componentPath)) {\n return hookMap.get(componentPath.node) || 'useIntlayer';\n }\n }\n current = current.parentPath;\n }\n return 'useIntlayer';\n };\n\n for (const {\n path,\n key,\n type,\n variables,\n childrenToReplace,\n } of replacements) {\n const existingInfo = getExistingInfoForPath(path);\n // When the existing call is destructured (e.g. `const { a } = getIntlayer(...)`),\n // new keys are added directly to that destructuring, so access them by name alone.\n const varName = existingInfo?.variableName ?? 'content';\n const contentAccessCode = existingInfo?.isDestructured\n ? key\n : isSolid\n ? `${varName}().${key}`\n : `${varName}.${key}`;\n const hookType = getProvidingHookType(path);\n const valueSuffix = hookType === 'getIntlayer' ? '' : '.value';\n\n if (type === 'jsx-text' && path.isJSXText()) {\n textEdits.push({\n start: path.node.start!,\n end: path.node.end!,\n replacement: `{${contentAccessCode}}`,\n });\n } else if (type === 'jsx-attribute' && path.isJSXAttribute()) {\n const valNode = path.node.value;\n\n if (valNode) {\n textEdits.push({\n start: valNode.start!,\n end: valNode.end!,\n replacement: `{${contentAccessCode}${valueSuffix}}`,\n });\n }\n } else if (type === 'string-literal' && path.isStringLiteral()) {\n textEdits.push({\n start: path.node.start!,\n end: path.node.end!,\n replacement: `${contentAccessCode}${valueSuffix}`,\n });\n } else if (\n type === 'jsx-text-combined' &&\n childrenToReplace &&\n childrenToReplace.length > 0\n ) {\n const accessStr = `{${contentAccessCode}}`;\n const start = childrenToReplace[0].start!;\n const end = childrenToReplace[childrenToReplace.length - 1].end!;\n textEdits.push({ start, end, replacement: accessStr });\n } else if (\n type === 'jsx-insertion' &&\n variables &&\n childrenToReplace &&\n childrenToReplace.length > 0\n ) {\n const objProps = variables\n .map((variableString) => {\n const [key, variableValue] = variableString\n .split(':')\n .map((part) => part.trim());\n return `${key}: ${variableValue}`;\n })\n .join(', ');\n\n const accessStr = `{${contentAccessCode}({ ${objProps} })}`;\n const start = childrenToReplace[0].start!;\n const end = childrenToReplace[childrenToReplace.length - 1].end!;\n textEdits.push({ start, end, replacement: accessStr });\n } else if (type === 'template-literal' && path.isTemplateLiteral()) {\n let replacement = `${contentAccessCode}`;\n\n if (variables && variables.length > 0) {\n const objProps = variables\n .map((variableString) => {\n const [key, variableValue] = variableString\n .split(':')\n .map((part) => part.trim());\n return `${key}: ${variableValue}`;\n })\n .join(', ');\n replacement += `({ ${objProps} })`;\n } else {\n replacement += valueSuffix;\n }\n\n textEdits.push({\n start: path.node.start!,\n end: path.node.end!,\n replacement,\n });\n }\n }\n\n let needsUseIntlayer = false;\n let needsGetIntlayer = false;\n\n for (const componentPath of componentsNeedingHooks) {\n const finalKey = componentKeyMap.get(componentPath.node)!;\n const existingInfo = existingInfoCache.get(componentPath.node);\n\n if (existingInfo) {\n if (existingInfo.hook === 'useIntlayer') needsUseIntlayer = true;\n\n if (existingInfo.hook === 'getIntlayer') needsGetIntlayer = true;\n\n // When the existing call is destructured, inject any missing keys into\n // the destructuring pattern so they can be accessed by name directly.\n if (existingInfo.isDestructured && existingInfo.objectPatternNode) {\n const neededKeys = new Set<string>();\n\n for (const { path: rPath, key: rKey } of replacements) {\n let current: NodePath | null = rPath;\n\n while (current) {\n if (current.node === componentPath.node) {\n neededKeys.add(rKey);\n break;\n }\n current = current.parentPath;\n }\n }\n\n const missingKeys = [...neededKeys].filter(\n (key) => !existingInfo.existingDestructuredKeys.includes(key)\n );\n\n if (missingKeys.length > 0) {\n const { objectPatternNode } = existingInfo;\n // Insert right after the last property so the space/newline before\n // `}` is naturally preserved: `{ a }` → `{ a, b }`.\n const lastProp =\n objectPatternNode.properties[\n objectPatternNode.properties.length - 1\n ];\n textEdits.push({\n start: lastProp.end!,\n end: lastProp.end!,\n replacement: `, ${missingKeys.join(', ')}`,\n });\n }\n }\n } else {\n const hook = hookMap.get(componentPath.node) || 'useIntlayer';\n\n if (hook === 'useIntlayer') needsUseIntlayer = true;\n\n if (hook === 'getIntlayer') needsGetIntlayer = true;\n\n const hookStatementStr = `\\n const content = ${hook}('${finalKey}');\\n`;\n\n if (componentPath.isProgram()) {\n // Find the last import or directive to inject the getIntlayer call\n let insertPos = 0;\n if (componentPath.node.directives.length > 0) {\n insertPos =\n componentPath.node.directives[\n componentPath.node.directives.length - 1\n ].end!;\n }\n for (const stmt of componentPath.node.body) {\n if (t.isImportDeclaration(stmt)) {\n insertPos = Math.max(insertPos, stmt.end!);\n }\n }\n\n textEdits.push({\n start: insertPos,\n end: insertPos,\n replacement: `\\n${hookStatementStr}`,\n });\n continue;\n }\n\n const bodyPath = componentPath.get('body') as NodePath;\n\n if (bodyPath.isBlockStatement()) {\n textEdits.push({\n start: bodyPath.node.start! + 1,\n end: bodyPath.node.start! + 1,\n replacement: hookStatementStr,\n });\n } else if (bodyPath.isExpression()) {\n const start = bodyPath.node.start!;\n const end = bodyPath.node.end!;\n\n // Detect wrapping parentheses: `() => (expr)` — scan backward from\n // the expression start and forward from its end for matching `(` / `)`.\n let parenStart = -1;\n let parenEnd = -1;\n\n for (let i = start - 1; i >= 0; i--) {\n const char = fileCode[i];\n if (char === '(') {\n parenStart = i;\n break;\n }\n if (char !== ' ' && char !== '\\n' && char !== '\\r' && char !== '\\t')\n break;\n }\n\n if (parenStart !== -1) {\n for (let i = end; i < fileCode.length; i++) {\n const char = fileCode[i];\n if (char === ')') {\n parenEnd = i;\n break;\n }\n if (char !== ' ' && char !== '\\n' && char !== '\\r' && char !== '\\t')\n break;\n }\n }\n\n if (parenStart !== -1 && parenEnd !== -1) {\n // Replace the surrounding `(` … `)` with a block statement. We keep\n // the parentheses as `return (…)` to prevent automatic semicolon\n // insertion when the returned expression starts on a new line.\n textEdits.push({\n start: parenEnd,\n end: parenEnd + 1,\n replacement: `)\\n}`,\n });\n textEdits.push({\n start: parenStart,\n end: parenStart + 1,\n replacement: `{\\n const content = ${hook}('${finalKey}');\\n return (`,\n });\n } else {\n textEdits.push({\n start: end,\n end: end,\n replacement: `\\n}`,\n });\n textEdits.push({\n start: start,\n end: start,\n replacement: `{\\n const content = ${hook}('${finalKey}');\\n return `,\n });\n }\n }\n }\n }\n\n const injectImport = (hookName: string, targetPackage: string) => {\n let existingImportPath: NodePath<t.ImportDeclaration> | undefined;\n\n traverse(ast, {\n ImportDeclaration(path) {\n if (path.node.source.value === targetPackage) {\n existingImportPath = path;\n path.stop();\n }\n },\n });\n\n if (!existingImportPath) {\n const newImportStr = `import { ${hookName} } from '${targetPackage}';\\n`;\n let insertPos = 0;\n\n if (ast.program.body.length > 0) {\n insertPos = ast.program.body[0].start!;\n } else if (ast.program.directives && ast.program.directives.length > 0) {\n insertPos =\n ast.program.directives[ast.program.directives.length - 1].end!;\n\n if (fileCode[insertPos] === ';') insertPos++;\n\n textEdits.push({\n start: insertPos,\n end: insertPos,\n replacement: `\\n${newImportStr}`,\n });\n return;\n }\n\n if (\n insertPos === 0 ||\n (ast.program.body.length > 0 &&\n insertPos === ast.program.body[0].start!)\n ) {\n textEdits.push({\n start: insertPos,\n end: insertPos,\n replacement: newImportStr,\n });\n }\n } else {\n const existingSpecifiers = existingImportPath.node.specifiers;\n const missingImport = !existingSpecifiers.some(\n (specifier) =>\n t.isImportSpecifier(specifier) &&\n t.isIdentifier(specifier.imported) &&\n specifier.imported.name === hookName\n );\n\n if (missingImport) {\n const importCode = fileCode.slice(\n existingImportPath.node.start!,\n existingImportPath.node.end!\n );\n const closingBraceIndex = importCode.lastIndexOf('}');\n\n if (closingBraceIndex !== -1) {\n const isCommaNeeded = !importCode\n .slice(0, closingBraceIndex)\n .trim()\n .endsWith(',');\n const absolutePos =\n existingImportPath.node.start! + closingBraceIndex;\n const prefix = isCommaNeeded ? ', ' : ' ';\n textEdits.push({\n start: absolutePos,\n end: absolutePos,\n replacement: `${prefix}${hookName} `,\n });\n }\n }\n }\n };\n\n if (needsUseIntlayer) injectImport('useIntlayer', effectivePackageName);\n\n if (needsGetIntlayer) injectImport('getIntlayer', 'intlayer');\n\n textEdits.sort((editA, editB) => {\n if (editB.start !== editA.start) return editB.start - editA.start;\n\n return editB.end - editA.end;\n });\n\n let formattedCode = fileCode;\n\n for (const edit of textEdits) {\n formattedCode =\n formattedCode.slice(0, edit.start) +\n edit.replacement +\n formattedCode.slice(edit.end);\n }\n\n if (save) {\n writeFileSync(filePath, formattedCode);\n\n const formatCommand = detectFormatCommand(configuration);\n\n if (formatCommand) {\n try {\n execSync(formatCommand.replace('{{file}}', filePath), {\n stdio: 'inherit',\n cwd: configuration.system.baseDir,\n });\n } catch (error) {\n console.error(error);\n }\n }\n }\n\n return { extractedContent, modifiedCode: formattedCode };\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"extractDictionaryInfo.mjs","names":[],"sources":["../../../../src/extractContent/utils/extractDictionaryInfo.ts"],"sourcesContent":["import { basename, dirname, extname, relative, resolve } from 'node:path';\nimport {\n getFormatFromExtension,\n resolveRelativePath,\n} from '@intlayer/chokidar/utils';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize } from '@intlayer/config/logger';\nimport { parseStringPattern } from '@intlayer/config/utils';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Fill } from '@intlayer/types/dictionary';\nimport type {\n FilePathPattern,\n FilePathPatternContext,\n FilePathPatternFunction,\n} from '@intlayer/types/filePathPattern';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport { extractDictionaryKey } from './extractDictionaryKey';\n\nexport const getOutput = (\n configuration: IntlayerConfig,\n locale?: Locale\n): FilePathPatternFunction | false => {\n const output = configuration.compiler?.output as Fill | undefined;\n\n if (output === undefined || output === null) {\n throw new Error(\n `No output configuration found. Add a ${colorize('compiler.output', ANSIColors.BLUE)} in your configuration.`\n );\n }\n\n if (output === false) return false;\n\n // Object per-locale record: look up the entry for the given locale\n if (typeof output === 'object' && typeof output !== 'function') {\n const entry = locale\n ? (output as Record<string, boolean | FilePathPattern>)[locale]\n : undefined;\n if (entry === false) return false;\n if (entry === undefined || entry === true) {\n throw new Error(\n `No output pattern configured for locale \"${locale}\" in compiler.output.`\n );\n }\n return typeof entry === 'string'\n ? (context: FilePathPatternContext) =>\n parseStringPattern(entry as string, context)\n : (entry as FilePathPatternFunction);\n }\n\n if (typeof output === 'string') {\n return (context: FilePathPatternContext) =>\n parseStringPattern(output, context);\n }\n\n if (typeof output === 'function') {\n return output as FilePathPatternFunction;\n }\n\n throw new Error(\n `No output configuration found. Add a ${colorize('compiler.output', ANSIColors.BLUE)} in your configuration.`\n );\n};\n\nexport type ResolveContentFilePaths = {\n absolutePath: string;\n relativePath: string;\n isPerLocale: boolean;\n};\n\n/**\n * Resolves the paths for the content files associated with a component.\n * Checks for existing dictionaries first.\n */\nexport const resolveContentFilePaths = async (\n filePath: string,\n componentKey: string,\n configuration: IntlayerConfig,\n locale?: Locale\n): Promise<ResolveContentFilePaths> => {\n const { baseDir } = configuration.system;\n const { defaultLocale } = configuration.internationalization;\n\n const unmergedDictionaries = getUnmergedDictionaries(configuration) ?? {};\n const existingDicts = unmergedDictionaries[componentKey]\n ?.filter(\n (dictionary) =>\n // Remove remote dictionaries (Fix: Check for !== instead of ===)\n dictionary.location !== 'remote'\n )\n .filter(\n (dictionary) =>\n // Check for first locale dictionary sorted by priority that include the targeted locale\n dictionary.locale === undefined ||\n dictionary.locale === (locale ?? defaultLocale)\n );\n\n if (existingDicts?.[0]?.filePath) {\n const existingPath = existingDicts[0].filePath;\n const resolvedAbsolutePath = resolve(baseDir, existingPath);\n\n return {\n absolutePath: resolvedAbsolutePath,\n relativePath: relative(baseDir, resolvedAbsolutePath),\n isPerLocale: false,\n };\n }\n\n const output = configuration.compiler?.output as Fill | undefined;\n const isObjectOutput =\n typeof output === 'object' &&\n output !== null &&\n typeof output !== 'function';\n\n // Build shared context (used for both object and scalar output paths)\n const extension = extname(\n filePath\n ) as FilePathPatternContext['componentExtension'];\n const componentName = basename(filePath, extension);\n const uncapitalizedName =\n componentName.charAt(0).toLowerCase() + componentName.slice(1);\n const componentFormat = getFormatFromExtension(\n extension!\n ) as FilePathPatternContext['componentFormat'];\n\n const targetLocale = (locale ?? defaultLocale) as Locale;\n\n const context: FilePathPatternContext = {\n key: componentKey,\n componentDirPath: relative(baseDir, dirname(filePath)),\n componentFileName: componentName,\n fileName: uncapitalizedName,\n componentFormat,\n componentExtension: extension,\n format: componentFormat!,\n locale: targetLocale,\n extension: configuration.content.fileExtensions[0],\n };\n\n // Object output: each locale has its own pattern → always per-locale\n if (isObjectOutput) {\n const pattern = getOutput(configuration, targetLocale);\n if (pattern === false) {\n throw new Error(\n `compiler.output is disabled for locale \"${targetLocale}\".`\n );\n }\n const rawAbsolutePath = await pattern(context);\n const absolutePath = resolveRelativePath(\n rawAbsolutePath,\n filePath,\n baseDir\n );\n return {\n absolutePath,\n relativePath: relative(baseDir, absolutePath),\n isPerLocale: true,\n };\n }\n\n // Scalar string/function output: detect per-locale via dummy locale probe\n const pattern = getOutput(configuration);\n if (pattern === false) {\n throw new Error(\n `No output configuration found. Add a ${colorize('compiler.output', ANSIColors.BLUE)} in your configuration.`\n );\n }\n\n const rawAbsolutePath = await pattern({ ...context, locale: defaultLocale });\n\n // Apply the resolution rules\n const absolutePath = resolveRelativePath(rawAbsolutePath, filePath, baseDir);\n\n const localeIdentifier = '###########locale###########' as Locale;\n\n const rawPerLocalePath = await pattern({\n ...context,\n locale: localeIdentifier,\n });\n const isPerLocale = rawPerLocalePath.includes(localeIdentifier);\n\n return {\n absolutePath,\n relativePath: relative(baseDir, absolutePath),\n isPerLocale,\n };\n};\n\nexport type ExtractDictionaryInfoOptions = {\n configuration?: IntlayerConfig;\n};\n\n/**\n * Extracts the dictionary key and dictionary file path for a given component file.\n */\nexport const extractDictionaryInfo = async (\n filePath: string,\n fileText: string,\n configuration: IntlayerConfig\n): Promise<\n {\n dictionaryKey: string;\n } & ResolveContentFilePaths\n> => {\n const dictionaryKey = extractDictionaryKey(filePath, fileText);\n\n const resolvedPaths = await resolveContentFilePaths(\n filePath,\n dictionaryKey,\n configuration\n );\n\n return {\n dictionaryKey,\n ...resolvedPaths,\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"extractDictionaryInfo.mjs","names":[],"sources":["../../../../src/extractContent/utils/extractDictionaryInfo.ts"],"sourcesContent":["import { basename, dirname, extname, relative, resolve } from 'node:path';\nimport {\n getFormatFromExtension,\n resolveRelativePath,\n} from '@intlayer/chokidar/utils';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize } from '@intlayer/config/logger';\nimport { parseStringPattern } from '@intlayer/config/utils';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Fill } from '@intlayer/types/dictionary';\nimport type {\n FilePathPattern,\n FilePathPatternContext,\n FilePathPatternFunction,\n} from '@intlayer/types/filePathPattern';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport { extractDictionaryKey } from './extractDictionaryKey';\n\nexport const getOutput = (\n configuration: IntlayerConfig,\n locale?: Locale\n): FilePathPatternFunction | false => {\n const output = configuration.compiler?.output as Fill | undefined;\n\n if (output === undefined || output === null) {\n throw new Error(\n `No output configuration found. Add a ${colorize('compiler.output', ANSIColors.BLUE)} in your configuration.`\n );\n }\n\n if (output === false) return false;\n\n // Object per-locale record: look up the entry for the given locale\n if (typeof output === 'object' && typeof output !== 'function') {\n const entry = locale\n ? (output as Record<string, boolean | FilePathPattern>)[locale]\n : undefined;\n if (entry === false) return false;\n if (entry === undefined || entry === true) {\n throw new Error(\n `No output pattern configured for locale \"${locale}\" in compiler.output.`\n );\n }\n return typeof entry === 'string'\n ? (context: FilePathPatternContext) =>\n parseStringPattern(entry as string, context)\n : (entry as FilePathPatternFunction);\n }\n\n if (typeof output === 'string') {\n return (context: FilePathPatternContext) =>\n parseStringPattern(output, context);\n }\n\n if (typeof output === 'function') {\n return output as FilePathPatternFunction;\n }\n\n throw new Error(\n `No output configuration found. Add a ${colorize('compiler.output', ANSIColors.BLUE)} in your configuration.`\n );\n};\n\nexport type ResolveContentFilePaths = {\n absolutePath: string;\n relativePath: string;\n isPerLocale: boolean;\n};\n\n/**\n * Resolves the paths for the content files associated with a component.\n * Checks for existing dictionaries first.\n */\nexport const resolveContentFilePaths = async (\n filePath: string,\n componentKey: string,\n configuration: IntlayerConfig,\n locale?: Locale\n): Promise<ResolveContentFilePaths> => {\n const { baseDir } = configuration.system;\n const { defaultLocale } = configuration.internationalization;\n\n const unmergedDictionaries = getUnmergedDictionaries(configuration) ?? {};\n const existingDicts = unmergedDictionaries[componentKey]\n ?.filter(\n (dictionary) =>\n // Remove remote dictionaries (Fix: Check for !== instead of ===)\n dictionary.location !== 'remote'\n )\n .filter(\n (dictionary) =>\n // Check for first locale dictionary sorted by priority that include the targeted locale\n dictionary.locale === undefined ||\n dictionary.locale === (locale ?? defaultLocale)\n );\n\n if (existingDicts?.[0]?.filePath) {\n const existingPath = existingDicts[0].filePath;\n const resolvedAbsolutePath = resolve(baseDir, existingPath);\n\n return {\n absolutePath: resolvedAbsolutePath,\n relativePath: relative(baseDir, resolvedAbsolutePath),\n isPerLocale: false,\n };\n }\n\n const output = configuration.compiler?.output as Fill | undefined;\n const isObjectOutput =\n typeof output === 'object' &&\n output !== null &&\n typeof output !== 'function';\n\n // Build shared context (used for both object and scalar output paths)\n const extension = extname(\n filePath\n ) as FilePathPatternContext['componentExtension'];\n const componentName = basename(filePath, extension);\n const uncapitalizedName =\n componentName.charAt(0).toLowerCase() + componentName.slice(1);\n const componentFormat = getFormatFromExtension(\n extension!\n ) as FilePathPatternContext['componentFormat'];\n\n const targetLocale = (locale ?? defaultLocale) as Locale;\n\n const context: FilePathPatternContext = {\n key: componentKey,\n componentDirPath: relative(baseDir, dirname(filePath)),\n componentFileName: componentName,\n fileName: uncapitalizedName,\n componentFormat,\n componentExtension: extension,\n format: componentFormat!,\n locale: targetLocale,\n extension: configuration.content.fileExtensions[0],\n };\n\n // Object output: each locale has its own pattern → always per-locale\n if (isObjectOutput) {\n const pattern = getOutput(configuration, targetLocale);\n if (pattern === false) {\n throw new Error(\n `compiler.output is disabled for locale \"${targetLocale}\".`\n );\n }\n const rawAbsolutePath = await pattern(context);\n const absolutePath = resolveRelativePath(\n rawAbsolutePath,\n filePath,\n baseDir\n );\n return {\n absolutePath,\n relativePath: relative(baseDir, absolutePath),\n isPerLocale: true,\n };\n }\n\n // Scalar string/function output: detect per-locale via dummy locale probe\n const pattern = getOutput(configuration);\n if (pattern === false) {\n throw new Error(\n `No output configuration found. Add a ${colorize('compiler.output', ANSIColors.BLUE)} in your configuration.`\n );\n }\n\n const rawAbsolutePath = await pattern({ ...context, locale: defaultLocale });\n\n // Apply the resolution rules\n const absolutePath = resolveRelativePath(rawAbsolutePath, filePath, baseDir);\n\n const localeIdentifier = '###########locale###########' as Locale;\n\n const rawPerLocalePath = await pattern({\n ...context,\n locale: localeIdentifier,\n });\n const isPerLocale = rawPerLocalePath.includes(localeIdentifier);\n\n return {\n absolutePath,\n relativePath: relative(baseDir, absolutePath),\n isPerLocale,\n };\n};\n\nexport type ExtractDictionaryInfoOptions = {\n configuration?: IntlayerConfig;\n};\n\n/**\n * Extracts the dictionary key and dictionary file path for a given component file.\n */\nexport const extractDictionaryInfo = async (\n filePath: string,\n fileText: string,\n configuration: IntlayerConfig\n): Promise<\n {\n dictionaryKey: string;\n } & ResolveContentFilePaths\n> => {\n const dictionaryKey = extractDictionaryKey(filePath, fileText);\n\n const resolvedPaths = await resolveContentFilePaths(\n filePath,\n dictionaryKey,\n configuration\n );\n\n return {\n dictionaryKey,\n ...resolvedPaths,\n };\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"shouldExtract.mjs","names":[],"sources":["../../../../src/extractContent/utils/shouldExtract.ts"],"sourcesContent":["/**\n * Checks whether the given text should be extracted as a translatable string.\n *\n * Filters out:\n * - Empty strings\n * - Emails\n * - Uncapitalized strings of 2 words or fewer (likely technical terms)\n * - Dynamic content patterns like Vue bindings (`v-`) or object patterns (`{`)\n */\nexport const shouldExtract = (text: string): boolean => {\n const trimmed = text.trim();\n\n if (!trimmed) return false;\n\n // Ignore emails\n if (/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(trimmed)) return false;\n\n // Ignore dynamic content patterns\n if (trimmed.startsWith('{') || trimmed.startsWith('v-')) return false;\n\n // Ignore explicit code patterns (markers)\n if (\n trimmed.includes('=>') ||\n trimmed.includes(');') ||\n trimmed.includes('(){') ||\n trimmed.includes('==') ||\n trimmed.includes('window.') ||\n trimmed.startsWith('(function') ||\n trimmed.startsWith('function(')\n ) {\n return false;\n }\n\n // Heuristic: check for characters that are common in code but rare in natural text.\n // Whitelist: letters, numbers, spaces, and frequent text symbols (including punctuation, braces, and technical symbols)\n const nonTextualRegex =\n /[^\\p{L}\\p{N}\\s.,!?;:'\"()[\\]{}–—/«»„“\\p{Sc}%&*+#@^_+=<>/~]/gu;\n const nonTextualMatches = trimmed.match(nonTextualRegex) || [];\n\n // If a string contains a high density of truly exceptional symbols (like |, \\, etc.),\n // it is highly likely to be code or complex technical data.\n if (nonTextualMatches.length > 5) return false;\n\n const wordCount = trimmed.split(/\\s+/).length;\n\n // Check if starts with a capital letter (including after an opening parenthesis/quote)\n const isCapitalized = /^['\"([]*\\p{Lu}/u.test(trimmed);\n\n // Ignore technical identifiers (one word strings with camelCase, kebab-case, snake_case etc.)\n if (wordCount === 1) {\n // CamelCase or internal capitals (like camelCaseProperty or CamelCaseProperty)\n if (/[a-z]\\p{Lu}/u.test(trimmed)) return false;\n // kebab-case or snake_case\n if (trimmed.includes('-') || trimmed.includes('_')) return false;\n }\n\n // We usually want to extract full sentences or labels, not single/short technical words.\n // Extract if capitalized, or if it contains more than 2 words.\n if (!isCapitalized && wordCount <= 2) return false;\n\n return true;\n};\n"],"mappings":";;;;;;;;;;AASA,MAAa,iBAAiB,SAA0B;CACtD,MAAM,UAAU,KAAK,MAAM;AAE3B,KAAI,CAAC,QAAS,QAAO;AAGrB,KAAI,6BAA6B,KAAK,QAAQ,CAAE,QAAO;AAGvD,KAAI,QAAQ,WAAW,IAAI,IAAI,QAAQ,WAAW,KAAK,CAAE,QAAO;AAGhE,KACE,QAAQ,SAAS,KAAK,IACtB,QAAQ,SAAS,KAAK,IACtB,QAAQ,SAAS,MAAM,IACvB,QAAQ,SAAS,KAAK,IACtB,QAAQ,SAAS,UAAU,IAC3B,QAAQ,WAAW,YAAY,IAC/B,QAAQ,WAAW,YAAY,CAE/B,QAAO;AAWT,MAJ0B,QAAQ,MADhC,8DACsD,IAAI,EAAE,EAIxC,SAAS,EAAG,QAAO;CAEzC,MAAM,YAAY,QAAQ,MAAM,MAAM,CAAC;CAGvC,MAAM,gBAAgB,kBAAkB,KAAK,QAAQ;AAGrD,KAAI,cAAc,GAAG;AAEnB,MAAI,eAAe,KAAK,QAAQ,CAAE,QAAO;AAEzC,MAAI,QAAQ,SAAS,IAAI,IAAI,QAAQ,SAAS,IAAI,CAAE,QAAO;;AAK7D,KAAI,CAAC,iBAAiB,aAAa,EAAG,QAAO;AAE7C,QAAO"}
1
+ {"version":3,"file":"shouldExtract.mjs","names":[],"sources":["../../../../src/extractContent/utils/shouldExtract.ts"],"sourcesContent":["/**\n * Checks whether the given text should be extracted as a translatable string.\n *\n * Filters out:\n * - Empty strings\n * - Emails\n * - Uncapitalized strings of 2 words or fewer (likely technical terms)\n * - Dynamic content patterns like Vue bindings (`v-`) or object patterns (`{`)\n */\nexport const shouldExtract = (text: string): boolean => {\n const trimmed = text.trim();\n\n if (!trimmed) return false;\n\n // Ignore emails\n if (/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(trimmed)) return false;\n\n // Ignore dynamic content patterns\n if (trimmed.startsWith('{') || trimmed.startsWith('v-')) return false;\n\n // Ignore explicit code patterns (markers)\n if (\n trimmed.includes('=>') ||\n trimmed.includes(');') ||\n trimmed.includes('(){') ||\n trimmed.includes('==') ||\n trimmed.includes('window.') ||\n trimmed.startsWith('(function') ||\n trimmed.startsWith('function(')\n ) {\n return false;\n }\n\n // Heuristic: check for characters that are common in code but rare in natural text.\n // Whitelist: letters, numbers, spaces, and frequent text symbols (including punctuation, braces, and technical symbols)\n const nonTextualRegex =\n /[^\\p{L}\\p{N}\\s.,!?;:'\"()[\\]{}–—/«»„“\\p{Sc}%&*+#@^_+=<>/~]/gu;\n const nonTextualMatches = trimmed.match(nonTextualRegex) || [];\n\n // If a string contains a high density of truly exceptional symbols (like |, \\, etc.),\n // it is highly likely to be code or complex technical data.\n if (nonTextualMatches.length > 5) return false;\n\n const wordCount = trimmed.split(/\\s+/).length;\n\n // Check if starts with a capital letter (including after an opening parenthesis/quote)\n const isCapitalized = /^['\"([]*\\p{Lu}/u.test(trimmed);\n\n // Ignore technical identifiers (one word strings with camelCase, kebab-case, snake_case etc.)\n if (wordCount === 1) {\n // CamelCase or internal capitals (like camelCaseProperty or CamelCaseProperty)\n if (/[a-z]\\p{Lu}/u.test(trimmed)) return false;\n // kebab-case or snake_case\n if (trimmed.includes('-') || trimmed.includes('_')) return false;\n }\n\n // We usually want to extract full sentences or labels, not single/short technical words.\n // Extract if capitalized, or if it contains more than 2 words.\n if (!isCapitalized && wordCount <= 2) return false;\n\n return true;\n};\n"],"mappings":";;;;;;;;;;AASA,MAAa,iBAAiB,SAA0B;CACtD,MAAM,UAAU,KAAK,MAAM;AAE3B,KAAI,CAAC,QAAS,QAAO;AAGrB,KAAI,6BAA6B,KAAK,QAAQ,CAAE,QAAO;AAGvD,KAAI,QAAQ,WAAW,IAAI,IAAI,QAAQ,WAAW,KAAK,CAAE,QAAO;AAGhE,KACE,QAAQ,SAAS,KAAK,IACtB,QAAQ,SAAS,KAAK,IACtB,QAAQ,SAAS,MAAM,IACvB,QAAQ,SAAS,KAAK,IACtB,QAAQ,SAAS,UAAU,IAC3B,QAAQ,WAAW,YAAY,IAC/B,QAAQ,WAAW,YAAY,CAE/B,QAAO;AAWT,MAJ0B,QAAQ,MAAM,8DAAgB,IAAI,EAAE,EAIxC,SAAS,EAAG,QAAO;CAEzC,MAAM,YAAY,QAAQ,MAAM,MAAM,CAAC;CAGvC,MAAM,gBAAgB,kBAAkB,KAAK,QAAQ;AAGrD,KAAI,cAAc,GAAG;AAEnB,MAAI,eAAe,KAAK,QAAQ,CAAE,QAAO;AAEzC,MAAI,QAAQ,SAAS,IAAI,IAAI,QAAQ,SAAS,IAAI,CAAE,QAAO;;AAK7D,KAAI,CAAC,iBAAiB,aAAa,EAAG,QAAO;AAE7C,QAAO"}