@vocoder/cli 0.1.16 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/bin.mjs +1139 -833
- package/dist/bin.mjs.map +1 -1
- package/dist/chunk-OFQLREXF.mjs +267 -0
- package/dist/chunk-OFQLREXF.mjs.map +1 -0
- package/dist/lib.d.mts +18 -73
- package/dist/lib.mjs +1 -1
- package/package.json +14 -20
- package/dist/chunk-KPIT5ETY.mjs +0 -547
- package/dist/chunk-KPIT5ETY.mjs.map +0 -1
package/dist/chunk-KPIT5ETY.mjs
DELETED
|
@@ -1,547 +0,0 @@
|
|
|
1
|
-
// src/utils/detect-local.ts
|
|
2
|
-
import { existsSync, readFileSync } from "fs";
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
function detectLocalEcosystem(cwd = process.cwd()) {
|
|
5
|
-
const packageManager = detectPackageManager(cwd);
|
|
6
|
-
const pkg = readPackageJson(cwd);
|
|
7
|
-
if (!pkg) {
|
|
8
|
-
return {
|
|
9
|
-
ecosystem: null,
|
|
10
|
-
framework: null,
|
|
11
|
-
packageManager,
|
|
12
|
-
uiPackage: null,
|
|
13
|
-
hasUnplugin: false,
|
|
14
|
-
hasUiPackage: false,
|
|
15
|
-
sourceLocale: null
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
const allDeps = {
|
|
19
|
-
...pkg.dependencies ?? {},
|
|
20
|
-
...pkg.devDependencies ?? {}
|
|
21
|
-
};
|
|
22
|
-
const hasUnplugin = "@vocoder/unplugin" in allDeps;
|
|
23
|
-
const { ecosystem, framework, uiPackage } = detectFromDeps(allDeps, cwd);
|
|
24
|
-
const hasUiPackage = uiPackage !== null && uiPackage in allDeps;
|
|
25
|
-
return {
|
|
26
|
-
ecosystem,
|
|
27
|
-
framework,
|
|
28
|
-
packageManager,
|
|
29
|
-
uiPackage,
|
|
30
|
-
hasUnplugin,
|
|
31
|
-
hasUiPackage,
|
|
32
|
-
sourceLocale: null
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
function detectPackageManager(cwd) {
|
|
36
|
-
if (existsSync(join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
37
|
-
if (existsSync(join(cwd, "bun.lockb")) || existsSync(join(cwd, "bun.lock"))) return "bun";
|
|
38
|
-
if (existsSync(join(cwd, "yarn.lock"))) return "yarn";
|
|
39
|
-
return "npm";
|
|
40
|
-
}
|
|
41
|
-
function readPackageJson(cwd) {
|
|
42
|
-
const pkgPath = join(cwd, "package.json");
|
|
43
|
-
if (!existsSync(pkgPath)) return null;
|
|
44
|
-
try {
|
|
45
|
-
return JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
46
|
-
} catch {
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
function detectFromDeps(allDeps, cwd) {
|
|
51
|
-
if ("vue" in allDeps) {
|
|
52
|
-
const framework = "nuxt" in allDeps ? "nuxt" : null;
|
|
53
|
-
return { ecosystem: "vue", framework, uiPackage: "@vocoder/vue" };
|
|
54
|
-
}
|
|
55
|
-
if ("svelte" in allDeps) {
|
|
56
|
-
const framework = "@sveltejs/kit" in allDeps ? "sveltekit" : null;
|
|
57
|
-
return { ecosystem: "svelte", framework, uiPackage: "@vocoder/svelte" };
|
|
58
|
-
}
|
|
59
|
-
if ("@angular/core" in allDeps || existsSync(join(cwd, "angular.json"))) {
|
|
60
|
-
return { ecosystem: "angular", framework: "angular", uiPackage: "@vocoder/angular" };
|
|
61
|
-
}
|
|
62
|
-
if ("react" in allDeps) {
|
|
63
|
-
let framework = null;
|
|
64
|
-
if ("next" in allDeps) framework = "nextjs";
|
|
65
|
-
else if ("@remix-run/react" in allDeps) framework = "remix";
|
|
66
|
-
else if ("gatsby" in allDeps) framework = "gatsby";
|
|
67
|
-
else if ("vite" in allDeps) framework = "vite";
|
|
68
|
-
return { ecosystem: "react", framework, uiPackage: "@vocoder/react" };
|
|
69
|
-
}
|
|
70
|
-
return { ecosystem: null, framework: null, uiPackage: null };
|
|
71
|
-
}
|
|
72
|
-
function buildInstallCommand(packageManager, packages) {
|
|
73
|
-
if (packages.length === 0) return "";
|
|
74
|
-
const pkgList = packages.join(" ");
|
|
75
|
-
switch (packageManager) {
|
|
76
|
-
case "pnpm":
|
|
77
|
-
return `pnpm add ${pkgList}`;
|
|
78
|
-
case "yarn":
|
|
79
|
-
return `yarn add ${pkgList}`;
|
|
80
|
-
case "bun":
|
|
81
|
-
return `bun add ${pkgList}`;
|
|
82
|
-
default:
|
|
83
|
-
return `npm install ${pkgList}`;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
function getPackagesToInstall(detection) {
|
|
87
|
-
const packages = [];
|
|
88
|
-
if (!detection.hasUnplugin) packages.push("@vocoder/unplugin");
|
|
89
|
-
if (detection.uiPackage && !detection.hasUiPackage) packages.push(detection.uiPackage);
|
|
90
|
-
return packages;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// src/utils/setup-snippets.ts
|
|
94
|
-
function getSetupSnippets(params) {
|
|
95
|
-
const { framework, ecosystem, sourceLocale, branchTriggers } = params;
|
|
96
|
-
const allTriggers = [...new Set(branchTriggers.flatMap((b) => b.triggers))];
|
|
97
|
-
return {
|
|
98
|
-
pluginStep: getPluginSnippet(framework, ecosystem),
|
|
99
|
-
providerStep: getProviderSnippet(ecosystem, sourceLocale),
|
|
100
|
-
wrapStep: getWrapSnippet(ecosystem),
|
|
101
|
-
whatsNext: getWhatsNextMessage(allTriggers)
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
function getPluginSnippet(framework, ecosystem) {
|
|
105
|
-
switch (framework) {
|
|
106
|
-
case "nextjs":
|
|
107
|
-
return {
|
|
108
|
-
file: "next.config.ts",
|
|
109
|
-
code: `import { withVocoder } from '@vocoder/unplugin/next';
|
|
110
|
-
|
|
111
|
-
export default withVocoder({
|
|
112
|
-
// your existing Next.js config
|
|
113
|
-
});`
|
|
114
|
-
};
|
|
115
|
-
case "vite":
|
|
116
|
-
case "remix":
|
|
117
|
-
return {
|
|
118
|
-
file: "vite.config.ts",
|
|
119
|
-
code: `import vocoder from '@vocoder/unplugin/vite';
|
|
120
|
-
|
|
121
|
-
export default defineConfig({
|
|
122
|
-
plugins: [
|
|
123
|
-
vocoder(),
|
|
124
|
-
// your other plugins
|
|
125
|
-
],
|
|
126
|
-
});`
|
|
127
|
-
};
|
|
128
|
-
case "nuxt":
|
|
129
|
-
return {
|
|
130
|
-
file: "nuxt.config.ts",
|
|
131
|
-
code: `import vocoder from '@vocoder/unplugin/vite';
|
|
132
|
-
|
|
133
|
-
export default defineNuxtConfig({
|
|
134
|
-
vite: {
|
|
135
|
-
plugins: [vocoder()],
|
|
136
|
-
},
|
|
137
|
-
});`
|
|
138
|
-
};
|
|
139
|
-
case "sveltekit":
|
|
140
|
-
return {
|
|
141
|
-
file: "vite.config.ts",
|
|
142
|
-
code: `import vocoder from '@vocoder/unplugin/vite';
|
|
143
|
-
import { sveltekit } from '@sveltejs/kit/vite';
|
|
144
|
-
|
|
145
|
-
export default defineConfig({
|
|
146
|
-
plugins: [
|
|
147
|
-
sveltekit(),
|
|
148
|
-
vocoder(),
|
|
149
|
-
],
|
|
150
|
-
});`
|
|
151
|
-
};
|
|
152
|
-
case "gatsby":
|
|
153
|
-
return {
|
|
154
|
-
file: "gatsby-node.js",
|
|
155
|
-
code: `const vocoder = require('@vocoder/unplugin/webpack');
|
|
156
|
-
|
|
157
|
-
exports.onCreateWebpackConfig = ({ actions }) => {
|
|
158
|
-
actions.setWebpackConfig({
|
|
159
|
-
plugins: [vocoder()],
|
|
160
|
-
});
|
|
161
|
-
};`
|
|
162
|
-
};
|
|
163
|
-
case "angular":
|
|
164
|
-
return null;
|
|
165
|
-
// Angular CLI doesn't expose plugin config easily
|
|
166
|
-
default:
|
|
167
|
-
if (ecosystem) {
|
|
168
|
-
return {
|
|
169
|
-
file: "your bundler config",
|
|
170
|
-
code: `// Vite
|
|
171
|
-
import vocoder from '@vocoder/unplugin/vite';
|
|
172
|
-
// Webpack
|
|
173
|
-
const vocoder = require('@vocoder/unplugin/webpack');
|
|
174
|
-
|
|
175
|
-
// Add vocoder() to your plugins array`
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
return null;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
function getProviderSnippet(ecosystem, sourceLocale) {
|
|
182
|
-
switch (ecosystem) {
|
|
183
|
-
case "react":
|
|
184
|
-
return {
|
|
185
|
-
file: "your root layout or App component",
|
|
186
|
-
code: `import { VocoderProvider } from '@vocoder/react';
|
|
187
|
-
|
|
188
|
-
<VocoderProvider defaultLocale="${sourceLocale}">
|
|
189
|
-
{children}
|
|
190
|
-
</VocoderProvider>`
|
|
191
|
-
};
|
|
192
|
-
case "vue":
|
|
193
|
-
return {
|
|
194
|
-
file: "your app entry",
|
|
195
|
-
code: `import { createVocoder } from '@vocoder/vue';
|
|
196
|
-
|
|
197
|
-
const vocoder = createVocoder({
|
|
198
|
-
defaultLocale: '${sourceLocale}',
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
app.use(vocoder);`
|
|
202
|
-
};
|
|
203
|
-
case "svelte":
|
|
204
|
-
return {
|
|
205
|
-
file: "your root layout",
|
|
206
|
-
code: `<script>
|
|
207
|
-
import { VocoderProvider } from '@vocoder/svelte';
|
|
208
|
-
</script>
|
|
209
|
-
|
|
210
|
-
<VocoderProvider defaultLocale="${sourceLocale}">
|
|
211
|
-
<slot />
|
|
212
|
-
</VocoderProvider>`
|
|
213
|
-
};
|
|
214
|
-
default:
|
|
215
|
-
return null;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
function getWrapSnippet(ecosystem) {
|
|
219
|
-
switch (ecosystem) {
|
|
220
|
-
case "react":
|
|
221
|
-
return {
|
|
222
|
-
code: `import { T } from '@vocoder/react';
|
|
223
|
-
|
|
224
|
-
<T>Hello, world!</T>`
|
|
225
|
-
};
|
|
226
|
-
case "vue":
|
|
227
|
-
return {
|
|
228
|
-
code: `<template>
|
|
229
|
-
<T>Hello, world!</T>
|
|
230
|
-
</template>
|
|
231
|
-
|
|
232
|
-
<script setup>
|
|
233
|
-
import { T } from '@vocoder/vue';
|
|
234
|
-
</script>`
|
|
235
|
-
};
|
|
236
|
-
case "svelte":
|
|
237
|
-
return {
|
|
238
|
-
code: `<script>
|
|
239
|
-
import { T } from '@vocoder/svelte';
|
|
240
|
-
</script>
|
|
241
|
-
|
|
242
|
-
<T>Hello, world!</T>`
|
|
243
|
-
};
|
|
244
|
-
default:
|
|
245
|
-
return {
|
|
246
|
-
code: `// Wrap translatable strings with <T>
|
|
247
|
-
<T>Hello, world!</T>`
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
function getWhatsNextMessage(triggers) {
|
|
252
|
-
const parts = [];
|
|
253
|
-
if (triggers.includes("push")) {
|
|
254
|
-
parts.push("Push to a target branch to trigger translations.");
|
|
255
|
-
}
|
|
256
|
-
if (triggers.includes("pull_request")) {
|
|
257
|
-
parts.push("Open a pull request to trigger translations.");
|
|
258
|
-
}
|
|
259
|
-
if (triggers.includes("manual")) {
|
|
260
|
-
parts.push("Run `vocoder sync` to extract and translate.");
|
|
261
|
-
}
|
|
262
|
-
if (parts.length === 0) {
|
|
263
|
-
parts.push("Push to a target branch to trigger translations.");
|
|
264
|
-
}
|
|
265
|
-
return parts.join("\n");
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// src/utils/extract.ts
|
|
269
|
-
import { createHash } from "crypto";
|
|
270
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
271
|
-
import { parse } from "@babel/parser";
|
|
272
|
-
import babelTraverse from "@babel/traverse";
|
|
273
|
-
import { glob } from "glob";
|
|
274
|
-
import { relative as pathRelative } from "path";
|
|
275
|
-
var traverse = babelTraverse.default || babelTraverse;
|
|
276
|
-
var StringExtractor = class {
|
|
277
|
-
/**
|
|
278
|
-
* Extract strings from all files matching the pattern(s)
|
|
279
|
-
*
|
|
280
|
-
* @param pattern - Glob pattern(s) to include
|
|
281
|
-
* @param projectRoot - Project root directory
|
|
282
|
-
* @param excludePattern - Glob pattern(s) to exclude (optional)
|
|
283
|
-
*/
|
|
284
|
-
async extractFromProject(pattern, projectRoot = process.cwd(), excludePattern) {
|
|
285
|
-
const includePatterns = Array.isArray(pattern) ? pattern : [pattern];
|
|
286
|
-
const defaultIgnore = ["**/node_modules/**", "**/.next/**", "**/dist/**", "**/build/**"];
|
|
287
|
-
const ignorePatterns = excludePattern ? [...defaultIgnore, ...Array.isArray(excludePattern) ? excludePattern : [excludePattern]] : defaultIgnore;
|
|
288
|
-
const allFiles = /* @__PURE__ */ new Set();
|
|
289
|
-
for (const includePattern of includePatterns) {
|
|
290
|
-
const files = await glob(includePattern, {
|
|
291
|
-
cwd: projectRoot,
|
|
292
|
-
absolute: true,
|
|
293
|
-
ignore: ignorePatterns
|
|
294
|
-
});
|
|
295
|
-
files.forEach((file) => allFiles.add(file));
|
|
296
|
-
}
|
|
297
|
-
const allStrings = [];
|
|
298
|
-
const sortedFiles = Array.from(allFiles).sort();
|
|
299
|
-
for (const file of sortedFiles) {
|
|
300
|
-
try {
|
|
301
|
-
const strings = await this.extractFromFile(file, projectRoot);
|
|
302
|
-
allStrings.push(...strings);
|
|
303
|
-
} catch (error) {
|
|
304
|
-
console.warn(`Warning: Failed to extract from ${file}:`, error);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
const unique = this.deduplicateStrings(allStrings);
|
|
308
|
-
return unique;
|
|
309
|
-
}
|
|
310
|
-
/**
|
|
311
|
-
* Extract strings from a single file
|
|
312
|
-
*/
|
|
313
|
-
async extractFromFile(filePath, projectRoot) {
|
|
314
|
-
const code = readFileSync2(filePath, "utf-8");
|
|
315
|
-
const strings = [];
|
|
316
|
-
const relativeFilePath = pathRelative(projectRoot, filePath).split("\\").join("/");
|
|
317
|
-
try {
|
|
318
|
-
const ast = parse(code, {
|
|
319
|
-
sourceType: "module",
|
|
320
|
-
plugins: ["jsx", "typescript"]
|
|
321
|
-
});
|
|
322
|
-
const vocoderImports = /* @__PURE__ */ new Map();
|
|
323
|
-
const tFunctionNames = /* @__PURE__ */ new Set();
|
|
324
|
-
traverse(ast, {
|
|
325
|
-
// Track imports of <T> component and t function
|
|
326
|
-
ImportDeclaration: (path) => {
|
|
327
|
-
const source = path.node.source.value;
|
|
328
|
-
if (source === "@vocoder/react") {
|
|
329
|
-
path.node.specifiers.forEach((spec) => {
|
|
330
|
-
if (spec.type === "ImportSpecifier") {
|
|
331
|
-
const imported = spec.imported.type === "Identifier" ? spec.imported.name : null;
|
|
332
|
-
const local = spec.local.name;
|
|
333
|
-
if (imported === "T") {
|
|
334
|
-
vocoderImports.set(local, "T");
|
|
335
|
-
}
|
|
336
|
-
if (imported === "t") {
|
|
337
|
-
tFunctionNames.add(local);
|
|
338
|
-
}
|
|
339
|
-
if (imported === "useVocoder") {
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
},
|
|
345
|
-
// Track destructured 't' from useVocoder hook
|
|
346
|
-
VariableDeclarator: (path) => {
|
|
347
|
-
const init = path.node.init;
|
|
348
|
-
if (init && init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "useVocoder" && path.node.id.type === "ObjectPattern") {
|
|
349
|
-
path.node.id.properties.forEach((prop) => {
|
|
350
|
-
if (prop.type === "ObjectProperty" && prop.key.type === "Identifier" && prop.key.name === "t") {
|
|
351
|
-
const localName = prop.value.type === "Identifier" ? prop.value.name : "t";
|
|
352
|
-
tFunctionNames.add(localName);
|
|
353
|
-
}
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
},
|
|
357
|
-
// Extract from t() function calls
|
|
358
|
-
CallExpression: (path) => {
|
|
359
|
-
const callee = path.node.callee;
|
|
360
|
-
const isTFunction = callee.type === "Identifier" && tFunctionNames.has(callee.name);
|
|
361
|
-
if (!isTFunction) return;
|
|
362
|
-
const firstArg = path.node.arguments[0];
|
|
363
|
-
if (!firstArg) return;
|
|
364
|
-
let text = null;
|
|
365
|
-
if (firstArg.type === "StringLiteral") {
|
|
366
|
-
text = firstArg.value;
|
|
367
|
-
} else if (firstArg.type === "TemplateLiteral") {
|
|
368
|
-
text = this.extractTemplateText(firstArg);
|
|
369
|
-
}
|
|
370
|
-
if (!text || text.trim().length === 0) return;
|
|
371
|
-
const secondArg = path.node.arguments[1];
|
|
372
|
-
let context;
|
|
373
|
-
let formality;
|
|
374
|
-
let explicitKey;
|
|
375
|
-
if (secondArg && secondArg.type === "ObjectExpression") {
|
|
376
|
-
secondArg.properties.forEach((prop) => {
|
|
377
|
-
if (prop.type === "ObjectProperty" && prop.key.type === "Identifier") {
|
|
378
|
-
if (prop.key.name === "context" && prop.value.type === "StringLiteral") {
|
|
379
|
-
context = prop.value.value;
|
|
380
|
-
}
|
|
381
|
-
if (prop.key.name === "formality" && prop.value.type === "StringLiteral") {
|
|
382
|
-
formality = prop.value.value;
|
|
383
|
-
}
|
|
384
|
-
if (prop.key.name === "id" && prop.value.type === "StringLiteral") {
|
|
385
|
-
explicitKey = prop.value.value.trim();
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
});
|
|
389
|
-
}
|
|
390
|
-
const line = path.node.loc?.start.line || 0;
|
|
391
|
-
const column = path.node.loc?.start.column || 0;
|
|
392
|
-
const key = explicitKey && explicitKey.length > 0 ? explicitKey : this.generateStableKey({
|
|
393
|
-
filePath: relativeFilePath,
|
|
394
|
-
kind: "t-call",
|
|
395
|
-
line,
|
|
396
|
-
column
|
|
397
|
-
});
|
|
398
|
-
strings.push({
|
|
399
|
-
key,
|
|
400
|
-
text: text.trim(),
|
|
401
|
-
file: filePath,
|
|
402
|
-
line,
|
|
403
|
-
context,
|
|
404
|
-
formality
|
|
405
|
-
});
|
|
406
|
-
},
|
|
407
|
-
// Extract from JSX elements
|
|
408
|
-
JSXElement: (path) => {
|
|
409
|
-
const opening = path.node.openingElement;
|
|
410
|
-
const tagName = opening.name.type === "JSXIdentifier" ? opening.name.name : null;
|
|
411
|
-
if (!tagName) return;
|
|
412
|
-
const isTranslationComponent = vocoderImports.has(tagName);
|
|
413
|
-
if (!isTranslationComponent) return;
|
|
414
|
-
const msgAttribute = this.getStringAttribute(opening.attributes, "msg");
|
|
415
|
-
const text = msgAttribute || this.extractTextContent(path.node.children);
|
|
416
|
-
if (!text || text.trim().length === 0) return;
|
|
417
|
-
const id = this.getStringAttribute(opening.attributes, "id");
|
|
418
|
-
const context = this.getStringAttribute(opening.attributes, "context");
|
|
419
|
-
const formality = this.getStringAttribute(
|
|
420
|
-
opening.attributes,
|
|
421
|
-
"formality"
|
|
422
|
-
);
|
|
423
|
-
const line = path.node.loc?.start.line || 0;
|
|
424
|
-
const column = path.node.loc?.start.column || 0;
|
|
425
|
-
const key = id && id.trim().length > 0 ? id.trim() : this.generateStableKey({
|
|
426
|
-
filePath: relativeFilePath,
|
|
427
|
-
kind: "jsx",
|
|
428
|
-
line,
|
|
429
|
-
column
|
|
430
|
-
});
|
|
431
|
-
strings.push({
|
|
432
|
-
key,
|
|
433
|
-
text: text.trim(),
|
|
434
|
-
file: filePath,
|
|
435
|
-
line,
|
|
436
|
-
context,
|
|
437
|
-
formality
|
|
438
|
-
});
|
|
439
|
-
}
|
|
440
|
-
});
|
|
441
|
-
} catch (error) {
|
|
442
|
-
throw new Error(
|
|
443
|
-
`Failed to parse ${filePath}: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
444
|
-
);
|
|
445
|
-
}
|
|
446
|
-
return strings;
|
|
447
|
-
}
|
|
448
|
-
/**
|
|
449
|
-
* Extract text from template literal
|
|
450
|
-
* Converts template literals like `Hello ${name}` to `Hello {name}`
|
|
451
|
-
*/
|
|
452
|
-
extractTemplateText(node) {
|
|
453
|
-
let text = "";
|
|
454
|
-
for (let i = 0; i < node.quasis.length; i++) {
|
|
455
|
-
const quasi = node.quasis[i];
|
|
456
|
-
text += quasi.value.raw;
|
|
457
|
-
if (i < node.expressions.length) {
|
|
458
|
-
const expr = node.expressions[i];
|
|
459
|
-
if (expr.type === "Identifier") {
|
|
460
|
-
text += `{${expr.name}}`;
|
|
461
|
-
} else {
|
|
462
|
-
text += "{value}";
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
return text;
|
|
467
|
-
}
|
|
468
|
-
/**
|
|
469
|
-
* Extract text content from JSX children
|
|
470
|
-
*/
|
|
471
|
-
extractTextContent(children) {
|
|
472
|
-
let text = "";
|
|
473
|
-
for (const child of children) {
|
|
474
|
-
if (child.type === "JSXText") {
|
|
475
|
-
text += child.value;
|
|
476
|
-
} else if (child.type === "JSXExpressionContainer") {
|
|
477
|
-
const expr = child.expression;
|
|
478
|
-
if (expr.type === "Identifier") {
|
|
479
|
-
text += `{${expr.name}}`;
|
|
480
|
-
} else if (expr.type === "StringLiteral") {
|
|
481
|
-
text += expr.value;
|
|
482
|
-
} else if (expr.type === "TemplateLiteral") {
|
|
483
|
-
text += this.extractTemplateText(expr);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
return text;
|
|
488
|
-
}
|
|
489
|
-
/**
|
|
490
|
-
* Get string value from JSX attribute
|
|
491
|
-
* Handles both string literals and template literals
|
|
492
|
-
*/
|
|
493
|
-
getStringAttribute(attributes, name) {
|
|
494
|
-
const attr = attributes.find(
|
|
495
|
-
(a) => a.type === "JSXAttribute" && a.name.name === name
|
|
496
|
-
);
|
|
497
|
-
if (!attr || !attr.value) return void 0;
|
|
498
|
-
if (attr.value.type === "StringLiteral") {
|
|
499
|
-
return attr.value.value;
|
|
500
|
-
}
|
|
501
|
-
if (attr.value.type === "JSXExpressionContainer") {
|
|
502
|
-
const expr = attr.value.expression;
|
|
503
|
-
if (expr.type === "TemplateLiteral") {
|
|
504
|
-
return this.extractTemplateText(expr);
|
|
505
|
-
}
|
|
506
|
-
if (expr.type === "StringLiteral") {
|
|
507
|
-
return expr.value;
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
return void 0;
|
|
511
|
-
}
|
|
512
|
-
/**
|
|
513
|
-
* Deduplicate strings (keep first occurrence)
|
|
514
|
-
*/
|
|
515
|
-
deduplicateStrings(strings) {
|
|
516
|
-
const seen = /* @__PURE__ */ new Map();
|
|
517
|
-
const unique = [];
|
|
518
|
-
for (const str of strings) {
|
|
519
|
-
const dedupeKey = `${str.text}|${str.context || ""}|${str.formality || ""}`;
|
|
520
|
-
const existingIndex = seen.get(dedupeKey);
|
|
521
|
-
if (existingIndex === void 0) {
|
|
522
|
-
seen.set(dedupeKey, unique.length);
|
|
523
|
-
unique.push(str);
|
|
524
|
-
continue;
|
|
525
|
-
}
|
|
526
|
-
const existing = unique[existingIndex];
|
|
527
|
-
if (existing && str.key < existing.key) {
|
|
528
|
-
existing.key = str.key;
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
return unique;
|
|
532
|
-
}
|
|
533
|
-
generateStableKey(params) {
|
|
534
|
-
const payload = `${params.filePath}|${params.kind}|${params.line}:${params.column}`;
|
|
535
|
-
const digest = createHash("sha1").update(payload).digest("hex");
|
|
536
|
-
return `SK_${digest.slice(0, 24).toUpperCase()}`;
|
|
537
|
-
}
|
|
538
|
-
};
|
|
539
|
-
|
|
540
|
-
export {
|
|
541
|
-
detectLocalEcosystem,
|
|
542
|
-
buildInstallCommand,
|
|
543
|
-
getPackagesToInstall,
|
|
544
|
-
getSetupSnippets,
|
|
545
|
-
StringExtractor
|
|
546
|
-
};
|
|
547
|
-
//# sourceMappingURL=chunk-KPIT5ETY.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/detect-local.ts","../src/utils/setup-snippets.ts","../src/utils/extract.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\n\nexport type PackageManager = 'pnpm' | 'npm' | 'yarn' | 'bun';\n\nexport type DetectedFramework =\n | 'nextjs'\n | 'vite'\n | 'remix'\n | 'nuxt'\n | 'sveltekit'\n | 'gatsby'\n | 'angular'\n | null;\n\nexport type DetectedEcosystem =\n | 'react'\n | 'vue'\n | 'svelte'\n | 'angular'\n | null;\n\nexport interface LocalDetectionResult {\n ecosystem: DetectedEcosystem;\n framework: DetectedFramework;\n packageManager: PackageManager;\n uiPackage: string | null;\n hasUnplugin: boolean;\n hasUiPackage: boolean;\n sourceLocale: string | null;\n}\n\n/**\n * Detect the local project's ecosystem, framework, and package manager\n * by inspecting filesystem artifacts. No network calls.\n */\nexport function detectLocalEcosystem(cwd: string = process.cwd()): LocalDetectionResult {\n const packageManager = detectPackageManager(cwd);\n const pkg = readPackageJson(cwd);\n\n if (!pkg) {\n return {\n ecosystem: null,\n framework: null,\n packageManager,\n uiPackage: null,\n hasUnplugin: false,\n hasUiPackage: false,\n sourceLocale: null,\n };\n }\n\n const allDeps = {\n ...((pkg.dependencies as Record<string, string>) ?? {}),\n ...((pkg.devDependencies as Record<string, string>) ?? {}),\n };\n\n const hasUnplugin = '@vocoder/unplugin' in allDeps;\n\n // Detect ecosystem + framework\n const { ecosystem, framework, uiPackage } = detectFromDeps(allDeps, cwd);\n const hasUiPackage = uiPackage !== null && uiPackage in allDeps;\n\n return {\n ecosystem,\n framework,\n packageManager,\n uiPackage,\n hasUnplugin,\n hasUiPackage,\n sourceLocale: null,\n };\n}\n\nfunction detectPackageManager(cwd: string): PackageManager {\n if (existsSync(join(cwd, 'pnpm-lock.yaml'))) return 'pnpm';\n if (existsSync(join(cwd, 'bun.lockb')) || existsSync(join(cwd, 'bun.lock'))) return 'bun';\n if (existsSync(join(cwd, 'yarn.lock'))) return 'yarn';\n return 'npm';\n}\n\nfunction readPackageJson(cwd: string): Record<string, unknown> | null {\n const pkgPath = join(cwd, 'package.json');\n if (!existsSync(pkgPath)) return null;\n try {\n return JSON.parse(readFileSync(pkgPath, 'utf-8')) as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\nfunction detectFromDeps(\n allDeps: Record<string, string>,\n cwd: string,\n): { ecosystem: DetectedEcosystem; framework: DetectedFramework; uiPackage: string | null } {\n // Vue ecosystem\n if ('vue' in allDeps) {\n const framework = 'nuxt' in allDeps ? 'nuxt' as const : null;\n return { ecosystem: 'vue', framework, uiPackage: '@vocoder/vue' };\n }\n\n // Svelte ecosystem\n if ('svelte' in allDeps) {\n const framework = '@sveltejs/kit' in allDeps ? 'sveltekit' as const : null;\n return { ecosystem: 'svelte', framework, uiPackage: '@vocoder/svelte' };\n }\n\n // Angular ecosystem\n if ('@angular/core' in allDeps || existsSync(join(cwd, 'angular.json'))) {\n return { ecosystem: 'angular', framework: 'angular', uiPackage: '@vocoder/angular' };\n }\n\n // React ecosystem (most common — check last)\n if ('react' in allDeps) {\n let framework: DetectedFramework = null;\n if ('next' in allDeps) framework = 'nextjs';\n else if ('@remix-run/react' in allDeps) framework = 'remix';\n else if ('gatsby' in allDeps) framework = 'gatsby';\n else if ('vite' in allDeps) framework = 'vite';\n return { ecosystem: 'react', framework, uiPackage: '@vocoder/react' };\n }\n\n return { ecosystem: null, framework: null, uiPackage: null };\n}\n\n/**\n * Build the install command for packages that aren't already installed.\n */\nexport function buildInstallCommand(\n packageManager: PackageManager,\n packages: string[],\n): string {\n if (packages.length === 0) return '';\n const pkgList = packages.join(' ');\n switch (packageManager) {\n case 'pnpm':\n return `pnpm add ${pkgList}`;\n case 'yarn':\n return `yarn add ${pkgList}`;\n case 'bun':\n return `bun add ${pkgList}`;\n default:\n return `npm install ${pkgList}`;\n }\n}\n\n/**\n * Get the list of packages that need to be installed.\n */\nexport function getPackagesToInstall(detection: LocalDetectionResult): string[] {\n const packages: string[] = [];\n if (!detection.hasUnplugin) packages.push('@vocoder/unplugin');\n if (detection.uiPackage && !detection.hasUiPackage) packages.push(detection.uiPackage);\n return packages;\n}\n","import type { DetectedEcosystem, DetectedFramework } from './detect-local.js';\n\nexport interface SetupSnippets {\n pluginStep: { file: string; code: string } | null;\n providerStep: { file: string; code: string } | null;\n wrapStep: { code: string };\n whatsNext: string;\n}\n\n/**\n * Generate framework-specific setup snippets.\n */\nexport function getSetupSnippets(params: {\n framework: DetectedFramework;\n ecosystem: DetectedEcosystem;\n sourceLocale: string;\n branchTriggers: Array<{ pattern: string; triggers: string[] }>;\n}): SetupSnippets {\n const { framework, ecosystem, sourceLocale, branchTriggers } = params;\n // Collect the union of all triggers across all branch configs\n const allTriggers = [...new Set(branchTriggers.flatMap((b) => b.triggers))];\n\n return {\n pluginStep: getPluginSnippet(framework, ecosystem),\n providerStep: getProviderSnippet(ecosystem, sourceLocale),\n wrapStep: getWrapSnippet(ecosystem),\n whatsNext: getWhatsNextMessage(allTriggers),\n };\n}\n\nfunction getPluginSnippet(\n framework: DetectedFramework,\n ecosystem: DetectedEcosystem,\n): { file: string; code: string } | null {\n switch (framework) {\n case 'nextjs':\n return {\n file: 'next.config.ts',\n code: `import { withVocoder } from '@vocoder/unplugin/next';\n\nexport default withVocoder({\n // your existing Next.js config\n});`,\n };\n\n case 'vite':\n case 'remix':\n return {\n file: 'vite.config.ts',\n code: `import vocoder from '@vocoder/unplugin/vite';\n\nexport default defineConfig({\n plugins: [\n vocoder(),\n // your other plugins\n ],\n});`,\n };\n\n case 'nuxt':\n return {\n file: 'nuxt.config.ts',\n code: `import vocoder from '@vocoder/unplugin/vite';\n\nexport default defineNuxtConfig({\n vite: {\n plugins: [vocoder()],\n },\n});`,\n };\n\n case 'sveltekit':\n return {\n file: 'vite.config.ts',\n code: `import vocoder from '@vocoder/unplugin/vite';\nimport { sveltekit } from '@sveltejs/kit/vite';\n\nexport default defineConfig({\n plugins: [\n sveltekit(),\n vocoder(),\n ],\n});`,\n };\n\n case 'gatsby':\n return {\n file: 'gatsby-node.js',\n code: `const vocoder = require('@vocoder/unplugin/webpack');\n\nexports.onCreateWebpackConfig = ({ actions }) => {\n actions.setWebpackConfig({\n plugins: [vocoder()],\n });\n};`,\n };\n\n case 'angular':\n return null; // Angular CLI doesn't expose plugin config easily\n\n default:\n // No known framework — if they have React/Vue/Svelte, they likely have a bundler\n // but we can't guess which config file. Give generic advice.\n if (ecosystem) {\n return {\n file: 'your bundler config',\n code: `// Vite\nimport vocoder from '@vocoder/unplugin/vite';\n// Webpack\nconst vocoder = require('@vocoder/unplugin/webpack');\n\n// Add vocoder() to your plugins array`,\n };\n }\n return null;\n }\n}\n\nfunction getProviderSnippet(\n ecosystem: DetectedEcosystem,\n sourceLocale: string,\n): { file: string; code: string } | null {\n switch (ecosystem) {\n case 'react':\n return {\n file: 'your root layout or App component',\n code: `import { VocoderProvider } from '@vocoder/react';\n\n<VocoderProvider defaultLocale=\"${sourceLocale}\">\n {children}\n</VocoderProvider>`,\n };\n\n case 'vue':\n return {\n file: 'your app entry',\n code: `import { createVocoder } from '@vocoder/vue';\n\nconst vocoder = createVocoder({\n defaultLocale: '${sourceLocale}',\n});\n\napp.use(vocoder);`,\n };\n\n case 'svelte':\n return {\n file: 'your root layout',\n code: `<script>\n import { VocoderProvider } from '@vocoder/svelte';\n</script>\n\n<VocoderProvider defaultLocale=\"${sourceLocale}\">\n <slot />\n</VocoderProvider>`,\n };\n\n default:\n return null;\n }\n}\n\nfunction getWrapSnippet(ecosystem: DetectedEcosystem): { code: string } {\n switch (ecosystem) {\n case 'react':\n return {\n code: `import { T } from '@vocoder/react';\n\n<T>Hello, world!</T>`,\n };\n\n case 'vue':\n return {\n code: `<template>\n <T>Hello, world!</T>\n</template>\n\n<script setup>\nimport { T } from '@vocoder/vue';\n</script>`,\n };\n\n case 'svelte':\n return {\n code: `<script>\n import { T } from '@vocoder/svelte';\n</script>\n\n<T>Hello, world!</T>`,\n };\n\n default:\n return {\n code: `// Wrap translatable strings with <T>\n<T>Hello, world!</T>`,\n };\n }\n}\n\nfunction getWhatsNextMessage(triggers: string[]): string {\n const parts: string[] = [];\n\n if (triggers.includes('push')) {\n parts.push('Push to a target branch to trigger translations.');\n }\n if (triggers.includes('pull_request')) {\n parts.push('Open a pull request to trigger translations.');\n }\n if (triggers.includes('manual')) {\n parts.push('Run `vocoder sync` to extract and translate.');\n }\n\n // Fallback for unknown or empty triggers\n if (parts.length === 0) {\n parts.push('Push to a target branch to trigger translations.');\n }\n\n return parts.join('\\n');\n}\n","import { createHash } from 'crypto';\nimport { readFileSync } from 'fs';\nimport { parse } from '@babel/parser';\nimport babelTraverse from '@babel/traverse';\nimport { glob } from 'glob';\nimport { relative as pathRelative } from 'path';\nimport type { ExtractedString } from '../types.js';\n\n// Handle default export difference between ESM and CommonJS\nconst traverse = (babelTraverse as any).default || babelTraverse;\n\n/**\n * Extract translatable strings from source files\n *\n * NOTE: This is a simplified version for the CLI MVP.\n * Eventually this logic should be moved to a shared @vocoder/extraction package\n * that can be used by both the CLI and the backend.\n */\nexport class StringExtractor {\n /**\n * Extract strings from all files matching the pattern(s)\n *\n * @param pattern - Glob pattern(s) to include\n * @param projectRoot - Project root directory\n * @param excludePattern - Glob pattern(s) to exclude (optional)\n */\n async extractFromProject(\n pattern: string | string[],\n projectRoot: string = process.cwd(),\n excludePattern?: string | string[],\n ): Promise<ExtractedString[]> {\n // Normalize patterns to arrays\n const includePatterns = Array.isArray(pattern) ? pattern : [pattern];\n\n // Default ignore patterns (always excluded)\n const defaultIgnore = ['**/node_modules/**', '**/.next/**', '**/dist/**', '**/build/**'];\n\n // Merge user exclude patterns with defaults\n const ignorePatterns = excludePattern\n ? [...defaultIgnore, ...(Array.isArray(excludePattern) ? excludePattern : [excludePattern])]\n : defaultIgnore;\n\n // Find all files matching any of the include patterns (OR behavior)\n const allFiles = new Set<string>();\n\n for (const includePattern of includePatterns) {\n const files = await glob(includePattern, {\n cwd: projectRoot,\n absolute: true,\n ignore: ignorePatterns,\n });\n\n files.forEach((file: string) => allFiles.add(file));\n }\n\n const allStrings: ExtractedString[] = [];\n\n // Extract from each file\n const sortedFiles = Array.from(allFiles).sort();\n\n for (const file of sortedFiles) {\n try {\n const strings = await this.extractFromFile(file, projectRoot);\n allStrings.push(...strings);\n } catch (error) {\n console.warn(`Warning: Failed to extract from ${file}:`, error);\n }\n }\n\n // Deduplicate strings (same text = one entry)\n const unique = this.deduplicateStrings(allStrings);\n\n return unique;\n }\n\n /**\n * Extract strings from a single file\n */\n private async extractFromFile(\n filePath: string,\n projectRoot: string,\n ): Promise<ExtractedString[]> {\n const code = readFileSync(filePath, 'utf-8');\n const strings: ExtractedString[] = [];\n const relativeFilePath = pathRelative(projectRoot, filePath).split('\\\\').join('/');\n\n try {\n // Parse the code\n const ast = parse(code, {\n sourceType: 'module',\n plugins: ['jsx', 'typescript'],\n });\n\n // Track imports from @vocoder/react\n const vocoderImports = new Map<string, string>();\n const tFunctionNames = new Set<string>(); // Track 't' function names\n\n // Traverse the AST\n traverse(ast, {\n // Track imports of <T> component and t function\n ImportDeclaration: (path: any) => {\n const source = path.node.source.value;\n\n if (source === '@vocoder/react') {\n path.node.specifiers.forEach((spec: any) => {\n if (spec.type === 'ImportSpecifier') {\n const imported =\n spec.imported.type === 'Identifier'\n ? spec.imported.name\n : null;\n const local = spec.local.name;\n\n if (imported === 'T') {\n vocoderImports.set(local, 'T');\n }\n // Track direct import of 't' function\n if (imported === 't') {\n tFunctionNames.add(local);\n }\n // Track useVocoder hook (which provides 't')\n if (imported === 'useVocoder') {\n // We'll track destructured 't' in VariableDeclarator\n }\n }\n });\n }\n },\n\n // Track destructured 't' from useVocoder hook\n VariableDeclarator: (path: any) => {\n const init = path.node.init;\n\n // Check if this is: const { t } = useVocoder()\n if (\n init &&\n init.type === 'CallExpression' &&\n init.callee.type === 'Identifier' &&\n init.callee.name === 'useVocoder' &&\n path.node.id.type === 'ObjectPattern'\n ) {\n path.node.id.properties.forEach((prop: any) => {\n if (\n prop.type === 'ObjectProperty' &&\n prop.key.type === 'Identifier' &&\n prop.key.name === 't'\n ) {\n const localName =\n prop.value.type === 'Identifier' ? prop.value.name : 't';\n tFunctionNames.add(localName);\n }\n });\n }\n },\n\n // Extract from t() function calls\n CallExpression: (path: any) => {\n const callee = path.node.callee;\n\n // Check if this is a call to 't' function\n const isTFunction =\n callee.type === 'Identifier' && tFunctionNames.has(callee.name);\n\n if (!isTFunction) return;\n\n // Get the first argument (the string to translate)\n const firstArg = path.node.arguments[0];\n if (!firstArg) return;\n\n let text: string | null = null;\n\n // Handle string literal: t('Hello')\n if (firstArg.type === 'StringLiteral') {\n text = firstArg.value;\n }\n // Handle template literal: t(`Hello ${name}`)\n else if (firstArg.type === 'TemplateLiteral') {\n text = this.extractTemplateText(firstArg);\n }\n\n if (!text || text.trim().length === 0) return;\n\n // Get options from second argument\n const secondArg = path.node.arguments[1];\n let context: string | undefined;\n let formality: 'formal' | 'informal' | 'neutral' | 'auto' | undefined;\n let explicitKey: string | undefined;\n\n if (secondArg && secondArg.type === 'ObjectExpression') {\n secondArg.properties.forEach((prop: any) => {\n if (prop.type === 'ObjectProperty' && prop.key.type === 'Identifier') {\n if (prop.key.name === 'context' && prop.value.type === 'StringLiteral') {\n context = prop.value.value;\n }\n if (prop.key.name === 'formality' && prop.value.type === 'StringLiteral') {\n formality = prop.value.value as 'formal' | 'informal' | 'neutral' | 'auto';\n }\n if (prop.key.name === 'id' && prop.value.type === 'StringLiteral') {\n explicitKey = prop.value.value.trim();\n }\n }\n });\n }\n\n const line = path.node.loc?.start.line || 0;\n const column = path.node.loc?.start.column || 0;\n const key = explicitKey && explicitKey.length > 0\n ? explicitKey\n : this.generateStableKey({\n filePath: relativeFilePath,\n kind: 't-call',\n line,\n column,\n });\n\n strings.push({\n key,\n text: text.trim(),\n file: filePath,\n line,\n context,\n formality,\n });\n },\n\n // Extract from JSX elements\n JSXElement: (path: any) => {\n const opening = path.node.openingElement;\n const tagName =\n opening.name.type === 'JSXIdentifier'\n ? opening.name.name\n : null;\n\n if (!tagName) return;\n\n // Check if this is a <T> component (or aliased import)\n const isTranslationComponent = vocoderImports.has(tagName);\n\n if (!isTranslationComponent) return;\n\n // Check for msg attribute first (preferred for ICU messages)\n const msgAttribute = this.getStringAttribute(opening.attributes, 'msg');\n\n // Extract text - prefer msg attribute over children\n const text = msgAttribute || this.extractTextContent(path.node.children);\n\n if (!text || text.trim().length === 0) return;\n\n // Extract context and formality from props\n const id = this.getStringAttribute(opening.attributes, 'id');\n const context = this.getStringAttribute(opening.attributes, 'context');\n const formality = this.getStringAttribute(\n opening.attributes,\n 'formality',\n ) as 'formal' | 'informal' | 'neutral' | 'auto' | undefined;\n const line = path.node.loc?.start.line || 0;\n const column = path.node.loc?.start.column || 0;\n const key = id && id.trim().length > 0\n ? id.trim()\n : this.generateStableKey({\n filePath: relativeFilePath,\n kind: 'jsx',\n line,\n column,\n });\n\n strings.push({\n key,\n text: text.trim(),\n file: filePath,\n line,\n context,\n formality,\n });\n },\n });\n } catch (error) {\n throw new Error(\n `Failed to parse ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`,\n );\n }\n\n return strings;\n }\n\n /**\n * Extract text from template literal\n * Converts template literals like `Hello ${name}` to `Hello {name}`\n */\n private extractTemplateText(node: any): string {\n let text = '';\n\n for (let i = 0; i < node.quasis.length; i++) {\n const quasi = node.quasis[i];\n text += quasi.value.raw;\n\n // Add placeholder for expressions\n if (i < node.expressions.length) {\n const expr = node.expressions[i];\n if (expr.type === 'Identifier') {\n text += `{${expr.name}}`;\n } else {\n // For complex expressions, use generic placeholder\n text += '{value}';\n }\n }\n }\n\n return text;\n }\n\n /**\n * Extract text content from JSX children\n */\n private extractTextContent(children: any[]): string {\n let text = '';\n\n for (const child of children) {\n if (child.type === 'JSXText') {\n text += child.value;\n } else if (child.type === 'JSXExpressionContainer') {\n const expr = child.expression;\n\n // Handle {variableName} - actual identifier\n if (expr.type === 'Identifier') {\n text += `{${expr.name}}`;\n }\n // Handle {\"{variableName}\"} - string literal placeholder\n else if (expr.type === 'StringLiteral') {\n text += expr.value;\n }\n // Handle {`${variableName}`} - template literal\n // Convert template literal syntax to ICU MessageFormat: `$${price}` → ${price}\n else if (expr.type === 'TemplateLiteral') {\n text += this.extractTemplateText(expr);\n }\n }\n }\n\n return text;\n }\n\n /**\n * Get string value from JSX attribute\n * Handles both string literals and template literals\n */\n private getStringAttribute(\n attributes: any[],\n name: string,\n ): string | undefined {\n const attr = attributes.find(\n (a) => a.type === 'JSXAttribute' && a.name.name === name,\n );\n\n if (!attr || !attr.value) return undefined;\n\n // Handle string literal: msg=\"Hello\"\n if (attr.value.type === 'StringLiteral') {\n return attr.value.value;\n }\n\n // Handle JSX expression container: msg={...}\n if (attr.value.type === 'JSXExpressionContainer') {\n const expr = attr.value.expression;\n\n // Handle template literal: msg={`Hello ${name}`}\n if (expr.type === 'TemplateLiteral') {\n return this.extractTemplateText(expr);\n }\n\n // Handle string literal in expression: msg={'Hello'}\n if (expr.type === 'StringLiteral') {\n return expr.value;\n }\n }\n\n return undefined;\n }\n\n /**\n * Deduplicate strings (keep first occurrence)\n */\n private deduplicateStrings(strings: ExtractedString[]): ExtractedString[] {\n const seen = new Map<string, number>();\n const unique: ExtractedString[] = [];\n\n for (const str of strings) {\n // Create a key based on text + context + formality\n const dedupeKey = `${str.text}|${str.context || ''}|${str.formality || ''}`;\n\n const existingIndex = seen.get(dedupeKey);\n if (existingIndex === undefined) {\n seen.set(dedupeKey, unique.length);\n unique.push(str);\n continue;\n }\n\n // Keep the lexicographically smallest key for deterministic stability\n const existing = unique[existingIndex];\n if (existing && str.key < existing.key) {\n existing.key = str.key;\n }\n }\n\n return unique;\n }\n\n private generateStableKey(params: {\n filePath: string;\n kind: 'jsx' | 't-call';\n line: number;\n column: number;\n }): string {\n const payload = `${params.filePath}|${params.kind}|${params.line}:${params.column}`;\n const digest = createHash('sha1').update(payload).digest('hex');\n return `SK_${digest.slice(0, 24).toUpperCase()}`;\n }\n}\n"],"mappings":"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","names":["readFileSync"]}
|