@dialexjs/cli 0.7.2 → 0.9.2

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/index.mjs CHANGED
@@ -1,27 +1,18 @@
1
1
  #!/usr/bin/env node
2
- import { A as scanFiles, C as lockPath, E as writeLock, F as getString, I as hasPath, L as listLeaves, M as copyLeaf, R as listLocales, S as lockFor, T as record, U as saveDictionaryFile, W as setString, a as renderGenerated, d as resolveDialexConfig, f as logger, i as generateDictionaries, j as TODO_PREFIX, m as analyzeProject, o as runGenerate, r as toKey, t as loadProject, w as readLock, x as isStale, y as LOCK_FILE, z as loadDictionaryFile } from "./project-Dz0FbydQ.mjs";
2
+ import { A as scanFiles, B as listLeaves, C as lockPath, E as writeLock, G as saveDictionaryFile, H as loadDictionaryFile, I as copyLeaf, K as TODO_PREFIX, L as setString, R as getString, S as lockFor, T as record, V as listLocales, a as generateDictionaries, d as resolveDialexConfig, f as logger, i as runGenerate, p as analyzeProject, r as toKey, s as renderGenerated, t as loadProject, w as readLock, x as isStale, y as LOCK_FILE, z as hasPath } from "./project-BTpZGNjh.mjs";
3
3
  import { Command } from "commander";
4
- import fs from "node:fs";
5
4
  import path from "node:path";
6
5
  import pc from "picocolors";
7
- import { fileURLToPath } from "node:url";
6
+ import fs from "node:fs";
8
7
  import { loadFile, parseModule, writeFile } from "magicast";
8
+ import { fileURLToPath } from "node:url";
9
9
  import { argumentSignature, getArguments, isIcuStructured, parseMessage } from "dialexjs/icu";
10
10
  import * as p from "@clack/prompts";
11
11
  import { addNuxtModule } from "magicast/helpers";
12
12
  //#region package.json
13
- var version = "0.7.2";
13
+ var version = "0.9.2";
14
14
  //#endregion
