@fluenti/cli 0.1.1 → 0.1.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.
- package/dist/catalog.d.ts +6 -2
- package/dist/catalog.d.ts.map +1 -1
- package/dist/cli.cjs +18 -6
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +411 -236
- package/dist/cli.js.map +1 -1
- package/dist/compile-BJdEF9QX.js +357 -0
- package/dist/compile-BJdEF9QX.js.map +1 -0
- package/dist/compile-d4Q8bND5.cjs +16 -0
- package/dist/compile-d4Q8bND5.cjs.map +1 -0
- package/dist/compile.d.ts +16 -1
- package/dist/compile.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +24 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/json-format.d.ts.map +1 -1
- package/dist/migrate.d.ts +36 -0
- package/dist/migrate.d.ts.map +1 -1
- package/dist/po-format.d.ts.map +1 -1
- package/dist/stats-format.d.ts +20 -0
- package/dist/stats-format.d.ts.map +1 -0
- package/dist/translate.d.ts +4 -0
- package/dist/translate.d.ts.map +1 -1
- package/dist/tsx-extractor-DZrY1LMS.js +268 -0
- package/dist/tsx-extractor-DZrY1LMS.js.map +1 -0
- package/dist/tsx-extractor-LEAVCuX9.cjs +2 -0
- package/dist/tsx-extractor-LEAVCuX9.cjs.map +1 -0
- package/dist/tsx-extractor.d.ts.map +1 -1
- package/dist/vue-extractor-BlHc3vzt.cjs +3 -0
- package/dist/vue-extractor-BlHc3vzt.cjs.map +1 -0
- package/dist/vue-extractor-iUl6SUkv.js +210 -0
- package/dist/vue-extractor-iUl6SUkv.js.map +1 -0
- package/package.json +2 -2
- package/dist/compile-DK1UYkah.cjs +0 -13
- package/dist/compile-DK1UYkah.cjs.map +0 -1
- package/dist/compile-DuHUSzlx.js +0 -747
- package/dist/compile-DuHUSzlx.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,15 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { t as e } from "./tsx-extractor-DZrY1LMS.js";
|
|
3
|
+
import { a as t, c as n, i as r, l as i, n as a, o, r as s, s as c, t as l } from "./compile-BJdEF9QX.js";
|
|
3
4
|
import { defineCommand as u, runMain as d } from "citty";
|
|
4
5
|
import f from "consola";
|
|
5
6
|
import p from "fast-glob";
|
|
6
|
-
import {
|
|
7
|
-
import { dirname as
|
|
7
|
+
import { appendFileSync as m, existsSync as h, mkdirSync as g, readFileSync as _, writeFileSync as v } from "node:fs";
|
|
8
|
+
import { dirname as ee, extname as y, join as b, resolve as x } from "node:path";
|
|
8
9
|
import { execFile as S } from "node:child_process";
|
|
9
10
|
import { promisify as C } from "node:util";
|
|
11
|
+
//#region src/stats-format.ts
|
|
12
|
+
var w = "█", T = "░";
|
|
13
|
+
function E(e, t = 20) {
|
|
14
|
+
let n = Math.max(0, Math.min(100, e)), r = Math.round(n / 100 * t);
|
|
15
|
+
return w.repeat(r) + T.repeat(t - r);
|
|
16
|
+
}
|
|
17
|
+
function D(e) {
|
|
18
|
+
let t = e.toFixed(1) + "%";
|
|
19
|
+
return e >= 90 ? `\x1b[32m${t}\x1b[0m` : e >= 70 ? `\x1b[33m${t}\x1b[0m` : `\x1b[31m${t}\x1b[0m`;
|
|
20
|
+
}
|
|
21
|
+
function O(e, t, n) {
|
|
22
|
+
let r = t > 0 ? n / t * 100 : 0, i = t > 0 ? D(r) : "—", a = t > 0 ? E(r) : "";
|
|
23
|
+
return ` ${e.padEnd(8)}│ ${String(t).padStart(5)} │ ${String(n).padStart(10)} │ ${a} ${i}`;
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
10
26
|
//#region src/translate.ts
|
|
11
|
-
var
|
|
12
|
-
function
|
|
27
|
+
var k = C(S);
|
|
28
|
+
function te(e, t, n) {
|
|
13
29
|
let r = JSON.stringify(n, null, 2);
|
|
14
30
|
return [
|
|
15
31
|
`You are a professional translator. Translate the following messages from "${e}" to "${t}".`,
|
|
@@ -24,14 +40,14 @@ function T(e, t, n) {
|
|
|
24
40
|
"- Do not add any explanation or markdown formatting, output raw JSON only."
|
|
25
41
|
].join("\n");
|
|
26
42
|
}
|
|
27
|
-
async function
|
|
43
|
+
async function ne(e, t) {
|
|
28
44
|
let n = 10 * 1024 * 1024;
|
|
29
45
|
try {
|
|
30
46
|
if (e === "claude") {
|
|
31
|
-
let { stdout: e } = await
|
|
47
|
+
let { stdout: e } = await k("claude", ["-p", t], { maxBuffer: n });
|
|
32
48
|
return e;
|
|
33
49
|
} else {
|
|
34
|
-
let { stdout: e } = await
|
|
50
|
+
let { stdout: e } = await k("codex", [
|
|
35
51
|
"-p",
|
|
36
52
|
t,
|
|
37
53
|
"--full-auto"
|
|
@@ -42,19 +58,19 @@ async function E(e, t) {
|
|
|
42
58
|
throw t.code === "ENOENT" ? Error(`"${e}" CLI not found. Please install it first:\n` + (e === "claude" ? " npm install -g @anthropic-ai/claude-code" : " npm install -g @openai/codex")) : t;
|
|
43
59
|
}
|
|
44
60
|
}
|
|
45
|
-
function
|
|
61
|
+
function A(e) {
|
|
46
62
|
let t = e.match(/\{[\s\S]*\}/);
|
|
47
63
|
if (!t) throw Error("No JSON object found in AI response");
|
|
48
64
|
let n = JSON.parse(t[0]);
|
|
49
65
|
if (typeof n != "object" || !n || Array.isArray(n)) throw Error("AI response is not a valid JSON object");
|
|
50
66
|
return n;
|
|
51
67
|
}
|
|
52
|
-
function
|
|
68
|
+
function j(e) {
|
|
53
69
|
let t = {};
|
|
54
70
|
for (let [n, r] of Object.entries(e)) r.obsolete || (!r.translation || r.translation.length === 0) && (t[n] = r.message ?? n);
|
|
55
71
|
return t;
|
|
56
72
|
}
|
|
57
|
-
function
|
|
73
|
+
function M(e, t) {
|
|
58
74
|
let n = Object.keys(e), r = [];
|
|
59
75
|
for (let i = 0; i < n.length; i += t) {
|
|
60
76
|
let a = {};
|
|
@@ -63,31 +79,31 @@ function k(e, t) {
|
|
|
63
79
|
}
|
|
64
80
|
return r;
|
|
65
81
|
}
|
|
66
|
-
async function
|
|
67
|
-
let { provider: t, sourceLocale: n, targetLocale: r, catalog: i, batchSize: a } = e, o =
|
|
82
|
+
async function N(e) {
|
|
83
|
+
let { provider: t, sourceLocale: n, targetLocale: r, catalog: i, batchSize: a } = e, o = j(i), s = Object.keys(o).length;
|
|
68
84
|
if (s === 0) return {
|
|
69
|
-
catalog: i,
|
|
85
|
+
catalog: { ...i },
|
|
70
86
|
translated: 0
|
|
71
87
|
};
|
|
72
88
|
f.info(` ${s} untranslated messages, translating with ${t}...`);
|
|
73
|
-
let c =
|
|
74
|
-
for (let e = 0; e <
|
|
75
|
-
let
|
|
76
|
-
|
|
77
|
-
let
|
|
78
|
-
for (let e of
|
|
79
|
-
...
|
|
80
|
-
translation:
|
|
81
|
-
},
|
|
89
|
+
let c = { ...i }, l = M(o, a), u = 0;
|
|
90
|
+
for (let e = 0; e < l.length; e++) {
|
|
91
|
+
let i = l[e], a = Object.keys(i);
|
|
92
|
+
l.length > 1 && f.info(` Batch ${e + 1}/${l.length} (${a.length} messages)`);
|
|
93
|
+
let o = A(await ne(t, te(n, r, i)));
|
|
94
|
+
for (let e of a) o[e] && typeof o[e] == "string" ? (c[e] = {
|
|
95
|
+
...c[e],
|
|
96
|
+
translation: o[e]
|
|
97
|
+
}, u++) : f.warn(` Missing translation for key: ${e}`);
|
|
82
98
|
}
|
|
83
99
|
return {
|
|
84
|
-
catalog:
|
|
85
|
-
translated:
|
|
100
|
+
catalog: c,
|
|
101
|
+
translated: u
|
|
86
102
|
};
|
|
87
103
|
}
|
|
88
104
|
//#endregion
|
|
89
105
|
//#region src/migrate.ts
|
|
90
|
-
var
|
|
106
|
+
var P = C(S), F = {
|
|
91
107
|
"vue-i18n": {
|
|
92
108
|
name: "vue-i18n",
|
|
93
109
|
framework: "Vue",
|
|
@@ -227,29 +243,29 @@ var j = C(S), M = {
|
|
|
227
243
|
],
|
|
228
244
|
migrationGuide: "react/llms-migration.txt"
|
|
229
245
|
}
|
|
230
|
-
},
|
|
231
|
-
function
|
|
246
|
+
}, I = Object.keys(F);
|
|
247
|
+
function L(e) {
|
|
232
248
|
let t = e.toLowerCase().replace(/^@nuxtjs\//, "nuxt-").replace(/^@/, "");
|
|
233
|
-
return
|
|
249
|
+
return I.find((e) => e === t);
|
|
234
250
|
}
|
|
235
|
-
async function
|
|
251
|
+
async function R(e) {
|
|
236
252
|
let t = {
|
|
237
253
|
configFiles: [],
|
|
238
254
|
localeFiles: [],
|
|
239
255
|
sampleSources: [],
|
|
240
256
|
packageJson: void 0
|
|
241
257
|
}, n = x("package.json");
|
|
242
|
-
|
|
258
|
+
h(n) && (t.packageJson = _(n, "utf-8"));
|
|
243
259
|
for (let n of e.configPatterns) {
|
|
244
260
|
let e = x(n);
|
|
245
|
-
|
|
261
|
+
h(e) && t.configFiles.push({
|
|
246
262
|
path: n,
|
|
247
|
-
content:
|
|
263
|
+
content: _(e, "utf-8")
|
|
248
264
|
});
|
|
249
265
|
}
|
|
250
266
|
let r = await p(e.localePatterns, { absolute: !1 });
|
|
251
267
|
for (let e of r.slice(0, 10)) {
|
|
252
|
-
let n =
|
|
268
|
+
let n = _(x(e), "utf-8");
|
|
253
269
|
t.localeFiles.push({
|
|
254
270
|
path: e,
|
|
255
271
|
content: n.length > 5e3 ? n.slice(0, 5e3) + "\n... (truncated)" : n
|
|
@@ -257,7 +273,7 @@ async function F(e) {
|
|
|
257
273
|
}
|
|
258
274
|
let i = await p(e.sourcePatterns, { absolute: !1 });
|
|
259
275
|
for (let e of i.slice(0, 5)) {
|
|
260
|
-
let n =
|
|
276
|
+
let n = _(x(e), "utf-8");
|
|
261
277
|
t.sampleSources.push({
|
|
262
278
|
path: e,
|
|
263
279
|
content: n.length > 3e3 ? n.slice(0, 3e3) + "\n... (truncated)" : n
|
|
@@ -265,16 +281,16 @@ async function F(e) {
|
|
|
265
281
|
}
|
|
266
282
|
return t;
|
|
267
283
|
}
|
|
268
|
-
function
|
|
284
|
+
function z(e) {
|
|
269
285
|
let t = [
|
|
270
286
|
x("node_modules", "@fluenti", "cli", "..", "..", e),
|
|
271
287
|
b(__dirname, "..", "..", "..", e),
|
|
272
288
|
b(__dirname, "..", "..", e)
|
|
273
289
|
];
|
|
274
|
-
for (let e of t) if (
|
|
290
|
+
for (let e of t) if (h(e)) return _(e, "utf-8");
|
|
275
291
|
return "";
|
|
276
292
|
}
|
|
277
|
-
function
|
|
293
|
+
function B(e, t, n) {
|
|
278
294
|
let r = [];
|
|
279
295
|
if (r.push(`You are a migration assistant helping convert a ${e.framework} project from "${e.name}" to Fluenti (@fluenti).`, "", "Your task:", "1. Generate a `fluenti.config.ts` file based on the existing i18n configuration", "2. Convert each locale/translation file to Fluenti PO format", "3. List the code changes needed (file by file) to migrate source code from the old API to Fluenti API", ""), n && r.push("=== MIGRATION GUIDE ===", n, ""), t.packageJson && r.push("=== package.json ===", t.packageJson, ""), t.configFiles.length > 0) {
|
|
280
296
|
r.push("=== EXISTING CONFIG FILES ===");
|
|
@@ -290,14 +306,14 @@ function L(e, t, n) {
|
|
|
290
306
|
}
|
|
291
307
|
return r.push("", "=== OUTPUT FORMAT ===", "Respond with the following sections, each starting with the exact header shown:", "", "### FLUENTI_CONFIG", "```ts", "// The fluenti.config.ts content", "```", "", "### LOCALE_FILES", "For each locale file, output:", "#### LOCALE: {locale_code}", "```po", "// The PO file content", "```", "", "### MIGRATION_STEPS", "A numbered checklist of specific code changes needed, with before/after examples.", "", "### INSTALL_COMMANDS", "```bash", "// The install and uninstall commands", "```"), r.join("\n");
|
|
292
308
|
}
|
|
293
|
-
async function
|
|
309
|
+
async function V(e, t) {
|
|
294
310
|
let n = 10 * 1024 * 1024;
|
|
295
311
|
try {
|
|
296
312
|
if (e === "claude") {
|
|
297
|
-
let { stdout: e } = await
|
|
313
|
+
let { stdout: e } = await P("claude", ["-p", t], { maxBuffer: n });
|
|
298
314
|
return e;
|
|
299
315
|
} else {
|
|
300
|
-
let { stdout: e } = await
|
|
316
|
+
let { stdout: e } = await P("codex", [
|
|
301
317
|
"-p",
|
|
302
318
|
t,
|
|
303
319
|
"--full-auto"
|
|
@@ -308,7 +324,7 @@ async function R(e, t) {
|
|
|
308
324
|
throw t.code === "ENOENT" ? Error(`"${e}" CLI not found. Please install it first:\n` + (e === "claude" ? " npm install -g @anthropic-ai/claude-code" : " npm install -g @openai/codex")) : t;
|
|
309
325
|
}
|
|
310
326
|
}
|
|
311
|
-
function
|
|
327
|
+
function H(e) {
|
|
312
328
|
let t = {
|
|
313
329
|
config: void 0,
|
|
314
330
|
localeFiles: [],
|
|
@@ -329,24 +345,24 @@ function z(e) {
|
|
|
329
345
|
let a = e.match(/### INSTALL_COMMANDS[\s\S]*?```(?:bash|sh)?\n([\s\S]*?)```/);
|
|
330
346
|
return a && (t.installCommands = a[1].trim()), t;
|
|
331
347
|
}
|
|
332
|
-
async function
|
|
333
|
-
let { from: t, provider: n, write: r } = e, i =
|
|
348
|
+
async function U(e) {
|
|
349
|
+
let { from: t, provider: n, write: r } = e, i = L(t);
|
|
334
350
|
if (!i) {
|
|
335
351
|
f.error(`Unsupported library "${t}". Supported libraries:`);
|
|
336
|
-
for (let e of
|
|
352
|
+
for (let e of I) f.log(` - ${e}`);
|
|
337
353
|
return;
|
|
338
354
|
}
|
|
339
|
-
let a =
|
|
355
|
+
let a = F[i];
|
|
340
356
|
f.info(`Migrating from ${a.name} (${a.framework}) to Fluenti`), f.info("Scanning project for existing i18n files...");
|
|
341
|
-
let o = await
|
|
357
|
+
let o = await R(a);
|
|
342
358
|
if (o.configFiles.length === 0 && o.localeFiles.length === 0) {
|
|
343
359
|
f.warn(`No ${a.name} configuration or locale files found.`), f.info("Make sure you are running this command from the project root directory.");
|
|
344
360
|
return;
|
|
345
361
|
}
|
|
346
362
|
f.info(`Found: ${o.configFiles.length} config file(s), ${o.localeFiles.length} locale file(s), ${o.sampleSources.length} source file(s)`);
|
|
347
|
-
let s =
|
|
363
|
+
let s = z(a.migrationGuide);
|
|
348
364
|
f.info(`Generating migration plan with ${n}...`);
|
|
349
|
-
let c =
|
|
365
|
+
let c = H(await V(n, B(a, o, s)));
|
|
350
366
|
if (c.installCommands && (f.log(""), f.box({
|
|
351
367
|
title: "Install Commands",
|
|
352
368
|
message: c.installCommands
|
|
@@ -374,43 +390,364 @@ async function B(e) {
|
|
|
374
390
|
})), !r && (c.config || c.localeFiles.length > 0) && (f.log(""), f.info("Run with --write to save generated files to disk:"), f.log(` fluenti migrate --from ${t} --write`));
|
|
375
391
|
}
|
|
376
392
|
//#endregion
|
|
393
|
+
//#region src/init.ts
|
|
394
|
+
var W = /^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{1,8})*$/;
|
|
395
|
+
function G(e) {
|
|
396
|
+
if (!W.test(e)) throw Error(`Invalid locale format: "${e}"`);
|
|
397
|
+
return e;
|
|
398
|
+
}
|
|
399
|
+
var K = [
|
|
400
|
+
{
|
|
401
|
+
dep: "next",
|
|
402
|
+
name: "nextjs",
|
|
403
|
+
pluginPackage: "@fluenti/next"
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
dep: "nuxt",
|
|
407
|
+
name: "nuxt",
|
|
408
|
+
pluginPackage: "@fluenti/vue"
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
dep: "@solidjs/start",
|
|
412
|
+
name: "solidstart",
|
|
413
|
+
pluginPackage: "@fluenti/vite-plugin"
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
dep: "vue",
|
|
417
|
+
name: "vue",
|
|
418
|
+
pluginPackage: "@fluenti/vite-plugin"
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
dep: "solid-js",
|
|
422
|
+
name: "solid",
|
|
423
|
+
pluginPackage: "@fluenti/vite-plugin"
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
dep: "react",
|
|
427
|
+
name: "react",
|
|
428
|
+
pluginPackage: "@fluenti/vite-plugin"
|
|
429
|
+
}
|
|
430
|
+
];
|
|
431
|
+
function q(e) {
|
|
432
|
+
for (let t of K) if (t.dep in e) return {
|
|
433
|
+
name: t.name,
|
|
434
|
+
pluginPackage: t.pluginPackage
|
|
435
|
+
};
|
|
436
|
+
return {
|
|
437
|
+
name: "unknown",
|
|
438
|
+
pluginPackage: null
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
function J(e) {
|
|
442
|
+
let t = e.locales.map((e) => `'${e}'`).join(", ");
|
|
443
|
+
return `import { defineConfig } from '@fluenti/cli'
|
|
444
|
+
|
|
445
|
+
export default defineConfig({
|
|
446
|
+
sourceLocale: '${e.sourceLocale}',
|
|
447
|
+
locales: [${t}],
|
|
448
|
+
catalogDir: './locales',
|
|
449
|
+
format: '${e.format}',
|
|
450
|
+
include: ['./src/**/*.{vue,tsx,jsx,ts,js}'],
|
|
451
|
+
compileOutDir: './src/locales/compiled',
|
|
452
|
+
})
|
|
453
|
+
`;
|
|
454
|
+
}
|
|
455
|
+
async function Y(e) {
|
|
456
|
+
let t = x(e.cwd, "package.json");
|
|
457
|
+
if (!h(t)) {
|
|
458
|
+
f.error("No package.json found in current directory.");
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
let n = JSON.parse(_(t, "utf-8")), r = q({
|
|
462
|
+
...n.dependencies,
|
|
463
|
+
...n.devDependencies
|
|
464
|
+
});
|
|
465
|
+
f.info(`Detected framework: ${r.name}`), r.pluginPackage && f.info(`Recommended plugin: ${r.pluginPackage}`);
|
|
466
|
+
let i = x(e.cwd, "fluenti.config.ts");
|
|
467
|
+
if (h(i)) {
|
|
468
|
+
f.warn("fluenti.config.ts already exists. Skipping config generation.");
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
let a = await f.prompt("Source locale?", {
|
|
472
|
+
type: "text",
|
|
473
|
+
default: "en",
|
|
474
|
+
placeholder: "en"
|
|
475
|
+
});
|
|
476
|
+
if (typeof a == "symbol") return;
|
|
477
|
+
let o = await f.prompt("Target locales (comma-separated)?", {
|
|
478
|
+
type: "text",
|
|
479
|
+
default: "ja,zh-CN",
|
|
480
|
+
placeholder: "ja,zh-CN"
|
|
481
|
+
});
|
|
482
|
+
if (typeof o == "symbol") return;
|
|
483
|
+
let s = await f.prompt("Catalog format?", {
|
|
484
|
+
type: "select",
|
|
485
|
+
options: ["po", "json"],
|
|
486
|
+
initial: "po"
|
|
487
|
+
});
|
|
488
|
+
if (typeof s == "symbol") return;
|
|
489
|
+
let c = o.split(",").map((e) => e.trim()).filter(Boolean);
|
|
490
|
+
G(a);
|
|
491
|
+
for (let e of c) G(e);
|
|
492
|
+
v(i, J({
|
|
493
|
+
sourceLocale: a,
|
|
494
|
+
locales: [a, ...c.filter((e) => e !== a)],
|
|
495
|
+
format: s
|
|
496
|
+
}), "utf-8"), f.success("Created fluenti.config.ts");
|
|
497
|
+
let l = x(e.cwd, ".gitignore"), u = "src/locales/compiled/";
|
|
498
|
+
h(l) ? _(l, "utf-8").includes(u) || (m(l, `\n# Fluenti compiled catalogs\n${u}\n`), f.success("Updated .gitignore")) : (v(l, `# Fluenti compiled catalogs\n${u}\n`), f.success("Created .gitignore"));
|
|
499
|
+
let d = n.scripts ?? {}, p = {}, g = !1;
|
|
500
|
+
if (d["i18n:extract"] || (p["i18n:extract"] = "fluenti extract", g = !0), d["i18n:compile"] || (p["i18n:compile"] = "fluenti compile", g = !0), g) {
|
|
501
|
+
let e = {
|
|
502
|
+
...n,
|
|
503
|
+
scripts: {
|
|
504
|
+
...d,
|
|
505
|
+
...p
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
v(t, JSON.stringify(e, null, 2) + "\n", "utf-8"), f.success("Added i18n:extract and i18n:compile scripts to package.json");
|
|
509
|
+
}
|
|
510
|
+
f.log(""), f.box({
|
|
511
|
+
title: "Next steps",
|
|
512
|
+
message: [
|
|
513
|
+
r.pluginPackage ? `1. Install: pnpm add -D ${r.pluginPackage} @fluenti/cli` : "1. Install: pnpm add -D @fluenti/cli",
|
|
514
|
+
r.name === "nextjs" ? "2. Add withFluenti() to your next.config.ts" : r.name === "unknown" ? "2. Configure your build tool to use @fluenti/vite-plugin or @fluenti/next" : "2. Add fluentiPlugin() to your vite.config.ts",
|
|
515
|
+
"3. Run: npx fluenti extract",
|
|
516
|
+
"4. Translate your messages",
|
|
517
|
+
"5. Run: npx fluenti compile"
|
|
518
|
+
].join("\n")
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
//#endregion
|
|
377
522
|
//#region src/cli.ts
|
|
378
|
-
var
|
|
523
|
+
var X = {
|
|
379
524
|
sourceLocale: "en",
|
|
380
525
|
locales: ["en"],
|
|
381
526
|
catalogDir: "./locales",
|
|
382
527
|
format: "po",
|
|
383
528
|
include: ["./src/**/*.{vue,tsx,jsx,ts,js}"],
|
|
384
|
-
compileOutDir: "./locales/compiled"
|
|
529
|
+
compileOutDir: "./src/locales/compiled"
|
|
385
530
|
};
|
|
386
|
-
async function
|
|
531
|
+
async function Z(e) {
|
|
387
532
|
let t = e ? [x(e)] : [
|
|
388
533
|
x("fluenti.config.ts"),
|
|
389
534
|
x("fluenti.config.js"),
|
|
390
535
|
x("fluenti.config.mjs")
|
|
391
536
|
];
|
|
392
|
-
for (let e of t) if (
|
|
537
|
+
for (let e of t) if (h(e)) try {
|
|
393
538
|
let { createJiti: t } = await import("jiti"), n = await t(import.meta.url).import(e), r = n.default ?? n;
|
|
394
539
|
return {
|
|
395
|
-
...
|
|
540
|
+
...X,
|
|
396
541
|
...r
|
|
397
542
|
};
|
|
398
543
|
} catch {
|
|
399
544
|
f.warn(`Failed to load config from ${e}, using defaults`);
|
|
400
545
|
}
|
|
401
|
-
return
|
|
546
|
+
return X;
|
|
402
547
|
}
|
|
403
|
-
function
|
|
404
|
-
if (!
|
|
405
|
-
let r =
|
|
406
|
-
return
|
|
548
|
+
function Q(e, n) {
|
|
549
|
+
if (!h(e)) return {};
|
|
550
|
+
let r = _(e, "utf-8");
|
|
551
|
+
return n === "json" ? c(r) : t(r);
|
|
407
552
|
}
|
|
408
|
-
function
|
|
409
|
-
|
|
553
|
+
function $(e, t, r) {
|
|
554
|
+
g(ee(e), { recursive: !0 }), v(e, r === "json" ? n(t) : o(t), "utf-8");
|
|
410
555
|
}
|
|
411
|
-
function
|
|
412
|
-
|
|
556
|
+
async function re(t, n) {
|
|
557
|
+
if (y(t) === ".vue") {
|
|
558
|
+
let { extractFromVue: e } = await import("./vue-extractor-iUl6SUkv.js").then((e) => e.n);
|
|
559
|
+
return e(n, t);
|
|
560
|
+
}
|
|
561
|
+
return e(n, t);
|
|
413
562
|
}
|
|
563
|
+
var ie = u({
|
|
564
|
+
meta: {
|
|
565
|
+
name: "extract",
|
|
566
|
+
description: "Extract messages from source files"
|
|
567
|
+
},
|
|
568
|
+
args: {
|
|
569
|
+
config: {
|
|
570
|
+
type: "string",
|
|
571
|
+
description: "Path to config file"
|
|
572
|
+
},
|
|
573
|
+
clean: {
|
|
574
|
+
type: "boolean",
|
|
575
|
+
description: "Remove obsolete entries instead of marking them",
|
|
576
|
+
default: !1
|
|
577
|
+
},
|
|
578
|
+
"no-fuzzy": {
|
|
579
|
+
type: "boolean",
|
|
580
|
+
description: "Strip fuzzy flags from all entries",
|
|
581
|
+
default: !1
|
|
582
|
+
}
|
|
583
|
+
},
|
|
584
|
+
async run({ args: e }) {
|
|
585
|
+
let t = await Z(e.config);
|
|
586
|
+
f.info(`Extracting messages from ${t.include.join(", ")}`);
|
|
587
|
+
let n = await p(t.include), r = [];
|
|
588
|
+
for (let e of n) {
|
|
589
|
+
let t = await re(e, _(e, "utf-8"));
|
|
590
|
+
r.push(...t);
|
|
591
|
+
}
|
|
592
|
+
f.info(`Found ${r.length} messages in ${n.length} files`);
|
|
593
|
+
let a = t.format === "json" ? ".json" : ".po", o = e.clean ?? !1, s = e["no-fuzzy"] ?? !1;
|
|
594
|
+
for (let e of t.locales) {
|
|
595
|
+
let n = x(t.catalogDir, `${e}${a}`), { catalog: c, result: l } = i(Q(n, t.format), r, { stripFuzzy: s });
|
|
596
|
+
$(n, o ? Object.fromEntries(Object.entries(c).filter(([, e]) => !e.obsolete)) : c, t.format);
|
|
597
|
+
let u = o ? `${l.obsolete} removed` : `${l.obsolete} obsolete`;
|
|
598
|
+
f.success(`${e}: ${l.added} added, ${l.unchanged} unchanged, ${u}`);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}), ae = u({
|
|
602
|
+
meta: {
|
|
603
|
+
name: "compile",
|
|
604
|
+
description: "Compile message catalogs to JS modules"
|
|
605
|
+
},
|
|
606
|
+
args: {
|
|
607
|
+
config: {
|
|
608
|
+
type: "string",
|
|
609
|
+
description: "Path to config file"
|
|
610
|
+
},
|
|
611
|
+
"skip-fuzzy": {
|
|
612
|
+
type: "boolean",
|
|
613
|
+
description: "Exclude fuzzy entries from compilation",
|
|
614
|
+
default: !1
|
|
615
|
+
}
|
|
616
|
+
},
|
|
617
|
+
async run({ args: e }) {
|
|
618
|
+
let t = await Z(e.config), n = t.format === "json" ? ".json" : ".po";
|
|
619
|
+
g(t.compileOutDir, { recursive: !0 });
|
|
620
|
+
let i = {};
|
|
621
|
+
for (let e of t.locales) i[e] = Q(x(t.catalogDir, `${e}${n}`), t.format);
|
|
622
|
+
let o = l(i);
|
|
623
|
+
f.info(`Compiling ${o.length} messages across ${t.locales.length} locales`);
|
|
624
|
+
let c = e["skip-fuzzy"] ?? !1;
|
|
625
|
+
for (let e of t.locales) {
|
|
626
|
+
let { code: n, stats: r } = a(i[e], e, o, t.sourceLocale, { skipFuzzy: c }), s = x(t.compileOutDir, `${e}.js`);
|
|
627
|
+
if (v(s, n, "utf-8"), r.missing.length > 0) {
|
|
628
|
+
f.warn(`${e}: ${r.compiled} compiled, ${r.missing.length} missing translations`);
|
|
629
|
+
for (let e of r.missing) f.warn(` ⤷ ${e}`);
|
|
630
|
+
} else f.success(`Compiled ${e}: ${r.compiled} messages → ${s}`);
|
|
631
|
+
}
|
|
632
|
+
let u = s(t.locales, t.compileOutDir), d = x(t.compileOutDir, "index.js");
|
|
633
|
+
v(d, u, "utf-8"), f.success(`Generated index → ${d}`);
|
|
634
|
+
let p = r(o, i, t.sourceLocale), m = x(t.compileOutDir, "messages.d.ts");
|
|
635
|
+
v(m, p, "utf-8"), f.success(`Generated types → ${m}`);
|
|
636
|
+
}
|
|
637
|
+
}), oe = u({
|
|
638
|
+
meta: {
|
|
639
|
+
name: "stats",
|
|
640
|
+
description: "Show translation progress"
|
|
641
|
+
},
|
|
642
|
+
args: { config: {
|
|
643
|
+
type: "string",
|
|
644
|
+
description: "Path to config file"
|
|
645
|
+
} },
|
|
646
|
+
async run({ args: e }) {
|
|
647
|
+
let t = await Z(e.config), n = t.format === "json" ? ".json" : ".po", r = [];
|
|
648
|
+
for (let e of t.locales) {
|
|
649
|
+
let i = Q(x(t.catalogDir, `${e}${n}`), t.format), a = Object.values(i).filter((e) => !e.obsolete), o = a.length, s = a.filter((e) => e.translation && e.translation.length > 0).length, c = o > 0 ? (s / o * 100).toFixed(1) + "%" : "—";
|
|
650
|
+
r.push({
|
|
651
|
+
locale: e,
|
|
652
|
+
total: o,
|
|
653
|
+
translated: s,
|
|
654
|
+
pct: c
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
f.log(""), f.log(" Locale │ Total │ Translated │ Progress"), f.log(" ────────┼───────┼────────────┼─────────────────────────────");
|
|
658
|
+
for (let e of r) f.log(O(e.locale, e.total, e.translated));
|
|
659
|
+
f.log("");
|
|
660
|
+
}
|
|
661
|
+
}), se = u({
|
|
662
|
+
meta: {
|
|
663
|
+
name: "translate",
|
|
664
|
+
description: "Translate messages using AI (Claude Code or Codex CLI)"
|
|
665
|
+
},
|
|
666
|
+
args: {
|
|
667
|
+
config: {
|
|
668
|
+
type: "string",
|
|
669
|
+
description: "Path to config file"
|
|
670
|
+
},
|
|
671
|
+
provider: {
|
|
672
|
+
type: "string",
|
|
673
|
+
description: "AI provider: claude or codex",
|
|
674
|
+
default: "claude"
|
|
675
|
+
},
|
|
676
|
+
locale: {
|
|
677
|
+
type: "string",
|
|
678
|
+
description: "Translate a specific locale only"
|
|
679
|
+
},
|
|
680
|
+
"batch-size": {
|
|
681
|
+
type: "string",
|
|
682
|
+
description: "Messages per batch",
|
|
683
|
+
default: "50"
|
|
684
|
+
}
|
|
685
|
+
},
|
|
686
|
+
async run({ args: e }) {
|
|
687
|
+
let t = await Z(e.config), n = e.provider;
|
|
688
|
+
if (n !== "claude" && n !== "codex") {
|
|
689
|
+
f.error(`Invalid provider "${n}". Use "claude" or "codex".`);
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
let r = parseInt(e["batch-size"] ?? "50", 10);
|
|
693
|
+
if (isNaN(r) || r < 1) {
|
|
694
|
+
f.error("Invalid batch-size. Must be a positive integer.");
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
let i = e.locale ? [e.locale] : t.locales.filter((e) => e !== t.sourceLocale);
|
|
698
|
+
if (i.length === 0) {
|
|
699
|
+
f.warn("No target locales to translate.");
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
f.info(`Translating with ${n} (batch size: ${r})`);
|
|
703
|
+
let a = t.format === "json" ? ".json" : ".po";
|
|
704
|
+
for (let e of i) {
|
|
705
|
+
f.info(`\n[${e}]`);
|
|
706
|
+
let i = x(t.catalogDir, `${e}${a}`), o = Q(i, t.format), { catalog: s, translated: c } = await N({
|
|
707
|
+
provider: n,
|
|
708
|
+
sourceLocale: t.sourceLocale,
|
|
709
|
+
targetLocale: e,
|
|
710
|
+
catalog: o,
|
|
711
|
+
batchSize: r
|
|
712
|
+
});
|
|
713
|
+
c > 0 ? ($(i, s, t.format), f.success(` ${e}: ${c} messages translated`)) : f.success(` ${e}: already fully translated`);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}), ce = u({
|
|
717
|
+
meta: {
|
|
718
|
+
name: "migrate",
|
|
719
|
+
description: "Migrate from another i18n library using AI"
|
|
720
|
+
},
|
|
721
|
+
args: {
|
|
722
|
+
from: {
|
|
723
|
+
type: "string",
|
|
724
|
+
description: "Source library: vue-i18n, nuxt-i18n, react-i18next, next-intl, next-i18next, lingui",
|
|
725
|
+
required: !0
|
|
726
|
+
},
|
|
727
|
+
provider: {
|
|
728
|
+
type: "string",
|
|
729
|
+
description: "AI provider: claude or codex",
|
|
730
|
+
default: "claude"
|
|
731
|
+
},
|
|
732
|
+
write: {
|
|
733
|
+
type: "boolean",
|
|
734
|
+
description: "Write generated files to disk",
|
|
735
|
+
default: !1
|
|
736
|
+
}
|
|
737
|
+
},
|
|
738
|
+
async run({ args: e }) {
|
|
739
|
+
let t = e.provider;
|
|
740
|
+
if (t !== "claude" && t !== "codex") {
|
|
741
|
+
f.error(`Invalid provider "${t}". Use "claude" or "codex".`);
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
await U({
|
|
745
|
+
from: e.from,
|
|
746
|
+
provider: t,
|
|
747
|
+
write: e.write ?? !1
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
});
|
|
414
751
|
d(u({
|
|
415
752
|
meta: {
|
|
416
753
|
name: "fluenti",
|
|
@@ -418,183 +755,21 @@ d(u({
|
|
|
418
755
|
description: "Compile-time i18n for modern frameworks"
|
|
419
756
|
},
|
|
420
757
|
subCommands: {
|
|
421
|
-
|
|
758
|
+
init: u({
|
|
422
759
|
meta: {
|
|
423
|
-
name: "
|
|
424
|
-
description: "
|
|
760
|
+
name: "init",
|
|
761
|
+
description: "Initialize Fluenti in your project"
|
|
425
762
|
},
|
|
426
|
-
args: {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
description: "Path to config file"
|
|
430
|
-
},
|
|
431
|
-
clean: {
|
|
432
|
-
type: "boolean",
|
|
433
|
-
description: "Remove obsolete entries instead of marking them",
|
|
434
|
-
default: !1
|
|
435
|
-
}
|
|
436
|
-
},
|
|
437
|
-
async run({ args: e }) {
|
|
438
|
-
let n = await H(e.config);
|
|
439
|
-
f.info(`Extracting messages from ${n.include.join(", ")}`);
|
|
440
|
-
let r = await p(n.include, { absolute: !0 }), i = [];
|
|
441
|
-
for (let e of r) {
|
|
442
|
-
let t = G(e, g(e, "utf-8"));
|
|
443
|
-
i.push(...t);
|
|
444
|
-
}
|
|
445
|
-
f.info(`Found ${i.length} messages in ${r.length} files`);
|
|
446
|
-
let a = n.format === "json" ? ".json" : ".po", o = e.clean ?? !1;
|
|
447
|
-
for (let e of n.locales) {
|
|
448
|
-
let r = x(n.catalogDir, `${e}${a}`), { catalog: s, result: c } = t(U(r, n.format), i);
|
|
449
|
-
W(r, o ? Object.fromEntries(Object.entries(s).filter(([, e]) => !e.obsolete)) : s, n.format);
|
|
450
|
-
let l = o ? `${c.obsolete} removed` : `${c.obsolete} obsolete`;
|
|
451
|
-
f.success(`${e}: ${c.added} added, ${c.unchanged} unchanged, ${l}`);
|
|
452
|
-
}
|
|
763
|
+
args: {},
|
|
764
|
+
async run() {
|
|
765
|
+
await Y({ cwd: process.cwd() });
|
|
453
766
|
}
|
|
454
767
|
}),
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
args: { config: {
|
|
461
|
-
type: "string",
|
|
462
|
-
description: "Path to config file"
|
|
463
|
-
} },
|
|
464
|
-
async run({ args: e }) {
|
|
465
|
-
let t = await H(e.config), n = t.format === "json" ? ".json" : ".po";
|
|
466
|
-
h(t.compileOutDir, { recursive: !0 });
|
|
467
|
-
let r = {};
|
|
468
|
-
for (let e of t.locales) r[e] = U(x(t.catalogDir, `${e}${n}`), t.format);
|
|
469
|
-
let a = c(r);
|
|
470
|
-
f.info(`Compiling ${a.length} messages across ${t.locales.length} locales`);
|
|
471
|
-
for (let e of t.locales) {
|
|
472
|
-
let { code: n, stats: o } = i(r[e], e, a, t.sourceLocale), s = x(t.compileOutDir, `${e}.js`);
|
|
473
|
-
if (_(s, n, "utf-8"), o.missing.length > 0) {
|
|
474
|
-
f.warn(`${e}: ${o.compiled} compiled, ${o.missing.length} missing translations`);
|
|
475
|
-
for (let e of o.missing) f.warn(` ⤷ ${e}`);
|
|
476
|
-
} else f.success(`Compiled ${e}: ${o.compiled} messages → ${s}`);
|
|
477
|
-
}
|
|
478
|
-
let s = o(t.locales, t.compileOutDir), l = x(t.compileOutDir, "index.js");
|
|
479
|
-
_(l, s, "utf-8"), f.success(`Generated index → ${l}`);
|
|
480
|
-
}
|
|
481
|
-
}),
|
|
482
|
-
stats: u({
|
|
483
|
-
meta: {
|
|
484
|
-
name: "stats",
|
|
485
|
-
description: "Show translation progress"
|
|
486
|
-
},
|
|
487
|
-
args: { config: {
|
|
488
|
-
type: "string",
|
|
489
|
-
description: "Path to config file"
|
|
490
|
-
} },
|
|
491
|
-
async run({ args: e }) {
|
|
492
|
-
let t = await H(e.config), n = t.format === "json" ? ".json" : ".po", r = [];
|
|
493
|
-
for (let e of t.locales) {
|
|
494
|
-
let i = U(x(t.catalogDir, `${e}${n}`), t.format), a = Object.values(i).filter((e) => !e.obsolete), o = a.length, s = a.filter((e) => e.translation && e.translation.length > 0).length, c = o > 0 ? (s / o * 100).toFixed(1) + "%" : "—";
|
|
495
|
-
r.push({
|
|
496
|
-
locale: e,
|
|
497
|
-
total: o,
|
|
498
|
-
translated: s,
|
|
499
|
-
pct: c
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
f.log(""), f.log(" Locale │ Total │ Translated │ Progress"), f.log(" ────────┼───────┼────────────┼─────────");
|
|
503
|
-
for (let e of r) f.log(` ${e.locale.padEnd(8)}│ ${String(e.total).padStart(5)} │ ${String(e.translated).padStart(10)} │ ${e.pct}`);
|
|
504
|
-
f.log("");
|
|
505
|
-
}
|
|
506
|
-
}),
|
|
507
|
-
translate: u({
|
|
508
|
-
meta: {
|
|
509
|
-
name: "translate",
|
|
510
|
-
description: "Translate messages using AI (Claude Code or Codex CLI)"
|
|
511
|
-
},
|
|
512
|
-
args: {
|
|
513
|
-
config: {
|
|
514
|
-
type: "string",
|
|
515
|
-
description: "Path to config file"
|
|
516
|
-
},
|
|
517
|
-
provider: {
|
|
518
|
-
type: "string",
|
|
519
|
-
description: "AI provider: claude or codex",
|
|
520
|
-
default: "claude"
|
|
521
|
-
},
|
|
522
|
-
locale: {
|
|
523
|
-
type: "string",
|
|
524
|
-
description: "Translate a specific locale only"
|
|
525
|
-
},
|
|
526
|
-
"batch-size": {
|
|
527
|
-
type: "string",
|
|
528
|
-
description: "Messages per batch",
|
|
529
|
-
default: "50"
|
|
530
|
-
}
|
|
531
|
-
},
|
|
532
|
-
async run({ args: e }) {
|
|
533
|
-
let t = await H(e.config), n = e.provider;
|
|
534
|
-
if (n !== "claude" && n !== "codex") {
|
|
535
|
-
f.error(`Invalid provider "${n}". Use "claude" or "codex".`);
|
|
536
|
-
return;
|
|
537
|
-
}
|
|
538
|
-
let r = parseInt(e["batch-size"] ?? "50", 10);
|
|
539
|
-
if (isNaN(r) || r < 1) {
|
|
540
|
-
f.error("Invalid batch-size. Must be a positive integer.");
|
|
541
|
-
return;
|
|
542
|
-
}
|
|
543
|
-
let i = e.locale ? [e.locale] : t.locales.filter((e) => e !== t.sourceLocale);
|
|
544
|
-
if (i.length === 0) {
|
|
545
|
-
f.warn("No target locales to translate.");
|
|
546
|
-
return;
|
|
547
|
-
}
|
|
548
|
-
f.info(`Translating with ${n} (batch size: ${r})`);
|
|
549
|
-
let a = t.format === "json" ? ".json" : ".po";
|
|
550
|
-
for (let e of i) {
|
|
551
|
-
f.info(`\n[${e}]`);
|
|
552
|
-
let i = x(t.catalogDir, `${e}${a}`), o = U(i, t.format), { catalog: s, translated: c } = await A({
|
|
553
|
-
provider: n,
|
|
554
|
-
sourceLocale: t.sourceLocale,
|
|
555
|
-
targetLocale: e,
|
|
556
|
-
catalog: o,
|
|
557
|
-
batchSize: r
|
|
558
|
-
});
|
|
559
|
-
c > 0 ? (W(i, s, t.format), f.success(` ${e}: ${c} messages translated`)) : f.success(` ${e}: already fully translated`);
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
}),
|
|
563
|
-
migrate: u({
|
|
564
|
-
meta: {
|
|
565
|
-
name: "migrate",
|
|
566
|
-
description: "Migrate from another i18n library using AI"
|
|
567
|
-
},
|
|
568
|
-
args: {
|
|
569
|
-
from: {
|
|
570
|
-
type: "string",
|
|
571
|
-
description: "Source library: vue-i18n, nuxt-i18n, react-i18next, next-intl, next-i18next, lingui",
|
|
572
|
-
required: !0
|
|
573
|
-
},
|
|
574
|
-
provider: {
|
|
575
|
-
type: "string",
|
|
576
|
-
description: "AI provider: claude or codex",
|
|
577
|
-
default: "claude"
|
|
578
|
-
},
|
|
579
|
-
write: {
|
|
580
|
-
type: "boolean",
|
|
581
|
-
description: "Write generated files to disk",
|
|
582
|
-
default: !1
|
|
583
|
-
}
|
|
584
|
-
},
|
|
585
|
-
async run({ args: e }) {
|
|
586
|
-
let t = e.provider;
|
|
587
|
-
if (t !== "claude" && t !== "codex") {
|
|
588
|
-
f.error(`Invalid provider "${t}". Use "claude" or "codex".`);
|
|
589
|
-
return;
|
|
590
|
-
}
|
|
591
|
-
await B({
|
|
592
|
-
from: e.from,
|
|
593
|
-
provider: t,
|
|
594
|
-
write: e.write ?? !1
|
|
595
|
-
});
|
|
596
|
-
}
|
|
597
|
-
})
|
|
768
|
+
extract: ie,
|
|
769
|
+
compile: ae,
|
|
770
|
+
stats: oe,
|
|
771
|
+
translate: se,
|
|
772
|
+
migrate: ce
|
|
598
773
|
}
|
|
599
774
|
}));
|
|
600
775
|
//#endregion
|