@fluenti/cli 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +434 -282
- package/dist/cli.js.map +1 -1
- package/dist/compile-runner.d.ts +7 -0
- package/dist/compile-runner.d.ts.map +1 -0
- package/dist/compile.d.ts +16 -1
- package/dist/compile.d.ts.map +1 -1
- package/dist/config-loader-CcqRnMzw.js +387 -0
- package/dist/config-loader-CcqRnMzw.js.map +1 -0
- package/dist/config-loader-DV5Yqrg5.cjs +16 -0
- package/dist/config-loader-DV5Yqrg5.cjs.map +1 -0
- package/dist/config-loader.d.ts +7 -0
- package/dist/config-loader.d.ts.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -4
- 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-DNg_iUSd.cjs +2 -0
- package/dist/tsx-extractor-DNg_iUSd.cjs.map +1 -0
- package/dist/tsx-extractor-DZrY1LMS.js +268 -0
- package/dist/tsx-extractor-DZrY1LMS.js.map +1 -0
- package/dist/tsx-extractor.d.ts.map +1 -1
- package/dist/vue-extractor-DWETY0BN.cjs +3 -0
- package/dist/vue-extractor-DWETY0BN.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 {
|
|
3
|
-
import {
|
|
4
|
-
import f from "
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
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, u } from "./config-loader-CcqRnMzw.js";
|
|
4
|
+
import { appendFileSync as d, existsSync as f, mkdirSync as p, readFileSync as m, writeFileSync as h } from "node:fs";
|
|
5
|
+
import { dirname as g, extname as _, join as v, resolve as y } from "node:path";
|
|
6
|
+
import { defineCommand as b, runMain as x } from "citty";
|
|
7
|
+
import S from "consola";
|
|
8
|
+
import C from "fast-glob";
|
|
9
|
+
import { execFile as w } from "node:child_process";
|
|
10
|
+
import { promisify as T } from "node:util";
|
|
11
|
+
//#region src/stats-format.ts
|
|
12
|
+
var ee = "█", E = "░";
|
|
13
|
+
function D(e, t = 20) {
|
|
14
|
+
let n = Math.max(0, Math.min(100, e)), r = Math.round(n / 100 * t);
|
|
15
|
+
return ee.repeat(r) + E.repeat(t - r);
|
|
16
|
+
}
|
|
17
|
+
function O(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 te(e, t, n) {
|
|
22
|
+
let r = t > 0 ? n / t * 100 : 0, i = t > 0 ? O(r) : "—", a = t > 0 ? D(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 = T(w);
|
|
28
|
+
function A(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 j(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 M(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 N(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 P(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 F(e) {
|
|
83
|
+
let { provider: t, sourceLocale: n, targetLocale: r, catalog: i, batchSize: a } = e, o = N(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
|
-
|
|
73
|
-
let c =
|
|
74
|
-
for (let e = 0; e <
|
|
75
|
-
let
|
|
76
|
-
|
|
77
|
-
let
|
|
78
|
-
for (let e of
|
|
79
|
-
...
|
|
80
|
-
translation:
|
|
81
|
-
},
|
|
88
|
+
S.info(` ${s} untranslated messages, translating with ${t}...`);
|
|
89
|
+
let c = { ...i }, l = P(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 && S.info(` Batch ${e + 1}/${l.length} (${a.length} messages)`);
|
|
93
|
+
let o = M(await j(t, A(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++) : S.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 I = T(w), L = {
|
|
91
107
|
"vue-i18n": {
|
|
92
108
|
name: "vue-i18n",
|
|
93
109
|
framework: "Vue",
|
|
@@ -227,37 +243,37 @@ var j = C(S), M = {
|
|
|
227
243
|
],
|
|
228
244
|
migrationGuide: "react/llms-migration.txt"
|
|
229
245
|
}
|
|
230
|
-
},
|
|
231
|
-
function
|
|
246
|
+
}, R = Object.keys(L);
|
|
247
|
+
function z(e) {
|
|
232
248
|
let t = e.toLowerCase().replace(/^@nuxtjs\//, "nuxt-").replace(/^@/, "");
|
|
233
|
-
return
|
|
249
|
+
return R.find((e) => e === t);
|
|
234
250
|
}
|
|
235
|
-
async function
|
|
251
|
+
async function B(e) {
|
|
236
252
|
let t = {
|
|
237
253
|
configFiles: [],
|
|
238
254
|
localeFiles: [],
|
|
239
255
|
sampleSources: [],
|
|
240
256
|
packageJson: void 0
|
|
241
|
-
}, n =
|
|
242
|
-
|
|
257
|
+
}, n = y("package.json");
|
|
258
|
+
f(n) && (t.packageJson = m(n, "utf-8"));
|
|
243
259
|
for (let n of e.configPatterns) {
|
|
244
|
-
let e =
|
|
245
|
-
|
|
260
|
+
let e = y(n);
|
|
261
|
+
f(e) && t.configFiles.push({
|
|
246
262
|
path: n,
|
|
247
|
-
content:
|
|
263
|
+
content: m(e, "utf-8")
|
|
248
264
|
});
|
|
249
265
|
}
|
|
250
|
-
let r = await
|
|
266
|
+
let r = await C(e.localePatterns, { absolute: !1 });
|
|
251
267
|
for (let e of r.slice(0, 10)) {
|
|
252
|
-
let n =
|
|
268
|
+
let n = m(y(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
|
|
256
272
|
});
|
|
257
273
|
}
|
|
258
|
-
let i = await
|
|
274
|
+
let i = await C(e.sourcePatterns, { absolute: !1 });
|
|
259
275
|
for (let e of i.slice(0, 5)) {
|
|
260
|
-
let n =
|
|
276
|
+
let n = m(y(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 V(e) {
|
|
269
285
|
let t = [
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
286
|
+
y("node_modules", "@fluenti", "cli", "..", "..", e),
|
|
287
|
+
v(__dirname, "..", "..", "..", e),
|
|
288
|
+
v(__dirname, "..", "..", e)
|
|
273
289
|
];
|
|
274
|
-
for (let e of t) if (
|
|
290
|
+
for (let e of t) if (f(e)) return m(e, "utf-8");
|
|
275
291
|
return "";
|
|
276
292
|
}
|
|
277
|
-
function
|
|
293
|
+
function H(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 U(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 I("claude", ["-p", t], { maxBuffer: n });
|
|
298
314
|
return e;
|
|
299
315
|
} else {
|
|
300
|
-
let { stdout: e } = await
|
|
316
|
+
let { stdout: e } = await I("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 W(e) {
|
|
312
328
|
let t = {
|
|
313
329
|
config: void 0,
|
|
314
330
|
localeFiles: [],
|
|
@@ -329,272 +345,408 @@ 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 G(e) {
|
|
349
|
+
let { from: t, provider: n, write: r } = e, i = z(t);
|
|
334
350
|
if (!i) {
|
|
335
|
-
|
|
336
|
-
for (let e of
|
|
351
|
+
S.error(`Unsupported library "${t}". Supported libraries:`);
|
|
352
|
+
for (let e of R) S.log(` - ${e}`);
|
|
337
353
|
return;
|
|
338
354
|
}
|
|
339
|
-
let a =
|
|
340
|
-
|
|
341
|
-
let o = await
|
|
355
|
+
let a = L[i];
|
|
356
|
+
S.info(`Migrating from ${a.name} (${a.framework}) to Fluenti`), S.info("Scanning project for existing i18n files...");
|
|
357
|
+
let o = await B(a);
|
|
342
358
|
if (o.configFiles.length === 0 && o.localeFiles.length === 0) {
|
|
343
|
-
|
|
359
|
+
S.warn(`No ${a.name} configuration or locale files found.`), S.info("Make sure you are running this command from the project root directory.");
|
|
344
360
|
return;
|
|
345
361
|
}
|
|
346
|
-
|
|
347
|
-
let s =
|
|
348
|
-
|
|
349
|
-
let c =
|
|
350
|
-
if (c.installCommands && (
|
|
362
|
+
S.info(`Found: ${o.configFiles.length} config file(s), ${o.localeFiles.length} locale file(s), ${o.sampleSources.length} source file(s)`);
|
|
363
|
+
let s = V(a.migrationGuide);
|
|
364
|
+
S.info(`Generating migration plan with ${n}...`);
|
|
365
|
+
let c = W(await U(n, H(a, o, s)));
|
|
366
|
+
if (c.installCommands && (S.log(""), S.box({
|
|
351
367
|
title: "Install Commands",
|
|
352
368
|
message: c.installCommands
|
|
353
369
|
})), c.config) if (r) {
|
|
354
|
-
let { writeFileSync: e } = await import("node:fs"), t =
|
|
355
|
-
e(t, c.config, "utf-8"),
|
|
356
|
-
} else
|
|
370
|
+
let { writeFileSync: e } = await import("node:fs"), t = y("fluenti.config.ts");
|
|
371
|
+
e(t, c.config, "utf-8"), S.success(`Written: ${t}`);
|
|
372
|
+
} else S.log(""), S.box({
|
|
357
373
|
title: "fluenti.config.ts",
|
|
358
374
|
message: c.config
|
|
359
375
|
});
|
|
360
376
|
if (c.localeFiles.length > 0) if (r) {
|
|
361
377
|
let { writeFileSync: e, mkdirSync: t } = await import("node:fs"), n = "./locales";
|
|
362
|
-
t(
|
|
378
|
+
t(y(n), { recursive: !0 });
|
|
363
379
|
for (let t of c.localeFiles) {
|
|
364
|
-
let r =
|
|
365
|
-
e(r, t.content, "utf-8"),
|
|
380
|
+
let r = y(n, `${t.locale}.po`);
|
|
381
|
+
e(r, t.content, "utf-8"), S.success(`Written: ${r}`);
|
|
366
382
|
}
|
|
367
|
-
} else for (let e of c.localeFiles)
|
|
383
|
+
} else for (let e of c.localeFiles) S.log(""), S.box({
|
|
368
384
|
title: `locales/${e.locale}.po`,
|
|
369
385
|
message: e.content.length > 500 ? e.content.slice(0, 500) + "\n... (use --write to save full file)" : e.content
|
|
370
386
|
});
|
|
371
|
-
c.steps && (
|
|
387
|
+
c.steps && (S.log(""), S.box({
|
|
372
388
|
title: "Migration Steps",
|
|
373
389
|
message: c.steps
|
|
374
|
-
})), !r && (c.config || c.localeFiles.length > 0) && (
|
|
390
|
+
})), !r && (c.config || c.localeFiles.length > 0) && (S.log(""), S.info("Run with --write to save generated files to disk:"), S.log(` fluenti migrate --from ${t} --write`));
|
|
375
391
|
}
|
|
376
392
|
//#endregion
|
|
377
|
-
//#region src/
|
|
378
|
-
var
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
393
|
+
//#region src/init.ts
|
|
394
|
+
var K = /^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{1,8})*$/;
|
|
395
|
+
function q(e) {
|
|
396
|
+
if (!K.test(e)) throw Error(`Invalid locale format: "${e}"`);
|
|
397
|
+
return e;
|
|
398
|
+
}
|
|
399
|
+
var J = [
|
|
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/solid"
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
dep: "vue",
|
|
417
|
+
name: "vue",
|
|
418
|
+
pluginPackage: "@fluenti/vue"
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
dep: "solid-js",
|
|
422
|
+
name: "solid",
|
|
423
|
+
pluginPackage: "@fluenti/solid"
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
dep: "react",
|
|
427
|
+
name: "react",
|
|
428
|
+
pluginPackage: "@fluenti/react"
|
|
429
|
+
}
|
|
430
|
+
];
|
|
431
|
+
function Y(e) {
|
|
432
|
+
for (let t of J) 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 X(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 Z(e) {
|
|
456
|
+
let t = y(e.cwd, "package.json");
|
|
457
|
+
if (!f(t)) {
|
|
458
|
+
S.error("No package.json found in current directory.");
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
let n = JSON.parse(m(t, "utf-8")), r = Y({
|
|
462
|
+
...n.dependencies,
|
|
463
|
+
...n.devDependencies
|
|
464
|
+
});
|
|
465
|
+
S.info(`Detected framework: ${r.name}`), r.pluginPackage && S.info(`Recommended plugin: ${r.pluginPackage}`);
|
|
466
|
+
let i = y(e.cwd, "fluenti.config.ts");
|
|
467
|
+
if (f(i)) {
|
|
468
|
+
S.warn("fluenti.config.ts already exists. Skipping config generation.");
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
let a = await S.prompt("Source locale?", {
|
|
472
|
+
type: "text",
|
|
473
|
+
default: "en",
|
|
474
|
+
placeholder: "en"
|
|
475
|
+
});
|
|
476
|
+
if (typeof a == "symbol") return;
|
|
477
|
+
let o = await S.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 S.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
|
+
q(a);
|
|
491
|
+
for (let e of c) q(e);
|
|
492
|
+
h(i, X({
|
|
493
|
+
sourceLocale: a,
|
|
494
|
+
locales: [a, ...c.filter((e) => e !== a)],
|
|
495
|
+
format: s
|
|
496
|
+
}), "utf-8"), S.success("Created fluenti.config.ts");
|
|
497
|
+
let l = y(e.cwd, ".gitignore"), u = "src/locales/compiled/";
|
|
498
|
+
f(l) ? m(l, "utf-8").includes(u) || (d(l, `\n# Fluenti compiled catalogs\n${u}\n`), S.success("Updated .gitignore")) : (h(l, `# Fluenti compiled catalogs\n${u}\n`), S.success("Created .gitignore"));
|
|
499
|
+
let p = n.scripts ?? {}, g = {}, _ = !1;
|
|
500
|
+
if (p["i18n:extract"] || (g["i18n:extract"] = "fluenti extract", _ = !0), p["i18n:compile"] || (g["i18n:compile"] = "fluenti compile", _ = !0), _) {
|
|
501
|
+
let e = {
|
|
502
|
+
...n,
|
|
503
|
+
scripts: {
|
|
504
|
+
...p,
|
|
505
|
+
...g
|
|
506
|
+
}
|
|
397
507
|
};
|
|
398
|
-
|
|
399
|
-
f.warn(`Failed to load config from ${e}, using defaults`);
|
|
508
|
+
h(t, JSON.stringify(e, null, 2) + "\n", "utf-8"), S.success("Added i18n:extract and i18n:compile scripts to package.json");
|
|
400
509
|
}
|
|
401
|
-
|
|
510
|
+
S.log(""), S.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 with the framework Vite plugin or @fluenti/next" : "2. Add the Vite plugin to your vite.config.ts (e.g. fluentiVue() from @fluenti/vue/vite-plugin)",
|
|
515
|
+
"3. Run: npx fluenti extract",
|
|
516
|
+
"4. Translate your messages",
|
|
517
|
+
"5. Run: npx fluenti compile"
|
|
518
|
+
].join("\n")
|
|
519
|
+
});
|
|
402
520
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
521
|
+
//#endregion
|
|
522
|
+
//#region src/cli.ts
|
|
523
|
+
function Q(e, t) {
|
|
524
|
+
if (!f(e)) return {};
|
|
525
|
+
let r = m(e, "utf-8");
|
|
526
|
+
return t === "json" ? n(r) : o(r);
|
|
407
527
|
}
|
|
408
|
-
function
|
|
409
|
-
|
|
528
|
+
function $(e, t, n) {
|
|
529
|
+
p(g(e), { recursive: !0 }), h(e, n === "json" ? i(t) : c(t), "utf-8");
|
|
410
530
|
}
|
|
411
|
-
function
|
|
412
|
-
|
|
531
|
+
async function ne(t, n) {
|
|
532
|
+
if (_(t) === ".vue") try {
|
|
533
|
+
let { extractFromVue: e } = await import("./vue-extractor-iUl6SUkv.js").then((e) => e.n);
|
|
534
|
+
return e(n, t);
|
|
535
|
+
} catch {
|
|
536
|
+
return S.warn(`Skipping ${t}: install @vue/compiler-sfc to extract from .vue files`), [];
|
|
537
|
+
}
|
|
538
|
+
return e(n, t);
|
|
413
539
|
}
|
|
414
|
-
|
|
540
|
+
var re = b({
|
|
541
|
+
meta: {
|
|
542
|
+
name: "extract",
|
|
543
|
+
description: "Extract messages from source files"
|
|
544
|
+
},
|
|
545
|
+
args: {
|
|
546
|
+
config: {
|
|
547
|
+
type: "string",
|
|
548
|
+
description: "Path to config file"
|
|
549
|
+
},
|
|
550
|
+
clean: {
|
|
551
|
+
type: "boolean",
|
|
552
|
+
description: "Remove obsolete entries instead of marking them",
|
|
553
|
+
default: !1
|
|
554
|
+
},
|
|
555
|
+
"no-fuzzy": {
|
|
556
|
+
type: "boolean",
|
|
557
|
+
description: "Strip fuzzy flags from all entries",
|
|
558
|
+
default: !1
|
|
559
|
+
}
|
|
560
|
+
},
|
|
561
|
+
async run({ args: e }) {
|
|
562
|
+
let t = await l(e.config);
|
|
563
|
+
S.info(`Extracting messages from ${t.include.join(", ")}`);
|
|
564
|
+
let n = await C(t.include), r = [];
|
|
565
|
+
for (let e of n) {
|
|
566
|
+
let t = await ne(e, m(e, "utf-8"));
|
|
567
|
+
r.push(...t);
|
|
568
|
+
}
|
|
569
|
+
S.info(`Found ${r.length} messages in ${n.length} files`);
|
|
570
|
+
let i = t.format === "json" ? ".json" : ".po", a = e.clean ?? !1, o = e["no-fuzzy"] ?? !1;
|
|
571
|
+
for (let e of t.locales) {
|
|
572
|
+
let n = y(t.catalogDir, `${e}${i}`), { catalog: s, result: c } = u(Q(n, t.format), r, { stripFuzzy: o });
|
|
573
|
+
$(n, a ? Object.fromEntries(Object.entries(s).filter(([, e]) => !e.obsolete)) : s, t.format);
|
|
574
|
+
let l = a ? `${c.obsolete} removed` : `${c.obsolete} obsolete`;
|
|
575
|
+
S.success(`${e}: ${c.added} added, ${c.unchanged} unchanged, ${l}`);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}), ie = b({
|
|
579
|
+
meta: {
|
|
580
|
+
name: "compile",
|
|
581
|
+
description: "Compile message catalogs to JS modules"
|
|
582
|
+
},
|
|
583
|
+
args: {
|
|
584
|
+
config: {
|
|
585
|
+
type: "string",
|
|
586
|
+
description: "Path to config file"
|
|
587
|
+
},
|
|
588
|
+
"skip-fuzzy": {
|
|
589
|
+
type: "boolean",
|
|
590
|
+
description: "Exclude fuzzy entries from compilation",
|
|
591
|
+
default: !1
|
|
592
|
+
}
|
|
593
|
+
},
|
|
594
|
+
async run({ args: e }) {
|
|
595
|
+
let n = await l(e.config), i = n.format === "json" ? ".json" : ".po";
|
|
596
|
+
p(n.compileOutDir, { recursive: !0 });
|
|
597
|
+
let o = {};
|
|
598
|
+
for (let e of n.locales) o[e] = Q(y(n.catalogDir, `${e}${i}`), n.format);
|
|
599
|
+
let c = a(o);
|
|
600
|
+
S.info(`Compiling ${c.length} messages across ${n.locales.length} locales`);
|
|
601
|
+
let u = e["skip-fuzzy"] ?? !1;
|
|
602
|
+
for (let e of n.locales) {
|
|
603
|
+
let { code: t, stats: r } = s(o[e], e, c, n.sourceLocale, { skipFuzzy: u }), i = y(n.compileOutDir, `${e}.js`);
|
|
604
|
+
if (h(i, t, "utf-8"), r.missing.length > 0) {
|
|
605
|
+
S.warn(`${e}: ${r.compiled} compiled, ${r.missing.length} missing translations`);
|
|
606
|
+
for (let e of r.missing) S.warn(` ⤷ ${e}`);
|
|
607
|
+
} else S.success(`Compiled ${e}: ${r.compiled} messages → ${i}`);
|
|
608
|
+
}
|
|
609
|
+
let d = r(n.locales, n.compileOutDir), f = y(n.compileOutDir, "index.js");
|
|
610
|
+
h(f, d, "utf-8"), S.success(`Generated index → ${f}`);
|
|
611
|
+
let m = t(c, o, n.sourceLocale), g = y(n.compileOutDir, "messages.d.ts");
|
|
612
|
+
h(g, m, "utf-8"), S.success(`Generated types → ${g}`);
|
|
613
|
+
}
|
|
614
|
+
}), ae = b({
|
|
615
|
+
meta: {
|
|
616
|
+
name: "stats",
|
|
617
|
+
description: "Show translation progress"
|
|
618
|
+
},
|
|
619
|
+
args: { config: {
|
|
620
|
+
type: "string",
|
|
621
|
+
description: "Path to config file"
|
|
622
|
+
} },
|
|
623
|
+
async run({ args: e }) {
|
|
624
|
+
let t = await l(e.config), n = t.format === "json" ? ".json" : ".po", r = [];
|
|
625
|
+
for (let e of t.locales) {
|
|
626
|
+
let i = Q(y(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) + "%" : "—";
|
|
627
|
+
r.push({
|
|
628
|
+
locale: e,
|
|
629
|
+
total: o,
|
|
630
|
+
translated: s,
|
|
631
|
+
pct: c
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
S.log(""), S.log(" Locale │ Total │ Translated │ Progress"), S.log(" ────────┼───────┼────────────┼─────────────────────────────");
|
|
635
|
+
for (let e of r) S.log(te(e.locale, e.total, e.translated));
|
|
636
|
+
S.log("");
|
|
637
|
+
}
|
|
638
|
+
}), oe = b({
|
|
639
|
+
meta: {
|
|
640
|
+
name: "translate",
|
|
641
|
+
description: "Translate messages using AI (Claude Code or Codex CLI)"
|
|
642
|
+
},
|
|
643
|
+
args: {
|
|
644
|
+
config: {
|
|
645
|
+
type: "string",
|
|
646
|
+
description: "Path to config file"
|
|
647
|
+
},
|
|
648
|
+
provider: {
|
|
649
|
+
type: "string",
|
|
650
|
+
description: "AI provider: claude or codex",
|
|
651
|
+
default: "claude"
|
|
652
|
+
},
|
|
653
|
+
locale: {
|
|
654
|
+
type: "string",
|
|
655
|
+
description: "Translate a specific locale only"
|
|
656
|
+
},
|
|
657
|
+
"batch-size": {
|
|
658
|
+
type: "string",
|
|
659
|
+
description: "Messages per batch",
|
|
660
|
+
default: "50"
|
|
661
|
+
}
|
|
662
|
+
},
|
|
663
|
+
async run({ args: e }) {
|
|
664
|
+
let t = await l(e.config), n = e.provider;
|
|
665
|
+
if (n !== "claude" && n !== "codex") {
|
|
666
|
+
S.error(`Invalid provider "${n}". Use "claude" or "codex".`);
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
let r = parseInt(e["batch-size"] ?? "50", 10);
|
|
670
|
+
if (isNaN(r) || r < 1) {
|
|
671
|
+
S.error("Invalid batch-size. Must be a positive integer.");
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
let i = e.locale ? [e.locale] : t.locales.filter((e) => e !== t.sourceLocale);
|
|
675
|
+
if (i.length === 0) {
|
|
676
|
+
S.warn("No target locales to translate.");
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
S.info(`Translating with ${n} (batch size: ${r})`);
|
|
680
|
+
let a = t.format === "json" ? ".json" : ".po";
|
|
681
|
+
for (let e of i) {
|
|
682
|
+
S.info(`\n[${e}]`);
|
|
683
|
+
let i = y(t.catalogDir, `${e}${a}`), o = Q(i, t.format), { catalog: s, translated: c } = await F({
|
|
684
|
+
provider: n,
|
|
685
|
+
sourceLocale: t.sourceLocale,
|
|
686
|
+
targetLocale: e,
|
|
687
|
+
catalog: o,
|
|
688
|
+
batchSize: r
|
|
689
|
+
});
|
|
690
|
+
c > 0 ? ($(i, s, t.format), S.success(` ${e}: ${c} messages translated`)) : S.success(` ${e}: already fully translated`);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}), se = b({
|
|
694
|
+
meta: {
|
|
695
|
+
name: "migrate",
|
|
696
|
+
description: "Migrate from another i18n library using AI"
|
|
697
|
+
},
|
|
698
|
+
args: {
|
|
699
|
+
from: {
|
|
700
|
+
type: "string",
|
|
701
|
+
description: "Source library: vue-i18n, nuxt-i18n, react-i18next, next-intl, next-i18next, lingui",
|
|
702
|
+
required: !0
|
|
703
|
+
},
|
|
704
|
+
provider: {
|
|
705
|
+
type: "string",
|
|
706
|
+
description: "AI provider: claude or codex",
|
|
707
|
+
default: "claude"
|
|
708
|
+
},
|
|
709
|
+
write: {
|
|
710
|
+
type: "boolean",
|
|
711
|
+
description: "Write generated files to disk",
|
|
712
|
+
default: !1
|
|
713
|
+
}
|
|
714
|
+
},
|
|
715
|
+
async run({ args: e }) {
|
|
716
|
+
let t = e.provider;
|
|
717
|
+
if (t !== "claude" && t !== "codex") {
|
|
718
|
+
S.error(`Invalid provider "${t}". Use "claude" or "codex".`);
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
await G({
|
|
722
|
+
from: e.from,
|
|
723
|
+
provider: t,
|
|
724
|
+
write: e.write ?? !1
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
});
|
|
728
|
+
x(b({
|
|
415
729
|
meta: {
|
|
416
730
|
name: "fluenti",
|
|
417
731
|
version: "0.0.1",
|
|
418
732
|
description: "Compile-time i18n for modern frameworks"
|
|
419
733
|
},
|
|
420
734
|
subCommands: {
|
|
421
|
-
|
|
422
|
-
meta: {
|
|
423
|
-
name: "extract",
|
|
424
|
-
description: "Extract messages from source files"
|
|
425
|
-
},
|
|
426
|
-
args: {
|
|
427
|
-
config: {
|
|
428
|
-
type: "string",
|
|
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), 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
|
-
}
|
|
453
|
-
}
|
|
454
|
-
}),
|
|
455
|
-
compile: u({
|
|
735
|
+
init: b({
|
|
456
736
|
meta: {
|
|
457
|
-
name: "
|
|
458
|
-
description: "
|
|
737
|
+
name: "init",
|
|
738
|
+
description: "Initialize Fluenti in your project"
|
|
459
739
|
},
|
|
460
|
-
args: {
|
|
461
|
-
|
|
462
|
-
|
|
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}`);
|
|
740
|
+
args: {},
|
|
741
|
+
async run() {
|
|
742
|
+
await Z({ cwd: process.cwd() });
|
|
480
743
|
}
|
|
481
744
|
}),
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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
|
-
})
|
|
745
|
+
extract: re,
|
|
746
|
+
compile: ie,
|
|
747
|
+
stats: ae,
|
|
748
|
+
translate: oe,
|
|
749
|
+
migrate: se
|
|
598
750
|
}
|
|
599
751
|
}));
|
|
600
752
|
//#endregion
|