@saidksi/localizer-cli 0.1.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.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +99 -0
  3. package/dist/bin/localize.d.ts +3 -0
  4. package/dist/bin/localize.d.ts.map +1 -0
  5. package/dist/bin/localize.js +7 -0
  6. package/dist/bin/localize.js.map +1 -0
  7. package/dist/commands/add-lang.d.ts +3 -0
  8. package/dist/commands/add-lang.d.ts.map +1 -0
  9. package/dist/commands/add-lang.js +103 -0
  10. package/dist/commands/add-lang.js.map +1 -0
  11. package/dist/commands/audit.d.ts +3 -0
  12. package/dist/commands/audit.d.ts.map +1 -0
  13. package/dist/commands/audit.js +174 -0
  14. package/dist/commands/audit.js.map +1 -0
  15. package/dist/commands/diff.d.ts +3 -0
  16. package/dist/commands/diff.d.ts.map +1 -0
  17. package/dist/commands/diff.js +76 -0
  18. package/dist/commands/diff.js.map +1 -0
  19. package/dist/commands/init.d.ts +3 -0
  20. package/dist/commands/init.d.ts.map +1 -0
  21. package/dist/commands/init.js +427 -0
  22. package/dist/commands/init.js.map +1 -0
  23. package/dist/commands/rewrite.d.ts +3 -0
  24. package/dist/commands/rewrite.d.ts.map +1 -0
  25. package/dist/commands/rewrite.js +140 -0
  26. package/dist/commands/rewrite.js.map +1 -0
  27. package/dist/commands/run.d.ts +3 -0
  28. package/dist/commands/run.d.ts.map +1 -0
  29. package/dist/commands/run.js +324 -0
  30. package/dist/commands/run.js.map +1 -0
  31. package/dist/commands/scan.d.ts +3 -0
  32. package/dist/commands/scan.d.ts.map +1 -0
  33. package/dist/commands/scan.js +121 -0
  34. package/dist/commands/scan.js.map +1 -0
  35. package/dist/commands/status.d.ts +3 -0
  36. package/dist/commands/status.d.ts.map +1 -0
  37. package/dist/commands/status.js +128 -0
  38. package/dist/commands/status.js.map +1 -0
  39. package/dist/commands/translate.d.ts +3 -0
  40. package/dist/commands/translate.d.ts.map +1 -0
  41. package/dist/commands/translate.js +233 -0
  42. package/dist/commands/translate.js.map +1 -0
  43. package/dist/commands/validate.d.ts +3 -0
  44. package/dist/commands/validate.d.ts.map +1 -0
  45. package/dist/commands/validate.js +121 -0
  46. package/dist/commands/validate.js.map +1 -0
  47. package/dist/index.d.ts +2 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +46 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/utils/config.d.ts +30 -0
  52. package/dist/utils/config.d.ts.map +1 -0
  53. package/dist/utils/config.js +94 -0
  54. package/dist/utils/config.js.map +1 -0
  55. package/dist/utils/diff.d.ts +10 -0
  56. package/dist/utils/diff.d.ts.map +1 -0
  57. package/dist/utils/diff.js +31 -0
  58. package/dist/utils/diff.js.map +1 -0
  59. package/dist/utils/json.d.ts +3 -0
  60. package/dist/utils/json.d.ts.map +1 -0
  61. package/dist/utils/json.js +15 -0
  62. package/dist/utils/json.js.map +1 -0
  63. package/dist/utils/logger.d.ts +28 -0
  64. package/dist/utils/logger.d.ts.map +1 -0
  65. package/dist/utils/logger.js +48 -0
  66. package/dist/utils/logger.js.map +1 -0
  67. package/dist/utils/prompt.d.ts +18 -0
  68. package/dist/utils/prompt.d.ts.map +1 -0
  69. package/dist/utils/prompt.js +65 -0
  70. package/dist/utils/prompt.js.map +1 -0
  71. package/dist/utils/reporter.d.ts +17 -0
  72. package/dist/utils/reporter.d.ts.map +1 -0
  73. package/dist/utils/reporter.js +87 -0
  74. package/dist/utils/reporter.js.map +1 -0
  75. package/package.json +62 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 SaidKSI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", BASIS WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # @localize/cli