15
- //#region src/commands/check.ts
16
- function addIssue(diag, issue) {
17
- const level = issue.level === "error" ? "error" : "warning";
18
- (level === "error" ? diag.errors : diag.warnings).push(issue.message);
19
- diag.annotations.push({
20
- level,
21
- message: issue.message,
22
- line: issue.range ? issue.range.start.line + 1 : void 0
23
- });
24
- }
15
+ //#region src/commands/check/fix.ts
25
16
  /**
26
17
  * Inserts keys that exist in one locale but not in another, copying the default-locale value
27
18
  * (strings get a `[TODO]` prefix). Returns how many keys were inserted.
@@ -49,9 +40,22 @@ async function applyFixes(files, configuredLocales, defaultLocale) {
49
40
  }
50
41
  return fixed;
51
42
  }
43
+ //#endregion
44
+ //#region src/commands/check/report.ts
45
+ function addIssue(diag, issue) {
46
+ const level = issue.level === "error" ? "error" : "warning";
47
+ (level === "error" ? diag.errors : diag.warnings).push(issue.message);
48
+ diag.annotations.push({
49
+ level,
50
+ message: issue.message,
51
+ line: issue.range ? issue.range.start.line + 1 : void 0
52
+ });
53
+ }
52
54
  function escapeAnnotation(value) {
53
55
  return value.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
54
56
  }
57
+ //#endregion
58
+ //#region src/commands/check/stale-generated.ts
55
59
  /**
56
60
  * What a formatter (Prettier, Biome, oxfmt) can change without changing the code: whitespace,
57
61
  * quote style, quoted object keys and trailing commas. Comparing this form keeps `check` quiet
@@ -108,6 +112,8 @@ function staleGeneratedFiles(root, options) {
108
112
  rewritten
109
113
  };
110
114
  }
115
+ //#endregion
116
+ //#region src/commands/check/index.ts
111
117
  async function runCheck(options = {}) {
112
118
  const root = options.cwd || process.cwd();
113
119
  const quiet = options.json === true;
@@ -300,7 +306,7 @@ ${units.join("\n")}
300
306
  return result;
301
307
  }
302
308
  //#endregion
303
- //#region src/commands/import.ts
309
+ //#region src/commands/import/formats.ts
304
310
  const unescapeXml = (value) => value.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, "\"").replace(/&apos;/g, "'").replace(/&amp;/g, "&");
305
311
  function parseCsv(text) {
306
312
  const rows = [];
@@ -383,6 +389,8 @@ function inferFormat(file) {
383
389
  if (ext === ".xlf" || ext === ".xliff") return "xliff";
384
390
  throw new Error(`Cannot infer format from "${ext}". Pass --format json|csv|xliff.`);
385
391
  }
392
+ //#endregion
393
+ //#region src/commands/import/icu-guard.ts
386
394
  /**
387
395
  * Why `value` must not replace the current text, if it is an ICU message that is invalid or uses
388
396
  * different arguments than the same key in another locale.
@@ -408,6 +416,8 @@ function icuProblem(df, defaultLocale, locale, keyPath, value) {
408
416
  }
409
417
  }
410
418
  }
419
+ //#endregion
420
+ //#region src/commands/import/index.ts
411
421
  async function runImport(options) {
412
422
  const root = options.cwd || process.cwd();
413
423
  const file = path.resolve(root, options.file);
@@ -510,6 +520,16 @@ function detectFramework(root) {
510
520
  confidence: "high",
511
521
  matchedRule: "package.json dependencies (@nestjs/core)"
512
522
  };
523
+ if ("@adonisjs/core" in allDeps) return {
524
+ framework: "adonis",
525
+ confidence: "high",
526
+ matchedRule: "package.json dependencies (@adonisjs/core)"
527
+ };
528
+ if ("@hapi/hapi" in allDeps) return {
529
+ framework: "hapi",
530
+ confidence: "high",
531
+ matchedRule: "package.json dependencies (@hapi/hapi)"
532
+ };
513
533
  if ("fastify" in allDeps) return {
514
534
  framework: "fastify",
515
535
  confidence: "high",
@@ -535,11 +555,51 @@ function detectFramework(root) {
535
555
  confidence: "high",
536
556
  matchedRule: "package.json dependencies (hono)"
537
557
  };
558
+ if ("@trpc/server" in allDeps) return {
559
+ framework: "trpc",
560
+ confidence: "high",
561
+ matchedRule: "package.json dependencies (@trpc/server)"
562
+ };
563
+ if ("@builder.io/qwik" in allDeps) return {
564
+ framework: "qwik",
565
+ confidence: "high",
566
+ matchedRule: "package.json dependencies (@builder.io/qwik)"
567
+ };
568
+ if ("react-native" in allDeps || "expo" in allDeps) return {
569
+ framework: "react-native",
570
+ confidence: "high",
571
+ matchedRule: "package.json dependencies (react-native/expo)"
572
+ };
573
+ if ("@react-router/dev" in allDeps) return {
574
+ framework: "react-router",
575
+ confidence: "high",
576
+ matchedRule: "package.json dependencies (@react-router/dev)"
577
+ };
578
+ if ("solid-js" in allDeps) return {
579
+ framework: "solid",
580
+ confidence: "high",
581
+ matchedRule: "package.json dependencies (solid-js)"
582
+ };
583
+ if ("svelte" in allDeps) return {
584
+ framework: "svelte",
585
+ confidence: "high",
586
+ matchedRule: "package.json dependencies (svelte)"
587
+ };
538
588
  if ("vue" in allDeps && !("react" in allDeps)) return {
539
589
  framework: "vue",
540
590
  confidence: "high",
541
591
  matchedRule: "package.json dependencies (vue)"
542
592
  };
593
+ if ("preact" in allDeps) return {
594
+ framework: "preact",
595
+ confidence: "high",
596
+ matchedRule: "package.json dependencies (preact)"
597
+ };
598
+ if ("lit" in allDeps && !("react" in allDeps)) return {
599
+ framework: "lit",
600
+ confidence: "high",
601
+ matchedRule: "package.json dependencies (lit)"
602
+ };
543
603
  if ("react" in allDeps || "vite" in allDeps) return {
544
604
  framework: "react",
545
605
  confidence: "high",
@@ -554,7 +614,8 @@ function detectFramework(root) {
554
614
  for (const [framework, base] of [
555
615
  ["nuxt", "nuxt.config"],
556
616
  ["sveltekit", "svelte.config"],
557
- ["astro", "astro.config"]
617
+ ["astro", "astro.config"],
618
+ ["react-router", "react-router.config"]
558
619
  ]) if ([
559
620
  "js",
560
621
  "mjs",
@@ -576,6 +637,125 @@ function detectFramework(root) {
576
637
  };
577
638
  }
578
639
  //#endregion
640
+ //#region src/commands/init/ai-setup.ts
641
+ /** Writes `.agents/skills/dialex/SKILL.md`, merges `.mcp.json` and adds a note to AGENTS.md. */
642
+ function setupAiSupport(root) {
643
+ try {
644
+ const skillDir = path.join(root, ".agents/skills/dialex");
645
+ fs.mkdirSync(skillDir, { recursive: true });
646
+ fs.writeFileSync(path.join(skillDir, "SKILL.md"), `---
647
+ name: dialex
648
+ 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.
649
+ ---
650
+
651
+ # Dialex Internationalization
652
+
653
+ Dialex is a high-performance, type-safe internationalization toolchain for JavaScript and TypeScript.
654
+
655
+ ## Core Concepts
656
+ - Dictionaries are defined with \`defineDictionary("name", { ... })\` in \`*.content.ts\` files.
657
+ - Keys must match across all locales.
658
+ - ICU message syntax is supported for plurals and selects.
659
+ - Parity is validated at compile time with \`dialex check\`.
660
+
661
+ ## Useful Commands
662
+ - \`dialex check --json\`: Check dictionary parity and references.
663
+ - \`dialex check --fix\`: Automatically insert missing keys marked with [TODO].
664
+ - \`dialex generate\`: Rebuild standalone dictionary bundle and types.
665
+ - \`dialex translate --locale <locale>\`: Automatically translate missing keys.
666
+ `, "utf-8");
667
+ const mcpConfigPath = path.join(root, ".mcp.json");
668
+ let mcpConfig = {};
669
+ if (fs.existsSync(mcpConfigPath)) try {
670
+ mcpConfig = JSON.parse(fs.readFileSync(mcpConfigPath, "utf-8"));
671
+ } catch {
672
+ mcpConfig = {};
673
+ }
674
+ mcpConfig.mcpServers = mcpConfig.mcpServers || {};
675
+ mcpConfig.mcpServers.dialex = mcpConfig.mcpServers.dialex || {
676
+ command: "npx",
677
+ args: ["@dialexjs/mcp"]
678
+ };
679
+ fs.writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + "\n", "utf-8");
680
+ const agentsMdPath = path.join(root, "AGENTS.md");
681
+ const dialexAgentSnippet = `
682
+ ## Internationalization (Dialex)
683
+
684
+ This project uses [Dialex](https://github.com/awaiden/dialex) for type-safe internationalization.
685
+ - Dictionaries are in \`*.content.ts\` files using \`defineDictionary\`.
686
+ - Run \`dialex check --json\` to verify parity.
687
+ - Run \`dialex check --fix\` to insert missing keys marked with \`[TODO]\`.
688
+ - Run \`dx generate\` (alias of \`dialex\`) or \`npm run dx:generate\` to compile dictionary bundles and update types.
689
+ `;
690
+ if (!fs.existsSync(agentsMdPath)) fs.writeFileSync(agentsMdPath, dialexAgentSnippet.trimStart(), "utf-8");
691
+ else {
692
+ const existing = fs.readFileSync(agentsMdPath, "utf-8");
693
+ if (!existing.includes("Dialex")) fs.writeFileSync(agentsMdPath, existing.trimEnd() + "\n" + dialexAgentSnippet, "utf-8");
694
+ }
695
+ logger.success("Configured AI assistant support (.agents/skills, .mcp.json, AGENTS.md)");
696
+ } catch (err) {
697
+ logger.warn(`Could not finish AI configuration: ${err.message || String(err)}`);
698
+ }
699
+ }
700
+ //#endregion
701
+ //#region src/commands/init/config-file.ts
702
+ /** Creates `dialex.config.ts`, or updates the locales of the one that exists. */
703
+ async function writeDialexConfig(root, framework, defaultLocale, localeList) {
704
+ 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");
705
+ const configFileName = path.basename(configPath);
706
+ const generatedOutput = framework === "react-router" ? "app/dialex.generated.ts" : framework === "adonis" ? "start/dialex.generated.ts" : void 0;
707
+ try {
708
+ if (fs.existsSync(configPath)) {
709
+ const mod = await loadFile(configPath);
710
+ if (mod && mod.exports.default) {
711
+ if (mod.exports.default.$args && mod.exports.default.$args[0]) {
712
+ mod.exports.default.$args[0].defaultLocale = defaultLocale;
713
+ mod.exports.default.$args[0].locales = localeList;
714
+ if (generatedOutput) mod.exports.default.$args[0].output = generatedOutput;
715
+ }
716
+ await writeFile(mod, configPath);
717
+ logger.success(`Updated ${pc.bold(configFileName)} via Magicast`);
718
+ }
719
+ } else {
720
+ const mod = parseModule(`import { defineConfig } from "dialexjs";
721
+
722
+ export default defineConfig({});
723
+ `);
724
+ mod.exports.default.$args[0].defaultLocale = defaultLocale;
725
+ mod.exports.default.$args[0].locales = localeList;
726
+ if (generatedOutput) mod.exports.default.$args[0].output = generatedOutput;
727
+ await writeFile(mod, configPath);
728
+ logger.success(`Created ${pc.bold(configFileName)} via Magicast`);
729
+ }
730
+ } catch (err) {
731
+ logger.warn(`Magicast config update notice: ${err.message || String(err)}`);
732
+ const fallback = `import { defineConfig } from "dialexjs";
733
+
734
+ export default defineConfig({
735
+ defaultLocale: "${defaultLocale}",
736
+ locales: [${localeList.map((l) => `"${l}"`).join(", ")}],${generatedOutput ? `\n output: "${generatedOutput}",` : ""}
737
+ });
738
+ `;
739
+ fs.writeFileSync(configPath, fallback, "utf-8");
740
+ }
741
+ }
742
+ /** Nuxt reads the generated file through its module; it has to be registered in the Nuxt config. */
743
+ async function registerNuxtModule(root) {
744
+ const nuxtConfig = [
745
+ "nuxt.config.ts",
746
+ "nuxt.config.js",
747
+ "nuxt.config.mjs"
748
+ ].map((f) => path.join(root, f)).find((f) => fs.existsSync(f));
749
+ if (nuxtConfig) try {
750
+ const nMod = await loadFile(nuxtConfig);
751
+ addNuxtModule(nMod, "dialexjs/nuxt", "dialex", {});
752
+ await writeFile(nMod, nuxtConfig);
753
+ logger.success(`Registered dialexjs/nuxt in ${pc.bold(path.basename(nuxtConfig))} via Magicast`);
754
+ } catch {
755
+ logger.warn("Could not update the Nuxt config; add the dialexjs/nuxt module manually.");
756
+ }
757
+ }
758
+ //#endregion
579
759
  //#region src/utils/package-json.ts
