@dialexjs/cli 0.1.0 → 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/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
- export type { AnalysisConfig, AnalysisIssue, AnalysisOptions, AnalysisResult, AnalyzedDictionary, DictionaryFile, IssueCode, IssueLevel, Leaf, LeafInfo, LeafKind, Project, ProjectDictionary, Reference, SourceRange, StaticConfig, StaticConfigResult };
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
@@ -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;;wBAyBjD,qBAAqB,IAAI;wBAazB,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"}
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-D4PmfqxM.mjs";
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-D4PmfqxM.mjs";
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 chokidar from "chokidar";
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
- logger.success(`Exported ${orderedKeys.size} key${orderedKeys.size === 1 ? "" : "s"} to ${files.length} ${format} file${files.length === 1 ? "" : "s"} in ${path.relative(root, outDir) || "."}`);
768
- if (skippedFunctions > 0) logger.warn(`${skippedFunctions} function value${skippedFunctions === 1 ? "" : "s"} skipped (not translatable as text)`);
769
- for (const file of project.unsupported) logger.warn(`Skipped ${file}: unsupported dictionary shape`);
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
- logger.success(`Imported ${result.created + result.updated} value${result.created + result.updated === 1 ? "" : "s"} (${result.created} added, ${result.updated} updated)`);
928
- 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 ? ", ..." : ""}`);
929
- if (result.rejected.length > 0) logger.warn(`${result.rejected.length} value${result.rejected.length === 1 ? "" : "s"} rejected: ${result.rejected.slice(0, 5).join("; ")}`);
930
- 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(", ")}`);
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
- logger.success(`Nothing to translate from "${source}".`);
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
- logger.info(`Would translate ${tasks.length} string${tasks.length === 1 ? "" : "s"} from "${source}":`);
1012
- for (const t of tasks) logger.log(` ${pc.dim(t.locale)} ${t.key} ${pc.dim(JSON.stringify(t.source))}`);
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
- logger.success(`Translated ${result.translated} of ${tasks.length} string${tasks.length === 1 ? "" : "s"}.`);
1051
- if (result.rejected.length > 0) logger.warn(`${result.rejected.length} left unchanged: ${result.rejected.slice(0, 5).join("; ")}${result.rejected.length > 5 ? "; ..." : ""}`);
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.0");
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.0");
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
- logger.error(err.message || String(err));
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
  });