2
+
3
+ Command-line tool for automating i18n workflows in JavaScript/TypeScript projects. Scans for hardcoded strings, generates semantic i18n keys via AI, translates into target languages, and rewrites source code.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ # Global installation
9
+ npm install -g @localize/cli
10
+
11
+ # Per-project installation
12
+ npm install --save-dev @localize/cli
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ```bash
18
+ # Initialize your project
19
+ localize init
20
+
21
+ # Scan for hardcoded strings
22
+ localize scan src/
23
+
24
+ # Full automation: scan → generate keys → translate → rewrite
25
+ localize run --yes
26
+
27
+ # Validate translation coverage
28
+ localize validate
29
+ ```
30
+
31
+ ## Commands
32
+
33
+ ### `localize init`
34
+ Interactive setup wizard. Creates `.localize.config.json` and stores API keys in `~/.localize`.
35
+
36
+ ### `localize audit`
37
+ Count total untranslated strings in your project.
38
+
39
+ ### `localize scan <path>`
40
+ List all hardcoded strings found in a file or directory.
41
+
42
+ ### `localize translate [options]`
43
+ Generate semantic i18n keys and translate strings into target languages.
44
+
45
+ ### `localize rewrite <path> [options]`
46
+ Replace hardcoded strings with i18n function calls (`t('key')`).
47
+
48
+ ### `localize run [options]`
49
+ Full pipeline: scan → generate keys → translate → rewrite → validate.
50
+
51
+ ### `localize validate [options]`
52
+ Check translation coverage across all languages. Use `--ci` for CI/CD.
53
+
54
+ ### `localize add-lang <language>`
55
+ Add a new language and translate all existing keys.
56
+
57
+ ### `localize status`
58
+ Show project health snapshot (files, strings, translation coverage).
59
+
60
+ ### `localize diff <language>`
61
+ Show missing keys for a specific language.
62
+
63
+ ## Configuration
64
+
65
+ Create `.localize.config.json` in your project root:
66
+
67
+ ```json
68
+ {
69
+ "defaultLanguage": "en",
70
+ "languages": ["en", "fr", "es"],
71
+ "messagesDir": "./messages",
72
+ "include": ["src/**/*.{ts,tsx,js,jsx}"],
73
+ "exclude": ["**/*.test.ts", "**/*.spec.ts"],
74
+ "aiProvider": "anthropic",
75
+ "aiModel": "claude-3-sonnet-20240229",
76
+ "keyStyle": "dot.notation",
77
+ "i18nLibrary": "react-i18next",
78
+ "fileOrganization": "per-page",
79
+ "strictMode": true,
80
+ "glossary": {}
81
+ }
82
+ ```
83
+
84
+ ## Development
85
+
86
+ ```bash
87
+ pnpm install
88
+ pnpm build
89
+ pnpm test
90
+ pnpm lint
91
+ ```
92
+
93
+ ## License
94
+
95
+ MIT
96
+
97
+ ## Repository
98
+
99
+ https://github.com/SaidKSI/localize-cli
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=localize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"localize.d.ts","sourceRoot":"","sources":["../../src/bin/localize.ts"],"names":[],"mappings":""}
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { main } from "../index.js";
3
+ main().catch((error) => {
4
+ console.error("Error:", error);
5
+ process.exit(1);
6
+ });
7
+ //# sourceMappingURL=localize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"localize.js","sourceRoot":"","sources":["../../src/bin/localize.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAEnC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC/B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare const addLangCommand: Command;
3
+ //# sourceMappingURL=add-lang.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add-lang.d.ts","sourceRoot":"","sources":["../../src/commands/add-lang.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsHpC,eAAO,MAAM,cAAc,SAWvB,CAAC"}
@@ -0,0 +1,103 @@
1
+ import { Command } from "commander";
2
+ import { resolve, join, basename, extname } from "path";
3
+ import { readdir } from "fs/promises";
4
+ import ora from "ora";
5
+ import chalk from "chalk";
6
+ import { translateExistingKeys } from "@saidksi/localizer-core";
7
+ import { logger } from "../utils/logger.js";
8
+ import { loadConfig, requireApiKey, writeProjectConfig } from "../utils/config.js";
9
+ import { readFile } from "fs/promises";
10
+ import { flattenJson } from "../utils/json.js";
11
+ async function readAllEntries(messagesDir, sourceLang) {
12
+ const langDir = join(resolve(messagesDir), sourceLang);
13
+ let files;
14
+ try {
15
+ const all = await readdir(langDir);
16
+ files = all.filter((f) => f.endsWith(".json"));
17
+ }
18
+ catch {
19
+ return [];
20
+ }
21
+ const entries = [];
22
+ for (const file of files) {
23
+ const pageName = basename(file, extname(file));
24
+ const raw = await readFile(join(langDir, file), "utf-8").catch(() => "{}");
25
+ const flat = flattenJson(JSON.parse(raw));
26
+ for (const [key, value] of Object.entries(flat)) {
27
+ entries.push({ key, value, pageName });
28
+ }
29
+ }
30
+ return entries;
31
+ }
32
+ async function runAddLang(options) {
33
+ const cwd = process.cwd();
34
+ const config = await loadConfig(cwd).catch((err) => logger.fatal(err instanceof Error ? err.message : String(err)));
35
+ const apiKey = await requireApiKey(config.aiProvider).catch((err) => logger.fatal(err instanceof Error ? err.message : String(err)));
36
+ const newLang = options.lang.trim().toLowerCase();
37
+ const sourceLang = options.from ?? config.defaultLanguage;
38
+ // Guard: already configured (e.g. add-lang was partially interrupted)
39
+ if (config.languages.includes(newLang)) {
40
+ logger.warn(`Language "${newLang}" is already in your config.`);
41
+ logger.dim(` To fill missing keys: localize translate --from-existing --missing-only --lang ${newLang}`);
42
+ return;
43
+ }
44
+ // Guard: source language must exist
45
+ const knownLangs = [config.defaultLanguage, ...config.languages];
46
+ if (!knownLangs.includes(sourceLang)) {
47
+ logger.fatal(`Source language "${sourceLang}" is not in your config.`);
48
+ }
49
+ logger.header(`Adding language: ${chalk.cyan(newLang)}`);
50
+ logger.dim(` Source: ${sourceLang} → Target: ${newLang}`);
51
+ logger.blank();
52
+ // ── Read existing keys
53
+ const readSpinner = ora(`Reading keys from ${sourceLang}…`).start();
54
+ const entries = await readAllEntries(config.messagesDir, sourceLang);
55
+ readSpinner.succeed(`Found ${entries.length} key${entries.length !== 1 ? "s" : ""} in "${sourceLang}".`);
56
+ if (entries.length === 0) {
57
+ logger.warn(`No keys found in messages/${sourceLang}/. Run \`localize translate\` first.`);
58
+ return;
59
+ }
60
+ // ── Translate into new language
61
+ const aiSpinner = ora(`Translating ${entries.length} keys into "${newLang}"…`).start();
62
+ const addLangOpts = {
63
+ overwrite: false,
64
+ langs: [newLang],
65
+ };
66
+ if (options.dryRun !== undefined)
67
+ addLangOpts.dryRun = options.dryRun;
68
+ const result = await translateExistingKeys(entries, config, apiKey, addLangOpts);
69
+ aiSpinner.succeed(`Translated ${result.translated} key${result.translated !== 1 ? "s" : ""}.`);
70
+ if (result.aiCostUsd > 0) {
71
+ logger.dim(` Estimated cost: ~$${result.aiCostUsd.toFixed(4)}`);
72
+ }
73
+ if (options.dryRun) {
74
+ logger.blank();
75
+ logger.warn("Dry run — no files written, config not updated.");
76
+ return;
77
+ }
78
+ // ── Update config
79
+ const updatedConfig = {
80
+ ...config,
81
+ languages: [...config.languages, newLang],
82
+ };
83
+ await writeProjectConfig(updatedConfig, cwd);
84
+ logger.blank();
85
+ logger.success(`Added "${newLang}" to .localize.config.json`);
86
+ logger.dim(` Files written: ${result.messagesWritten.length}`);
87
+ logger.dim(` Run \`localize validate --lang ${newLang}\` to check coverage.`);
88
+ logger.blank();
89
+ }
90
+ export const addLangCommand = new Command("add-lang")
91
+ .description("Add a new target language and translate all existing keys into it")
92
+ .requiredOption("--lang <lang>", "ISO 639-1 language code to add (e.g. ja)")
93
+ .option("--from <lang>", "Source language to translate from (default: config.defaultLanguage)")
94
+ .option("--dry-run", "Preview translations without writing files")
95
+ .action(async (options) => {
96
+ try {
97
+ await runAddLang(options);
98
+ }
99
+ catch (err) {
100
+ logger.fatal(err instanceof Error ? err.message : String(err));
101
+ }
102
+ });
103
+ //# sourceMappingURL=add-lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add-lang.js","sourceRoot":"","sources":["../../src/commands/add-lang.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare const auditCommand: Command;
3
+ //# sourceMappingURL=audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/commands/audit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqNpC,eAAO,MAAM,YAAY,SAcrB,CAAC"}
@@ -0,0 +1,174 @@
1
+ import { Command } from "commander";
2
+ import { resolve, relative, dirname, basename } from "path";
3
+ import ora from "ora";
4
+ import chalk from "chalk";
5
+ import { scanFile, scanDirectory } from "@saidksi/localizer-core";
6
+ import { logger } from "../utils/logger.js";
7
+ import { loadConfig } from "../utils/config.js";
8
+ import { writeReport } from "../utils/reporter.js";
9
+ /**
10
+ * Group scan results by relative directory path.
11
+ * e.g. all results from "src/pages/Login.tsx" and "src/pages/Register.tsx"
12
+ * → one group labelled "src/pages/"
13
+ */
14
+ function groupByDirectory(results, cwd) {
15
+ const map = new Map();
16
+ for (const r of results) {
17
+ const rel = relative(cwd, r.file);
18
+ const dir = dirname(rel) + "/";
19
+ const entry = map.get(dir) ?? { files: new Set(), count: 0 };
20
+ entry.files.add(r.file);
21
+ entry.count++;
22
+ map.set(dir, entry);
23
+ }
24
+ return [...map.entries()]
25
+ .map(([label, { files, count }]) => ({
26
+ label,
27
+ stringCount: count,
28
+ fileCount: files.size,
29
+ files: [...files],
30
+ }))
31
+ .sort((a, b) => b.stringCount - a.stringCount);
32
+ }
33
+ /**
34
+ * Group scan results by source file (page name).
35
+ * e.g. all results from "src/pages/Login.tsx" → one group labelled "Login.tsx"
36
+ */
37
+ function groupByPage(results, cwd) {
38
+ const map = new Map();
39
+ for (const r of results) {
40
+ const rel = relative(cwd, r.file);
41
+ const page = basename(r.file);
42
+ const entry = map.get(rel) ?? { file: rel, count: 0 };
43
+ entry.count++;
44
+ map.set(rel, entry);
45
+ }
46
+ return [...map.entries()]
47
+ .map(([rel, { count }]) => ({
48
+ label: rel,
49
+ stringCount: count,
50
+ fileCount: 1,
51
+ files: [rel],
52
+ }))
53
+ .sort((a, b) => b.stringCount - a.stringCount);
54
+ }
55
+ // ─── Output ───────────────────────────────────────────────────────────────────
56
+ function printAuditTable(groups, total, cwd) {
57
+ if (total.length === 0) {
58
+ logger.success("No untranslated strings found.");
59
+ return;
60
+ }
61
+ logger.blank();
62
+ logger.raw(chalk.bold(" Untranslated strings found:\n"));
63
+ // Calculate column widths for alignment
64
+ const maxLabelLen = Math.max(...groups.map((g) => g.label.length), 10);
65
+ for (const g of groups) {
66
+ const label = g.label.padEnd(maxLabelLen + 2);
67
+ const strCount = chalk.yellow(`${g.stringCount} string${g.stringCount !== 1 ? "s" : ""}`);
68
+ const fileCount = chalk.dim(`across ${g.fileCount} file${g.fileCount !== 1 ? "s" : ""}`);
69
+ logger.raw(` ${chalk.cyan(label)} ${strCount} ${fileCount}`);
70
+ }
71
+ const totalFiles = new Set(total.map((r) => r.file)).size;
72
+ logger.blank();
73
+ logger.raw(` ${chalk.bold("Total:")} ${chalk.yellow(total.length)} strings in ${chalk.yellow(totalFiles)} files`);
74
+ logger.blank();
75
+ logger.raw(chalk.dim(" Run `localize run --dir <path>` to process any directory."));
76
+ logger.raw(chalk.dim(" Run `localize audit --output audit.json` to export as a work list."));
77
+ logger.blank();
78
+ }
79
+ function printSummaryOnly(groups, total) {
80
+ if (total.length === 0) {
81
+ logger.success("No untranslated strings found.");
82
+ return;
83
+ }
84
+ for (const g of groups) {
85
+ logger.raw(` ${g.label.padEnd(40)} ${g.stringCount}`);
86
+ }
87
+ const totalFiles = new Set(total.map((r) => r.file)).size;
88
+ logger.blank();
89
+ logger.raw(` Total: ${total.length} strings in ${totalFiles} files`);
90
+ }
91
+ async function runAudit(options) {
92
+ const cwd = process.cwd();
93
+ const config = await loadConfig(cwd).catch((err) => logger.fatal(err instanceof Error ? err.message : String(err)));
94
+ const spinner = ora("Scanning for untranslated strings...").start();
95
+ let results = [];
96
+ try {
97
+ if (options.file) {
98
+ results = await scanFile(resolve(cwd, options.file), config);
99
+ }
100
+ else if (options.dir) {
101
+ results = await scanDirectory(resolve(cwd, options.dir), config);
102
+ }
103
+ else {
104
+ // Scan all include dirs from config
105
+ const all = await Promise.all(config.include.map((dir) => scanDirectory(resolve(cwd, dir), config).catch(() => [])));
106
+ results = all.flat();
107
+ }
108
+ }
109
+ catch (err) {
110
+ spinner.fail("Scan failed.");
111
+ logger.fatal(err instanceof Error ? err.message : String(err));
112
+ }
113
+ // Filter already-translated strings — audit only shows untranslated ones
114
+ const untranslated = results.filter((r) => !r.alreadyTranslated);
115
+ spinner.succeed(`Scanned ${new Set(results.map((r) => r.file)).size} files — ` +
116
+ `${untranslated.length} untranslated string${untranslated.length !== 1 ? "s" : ""} found.`);
117
+ // Group results
118
+ const groupFn = options.groupBy === "page" ? groupByPage : groupByDirectory;
119
+ const groups = groupFn(untranslated, cwd);
120
+ // CI mode: output JSON to stdout and exit with code 1 if issues found
121
+ if (options.ci) {
122
+ const reportData = {
123
+ generatedAt: new Date().toISOString(),
124
+ file: options.file,
125
+ dir: options.dir,
126
+ totalStrings: untranslated.length,
127
+ totalFiles: new Set(untranslated.map((r) => r.file)).size,
128
+ groups,
129
+ results: untranslated,
130
+ };
131
+ console.log(JSON.stringify(reportData, null, 2));
132
+ if (untranslated.length > 0) {
133
+ process.exit(1);
134
+ }
135
+ return;
136
+ }
137
+ // Print output
138
+ if (options.summary) {
139
+ printSummaryOnly(groups, untranslated);
140
+ }
141
+ else {
142
+ printAuditTable(groups, untranslated, cwd);
143
+ }
144
+ // Write JSON report if requested
145
+ if (options.output) {
146
+ await writeReport(options.output, {
147
+ generatedAt: new Date().toISOString(),
148
+ file: options.file,
149
+ dir: options.dir,
150
+ totalStrings: untranslated.length,
151
+ totalFiles: new Set(untranslated.map((r) => r.file)).size,
152
+ groups,
153
+ results: untranslated,
154
+ });
155
+ logger.success(`Report written to ${options.output}`);
156
+ }
157
+ }
158
+ export const auditCommand = new Command("audit")
159
+ .description("Scan the full app for untranslated strings (read-only, no AI)")
160
+ .option("--dir <dir>", "Scope to a directory")
161
+ .option("--file <file>", "Scope to a single file")
162
+ .option("--summary", "Print counts per directory only")
163
+ .option("--output <path>", "Export structured report as JSON")
164
+ .option("--group-by <by>", "Group results by: page")
165
+ .option("--ci", "Machine-readable JSON output to stdout, exit 1 if any strings found")
166
+ .action(async (options) => {
167
+ try {
168
+ await runAudit(options);
169
+ }
170
+ catch (err) {
171
+ logger.fatal(err instanceof Error ? err.message : String(err));
172
+ }
173
+ });
174
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/commands/audit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare const diffCommand: Command;
3
+ //# sourceMappingURL=diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/commands/diff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmGpC,eAAO,MAAM,WAAW,SAUpB,CAAC"}
@@ -0,0 +1,76 @@
1
+ import { Command } from "commander";
2
+ import { writeFile } from "fs/promises";
3
+ import { resolve } from "path";
4
+ import chalk from "chalk";
5
+ import ora from "ora";
6
+ import { validateCoverage } from "@saidksi/localizer-core";
7
+ import { logger } from "../utils/logger.js";
8
+ import { loadConfig } from "../utils/config.js";
9
+ async function runDiff(options) {
10
+ const cwd = process.cwd();
11
+ const config = await loadConfig(cwd).catch((err) => logger.fatal(err instanceof Error ? err.message : String(err)));
12
+ const lang = options.lang.trim().toLowerCase();
13
+ // Validate: must be a known language
14
+ const known = [config.defaultLanguage, ...config.languages];
15
+ if (!known.includes(lang)) {
16
+ logger.fatal(`Language "${lang}" is not in your config. Known languages: ${known.join(", ")}`);
17
+ }
18
+ if (lang === config.defaultLanguage) {
19
+ logger.warn(`"${lang}" is the default language — it always has 100% coverage.`);
20
+ return;
21
+ }
22
+ const spinner = ora(`Computing diff for "${lang}"…`).start();
23
+ // Single validateCoverage call provides both missing keys and coverage data
24
+ const coverageResults = await validateCoverage(config, { lang });
25
+ const result = coverageResults.find((r) => r.language === lang);
26
+ const missingKeys = result?.missingKeys ?? [];
27
+ const percent = result?.coveragePercent ?? 0;
28
+ const total = result?.totalKeys ?? 0;
29
+ spinner.succeed(`Diff computed.`);
30
+ logger.blank();
31
+ if (missingKeys.length === 0) {
32
+ logger.success(`"${lang}" has full coverage — no missing keys.`);
33
+ logger.blank();
34
+ if (options.output) {
35
+ await writeFile(resolve(cwd, options.output), JSON.stringify([], null, 2) + "\n", "utf-8");
36
+ logger.dim(`Empty list written to ${options.output}`);
37
+ logger.blank();
38
+ }
39
+ return;
40
+ }
41
+ logger.raw(chalk.bold(` Missing keys in ${chalk.red(lang)}`) +
42
+ chalk.dim(` — ${missingKeys.length} of ${total} keys missing (${percent}% covered):`));
43
+ logger.blank();
44
+ for (const key of missingKeys) {
45
+ logger.raw(` ${chalk.dim("·")} ${key}`);
46
+ }
47
+ logger.blank();
48
+ logger.dim(` Run \`localize translate --missing-only --lang ${lang}\` to fill these keys.`);
49
+ logger.blank();
50
+ // Optional JSON output
51
+ if (options.output) {
52
+ const outPath = resolve(cwd, options.output);
53
+ await writeFile(outPath, JSON.stringify({
54
+ language: lang,
55
+ totalKeys: total,
56
+ missingCount: missingKeys.length,
57
+ coveragePercent: percent,
58
+ missingKeys,
59
+ }, null, 2) + "\n", "utf-8");
60
+ logger.success(`Missing key list saved to ${options.output}`);
61
+ logger.blank();
62
+ }
63
+ }
64
+ export const diffCommand = new Command("diff")
65
+ .description("Show keys missing from a target language relative to the default")
66
+ .requiredOption("--lang <lang>", "Language to diff against the default")
67
+ .option("--output <path>", "Save missing key list as JSON")
68
+ .action(async (options) => {
69
+ try {
70
+ await runDiff(options);
71
+ }
72
+ catch (err) {
73
+ logger.fatal(err instanceof Error ? err.message : String(err));
74
+ }
75
+ });
76
+ //# sourceMappingURL=diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/commands/diff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC/B,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAShD,KAAK,UAAU,OAAO,CAAC,OAAoB;IACzC,MAAM,GAAG,GAAM,OAAO,CAAC,GAAG,EAAE,CAAC;IAC7B,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE,CAC1D,MAAM,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAC/D,CAAC;IAEF,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAE/C,qCAAqC;IACrC,MAAM,KAAK,GAAG,CAAC,MAAM,CAAC,eAAe,EAAE,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;IAC5D,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1B,MAAM,CAAC,KAAK,CACV,aAAa,IAAI,6CAA6C,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACjF,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,KAAK,MAAM,CAAC,eAAe,EAAE,CAAC;QACpC,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,0DAA0D,CAAC,CAAC;QAChF,OAAO;IACT,CAAC;IAED,MAAM,OAAO,GAAG,GAAG,CAAC,uBAAuB,IAAI,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;IAC7D,4EAA4E;IAC5E,MAAM,eAAe,GAAG,MAAM,gBAAgB,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IACjE,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC;IAChE,MAAM,WAAW,GAAG,MAAM,EAAE,WAAW,IAAI,EAAE,CAAC;IAC9C,MAAM,OAAO,GAAG,MAAM,EAAE,eAAe,IAAI,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAK,MAAM,EAAE,SAAS,IAAI,CAAC,CAAC;IACvC,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAElC,MAAM,CAAC,KAAK,EAAE,CAAC;IAEf,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,wCAAwC,CAAC,CAAC;QACjE,MAAM,CAAC,KAAK,EAAE,CAAC;QAEf,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACnB,MAAM,SAAS,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;YAC3F,MAAM,CAAC,GAAG,CAAC,yBAAyB,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;YACtD,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,CAAC;QACD,OAAO;IACT,CAAC;IAED,MAAM,CAAC,GAAG,CACR,KAAK,CAAC,IAAI,CAAC,qBAAqB,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QAClD,KAAK,CAAC,GAAG,CAAC,MAAM,WAAW,CAAC,MAAM,OAAO,KAAK,kBAAkB,OAAO,aAAa,CAAC,CACtF,CAAC;IACF,MAAM,CAAC,KAAK,EAAE,CAAC;IAEf,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;QAC9B,MAAM,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;IAC3C,CAAC;IAED,MAAM,CAAC,KAAK,EAAE,CAAC;IACf,MAAM,CAAC,GAAG,CACR,oDAAoD,IAAI,wBAAwB,CACjF,CAAC;IACF,MAAM,CAAC,KAAK,EAAE,CAAC;IAEf,uBAAuB;IACvB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;QAC7C,MAAM,SAAS,CACb,OAAO,EACP,IAAI,CAAC,SAAS,CACZ;YACE,QAAQ,EAAK,IAAI;YACjB,SAAS,EAAI,KAAK;YAClB,YAAY,EAAE,WAAW,CAAC,MAAM;YAChC,eAAe,EAAE,OAAO;YACxB,WAAW;SACZ,EACD,IAAI,EACJ,CAAC,CACF,GAAG,IAAI,EACR,OAAO,CACR,CAAC;QACF,MAAM,CAAC,OAAO,CAAC,6BAA6B,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QAC9D,MAAM,CAAC,KAAK,EAAE,CAAC;IACjB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC;KAC3C,WAAW,CAAC,kEAAkE,CAAC;KAC/E,cAAc,CAAC,eAAe,EAAE,sCAAsC,CAAC;KACvE,MAAM,CAAC,iBAAiB,EAAQ,+BAA+B,CAAC;KAChE,MAAM,CAAC,KAAK,EAAE,OAAoB,EAAE,EAAE;IACrC,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACzB,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,MAAM,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IACjE,CAAC;AACH,CAAC,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare const initCommand: Command;
3
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAwfpC,eAAO,MAAM,WAAW,SAiBpB,CAAC"}