580
760
  /** Version of this CLI, which the other Dialex packages share. Read from the nearest package.json. */
581
761
  function cliVersion() {
@@ -627,184 +807,9 @@ function addRequiredPackages(pkg, version = cliVersion()) {
627
807
  return added;
628
808
  }
629
809
  //#endregion
630
- //#region src/commands/init.ts
631
- async function runInit(options = {}) {
632
- const root = options.cwd || process.cwd();
633
- const detected = detectFramework(root);
634
- const isInteractive = !options.yes && process.stdin.isTTY;
635
- let framework = options.framework || detected.framework;
636
- let defaultLocale = options.defaultLocale || "en";
637
- let localesInput = options.locales || "en,tr";
638
- if (isInteractive) {
639
- p.intro(pc.bgCyan(pc.black(" dialex init ")));
640
- const frameworkOptions = [
641
- {
642
- value: "hono",
643
- label: "Hono",
644
- hint: detected.framework === "hono" ? "detected" : void 0
645
- },
646
- {
647
- value: "express",
648
- label: "Express",
649
- hint: detected.framework === "express" ? "detected" : void 0
650
- },
651
- {
652
- value: "fastify",
653
- label: "Fastify",
654
- hint: detected.framework === "fastify" ? "detected" : void 0
655
- },
656
- {
657
- value: "koa",
658
- label: "Koa",
659
- hint: detected.framework === "koa" ? "detected" : void 0
660
- },
661
- {
662
- value: "nestjs",
663
- label: "NestJS",
664
- hint: detected.framework === "nestjs" ? "detected" : void 0
665
- },
666
- {
667
- value: "next",
668
- label: "Next.js",
669
- hint: detected.framework === "next" ? "detected" : void 0
670
- },
671
- {
672
- value: "react",
673
- label: "React / Vite",
674
- hint: detected.framework === "react" ? "detected" : void 0
675
- },
676
- {
677
- value: "elysia",
678
- label: "Elysia",
679
- hint: detected.framework === "elysia" ? "detected" : void 0
680
- },
681
- {
682
- value: "sveltekit",
683
- label: "SvelteKit",
684
- hint: detected.framework === "sveltekit" ? "detected" : void 0
685
- },
686
- {
687
- value: "astro",
688
- label: "Astro",
689
- hint: detected.framework === "astro" ? "detected" : void 0
690
- },
691
- {
692
- value: "vue",
693
- label: "Vue / Vite",
694
- hint: detected.framework === "vue" ? "detected" : void 0
695
- },
696
- {
697
- value: "nuxt",
698
- label: "Nuxt",
699
- hint: detected.framework === "nuxt" ? "detected" : void 0
700
- }
701
- ];
702
- const selectedFramework = await p.select({
703
- message: `Framework detected as ${pc.bold(detected.framework)} (${detected.matchedRule}). Select framework:`,
704
- options: frameworkOptions,
705
- initialValue: detected.framework
706
- });
707
- if (p.isCancel(selectedFramework)) {
708
- p.cancel("Operation cancelled.");
709
- return;
710
- }
711
- framework = selectedFramework;
712
- const inputDefault = await p.text({
713
- message: "What is your default locale?",
714
- initialValue: "en",
715
- validate: (v) => !v || !v.trim() ? "Default locale cannot be empty" : void 0
716
- });
717
- if (p.isCancel(inputDefault)) {
718
- p.cancel("Operation cancelled.");
719
- return;
720
- }
721
- defaultLocale = inputDefault.trim();
722
- const inputLocales = await p.text({
723
- message: "Comma-separated list of supported locales:",
724
- initialValue: `${defaultLocale},tr`,
725
- validate: (v) => !v || !v.trim() ? "Locales list cannot be empty" : void 0
726
- });
727
- if (p.isCancel(inputLocales)) {
728
- p.cancel("Operation cancelled.");
729
- return;
730
- }
731
- localesInput = inputLocales.trim();
732
- if (options.ai === void 0 && options.noAi === void 0) {
733
- const confirmAi = await p.confirm({
734
- message: "Configure AI agent support (Claude/Cursor skills, .mcp.json, AGENTS.md)?",
735
- initialValue: true
736
- });
737
- if (!p.isCancel(confirmAi)) options.ai = confirmAi;
738
- }
739
- } else logger.info(`Auto-detected framework: ${pc.bold(framework)} (${detected.matchedRule})`);
740
- const localeList = Array.from(/* @__PURE__ */ new Set([defaultLocale, ...localesInput.split(",").map((s) => s.trim()).filter(Boolean)]));
741
- 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");
742
- const configFileName = path.basename(configPath);
743
- try {
744
- if (fs.existsSync(configPath)) {
745
- const mod = await loadFile(configPath);
746
- if (mod && mod.exports.default) {
747
- if (mod.exports.default.$args && mod.exports.default.$args[0]) {
748
- mod.exports.default.$args[0].defaultLocale = defaultLocale;
749
- mod.exports.default.$args[0].locales = localeList;
750
- }
751
- await writeFile(mod, configPath);
752
- logger.success(`Updated ${pc.bold(configFileName)} via Magicast`);
753
- }
754
- } else {
755
- const mod = parseModule(`import { defineConfig } from "dialexjs";
756
-
757
- export default defineConfig({});
758
- `);
759
- mod.exports.default.$args[0].defaultLocale = defaultLocale;
760
- mod.exports.default.$args[0].locales = localeList;
761
- await writeFile(mod, configPath);
762
- logger.success(`Created ${pc.bold(configFileName)} via Magicast`);
763
- }
764
- } catch (err) {
765
- logger.warn(`Magicast config update notice: ${err.message || String(err)}`);
766
- const fallback = `import { defineConfig } from "dialexjs";
767
-
768
- export default defineConfig({
769
- defaultLocale: "${defaultLocale}",
770
- locales: [${localeList.map((l) => `"${l}"`).join(", ")}],
771
- });
772
- `;
773
- fs.writeFileSync(configPath, fallback, "utf-8");
774
- }
775
- if (framework === "nuxt") {
776
- const nuxtConfig = [
777
- "nuxt.config.ts",
778
- "nuxt.config.js",
779
- "nuxt.config.mjs"
780
- ].map((f) => path.join(root, f)).find((f) => fs.existsSync(f));
781
- if (nuxtConfig) try {
782
- const nMod = await loadFile(nuxtConfig);
783
- addNuxtModule(nMod, "dialexjs/nuxt", "dialex", {});
784
- await writeFile(nMod, nuxtConfig);
785
- logger.success(`Registered dialexjs/nuxt in ${pc.bold(path.basename(nuxtConfig))} via Magicast`);
786
- } catch {
787
- logger.warn("Could not update the Nuxt config; add the dialexjs/nuxt module manually.");
788
- }
789
- }
790
- const targetDir = fs.existsSync(path.join(root, "src")) ? path.join(root, "src") : root;
791
- const dictPath = path.join(targetDir, "home.content.ts");
792
- if (!fs.existsSync(dictPath)) {
793
- const dictContent = `import { defineDictionary } from "dialexjs";
794
-
795
- export default defineDictionary("home", {
796
- ${localeList.map((loc) => ` ${loc}: {
797
- title: "Welcome to Dialex (${loc})!",
798
- description: "Type-safe internationalization.",
799
- greeting: (name: string) => "Hello, " + name + "!",
800
- },`).join("\n")}
801
- });
802
- `;
803
- fs.writeFileSync(dictPath, dictContent, "utf-8");
804
- logger.success(`Created starter dictionary at ${pc.bold(path.relative(root, dictPath))}`);
805
- } else logger.info("home.content.ts already exists, skipping creation.");
806
- generateDictionaries(root);
807
- logger.success("Generated types and standalone dictionary registry!");
810
+ //#region src/commands/init/dependencies.ts
811
+ /** Adds `dialexjs` and `@dialexjs/cli` to package.json, and the `dx:generate` script. */
812
+ function updatePackageJson(root) {
808
813
  const pkgPath = path.join(root, "package.json");
809
814
  if (fs.existsSync(pkgPath)) try {
810
815
  const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
@@ -822,139 +827,155 @@ ${localeList.map((loc) => ` ${loc}: {
822
827
  logger.warn("Could not update package.json; add dialexjs and @dialexjs/cli manually.");
823
828
  }
824
829
  else logger.warn("No package.json found; install dialexjs and @dialexjs/cli in your project.");
825
- if (options.ai === true || options.yes && options.ai !== false && options.noAi !== true) try {
826
- const skillDir = path.join(root, ".agents/skills/dialex");
827
- fs.mkdirSync(skillDir, { recursive: true });
828
- fs.writeFileSync(path.join(skillDir, "SKILL.md"), `---
829
- name: dialex
830
- 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.
831
- ---
832
-
833
- # Dialex Internationalization
834
-
835
- Dialex is a high-performance, type-safe internationalization toolchain for JavaScript and TypeScript.
836
-
837
- ## Core Concepts
838
- - Dictionaries are defined with \`defineDictionary("name", { ... })\` in \`*.content.ts\` files.
839
- - Keys must match across all locales.
840
- - ICU message syntax is supported for plurals and selects.
841
- - Parity is validated at compile time with \`dialex check\`.
842
-
843
- ## Useful Commands
844
- - \`dialex check --json\`: Check dictionary parity and references.
845
- - \`dialex check --fix\`: Automatically insert missing keys marked with [TODO].
846
- - \`dialex generate\`: Rebuild standalone dictionary bundle and types.
847
- - \`dialex translate --locale <locale>\`: Automatically translate missing keys.
848
- `, "utf-8");
849
- const mcpConfigPath = path.join(root, ".mcp.json");
850
- let mcpConfig = {};
851
- if (fs.existsSync(mcpConfigPath)) try {
852
- mcpConfig = JSON.parse(fs.readFileSync(mcpConfigPath, "utf-8"));
853
- } catch {
854
- mcpConfig = {};
855
- }
856
- mcpConfig.mcpServers = mcpConfig.mcpServers || {};
857
- mcpConfig.mcpServers.dialex = mcpConfig.mcpServers.dialex || {
858
- command: "npx",
859
- args: ["@dialexjs/mcp"]
860
- };
861
- fs.writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + "\n", "utf-8");
862
- const agentsMdPath = path.join(root, "AGENTS.md");
863
- const dialexAgentSnippet = `
864
- ## Internationalization (Dialex)
865
-
866
- This project uses [Dialex](https://github.com/awaiden/dialex) for type-safe internationalization.
867
- - Dictionaries are in \`*.content.ts\` files using \`defineDictionary\`.
868
- - Run \`dialex check --json\` to verify parity.
869
- - Run \`dialex check --fix\` to insert missing keys marked with \`[TODO]\`.
870
- - Run \`dx generate\` (alias of \`dialex\`) or \`npm run dx:generate\` to compile dictionary bundles and update types.
871
- `;
872
- if (!fs.existsSync(agentsMdPath)) fs.writeFileSync(agentsMdPath, dialexAgentSnippet.trimStart(), "utf-8");
873
- else {
874
- const existing = fs.readFileSync(agentsMdPath, "utf-8");
875
- if (!existing.includes("Dialex")) fs.writeFileSync(agentsMdPath, existing.trimEnd() + "\n" + dialexAgentSnippet, "utf-8");
876
- }
877
- logger.success("Configured AI assistant support (.agents/skills, .mcp.json, AGENTS.md)");
878
- } catch (err) {
879
- logger.warn(`Could not finish AI configuration: ${err.message || String(err)}`);
830
+ }
831
+ //#endregion
832
+ //#region src/commands/init/prompts.ts
833
+ /** The frameworks `dialex init` offers, in the order of the prompt: value and label. */
834
+ const FRAMEWORKS = [
835
+ ["hono", "Hono"],
836
+ ["express", "Express"],
837
+ ["fastify", "Fastify"],
838
+ ["koa", "Koa"],
839
+ ["nestjs", "NestJS"],
840
+ ["next", "Next.js"],
841
+ ["react", "React / Vite"],
842
+ ["elysia", "Elysia"],
843
+ ["sveltekit", "SvelteKit"],
844
+ ["astro", "Astro"],
845
+ ["vue", "Vue / Vite"],
846
+ ["nuxt", "Nuxt"],
847
+ ["svelte", "Svelte / Vite"],
848
+ ["solid", "Solid / Vite"],
849
+ ["react-router", "React Router"],
850
+ ["react-native", "React Native / Expo"],
851
+ ["preact", "Preact / Vite"],
852
+ ["qwik", "Qwik"],
853
+ ["lit", "Lit / Web Components"],
854
+ ["hapi", "Hapi"],
855
+ ["adonis", "AdonisJS"],
856
+ ["trpc", "tRPC"]
857
+ ];
858
+ /**
859
+ * Asks for the framework, the locales and (when not decided by a flag) AI support. Returns
860
+ * `undefined` when the user cancels. May set `options.ai`.
861
+ */
862
+ async function askInit(options, detected) {
863
+ p.intro(pc.bgCyan(pc.black(" dialex init ")));
864
+ const frameworkOptions = FRAMEWORKS.map(([value, label]) => ({
865
+ value,
866
+ label,
867
+ hint: detected.framework === value ? "detected" : void 0
868
+ }));
869
+ const selectedFramework = await p.select({
870
+ message: `Framework detected as ${pc.bold(detected.framework)} (${detected.matchedRule}). Select framework:`,
871
+ options: frameworkOptions,
872
+ initialValue: detected.framework
873
+ });
874
+ if (p.isCancel(selectedFramework)) {
875
+ p.cancel("Operation cancelled.");
876
+ return;
880
877
  }
881
- logger.log("");
882
- logger.info(pc.bold(`Quick start for ${framework.toUpperCase()}:`));
883
- switch (framework) {
884
- case "hono":
885
- logger.log(`
878
+ const framework = selectedFramework;
879
+ const inputDefault = await p.text({
880
+ message: "What is your default locale?",
881
+ initialValue: "en",
882
+ validate: (v) => !v || !v.trim() ? "Default locale cannot be empty" : void 0
883
+ });
884
+ if (p.isCancel(inputDefault)) {
885
+ p.cancel("Operation cancelled.");
886
+ return;
887
+ }
888
+ const defaultLocale = inputDefault.trim();
889
+ const inputLocales = await p.text({
890
+ message: "Comma-separated list of supported locales:",
891
+ initialValue: `${defaultLocale},tr`,
892
+ validate: (v) => !v || !v.trim() ? "Locales list cannot be empty" : void 0
893
+ });
894
+ if (p.isCancel(inputLocales)) {
895
+ p.cancel("Operation cancelled.");
896
+ return;
897
+ }
898
+ const localesInput = inputLocales.trim();
899
+ if (options.ai === void 0 && options.noAi === void 0) {
900
+ const confirmAi = await p.confirm({
901
+ message: "Configure AI agent support (Claude/Cursor skills, .mcp.json, AGENTS.md)?",
902
+ initialValue: true
903
+ });
904
+ if (!p.isCancel(confirmAi)) options.ai = confirmAi;
905
+ }
906
+ return {
907
+ framework,
908
+ defaultLocale,
909
+ localesInput
910
+ };
911
+ }
912
+ //#endregion
913
+ //#region src/commands/init/quickstart.ts
914
+ /** What `dialex init` prints for each framework, after the files are written. */
915
+ const SNIPPETS = {
916
+ hono: `
886
917
  import { Hono } from "hono";
887
- import { dialex } from "dialexjs/hono";
888
- import dictionaries from "./src/dialex.generated.js";
918
+ import { dialexHono } from "dialexjs/hono";
919
+ import { dialex } from "./src/dialex.generated.js";
889
920
 
890
921
  const app = new Hono();
891
- app.use("*", dialex({ dictionaries }));
922
+ app.use("*", dialexHono({ ...dialex }));
892
923
 
893
924
  app.get("/", (c) => {
894
925
  const dict = c.var.getDictionary("home");
895
926
  return c.json({ title: dict.title });
896
927
  });
897
- `);
898
- break;
899
- case "fastify":
900
- logger.log(`
928
+ `,
929
+ fastify: `
901
930
  import Fastify from "fastify";
902
931
  import { dialexPlugin } from "dialexjs/fastify";
903
- import dictionaries from "./src/dialex.generated.js";
932
+ import { dialex } from "./src/dialex.generated.js";
904
933
 
905
934
  const app = Fastify();
906
- await app.register(dialexPlugin, { dictionaries });
935
+ await app.register(dialexPlugin, { ...dialex });
907
936
 
908
937
  app.get("/", (req) => {
909
938
  const dict = req.getDictionary("home");
910
939
  return { title: dict.title };
911
940
  });
912
- `);
913
- break;
914
- case "express":
915
- logger.log(`
941
+ `,
942
+ express: `
916
943
  import express from "express";
917
- import { dialex } from "dialexjs/express";
918
- import dictionaries from "./src/dialex.generated.js";
944
+ import { dialexExpress } from "dialexjs/express";
945
+ import { dialex } from "./src/dialex.generated.js";
919
946
 
920
947
  const app = express();
921
- app.use(dialex({ dictionaries }));
948
+ app.use(dialexExpress({ ...dialex }));
922
949
 
923
950
  app.get("/", (req, res) => {
924
951
  const dict = req.getDictionary("home");
925
952
  res.json({ title: dict.title });
926
953
  });
927
- `);
928
- break;
929
- case "koa":
930
- logger.log(`
954
+ `,
955
+ koa: `
931
956
  import Koa from "koa";
932
- import { dialex } from "dialexjs/koa";
933
- import dictionaries from "./src/dialex.generated.js";
957
+ import { dialexKoa } from "dialexjs/koa";
958
+ import { dialex } from "./src/dialex.generated.js";
934
959
 
935
960
  const app = new Koa();
936
- app.use(dialex({ dictionaries }));
961
+ app.use(dialexKoa({ ...dialex }));
937
962
 
938
963
  app.use((ctx) => {
939
964
  const dict = ctx.getDictionary("home");
940
965
  ctx.body = { title: dict.title };
941
966
  });
942
- `);
943
- break;
944
- case "nestjs":
945
- logger.log(`
967
+ `,
968
+ nestjs: `
946
969
  import { Module } from "@nestjs/common";
947
970
  import { DialexModule } from "dialexjs/nestjs";
948
- import dictionaries from "./src/dialex.generated.js";
971
+ import { dialex } from "./src/dialex.generated.js";
949
972
 
950
973
  @Module({
951
- imports: [DialexModule.forRoot({ dictionaries })],
974
+ imports: [DialexModule.forRoot({ ...dialex })],
952
975
  })
953
976
  export class AppModule {}
954
- `);
955
- break;
956
- case "next":
957
- logger.log(`
977
+ `,
978
+ next: `
958
979
  // src/dialex.ts
959
980
  import { createDialexServer } from "dialexjs/server";
960
981
  import { dialex } from "./dialex.generated";
@@ -975,10 +996,8 @@ export default async function Page({ params }: { params: Promise<{ locale: strin
975
996
  // import { DialexProvider } from "dialexjs/react";
976
997
  // import { dialex } from "./dialex.generated";
977
998
  // export const Providers = ({ children }) => <DialexProvider {...dialex}>{children}</DialexProvider>;
978
- `);
979
- break;
980
- case "react":
981
- logger.log(`
999
+ `,
1000
+ react: `
982
1001
  // src/main.tsx
983
1002
  import { createRoot } from "react-dom/client";
984
1003
  import { DialexProvider } from "dialexjs/react";
@@ -992,45 +1011,37 @@ createRoot(document.getElementById("root")!).render(
992
1011
  );
993
1012
 
994
1013
  // In components: const dict = useDictionary("home")
995
- `);
996
- break;
997
- case "elysia":
998
- logger.log(`
1014
+ `,
1015
+ elysia: `
999
1016
  import { Elysia } from "elysia";
1000
- import { dialex } from "dialexjs/elysia";
1001
- import dictionaries from "./src/dialex.generated.js";
1017
+ import { dialexElysia } from "dialexjs/elysia";
1018
+ import { dialex } from "./src/dialex.generated.js";
1002
1019
 
1003
1020
  new Elysia()
1004
- .use(dialex({ dictionaries }))
1021
+ .use(dialexElysia({ ...dialex }))
1005
1022
  .get("/", ({ getDictionary }) => ({ title: getDictionary("home").title }))
1006
1023
  .listen(3000);
1007
- `);
1008
- break;
1009
- case "sveltekit":
1010
- logger.log(`
1024
+ `,
1025
+ sveltekit: `
1011
1026
  // src/hooks.server.ts
1012
1027
  import { dialexHandle } from "dialexjs/sveltekit";
1013
- import dictionaries from "./dialex.generated.js";
1028
+ import { dialex } from "./dialex.generated.js";
1014
1029
 
1015
- export const handle = dialexHandle({ dictionaries });
1030
+ export const handle = dialexHandle({ ...dialex });
1016
1031
 
1017
1032
  // src/app.html: <html lang="%dialex.lang%">
1018
1033
  // In load functions: locals.getDictionary("home")
1019
- `);
1020
- break;
1021
- case "astro":
1022
- logger.log(`
1034
+ `,
1035
+ astro: `
1023
1036
  // src/middleware.ts
1024
- import { dialex } from "dialexjs/astro";
1025
- import dictionaries from "./dialex.generated.js";
1037
+ import { dialexAstro } from "dialexjs/astro";
1038
+ import { dialex } from "./dialex.generated.js";
1026
1039
 
1027
- export const onRequest = dialex({ dictionaries });
1040
+ export const onRequest = dialexAstro({ ...dialex });
1028
1041
 
1029
1042
  // In pages: Astro.locals.getDictionary("home")
1030
- `);
1031
- break;
1032
- case "vue":
1033
- logger.log(`
1043
+ `,
1044
+ vue: `
1034
1045
  // src/main.ts
1035
1046
  import { createApp } from "vue";
1036
1047
  import { createDialex } from "dialexjs/vue";
@@ -1040,23 +1051,243 @@ import App from "./App.vue";
1040
1051
  createApp(App).use(createDialex({ ...dialex })).mount("#app");
1041
1052
 
1042
1053
  // In components: const dict = useDictionary("home")
1043
- `);
1044
- break;
1045
- case "nuxt":
1046
- logger.log(`
1054
+ `,
1055
+ svelte: `
1056
+ <!-- src/App.svelte -->
1057
+ <script lang="ts">
1058
+ import { provideDialex, useDictionary } from "dialexjs/svelte";
1059
+ import { dialex } from "./dialex.generated";
1060
+
1061
+ const { locale, setLocale } = provideDialex({ ...dialex });
1062
+ const home = useDictionary("home");
1063
+ <\/script>
1064
+
1065
+ <h1>{$home.title}</h1>
1066
+ <button onclick={() => setLocale("tr")}>TR</button>
1067
+ `,
1068
+ solid: `
1069
+ // src/index.tsx
1070
+ import { render } from "solid-js/web";
1071
+ import { DialexProvider } from "dialexjs/solid";
1072
+ import { dialex } from "./dialex.generated";
1073
+ import App from "./App";
1074
+
1075
+ render(
1076
+ () => (
1077
+ <DialexProvider {...dialex}>
1078
+ <App />
1079
+ </DialexProvider>
1080
+ ),
1081
+ document.getElementById("root")!,
1082
+ );
1083
+
1084
+ // In components: const home = useDictionary("home"); then {home().title}
1085
+ `,
1086
+ "react-router": `
1087
+ // app/dialex.server.ts
1088
+ import { createDialexHandler } from "dialexjs/web";
1089
+ import { dialex } from "./dialex.generated";
1090
+
1091
+ export const resolveDialex = createDialexHandler({ ...dialex });
1092
+
1093
+ // app/root.tsx
1094
+ import { DialexProvider } from "dialexjs/react";
1095
+ import { useLoaderData } from "react-router";
1096
+ import { dialex } from "./dialex.generated";
1097
+ import { resolveDialex } from "./dialex.server";
1098
+
1099
+ export async function loader({ request }: Route.LoaderArgs) {
1100
+ const { locale } = await resolveDialex(request);
1101
+ return { locale };
1102
+ }
1103
+
1104
+ export default function App() {
1105
+ const { locale } = useLoaderData<typeof loader>();
1106
+ return (
1107
+ <DialexProvider {...dialex} initialLocale={locale}>
1108
+ <Outlet />
1109
+ </DialexProvider>
1110
+ );
1111
+ }
1112
+ `,
1113
+ preact: `
1114
+ // src/index.tsx
1115
+ import { render } from "preact";
1116
+ import { DialexProvider } from "dialexjs/preact";
1117
+ import { dialex } from "./dialex.generated";
1118
+ import { App } from "./app";
1119
+
1120
+ render(
1121
+ <DialexProvider {...dialex}>
1122
+ <App />
1123
+ </DialexProvider>,
1124
+ document.getElementById("app")!,
1125
+ );
1126
+
1127
+ // In components: const dict = useDictionary("home")
1128
+ `,
1129
+ qwik: `
1130
+ // src/i18n.ts
1131
+ import { createDialexQwik } from "dialexjs/qwik";
1132
+ import { dialex } from "./dialex.generated";
1133
+
1134
+ export const { useDialexProvider, useDialex, setLocale, useDictionary, useT } =
1135
+ createDialexQwik({ ...dialex });
1136
+
1137
+ // src/routes/layout.tsx
1138
+ export default component$(() => {
1139
+ useDialexProvider();
1140
+ return <Slot />;
1141
+ });
1142
+
1143
+ // In components: const home = useDictionary("home"); then {home().title}
1144
+ `,
1145
+ lit: `
1146
+ // src/i18n.ts
1147
+ import { createDialex } from "dialexjs/lit";
1148
+ import { dialex } from "./dialex.generated";
1149
+
1150
+ export const i18n = createDialex({ ...dialex });
1151
+
1152
+ // src/my-element.ts
1153
+ import { LitElement, html } from "lit";
1154
+ import { DialexController } from "dialexjs/lit";
1155
+ import { i18n } from "./i18n";
1156
+
1157
+ class MyElement extends LitElement {
1158
+ private dx = new DialexController(this, i18n);
1159
+
1160
+ render() {
1161
+ return html\`<h1>\${this.dx.dictionary("home").title}</h1>\`;
1162
+ }
1163
+ }
1164
+ `,
1165
+ "react-native": `
1166
+ // App.tsx
1167
+ import AsyncStorage from "@react-native-async-storage/async-storage";
1168
+ import { getLocales } from "expo-localization";
1169
+ import { DialexProvider } from "dialexjs/react-native";
1170
+ import { dialex } from "./src/dialex.generated";
1171
+
1172
+ export default function App() {
1173
+ return (
1174
+ <DialexProvider
1175
+ {...dialex}
1176
+ deviceLocales={getLocales().map((locale) => locale.languageTag)}
1177
+ storage={AsyncStorage}
1178
+ >
1179
+ <Home />
1180
+ </DialexProvider>
1181
+ );
1182
+ }
1183
+
1184
+ // In components: const dict = useDictionary("home")
1185
+ `,
1186
+ hapi: `
1187
+ import Hapi from "@hapi/hapi";
1188
+ import { dialexPlugin } from "dialexjs/hapi";
1189
+ import { dialex } from "./src/dialex.generated.js";
1190
+
1191
+ const server = Hapi.server({ port: 3000 });
1192
+ await server.register({ plugin: dialexPlugin, options: { ...dialex } });
1193
+
1194
+ server.route({
1195
+ method: "GET",
1196
+ path: "/",
1197
+ handler: (request) => ({ title: request.getDictionary("home").title }),
1198
+ });
1199
+ `,
1200
+ adonis: `
1201
+ // app/middleware/dialex_middleware.ts
1202
+ import { createDialexMiddleware } from "dialexjs/adonis";
1203
+ import { dialex } from "#start/dialex.generated";
1204
+
1205
+ export default createDialexMiddleware({ ...dialex });
1206
+
1207
+ // start/kernel.ts
1208
+ router.use([() => import("#middleware/dialex_middleware")]);
1209
+
1210
+ // In handlers: ctx.locale, ctx.getDictionary("home")
1211
+ `,
1212
+ trpc: `
1213
+ // src/trpc.ts
1214
+ import { initTRPC } from "@trpc/server";
1215
+ import { createDialexContext } from "dialexjs/trpc";
1216
+ import { dialex } from "./dialex.generated.js";
1217
+
1218
+ const resolveDialex = createDialexContext({ ...dialex });
1219
+
1220
+ export const createContext = ({ req }: { req: Request }) => ({ ...resolveDialex(req) });
1221
+
1222
+ const t = initTRPC.context<Awaited<ReturnType<typeof createContext>>>().create();
1223
+
1224
+ export const appRouter = t.router({
1225
+ greeting: t.procedure.query(({ ctx }) => ctx.getDictionary("home").title),
1226
+ });
1227
+ `,
1228
+ nuxt: `
1047
1229
  // nuxt.config.ts
1048
1230
  export default defineNuxtConfig({
1049
1231
  modules: ["dialexjs/nuxt"],
1050
1232
  });
1051
1233
 
1052
1234
  // In components: const dict = useDictionary("home")
1053
- `);
1054
- break;
1055
- default: logger.log(`
1235
+ `
1236
+ };
1237
+ /** Printed for a framework without a snippet of its own. */
1238
+ const FALLBACK = `
1056
1239
  Import dictionaries and register your framework middleware to begin!
1057
1240
  Run \`npm run dx:generate\` whenever you add new dictionary files.
1058
- `);
1059
- }
1241
+ `;
1242
+ function printQuickstart(framework) {
1243
+ logger.log("");
1244
+ logger.info(pc.bold(`Quick start for ${framework.toUpperCase()}:`));
1245
+ logger.log(Object.hasOwn(SNIPPETS, framework) ? SNIPPETS[framework] : FALLBACK);
1246
+ }
1247
+ //#endregion
1248
+ //#region src/commands/init/starter-dictionary.ts
1249
+ /** Writes `home.content.ts` with one entry per locale, unless it is already there. */
1250
+ function createStarterDictionary(root, framework, localeList) {
1251
+ const targetDir = (framework === "react-router" || framework === "adonis") && fs.existsSync(path.join(root, "app")) ? path.join(root, "app") : fs.existsSync(path.join(root, "src")) ? path.join(root, "src") : root;
1252
+ const dictPath = path.join(targetDir, "home.content.ts");
1253
+ if (!fs.existsSync(dictPath)) {
1254
+ const dictContent = `import { defineDictionary } from "dialexjs";
1255
+
1256
+ export default defineDictionary("home", {
1257
+ ${localeList.map((loc) => ` ${loc}: {
1258
+ title: "Welcome to Dialex (${loc})!",
1259
+ description: "Type-safe internationalization.",
1260
+ greeting: (name: string) => "Hello, " + name + "!",
1261
+ },`).join("\n")}
1262
+ });
1263
+ `;
1264
+ fs.writeFileSync(dictPath, dictContent, "utf-8");
1265
+ logger.success(`Created starter dictionary at ${pc.bold(path.relative(root, dictPath))}`);
1266
+ } else logger.info("home.content.ts already exists, skipping creation.");
1267
+ }
1268
+ //#endregion
1269
+ //#region src/commands/init/index.ts
1270
+ async function runInit(options = {}) {
1271
+ const root = options.cwd || process.cwd();
1272
+ const detected = detectFramework(root);
1273
+ const isInteractive = !options.yes && process.stdin.isTTY;
1274
+ let framework = options.framework || detected.framework;
1275
+ let defaultLocale = options.defaultLocale || "en";
1276
+ let localesInput = options.locales || "en,tr";
1277
+ if (isInteractive) {
1278
+ const answers = await askInit(options, detected);
1279
+ if (!answers) return;
1280
+ ({framework, defaultLocale, localesInput} = answers);
1281
+ } else logger.info(`Auto-detected framework: ${pc.bold(framework)} (${detected.matchedRule})`);
1282
+ const localeList = Array.from(/* @__PURE__ */ new Set([defaultLocale, ...localesInput.split(",").map((s) => s.trim()).filter(Boolean)]));
1283
+ await writeDialexConfig(root, framework, defaultLocale, localeList);
1284
+ if (framework === "nuxt") await registerNuxtModule(root);
1285
+ createStarterDictionary(root, framework, localeList);
1286
+ generateDictionaries(root, { static: true });
1287
+ logger.success("Generated types and standalone dictionary registry!");
1288
+ updatePackageJson(root);
1289
+ if (options.ai === true || options.yes && options.ai !== false && options.noAi !== true) setupAiSupport(root);
1290
+ printQuickstart(framework);
1060
1291
  logger.info("Keep dialex.generated.ts current: run `dx generate --watch` while you develop, or use the Dialex VS Code extension, which regenerates on save.");
1061
1292
  if (isInteractive) p.outro(pc.green("✔ dialex is configured and ready to go!"));
1062
1293
  }
@@ -1150,6 +1381,13 @@ function preservesPlaceholders(source, translated) {
1150
1381
  }
1151
1382
  //#endregion
1152
1383
  //#region src/commands/translate.ts
1384
+ /**
1385
+ * The JSON of a result. Tasks carry the parsed dictionary and the key path for the writing step;
1386
+ * those are not part of the output (the parsed file is large and not meant to be read).
1387
+ */
1388
+ function printable(result) {
1389
+ return JSON.stringify(result, (key, value) => key === "dict" || key === "path" ? void 0 : value, 2);
1390
+ }
1153
1391
  async function runTranslate(options = {}) {
1154
1392
  const root = options.cwd || process.cwd();
1155
1393
  const project = await loadProject(root, options.config);
@@ -1193,12 +1431,12 @@ async function runTranslate(options = {}) {
1193
1431
  rejected: []
1194
1432
  };
1195
1433
  if (tasks.length === 0) {
1196
- if (quiet) console.log(JSON.stringify(result, null, 2));
1434
+ if (quiet) console.log(printable(result));
1197
1435
  else logger.success(`Nothing to translate from "${source}".`);
1198
1436
  return result;
1199
1437
  }
1200
1438
  if (options.dryRun) {
1201
- if (quiet) console.log(JSON.stringify(result, null, 2));
1439
+ if (quiet) console.log(printable(result));
1202
1440
  else {
1203
1441
  logger.info(`Would translate ${tasks.length} string${tasks.length === 1 ? "" : "s"} from "${source}":`);
1204
1442
  for (const t of tasks) logger.log(` ${pc.dim(t.locale)} ${t.key} ${pc.dim(JSON.stringify(t.source))}${t.stale ? pc.yellow(" (out of date)") : ""}`);
@@ -1243,7 +1481,7 @@ async function runTranslate(options = {}) {
1243
1481
  for (const dict of touched) await saveDictionaryFile(dict.df);
1244
1482
  if (result.translated > 0) writeLock(root, lock);
1245
1483
  if (result.rejected.length > 0) result.success = false;
1246
- if (quiet) console.log(JSON.stringify(result, null, 2));
1484
+ if (quiet) console.log(printable(result));
1247
1485
  else {
1248
1486
  logger.success(`Translated ${result.translated} of ${tasks.length} string${tasks.length === 1 ? "" : "s"}.`);
1249
1487
  if (result.rejected.length > 0) logger.warn(`${result.rejected.length} left unchanged: ${result.rejected.slice(0, 5).join("; ")}${result.rejected.length > 5 ? "; ..." : ""}`);
@@ -1257,7 +1495,7 @@ program.name("dialex").description("Dialex CLI - High-performance, type-safe int
1257
1495
  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) => {
1258
1496
  await runGenerate(opts);
1259
1497
  });
1260
- 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 (.agents/skills, .mcp.json, AGENTS.md)").option("--no-ai", "Skip AI agent configuration").action(async (opts) => {
1498
+ program.command("init").description("Initialize Dialex in the current project").option("-f, --framework <framework>", "Target framework (hono, express, fastify, koa, nestjs, elysia, sveltekit, astro, vue, nuxt, next, react, react-router, svelte, solid, preact, qwik, lit, react-native, hapi, adonis, trpc)").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 (.agents/skills, .mcp.json, AGENTS.md)").option("--no-ai", "Skip AI agent configuration").action(async (opts) => {
1261
1499
  await runInit(opts);
1262
1500
  });
1263
1501
  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("--fail-on-stale", "Treat translations whose source text changed as errors").option("--src <globs...>", "Source globs to scan for dictionary references").action(async (opts) => {