@fluenti/cli 0.1.3 → 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/cli.cjs +9 -9
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +174 -197
- 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.map +1 -1
- package/dist/{compile-BJdEF9QX.js → config-loader-CcqRnMzw.js} +123 -93
- 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 +28 -4
- package/dist/index.js.map +1 -1
- package/dist/tsx-extractor-DNg_iUSd.cjs +2 -0
- package/dist/{tsx-extractor-LEAVCuX9.cjs.map → tsx-extractor-DNg_iUSd.cjs.map} +1 -1
- package/dist/vue-extractor-DWETY0BN.cjs +3 -0
- package/dist/{vue-extractor-BlHc3vzt.cjs.map → vue-extractor-DWETY0BN.cjs.map} +1 -1
- package/package.json +2 -2
- package/dist/compile-BJdEF9QX.js.map +0 -1
- package/dist/compile-d4Q8bND5.cjs +0 -16
- package/dist/compile-d4Q8bND5.cjs.map +0 -1
- package/dist/tsx-extractor-LEAVCuX9.cjs +0 -2
- package/dist/vue-extractor-BlHc3vzt.cjs +0 -3
package/dist/cli.js
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
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 "./
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import { execFile as
|
|
10
|
-
import { promisify as
|
|
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
11
|
//#region src/stats-format.ts
|
|
12
|
-
var
|
|
13
|
-
function
|
|
12
|
+
var ee = "█", E = "░";
|
|
13
|
+
function D(e, t = 20) {
|
|
14
14
|
let n = Math.max(0, Math.min(100, e)), r = Math.round(n / 100 * t);
|
|
15
|
-
return
|
|
15
|
+
return ee.repeat(r) + E.repeat(t - r);
|
|
16
16
|
}
|
|
17
|
-
function
|
|
17
|
+
function O(e) {
|
|
18
18
|
let t = e.toFixed(1) + "%";
|
|
19
19
|
return e >= 90 ? `\x1b[32m${t}\x1b[0m` : e >= 70 ? `\x1b[33m${t}\x1b[0m` : `\x1b[31m${t}\x1b[0m`;
|
|
20
20
|
}
|
|
21
|
-
function
|
|
22
|
-
let r = t > 0 ? n / t * 100 : 0, i = t > 0 ?
|
|
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
23
|
return ` ${e.padEnd(8)}│ ${String(t).padStart(5)} │ ${String(n).padStart(10)} │ ${a} ${i}`;
|
|
24
24
|
}
|
|
25
25
|
//#endregion
|
|
26
26
|
//#region src/translate.ts
|
|
27
|
-
var k =
|
|
28
|
-
function
|
|
27
|
+
var k = T(w);
|
|
28
|
+
function A(e, t, n) {
|
|
29
29
|
let r = JSON.stringify(n, null, 2);
|
|
30
30
|
return [
|
|
31
31
|
`You are a professional translator. Translate the following messages from "${e}" to "${t}".`,
|
|
@@ -40,7 +40,7 @@ function te(e, t, n) {
|
|
|
40
40
|
"- Do not add any explanation or markdown formatting, output raw JSON only."
|
|
41
41
|
].join("\n");
|
|
42
42
|
}
|
|
43
|
-
async function
|
|
43
|
+
async function j(e, t) {
|
|
44
44
|
let n = 10 * 1024 * 1024;
|
|
45
45
|
try {
|
|
46
46
|
if (e === "claude") {
|
|
@@ -58,19 +58,19 @@ async function ne(e, t) {
|
|
|
58
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;
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
-
function
|
|
61
|
+
function M(e) {
|
|
62
62
|
let t = e.match(/\{[\s\S]*\}/);
|
|
63
63
|
if (!t) throw Error("No JSON object found in AI response");
|
|
64
64
|
let n = JSON.parse(t[0]);
|
|
65
65
|
if (typeof n != "object" || !n || Array.isArray(n)) throw Error("AI response is not a valid JSON object");
|
|
66
66
|
return n;
|
|
67
67
|
}
|
|
68
|
-
function
|
|
68
|
+
function N(e) {
|
|
69
69
|
let t = {};
|
|
70
70
|
for (let [n, r] of Object.entries(e)) r.obsolete || (!r.translation || r.translation.length === 0) && (t[n] = r.message ?? n);
|
|
71
71
|
return t;
|
|
72
72
|
}
|
|
73
|
-
function
|
|
73
|
+
function P(e, t) {
|
|
74
74
|
let n = Object.keys(e), r = [];
|
|
75
75
|
for (let i = 0; i < n.length; i += t) {
|
|
76
76
|
let a = {};
|
|
@@ -79,22 +79,22 @@ function M(e, t) {
|
|
|
79
79
|
}
|
|
80
80
|
return r;
|
|
81
81
|
}
|
|
82
|
-
async function
|
|
83
|
-
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;
|
|
84
84
|
if (s === 0) return {
|
|
85
85
|
catalog: { ...i },
|
|
86
86
|
translated: 0
|
|
87
87
|
};
|
|
88
|
-
|
|
89
|
-
let c = { ...i }, l =
|
|
88
|
+
S.info(` ${s} untranslated messages, translating with ${t}...`);
|
|
89
|
+
let c = { ...i }, l = P(o, a), u = 0;
|
|
90
90
|
for (let e = 0; e < l.length; e++) {
|
|
91
91
|
let i = l[e], a = Object.keys(i);
|
|
92
|
-
l.length > 1 &&
|
|
93
|
-
let o =
|
|
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
94
|
for (let e of a) o[e] && typeof o[e] == "string" ? (c[e] = {
|
|
95
95
|
...c[e],
|
|
96
96
|
translation: o[e]
|
|
97
|
-
}, u++) :
|
|
97
|
+
}, u++) : S.warn(` Missing translation for key: ${e}`);
|
|
98
98
|
}
|
|
99
99
|
return {
|
|
100
100
|
catalog: c,
|
|
@@ -103,7 +103,7 @@ async function N(e) {
|
|
|
103
103
|
}
|
|
104
104
|
//#endregion
|
|
105
105
|
//#region src/migrate.ts
|
|
106
|
-
var
|
|
106
|
+
var I = T(w), L = {
|
|
107
107
|
"vue-i18n": {
|
|
108
108
|
name: "vue-i18n",
|
|
109
109
|
framework: "Vue",
|
|
@@ -243,37 +243,37 @@ var P = C(S), F = {
|
|
|
243
243
|
],
|
|
244
244
|
migrationGuide: "react/llms-migration.txt"
|
|
245
245
|
}
|
|
246
|
-
},
|
|
247
|
-
function
|
|
246
|
+
}, R = Object.keys(L);
|
|
247
|
+
function z(e) {
|
|
248
248
|
let t = e.toLowerCase().replace(/^@nuxtjs\//, "nuxt-").replace(/^@/, "");
|
|
249
|
-
return
|
|
249
|
+
return R.find((e) => e === t);
|
|
250
250
|
}
|
|
251
|
-
async function
|
|
251
|
+
async function B(e) {
|
|
252
252
|
let t = {
|
|
253
253
|
configFiles: [],
|
|
254
254
|
localeFiles: [],
|
|
255
255
|
sampleSources: [],
|
|
256
256
|
packageJson: void 0
|
|
257
|
-
}, n =
|
|
258
|
-
|
|
257
|
+
}, n = y("package.json");
|
|
258
|
+
f(n) && (t.packageJson = m(n, "utf-8"));
|
|
259
259
|
for (let n of e.configPatterns) {
|
|
260
|
-
let e =
|
|
261
|
-
|
|
260
|
+
let e = y(n);
|
|
261
|
+
f(e) && t.configFiles.push({
|
|
262
262
|
path: n,
|
|
263
|
-
content:
|
|
263
|
+
content: m(e, "utf-8")
|
|
264
264
|
});
|
|
265
265
|
}
|
|
266
|
-
let r = await
|
|
266
|
+
let r = await C(e.localePatterns, { absolute: !1 });
|
|
267
267
|
for (let e of r.slice(0, 10)) {
|
|
268
|
-
let n =
|
|
268
|
+
let n = m(y(e), "utf-8");
|
|
269
269
|
t.localeFiles.push({
|
|
270
270
|
path: e,
|
|
271
271
|
content: n.length > 5e3 ? n.slice(0, 5e3) + "\n... (truncated)" : n
|
|
272
272
|
});
|
|
273
273
|
}
|
|
274
|
-
let i = await
|
|
274
|
+
let i = await C(e.sourcePatterns, { absolute: !1 });
|
|
275
275
|
for (let e of i.slice(0, 5)) {
|
|
276
|
-
let n =
|
|
276
|
+
let n = m(y(e), "utf-8");
|
|
277
277
|
t.sampleSources.push({
|
|
278
278
|
path: e,
|
|
279
279
|
content: n.length > 3e3 ? n.slice(0, 3e3) + "\n... (truncated)" : n
|
|
@@ -281,16 +281,16 @@ async function R(e) {
|
|
|
281
281
|
}
|
|
282
282
|
return t;
|
|
283
283
|
}
|
|
284
|
-
function
|
|
284
|
+
function V(e) {
|
|
285
285
|
let t = [
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
286
|
+
y("node_modules", "@fluenti", "cli", "..", "..", e),
|
|
287
|
+
v(__dirname, "..", "..", "..", e),
|
|
288
|
+
v(__dirname, "..", "..", e)
|
|
289
289
|
];
|
|
290
|
-
for (let e of t) if (
|
|
290
|
+
for (let e of t) if (f(e)) return m(e, "utf-8");
|
|
291
291
|
return "";
|
|
292
292
|
}
|
|
293
|
-
function
|
|
293
|
+
function H(e, t, n) {
|
|
294
294
|
let r = [];
|
|
295
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) {
|
|
296
296
|
r.push("=== EXISTING CONFIG FILES ===");
|
|
@@ -306,14 +306,14 @@ function B(e, t, n) {
|
|
|
306
306
|
}
|
|
307
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");
|
|
308
308
|
}
|
|
309
|
-
async function
|
|
309
|
+
async function U(e, t) {
|
|
310
310
|
let n = 10 * 1024 * 1024;
|
|
311
311
|
try {
|
|
312
312
|
if (e === "claude") {
|
|
313
|
-
let { stdout: e } = await
|
|
313
|
+
let { stdout: e } = await I("claude", ["-p", t], { maxBuffer: n });
|
|
314
314
|
return e;
|
|
315
315
|
} else {
|
|
316
|
-
let { stdout: e } = await
|
|
316
|
+
let { stdout: e } = await I("codex", [
|
|
317
317
|
"-p",
|
|
318
318
|
t,
|
|
319
319
|
"--full-auto"
|
|
@@ -324,7 +324,7 @@ async function V(e, t) {
|
|
|
324
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;
|
|
325
325
|
}
|
|
326
326
|
}
|
|
327
|
-
function
|
|
327
|
+
function W(e) {
|
|
328
328
|
let t = {
|
|
329
329
|
config: void 0,
|
|
330
330
|
localeFiles: [],
|
|
@@ -345,58 +345,58 @@ function H(e) {
|
|
|
345
345
|
let a = e.match(/### INSTALL_COMMANDS[\s\S]*?```(?:bash|sh)?\n([\s\S]*?)```/);
|
|
346
346
|
return a && (t.installCommands = a[1].trim()), t;
|
|
347
347
|
}
|
|
348
|
-
async function
|
|
349
|
-
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);
|
|
350
350
|
if (!i) {
|
|
351
|
-
|
|
352
|
-
for (let e of
|
|
351
|
+
S.error(`Unsupported library "${t}". Supported libraries:`);
|
|
352
|
+
for (let e of R) S.log(` - ${e}`);
|
|
353
353
|
return;
|
|
354
354
|
}
|
|
355
|
-
let a =
|
|
356
|
-
|
|
357
|
-
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);
|
|
358
358
|
if (o.configFiles.length === 0 && o.localeFiles.length === 0) {
|
|
359
|
-
|
|
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.");
|
|
360
360
|
return;
|
|
361
361
|
}
|
|
362
|
-
|
|
363
|
-
let s =
|
|
364
|
-
|
|
365
|
-
let c =
|
|
366
|
-
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({
|
|
367
367
|
title: "Install Commands",
|
|
368
368
|
message: c.installCommands
|
|
369
369
|
})), c.config) if (r) {
|
|
370
|
-
let { writeFileSync: e } = await import("node:fs"), t =
|
|
371
|
-
e(t, c.config, "utf-8"),
|
|
372
|
-
} 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({
|
|
373
373
|
title: "fluenti.config.ts",
|
|
374
374
|
message: c.config
|
|
375
375
|
});
|
|
376
376
|
if (c.localeFiles.length > 0) if (r) {
|
|
377
377
|
let { writeFileSync: e, mkdirSync: t } = await import("node:fs"), n = "./locales";
|
|
378
|
-
t(
|
|
378
|
+
t(y(n), { recursive: !0 });
|
|
379
379
|
for (let t of c.localeFiles) {
|
|
380
|
-
let r =
|
|
381
|
-
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}`);
|
|
382
382
|
}
|
|
383
|
-
} else for (let e of c.localeFiles)
|
|
383
|
+
} else for (let e of c.localeFiles) S.log(""), S.box({
|
|
384
384
|
title: `locales/${e.locale}.po`,
|
|
385
385
|
message: e.content.length > 500 ? e.content.slice(0, 500) + "\n... (use --write to save full file)" : e.content
|
|
386
386
|
});
|
|
387
|
-
c.steps && (
|
|
387
|
+
c.steps && (S.log(""), S.box({
|
|
388
388
|
title: "Migration Steps",
|
|
389
389
|
message: c.steps
|
|
390
|
-
})), !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`));
|
|
391
391
|
}
|
|
392
392
|
//#endregion
|
|
393
393
|
//#region src/init.ts
|
|
394
|
-
var
|
|
395
|
-
function
|
|
396
|
-
if (!
|
|
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
397
|
return e;
|
|
398
398
|
}
|
|
399
|
-
var
|
|
399
|
+
var J = [
|
|
400
400
|
{
|
|
401
401
|
dep: "next",
|
|
402
402
|
name: "nextjs",
|
|
@@ -410,26 +410,26 @@ var K = [
|
|
|
410
410
|
{
|
|
411
411
|
dep: "@solidjs/start",
|
|
412
412
|
name: "solidstart",
|
|
413
|
-
pluginPackage: "@fluenti/
|
|
413
|
+
pluginPackage: "@fluenti/solid"
|
|
414
414
|
},
|
|
415
415
|
{
|
|
416
416
|
dep: "vue",
|
|
417
417
|
name: "vue",
|
|
418
|
-
pluginPackage: "@fluenti/
|
|
418
|
+
pluginPackage: "@fluenti/vue"
|
|
419
419
|
},
|
|
420
420
|
{
|
|
421
421
|
dep: "solid-js",
|
|
422
422
|
name: "solid",
|
|
423
|
-
pluginPackage: "@fluenti/
|
|
423
|
+
pluginPackage: "@fluenti/solid"
|
|
424
424
|
},
|
|
425
425
|
{
|
|
426
426
|
dep: "react",
|
|
427
427
|
name: "react",
|
|
428
|
-
pluginPackage: "@fluenti/
|
|
428
|
+
pluginPackage: "@fluenti/react"
|
|
429
429
|
}
|
|
430
430
|
];
|
|
431
|
-
function
|
|
432
|
-
for (let t of
|
|
431
|
+
function Y(e) {
|
|
432
|
+
for (let t of J) if (t.dep in e) return {
|
|
433
433
|
name: t.name,
|
|
434
434
|
pluginPackage: t.pluginPackage
|
|
435
435
|
};
|
|
@@ -438,7 +438,7 @@ function q(e) {
|
|
|
438
438
|
pluginPackage: null
|
|
439
439
|
};
|
|
440
440
|
}
|
|
441
|
-
function
|
|
441
|
+
function X(e) {
|
|
442
442
|
let t = e.locales.map((e) => `'${e}'`).join(", ");
|
|
443
443
|
return `import { defineConfig } from '@fluenti/cli'
|
|
444
444
|
|
|
@@ -452,66 +452,66 @@ export default defineConfig({
|
|
|
452
452
|
})
|
|
453
453
|
`;
|
|
454
454
|
}
|
|
455
|
-
async function
|
|
456
|
-
let t =
|
|
457
|
-
if (!
|
|
458
|
-
|
|
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
459
|
return;
|
|
460
460
|
}
|
|
461
|
-
let n = JSON.parse(
|
|
461
|
+
let n = JSON.parse(m(t, "utf-8")), r = Y({
|
|
462
462
|
...n.dependencies,
|
|
463
463
|
...n.devDependencies
|
|
464
464
|
});
|
|
465
|
-
|
|
466
|
-
let i =
|
|
467
|
-
if (
|
|
468
|
-
|
|
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
469
|
return;
|
|
470
470
|
}
|
|
471
|
-
let a = await
|
|
471
|
+
let a = await S.prompt("Source locale?", {
|
|
472
472
|
type: "text",
|
|
473
473
|
default: "en",
|
|
474
474
|
placeholder: "en"
|
|
475
475
|
});
|
|
476
476
|
if (typeof a == "symbol") return;
|
|
477
|
-
let o = await
|
|
477
|
+
let o = await S.prompt("Target locales (comma-separated)?", {
|
|
478
478
|
type: "text",
|
|
479
479
|
default: "ja,zh-CN",
|
|
480
480
|
placeholder: "ja,zh-CN"
|
|
481
481
|
});
|
|
482
482
|
if (typeof o == "symbol") return;
|
|
483
|
-
let s = await
|
|
483
|
+
let s = await S.prompt("Catalog format?", {
|
|
484
484
|
type: "select",
|
|
485
485
|
options: ["po", "json"],
|
|
486
486
|
initial: "po"
|
|
487
487
|
});
|
|
488
488
|
if (typeof s == "symbol") return;
|
|
489
489
|
let c = o.split(",").map((e) => e.trim()).filter(Boolean);
|
|
490
|
-
|
|
491
|
-
for (let e of c)
|
|
492
|
-
|
|
490
|
+
q(a);
|
|
491
|
+
for (let e of c) q(e);
|
|
492
|
+
h(i, X({
|
|
493
493
|
sourceLocale: a,
|
|
494
494
|
locales: [a, ...c.filter((e) => e !== a)],
|
|
495
495
|
format: s
|
|
496
|
-
}), "utf-8"),
|
|
497
|
-
let l =
|
|
498
|
-
|
|
499
|
-
let
|
|
500
|
-
if (
|
|
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
501
|
let e = {
|
|
502
502
|
...n,
|
|
503
503
|
scripts: {
|
|
504
|
-
...
|
|
505
|
-
...
|
|
504
|
+
...p,
|
|
505
|
+
...g
|
|
506
506
|
}
|
|
507
507
|
};
|
|
508
|
-
|
|
508
|
+
h(t, JSON.stringify(e, null, 2) + "\n", "utf-8"), S.success("Added i18n:extract and i18n:compile scripts to package.json");
|
|
509
509
|
}
|
|
510
|
-
|
|
510
|
+
S.log(""), S.box({
|
|
511
511
|
title: "Next steps",
|
|
512
512
|
message: [
|
|
513
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
|
|
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
515
|
"3. Run: npx fluenti extract",
|
|
516
516
|
"4. Translate your messages",
|
|
517
517
|
"5. Run: npx fluenti compile"
|
|
@@ -520,47 +520,24 @@ async function Y(e) {
|
|
|
520
520
|
}
|
|
521
521
|
//#endregion
|
|
522
522
|
//#region src/cli.ts
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
format: "po",
|
|
528
|
-
include: ["./src/**/*.{vue,tsx,jsx,ts,js}"],
|
|
529
|
-
compileOutDir: "./src/locales/compiled"
|
|
530
|
-
};
|
|
531
|
-
async function Z(e) {
|
|
532
|
-
let t = e ? [x(e)] : [
|
|
533
|
-
x("fluenti.config.ts"),
|
|
534
|
-
x("fluenti.config.js"),
|
|
535
|
-
x("fluenti.config.mjs")
|
|
536
|
-
];
|
|
537
|
-
for (let e of t) if (h(e)) try {
|
|
538
|
-
let { createJiti: t } = await import("jiti"), n = await t(import.meta.url).import(e), r = n.default ?? n;
|
|
539
|
-
return {
|
|
540
|
-
...X,
|
|
541
|
-
...r
|
|
542
|
-
};
|
|
543
|
-
} catch {
|
|
544
|
-
f.warn(`Failed to load config from ${e}, using defaults`);
|
|
545
|
-
}
|
|
546
|
-
return X;
|
|
547
|
-
}
|
|
548
|
-
function Q(e, n) {
|
|
549
|
-
if (!h(e)) return {};
|
|
550
|
-
let r = _(e, "utf-8");
|
|
551
|
-
return n === "json" ? c(r) : t(r);
|
|
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);
|
|
552
527
|
}
|
|
553
|
-
function $(e, t,
|
|
554
|
-
g(
|
|
528
|
+
function $(e, t, n) {
|
|
529
|
+
p(g(e), { recursive: !0 }), h(e, n === "json" ? i(t) : c(t), "utf-8");
|
|
555
530
|
}
|
|
556
|
-
async function
|
|
557
|
-
if (
|
|
531
|
+
async function ne(t, n) {
|
|
532
|
+
if (_(t) === ".vue") try {
|
|
558
533
|
let { extractFromVue: e } = await import("./vue-extractor-iUl6SUkv.js").then((e) => e.n);
|
|
559
534
|
return e(n, t);
|
|
535
|
+
} catch {
|
|
536
|
+
return S.warn(`Skipping ${t}: install @vue/compiler-sfc to extract from .vue files`), [];
|
|
560
537
|
}
|
|
561
538
|
return e(n, t);
|
|
562
539
|
}
|
|
563
|
-
var
|
|
540
|
+
var re = b({
|
|
564
541
|
meta: {
|
|
565
542
|
name: "extract",
|
|
566
543
|
description: "Extract messages from source files"
|
|
@@ -582,23 +559,23 @@ var ie = u({
|
|
|
582
559
|
}
|
|
583
560
|
},
|
|
584
561
|
async run({ args: e }) {
|
|
585
|
-
let t = await
|
|
586
|
-
|
|
587
|
-
let n = await
|
|
562
|
+
let t = await l(e.config);
|
|
563
|
+
S.info(`Extracting messages from ${t.include.join(", ")}`);
|
|
564
|
+
let n = await C(t.include), r = [];
|
|
588
565
|
for (let e of n) {
|
|
589
|
-
let t = await
|
|
566
|
+
let t = await ne(e, m(e, "utf-8"));
|
|
590
567
|
r.push(...t);
|
|
591
568
|
}
|
|
592
|
-
|
|
593
|
-
let
|
|
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;
|
|
594
571
|
for (let e of t.locales) {
|
|
595
|
-
let n =
|
|
596
|
-
$(n,
|
|
597
|
-
let
|
|
598
|
-
|
|
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}`);
|
|
599
576
|
}
|
|
600
577
|
}
|
|
601
|
-
}),
|
|
578
|
+
}), ie = b({
|
|
602
579
|
meta: {
|
|
603
580
|
name: "compile",
|
|
604
581
|
description: "Compile message catalogs to JS modules"
|
|
@@ -615,26 +592,26 @@ var ie = u({
|
|
|
615
592
|
}
|
|
616
593
|
},
|
|
617
594
|
async run({ args: e }) {
|
|
618
|
-
let
|
|
619
|
-
|
|
620
|
-
let
|
|
621
|
-
for (let e of
|
|
622
|
-
let
|
|
623
|
-
|
|
624
|
-
let
|
|
625
|
-
for (let e of
|
|
626
|
-
let { code:
|
|
627
|
-
if (
|
|
628
|
-
|
|
629
|
-
for (let e of r.missing)
|
|
630
|
-
} else
|
|
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}`);
|
|
631
608
|
}
|
|
632
|
-
let
|
|
633
|
-
|
|
634
|
-
let
|
|
635
|
-
|
|
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}`);
|
|
636
613
|
}
|
|
637
|
-
}),
|
|
614
|
+
}), ae = b({
|
|
638
615
|
meta: {
|
|
639
616
|
name: "stats",
|
|
640
617
|
description: "Show translation progress"
|
|
@@ -644,9 +621,9 @@ var ie = u({
|
|
|
644
621
|
description: "Path to config file"
|
|
645
622
|
} },
|
|
646
623
|
async run({ args: e }) {
|
|
647
|
-
let t = await
|
|
624
|
+
let t = await l(e.config), n = t.format === "json" ? ".json" : ".po", r = [];
|
|
648
625
|
for (let e of t.locales) {
|
|
649
|
-
let i = Q(
|
|
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) + "%" : "—";
|
|
650
627
|
r.push({
|
|
651
628
|
locale: e,
|
|
652
629
|
total: o,
|
|
@@ -654,11 +631,11 @@ var ie = u({
|
|
|
654
631
|
pct: c
|
|
655
632
|
});
|
|
656
633
|
}
|
|
657
|
-
|
|
658
|
-
for (let e of r)
|
|
659
|
-
|
|
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("");
|
|
660
637
|
}
|
|
661
|
-
}),
|
|
638
|
+
}), oe = b({
|
|
662
639
|
meta: {
|
|
663
640
|
name: "translate",
|
|
664
641
|
description: "Translate messages using AI (Claude Code or Codex CLI)"
|
|
@@ -684,36 +661,36 @@ var ie = u({
|
|
|
684
661
|
}
|
|
685
662
|
},
|
|
686
663
|
async run({ args: e }) {
|
|
687
|
-
let t = await
|
|
664
|
+
let t = await l(e.config), n = e.provider;
|
|
688
665
|
if (n !== "claude" && n !== "codex") {
|
|
689
|
-
|
|
666
|
+
S.error(`Invalid provider "${n}". Use "claude" or "codex".`);
|
|
690
667
|
return;
|
|
691
668
|
}
|
|
692
669
|
let r = parseInt(e["batch-size"] ?? "50", 10);
|
|
693
670
|
if (isNaN(r) || r < 1) {
|
|
694
|
-
|
|
671
|
+
S.error("Invalid batch-size. Must be a positive integer.");
|
|
695
672
|
return;
|
|
696
673
|
}
|
|
697
674
|
let i = e.locale ? [e.locale] : t.locales.filter((e) => e !== t.sourceLocale);
|
|
698
675
|
if (i.length === 0) {
|
|
699
|
-
|
|
676
|
+
S.warn("No target locales to translate.");
|
|
700
677
|
return;
|
|
701
678
|
}
|
|
702
|
-
|
|
679
|
+
S.info(`Translating with ${n} (batch size: ${r})`);
|
|
703
680
|
let a = t.format === "json" ? ".json" : ".po";
|
|
704
681
|
for (let e of i) {
|
|
705
|
-
|
|
706
|
-
let 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({
|
|
707
684
|
provider: n,
|
|
708
685
|
sourceLocale: t.sourceLocale,
|
|
709
686
|
targetLocale: e,
|
|
710
687
|
catalog: o,
|
|
711
688
|
batchSize: r
|
|
712
689
|
});
|
|
713
|
-
c > 0 ? ($(i, s, t.format),
|
|
690
|
+
c > 0 ? ($(i, s, t.format), S.success(` ${e}: ${c} messages translated`)) : S.success(` ${e}: already fully translated`);
|
|
714
691
|
}
|
|
715
692
|
}
|
|
716
|
-
}),
|
|
693
|
+
}), se = b({
|
|
717
694
|
meta: {
|
|
718
695
|
name: "migrate",
|
|
719
696
|
description: "Migrate from another i18n library using AI"
|
|
@@ -738,38 +715,38 @@ var ie = u({
|
|
|
738
715
|
async run({ args: e }) {
|
|
739
716
|
let t = e.provider;
|
|
740
717
|
if (t !== "claude" && t !== "codex") {
|
|
741
|
-
|
|
718
|
+
S.error(`Invalid provider "${t}". Use "claude" or "codex".`);
|
|
742
719
|
return;
|
|
743
720
|
}
|
|
744
|
-
await
|
|
721
|
+
await G({
|
|
745
722
|
from: e.from,
|
|
746
723
|
provider: t,
|
|
747
724
|
write: e.write ?? !1
|
|
748
725
|
});
|
|
749
726
|
}
|
|
750
727
|
});
|
|
751
|
-
|
|
728
|
+
x(b({
|
|
752
729
|
meta: {
|
|
753
730
|
name: "fluenti",
|
|
754
731
|
version: "0.0.1",
|
|
755
732
|
description: "Compile-time i18n for modern frameworks"
|
|
756
733
|
},
|
|
757
734
|
subCommands: {
|
|
758
|
-
init:
|
|
735
|
+
init: b({
|
|
759
736
|
meta: {
|
|
760
737
|
name: "init",
|
|
761
738
|
description: "Initialize Fluenti in your project"
|
|
762
739
|
},
|
|
763
740
|
args: {},
|
|
764
741
|
async run() {
|
|
765
|
-
await
|
|
742
|
+
await Z({ cwd: process.cwd() });
|
|
766
743
|
}
|
|
767
744
|
}),
|
|
768
|
-
extract:
|
|
769
|
-
compile:
|
|
770
|
-
stats:
|
|
771
|
-
translate:
|
|
772
|
-
migrate:
|
|
745
|
+
extract: re,
|
|
746
|
+
compile: ie,
|
|
747
|
+
stats: ae,
|
|
748
|
+
translate: oe,
|
|
749
|
+
migrate: se
|
|
773
750
|
}
|
|
774
751
|
}));
|
|
775
752
|
//#endregion
|