@intlayer/vue-compiler 9.3.1 → 9.3.3

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.
@@ -8,13 +8,18 @@ _vue_compiler_sfc = require_runtime.__toESM(_vue_compiler_sfc);
8
8
  * Extracts intlayer dictionary field usage from a Vue SFC for a set of
9
9
  * plain variable bindings (i.e. `const content = useIntlayer('key')`).
10
10
  *
11
- * Two access patterns are recognised:
11
+ * Three access patterns are recognised:
12
12
  *
13
- * 1. `varName.value.fieldName` in script blocks
14
- * Vue's `useIntlayer` returns a reactive `Ref<Content>`, so fields are
15
- * accessed one level deeper via `.value`.
13
+ * 1. `varName.fieldName` in script blocks
14
+ * `useIntlayer` returns a deep reactive proxy, so content fields are read
15
+ * directly on the variable (`content.title`, `content.count(2)`), the
16
+ * `.value` unwrapping happening on the leaf node instead.
16
17
  *
17
- * 2. `varName.fieldName` in the template block
18
+ * 2. `varName.value.fieldName` in script blocks
19
+ * Legacy `Ref<Content>` accessor indirection, where fields live one level
20
+ * deeper than the variable.
21
+ *
22
+ * 3. `varName.fieldName` in the template block
18
23
  * Inside `<template>`, Vue automatically unwraps top-level refs, so
19
24
  * fields are accessed directly without `.value`.
20
25
  *
@@ -25,6 +30,21 @@ _vue_compiler_sfc = require_runtime.__toESM(_vue_compiler_sfc);
25
30
  /** Escapes special regex characters in a string used as a regex literal. */
26
31
  const escapeRegExp = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
27
32
  /**
33
+ * Runs `fieldRegex` over `source` and adds every captured field name to
34
+ * `fields`, skipping the `value` accessor of an intlayer node (it addresses the
35
+ * node wrapper, not a content field).
36
+ *
37
+ * Over-approximating is safe here: an extra name only keeps a field that could
38
+ * have been pruned, while a missing name removes content the app reads.
39
+ */
40
+ const collectFieldNames = (source, fieldRegex, fields) => {
41
+ fieldRegex.lastIndex = 0;
42
+ for (let match = fieldRegex.exec(source); match !== null; match = fieldRegex.exec(source)) {
43
+ const field = match[1];
44
+ if (field && field !== "value") fields.add(field);
45
+ }
46
+ };
47
+ /**
28
48
  * Splits a Vue SFC source string into its script and template regions.
29
49
  *
30
50
  * Uses `@vue/compiler-sfc` to get exact character offsets so that nested
@@ -68,17 +88,9 @@ const extractVueIntlayerFieldUsage = (code, plainVariables) => {
68
88
  for (const { variableName, dictionaryKey } of plainVariables) {
69
89
  const fields = /* @__PURE__ */ new Set();
70
90
  const esc = escapeRegExp(variableName);
71
- const scriptRe = new RegExp(`\\b${esc}\\.value\\.(\\w+)`, "g");
72
- scriptRe.lastIndex = 0;
73
- for (let m = scriptRe.exec(scriptSource); m !== null; m = scriptRe.exec(scriptSource)) {
74
- const field = m[1];
75
- if (field !== "value") fields.add(field);
76
- }
77
- if (templateSource) {
78
- const templateRe = new RegExp(`\\b${esc}\\.(\\w+)`, "g");
79
- templateRe.lastIndex = 0;
80
- for (let m = templateRe.exec(templateSource); m !== null; m = templateRe.exec(templateSource)) fields.add(m[1]);
81
- }
91
+ collectFieldNames(scriptSource, new RegExp(`\\b${esc}\\.(\\w+)`, "g"), fields);
92
+ collectFieldNames(scriptSource, new RegExp(`\\b${esc}\\.value\\.(\\w+)`, "g"), fields);
93
+ if (templateSource) collectFieldNames(templateSource, new RegExp(`\\b${esc}\\.(\\w+)`, "g"), fields);
82
94
  if (fields.size > 0) result.set(dictionaryKey, fields);
83
95
  }
84
96
  return result;
