@dialexjs/cli 0.1.0 → 0.2.1
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/api.d.mts +18 -1
- package/dist/api.d.mts.map +1 -1
- package/dist/api.mjs +2 -2
- package/dist/index.mjs +112 -117
- package/dist/index.mjs.map +1 -1
- package/dist/{project-D4PmfqxM.mjs → project-gkFBurqG.mjs} +112 -3
- package/dist/project-gkFBurqG.mjs.map +1 -0
- package/package.json +2 -2
- package/dist/project-D4PmfqxM.mjs.map +0 -1
package/dist/api.d.mts
CHANGED
|
@@ -57,8 +57,11 @@ interface Leaf {
|
|
|
57
57
|
* the file does not have an editable shape; throws on syntax errors.
|
|
58
58
|
*/
|
|
59
59
|
export declare function parseDictionaryText(file: string, text: string): DictionaryFile | undefined;
|
|
60
|
+
/** Loads a dictionary file; returns `undefined` when its shape is not editable. */
|
|
61
|
+
export declare function loadDictionaryFile(file: string): Promise<DictionaryFile | undefined>;
|
|
60
62
|
/** The source text with all edits applied, without writing anything. */
|
|
61
63
|
export declare function renderDictionaryFile(df: DictionaryFile): string;
|
|
64
|
+
export declare function saveDictionaryFile(df: DictionaryFile): Promise<void>;
|
|
62
65
|
export declare function listLocales(df: DictionaryFile): string[];
|
|
63
66
|
/** Lists every leaf (string, function, or other value) of a locale, in source order. */
|
|
64
67
|
export declare function listLeaves(df: DictionaryFile, locale: string): Leaf[];
|
|
@@ -225,5 +228,19 @@ export declare function loadStaticProject(root: string, configFile?: string): Pr
|
|
|
225
228
|
/** `home` + `["nav", "about"]` -> `home.nav.about` */
|
|
226
229
|
export declare const toKey: (dictionary: string, keyPath: string[]) => string;
|
|
227
230
|
//#endregion
|
|
228
|
-
|
|
231
|
+
//#region src/commands/generate.d.ts
|
|
232
|
+
interface GenerateOptions {
|
|
233
|
+
cwd?: string;
|
|
234
|
+
watch?: boolean;
|
|
235
|
+
output?: string;
|
|
236
|
+
config?: string;
|
|
237
|
+
json?: boolean;
|
|
238
|
+
}
|
|
239
|
+
export declare function generateDictionaries(root: string, options?: GenerateOptions): {
|
|
240
|
+
files: string[];
|
|
241
|
+
outputPath: string;
|
|
242
|
+
dtsPath: string;
|
|
243
|
+
};
|
|
244
|
+
//#endregion
|
|
245
|
+
export type { AnalysisConfig, AnalysisIssue, AnalysisOptions, AnalysisResult, AnalyzedDictionary, DictionaryFile, GenerateOptions, IssueCode, IssueLevel, Leaf, LeafInfo, LeafKind, Project, ProjectDictionary, Reference, SourceRange, StaticConfig, StaticConfigResult };
|
|
229
246
|
//# sourceMappingURL=api.d.mts.map
|
package/dist/api.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.mts","names":[],"sources":["../src/utils/dictionary-edit.ts","../src/analysis.ts","../src/utils/static-config.ts","../src/utils/project.ts"],"mappings":";;;;;;;;;;;;;;;KAgBK;EAAS;GAAe;;qBAEhB;UAII;EACf;;EAEA;EACA,KAAK,QAAQ,kBAAkB;;EAE/B,YAAY;;EAEZ;;;;;;EAMA;;;UAIe;EACf;IAAS;IAAc;;EACvB;IAAO;IAAc;;;KAGX;UAEK;EACf;EACA,MAAM;;EAEN;;;;;;wBAgIc,oBAAoB,cAAc,eAAe;;
|
|
1
|
+
{"version":3,"file":"api.d.mts","names":[],"sources":["../src/utils/dictionary-edit.ts","../src/analysis.ts","../src/utils/static-config.ts","../src/utils/project.ts","../src/commands/generate.ts"],"mappings":";;;;;;;;;;;;;;;KAgBK;EAAS;GAAe;;qBAEhB;UAII;EACf;;EAEA;EACA,KAAK,QAAQ,kBAAkB;;EAE/B,YAAY;;EAEZ;;;;;;EAMA;;;UAIe;EACf;IAAS;IAAc;;EACvB;IAAO;IAAc;;;KAGX;UAEK;EACf;EACA,MAAM;;EAEN;;;;;;wBAgIc,oBAAoB,cAAc,eAAe;;wBAoB3C,mBAAmB,eAAe,QAAQ;;wBAKhD,qBAAqB,IAAI;wBAKnB,mBAAmB,IAAI,iBAAiB;wBAQ9C,YAAY,IAAI;;wBAahB,WAAW,IAAI,gBAAgB,iBAAiB;wBAoChD,QAAQ,IAAI,gBAAgB,gBAAgB;wBAI5C,UAAU,IAAI,gBAAgB,gBAAgB;KA+BlD;;;;;wBAMI,UACd,IAAI,gBACJ,gBACA,gBACA,gBACC;;;;;;wBAsBa,SACd,IAAI,gBACJ,oBACA,kBACA,gBACA,aAAa;;;;;wBAwCC,WACd,IAAI,gBACJ,gBACA,kBACC;;wBAMa,mBAAmB,IAAI,iBAAiB;;wBAKxC,cACd,IAAI,gBACJ,gBACA;;;;;;;;;;;KCnXU;KAEA;UAeK;;EAEf;EACA,OAAO;EACP,MAAM;EACN;;EAEA,QAAQ;;EAER;EACA;;EAEA;;EAEA;;UAGe;EACf;EACA;EACA;;UAGe;EACf;EACA,QAAQ;;EAER;;EAEA;;EAEA;;EAEA;;UAGe;EACf;EACA;;UAGe;EACf;;EAEA;;EAEA;;EAEA,QAAQ,YAAY,YAAY;;EAEhC;;EAEA,KAAK;;UAGU;EACf,QAAQ;EACR,cAAc;;EAEd;EACA;;qBAKW;UAqBI;;EAEf;EACA;;EAEA;EACA;;;wBAIc,eAAe,eAAe;wBA4IxB,eAAe,SAAS,kBAAkB,QAAQ;;wBA+NxD,aAAa,cAAc;;;;;;;;UCte1B;EACf;EACA;EACA;EACA,WAAW;EACX;EACA;;UAGe;EACf,QAAQ;;EAER;;EAEA;;qBAMW,uBAAuB;;wBAiFpB,kBAAkB,cAAc,gBAA4B;;wBA8C5D,iBAAiB,cAAc,sBAAsB;;;UC5JpD;;EAEf;EACA;EACA,IAAI;;UAGW;EACf;EACA,QAAQ;EACR,cAAc;;EAEd;;EAEA;;;;;;wBAgCoB,YAAY,cAAc,sBAAsB,QAAQ;;;;;wBASxD,kBAAkB,cAAc,sBAAsB,QAAQ;;qBAMvE,QAAK,oBAAsB;;;UC7DvB;EACf;EACA;EACA;EACA;EACA;;wBAGc,qBACd,cACA,UAAS;EAET;EACA;EACA"}
|
package/dist/api.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { S as renderDictionaryFile, _ as listLeaves, a as parseStaticConfig, b as locationOf, c as analyzeProject, d as TODO_PREFIX, f as copyLeaf, g as hasPath, h as getString, i as DEFAULT_STATIC_CONFIG, l as relativeFile, m as getLeafSource, n as loadStaticProject, o as readStaticConfig, p as dictionaryLocation, r as toKey, s as DEFAULT_SOURCE_GLOB, t as loadProject, u as scanReferences, v as listLocales, w as setString, x as parseDictionaryText } from "./project-
|
|
2
|
-
export { DEFAULT_SOURCE_GLOB, DEFAULT_STATIC_CONFIG, TODO_PREFIX, analyzeProject, copyLeaf, dictionaryLocation, getLeafSource, getString, hasPath, listLeaves, listLocales, loadProject, loadStaticProject, locationOf, parseDictionaryText, parseStaticConfig, readStaticConfig, relativeFile, renderDictionaryFile, scanReferences, setString, toKey };
|
|
1
|
+
import { C as saveDictionaryFile, S as renderDictionaryFile, T as generateDictionaries, _ as listLeaves, a as parseStaticConfig, b as locationOf, c as analyzeProject, d as TODO_PREFIX, f as copyLeaf, g as hasPath, h as getString, i as DEFAULT_STATIC_CONFIG, l as relativeFile, m as getLeafSource, n as loadStaticProject, o as readStaticConfig, p as dictionaryLocation, r as toKey, s as DEFAULT_SOURCE_GLOB, t as loadProject, u as scanReferences, v as listLocales, w as setString, x as parseDictionaryText, y as loadDictionaryFile } from "./project-gkFBurqG.mjs";
|
|
2
|
+
export { DEFAULT_SOURCE_GLOB, DEFAULT_STATIC_CONFIG, TODO_PREFIX, analyzeProject, copyLeaf, dictionaryLocation, generateDictionaries, getLeafSource, getString, hasPath, listLeaves, listLocales, loadDictionaryFile, loadProject, loadStaticProject, locationOf, parseDictionaryText, parseStaticConfig, readStaticConfig, relativeFile, renderDictionaryFile, saveDictionaryFile, scanReferences, setString, toKey };
|
package/dist/index.mjs
CHANGED
|
@@ -1,109 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as saveDictionaryFile, _ as listLeaves, c as analyzeProject, d as TODO_PREFIX, f as copyLeaf, g as hasPath, h as getString, r as toKey, t as loadProject, v as listLocales, w as setString, y as loadDictionaryFile } from "./project-
|
|
2
|
+
import { C as saveDictionaryFile, D as logger, E as runGenerate, T as generateDictionaries, _ as listLeaves, c as analyzeProject, d as TODO_PREFIX, f as copyLeaf, g as hasPath, h as getString, r as toKey, t as loadProject, v as listLocales, w as setString, y as loadDictionaryFile } from "./project-gkFBurqG.mjs";
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import fg from "fast-glob";
|
|
7
|
-
import
|
|
8
|
-
import { generateDts, resolveI18nConfig } from "dialexjs/scanner";
|
|
7
|
+
import { resolveI18nConfig } from "dialexjs/scanner";
|
|
9
8
|
import pc from "picocolors";
|
|
10
9
|
import * as p from "@clack/prompts";
|
|
11
10
|
import { loadFile, parseModule, writeFile } from "magicast";
|
|
12
11
|
import { addNuxtModule, addVitePlugin } from "magicast/helpers";
|
|
13
12
|
import { argumentSignature, getArguments, isIcuStructured, parseMessage } from "dialexjs/icu";
|
|
14
|
-
//#region src/utils/logger.ts
|
|
15
|
-
const logger = {
|
|
16
|
-
info: (msg) => console.log(pc.blue("ℹ ") + msg),
|
|
17
|
-
success: (msg) => console.log(pc.green("✔ ") + pc.bold(msg)),
|
|
18
|
-
warn: (msg) => console.warn(pc.yellow("⚠ ") + msg),
|
|
19
|
-
error: (msg) => console.error(pc.red("✖ ") + pc.bold(msg)),
|
|
20
|
-
log: (msg) => console.log(msg)
|
|
21
|
-
};
|
|
22
|
-
//#endregion
|
|
23
|
-
//#region src/commands/generate.ts
|
|
24
|
-
function generateDictionaries(root, options = {}) {
|
|
25
|
-
const inlineConfig = options.config ? { configFile: options.config } : {};
|
|
26
|
-
const config = resolveI18nConfig(root, inlineConfig);
|
|
27
|
-
const include = config.include || "**/*.content.ts";
|
|
28
|
-
const files = fg.sync(include, {
|
|
29
|
-
cwd: root,
|
|
30
|
-
absolute: true,
|
|
31
|
-
ignore: [
|
|
32
|
-
"**/node_modules/**",
|
|
33
|
-
"**/dist/**",
|
|
34
|
-
"**/.next/**"
|
|
35
|
-
]
|
|
36
|
-
});
|
|
37
|
-
const srcDir = fs.existsSync(path.join(root, "src")) ? path.join(root, "src") : root;
|
|
38
|
-
const targetOutput = options.output ? path.resolve(root, options.output) : path.join(srcDir, "i18n.generated.ts");
|
|
39
|
-
const targetDir = path.dirname(targetOutput);
|
|
40
|
-
generateDts(root, files, config.locales);
|
|
41
|
-
const dtsPath = path.join(srcDir, "dialex-env.d.ts");
|
|
42
|
-
const importLines = [];
|
|
43
|
-
const dictIdentifiers = [];
|
|
44
|
-
files.forEach((file, idx) => {
|
|
45
|
-
let rel = path.relative(targetDir, file).replace(/\\/g, "/");
|
|
46
|
-
rel = rel.replace(/\.ts$/, ".js");
|
|
47
|
-
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
48
|
-
const id = `dict_${idx}`;
|
|
49
|
-
importLines.push(`import ${id} from "${rel}";`);
|
|
50
|
-
dictIdentifiers.push(id);
|
|
51
|
-
});
|
|
52
|
-
const content = `// Auto-generated by dialex CLI. Do not edit directly.
|
|
53
|
-
/* eslint-disable */
|
|
54
|
-
// @ts-nocheck
|
|
55
|
-
${importLines.join("\n")}
|
|
56
|
-
|
|
57
|
-
export const dictionaries = [
|
|
58
|
-
${dictIdentifiers.join(",\n ")}
|
|
59
|
-
];
|
|
60
|
-
|
|
61
|
-
export const dictionaryMap = {
|
|
62
|
-
${dictIdentifiers.map((id) => ` [${id}.name]: ${id}.dictionary,`).join("\n")}
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
export default dictionaries;
|
|
66
|
-
`;
|
|
67
|
-
fs.mkdirSync(targetDir, { recursive: true });
|
|
68
|
-
if ((fs.existsSync(targetOutput) ? fs.readFileSync(targetOutput, "utf-8") : "") !== content) fs.writeFileSync(targetOutput, content, "utf-8");
|
|
69
|
-
return {
|
|
70
|
-
files,
|
|
71
|
-
outputPath: targetOutput,
|
|
72
|
-
dtsPath
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
async function runGenerate(options = {}) {
|
|
76
|
-
const root = options.cwd || process.cwd();
|
|
77
|
-
const doGenerate = () => {
|
|
78
|
-
try {
|
|
79
|
-
const { files, outputPath, dtsPath } = generateDictionaries(root, options);
|
|
80
|
-
logger.success(`Generated dictionaries for ${files.length} file${files.length === 1 ? "" : "s"}:`);
|
|
81
|
-
logger.info(`→ ${path.relative(root, outputPath)}`);
|
|
82
|
-
logger.info(`→ ${path.relative(root, dtsPath)}`);
|
|
83
|
-
} catch (err) {
|
|
84
|
-
logger.error(`Generation failed: ${err.message || String(err)}`);
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
|
-
doGenerate();
|
|
88
|
-
if (options.watch) {
|
|
89
|
-
logger.info("Watching for dictionary changes (-w, --watch)...");
|
|
90
|
-
const inlineConfig = options.config ? { configFile: options.config } : {};
|
|
91
|
-
const pattern = resolveI18nConfig(root, inlineConfig).include || "**/*.content.ts";
|
|
92
|
-
chokidar.watch(pattern, {
|
|
93
|
-
cwd: root,
|
|
94
|
-
ignoreInitial: true,
|
|
95
|
-
ignored: [
|
|
96
|
-
"**/node_modules/**",
|
|
97
|
-
"**/dist/**",
|
|
98
|
-
"**/.next/**"
|
|
99
|
-
]
|
|
100
|
-
}).on("all", (event, filePath) => {
|
|
101
|
-
logger.info(`[${event}] ${filePath} changed, regenerating...`);
|
|
102
|
-
doGenerate();
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
//#endregion
|
|
107
13
|
//#region src/utils/detector.ts
|
|
108
14
|
/**
|
|
109
15
|
* Automatically inspects package.json dependencies and configuration files
|
|
@@ -310,6 +216,13 @@ async function runInit(options = {}) {
|
|
|
310
216
|
return;
|
|
311
217
|
}
|
|
312
218
|
localesInput = inputLocales.trim();
|
|
219
|
+
if (options.ai === void 0 && options.noAi === void 0) {
|
|
220
|
+
const confirmAi = await p.confirm({
|
|
221
|
+
message: "Configure AI agent support (Claude/Cursor skills, .mcp.json, AGENTS.md)?",
|
|
222
|
+
initialValue: true
|
|
223
|
+
});
|
|
224
|
+
if (!p.isCancel(confirmAi)) options.ai = confirmAi;
|
|
225
|
+
}
|
|
313
226
|
} else logger.info(`Auto-detected framework: ${pc.bold(framework)} (${detected.matchedRule})`);
|
|
314
227
|
const localeList = Array.from(/* @__PURE__ */ new Set([defaultLocale, ...localesInput.split(",").map((s) => s.trim()).filter(Boolean)]));
|
|
315
228
|
const configPath = fs.existsSync(path.join(root, "dialex.config.ts")) ? path.join(root, "dialex.config.ts") : fs.existsSync(path.join(root, "i18n.config.ts")) ? path.join(root, "i18n.config.ts") : path.join(root, "dialex.config.ts");
|
|
@@ -410,6 +323,62 @@ ${localeList.map((loc) => ` ${loc}: {
|
|
|
410
323
|
logger.success("Added \"i18n:generate\" script to package.json");
|
|
411
324
|
}
|
|
412
325
|
} catch {}
|
|
326
|
+
if (options.ai === true || options.yes && options.ai !== false && options.noAi !== true) try {
|
|
327
|
+
const claudeSkillDir = path.join(root, ".claude/skills/dialex");
|
|
328
|
+
fs.mkdirSync(claudeSkillDir, { recursive: true });
|
|
329
|
+
fs.writeFileSync(path.join(claudeSkillDir, "SKILL.md"), `---
|
|
330
|
+
name: dialex
|
|
331
|
+
description: Guide for internationalizing JavaScript and TypeScript apps with Dialex. Use when adding translations, creating content dictionaries, configuring locales, or using Dialex with frameworks like Next.js, React, Express, Hono, Fastify, SvelteKit, and Nuxt.
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
# Dialex Internationalization
|
|
335
|
+
|
|
336
|
+
Dialex is a high-performance, type-safe internationalization toolchain for JavaScript and TypeScript.
|
|
337
|
+
|
|
338
|
+
## Core Concepts
|
|
339
|
+
- Dictionaries are defined with \`defineDictionary("name", { ... })\` in \`*.content.ts\` files.
|
|
340
|
+
- Keys must match across all locales.
|
|
341
|
+
- ICU message syntax is supported for plurals and selects.
|
|
342
|
+
- Parity is validated at compile time with \`dialex check\`.
|
|
343
|
+
|
|
344
|
+
## Useful Commands
|
|
345
|
+
- \`dialex check --json\`: Check dictionary parity and references.
|
|
346
|
+
- \`dialex check --fix\`: Automatically insert missing keys marked with [TODO].
|
|
347
|
+
- \`dialex generate\`: Rebuild standalone dictionary bundle and types.
|
|
348
|
+
- \`dialex translate --locale <locale>\`: Automatically translate missing keys.
|
|
349
|
+
`, "utf-8");
|
|
350
|
+
const mcpConfigPath = path.join(root, ".mcp.json");
|
|
351
|
+
let mcpConfig = {};
|
|
352
|
+
if (fs.existsSync(mcpConfigPath)) try {
|
|
353
|
+
mcpConfig = JSON.parse(fs.readFileSync(mcpConfigPath, "utf-8"));
|
|
354
|
+
} catch {
|
|
355
|
+
mcpConfig = {};
|
|
356
|
+
}
|
|
357
|
+
mcpConfig.mcpServers = mcpConfig.mcpServers || {};
|
|
358
|
+
mcpConfig.mcpServers.dialex = mcpConfig.mcpServers.dialex || {
|
|
359
|
+
command: "npx",
|
|
360
|
+
args: ["@dialexjs/mcp"]
|
|
361
|
+
};
|
|
362
|
+
fs.writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + "\n", "utf-8");
|
|
363
|
+
const agentsMdPath = path.join(root, "AGENTS.md");
|
|
364
|
+
const dialexAgentSnippet = `
|
|
365
|
+
## Internationalization (Dialex)
|
|
366
|
+
|
|
367
|
+
This project uses [Dialex](https://github.com/awaiden/dialex) for type-safe internationalization.
|
|
368
|
+
- Dictionaries are in \`*.content.ts\` files using \`defineDictionary\`.
|
|
369
|
+
- Run \`dialex check --json\` to verify parity.
|
|
370
|
+
- Run \`dialex check --fix\` to insert missing keys marked with \`[TODO]\`.
|
|
371
|
+
- Run \`dialex generate\` or \`npm run i18n:generate\` to compile dictionary bundles and update types.
|
|
372
|
+
`;
|
|
373
|
+
if (!fs.existsSync(agentsMdPath)) fs.writeFileSync(agentsMdPath, dialexAgentSnippet.trimStart(), "utf-8");
|
|
374
|
+
else {
|
|
375
|
+
const existing = fs.readFileSync(agentsMdPath, "utf-8");
|
|
376
|
+
if (!existing.includes("Dialex")) fs.writeFileSync(agentsMdPath, existing.trimEnd() + "\n" + dialexAgentSnippet, "utf-8");
|
|
377
|
+
}
|
|
378
|
+
logger.success("Configured AI assistant support (.claude/skills, .mcp.json, AGENTS.md)");
|
|
379
|
+
} catch (err) {
|
|
380
|
+
logger.warn(`Could not finish AI configuration: ${err.message || String(err)}`);
|
|
381
|
+
}
|
|
413
382
|
logger.log("");
|
|
414
383
|
logger.info(pc.bold(`Quick start for ${framework.toUpperCase()}:`));
|
|
415
384
|
switch (framework) {
|
|
@@ -764,15 +733,21 @@ ${units.join("\n")}
|
|
|
764
733
|
`);
|
|
765
734
|
}
|
|
766
735
|
}
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
return {
|
|
736
|
+
const quiet = options.json === true;
|
|
737
|
+
const result = {
|
|
738
|
+
success: true,
|
|
771
739
|
files,
|
|
772
740
|
keys: orderedKeys.size,
|
|
773
741
|
skippedFunctions,
|
|
774
742
|
skippedFiles: project.unsupported
|
|
775
743
|
};
|
|
744
|
+
if (quiet) console.log(JSON.stringify(result, null, 2));
|
|
745
|
+
else {
|
|
746
|
+
logger.success(`Exported ${orderedKeys.size} key${orderedKeys.size === 1 ? "" : "s"} to ${files.length} ${format} file${files.length === 1 ? "" : "s"} in ${path.relative(root, outDir) || "."}`);
|
|
747
|
+
if (skippedFunctions > 0) logger.warn(`${skippedFunctions} function value${skippedFunctions === 1 ? "" : "s"} skipped (not translatable as text)`);
|
|
748
|
+
for (const file of project.unsupported) logger.warn(`Skipped ${file}: unsupported dictionary shape`);
|
|
749
|
+
}
|
|
750
|
+
return result;
|
|
776
751
|
}
|
|
777
752
|
//#endregion
|
|
778
753
|
//#region src/commands/import.ts
|
|
@@ -891,7 +866,9 @@ async function runImport(options) {
|
|
|
891
866
|
const entries = parseEntries(fs.readFileSync(file, "utf-8"), format, options.locale ?? fallbackLocale);
|
|
892
867
|
const project = await loadProject(root, options.config);
|
|
893
868
|
const byName = new Map(project.dictionaries.map((d) => [d.name, d]));
|
|
869
|
+
const quiet = options.json === true;
|
|
894
870
|
const result = {
|
|
871
|
+
success: true,
|
|
895
872
|
created: 0,
|
|
896
873
|
updated: 0,
|
|
897
874
|
skipped: [],
|
|
@@ -924,10 +901,14 @@ async function runImport(options) {
|
|
|
924
901
|
}
|
|
925
902
|
}
|
|
926
903
|
for (const dict of project.dictionaries) if (touched.has(dict.rel)) await saveDictionaryFile(dict.df);
|
|
927
|
-
|
|
928
|
-
if (
|
|
929
|
-
|
|
930
|
-
|
|
904
|
+
if (result.rejected.length > 0) result.success = false;
|
|
905
|
+
if (quiet) console.log(JSON.stringify(result, null, 2));
|
|
906
|
+
else {
|
|
907
|
+
logger.success(`Imported ${result.created + result.updated} value${result.created + result.updated === 1 ? "" : "s"} (${result.created} added, ${result.updated} updated)`);
|
|
908
|
+
if (result.unknown.length > 0) logger.warn(`${result.unknown.length} unknown key${result.unknown.length === 1 ? "" : "s"} ignored (use --allow-new to add): ${result.unknown.slice(0, 5).join(", ")}${result.unknown.length > 5 ? ", ..." : ""}`);
|
|
909
|
+
if (result.rejected.length > 0) logger.warn(`${result.rejected.length} value${result.rejected.length === 1 ? "" : "s"} rejected: ${result.rejected.slice(0, 5).join("; ")}`);
|
|
910
|
+
if (result.skipped.length > 0) logger.warn(`${result.skipped.length} non-text value${result.skipped.length === 1 ? "" : "s"} left unchanged: ${result.skipped.slice(0, 5).join(", ")}`);
|
|
911
|
+
}
|
|
931
912
|
return result;
|
|
932
913
|
}
|
|
933
914
|
//#endregion
|
|
@@ -997,19 +978,25 @@ async function runTranslate(options = {}) {
|
|
|
997
978
|
}
|
|
998
979
|
}
|
|
999
980
|
}
|
|
981
|
+
const quiet = options.json === true;
|
|
1000
982
|
const result = {
|
|
983
|
+
success: true,
|
|
1001
984
|
dryRun: options.dryRun === true,
|
|
1002
985
|
pending: tasks,
|
|
1003
986
|
translated: 0,
|
|
1004
987
|
rejected: []
|
|
1005
988
|
};
|
|
1006
989
|
if (tasks.length === 0) {
|
|
1007
|
-
|
|
990
|
+
if (quiet) console.log(JSON.stringify(result, null, 2));
|
|
991
|
+
else logger.success(`Nothing to translate from "${source}".`);
|
|
1008
992
|
return result;
|
|
1009
993
|
}
|
|
1010
994
|
if (options.dryRun) {
|
|
1011
|
-
|
|
1012
|
-
|
|
995
|
+
if (quiet) console.log(JSON.stringify(result, null, 2));
|
|
996
|
+
else {
|
|
997
|
+
logger.info(`Would translate ${tasks.length} string${tasks.length === 1 ? "" : "s"} from "${source}":`);
|
|
998
|
+
for (const t of tasks) logger.log(` ${pc.dim(t.locale)} ${t.key} ${pc.dim(JSON.stringify(t.source))}`);
|
|
999
|
+
}
|
|
1013
1000
|
return result;
|
|
1014
1001
|
}
|
|
1015
1002
|
const touched = /* @__PURE__ */ new Set();
|
|
@@ -1023,7 +1010,7 @@ async function runTranslate(options = {}) {
|
|
|
1023
1010
|
});
|
|
1024
1011
|
if (sendable.length === 0) continue;
|
|
1025
1012
|
const unique = [...new Set(sendable.map((t) => t.source))];
|
|
1026
|
-
logger.info(`Translating ${unique.length} string${unique.length === 1 ? "" : "s"} to "${locale}" with ${provider.name ?? "provider"}...`);
|
|
1013
|
+
if (!quiet) logger.info(`Translating ${unique.length} string${unique.length === 1 ? "" : "s"} to "${locale}" with ${provider.name ?? "provider"}...`);
|
|
1027
1014
|
const translations = await provider.translate(unique, source, locale);
|
|
1028
1015
|
if (translations.length !== unique.length) throw new Error(`Provider returned ${translations.length} translations for ${unique.length} strings.`);
|
|
1029
1016
|
const byText = new Map(unique.map((text, i) => [text, translations[i]]));
|
|
@@ -1047,37 +1034,45 @@ async function runTranslate(options = {}) {
|
|
|
1047
1034
|
}
|
|
1048
1035
|
}
|
|
1049
1036
|
for (const dict of touched) await saveDictionaryFile(dict.df);
|
|
1050
|
-
|
|
1051
|
-
if (
|
|
1037
|
+
if (result.rejected.length > 0) result.success = false;
|
|
1038
|
+
if (quiet) console.log(JSON.stringify(result, null, 2));
|
|
1039
|
+
else {
|
|
1040
|
+
logger.success(`Translated ${result.translated} of ${tasks.length} string${tasks.length === 1 ? "" : "s"}.`);
|
|
1041
|
+
if (result.rejected.length > 0) logger.warn(`${result.rejected.length} left unchanged: ${result.rejected.slice(0, 5).join("; ")}${result.rejected.length > 5 ? "; ..." : ""}`);
|
|
1042
|
+
}
|
|
1052
1043
|
return result;
|
|
1053
1044
|
}
|
|
1054
1045
|
//#endregion
|
|
1055
1046
|
//#region src/index.ts
|
|
1056
1047
|
const program = new Command();
|
|
1057
|
-
program.name("dialex").description("Dialex CLI - High-performance, type-safe internationalization compiler and tooling").version("0.1
|
|
1058
|
-
program.command("generate").alias("gen").description("Generate standalone dictionary bundle and TypeScript declarations").option("-w, --watch", "Watch dictionary files for changes").option("-o, --output <path>", "Custom output file path for generated dictionaries").option("-c, --config <path>", "Custom config path").action(async (opts) => {
|
|
1048
|
+
program.name("dialex").description("Dialex CLI - High-performance, type-safe internationalization compiler and tooling").version("0.2.1");
|
|
1049
|
+
program.command("generate").alias("gen").description("Generate standalone dictionary bundle and TypeScript declarations").option("-w, --watch", "Watch dictionary files for changes").option("-o, --output <path>", "Custom output file path for generated dictionaries").option("-c, --config <path>", "Custom config path").option("--json", "Print machine-readable JSON").action(async (opts) => {
|
|
1059
1050
|
await runGenerate(opts);
|
|
1060
1051
|
});
|
|
1061
|
-
program.command("init").description("Initialize Dialex in the current project").option("-f, --framework <framework>", "Target framework (hono, express, fastify, koa, nestjs, next, react)").option("-d, --default-locale <locale>", "Default locale (e.g. en)").option("-l, --locales <locales>", "Comma-separated supported locales (e.g. en,tr)").option("-y, --yes", "Skip prompts and use defaults").action(async (opts) => {
|
|
1052
|
+
program.command("init").description("Initialize Dialex in the current project").option("-f, --framework <framework>", "Target framework (hono, express, fastify, koa, nestjs, next, react)").option("-d, --default-locale <locale>", "Default locale (e.g. en)").option("-l, --locales <locales>", "Comma-separated supported locales (e.g. en,tr)").option("-y, --yes", "Skip prompts and use defaults").option("--ai", "Configure AI agent support (.claude/skills, .mcp.json, AGENTS.md)").option("--no-ai", "Skip AI agent configuration").action(async (opts) => {
|
|
1062
1053
|
await runInit(opts);
|
|
1063
1054
|
});
|
|
1064
1055
|
program.command("check").alias("lint").description("Verify dictionary key parity and locale completeness").option("-c, --config <path>", "Custom config path").option("--json", "Print machine-readable JSON").option("--github", "Print GitHub Actions annotations").option("--fix", "Insert missing keys marked with [TODO]").option("--src <globs...>", "Source globs to scan for dictionary references").action(async (opts) => {
|
|
1065
1056
|
await runCheck(opts);
|
|
1066
1057
|
});
|
|
1067
|
-
program.command("export").description("Export dictionary strings for translators (json, csv, or xliff)").option("-f, --format <format>", "Output format: json, csv, xliff", "json").option("-o, --out <dir>", "Output directory", "i18n-export").option("-l, --locale <locales...>", "Limit to these locales").option("-c, --config <path>", "Custom config path").action(async (opts) => {
|
|
1058
|
+
program.command("export").description("Export dictionary strings for translators (json, csv, or xliff)").option("-f, --format <format>", "Output format: json, csv, xliff", "json").option("-o, --out <dir>", "Output directory", "i18n-export").option("-l, --locale <locales...>", "Limit to these locales").option("-c, --config <path>", "Custom config path").option("--json", "Print machine-readable JSON").action(async (opts) => {
|
|
1068
1059
|
await runExport(opts);
|
|
1069
1060
|
});
|
|
1070
|
-
program.command("import <file>").description("Write translated strings from a json, csv, or xliff file back into dictionaries").option("-f, --format <format>", "Input format (inferred from the extension)").option("-l, --locale <locale>", "Locale for single-locale files").option("--allow-new", "Add keys that do not exist in any locale yet").option("-c, --config <path>", "Custom config path").action(async (file, opts) => {
|
|
1061
|
+
program.command("import <file>").description("Write translated strings from a json, csv, or xliff file back into dictionaries").option("-f, --format <format>", "Input format (inferred from the extension)").option("-l, --locale <locale>", "Locale for single-locale files").option("--allow-new", "Add keys that do not exist in any locale yet").option("-c, --config <path>", "Custom config path").option("--json", "Print machine-readable JSON").action(async (file, opts) => {
|
|
1071
1062
|
await runImport({
|
|
1072
1063
|
file,
|
|
1073
1064
|
...opts
|
|
1074
1065
|
});
|
|
1075
1066
|
});
|
|
1076
|
-
program.command("translate").description("Fill missing translations with the provider configured in dialex.config.ts").option("-l, --locale <locales...>", "Target locales (default: all configured)").option("-s, --source <locale>", "Source locale (default: translate.sourceLocale or defaultLocale)").option("--dry-run", "List what would be translated without calling the provider").option("-c, --config <path>", "Custom config path").action(async (opts) => {
|
|
1067
|
+
program.command("translate").description("Fill missing translations with the provider configured in dialex.config.ts").option("-l, --locale <locales...>", "Target locales (default: all configured)").option("-s, --source <locale>", "Source locale (default: translate.sourceLocale or defaultLocale)").option("--dry-run", "List what would be translated without calling the provider").option("-c, --config <path>", "Custom config path").option("--json", "Print machine-readable JSON").action(async (opts) => {
|
|
1077
1068
|
try {
|
|
1078
1069
|
await runTranslate(opts);
|
|
1079
1070
|
} catch (err) {
|
|
1080
|
-
|
|
1071
|
+
if (opts.json) console.log(JSON.stringify({
|
|
1072
|
+
success: false,
|
|
1073
|
+
error: err.message || String(err)
|
|
1074
|
+
}, null, 2));
|
|
1075
|
+
else logger.error(err.message || String(err));
|
|
1081
1076
|
process.exit(1);
|
|
1082
1077
|
}
|
|
1083
1078
|
});
|