@@ -1 +1 @@
1
- {"version":3,"file":"extractVueFieldUsage.cjs","names":["vueSfc"],"sources":["../../src/extractVueFieldUsage.ts"],"sourcesContent":["/**\n * Extracts intlayer dictionary field usage from a Vue SFC for a set of\n * plain variable bindings (i.e. `const content = useIntlayer('key')`).\n *\n * Two access patterns are recognised:\n *\n * 1. `varName.value.fieldName` in script blocks\n * Vue's `useIntlayer` returns a reactive `Ref<Content>`, so fields are\n * accessed one level deeper via `.value`.\n *\n * 2. `varName.fieldName` in the template block\n * Inside `<template>`, Vue automatically unwraps top-level refs, so\n * fields are accessed directly without `.value`.\n *\n * The template block is extracted via `@vue/compiler-sfc` so that nested\n * `<template v-for>` / `<template v-if>` tags do not confuse the parser.\n * Falls back to a greedy regex when the package is unavailable.\n */\n\nimport vueSfc from '@vue/compiler-sfc';\n\n/** Escapes special regex characters in a string used as a regex literal. */\nconst escapeRegExp = (str: string): string =>\n str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n/** Input descriptor for a single plain variable binding. */\nexport type PlainVariableInfo = {\n /** The local variable name in the script (`content` in `const content = useIntlayer(…)`). */\n variableName: string;\n /** The intlayer dictionary key passed to `useIntlayer`. */\n dictionaryKey: string;\n};\n\n/**\n * Splits a Vue SFC source string into its script and template regions.\n *\n * Uses `@vue/compiler-sfc` to get exact character offsets so that nested\n * `<template>` tags (used by Vue directives like `v-for` / `v-if`) never\n * cause the template region to be mis-identified.\n */\nconst splitVueSfc = (\n code: string\n): { scriptSource: string; templateSource: string } => {\n try {\n const { descriptor } = (\n vueSfc.parse as (src: string) => { descriptor: any }\n )(code);\n\n const templateSource: string = descriptor.template?.content ?? '';\n\n const scriptParts: string[] = [];\n if (descriptor.script?.content) scriptParts.push(descriptor.script.content);\n if (descriptor.scriptSetup?.content)\n scriptParts.push(descriptor.scriptSetup.content);\n const scriptSource = scriptParts.join('\\n');\n\n return { scriptSource, templateSource };\n } catch {\n // @vue/compiler-sfc not available or parse failed.\n // Fall back: use a greedy regex to capture everything between the\n // outermost <template> tags (robust against nested tags unlike the\n // non-greedy variant).\n const templateMatch = /<template(?:[^>]*)>([\\s\\S]*)<\\/template>/i.exec(\n code\n );\n const templateSource = templateMatch ? templateMatch[1] : '';\n const scriptSource = code.replace(/<template[\\s\\S]*<\\/template>/gi, '');\n return { scriptSource, templateSource };\n }\n};\n\n/**\n * Analyzes a Vue SFC source string and returns the top-level content field\n * names that are statically accessed for each plain intlayer variable binding.\n *\n * @param code - Full `.vue` file source.\n * @param plainVariables - List of plain variable bindings to analyse.\n * @returns Map from dictionary key to the set of accessed top-level field names.\n * If no fields can be determined for a given key it is omitted from the\n * map so the caller can fall back to `'all'`.\n */\nexport const extractVueIntlayerFieldUsage = (\n code: string,\n plainVariables: PlainVariableInfo[]\n): Map<string, Set<string>> => {\n const result = new Map<string, Set<string>>();\n\n if (plainVariables.length === 0) return result;\n\n const { scriptSource, templateSource } = splitVueSfc(code);\n\n for (const { variableName, dictionaryKey } of plainVariables) {\n const fields = new Set<string>();\n const esc = escapeRegExp(variableName);\n\n // ── 1. Script pattern: varName.value.fieldName ─────────────────────────\n // Vue's reactive ref accessor; the actual content fields live one level\n // deeper than the variable itself.\n const scriptRe = new RegExp(`\\\\b${esc}\\\\.value\\\\.(\\\\w+)`, 'g');\n scriptRe.lastIndex = 0;\n for (\n let m = scriptRe.exec(scriptSource);\n m !== null;\n m = scriptRe.exec(scriptSource)\n ) {\n const field = m[1];\n // Skip chained `.value.value` artefacts\n if (field !== 'value') fields.add(field);\n }\n\n // ── 2. Template pattern: varName.fieldName ─────────────────────────────\n // Inside `<template>` Vue auto-unwraps refs, so content is accessed\n // directly without `.value`.\n if (templateSource) {\n const templateRe = new RegExp(`\\\\b${esc}\\\\.(\\\\w+)`, 'g');\n\n templateRe.lastIndex = 0;\n for (\n let m = templateRe.exec(templateSource);\n m !== null;\n m = templateRe.exec(templateSource)\n ) {\n fields.add(m[1]);\n }\n }\n\n if (fields.size > 0) {\n result.set(dictionaryKey, fields);\n }\n }\n\n return result;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAsBA,MAAM,gBAAgB,QACpB,IAAI,QAAQ,uBAAuB,MAAM;;;;;;;;AAiB3C,MAAM,eACJ,SACqD;CACrD,IAAI;EACF,MAAM,EAAE,eACNA,0BAAO,MACP,IAAI;EAEN,MAAM,iBAAyB,WAAW,UAAU,WAAW;EAE/D,MAAM,cAAwB,CAAC;EAC/B,IAAI,WAAW,QAAQ,SAAS,YAAY,KAAK,WAAW,OAAO,OAAO;EAC1E,IAAI,WAAW,aAAa,SAC1B,YAAY,KAAK,WAAW,YAAY,OAAO;EAGjD,OAAO;GAAE,cAFY,YAAY,KAAK,IAElB;GAAG;EAAe;CACxC,QAAQ;EAKN,MAAM,gBAAgB,4CAA4C,KAChE,IACF;EACA,MAAM,iBAAiB,gBAAgB,cAAc,KAAK;EAE1D,OAAO;GAAE,cADY,KAAK,QAAQ,kCAAkC,EAChD;GAAG;EAAe;CACxC;AACF;;;;;;;;;;;AAYA,MAAa,gCACX,MACA,mBAC6B;CAC7B,MAAM,yBAAS,IAAI,IAAyB;CAE5C,IAAI,eAAe,WAAW,GAAG,OAAO;CAExC,MAAM,EAAE,cAAc,mBAAmB,YAAY,IAAI;CAEzD,KAAK,MAAM,EAAE,cAAc,mBAAmB,gBAAgB;EAC5D,MAAM,yBAAS,IAAI,IAAY;EAC/B,MAAM,MAAM,aAAa,YAAY;EAKrC,MAAM,WAAW,IAAI,OAAO,MAAM,IAAI,oBAAoB,GAAG;EAC7D,SAAS,YAAY;EACrB,KACE,IAAI,IAAI,SAAS,KAAK,YAAY,GAClC,MAAM,MACN,IAAI,SAAS,KAAK,YAAY,GAC9B;GACA,MAAM,QAAQ,EAAE;GAEhB,IAAI,UAAU,SAAS,OAAO,IAAI,KAAK;EACzC;EAKA,IAAI,gBAAgB;GAClB,MAAM,aAAa,IAAI,OAAO,MAAM,IAAI,YAAY,GAAG;GAEvD,WAAW,YAAY;GACvB,KACE,IAAI,IAAI,WAAW,KAAK,cAAc,GACtC,MAAM,MACN,IAAI,WAAW,KAAK,cAAc,GAElC,OAAO,IAAI,EAAE,EAAE;EAEnB;EAEA,IAAI,OAAO,OAAO,GAChB,OAAO,IAAI,eAAe,MAAM;CAEpC;CAEA,OAAO;AACT"}
1
+ {"version":3,"file":"extractVueFieldUsage.cjs","names":["vueSfc"],"sources":["../../src/extractVueFieldUsage.ts"],"sourcesContent":["/**\n * Extracts intlayer dictionary field usage from a Vue SFC for a set of\n * plain variable bindings (i.e. `const content = useIntlayer('key')`).\n *\n * Three access patterns are recognised:\n *\n * 1. `varName.fieldName` in script blocks\n * `useIntlayer` returns a deep reactive proxy, so content fields are read\n * directly on the variable (`content.title`, `content.count(2)`), the\n * `.value` unwrapping happening on the leaf node instead.\n *\n * 2. `varName.value.fieldName` in script blocks\n * Legacy `Ref<Content>` accessor indirection, where fields live one level\n * deeper than the variable.\n *\n * 3. `varName.fieldName` in the template block\n * Inside `<template>`, Vue automatically unwraps top-level refs, so\n * fields are accessed directly without `.value`.\n *\n * The template block is extracted via `@vue/compiler-sfc` so that nested\n * `<template v-for>` / `<template v-if>` tags do not confuse the parser.\n * Falls back to a greedy regex when the package is unavailable.\n */\n\nimport vueSfc from '@vue/compiler-sfc';\n\n/** Escapes special regex characters in a string used as a regex literal. */\nconst escapeRegExp = (str: string): string =>\n str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n/**\n * Runs `fieldRegex` over `source` and adds every captured field name to\n * `fields`, skipping the `value` accessor of an intlayer node (it addresses the\n * node wrapper, not a content field).\n *\n * Over-approximating is safe here: an extra name only keeps a field that could\n * have been pruned, while a missing name removes content the app reads.\n */\nconst collectFieldNames = (\n source: string,\n fieldRegex: RegExp,\n fields: Set<string>\n): void => {\n fieldRegex.lastIndex = 0;\n\n for (\n let match = fieldRegex.exec(source);\n match !== null;\n match = fieldRegex.exec(source)\n ) {\n const field = match[1];\n if (field && field !== 'value') fields.add(field);\n }\n};\n\n/** Input descriptor for a single plain variable binding. */\nexport type PlainVariableInfo = {\n /** The local variable name in the script (`content` in `const content = useIntlayer(…)`). */\n variableName: string;\n /** The intlayer dictionary key passed to `useIntlayer`. */\n dictionaryKey: string;\n};\n\n/**\n * Splits a Vue SFC source string into its script and template regions.\n *\n * Uses `@vue/compiler-sfc` to get exact character offsets so that nested\n * `<template>` tags (used by Vue directives like `v-for` / `v-if`) never\n * cause the template region to be mis-identified.\n */\nconst splitVueSfc = (\n code: string\n): { scriptSource: string; templateSource: string } => {\n try {\n const { descriptor } = (\n vueSfc.parse as (src: string) => { descriptor: any }\n )(code);\n\n const templateSource: string = descriptor.template?.content ?? '';\n\n const scriptParts: string[] = [];\n if (descriptor.script?.content) scriptParts.push(descriptor.script.content);\n if (descriptor.scriptSetup?.content)\n scriptParts.push(descriptor.scriptSetup.content);\n const scriptSource = scriptParts.join('\\n');\n\n return { scriptSource, templateSource };\n } catch {\n // @vue/compiler-sfc not available or parse failed.\n // Fall back: use a greedy regex to capture everything between the\n // outermost <template> tags (robust against nested tags unlike the\n // non-greedy variant).\n const templateMatch = /<template(?:[^>]*)>([\\s\\S]*)<\\/template>/i.exec(\n code\n );\n const templateSource = templateMatch ? templateMatch[1] : '';\n const scriptSource = code.replace(/<template[\\s\\S]*<\\/template>/gi, '');\n return { scriptSource, templateSource };\n }\n};\n\n/**\n * Analyzes a Vue SFC source string and returns the top-level content field\n * names that are statically accessed for each plain intlayer variable binding.\n *\n * @param code - Full `.vue` file source.\n * @param plainVariables - List of plain variable bindings to analyse.\n * @returns Map from dictionary key to the set of accessed top-level field names.\n * If no fields can be determined for a given key it is omitted from the\n * map so the caller can fall back to `'all'`.\n */\nexport const extractVueIntlayerFieldUsage = (\n code: string,\n plainVariables: PlainVariableInfo[]\n): Map<string, Set<string>> => {\n const result = new Map<string, Set<string>>();\n\n if (plainVariables.length === 0) return result;\n\n const { scriptSource, templateSource } = splitVueSfc(code);\n\n for (const { variableName, dictionaryKey } of plainVariables) {\n const fields = new Set<string>();\n const esc = escapeRegExp(variableName);\n\n // ── 1. Script pattern: varName.fieldName ───────────────────────────────\n // The proxy returned by `useIntlayer` exposes content fields directly, so\n // scripts read them without any indirection. `.value` is the leaf-node\n // accessor, never a content field — the deeper name is picked up by the\n // `.value.` pattern below.\n collectFieldNames(\n scriptSource,\n new RegExp(`\\\\b${esc}\\\\.(\\\\w+)`, 'g'),\n fields\n );\n\n // ── 2. Script pattern: varName.value.fieldName ─────────────────────────\n // Legacy reactive ref accessor; the actual content fields live one level\n // deeper than the variable itself.\n collectFieldNames(\n scriptSource,\n new RegExp(`\\\\b${esc}\\\\.value\\\\.(\\\\w+)`, 'g'),\n fields\n );\n\n // ── 3. Template pattern: varName.fieldName ─────────────────────────────\n // Inside `<template>` Vue auto-unwraps refs, so content is accessed\n // directly without `.value`.\n if (templateSource) {\n collectFieldNames(\n templateSource,\n new RegExp(`\\\\b${esc}\\\\.(\\\\w+)`, 'g'),\n fields\n );\n }\n\n if (fields.size > 0) {\n result.set(dictionaryKey, fields);\n }\n }\n\n return result;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,MAAM,gBAAgB,QACpB,IAAI,QAAQ,uBAAuB,MAAM;;;;;;;;;AAU3C,MAAM,qBACJ,QACA,YACA,WACS;CACT,WAAW,YAAY;CAEvB,KACE,IAAI,QAAQ,WAAW,KAAK,MAAM,GAClC,UAAU,MACV,QAAQ,WAAW,KAAK,MAAM,GAC9B;EACA,MAAM,QAAQ,MAAM;EACpB,IAAI,SAAS,UAAU,SAAS,OAAO,IAAI,KAAK;CAClD;AACF;;;;;;;;AAiBA,MAAM,eACJ,SACqD;CACrD,IAAI;EACF,MAAM,EAAE,eACNA,0BAAO,MACP,IAAI;EAEN,MAAM,iBAAyB,WAAW,UAAU,WAAW;EAE/D,MAAM,cAAwB,CAAC;EAC/B,IAAI,WAAW,QAAQ,SAAS,YAAY,KAAK,WAAW,OAAO,OAAO;EAC1E,IAAI,WAAW,aAAa,SAC1B,YAAY,KAAK,WAAW,YAAY,OAAO;EAGjD,OAAO;GAAE,cAFY,YAAY,KAAK,IAElB;GAAG;EAAe;CACxC,QAAQ;EAKN,MAAM,gBAAgB,4CAA4C,KAChE,IACF;EACA,MAAM,iBAAiB,gBAAgB,cAAc,KAAK;EAE1D,OAAO;GAAE,cADY,KAAK,QAAQ,kCAAkC,EAChD;GAAG;EAAe;CACxC;AACF;;;;;;;;;;;AAYA,MAAa,gCACX,MACA,mBAC6B;CAC7B,MAAM,yBAAS,IAAI,IAAyB;CAE5C,IAAI,eAAe,WAAW,GAAG,OAAO;CAExC,MAAM,EAAE,cAAc,mBAAmB,YAAY,IAAI;CAEzD,KAAK,MAAM,EAAE,cAAc,mBAAmB,gBAAgB;EAC5D,MAAM,yBAAS,IAAI,IAAY;EAC/B,MAAM,MAAM,aAAa,YAAY;EAOrC,kBACE,cACA,IAAI,OAAO,MAAM,IAAI,YAAY,GAAG,GACpC,MACF;EAKA,kBACE,cACA,IAAI,OAAO,MAAM,IAAI,oBAAoB,GAAG,GAC5C,MACF;EAKA,IAAI,gBACF,kBACE,gBACA,IAAI,OAAO,MAAM,IAAI,YAAY,GAAG,GACpC,MACF;EAGF,IAAI,OAAO,OAAO,GAChB,OAAO,IAAI,eAAe,MAAM;CAEpC;CAEA,OAAO;AACT"}
@@ -19,10 +19,14 @@ magic_string = require_runtime.__toESM(magic_string);
19
19
  const detectExistingIntlayerCall = (scriptText, absoluteOffset) => {
20
20
  let info = null;
21
21
  try {
22
- const ast = (0, _babel_core.parse)(scriptText, { parserOpts: {
23
- sourceType: "module",
24
- plugins: ["typescript", "jsx"]
25
- } });
22
+ const ast = (0, _babel_core.parseSync)(scriptText, {
23
+ configFile: false,
24
+ babelrc: false,
25
+ parserOpts: {
26
+ sourceType: "module",
27
+ plugins: ["typescript", "jsx"]
28
+ }
29
+ });
26
30
  if (!ast) return null;
27
31
  (0, _babel_core.traverse)(ast, { CallExpression(path) {
28
32
  const callee = path.node.callee;
@@ -192,10 +196,14 @@ const intlayerVueExtract = (code, filename, options = {}) => {
192
196
  const scriptText = scriptBlock.content;
193
197
  const offset = scriptBlock.loc.start.offset;
194
198
  try {
195
- const ast = (0, _babel_core.parse)(scriptText, { parserOpts: {
196
- sourceType: "module",
197
- plugins: ["typescript", "jsx"]
198
- } });
199
+ const ast = (0, _babel_core.parseSync)(scriptText, {
200
+ configFile: false,
201
+ babelrc: false,
202
+ parserOpts: {
203
+ sourceType: "module",
204
+ plugins: ["typescript", "jsx"]
205
+ }
206
+ });
199
207
  if (ast) (0, _babel_core.traverse)(ast, { StringLiteral(path) {
200
208
  if (path.parentPath.isImportDeclaration()) return;
201
209
  if (path.parentPath.isExportDeclaration()) return;
@@ -1 +1 @@
1
- {"version":3,"file":"vue-intlayer-extract.cjs","names":["babelParse","t","DEFAULT_LOCALE","vueSfc","MagicString","readFileSync"],"sources":["../../src/vue-intlayer-extract.ts"],"sourcesContent":["import { readFileSync, writeFileSync } from 'node:fs';\nimport { parse as babelParse, types as t, traverse } from '@babel/core';\nimport { DEFAULT_LOCALE } from '@intlayer/config/defaultValues';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport vueSfc from '@vue/compiler-sfc';\nimport MagicString from 'magic-string';\n\ntype ExistingCallInfo = {\n isDestructured: boolean;\n existingDestructuredKeys: string[];\n /** The variable name used to store the call result (e.g. `t` in `const t = useIntlayer(...)`) */\n variableName: string;\n /** Absolute position of `}` in the full file — only valid when `isDestructured` */\n closingBraceAbsolutePos: number;\n /** Absolute position of end of last property — only valid when `isDestructured` */\n lastPropAbsoluteEnd: number;\n} | null;\n\n/**\n * Detects whether the script block already contains a `useIntlayer` /\n * `getIntlayer` call and, if so, whether its result is destructured.\n *\n * @param scriptText Raw text of the script block content.\n * @param absoluteOffset Byte offset of `scriptText[0]` in the full SFC source.\n */\nconst detectExistingIntlayerCall = (\n scriptText: string,\n absoluteOffset: number\n): ExistingCallInfo => {\n let info: ExistingCallInfo = null;\n\n try {\n const ast = babelParse(scriptText, {\n parserOpts: { sourceType: 'module', plugins: ['typescript', 'jsx'] },\n });\n\n if (!ast) return null;\n\n traverse(ast, {\n CallExpression(path: any) {\n const callee = path.node.callee;\n\n if (\n !t.isIdentifier(callee) ||\n (callee.name !== 'useIntlayer' && callee.name !== 'getIntlayer')\n )\n return;\n\n const parent = path.parent;\n\n if (t.isVariableDeclarator(parent) && t.isObjectPattern(parent.id)) {\n const properties = parent.id.properties;\n const existingDestructuredKeys = properties\n .filter(\n (property: any): property is typeof t.objectProperty =>\n t.isObjectProperty(property) && t.isIdentifier(property.key)\n )\n .map((property: any) => (property.key as any).name as string);\n const lastProp = properties[properties.length - 1];\n\n info = {\n isDestructured: true,\n variableName: 'content',\n existingDestructuredKeys,\n closingBraceAbsolutePos: absoluteOffset + (parent.id.end! - 1),\n lastPropAbsoluteEnd: absoluteOffset + lastProp.end!,\n };\n } else {\n const variableName =\n t.isVariableDeclarator(parent) && t.isIdentifier(parent.id)\n ? parent.id.name\n : 'content';\n\n info = {\n isDestructured: false,\n variableName,\n existingDestructuredKeys: [],\n closingBraceAbsolutePos: -1,\n lastPropAbsoluteEnd: -1,\n };\n }\n\n path.stop();\n },\n });\n } catch {\n // Silently ignore parse failures — fall back to no-info\n }\n\n return info;\n};\n\nexport type ExtractedContent = Record<string, string>;\n\nexport type ExtractResult = {\n dictionaryKey: string;\n filePath: string;\n content: ExtractedContent;\n locale: Locale;\n};\n\nexport type ExtractPluginOptions = {\n defaultLocale?: Locale;\n packageName?: string;\n filesList?: string[];\n shouldExtract?: (text: string) => boolean;\n onExtract?: (result: ExtractResult) => void;\n dictionaryKey?: string;\n attributesToExtract?: readonly string[];\n extractDictionaryKeyFromPath?: (path: string) => string;\n generateKey?: (text: string, existingKeys: Set<string>) => string;\n};\n\ntype Replacement = {\n start: number;\n end: number;\n replacement: string;\n key: string;\n value: string;\n};\n\nexport const shouldProcessFile = (\n filename: string | undefined,\n filesList?: string[]\n): boolean => {\n if (!filename) return false;\n if (!filesList || filesList.length === 0) return true;\n\n const normalizedFilename = filename.replace(/\\\\/g, '/');\n return filesList.some((f) => {\n const normalizedF = f.replace(/\\\\/g, '/');\n return normalizedF === normalizedFilename;\n });\n};\n\ntype VueParseResult = {\n descriptor: {\n template?: {\n ast: VueAstNode;\n loc: { start: { offset: number }; end: { offset: number } };\n };\n script?: {\n content: string;\n loc: { start: { offset: number }; end: { offset: number } };\n };\n scriptSetup?: {\n content: string;\n loc: { start: { offset: number }; end: { offset: number } };\n };\n };\n};\n\ntype VueAstNode = {\n type: number;\n content?: string;\n children?: VueAstNode[];\n props?: VueAstProp[];\n loc: { start: { offset: number }; end: { offset: number } };\n};\n\ntype VueAstProp = {\n type: number;\n name: string;\n value?: { content: string };\n loc: { start: { offset: number }; end: { offset: number } };\n};\n\nconst NODE_TYPES = {\n TEXT: 2,\n ELEMENT: 1,\n ATTRIBUTE: 6,\n INTERPOLATION: 5,\n};\n\nexport const intlayerVueExtract = (\n code: string,\n filename: string,\n options: ExtractPluginOptions = {}\n): { code: string; map?: unknown; extracted: boolean } | null => {\n const {\n defaultLocale = DEFAULT_LOCALE,\n packageName = 'vue-intlayer',\n filesList,\n shouldExtract,\n onExtract,\n dictionaryKey: dictionaryKeyOption,\n attributesToExtract = [],\n extractDictionaryKeyFromPath,\n generateKey,\n } = options;\n\n if (!shouldProcessFile(filename, filesList)) return null;\n if (!filename.endsWith('.vue')) return null;\n\n let parseVue: (code: string) => VueParseResult;\n\n try {\n parseVue = vueSfc.parse as unknown as (code: string) => VueParseResult;\n } catch {\n console.warn('Vue extraction: @vue/compiler-sfc not found.');\n return null;\n }\n\n const sfc = parseVue(code);\n const magic = new MagicString(code);\n\n const extractedContent: ExtractedContent = {};\n const existingKeys = new Set<string>();\n const dictionaryKey =\n dictionaryKeyOption ?? extractDictionaryKeyFromPath?.(filename) ?? '';\n const replacements: Replacement[] = [];\n\n // Detect existing useIntlayer / getIntlayer call in the script block BEFORE\n // walking the template, so the correct access pattern can be chosen.\n const scriptBlock = sfc.descriptor.scriptSetup ?? sfc.descriptor.script;\n const existingCallInfo = scriptBlock\n ? detectExistingIntlayerCall(\n scriptBlock.content,\n scriptBlock.loc.start.offset\n )\n : null;\n\n const isDestructured = existingCallInfo?.isDestructured ?? false;\n const varName = existingCallInfo?.variableName ?? 'content';\n\n // Walk Vue Template AST\n if (sfc.descriptor.template) {\n const walkVueAst = (node: VueAstNode) => {\n if (node.type === NODE_TYPES.TEXT) {\n const text = node.content ?? '';\n\n if (shouldExtract?.(text) && generateKey) {\n const key = generateKey(text, existingKeys);\n existingKeys.add(key);\n // When the existing call is destructured, access the key directly;\n // otherwise use the `content` variable.\n const ref = isDestructured ? key : `${varName}.${key}`;\n replacements.push({\n start: node.loc.start.offset,\n end: node.loc.end.offset,\n replacement: `{{ ${ref} }}`,\n key,\n value: text.replace(/\\s+/g, ' ').trim(),\n });\n }\n } else if (node.type === NODE_TYPES.ELEMENT) {\n const children = node.children ?? [];\n\n // Try to handle as insertion (mixed TEXT + INTERPOLATION children)\n if (\n children.length > 0 &&\n children.some((c) => c.type === NODE_TYPES.INTERPOLATION)\n ) {\n const parts: {\n type: 'text' | 'var';\n value: string;\n originalExpr: string;\n }[] = [];\n let hasSignificantText = false;\n let isValid = true;\n\n for (const child of children) {\n if (child.type === NODE_TYPES.TEXT) {\n const text = child.content ?? '';\n if (text.trim().length > 0) hasSignificantText = true;\n parts.push({ type: 'text', value: text, originalExpr: '' });\n } else if (child.type === NODE_TYPES.INTERPOLATION) {\n // Extract the expression source between {{ and }}\n const exprCode = code\n .slice(child.loc.start.offset + 2, child.loc.end.offset - 2)\n .trim();\n const varName = exprCode.includes('.')\n ? exprCode\n .split('.')\n .pop()!\n .replace(/[^\\w$]/g, '')\n : exprCode;\n parts.push({\n type: 'var',\n value: varName,\n originalExpr: exprCode,\n });\n } else {\n isValid = false;\n break;\n }\n }\n\n if (\n isValid &&\n hasSignificantText &&\n parts.some((p) => p.type === 'var')\n ) {\n let combined = '';\n for (const p of parts) {\n combined += p.type === 'var' ? `{{${p.value}}}` : p.value;\n }\n const cleanString = combined.replace(/\\s+/g, ' ').trim();\n\n if (shouldExtract?.(cleanString) && generateKey) {\n const key = generateKey(cleanString, existingKeys);\n existingKeys.add(key);\n const ref = isDestructured ? key : `${varName}.${key}`;\n\n const uniqueVarPairs = [\n ...new Set(\n parts\n .filter((p) => p.type === 'var')\n .map((p) => `${p.value}: ${p.originalExpr}`)\n ),\n ];\n const varArgs = uniqueVarPairs.join(', ');\n const replacement = `{{ ${ref}({ ${varArgs} }) }}`;\n\n const firstChild = children[0];\n const lastChild = children[children.length - 1];\n replacements.push({\n start: firstChild.loc.start.offset,\n end: lastChild.loc.end.offset,\n replacement,\n key,\n value: cleanString,\n });\n\n // Process props but skip children (they are replaced)\n node.props?.forEach((prop) => {\n if (\n prop.type === NODE_TYPES.ATTRIBUTE &&\n (attributesToExtract as readonly string[]).includes(\n prop.name\n ) &&\n prop.value\n ) {\n const text = prop.value.content;\n if (shouldExtract?.(text) && generateKey) {\n const propKey = generateKey(text, existingKeys);\n existingKeys.add(propKey);\n const propRef = isDestructured\n ? propKey\n : `${varName}.${propKey}`;\n replacements.push({\n start: prop.loc.start.offset,\n end: prop.loc.end.offset,\n replacement: `:${prop.name}=\"${propRef}\"`,\n key: propKey,\n value: text.trim(),\n });\n }\n }\n });\n return; // don't recurse into children\n }\n }\n }\n\n // Regular element: handle props\n node.props?.forEach((prop) => {\n if (\n prop.type === NODE_TYPES.ATTRIBUTE &&\n (attributesToExtract as readonly string[]).includes(prop.name) &&\n prop.value\n ) {\n const text = prop.value.content;\n\n if (shouldExtract?.(text) && generateKey) {\n const key = generateKey(text, existingKeys);\n existingKeys.add(key);\n const ref = isDestructured ? key : `${varName}.${key}`;\n replacements.push({\n start: prop.loc.start.offset,\n end: prop.loc.end.offset,\n replacement: `:${prop.name}=\"${ref}\"`,\n key,\n value: text.trim(),\n });\n }\n }\n });\n }\n\n if (node.children) {\n node.children.forEach(walkVueAst);\n }\n };\n\n walkVueAst(sfc.descriptor.template.ast);\n }\n\n // Extract and Walk Script using Babel\n if (scriptBlock) {\n const scriptText = scriptBlock.content;\n const offset = scriptBlock.loc.start.offset;\n\n try {\n const ast = babelParse(scriptText, {\n parserOpts: {\n sourceType: 'module',\n plugins: ['typescript', 'jsx'],\n },\n });\n\n if (ast) {\n traverse(ast, {\n StringLiteral(path: any) {\n if (path.parentPath.isImportDeclaration()) return;\n\n if (path.parentPath.isExportDeclaration()) return;\n\n if (path.parentPath.isImportSpecifier()) return;\n\n if (path.parentPath.isObjectProperty() && path.key === 'key')\n return;\n\n if (path.parentPath.isCallExpression()) {\n const callee = path.parentPath.node.callee;\n\n if (\n t.isMemberExpression(callee) &&\n t.isIdentifier(callee.object) &&\n callee.object.name === 'console'\n )\n return;\n\n if (\n t.isIdentifier(callee) &&\n (callee.name === 'useIntlayer' || callee.name === 't')\n )\n return;\n\n if (callee.type === 'Import') return;\n\n if (t.isIdentifier(callee) && callee.name === 'require') return;\n }\n\n const text = path.node.value;\n\n if (shouldExtract?.(text) && generateKey) {\n const key = generateKey(text, existingKeys);\n existingKeys.add(key);\n\n if (path.node.start != null && path.node.end != null) {\n const ref = isDestructured ? key : `${varName}.${key}`;\n replacements.push({\n start: offset + path.node.start,\n end: offset + path.node.end,\n replacement: ref,\n key,\n value: text.trim(),\n });\n }\n }\n },\n });\n }\n } catch (e) {\n console.warn(\n `Vue extraction: Failed to parse script content for ${filename}`,\n e\n );\n }\n }\n\n // Abort if nothing was extracted\n if (replacements.length === 0) return null;\n\n // Apply Replacements in Reverse Order\n replacements.sort((a, b) => b.start - a.start);\n\n for (const { start, end, replacement, key, value } of replacements) {\n magic.overwrite(start, end, replacement);\n extractedContent[key] = value;\n }\n\n // When the existing call is destructured, inject only the missing keys into\n // the ObjectPattern — no new `content` variable is needed.\n if (\n existingCallInfo?.isDestructured &&\n existingCallInfo.closingBraceAbsolutePos >= 0\n ) {\n const missingKeys = Object.keys(extractedContent).filter(\n (k) => !existingCallInfo.existingDestructuredKeys.includes(k)\n );\n\n if (missingKeys.length > 0) {\n // Insert right after the last property so the space/newline before `}`\n // is naturally preserved: `{ a }` → `{ a, b }`.\n magic.appendLeft(\n existingCallInfo.lastPropAbsoluteEnd,\n `, ${missingKeys.join(', ')}`\n );\n }\n }\n\n // Inject necessary imports and setup (only when no existing call was detected)\n const finalScriptContent = scriptBlock?.content ?? '';\n\n const hasUseIntlayerImport =\n /import\\s*{[^}]*useIntlayer[^}]*}\\s*from\\s*['\"][^'\"]+['\"]/.test(\n finalScriptContent\n ) ||\n /import\\s+useIntlayer\\s+from\\s*['\"][^'\"]+['\"]/.test(finalScriptContent);\n\n // An existing call (destructured or not) means no new declaration is needed.\n const hasContentDeclaration =\n existingCallInfo !== null ||\n /const\\s+content\\s*=\\s*useIntlayer\\s*\\(/.test(finalScriptContent);\n\n const importStmt = hasUseIntlayerImport\n ? ''\n : `import { useIntlayer } from '${packageName}';`;\n const contentDecl = hasContentDeclaration\n ? ''\n : `const content = useIntlayer('${dictionaryKey}');`;\n\n const injectionParts = [importStmt, contentDecl].filter(Boolean);\n\n if (injectionParts.length > 0) {\n const injection = `\\n${injectionParts.join('\\n')}\\n`;\n\n if (sfc.descriptor.scriptSetup) {\n magic.appendLeft(sfc.descriptor.scriptSetup.loc.start.offset, injection);\n } else if (sfc.descriptor.script) {\n magic.appendLeft(sfc.descriptor.script.loc.start.offset, injection);\n } else {\n magic.prepend(\n `<script setup>\\n${importStmt}\\n${contentDecl}\\n</script>\\n`\n );\n }\n }\n\n if (onExtract) {\n onExtract({\n dictionaryKey,\n filePath: filename,\n content: { ...extractedContent },\n locale: defaultLocale,\n });\n }\n\n return {\n code: magic.toString(),\n map: magic.generateMap({ source: filename, includeContent: true }),\n extracted: true,\n };\n};\n\ntype Tools = {\n generateKey: (text: string, existingKeys: Set<string>) => string;\n shouldExtract: (text: string) => boolean;\n extractDictionaryKeyFromPath: (path: string) => string;\n attributesToExtract: readonly string[];\n extractTsContent: any;\n};\n\nexport const processVueFile = (\n filePath: string,\n _componentKey: string,\n packageName: string,\n tools: Tools,\n save: boolean = true,\n providedCode?: string\n): {\n extractedContent: Record<string, string>;\n code: string;\n map?: any;\n} | null => {\n const code = providedCode ?? readFileSync(filePath, 'utf-8');\n let extractedContent: Record<string, string> = {};\n\n const result = intlayerVueExtract(code, filePath, {\n packageName,\n dictionaryKey: _componentKey,\n shouldExtract: tools.shouldExtract,\n generateKey: tools.generateKey,\n extractDictionaryKeyFromPath: tools.extractDictionaryKeyFromPath,\n attributesToExtract: tools.attributesToExtract,\n onExtract: (extractResult) => {\n extractedContent = extractResult.content;\n },\n });\n\n if (!result) return null;\n\n if (save) {\n writeFileSync(filePath, result.code);\n }\n\n return {\n extractedContent,\n code: result.code,\n map: result.map,\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"vue-intlayer-extract.cjs","names":["babelParse","t","DEFAULT_LOCALE","vueSfc","MagicString","readFileSync"],"sources":["../../src/vue-intlayer-extract.ts"],"sourcesContent":["import { readFileSync, writeFileSync } from 'node:fs';\nimport { parseSync as babelParse, types as t, traverse } from '@babel/core';\nimport { DEFAULT_LOCALE } from '@intlayer/config/defaultValues';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport vueSfc from '@vue/compiler-sfc';\nimport MagicString from 'magic-string';\n\ntype ExistingCallInfo = {\n isDestructured: boolean;\n existingDestructuredKeys: string[];\n /** The variable name used to store the call result (e.g. `t` in `const t = useIntlayer(...)`) */\n variableName: string;\n /** Absolute position of `}` in the full file — only valid when `isDestructured` */\n closingBraceAbsolutePos: number;\n /** Absolute position of end of last property — only valid when `isDestructured` */\n lastPropAbsoluteEnd: number;\n} | null;\n\n/**\n * Detects whether the script block already contains a `useIntlayer` /\n * `getIntlayer` call and, if so, whether its result is destructured.\n *\n * @param scriptText Raw text of the script block content.\n * @param absoluteOffset Byte offset of `scriptText[0]` in the full SFC source.\n */\nconst detectExistingIntlayerCall = (\n scriptText: string,\n absoluteOffset: number\n): ExistingCallInfo => {\n let info: ExistingCallInfo = null;\n\n try {\n const ast = babelParse(scriptText, {\n configFile: false,\n babelrc: false,\n parserOpts: { sourceType: 'module', plugins: ['typescript', 'jsx'] },\n });\n\n if (!ast) return null;\n\n traverse(ast, {\n CallExpression(path: any) {\n const callee = path.node.callee;\n\n if (\n !t.isIdentifier(callee) ||\n (callee.name !== 'useIntlayer' && callee.name !== 'getIntlayer')\n )\n return;\n\n const parent = path.parent;\n\n if (t.isVariableDeclarator(parent) && t.isObjectPattern(parent.id)) {\n const properties = parent.id.properties;\n const existingDestructuredKeys = properties\n .filter(\n (property: any): property is typeof t.objectProperty =>\n t.isObjectProperty(property) && t.isIdentifier(property.key)\n )\n .map((property: any) => (property.key as any).name as string);\n const lastProp = properties[properties.length - 1];\n\n info = {\n isDestructured: true,\n variableName: 'content',\n existingDestructuredKeys,\n closingBraceAbsolutePos: absoluteOffset + (parent.id.end! - 1),\n lastPropAbsoluteEnd: absoluteOffset + lastProp.end!,\n };\n } else {\n const variableName =\n t.isVariableDeclarator(parent) && t.isIdentifier(parent.id)\n ? parent.id.name\n : 'content';\n\n info = {\n isDestructured: false,\n variableName,\n existingDestructuredKeys: [],\n closingBraceAbsolutePos: -1,\n lastPropAbsoluteEnd: -1,\n };\n }\n\n path.stop();\n },\n });\n } catch {\n // Silently ignore parse failures — fall back to no-info\n }\n\n return info;\n};\n\nexport type ExtractedContent = Record<string, string>;\n\nexport type ExtractResult = {\n dictionaryKey: string;\n filePath: string;\n content: ExtractedContent;\n locale: Locale;\n};\n\nexport type ExtractPluginOptions = {\n defaultLocale?: Locale;\n packageName?: string;\n filesList?: string[];\n shouldExtract?: (text: string) => boolean;\n onExtract?: (result: ExtractResult) => void;\n dictionaryKey?: string;\n attributesToExtract?: readonly string[];\n extractDictionaryKeyFromPath?: (path: string) => string;\n generateKey?: (text: string, existingKeys: Set<string>) => string;\n};\n\ntype Replacement = {\n start: number;\n end: number;\n replacement: string;\n key: string;\n value: string;\n};\n\nexport const shouldProcessFile = (\n filename: string | undefined,\n filesList?: string[]\n): boolean => {\n if (!filename) return false;\n if (!filesList || filesList.length === 0) return true;\n\n const normalizedFilename = filename.replace(/\\\\/g, '/');\n return filesList.some((f) => {\n const normalizedF = f.replace(/\\\\/g, '/');\n return normalizedF === normalizedFilename;\n });\n};\n\ntype VueParseResult = {\n descriptor: {\n template?: {\n ast: VueAstNode;\n loc: { start: { offset: number }; end: { offset: number } };\n };\n script?: {\n content: string;\n loc: { start: { offset: number }; end: { offset: number } };\n };\n scriptSetup?: {\n content: string;\n loc: { start: { offset: number }; end: { offset: number } };\n };\n };\n};\n\ntype VueAstNode = {\n type: number;\n content?: string;\n children?: VueAstNode[];\n props?: VueAstProp[];\n loc: { start: { offset: number }; end: { offset: number } };\n};\n\ntype VueAstProp = {\n type: number;\n name: string;\n value?: { content: string };\n loc: { start: { offset: number }; end: { offset: number } };\n};\n\nconst NODE_TYPES = {\n TEXT: 2,\n ELEMENT: 1,\n ATTRIBUTE: 6,\n INTERPOLATION: 5,\n};\n\nexport const intlayerVueExtract = (\n code: string,\n filename: string,\n options: ExtractPluginOptions = {}\n): { code: string; map?: unknown; extracted: boolean } | null => {\n const {\n defaultLocale = DEFAULT_LOCALE,\n packageName = 'vue-intlayer',\n filesList,\n shouldExtract,\n onExtract,\n dictionaryKey: dictionaryKeyOption,\n attributesToExtract = [],\n extractDictionaryKeyFromPath,\n generateKey,\n } = options;\n\n if (!shouldProcessFile(filename, filesList)) return null;\n if (!filename.endsWith('.vue')) return null;\n\n let parseVue: (code: string) => VueParseResult;\n\n try {\n parseVue = vueSfc.parse as unknown as (code: string) => VueParseResult;\n } catch {\n console.warn('Vue extraction: @vue/compiler-sfc not found.');\n return null;\n }\n\n const sfc = parseVue(code);\n const magic = new MagicString(code);\n\n const extractedContent: ExtractedContent = {};\n const existingKeys = new Set<string>();\n const dictionaryKey =\n dictionaryKeyOption ?? extractDictionaryKeyFromPath?.(filename) ?? '';\n const replacements: Replacement[] = [];\n\n // Detect existing useIntlayer / getIntlayer call in the script block BEFORE\n // walking the template, so the correct access pattern can be chosen.\n const scriptBlock = sfc.descriptor.scriptSetup ?? sfc.descriptor.script;\n const existingCallInfo = scriptBlock\n ? detectExistingIntlayerCall(\n scriptBlock.content,\n scriptBlock.loc.start.offset\n )\n : null;\n\n const isDestructured = existingCallInfo?.isDestructured ?? false;\n const varName = existingCallInfo?.variableName ?? 'content';\n\n // Walk Vue Template AST\n if (sfc.descriptor.template) {\n const walkVueAst = (node: VueAstNode) => {\n if (node.type === NODE_TYPES.TEXT) {\n const text = node.content ?? '';\n\n if (shouldExtract?.(text) && generateKey) {\n const key = generateKey(text, existingKeys);\n existingKeys.add(key);\n // When the existing call is destructured, access the key directly;\n // otherwise use the `content` variable.\n const ref = isDestructured ? key : `${varName}.${key}`;\n replacements.push({\n start: node.loc.start.offset,\n end: node.loc.end.offset,\n replacement: `{{ ${ref} }}`,\n key,\n value: text.replace(/\\s+/g, ' ').trim(),\n });\n }\n } else if (node.type === NODE_TYPES.ELEMENT) {\n const children = node.children ?? [];\n\n // Try to handle as insertion (mixed TEXT + INTERPOLATION children)\n if (\n children.length > 0 &&\n children.some((c) => c.type === NODE_TYPES.INTERPOLATION)\n ) {\n const parts: {\n type: 'text' | 'var';\n value: string;\n originalExpr: string;\n }[] = [];\n let hasSignificantText = false;\n let isValid = true;\n\n for (const child of children) {\n if (child.type === NODE_TYPES.TEXT) {\n const text = child.content ?? '';\n if (text.trim().length > 0) hasSignificantText = true;\n parts.push({ type: 'text', value: text, originalExpr: '' });\n } else if (child.type === NODE_TYPES.INTERPOLATION) {\n // Extract the expression source between {{ and }}\n const exprCode = code\n .slice(child.loc.start.offset + 2, child.loc.end.offset - 2)\n .trim();\n const varName = exprCode.includes('.')\n ? exprCode\n .split('.')\n .pop()!\n .replace(/[^\\w$]/g, '')\n : exprCode;\n parts.push({\n type: 'var',\n value: varName,\n originalExpr: exprCode,\n });\n } else {\n isValid = false;\n break;\n }\n }\n\n if (\n isValid &&\n hasSignificantText &&\n parts.some((p) => p.type === 'var')\n ) {\n let combined = '';\n for (const p of parts) {\n combined += p.type === 'var' ? `{{${p.value}}}` : p.value;\n }\n const cleanString = combined.replace(/\\s+/g, ' ').trim();\n\n if (shouldExtract?.(cleanString) && generateKey) {\n const key = generateKey(cleanString, existingKeys);\n existingKeys.add(key);\n const ref = isDestructured ? key : `${varName}.${key}`;\n\n const uniqueVarPairs = [\n ...new Set(\n parts\n .filter((p) => p.type === 'var')\n .map((p) => `${p.value}: ${p.originalExpr}`)\n ),\n ];\n const varArgs = uniqueVarPairs.join(', ');\n const replacement = `{{ ${ref}({ ${varArgs} }) }}`;\n\n const firstChild = children[0];\n const lastChild = children[children.length - 1];\n replacements.push({\n start: firstChild.loc.start.offset,\n end: lastChild.loc.end.offset,\n replacement,\n key,\n value: cleanString,\n });\n\n // Process props but skip children (they are replaced)\n node.props?.forEach((prop) => {\n if (\n prop.type === NODE_TYPES.ATTRIBUTE &&\n (attributesToExtract as readonly string[]).includes(\n prop.name\n ) &&\n prop.value\n ) {\n const text = prop.value.content;\n if (shouldExtract?.(text) && generateKey) {\n const propKey = generateKey(text, existingKeys);\n existingKeys.add(propKey);\n const propRef = isDestructured\n ? propKey\n : `${varName}.${propKey}`;\n replacements.push({\n start: prop.loc.start.offset,\n end: prop.loc.end.offset,\n replacement: `:${prop.name}=\"${propRef}\"`,\n key: propKey,\n value: text.trim(),\n });\n }\n }\n });\n return; // don't recurse into children\n }\n }\n }\n\n // Regular element: handle props\n node.props?.forEach((prop) => {\n if (\n prop.type === NODE_TYPES.ATTRIBUTE &&\n (attributesToExtract as readonly string[]).includes(prop.name) &&\n prop.value\n ) {\n const text = prop.value.content;\n\n if (shouldExtract?.(text) && generateKey) {\n const key = generateKey(text, existingKeys);\n existingKeys.add(key);\n const ref = isDestructured ? key : `${varName}.${key}`;\n replacements.push({\n start: prop.loc.start.offset,\n end: prop.loc.end.offset,\n replacement: `:${prop.name}=\"${ref}\"`,\n key,\n value: text.trim(),\n });\n }\n }\n });\n }\n\n if (node.children) {\n node.children.forEach(walkVueAst);\n }\n };\n\n walkVueAst(sfc.descriptor.template.ast);\n }\n\n // Extract and Walk Script using Babel\n if (scriptBlock) {\n const scriptText = scriptBlock.content;\n const offset = scriptBlock.loc.start.offset;\n\n try {\n const ast = babelParse(scriptText, {\n configFile: false,\n babelrc: false,\n parserOpts: {\n sourceType: 'module',\n plugins: ['typescript', 'jsx'],\n },\n });\n\n if (ast) {\n traverse(ast, {\n StringLiteral(path: any) {\n if (path.parentPath.isImportDeclaration()) return;\n\n if (path.parentPath.isExportDeclaration()) return;\n\n if (path.parentPath.isImportSpecifier()) return;\n\n if (path.parentPath.isObjectProperty() && path.key === 'key')\n return;\n\n if (path.parentPath.isCallExpression()) {\n const callee = path.parentPath.node.callee;\n\n if (\n t.isMemberExpression(callee) &&\n t.isIdentifier(callee.object) &&\n callee.object.name === 'console'\n )\n return;\n\n if (\n t.isIdentifier(callee) &&\n (callee.name === 'useIntlayer' || callee.name === 't')\n )\n return;\n\n if (callee.type === 'Import') return;\n\n if (t.isIdentifier(callee) && callee.name === 'require') return;\n }\n\n const text = path.node.value;\n\n if (shouldExtract?.(text) && generateKey) {\n const key = generateKey(text, existingKeys);\n existingKeys.add(key);\n\n if (path.node.start != null && path.node.end != null) {\n const ref = isDestructured ? key : `${varName}.${key}`;\n replacements.push({\n start: offset + path.node.start,\n end: offset + path.node.end,\n replacement: ref,\n key,\n value: text.trim(),\n });\n }\n }\n },\n });\n }\n } catch (e) {\n console.warn(\n `Vue extraction: Failed to parse script content for ${filename}`,\n e\n );\n }\n }\n\n // Abort if nothing was extracted\n if (replacements.length === 0) return null;\n\n // Apply Replacements in Reverse Order\n replacements.sort((a, b) => b.start - a.start);\n\n for (const { start, end, replacement, key, value } of replacements) {\n magic.overwrite(start, end, replacement);\n extractedContent[key] = value;\n }\n\n // When the existing call is destructured, inject only the missing keys into\n // the ObjectPattern — no new `content` variable is needed.\n if (\n existingCallInfo?.isDestructured &&\n existingCallInfo.closingBraceAbsolutePos >= 0\n ) {\n const missingKeys = Object.keys(extractedContent).filter(\n (k) => !existingCallInfo.existingDestructuredKeys.includes(k)\n );\n\n if (missingKeys.length > 0) {\n // Insert right after the last property so the space/newline before `}`\n // is naturally preserved: `{ a }` → `{ a, b }`.\n magic.appendLeft(\n existingCallInfo.lastPropAbsoluteEnd,\n `, ${missingKeys.join(', ')}`\n );\n }\n }\n\n // Inject necessary imports and setup (only when no existing call was detected)\n const finalScriptContent = scriptBlock?.content ?? '';\n\n const hasUseIntlayerImport =\n /import\\s*{[^}]*useIntlayer[^}]*}\\s*from\\s*['\"][^'\"]+['\"]/.test(\n finalScriptContent\n ) ||\n /import\\s+useIntlayer\\s+from\\s*['\"][^'\"]+['\"]/.test(finalScriptContent);\n\n // An existing call (destructured or not) means no new declaration is needed.\n const hasContentDeclaration =\n existingCallInfo !== null ||\n /const\\s+content\\s*=\\s*useIntlayer\\s*\\(/.test(finalScriptContent);\n\n const importStmt = hasUseIntlayerImport\n ? ''\n : `import { useIntlayer } from '${packageName}';`;\n const contentDecl = hasContentDeclaration\n ? ''\n : `const content = useIntlayer('${dictionaryKey}');`;\n\n const injectionParts = [importStmt, contentDecl].filter(Boolean);\n\n if (injectionParts.length > 0) {\n const injection = `\\n${injectionParts.join('\\n')}\\n`;\n\n if (sfc.descriptor.scriptSetup) {\n magic.appendLeft(sfc.descriptor.scriptSetup.loc.start.offset, injection);\n } else if (sfc.descriptor.script) {\n magic.appendLeft(sfc.descriptor.script.loc.start.offset, injection);\n } else {\n magic.prepend(\n `<script setup>\\n${importStmt}\\n${contentDecl}\\n</script>\\n`\n );\n }\n }\n\n if (onExtract) {\n onExtract({\n dictionaryKey,\n filePath: filename,\n content: { ...extractedContent },\n locale: defaultLocale,\n });\n }\n\n return {\n code: magic.toString(),\n map: magic.generateMap({ source: filename, includeContent: true }),\n extracted: true,\n };\n};\n\ntype Tools = {\n generateKey: (text: string, existingKeys: Set<string>) => string;\n shouldExtract: (text: string) => boolean;\n extractDictionaryKeyFromPath: (path: string) => string;\n attributesToExtract: readonly string[];\n extractTsContent: any;\n};\n\nexport const processVueFile = (\n filePath: string,\n _componentKey: string,\n packageName: string,\n tools: Tools,\n save: boolean = true,\n providedCode?: string\n): {\n extractedContent: Record<string, string>;\n code: string;\n map?: any;\n} | null => {\n const code = providedCode ?? readFileSync(filePath, 'utf-8');\n let extractedContent: Record<string, string> = {};\n\n const result = intlayerVueExtract(code, filePath, {\n packageName,\n dictionaryKey: _componentKey,\n shouldExtract: tools.shouldExtract,\n generateKey: tools.generateKey,\n extractDictionaryKeyFromPath: tools.extractDictionaryKeyFromPath,\n attributesToExtract: tools.attributesToExtract,\n onExtract: (extractResult) => {\n extractedContent = extractResult.content;\n },\n });\n\n if (!result) return null;\n\n if (save) {\n writeFileSync(filePath, result.code);\n }\n\n return {\n extractedContent,\n code: result.code,\n map: result.map,\n };\n};\n"],"mappings":"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"}
@@ -5,13 +5,18 @@ import vueSfc from "@vue/compiler-sfc";
5
5
  * Extracts intlayer dictionary field usage from a Vue SFC for a set of
6
6
  * plain variable bindings (i.e. `const content = useIntlayer('key')`).
7
7
  *
8
- * Two access patterns are recognised:
8
+ * Three access patterns are recognised:
9
9
  *
10
- * 1. `varName.value.fieldName` in script blocks
11
- * Vue's `useIntlayer` returns a reactive `Ref<Content>`, so fields are
12
- * accessed one level deeper via `.value`.
10
+ * 1. `varName.fieldName` in script blocks
11
+ * `useIntlayer` returns a deep reactive proxy, so content fields are read
12
+ * directly on the variable (`content.title`, `content.count(2)`), the
13
+ * `.value` unwrapping happening on the leaf node instead.
13
14
  *
14
- * 2. `varName.fieldName` in the template block
15
+ * 2. `varName.value.fieldName` in script blocks
16
+ * Legacy `Ref<Content>` accessor indirection, where fields live one level
17
+ * deeper than the variable.
18
+ *
19
+ * 3. `varName.fieldName` in the template block
15
20
  * Inside `<template>`, Vue automatically unwraps top-level refs, so
16
21
  * fields are accessed directly without `.value`.
17
22
  *
@@ -22,6 +27,21 @@ import vueSfc from "@vue/compiler-sfc";
22
27
  /** Escapes special regex characters in a string used as a regex literal. */
23
28
  const escapeRegExp = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
24
29
  /**
30
+ * Runs `fieldRegex` over `source` and adds every captured field name to
31
+ * `fields`, skipping the `value` accessor of an intlayer node (it addresses the
32
+ * node wrapper, not a content field).
33
+ *
34
+ * Over-approximating is safe here: an extra name only keeps a field that could
35
+ * have been pruned, while a missing name removes content the app reads.
36
+ */
37
+ const collectFieldNames = (source, fieldRegex, fields) => {
38
+ fieldRegex.lastIndex = 0;
39
+ for (let match = fieldRegex.exec(source); match !== null; match = fieldRegex.exec(source)) {
40
+ const field = match[1];
41
+ if (field && field !== "value") fields.add(field);
42
+ }
43
+ };
44
+ /**
25
45
  * Splits a Vue SFC source string into its script and template regions.
26
46
  *
27
47
  * Uses `@vue/compiler-sfc` to get exact character offsets so that nested
@@ -65,17 +85,9 @@ const extractVueIntlayerFieldUsage = (code, plainVariables) => {
65
85
  for (const { variableName, dictionaryKey } of plainVariables) {
66
86
  const fields = /* @__PURE__ */ new Set();
67
87
  const esc = escapeRegExp(variableName);
68
- const scriptRe = new RegExp(`\\b${esc}\\.value\\.(\\w+)`, "g");
69
- scriptRe.lastIndex = 0;
70
- for (let m = scriptRe.exec(scriptSource); m !== null; m = scriptRe.exec(scriptSource)) {
71
- const field = m[1];
72
- if (field !== "value") fields.add(field);
73
- }
74
- if (templateSource) {
75
- const templateRe = new RegExp(`\\b${esc}\\.(\\w+)`, "g");
76
- templateRe.lastIndex = 0;
77
- for (let m = templateRe.exec(templateSource); m !== null; m = templateRe.exec(templateSource)) fields.add(m[1]);
78
- }
88
+ collectFieldNames(scriptSource, new RegExp(`\\b${esc}\\.(\\w+)`, "g"), fields);
89
+ collectFieldNames(scriptSource, new RegExp(`\\b${esc}\\.value\\.(\\w+)`, "g"), fields);
90
+ if (templateSource) collectFieldNames(templateSource, new RegExp(`\\b${esc}\\.(\\w+)`, "g"), fields);
79
91
  if (fields.size > 0) result.set(dictionaryKey, fields);
80
92
  }
81
93
  return result;
@@ -1 +1 @@
1
- {"version":3,"file":"extractVueFieldUsage.mjs","names":[],"sources":["../../src/extractVueFieldUsage.ts"],"sourcesContent":["/**\n * Extracts intlayer dictionary field usage from a Vue SFC for a set of\n * plain variable bindings (i.e. `const content = useIntlayer('key')`).\n *\n * Two access patterns are recognised:\n *\n * 1. `varName.value.fieldName` in script blocks\n * Vue's `useIntlayer` returns a reactive `Ref<Content>`, so fields are\n * accessed one level deeper via `.value`.\n *\n * 2. `varName.fieldName` in the template block\n * Inside `<template>`, Vue automatically unwraps top-level refs, so\n * fields are accessed directly without `.value`.\n *\n * The template block is extracted via `@vue/compiler-sfc` so that nested\n * `<template v-for>` / `<template v-if>` tags do not confuse the parser.\n * Falls back to a greedy regex when the package is unavailable.\n */\n\nimport vueSfc from '@vue/compiler-sfc';\n\n/** Escapes special regex characters in a string used as a regex literal. */\nconst escapeRegExp = (str: string): string =>\n str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n/** Input descriptor for a single plain variable binding. */\nexport type PlainVariableInfo = {\n /** The local variable name in the script (`content` in `const content = useIntlayer(…)`). */\n variableName: string;\n /** The intlayer dictionary key passed to `useIntlayer`. */\n dictionaryKey: string;\n};\n\n/**\n * Splits a Vue SFC source string into its script and template regions.\n *\n * Uses `@vue/compiler-sfc` to get exact character offsets so that nested\n * `<template>` tags (used by Vue directives like `v-for` / `v-if`) never\n * cause the template region to be mis-identified.\n */\nconst splitVueSfc = (\n code: string\n): { scriptSource: string; templateSource: string } => {\n try {\n const { descriptor } = (\n vueSfc.parse as (src: string) => { descriptor: any }\n )(code);\n\n const templateSource: string = descriptor.template?.content ?? '';\n\n const scriptParts: string[] = [];\n if (descriptor.script?.content) scriptParts.push(descriptor.script.content);\n if (descriptor.scriptSetup?.content)\n scriptParts.push(descriptor.scriptSetup.content);\n const scriptSource = scriptParts.join('\\n');\n\n return { scriptSource, templateSource };\n } catch {\n // @vue/compiler-sfc not available or parse failed.\n // Fall back: use a greedy regex to capture everything between the\n // outermost <template> tags (robust against nested tags unlike the\n // non-greedy variant).\n const templateMatch = /<template(?:[^>]*)>([\\s\\S]*)<\\/template>/i.exec(\n code\n );\n const templateSource = templateMatch ? templateMatch[1] : '';\n const scriptSource = code.replace(/<template[\\s\\S]*<\\/template>/gi, '');\n return { scriptSource, templateSource };\n }\n};\n\n/**\n * Analyzes a Vue SFC source string and returns the top-level content field\n * names that are statically accessed for each plain intlayer variable binding.\n *\n * @param code - Full `.vue` file source.\n * @param plainVariables - List of plain variable bindings to analyse.\n * @returns Map from dictionary key to the set of accessed top-level field names.\n * If no fields can be determined for a given key it is omitted from the\n * map so the caller can fall back to `'all'`.\n */\nexport const extractVueIntlayerFieldUsage = (\n code: string,\n plainVariables: PlainVariableInfo[]\n): Map<string, Set<string>> => {\n const result = new Map<string, Set<string>>();\n\n if (plainVariables.length === 0) return result;\n\n const { scriptSource, templateSource } = splitVueSfc(code);\n\n for (const { variableName, dictionaryKey } of plainVariables) {\n const fields = new Set<string>();\n const esc = escapeRegExp(variableName);\n\n // ── 1. Script pattern: varName.value.fieldName ─────────────────────────\n // Vue's reactive ref accessor; the actual content fields live one level\n // deeper than the variable itself.\n const scriptRe = new RegExp(`\\\\b${esc}\\\\.value\\\\.(\\\\w+)`, 'g');\n scriptRe.lastIndex = 0;\n for (\n let m = scriptRe.exec(scriptSource);\n m !== null;\n m = scriptRe.exec(scriptSource)\n ) {\n const field = m[1];\n // Skip chained `.value.value` artefacts\n if (field !== 'value') fields.add(field);\n }\n\n // ── 2. Template pattern: varName.fieldName ─────────────────────────────\n // Inside `<template>` Vue auto-unwraps refs, so content is accessed\n // directly without `.value`.\n if (templateSource) {\n const templateRe = new RegExp(`\\\\b${esc}\\\\.(\\\\w+)`, 'g');\n\n templateRe.lastIndex = 0;\n for (\n let m = templateRe.exec(templateSource);\n m !== null;\n m = templateRe.exec(templateSource)\n ) {\n fields.add(m[1]);\n }\n }\n\n if (fields.size > 0) {\n result.set(dictionaryKey, fields);\n }\n }\n\n return result;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAsBA,MAAM,gBAAgB,QACpB,IAAI,QAAQ,uBAAuB,MAAM;;;;;;;;AAiB3C,MAAM,eACJ,SACqD;CACrD,IAAI;EACF,MAAM,EAAE,eACN,OAAO,MACP,IAAI;EAEN,MAAM,iBAAyB,WAAW,UAAU,WAAW;EAE/D,MAAM,cAAwB,CAAC;EAC/B,IAAI,WAAW,QAAQ,SAAS,YAAY,KAAK,WAAW,OAAO,OAAO;EAC1E,IAAI,WAAW,aAAa,SAC1B,YAAY,KAAK,WAAW,YAAY,OAAO;EAGjD,OAAO;GAAE,cAFY,YAAY,KAAK,IAElB;GAAG;EAAe;CACxC,QAAQ;EAKN,MAAM,gBAAgB,4CAA4C,KAChE,IACF;EACA,MAAM,iBAAiB,gBAAgB,cAAc,KAAK;EAE1D,OAAO;GAAE,cADY,KAAK,QAAQ,kCAAkC,EAChD;GAAG;EAAe;CACxC;AACF;;;;;;;;;;;AAYA,MAAa,gCACX,MACA,mBAC6B;CAC7B,MAAM,yBAAS,IAAI,IAAyB;CAE5C,IAAI,eAAe,WAAW,GAAG,OAAO;CAExC,MAAM,EAAE,cAAc,mBAAmB,YAAY,IAAI;CAEzD,KAAK,MAAM,EAAE,cAAc,mBAAmB,gBAAgB;EAC5D,MAAM,yBAAS,IAAI,IAAY;EAC/B,MAAM,MAAM,aAAa,YAAY;EAKrC,MAAM,WAAW,IAAI,OAAO,MAAM,IAAI,oBAAoB,GAAG;EAC7D,SAAS,YAAY;EACrB,KACE,IAAI,IAAI,SAAS,KAAK,YAAY,GAClC,MAAM,MACN,IAAI,SAAS,KAAK,YAAY,GAC9B;GACA,MAAM,QAAQ,EAAE;GAEhB,IAAI,UAAU,SAAS,OAAO,IAAI,KAAK;EACzC;EAKA,IAAI,gBAAgB;GAClB,MAAM,aAAa,IAAI,OAAO,MAAM,IAAI,YAAY,GAAG;GAEvD,WAAW,YAAY;GACvB,KACE,IAAI,IAAI,WAAW,KAAK,cAAc,GACtC,MAAM,MACN,IAAI,WAAW,KAAK,cAAc,GAElC,OAAO,IAAI,EAAE,EAAE;EAEnB;EAEA,IAAI,OAAO,OAAO,GAChB,OAAO,IAAI,eAAe,MAAM;CAEpC;CAEA,OAAO;AACT"}
1
+ {"version":3,"file":"extractVueFieldUsage.mjs","names":[],"sources":["../../src/extractVueFieldUsage.ts"],"sourcesContent":["/**\n * Extracts intlayer dictionary field usage from a Vue SFC for a set of\n * plain variable bindings (i.e. `const content = useIntlayer('key')`).\n *\n * Three access patterns are recognised:\n *\n * 1. `varName.fieldName` in script blocks\n * `useIntlayer` returns a deep reactive proxy, so content fields are read\n * directly on the variable (`content.title`, `content.count(2)`), the\n * `.value` unwrapping happening on the leaf node instead.\n *\n * 2. `varName.value.fieldName` in script blocks\n * Legacy `Ref<Content>` accessor indirection, where fields live one level\n * deeper than the variable.\n *\n * 3. `varName.fieldName` in the template block\n * Inside `<template>`, Vue automatically unwraps top-level refs, so\n * fields are accessed directly without `.value`.\n *\n * The template block is extracted via `@vue/compiler-sfc` so that nested\n * `<template v-for>` / `<template v-if>` tags do not confuse the parser.\n * Falls back to a greedy regex when the package is unavailable.\n */\n\nimport vueSfc from '@vue/compiler-sfc';\n\n/** Escapes special regex characters in a string used as a regex literal. */\nconst escapeRegExp = (str: string): string =>\n str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n/**\n * Runs `fieldRegex` over `source` and adds every captured field name to\n * `fields`, skipping the `value` accessor of an intlayer node (it addresses the\n * node wrapper, not a content field).\n *\n * Over-approximating is safe here: an extra name only keeps a field that could\n * have been pruned, while a missing name removes content the app reads.\n */\nconst collectFieldNames = (\n source: string,\n fieldRegex: RegExp,\n fields: Set<string>\n): void => {\n fieldRegex.lastIndex = 0;\n\n for (\n let match = fieldRegex.exec(source);\n match !== null;\n match = fieldRegex.exec(source)\n ) {\n const field = match[1];\n if (field && field !== 'value') fields.add(field);\n }\n};\n\n/** Input descriptor for a single plain variable binding. */\nexport type PlainVariableInfo = {\n /** The local variable name in the script (`content` in `const content = useIntlayer(…)`). */\n variableName: string;\n /** The intlayer dictionary key passed to `useIntlayer`. */\n dictionaryKey: string;\n};\n\n/**\n * Splits a Vue SFC source string into its script and template regions.\n *\n * Uses `@vue/compiler-sfc` to get exact character offsets so that nested\n * `<template>` tags (used by Vue directives like `v-for` / `v-if`) never\n * cause the template region to be mis-identified.\n */\nconst splitVueSfc = (\n code: string\n): { scriptSource: string; templateSource: string } => {\n try {\n const { descriptor } = (\n vueSfc.parse as (src: string) => { descriptor: any }\n )(code);\n\n const templateSource: string = descriptor.template?.content ?? '';\n\n const scriptParts: string[] = [];\n if (descriptor.script?.content) scriptParts.push(descriptor.script.content);\n if (descriptor.scriptSetup?.content)\n scriptParts.push(descriptor.scriptSetup.content);\n const scriptSource = scriptParts.join('\\n');\n\n return { scriptSource, templateSource };\n } catch {\n // @vue/compiler-sfc not available or parse failed.\n // Fall back: use a greedy regex to capture everything between the\n // outermost <template> tags (robust against nested tags unlike the\n // non-greedy variant).\n const templateMatch = /<template(?:[^>]*)>([\\s\\S]*)<\\/template>/i.exec(\n code\n );\n const templateSource = templateMatch ? templateMatch[1] : '';\n const scriptSource = code.replace(/<template[\\s\\S]*<\\/template>/gi, '');\n return { scriptSource, templateSource };\n }\n};\n\n/**\n * Analyzes a Vue SFC source string and returns the top-level content field\n * names that are statically accessed for each plain intlayer variable binding.\n *\n * @param code - Full `.vue` file source.\n * @param plainVariables - List of plain variable bindings to analyse.\n * @returns Map from dictionary key to the set of accessed top-level field names.\n * If no fields can be determined for a given key it is omitted from the\n * map so the caller can fall back to `'all'`.\n */\nexport const extractVueIntlayerFieldUsage = (\n code: string,\n plainVariables: PlainVariableInfo[]\n): Map<string, Set<string>> => {\n const result = new Map<string, Set<string>>();\n\n if (plainVariables.length === 0) return result;\n\n const { scriptSource, templateSource } = splitVueSfc(code);\n\n for (const { variableName, dictionaryKey } of plainVariables) {\n const fields = new Set<string>();\n const esc = escapeRegExp(variableName);\n\n // ── 1. Script pattern: varName.fieldName ───────────────────────────────\n // The proxy returned by `useIntlayer` exposes content fields directly, so\n // scripts read them without any indirection. `.value` is the leaf-node\n // accessor, never a content field — the deeper name is picked up by the\n // `.value.` pattern below.\n collectFieldNames(\n scriptSource,\n new RegExp(`\\\\b${esc}\\\\.(\\\\w+)`, 'g'),\n fields\n );\n\n // ── 2. Script pattern: varName.value.fieldName ─────────────────────────\n // Legacy reactive ref accessor; the actual content fields live one level\n // deeper than the variable itself.\n collectFieldNames(\n scriptSource,\n new RegExp(`\\\\b${esc}\\\\.value\\\\.(\\\\w+)`, 'g'),\n fields\n );\n\n // ── 3. Template pattern: varName.fieldName ─────────────────────────────\n // Inside `<template>` Vue auto-unwraps refs, so content is accessed\n // directly without `.value`.\n if (templateSource) {\n collectFieldNames(\n templateSource,\n new RegExp(`\\\\b${esc}\\\\.(\\\\w+)`, 'g'),\n fields\n );\n }\n\n if (fields.size > 0) {\n result.set(dictionaryKey, fields);\n }\n }\n\n return result;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,MAAM,gBAAgB,QACpB,IAAI,QAAQ,uBAAuB,MAAM;;;;;;;;;AAU3C,MAAM,qBACJ,QACA,YACA,WACS;CACT,WAAW,YAAY;CAEvB,KACE,IAAI,QAAQ,WAAW,KAAK,MAAM,GAClC,UAAU,MACV,QAAQ,WAAW,KAAK,MAAM,GAC9B;EACA,MAAM,QAAQ,MAAM;EACpB,IAAI,SAAS,UAAU,SAAS,OAAO,IAAI,KAAK;CAClD;AACF;;;;;;;;AAiBA,MAAM,eACJ,SACqD;CACrD,IAAI;EACF,MAAM,EAAE,eACN,OAAO,MACP,IAAI;EAEN,MAAM,iBAAyB,WAAW,UAAU,WAAW;EAE/D,MAAM,cAAwB,CAAC;EAC/B,IAAI,WAAW,QAAQ,SAAS,YAAY,KAAK,WAAW,OAAO,OAAO;EAC1E,IAAI,WAAW,aAAa,SAC1B,YAAY,KAAK,WAAW,YAAY,OAAO;EAGjD,OAAO;GAAE,cAFY,YAAY,KAAK,IAElB;GAAG;EAAe;CACxC,QAAQ;EAKN,MAAM,gBAAgB,4CAA4C,KAChE,IACF;EACA,MAAM,iBAAiB,gBAAgB,cAAc,KAAK;EAE1D,OAAO;GAAE,cADY,KAAK,QAAQ,kCAAkC,EAChD;GAAG;EAAe;CACxC;AACF;;;;;;;;;;;AAYA,MAAa,gCACX,MACA,mBAC6B;CAC7B,MAAM,yBAAS,IAAI,IAAyB;CAE5C,IAAI,eAAe,WAAW,GAAG,OAAO;CAExC,MAAM,EAAE,cAAc,mBAAmB,YAAY,IAAI;CAEzD,KAAK,MAAM,EAAE,cAAc,mBAAmB,gBAAgB;EAC5D,MAAM,yBAAS,IAAI,IAAY;EAC/B,MAAM,MAAM,aAAa,YAAY;EAOrC,kBACE,cACA,IAAI,OAAO,MAAM,IAAI,YAAY,GAAG,GACpC,MACF;EAKA,kBACE,cACA,IAAI,OAAO,MAAM,IAAI,oBAAoB,GAAG,GAC5C,MACF;EAKA,IAAI,gBACF,kBACE,gBACA,IAAI,OAAO,MAAM,IAAI,YAAY,GAAG,GACpC,MACF;EAGF,IAAI,OAAO,OAAO,GAChB,OAAO,IAAI,eAAe,MAAM;CAEpC;CAEA,OAAO;AACT"}
@@ -1,5 +1,5 @@
1
1
  import { readFileSync, writeFileSync } from "node:fs";
2
- import { parse, traverse, types } from "@babel/core";
2
+ import { parseSync, traverse, types } from "@babel/core";
3
3
  import { DEFAULT_LOCALE } from "@intlayer/config/defaultValues";
4
4
  import vueSfc from "@vue/compiler-sfc";
5
5
  import MagicString from "magic-string";
@@ -15,10 +15,14 @@ import MagicString from "magic-string";
15
15
  const detectExistingIntlayerCall = (scriptText, absoluteOffset) => {
16
16
  let info = null;
17
17
  try {
18
- const ast = parse(scriptText, { parserOpts: {
19
- sourceType: "module",
20
- plugins: ["typescript", "jsx"]
21
- } });
18
+ const ast = parseSync(scriptText, {
19
+ configFile: false,
20
+ babelrc: false,
21
+ parserOpts: {
22
+ sourceType: "module",
23
+ plugins: ["typescript", "jsx"]
24
+ }
25
+ });
22
26
  if (!ast) return null;
23
27
  traverse(ast, { CallExpression(path) {
24
28
  const callee = path.node.callee;
@@ -188,10 +192,14 @@ const intlayerVueExtract = (code, filename, options = {}) => {
188
192
  const scriptText = scriptBlock.content;
189
193
  const offset = scriptBlock.loc.start.offset;
190
194
  try {
191
- const ast = parse(scriptText, { parserOpts: {
192
- sourceType: "module",
193
- plugins: ["typescript", "jsx"]
194
- } });
195
+ const ast = parseSync(scriptText, {
196
+ configFile: false,
197
+ babelrc: false,
198
+ parserOpts: {
199
+ sourceType: "module",
200
+ plugins: ["typescript", "jsx"]
201
+ }
202
+ });
195
203
  if (ast) traverse(ast, { StringLiteral(path) {
196
204
  if (path.parentPath.isImportDeclaration()) return;
197
205
  if (path.parentPath.isExportDeclaration()) return;
@@ -1 +1 @@
1
- {"version":3,"file":"vue-intlayer-extract.mjs","names":["babelParse","t"],"sources":["../../src/vue-intlayer-extract.ts"],"sourcesContent":["import { readFileSync, writeFileSync } from 'node:fs';\nimport { parse as babelParse, types as t, traverse } from '@babel/core';\nimport { DEFAULT_LOCALE } from '@intlayer/config/defaultValues';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport vueSfc from '@vue/compiler-sfc';\nimport MagicString from 'magic-string';\n\ntype ExistingCallInfo = {\n isDestructured: boolean;\n existingDestructuredKeys: string[];\n /** The variable name used to store the call result (e.g. `t` in `const t = useIntlayer(...)`) */\n variableName: string;\n /** Absolute position of `}` in the full file — only valid when `isDestructured` */\n closingBraceAbsolutePos: number;\n /** Absolute position of end of last property — only valid when `isDestructured` */\n lastPropAbsoluteEnd: number;\n} | null;\n\n/**\n * Detects whether the script block already contains a `useIntlayer` /\n * `getIntlayer` call and, if so, whether its result is destructured.\n *\n * @param scriptText Raw text of the script block content.\n * @param absoluteOffset Byte offset of `scriptText[0]` in the full SFC source.\n */\nconst detectExistingIntlayerCall = (\n scriptText: string,\n absoluteOffset: number\n): ExistingCallInfo => {\n let info: ExistingCallInfo = null;\n\n try {\n const ast = babelParse(scriptText, {\n parserOpts: { sourceType: 'module', plugins: ['typescript', 'jsx'] },\n });\n\n if (!ast) return null;\n\n traverse(ast, {\n CallExpression(path: any) {\n const callee = path.node.callee;\n\n if (\n !t.isIdentifier(callee) ||\n (callee.name !== 'useIntlayer' && callee.name !== 'getIntlayer')\n )\n return;\n\n const parent = path.parent;\n\n if (t.isVariableDeclarator(parent) && t.isObjectPattern(parent.id)) {\n const properties = parent.id.properties;\n const existingDestructuredKeys = properties\n .filter(\n (property: any): property is typeof t.objectProperty =>\n t.isObjectProperty(property) && t.isIdentifier(property.key)\n )\n .map((property: any) => (property.key as any).name as string);\n const lastProp = properties[properties.length - 1];\n\n info = {\n isDestructured: true,\n variableName: 'content',\n existingDestructuredKeys,\n closingBraceAbsolutePos: absoluteOffset + (parent.id.end! - 1),\n lastPropAbsoluteEnd: absoluteOffset + lastProp.end!,\n };\n } else {\n const variableName =\n t.isVariableDeclarator(parent) && t.isIdentifier(parent.id)\n ? parent.id.name\n : 'content';\n\n info = {\n isDestructured: false,\n variableName,\n existingDestructuredKeys: [],\n closingBraceAbsolutePos: -1,\n lastPropAbsoluteEnd: -1,\n };\n }\n\n path.stop();\n },\n });\n } catch {\n // Silently ignore parse failures — fall back to no-info\n }\n\n return info;\n};\n\nexport type ExtractedContent = Record<string, string>;\n\nexport type ExtractResult = {\n dictionaryKey: string;\n filePath: string;\n content: ExtractedContent;\n locale: Locale;\n};\n\nexport type ExtractPluginOptions = {\n defaultLocale?: Locale;\n packageName?: string;\n filesList?: string[];\n shouldExtract?: (text: string) => boolean;\n onExtract?: (result: ExtractResult) => void;\n dictionaryKey?: string;\n attributesToExtract?: readonly string[];\n extractDictionaryKeyFromPath?: (path: string) => string;\n generateKey?: (text: string, existingKeys: Set<string>) => string;\n};\n\ntype Replacement = {\n start: number;\n end: number;\n replacement: string;\n key: string;\n value: string;\n};\n\nexport const shouldProcessFile = (\n filename: string | undefined,\n filesList?: string[]\n): boolean => {\n if (!filename) return false;\n if (!filesList || filesList.length === 0) return true;\n\n const normalizedFilename = filename.replace(/\\\\/g, '/');\n return filesList.some((f) => {\n const normalizedF = f.replace(/\\\\/g, '/');\n return normalizedF === normalizedFilename;\n });\n};\n\ntype VueParseResult = {\n descriptor: {\n template?: {\n ast: VueAstNode;\n loc: { start: { offset: number }; end: { offset: number } };\n };\n script?: {\n content: string;\n loc: { start: { offset: number }; end: { offset: number } };\n };\n scriptSetup?: {\n content: string;\n loc: { start: { offset: number }; end: { offset: number } };\n };\n };\n};\n\ntype VueAstNode = {\n type: number;\n content?: string;\n children?: VueAstNode[];\n props?: VueAstProp[];\n loc: { start: { offset: number }; end: { offset: number } };\n};\n\ntype VueAstProp = {\n type: number;\n name: string;\n value?: { content: string };\n loc: { start: { offset: number }; end: { offset: number } };\n};\n\nconst NODE_TYPES = {\n TEXT: 2,\n ELEMENT: 1,\n ATTRIBUTE: 6,\n INTERPOLATION: 5,\n};\n\nexport const intlayerVueExtract = (\n code: string,\n filename: string,\n options: ExtractPluginOptions = {}\n): { code: string; map?: unknown; extracted: boolean } | null => {\n const {\n defaultLocale = DEFAULT_LOCALE,\n packageName = 'vue-intlayer',\n filesList,\n shouldExtract,\n onExtract,\n dictionaryKey: dictionaryKeyOption,\n attributesToExtract = [],\n extractDictionaryKeyFromPath,\n generateKey,\n } = options;\n\n if (!shouldProcessFile(filename, filesList)) return null;\n if (!filename.endsWith('.vue')) return null;\n\n let parseVue: (code: string) => VueParseResult;\n\n try {\n parseVue = vueSfc.parse as unknown as (code: string) => VueParseResult;\n } catch {\n console.warn('Vue extraction: @vue/compiler-sfc not found.');\n return null;\n }\n\n const sfc = parseVue(code);\n const magic = new MagicString(code);\n\n const extractedContent: ExtractedContent = {};\n const existingKeys = new Set<string>();\n const dictionaryKey =\n dictionaryKeyOption ?? extractDictionaryKeyFromPath?.(filename) ?? '';\n const replacements: Replacement[] = [];\n\n // Detect existing useIntlayer / getIntlayer call in the script block BEFORE\n // walking the template, so the correct access pattern can be chosen.\n const scriptBlock = sfc.descriptor.scriptSetup ?? sfc.descriptor.script;\n const existingCallInfo = scriptBlock\n ? detectExistingIntlayerCall(\n scriptBlock.content,\n scriptBlock.loc.start.offset\n )\n : null;\n\n const isDestructured = existingCallInfo?.isDestructured ?? false;\n const varName = existingCallInfo?.variableName ?? 'content';\n\n // Walk Vue Template AST\n if (sfc.descriptor.template) {\n const walkVueAst = (node: VueAstNode) => {\n if (node.type === NODE_TYPES.TEXT) {\n const text = node.content ?? '';\n\n if (shouldExtract?.(text) && generateKey) {\n const key = generateKey(text, existingKeys);\n existingKeys.add(key);\n // When the existing call is destructured, access the key directly;\n // otherwise use the `content` variable.\n const ref = isDestructured ? key : `${varName}.${key}`;\n replacements.push({\n start: node.loc.start.offset,\n end: node.loc.end.offset,\n replacement: `{{ ${ref} }}`,\n key,\n value: text.replace(/\\s+/g, ' ').trim(),\n });\n }\n } else if (node.type === NODE_TYPES.ELEMENT) {\n const children = node.children ?? [];\n\n // Try to handle as insertion (mixed TEXT + INTERPOLATION children)\n if (\n children.length > 0 &&\n children.some((c) => c.type === NODE_TYPES.INTERPOLATION)\n ) {\n const parts: {\n type: 'text' | 'var';\n value: string;\n originalExpr: string;\n }[] = [];\n let hasSignificantText = false;\n let isValid = true;\n\n for (const child of children) {\n if (child.type === NODE_TYPES.TEXT) {\n const text = child.content ?? '';\n if (text.trim().length > 0) hasSignificantText = true;\n parts.push({ type: 'text', value: text, originalExpr: '' });\n } else if (child.type === NODE_TYPES.INTERPOLATION) {\n // Extract the expression source between {{ and }}\n const exprCode = code\n .slice(child.loc.start.offset + 2, child.loc.end.offset - 2)\n .trim();\n const varName = exprCode.includes('.')\n ? exprCode\n .split('.')\n .pop()!\n .replace(/[^\\w$]/g, '')\n : exprCode;\n parts.push({\n type: 'var',\n value: varName,\n originalExpr: exprCode,\n });\n } else {\n isValid = false;\n break;\n }\n }\n\n if (\n isValid &&\n hasSignificantText &&\n parts.some((p) => p.type === 'var')\n ) {\n let combined = '';\n for (const p of parts) {\n combined += p.type === 'var' ? `{{${p.value}}}` : p.value;\n }\n const cleanString = combined.replace(/\\s+/g, ' ').trim();\n\n if (shouldExtract?.(cleanString) && generateKey) {\n const key = generateKey(cleanString, existingKeys);\n existingKeys.add(key);\n const ref = isDestructured ? key : `${varName}.${key}`;\n\n const uniqueVarPairs = [\n ...new Set(\n parts\n .filter((p) => p.type === 'var')\n .map((p) => `${p.value}: ${p.originalExpr}`)\n ),\n ];\n const varArgs = uniqueVarPairs.join(', ');\n const replacement = `{{ ${ref}({ ${varArgs} }) }}`;\n\n const firstChild = children[0];\n const lastChild = children[children.length - 1];\n replacements.push({\n start: firstChild.loc.start.offset,\n end: lastChild.loc.end.offset,\n replacement,\n key,\n value: cleanString,\n });\n\n // Process props but skip children (they are replaced)\n node.props?.forEach((prop) => {\n if (\n prop.type === NODE_TYPES.ATTRIBUTE &&\n (attributesToExtract as readonly string[]).includes(\n prop.name\n ) &&\n prop.value\n ) {\n const text = prop.value.content;\n if (shouldExtract?.(text) && generateKey) {\n const propKey = generateKey(text, existingKeys);\n existingKeys.add(propKey);\n const propRef = isDestructured\n ? propKey\n : `${varName}.${propKey}`;\n replacements.push({\n start: prop.loc.start.offset,\n end: prop.loc.end.offset,\n replacement: `:${prop.name}=\"${propRef}\"`,\n key: propKey,\n value: text.trim(),\n });\n }\n }\n });\n return; // don't recurse into children\n }\n }\n }\n\n // Regular element: handle props\n node.props?.forEach((prop) => {\n if (\n prop.type === NODE_TYPES.ATTRIBUTE &&\n (attributesToExtract as readonly string[]).includes(prop.name) &&\n prop.value\n ) {\n const text = prop.value.content;\n\n if (shouldExtract?.(text) && generateKey) {\n const key = generateKey(text, existingKeys);\n existingKeys.add(key);\n const ref = isDestructured ? key : `${varName}.${key}`;\n replacements.push({\n start: prop.loc.start.offset,\n end: prop.loc.end.offset,\n replacement: `:${prop.name}=\"${ref}\"`,\n key,\n value: text.trim(),\n });\n }\n }\n });\n }\n\n if (node.children) {\n node.children.forEach(walkVueAst);\n }\n };\n\n walkVueAst(sfc.descriptor.template.ast);\n }\n\n // Extract and Walk Script using Babel\n if (scriptBlock) {\n const scriptText = scriptBlock.content;\n const offset = scriptBlock.loc.start.offset;\n\n try {\n const ast = babelParse(scriptText, {\n parserOpts: {\n sourceType: 'module',\n plugins: ['typescript', 'jsx'],\n },\n });\n\n if (ast) {\n traverse(ast, {\n StringLiteral(path: any) {\n if (path.parentPath.isImportDeclaration()) return;\n\n if (path.parentPath.isExportDeclaration()) return;\n\n if (path.parentPath.isImportSpecifier()) return;\n\n if (path.parentPath.isObjectProperty() && path.key === 'key')\n return;\n\n if (path.parentPath.isCallExpression()) {\n const callee = path.parentPath.node.callee;\n\n if (\n t.isMemberExpression(callee) &&\n t.isIdentifier(callee.object) &&\n callee.object.name === 'console'\n )\n return;\n\n if (\n t.isIdentifier(callee) &&\n (callee.name === 'useIntlayer' || callee.name === 't')\n )\n return;\n\n if (callee.type === 'Import') return;\n\n if (t.isIdentifier(callee) && callee.name === 'require') return;\n }\n\n const text = path.node.value;\n\n if (shouldExtract?.(text) && generateKey) {\n const key = generateKey(text, existingKeys);\n existingKeys.add(key);\n\n if (path.node.start != null && path.node.end != null) {\n const ref = isDestructured ? key : `${varName}.${key}`;\n replacements.push({\n start: offset + path.node.start,\n end: offset + path.node.end,\n replacement: ref,\n key,\n value: text.trim(),\n });\n }\n }\n },\n });\n }\n } catch (e) {\n console.warn(\n `Vue extraction: Failed to parse script content for ${filename}`,\n e\n );\n }\n }\n\n // Abort if nothing was extracted\n if (replacements.length === 0) return null;\n\n // Apply Replacements in Reverse Order\n replacements.sort((a, b) => b.start - a.start);\n\n for (const { start, end, replacement, key, value } of replacements) {\n magic.overwrite(start, end, replacement);\n extractedContent[key] = value;\n }\n\n // When the existing call is destructured, inject only the missing keys into\n // the ObjectPattern — no new `content` variable is needed.\n if (\n existingCallInfo?.isDestructured &&\n existingCallInfo.closingBraceAbsolutePos >= 0\n ) {\n const missingKeys = Object.keys(extractedContent).filter(\n (k) => !existingCallInfo.existingDestructuredKeys.includes(k)\n );\n\n if (missingKeys.length > 0) {\n // Insert right after the last property so the space/newline before `}`\n // is naturally preserved: `{ a }` → `{ a, b }`.\n magic.appendLeft(\n existingCallInfo.lastPropAbsoluteEnd,\n `, ${missingKeys.join(', ')}`\n );\n }\n }\n\n // Inject necessary imports and setup (only when no existing call was detected)\n const finalScriptContent = scriptBlock?.content ?? '';\n\n const hasUseIntlayerImport =\n /import\\s*{[^}]*useIntlayer[^}]*}\\s*from\\s*['\"][^'\"]+['\"]/.test(\n finalScriptContent\n ) ||\n /import\\s+useIntlayer\\s+from\\s*['\"][^'\"]+['\"]/.test(finalScriptContent);\n\n // An existing call (destructured or not) means no new declaration is needed.\n const hasContentDeclaration =\n existingCallInfo !== null ||\n /const\\s+content\\s*=\\s*useIntlayer\\s*\\(/.test(finalScriptContent);\n\n const importStmt = hasUseIntlayerImport\n ? ''\n : `import { useIntlayer } from '${packageName}';`;\n const contentDecl = hasContentDeclaration\n ? ''\n : `const content = useIntlayer('${dictionaryKey}');`;\n\n const injectionParts = [importStmt, contentDecl].filter(Boolean);\n\n if (injectionParts.length > 0) {\n const injection = `\\n${injectionParts.join('\\n')}\\n`;\n\n if (sfc.descriptor.scriptSetup) {\n magic.appendLeft(sfc.descriptor.scriptSetup.loc.start.offset, injection);\n } else if (sfc.descriptor.script) {\n magic.appendLeft(sfc.descriptor.script.loc.start.offset, injection);\n } else {\n magic.prepend(\n `<script setup>\\n${importStmt}\\n${contentDecl}\\n</script>\\n`\n );\n }\n }\n\n if (onExtract) {\n onExtract({\n dictionaryKey,\n filePath: filename,\n content: { ...extractedContent },\n locale: defaultLocale,\n });\n }\n\n return {\n code: magic.toString(),\n map: magic.generateMap({ source: filename, includeContent: true }),\n extracted: true,\n };\n};\n\ntype Tools = {\n generateKey: (text: string, existingKeys: Set<string>) => string;\n shouldExtract: (text: string) => boolean;\n extractDictionaryKeyFromPath: (path: string) => string;\n attributesToExtract: readonly string[];\n extractTsContent: any;\n};\n\nexport const processVueFile = (\n filePath: string,\n _componentKey: string,\n packageName: string,\n tools: Tools,\n save: boolean = true,\n providedCode?: string\n): {\n extractedContent: Record<string, string>;\n code: string;\n map?: any;\n} | null => {\n const code = providedCode ?? readFileSync(filePath, 'utf-8');\n let extractedContent: Record<string, string> = {};\n\n const result = intlayerVueExtract(code, filePath, {\n packageName,\n dictionaryKey: _componentKey,\n shouldExtract: tools.shouldExtract,\n generateKey: tools.generateKey,\n extractDictionaryKeyFromPath: tools.extractDictionaryKeyFromPath,\n attributesToExtract: tools.attributesToExtract,\n onExtract: (extractResult) => {\n extractedContent = extractResult.content;\n },\n });\n\n if (!result) return null;\n\n if (save) {\n writeFileSync(filePath, result.code);\n }\n\n return {\n extractedContent,\n code: result.code,\n map: result.map,\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"vue-intlayer-extract.mjs","names":["babelParse","t"],"sources":["../../src/vue-intlayer-extract.ts"],"sourcesContent":["import { readFileSync, writeFileSync } from 'node:fs';\nimport { parseSync as babelParse, types as t, traverse } from '@babel/core';\nimport { DEFAULT_LOCALE } from '@intlayer/config/defaultValues';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport vueSfc from '@vue/compiler-sfc';\nimport MagicString from 'magic-string';\n\ntype ExistingCallInfo = {\n isDestructured: boolean;\n existingDestructuredKeys: string[];\n /** The variable name used to store the call result (e.g. `t` in `const t = useIntlayer(...)`) */\n variableName: string;\n /** Absolute position of `}` in the full file — only valid when `isDestructured` */\n closingBraceAbsolutePos: number;\n /** Absolute position of end of last property — only valid when `isDestructured` */\n lastPropAbsoluteEnd: number;\n} | null;\n\n/**\n * Detects whether the script block already contains a `useIntlayer` /\n * `getIntlayer` call and, if so, whether its result is destructured.\n *\n * @param scriptText Raw text of the script block content.\n * @param absoluteOffset Byte offset of `scriptText[0]` in the full SFC source.\n */\nconst detectExistingIntlayerCall = (\n scriptText: string,\n absoluteOffset: number\n): ExistingCallInfo => {\n let info: ExistingCallInfo = null;\n\n try {\n const ast = babelParse(scriptText, {\n configFile: false,\n babelrc: false,\n parserOpts: { sourceType: 'module', plugins: ['typescript', 'jsx'] },\n });\n\n if (!ast) return null;\n\n traverse(ast, {\n CallExpression(path: any) {\n const callee = path.node.callee;\n\n if (\n !t.isIdentifier(callee) ||\n (callee.name !== 'useIntlayer' && callee.name !== 'getIntlayer')\n )\n return;\n\n const parent = path.parent;\n\n if (t.isVariableDeclarator(parent) && t.isObjectPattern(parent.id)) {\n const properties = parent.id.properties;\n const existingDestructuredKeys = properties\n .filter(\n (property: any): property is typeof t.objectProperty =>\n t.isObjectProperty(property) && t.isIdentifier(property.key)\n )\n .map((property: any) => (property.key as any).name as string);\n const lastProp = properties[properties.length - 1];\n\n info = {\n isDestructured: true,\n variableName: 'content',\n existingDestructuredKeys,\n closingBraceAbsolutePos: absoluteOffset + (parent.id.end! - 1),\n lastPropAbsoluteEnd: absoluteOffset + lastProp.end!,\n };\n } else {\n const variableName =\n t.isVariableDeclarator(parent) && t.isIdentifier(parent.id)\n ? parent.id.name\n : 'content';\n\n info = {\n isDestructured: false,\n variableName,\n existingDestructuredKeys: [],\n closingBraceAbsolutePos: -1,\n lastPropAbsoluteEnd: -1,\n };\n }\n\n path.stop();\n },\n });\n } catch {\n // Silently ignore parse failures — fall back to no-info\n }\n\n return info;\n};\n\nexport type ExtractedContent = Record<string, string>;\n\nexport type ExtractResult = {\n dictionaryKey: string;\n filePath: string;\n content: ExtractedContent;\n locale: Locale;\n};\n\nexport type ExtractPluginOptions = {\n defaultLocale?: Locale;\n packageName?: string;\n filesList?: string[];\n shouldExtract?: (text: string) => boolean;\n onExtract?: (result: ExtractResult) => void;\n dictionaryKey?: string;\n attributesToExtract?: readonly string[];\n extractDictionaryKeyFromPath?: (path: string) => string;\n generateKey?: (text: string, existingKeys: Set<string>) => string;\n};\n\ntype Replacement = {\n start: number;\n end: number;\n replacement: string;\n key: string;\n value: string;\n};\n\nexport const shouldProcessFile = (\n filename: string | undefined,\n filesList?: string[]\n): boolean => {\n if (!filename) return false;\n if (!filesList || filesList.length === 0) return true;\n\n const normalizedFilename = filename.replace(/\\\\/g, '/');\n return filesList.some((f) => {\n const normalizedF = f.replace(/\\\\/g, '/');\n return normalizedF === normalizedFilename;\n });\n};\n\ntype VueParseResult = {\n descriptor: {\n template?: {\n ast: VueAstNode;\n loc: { start: { offset: number }; end: { offset: number } };\n };\n script?: {\n content: string;\n loc: { start: { offset: number }; end: { offset: number } };\n };\n scriptSetup?: {\n content: string;\n loc: { start: { offset: number }; end: { offset: number } };\n };\n };\n};\n\ntype VueAstNode = {\n type: number;\n content?: string;\n children?: VueAstNode[];\n props?: VueAstProp[];\n loc: { start: { offset: number }; end: { offset: number } };\n};\n\ntype VueAstProp = {\n type: number;\n name: string;\n value?: { content: string };\n loc: { start: { offset: number }; end: { offset: number } };\n};\n\nconst NODE_TYPES = {\n TEXT: 2,\n ELEMENT: 1,\n ATTRIBUTE: 6,\n INTERPOLATION: 5,\n};\n\nexport const intlayerVueExtract = (\n code: string,\n filename: string,\n options: ExtractPluginOptions = {}\n): { code: string; map?: unknown; extracted: boolean } | null => {\n const {\n defaultLocale = DEFAULT_LOCALE,\n packageName = 'vue-intlayer',\n filesList,\n shouldExtract,\n onExtract,\n dictionaryKey: dictionaryKeyOption,\n attributesToExtract = [],\n extractDictionaryKeyFromPath,\n generateKey,\n } = options;\n\n if (!shouldProcessFile(filename, filesList)) return null;\n if (!filename.endsWith('.vue')) return null;\n\n let parseVue: (code: string) => VueParseResult;\n\n try {\n parseVue = vueSfc.parse as unknown as (code: string) => VueParseResult;\n } catch {\n console.warn('Vue extraction: @vue/compiler-sfc not found.');\n return null;\n }\n\n const sfc = parseVue(code);\n const magic = new MagicString(code);\n\n const extractedContent: ExtractedContent = {};\n const existingKeys = new Set<string>();\n const dictionaryKey =\n dictionaryKeyOption ?? extractDictionaryKeyFromPath?.(filename) ?? '';\n const replacements: Replacement[] = [];\n\n // Detect existing useIntlayer / getIntlayer call in the script block BEFORE\n // walking the template, so the correct access pattern can be chosen.\n const scriptBlock = sfc.descriptor.scriptSetup ?? sfc.descriptor.script;\n const existingCallInfo = scriptBlock\n ? detectExistingIntlayerCall(\n scriptBlock.content,\n scriptBlock.loc.start.offset\n )\n : null;\n\n const isDestructured = existingCallInfo?.isDestructured ?? false;\n const varName = existingCallInfo?.variableName ?? 'content';\n\n // Walk Vue Template AST\n if (sfc.descriptor.template) {\n const walkVueAst = (node: VueAstNode) => {\n if (node.type === NODE_TYPES.TEXT) {\n const text = node.content ?? '';\n\n if (shouldExtract?.(text) && generateKey) {\n const key = generateKey(text, existingKeys);\n existingKeys.add(key);\n // When the existing call is destructured, access the key directly;\n // otherwise use the `content` variable.\n const ref = isDestructured ? key : `${varName}.${key}`;\n replacements.push({\n start: node.loc.start.offset,\n end: node.loc.end.offset,\n replacement: `{{ ${ref} }}`,\n key,\n value: text.replace(/\\s+/g, ' ').trim(),\n });\n }\n } else if (node.type === NODE_TYPES.ELEMENT) {\n const children = node.children ?? [];\n\n // Try to handle as insertion (mixed TEXT + INTERPOLATION children)\n if (\n children.length > 0 &&\n children.some((c) => c.type === NODE_TYPES.INTERPOLATION)\n ) {\n const parts: {\n type: 'text' | 'var';\n value: string;\n originalExpr: string;\n }[] = [];\n let hasSignificantText = false;\n let isValid = true;\n\n for (const child of children) {\n if (child.type === NODE_TYPES.TEXT) {\n const text = child.content ?? '';\n if (text.trim().length > 0) hasSignificantText = true;\n parts.push({ type: 'text', value: text, originalExpr: '' });\n } else if (child.type === NODE_TYPES.INTERPOLATION) {\n // Extract the expression source between {{ and }}\n const exprCode = code\n .slice(child.loc.start.offset + 2, child.loc.end.offset - 2)\n .trim();\n const varName = exprCode.includes('.')\n ? exprCode\n .split('.')\n .pop()!\n .replace(/[^\\w$]/g, '')\n : exprCode;\n parts.push({\n type: 'var',\n value: varName,\n originalExpr: exprCode,\n });\n } else {\n isValid = false;\n break;\n }\n }\n\n if (\n isValid &&\n hasSignificantText &&\n parts.some((p) => p.type === 'var')\n ) {\n let combined = '';\n for (const p of parts) {\n combined += p.type === 'var' ? `{{${p.value}}}` : p.value;\n }\n const cleanString = combined.replace(/\\s+/g, ' ').trim();\n\n if (shouldExtract?.(cleanString) && generateKey) {\n const key = generateKey(cleanString, existingKeys);\n existingKeys.add(key);\n const ref = isDestructured ? key : `${varName}.${key}`;\n\n const uniqueVarPairs = [\n ...new Set(\n parts\n .filter((p) => p.type === 'var')\n .map((p) => `${p.value}: ${p.originalExpr}`)\n ),\n ];\n const varArgs = uniqueVarPairs.join(', ');\n const replacement = `{{ ${ref}({ ${varArgs} }) }}`;\n\n const firstChild = children[0];\n const lastChild = children[children.length - 1];\n replacements.push({\n start: firstChild.loc.start.offset,\n end: lastChild.loc.end.offset,\n replacement,\n key,\n value: cleanString,\n });\n\n // Process props but skip children (they are replaced)\n node.props?.forEach((prop) => {\n if (\n prop.type === NODE_TYPES.ATTRIBUTE &&\n (attributesToExtract as readonly string[]).includes(\n prop.name\n ) &&\n prop.value\n ) {\n const text = prop.value.content;\n if (shouldExtract?.(text) && generateKey) {\n const propKey = generateKey(text, existingKeys);\n existingKeys.add(propKey);\n const propRef = isDestructured\n ? propKey\n : `${varName}.${propKey}`;\n replacements.push({\n start: prop.loc.start.offset,\n end: prop.loc.end.offset,\n replacement: `:${prop.name}=\"${propRef}\"`,\n key: propKey,\n value: text.trim(),\n });\n }\n }\n });\n return; // don't recurse into children\n }\n }\n }\n\n // Regular element: handle props\n node.props?.forEach((prop) => {\n if (\n prop.type === NODE_TYPES.ATTRIBUTE &&\n (attributesToExtract as readonly string[]).includes(prop.name) &&\n prop.value\n ) {\n const text = prop.value.content;\n\n if (shouldExtract?.(text) && generateKey) {\n const key = generateKey(text, existingKeys);\n existingKeys.add(key);\n const ref = isDestructured ? key : `${varName}.${key}`;\n replacements.push({\n start: prop.loc.start.offset,\n end: prop.loc.end.offset,\n replacement: `:${prop.name}=\"${ref}\"`,\n key,\n value: text.trim(),\n });\n }\n }\n });\n }\n\n if (node.children) {\n node.children.forEach(walkVueAst);\n }\n };\n\n walkVueAst(sfc.descriptor.template.ast);\n }\n\n // Extract and Walk Script using Babel\n if (scriptBlock) {\n const scriptText = scriptBlock.content;\n const offset = scriptBlock.loc.start.offset;\n\n try {\n const ast = babelParse(scriptText, {\n configFile: false,\n babelrc: false,\n parserOpts: {\n sourceType: 'module',\n plugins: ['typescript', 'jsx'],\n },\n });\n\n if (ast) {\n traverse(ast, {\n StringLiteral(path: any) {\n if (path.parentPath.isImportDeclaration()) return;\n\n if (path.parentPath.isExportDeclaration()) return;\n\n if (path.parentPath.isImportSpecifier()) return;\n\n if (path.parentPath.isObjectProperty() && path.key === 'key')\n return;\n\n if (path.parentPath.isCallExpression()) {\n const callee = path.parentPath.node.callee;\n\n if (\n t.isMemberExpression(callee) &&\n t.isIdentifier(callee.object) &&\n callee.object.name === 'console'\n )\n return;\n\n if (\n t.isIdentifier(callee) &&\n (callee.name === 'useIntlayer' || callee.name === 't')\n )\n return;\n\n if (callee.type === 'Import') return;\n\n if (t.isIdentifier(callee) && callee.name === 'require') return;\n }\n\n const text = path.node.value;\n\n if (shouldExtract?.(text) && generateKey) {\n const key = generateKey(text, existingKeys);\n existingKeys.add(key);\n\n if (path.node.start != null && path.node.end != null) {\n const ref = isDestructured ? key : `${varName}.${key}`;\n replacements.push({\n start: offset + path.node.start,\n end: offset + path.node.end,\n replacement: ref,\n key,\n value: text.trim(),\n });\n }\n }\n },\n });\n }\n } catch (e) {\n console.warn(\n `Vue extraction: Failed to parse script content for ${filename}`,\n e\n );\n }\n }\n\n // Abort if nothing was extracted\n if (replacements.length === 0) return null;\n\n // Apply Replacements in Reverse Order\n replacements.sort((a, b) => b.start - a.start);\n\n for (const { start, end, replacement, key, value } of replacements) {\n magic.overwrite(start, end, replacement);\n extractedContent[key] = value;\n }\n\n // When the existing call is destructured, inject only the missing keys into\n // the ObjectPattern — no new `content` variable is needed.\n if (\n existingCallInfo?.isDestructured &&\n existingCallInfo.closingBraceAbsolutePos >= 0\n ) {\n const missingKeys = Object.keys(extractedContent).filter(\n (k) => !existingCallInfo.existingDestructuredKeys.includes(k)\n );\n\n if (missingKeys.length > 0) {\n // Insert right after the last property so the space/newline before `}`\n // is naturally preserved: `{ a }` → `{ a, b }`.\n magic.appendLeft(\n existingCallInfo.lastPropAbsoluteEnd,\n `, ${missingKeys.join(', ')}`\n );\n }\n }\n\n // Inject necessary imports and setup (only when no existing call was detected)\n const finalScriptContent = scriptBlock?.content ?? '';\n\n const hasUseIntlayerImport =\n /import\\s*{[^}]*useIntlayer[^}]*}\\s*from\\s*['\"][^'\"]+['\"]/.test(\n finalScriptContent\n ) ||\n /import\\s+useIntlayer\\s+from\\s*['\"][^'\"]+['\"]/.test(finalScriptContent);\n\n // An existing call (destructured or not) means no new declaration is needed.\n const hasContentDeclaration =\n existingCallInfo !== null ||\n /const\\s+content\\s*=\\s*useIntlayer\\s*\\(/.test(finalScriptContent);\n\n const importStmt = hasUseIntlayerImport\n ? ''\n : `import { useIntlayer } from '${packageName}';`;\n const contentDecl = hasContentDeclaration\n ? ''\n : `const content = useIntlayer('${dictionaryKey}');`;\n\n const injectionParts = [importStmt, contentDecl].filter(Boolean);\n\n if (injectionParts.length > 0) {\n const injection = `\\n${injectionParts.join('\\n')}\\n`;\n\n if (sfc.descriptor.scriptSetup) {\n magic.appendLeft(sfc.descriptor.scriptSetup.loc.start.offset, injection);\n } else if (sfc.descriptor.script) {\n magic.appendLeft(sfc.descriptor.script.loc.start.offset, injection);\n } else {\n magic.prepend(\n `<script setup>\\n${importStmt}\\n${contentDecl}\\n</script>\\n`\n );\n }\n }\n\n if (onExtract) {\n onExtract({\n dictionaryKey,\n filePath: filename,\n content: { ...extractedContent },\n locale: defaultLocale,\n });\n }\n\n return {\n code: magic.toString(),\n map: magic.generateMap({ source: filename, includeContent: true }),\n extracted: true,\n };\n};\n\ntype Tools = {\n generateKey: (text: string, existingKeys: Set<string>) => string;\n shouldExtract: (text: string) => boolean;\n extractDictionaryKeyFromPath: (path: string) => string;\n attributesToExtract: readonly string[];\n extractTsContent: any;\n};\n\nexport const processVueFile = (\n filePath: string,\n _componentKey: string,\n packageName: string,\n tools: Tools,\n save: boolean = true,\n providedCode?: string\n): {\n extractedContent: Record<string, string>;\n code: string;\n map?: any;\n} | null => {\n const code = providedCode ?? readFileSync(filePath, 'utf-8');\n let extractedContent: Record<string, string> = {};\n\n const result = intlayerVueExtract(code, filePath, {\n packageName,\n dictionaryKey: _componentKey,\n shouldExtract: tools.shouldExtract,\n generateKey: tools.generateKey,\n extractDictionaryKeyFromPath: tools.extractDictionaryKeyFromPath,\n attributesToExtract: tools.attributesToExtract,\n onExtract: (extractResult) => {\n extractedContent = extractResult.content;\n },\n });\n\n if (!result) return null;\n\n if (save) {\n writeFileSync(filePath, result.code);\n }\n\n return {\n extractedContent,\n code: result.code,\n map: result.map,\n };\n};\n"],"mappings":";;;;;;;;;;;;;;AAyBA,MAAM,8BACJ,YACA,mBACqB;CACrB,IAAI,OAAyB;CAE7B,IAAI;EACF,MAAM,MAAMA,UAAW,YAAY;GACjC,YAAY;GACZ,SAAS;GACT,YAAY;IAAE,YAAY;IAAU,SAAS,CAAC,cAAc,KAAK;GAAE;EACrE,CAAC;EAED,IAAI,CAAC,KAAK,OAAO;EAEjB,SAAS,KAAK,EACZ,eAAe,MAAW;GACxB,MAAM,SAAS,KAAK,KAAK;GAEzB,IACE,CAACC,MAAE,aAAa,MAAM,KACrB,OAAO,SAAS,iBAAiB,OAAO,SAAS,eAElD;GAEF,MAAM,SAAS,KAAK;GAEpB,IAAIA,MAAE,qBAAqB,MAAM,KAAKA,MAAE,gBAAgB,OAAO,EAAE,GAAG;IAClE,MAAM,aAAa,OAAO,GAAG;IAC7B,MAAM,2BAA2B,WAC9B,QACE,aACCA,MAAE,iBAAiB,QAAQ,KAAKA,MAAE,aAAa,SAAS,GAAG,CAC/D,CAAC,CACA,KAAK,aAAmB,SAAS,IAAY,IAAc;IAC9D,MAAM,WAAW,WAAW,WAAW,SAAS;IAEhD,OAAO;KACL,gBAAgB;KAChB,cAAc;KACd;KACA,yBAAyB,kBAAkB,OAAO,GAAG,MAAO;KAC5D,qBAAqB,iBAAiB,SAAS;IACjD;GACF,OAME,OAAO;IACL,gBAAgB;IAChB,cANAA,MAAE,qBAAqB,MAAM,KAAKA,MAAE,aAAa,OAAO,EAAE,IACtD,OAAO,GAAG,OACV;IAKJ,0BAA0B,CAAC;IAC3B,yBAAyB;IACzB,qBAAqB;GACvB;GAGF,KAAK,KAAK;EACZ,EACF,CAAC;CACH,QAAQ,CAER;CAEA,OAAO;AACT;AA+BA,MAAa,qBACX,UACA,cACY;CACZ,IAAI,CAAC,UAAU,OAAO;CACtB,IAAI,CAAC,aAAa,UAAU,WAAW,GAAG,OAAO;CAEjD,MAAM,qBAAqB,SAAS,QAAQ,OAAO,GAAG;CACtD,OAAO,UAAU,MAAM,MAAM;EAE3B,OADoB,EAAE,QAAQ,OAAO,GACpB,MAAM;CACzB,CAAC;AACH;AAkCA,MAAM,aAAa;CACjB,MAAM;CACN,SAAS;CACT,WAAW;CACX,eAAe;AACjB;AAEA,MAAa,sBACX,MACA,UACA,UAAgC,CAAC,MAC8B;CAC/D,MAAM,EACJ,gBAAgB,gBAChB,cAAc,gBACd,WACA,eACA,WACA,eAAe,qBACf,sBAAsB,CAAC,GACvB,8BACA,gBACE;CAEJ,IAAI,CAAC,kBAAkB,UAAU,SAAS,GAAG,OAAO;CACpD,IAAI,CAAC,SAAS,SAAS,MAAM,GAAG,OAAO;CAEvC,IAAI;CAEJ,IAAI;EACF,WAAW,OAAO;CACpB,QAAQ;EACN,QAAQ,KAAK,8CAA8C;EAC3D,OAAO;CACT;CAEA,MAAM,MAAM,SAAS,IAAI;CACzB,MAAM,QAAQ,IAAI,YAAY,IAAI;CAElC,MAAM,mBAAqC,CAAC;CAC5C,MAAM,+BAAe,IAAI,IAAY;CACrC,MAAM,gBACJ,uBAAuB,+BAA+B,QAAQ,KAAK;CACrE,MAAM,eAA8B,CAAC;CAIrC,MAAM,cAAc,IAAI,WAAW,eAAe,IAAI,WAAW;CACjE,MAAM,mBAAmB,cACrB,2BACE,YAAY,SACZ,YAAY,IAAI,MAAM,MACxB,IACA;CAEJ,MAAM,iBAAiB,kBAAkB,kBAAkB;CAC3D,MAAM,UAAU,kBAAkB,gBAAgB;CAGlD,IAAI,IAAI,WAAW,UAAU;EAC3B,MAAM,cAAc,SAAqB;GACvC,IAAI,KAAK,SAAS,WAAW,MAAM;IACjC,MAAM,OAAO,KAAK,WAAW;IAE7B,IAAI,gBAAgB,IAAI,KAAK,aAAa;KACxC,MAAM,MAAM,YAAY,MAAM,YAAY;KAC1C,aAAa,IAAI,GAAG;KAGpB,MAAM,MAAM,iBAAiB,MAAM,GAAG,QAAQ,GAAG;KACjD,aAAa,KAAK;MAChB,OAAO,KAAK,IAAI,MAAM;MACtB,KAAK,KAAK,IAAI,IAAI;MAClB,aAAa,MAAM,IAAI;MACvB;MACA,OAAO,KAAK,QAAQ,QAAQ,GAAG,CAAC,CAAC,KAAK;KACxC,CAAC;IACH;GACF,OAAO,IAAI,KAAK,SAAS,WAAW,SAAS;IAC3C,MAAM,WAAW,KAAK,YAAY,CAAC;IAGnC,IACE,SAAS,SAAS,KAClB,SAAS,MAAM,MAAM,EAAE,SAAS,WAAW,aAAa,GACxD;KACA,MAAM,QAIA,CAAC;KACP,IAAI,qBAAqB;KACzB,IAAI,UAAU;KAEd,KAAK,MAAM,SAAS,UAClB,IAAI,MAAM,SAAS,WAAW,MAAM;MAClC,MAAM,OAAO,MAAM,WAAW;MAC9B,IAAI,KAAK,KAAK,CAAC,CAAC,SAAS,GAAG,qBAAqB;MACjD,MAAM,KAAK;OAAE,MAAM;OAAQ,OAAO;OAAM,cAAc;MAAG,CAAC;KAC5D,OAAO,IAAI,MAAM,SAAS,WAAW,eAAe;MAElD,MAAM,WAAW,KACd,MAAM,MAAM,IAAI,MAAM,SAAS,GAAG,MAAM,IAAI,IAAI,SAAS,CAAC,CAAC,CAC3D,KAAK;MACR,MAAM,UAAU,SAAS,SAAS,GAAG,IACjC,SACG,MAAM,GAAG,CAAC,CACV,IAAI,CAAC,CACL,QAAQ,WAAW,EAAE,IACxB;MACJ,MAAM,KAAK;OACT,MAAM;OACN,OAAO;OACP,cAAc;MAChB,CAAC;KACH,OAAO;MACL,UAAU;MACV;KACF;KAGF,IACE,WACA,sBACA,MAAM,MAAM,MAAM,EAAE,SAAS,KAAK,GAClC;MACA,IAAI,WAAW;MACf,KAAK,MAAM,KAAK,OACd,YAAY,EAAE,SAAS,QAAQ,KAAK,EAAE,MAAM,MAAM,EAAE;MAEtD,MAAM,cAAc,SAAS,QAAQ,QAAQ,GAAG,CAAC,CAAC,KAAK;MAEvD,IAAI,gBAAgB,WAAW,KAAK,aAAa;OAC/C,MAAM,MAAM,YAAY,aAAa,YAAY;OACjD,aAAa,IAAI,GAAG;OAWpB,MAAM,cAAc,MAVR,iBAAiB,MAAM,GAAG,QAAQ,GAAG,MAUnB,KADd,CANd,GAAG,IAAI,IACL,MACG,QAAQ,MAAM,EAAE,SAAS,KAAK,CAAC,CAC/B,KAAK,MAAM,GAAG,EAAE,MAAM,IAAI,EAAE,cAAc,CAC/C,CAE2B,CAAC,CAAC,KAAK,IACK,EAAE;OAE3C,MAAM,aAAa,SAAS;OAC5B,MAAM,YAAY,SAAS,SAAS,SAAS;OAC7C,aAAa,KAAK;QAChB,OAAO,WAAW,IAAI,MAAM;QAC5B,KAAK,UAAU,IAAI,IAAI;QACvB;QACA;QACA,OAAO;OACT,CAAC;OAGD,KAAK,OAAO,SAAS,SAAS;QAC5B,IACE,KAAK,SAAS,WAAW,aACxB,oBAA0C,SACzC,KAAK,IACP,KACA,KAAK,OACL;SACA,MAAM,OAAO,KAAK,MAAM;SACxB,IAAI,gBAAgB,IAAI,KAAK,aAAa;UACxC,MAAM,UAAU,YAAY,MAAM,YAAY;UAC9C,aAAa,IAAI,OAAO;UACxB,MAAM,UAAU,iBACZ,UACA,GAAG,QAAQ,GAAG;UAClB,aAAa,KAAK;WAChB,OAAO,KAAK,IAAI,MAAM;WACtB,KAAK,KAAK,IAAI,IAAI;WAClB,aAAa,IAAI,KAAK,KAAK,IAAI,QAAQ;WACvC,KAAK;WACL,OAAO,KAAK,KAAK;UACnB,CAAC;SACH;QACF;OACF,CAAC;OACD;MACF;KACF;IACF;IAGA,KAAK,OAAO,SAAS,SAAS;KAC5B,IACE,KAAK,SAAS,WAAW,aACxB,oBAA0C,SAAS,KAAK,IAAI,KAC7D,KAAK,OACL;MACA,MAAM,OAAO,KAAK,MAAM;MAExB,IAAI,gBAAgB,IAAI,KAAK,aAAa;OACxC,MAAM,MAAM,YAAY,MAAM,YAAY;OAC1C,aAAa,IAAI,GAAG;OACpB,MAAM,MAAM,iBAAiB,MAAM,GAAG,QAAQ,GAAG;OACjD,aAAa,KAAK;QAChB,OAAO,KAAK,IAAI,MAAM;QACtB,KAAK,KAAK,IAAI,IAAI;QAClB,aAAa,IAAI,KAAK,KAAK,IAAI,IAAI;QACnC;QACA,OAAO,KAAK,KAAK;OACnB,CAAC;MACH;KACF;IACF,CAAC;GACH;GAEA,IAAI,KAAK,UACP,KAAK,SAAS,QAAQ,UAAU;EAEpC;EAEA,WAAW,IAAI,WAAW,SAAS,GAAG;CACxC;CAGA,IAAI,aAAa;EACf,MAAM,aAAa,YAAY;EAC/B,MAAM,SAAS,YAAY,IAAI,MAAM;EAErC,IAAI;GACF,MAAM,MAAMD,UAAW,YAAY;IACjC,YAAY;IACZ,SAAS;IACT,YAAY;KACV,YAAY;KACZ,SAAS,CAAC,cAAc,KAAK;IAC/B;GACF,CAAC;GAED,IAAI,KACF,SAAS,KAAK,EACZ,cAAc,MAAW;IACvB,IAAI,KAAK,WAAW,oBAAoB,GAAG;IAE3C,IAAI,KAAK,WAAW,oBAAoB,GAAG;IAE3C,IAAI,KAAK,WAAW,kBAAkB,GAAG;IAEzC,IAAI,KAAK,WAAW,iBAAiB,KAAK,KAAK,QAAQ,OACrD;IAEF,IAAI,KAAK,WAAW,iBAAiB,GAAG;KACtC,MAAM,SAAS,KAAK,WAAW,KAAK;KAEpC,IACEC,MAAE,mBAAmB,MAAM,KAC3BA,MAAE,aAAa,OAAO,MAAM,KAC5B,OAAO,OAAO,SAAS,WAEvB;KAEF,IACEA,MAAE,aAAa,MAAM,MACpB,OAAO,SAAS,iBAAiB,OAAO,SAAS,MAElD;KAEF,IAAI,OAAO,SAAS,UAAU;KAE9B,IAAIA,MAAE,aAAa,MAAM,KAAK,OAAO,SAAS,WAAW;IAC3D;IAEA,MAAM,OAAO,KAAK,KAAK;IAEvB,IAAI,gBAAgB,IAAI,KAAK,aAAa;KACxC,MAAM,MAAM,YAAY,MAAM,YAAY;KAC1C,aAAa,IAAI,GAAG;KAEpB,IAAI,KAAK,KAAK,SAAS,QAAQ,KAAK,KAAK,OAAO,MAAM;MACpD,MAAM,MAAM,iBAAiB,MAAM,GAAG,QAAQ,GAAG;MACjD,aAAa,KAAK;OAChB,OAAO,SAAS,KAAK,KAAK;OAC1B,KAAK,SAAS,KAAK,KAAK;OACxB,aAAa;OACb;OACA,OAAO,KAAK,KAAK;MACnB,CAAC;KACH;IACF;GACF,EACF,CAAC;EAEL,SAAS,GAAG;GACV,QAAQ,KACN,sDAAsD,YACtD,CACF;EACF;CACF;CAGA,IAAI,aAAa,WAAW,GAAG,OAAO;CAGtC,aAAa,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;CAE7C,KAAK,MAAM,EAAE,OAAO,KAAK,aAAa,KAAK,WAAW,cAAc;EAClE,MAAM,UAAU,OAAO,KAAK,WAAW;EACvC,iBAAiB,OAAO;CAC1B;CAIA,IACE,kBAAkB,kBAClB,iBAAiB,2BAA2B,GAC5C;EACA,MAAM,cAAc,OAAO,KAAK,gBAAgB,CAAC,CAAC,QAC/C,MAAM,CAAC,iBAAiB,yBAAyB,SAAS,CAAC,CAC9D;EAEA,IAAI,YAAY,SAAS,GAGvB,MAAM,WACJ,iBAAiB,qBACjB,KAAK,YAAY,KAAK,IAAI,GAC5B;CAEJ;CAGA,MAAM,qBAAqB,aAAa,WAAW;CAEnD,MAAM,uBACJ,2DAA2D,KACzD,kBACF,KACA,+CAA+C,KAAK,kBAAkB;CAGxE,MAAM,wBACJ,qBAAqB,QACrB,yCAAyC,KAAK,kBAAkB;CAElE,MAAM,aAAa,uBACf,KACA,gCAAgC,YAAY;CAChD,MAAM,cAAc,wBAChB,KACA,gCAAgC,cAAc;CAElD,MAAM,iBAAiB,CAAC,YAAY,WAAW,CAAC,CAAC,OAAO,OAAO;CAE/D,IAAI,eAAe,SAAS,GAAG;EAC7B,MAAM,YAAY,KAAK,eAAe,KAAK,IAAI,EAAE;EAEjD,IAAI,IAAI,WAAW,aACjB,MAAM,WAAW,IAAI,WAAW,YAAY,IAAI,MAAM,QAAQ,SAAS;OAClE,IAAI,IAAI,WAAW,QACxB,MAAM,WAAW,IAAI,WAAW,OAAO,IAAI,MAAM,QAAQ,SAAS;OAElE,MAAM,QACJ,mBAAmB,WAAW,IAAI,YAAY,eAChD;CAEJ;CAEA,IAAI,WACF,UAAU;EACR;EACA,UAAU;EACV,SAAS,EAAE,GAAG,iBAAiB;EAC/B,QAAQ;CACV,CAAC;CAGH,OAAO;EACL,MAAM,MAAM,SAAS;EACrB,KAAK,MAAM,YAAY;GAAE,QAAQ;GAAU,gBAAgB;EAAK,CAAC;EACjE,WAAW;CACb;AACF;AAUA,MAAa,kBACX,UACA,eACA,aACA,OACA,OAAgB,MAChB,iBAKU;CACV,MAAM,OAAO,gBAAgB,aAAa,UAAU,OAAO;CAC3D,IAAI,mBAA2C,CAAC;CAEhD,MAAM,SAAS,mBAAmB,MAAM,UAAU;EAChD;EACA,eAAe;EACf,eAAe,MAAM;EACrB,aAAa,MAAM;EACnB,8BAA8B,MAAM;EACpC,qBAAqB,MAAM;EAC3B,YAAY,kBAAkB;GAC5B,mBAAmB,cAAc;EACnC;CACF,CAAC;CAED,IAAI,CAAC,QAAQ,OAAO;CAEpB,IAAI,MACF,cAAc,UAAU,OAAO,IAAI;CAGrC,OAAO;EACL;EACA,MAAM,OAAO;EACb,KAAK,OAAO;CACd;AACF"}
@@ -3,13 +3,18 @@
3
3
  * Extracts intlayer dictionary field usage from a Vue SFC for a set of
4
4
  * plain variable bindings (i.e. `const content = useIntlayer('key')`).
5
5
  *
6
- * Two access patterns are recognised:
6
+ * Three access patterns are recognised:
7
7
  *
8
- * 1. `varName.value.fieldName` in script blocks
9
- * Vue's `useIntlayer` returns a reactive `Ref<Content>`, so fields are
10
- * accessed one level deeper via `.value`.
8
+ * 1. `varName.fieldName` in script blocks
9
+ * `useIntlayer` returns a deep reactive proxy, so content fields are read
10
+ * directly on the variable (`content.title`, `content.count(2)`), the
11
+ * `.value` unwrapping happening on the leaf node instead.
11
12
  *
12
- * 2. `varName.fieldName` in the template block
13
+ * 2. `varName.value.fieldName` in script blocks
14
+ * Legacy `Ref<Content>` accessor indirection, where fields live one level
15
+ * deeper than the variable.
16
+ *
17
+ * 3. `varName.fieldName` in the template block
13
18
  * Inside `<template>`, Vue automatically unwraps top-level refs, so
14
19
  * fields are accessed directly without `.value`.
15
20
  *
@@ -1 +1 @@
1
- {"version":3,"file":"extractVueFieldUsage.d.ts","names":[],"sources":["../../src/extractVueFieldUsage.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;KA0BY;;EAEV;;EAEA;;;;;;;;;;;;cAmDW,+BAA4B,cAC3B,gBACI,wBACf,YAAY"}
1
+ {"version":3,"file":"extractVueFieldUsage.d.ts","names":[],"sources":["../../src/extractVueFieldUsage.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;KAwDY;;EAEV;;EAEA;;;;;;;;;;;;cAmDW,+BAA4B,cAC3B,gBACI,wBACf,YAAY"}
@@ -1 +1 @@
1
- {"version":3,"file":"vue-intlayer-extract.d.ts","names":[],"sources":["../../src/vue-intlayer-extract.ts"],"mappings":";;KA4FY,mBAAmB;KAEnB;EACV;EACA;EACA,SAAS;EACT,QAAQ;;KAGE;EACV,gBAAgB;EAChB;EACA;EACA,iBAAiB;EACjB,aAAa,QAAQ;EACrB;EACA;EACA,gCAAgC;EAChC,eAAe,cAAc,cAAc;;cAWhC,oBAAiB,8BACA;cAoDjB,qBAAkB,cACjB,kBACI,UACP;EACN;EAAc;EAAe;;KAgX7B;EACH,cAAc,cAAc,cAAc;EAC1C,gBAAgB;EAChB,+BAA+B;EAC/B;EACA;;cAGW,iBAAc,kBACT,uBACK,qBACF,OACZ,OAAK,gBACC;EAGb,kBAAkB;EAClB;EACA"}
1
+ {"version":3,"file":"vue-intlayer-extract.d.ts","names":[],"sources":["../../src/vue-intlayer-extract.ts"],"mappings":";;KA8FY,mBAAmB;KAEnB;EACV;EACA;EACA,SAAS;EACT,QAAQ;;KAGE;EACV,gBAAgB;EAChB;EACA;EACA,iBAAiB;EACjB,aAAa,QAAQ;EACrB;EACA;EACA,gCAAgC;EAChC,eAAe,cAAc,cAAc;;cAWhC,oBAAiB,8BACA;cAoDjB,qBAAkB,cACjB,kBACI,UACP;EACN;EAAc;EAAe;;KAkX7B;EACH,cAAc,cAAc,cAAc;EAC1C,gBAAgB;EAChB,+BAA+B;EAC/B;EACA;;cAGW,iBAAc,kBACT,uBACK,qBACF,OACZ,OAAK,gBACC;EAGb,kBAAkB;EAClB;EACA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intlayer/vue-compiler",
3
- "version": "9.3.1",
3
+ "version": "9.3.3",
4
4
  "private": false,
5
5
  "description": "Vite-compatible compiler plugin for Vue with Intlayer, providing HMR support, file transformation, and optimized dictionary loading for Vue applications.",
6
6
  "keywords": [
@@ -78,8 +78,8 @@
78
78
  },
79
79
  "dependencies": {
80
80
  "@babel/core": "8.0.1",
81
- "@intlayer/config": "9.3.1",
82
- "@intlayer/types": "9.3.1",
81
+ "@intlayer/config": "9.3.3",
82
+ "@intlayer/types": "9.3.3",
83
83
  "@vue/compiler-sfc": ">=3.0.0",
84
84
  "fast-glob": "3.3.3",
85
85
  "magic-string": "1.1.0"
@@ -95,7 +95,7 @@
95
95
  "rimraf": "6.1.3",
96
96
  "tsdown": "0.22.14",
97
97
  "typescript": "7.0.2",
98
- "vitest": "4.1.10"
98
+ "vitest": "4.1.11"
99
99
  },
100
100
  "peerDependencies": {
101
101
  "@babel/core": ">=6.0.0",