@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
@@ -0,0 +1,324 @@
1
+ import { Command } from "commander";
2
+ import { resolve, relative, basename, extname, dirname } from "path";
3
+ import { readFile, stat, readdir } from "fs/promises";
4
+ import ora from "ora";
5
+ import chalk from "chalk";
6
+ import { scanFile, scanDirectory, translateStrings, rewriteFile, applyRewrite, groupResultsByFile, resolveKeysFromMessages, validateCoverage, readCache, writeCache, clearCache, isCached, markBatchCached, } from "@saidksi/localizer-core";
7
+ import { logger, progressBar } from "../utils/logger.js";
8
+ import { loadConfig, requireApiKey } from "../utils/config.js";
9
+ import { promptApplyChanges } from "../utils/prompt.js";
10
+ import { printDiff } from "../utils/diff.js";
11
+ // ─── File validation helper ──────────────────────────────────────────────────
12
+ /**
13
+ * Find files with similar names (case-insensitive) in the same directory.
14
+ * Useful for suggesting corrections when a file is not found.
15
+ */
16
+ async function findSimilarFiles(filePath) {
17
+ const dir = dirname(filePath);
18
+ const fileName = basename(filePath).toLowerCase();
19
+ try {
20
+ const entries = await readdir(dir);
21
+ return entries.filter((e) => e.toLowerCase() === fileName ||
22
+ e.toLowerCase().includes(fileName.replace(/\.(tsx?|jsx?)$/i, "")));
23
+ }
24
+ catch {
25
+ return [];
26
+ }
27
+ }
28
+ async function processOneFile(filePath, results, options, config) {
29
+ const cwd = process.cwd();
30
+ const relPath = relative(cwd, filePath);
31
+ const rewrite = await rewriteFile(filePath, results, config);
32
+ if (rewrite.changesCount === 0) {
33
+ logger.dim(`${relPath} — no changes`);
34
+ return rewrite;
35
+ }
36
+ logger.blank();
37
+ logger.raw(` ${chalk.bold(chalk.cyan(relPath))} ` +
38
+ chalk.dim(`(${rewrite.changesCount} change${rewrite.changesCount !== 1 ? "s" : ""})`));
39
+ logger.blank();
40
+ printDiff(rewrite.diff);
41
+ logger.blank();
42
+ if (options.dryRun) {
43
+ logger.dim(" Dry run — skipping write.");
44
+ return rewrite;
45
+ }
46
+ if (options.yes) {
47
+ const applied = await applyRewrite(rewrite);
48
+ logger.success(`Applied changes to ${relPath}`);
49
+ return applied;
50
+ }
51
+ const confirmed = await promptApplyChanges(relPath);
52
+ if (confirmed) {
53
+ const applied = await applyRewrite(rewrite);
54
+ logger.success(`Applied changes to ${relPath}`);
55
+ return applied;
56
+ }
57
+ logger.warn(`Skipped ${relPath}`);
58
+ return rewrite;
59
+ }
60
+ async function runPipeline(options) {
61
+ const startMs = Date.now();
62
+ const cwd = process.cwd();
63
+ const config = await loadConfig(cwd).catch((err) => logger.fatal(err instanceof Error ? err.message : String(err)));
64
+ const apiKey = await requireApiKey(config.aiProvider).catch((err) => logger.fatal(err instanceof Error ? err.message : String(err)));
65
+ // Build effective config (lang override)
66
+ const langs = options.lang
67
+ ? options.lang.split(",").map((l) => l.trim()).filter(Boolean)
68
+ : config.languages;
69
+ const effectiveConfig = { ...config, languages: langs };
70
+ // Step numbering depends on which steps are active
71
+ const steps = ["scan", "translate"];
72
+ if (!options.skipRewrite)
73
+ steps.push("rewrite");
74
+ if (!options.skipValidate)
75
+ steps.push("validate");
76
+ const total = steps.length;
77
+ let stepIdx = 0;
78
+ // ── Step 1: Scan ─────────────────────────────────────────────────────────────
79
+ stepIdx++;
80
+ logger.step(stepIdx, total, "Scanning for hardcoded strings…");
81
+ // Cache handling
82
+ let cache = await readCache(cwd);
83
+ if (options.force) {
84
+ await clearCache(cwd);
85
+ cache = { version: 1, entries: {} };
86
+ logger.dim(" Cache cleared (--force).");
87
+ }
88
+ const scanSpinner = ora("Scanning…").start();
89
+ let rawResults = [];
90
+ try {
91
+ if (options.file) {
92
+ const filePath = resolve(cwd, options.file);
93
+ try {
94
+ await stat(filePath);
95
+ }
96
+ catch {
97
+ // File not found — provide helpful suggestions
98
+ const similar = await findSimilarFiles(filePath);
99
+ let errorMsg = `File not found: ${relative(cwd, filePath)}`;
100
+ if (similar.length > 0) {
101
+ errorMsg += `\n\n Did you mean:\n`;
102
+ similar.forEach((f) => {
103
+ errorMsg += ` ${relative(cwd, filePath.substring(0, filePath.lastIndexOf("/")) + "/" + f)}\n`;
104
+ });
105
+ }
106
+ else {
107
+ errorMsg += `\n\n Check the file path (case-sensitive on Unix-like systems)`;
108
+ }
109
+ throw new Error(errorMsg);
110
+ }
111
+ rawResults = await scanFile(filePath, effectiveConfig);
112
+ }
113
+ else if (options.dir) {
114
+ const dirPath = resolve(cwd, options.dir);
115
+ try {
116
+ const stats = await stat(dirPath);
117
+ if (!stats.isDirectory()) {
118
+ throw new Error(`Not a directory: ${relative(cwd, dirPath)}`);
119
+ }
120
+ }
121
+ catch (err) {
122
+ if (err instanceof Error && err.message.startsWith("Not a directory")) {
123
+ throw err;
124
+ }
125
+ throw new Error(`Directory not found: ${relative(cwd, dirPath)}`);
126
+ }
127
+ rawResults = await scanDirectory(dirPath, effectiveConfig);
128
+ }
129
+ else {
130
+ const all = await Promise.all(config.include.map((d) => scanDirectory(resolve(cwd, d), effectiveConfig).catch(() => [])));
131
+ rawResults = all.flat();
132
+ }
133
+ }
134
+ catch (err) {
135
+ scanSpinner.fail("Scan failed.");
136
+ logger.fatal(err instanceof Error ? err.message : String(err));
137
+ }
138
+ // Filter out files that haven't changed since last run (cache)
139
+ const allFiles = [...new Set(rawResults.map((r) => r.file))];
140
+ const uncachedFiles = new Set();
141
+ if (!options.force) {
142
+ for (const filePath of allFiles) {
143
+ try {
144
+ const source = await readFile(filePath, "utf-8");
145
+ const relPath = relative(cwd, filePath);
146
+ if (!isCached(cache, relPath, source)) {
147
+ uncachedFiles.add(filePath);
148
+ }
149
+ }
150
+ catch {
151
+ uncachedFiles.add(filePath);
152
+ }
153
+ }
154
+ }
155
+ else {
156
+ allFiles.forEach((f) => uncachedFiles.add(f));
157
+ }
158
+ const cachedCount = allFiles.length - uncachedFiles.size;
159
+ const scanResults = rawResults.filter((r) => uncachedFiles.has(r.file));
160
+ const untranslated = scanResults.filter((r) => !r.alreadyTranslated);
161
+ scanSpinner.succeed(`Scanned ${allFiles.length} file${allFiles.length !== 1 ? "s" : ""}` +
162
+ (cachedCount > 0 ? chalk.dim(` (${cachedCount} cached, skipped)`) : "") +
163
+ ` — ${untranslated.length} untranslated string${untranslated.length !== 1 ? "s" : ""} found.`);
164
+ const pipeline = {
165
+ ...(options.file !== undefined && { file: options.file }),
166
+ ...(options.dir !== undefined && { dir: options.dir }),
167
+ scanned: rawResults.length,
168
+ translated: 0,
169
+ rewritten: 0,
170
+ validated: false,
171
+ durationMs: 0,
172
+ aiCostUsd: 0,
173
+ };
174
+ if (untranslated.length === 0 && !options.skipValidate) {
175
+ logger.blank();
176
+ logger.dim(" Nothing new to translate.");
177
+ }
178
+ // ── Step 2: Translate ─────────────────────────────────────────────────────────
179
+ stepIdx++;
180
+ logger.blank();
181
+ logger.step(stepIdx, total, "Translating…");
182
+ let translatedResults = scanResults;
183
+ if (untranslated.length > 0) {
184
+ const aiSpinner = ora(`Calling ${effectiveConfig.aiProvider} (${effectiveConfig.aiModel})…`).start();
185
+ const translateOpts = {
186
+ overwrite: config.overwriteExisting,
187
+ };
188
+ if (options.dryRun !== undefined)
189
+ translateOpts.dryRun = options.dryRun;
190
+ const translateResult = await translateStrings(scanResults, effectiveConfig, apiKey, translateOpts);
191
+ aiSpinner.succeed(`Translated ${chalk.yellow(translateResult.uniqueStrings)} string${translateResult.uniqueStrings !== 1 ? "s" : ""} via ${translateResult.aiCalls} AI call${translateResult.aiCalls !== 1 ? "s" : ""}.`);
192
+ if (translateResult.aiCostUsd > 0) {
193
+ logger.dim(` Estimated cost: ~$${translateResult.aiCostUsd.toFixed(4)}`);
194
+ }
195
+ translatedResults = translateResult.results;
196
+ pipeline.translated = translateResult.uniqueStrings;
197
+ pipeline.aiCostUsd = translateResult.aiCostUsd;
198
+ }
199
+ else {
200
+ logger.dim(" Nothing to translate — all strings already have keys.");
201
+ }
202
+ // ── Step 3: Rewrite (optional) ────────────────────────────────────────────────
203
+ if (!options.skipRewrite) {
204
+ stepIdx++;
205
+ logger.blank();
206
+ logger.step(stepIdx, total, "Rewriting source files…");
207
+ // Resolve keys from messages JSON for any results still missing resolvedKey
208
+ const resolveSpinner = ora("Resolving keys…").start();
209
+ const resolved = await resolveKeysFromMessages(translatedResults, effectiveConfig);
210
+ const resolvable = resolved.filter((r) => r.resolvedKey !== null && !r.alreadyTranslated);
211
+ resolveSpinner.succeed(`${resolvable.length} string${resolvable.length !== 1 ? "s" : ""} ready to rewrite.`);
212
+ if (resolvable.length === 0) {
213
+ logger.dim(" Nothing to rewrite.");
214
+ }
215
+ else {
216
+ const byFile = groupResultsByFile(resolvable);
217
+ const fileList = [...byFile.keys()];
218
+ if (!options.yes && !options.dryRun) {
219
+ logger.raw(chalk.dim(`\n Processing ${fileList.length} file${fileList.length !== 1 ? "s" : ""} — you will be asked to confirm each diff.\n`));
220
+ }
221
+ let appliedCount = 0;
222
+ for (const [filePath, fileResults] of byFile) {
223
+ const result = await processOneFile(filePath, fileResults, options, effectiveConfig);
224
+ if (result.applied)
225
+ appliedCount++;
226
+ }
227
+ pipeline.rewritten = appliedCount;
228
+ if (appliedCount > 0) {
229
+ logger.success(`${appliedCount} file${appliedCount !== 1 ? "s" : ""} rewritten.`);
230
+ }
231
+ }
232
+ // Update cache with rewritten source files (after rewrite completes)
233
+ if (!options.dryRun && translatedResults.length > 0) {
234
+ const processed = [];
235
+ const processedFiles = new Set(translatedResults.map((r) => r.file));
236
+ for (const filePath of processedFiles) {
237
+ try {
238
+ const source = await readFile(filePath, "utf-8");
239
+ const relPath = relative(cwd, filePath);
240
+ const count = translatedResults.filter((r) => r.file === filePath).length;
241
+ processed.push({ relPath, source, stringCount: count });
242
+ }
243
+ catch { /* skip */ }
244
+ }
245
+ if (processed.length > 0) {
246
+ cache = markBatchCached(cache, processed);
247
+ await writeCache(cwd, cache);
248
+ }
249
+ }
250
+ }
251
+ // ── Step 4: Validate (optional) ───────────────────────────────────────────────
252
+ if (!options.skipValidate) {
253
+ stepIdx++;
254
+ logger.blank();
255
+ logger.step(stepIdx, total, "Checking key coverage…");
256
+ const validateSpinner = ora("Validating…").start();
257
+ const page = options.file
258
+ ? basename(options.file, extname(options.file)).toLowerCase()
259
+ : undefined;
260
+ const validateOpts = {};
261
+ if (page !== undefined)
262
+ validateOpts.page = page;
263
+ const results = await validateCoverage(effectiveConfig, validateOpts);
264
+ const totalMissing = results.reduce((n, r) => n + r.missingKeys.length, 0);
265
+ const allCovered = totalMissing === 0;
266
+ validateSpinner.succeed(allCovered
267
+ ? "All keys present across all languages."
268
+ : `${totalMissing} missing key${totalMissing !== 1 ? "s" : ""} found.`);
269
+ pipeline.validated = allCovered;
270
+ logger.blank();
271
+ logger.raw(chalk.bold(" Key coverage report:\n"));
272
+ for (const r of results) {
273
+ console.log(progressBar(r.language, r.coveragePercent, r.totalKeys, r.missingKeys.length));
274
+ }
275
+ logger.blank();
276
+ }
277
+ // ── Summary ───────────────────────────────────────────────────────────────────
278
+ pipeline.durationMs = Date.now() - startMs;
279
+ const durationSec = (pipeline.durationMs / 1000).toFixed(1);
280
+ logger.raw(chalk.bold(" Pipeline complete."));
281
+ logger.dim(` Duration: ${durationSec}s`);
282
+ logger.dim(` Scanned: ${pipeline.scanned} string${pipeline.scanned !== 1 ? "s" : ""}`);
283
+ if (pipeline.translated > 0) {
284
+ logger.dim(` Translated: ${pipeline.translated} string${pipeline.translated !== 1 ? "s" : ""}`);
285
+ }
286
+ if (pipeline.rewritten > 0) {
287
+ logger.dim(` Rewritten: ${pipeline.rewritten} file${pipeline.rewritten !== 1 ? "s" : ""}`);
288
+ }
289
+ if (pipeline.aiCostUsd > 0) {
290
+ logger.dim(` Estimated AI cost: ~$${pipeline.aiCostUsd.toFixed(4)}`);
291
+ }
292
+ if (options.dryRun) {
293
+ logger.blank();
294
+ logger.warn("Dry run — no files written.");
295
+ }
296
+ logger.blank();
297
+ // Exit 1 in strict mode or CI mode if validation failed
298
+ if (!options.skipValidate && !pipeline.validated && (config.strictMode || options.ci)) {
299
+ process.exit(1);
300
+ }
301
+ }
302
+ export const runCommand = new Command("run")
303
+ .description("Full pipeline: scan → translate → rewrite → validate")
304
+ .option("--file <file>", "Scope to a single file")
305
+ .option("--dir <dir>", "Scope to a directory")
306
+ .option("--lang <langs>", "Override target languages (comma-separated)")
307
+ .option("--dry-run", "Preview all changes, no writes")
308
+ .option("--skip-rewrite", "Translate and update JSON only, skip source rewrite")
309
+ .option("--skip-validate", "Skip final key coverage check")
310
+ .option("--yes", "Skip all confirmation prompts")
311
+ .option("--force", "Ignore cache, re-process all files")
312
+ .option("--ci", "CI mode: non-interactive, exit 1 on validation failure")
313
+ .action(async (options) => {
314
+ try {
315
+ // CI mode implies --yes
316
+ if (options.ci)
317
+ options.yes = true;
318
+ await runPipeline(options);
319
+ }
320
+ catch (err) {
321
+ logger.fatal(err instanceof Error ? err.message : String(err));
322
+ }
323
+ });
324
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare const scanCommand: Command;
3
+ //# sourceMappingURL=scan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../../src/commands/scan.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmJpC,eAAO,MAAM,WAAW,SAYpB,CAAC"}
@@ -0,0 +1,121 @@
1
+ import { Command } from "commander";
2
+ import { resolve, relative } from "path";
3
+ import ora from "ora";
4
+ import chalk from "chalk";
5
+ import { scanDirectory, buildScanReport, } from "@saidksi/localizer-core";
6
+ import { logger } from "../utils/logger.js";
7
+ import { loadConfig } from "../utils/config.js";
8
+ import { writeScanReport } from "../utils/reporter.js";
9
+ // ─── Output ───────────────────────────────────────────────────────────────────
10
+ /**
11
+ * Print a human-readable table of scan results grouped by file.
12
+ *
13
+ * src/pages/Login.tsx
14
+ * ──────────────────────────────────────────────────────
15
+ * 14:8 "Welcome back" JSXText inside <h1>
16
+ * 18:12 "Enter your email" "placeholder" attribute on <input>
17
+ * 22:6 "Sign in" JSXText inside <button>
18
+ */
19
+ function printScanResults(results, cwd) {
20
+ if (results.length === 0) {
21
+ logger.success("No untranslated strings found.");
22
+ return;
23
+ }
24
+ // Group by file
25
+ const byFile = new Map();
26
+ for (const r of results) {
27
+ const existing = byFile.get(r.file) ?? [];
28
+ existing.push(r);
29
+ byFile.set(r.file, existing);
30
+ }
31
+ logger.blank();
32
+ for (const [file, fileResults] of byFile) {
33
+ const relPath = relative(cwd, file);
34
+ const divider = "─".repeat(Math.min(relPath.length + 4, 60));
35
+ logger.raw(` ${chalk.bold(chalk.cyan(relPath))}`);
36
+ logger.raw(` ${chalk.dim(divider)}`);
37
+ for (const r of fileResults) {
38
+ const loc = chalk.dim(`${r.line}:${r.column}`.padEnd(8));
39
+ const value = chalk.yellow(`"${r.value}"`).padEnd(42);
40
+ const context = chalk.dim(r.context);
41
+ const already = r.alreadyTranslated ? chalk.green(" ✔ already translated") : "";
42
+ logger.raw(` ${loc} ${value} ${context}${already}`);
43
+ }
44
+ logger.blank();
45
+ }
46
+ const totalFiles = byFile.size;
47
+ const untranslated = results.filter((r) => !r.alreadyTranslated);
48
+ logger.raw(` ${chalk.bold("Found")} ${chalk.yellow(untranslated.length)} untranslated string${untranslated.length !== 1 ? "s" : ""}` +
49
+ ` (${results.length - untranslated.length} already translated)` +
50
+ ` in ${totalFiles} file${totalFiles !== 1 ? "s" : ""}.`);
51
+ logger.blank();
52
+ }
53
+ function printNextSteps(options) {
54
+ if (options.file) {
55
+ logger.raw(chalk.dim(` Run \`localize translate --file ${options.file}\` to generate translations.`));
56
+ logger.raw(chalk.dim(` Run \`localize run --file ${options.file}\` for the full pipeline.`));
57
+ }
58
+ else if (options.dir) {
59
+ logger.raw(chalk.dim(` Run \`localize run --dir ${options.dir}\` for the full pipeline.`));
60
+ }
61
+ logger.blank();
62
+ }
63
+ async function runScan(options) {
64
+ const cwd = process.cwd();
65
+ const config = await loadConfig(cwd).catch((err) => logger.fatal(err instanceof Error ? err.message : String(err)));
66
+ const spinner = ora("Scanning...").start();
67
+ let report = { generatedAt: new Date().toISOString(), results: [] };
68
+ try {
69
+ if (options.file) {
70
+ report = await buildScanReport({ file: resolve(cwd, options.file) }, config);
71
+ }
72
+ else if (options.dir) {
73
+ report = await buildScanReport({ dir: resolve(cwd, options.dir) }, config);
74
+ }
75
+ else {
76
+ // Scan all include dirs and merge into a single report
77
+ const all = await Promise.all(config.include.map((dir) => scanDirectory(resolve(cwd, dir), config).catch(() => [])));
78
+ report = {
79
+ generatedAt: new Date().toISOString(),
80
+ results: all.flat(),
81
+ };
82
+ }
83
+ }
84
+ catch (err) {
85
+ spinner.fail("Scan failed.");
86
+ logger.fatal(err instanceof Error ? err.message : String(err));
87
+ }
88
+ const untranslated = report.results.filter((r) => !r.alreadyTranslated);
89
+ spinner.succeed(`Scanned ${new Set(report.results.map((r) => r.file)).size} files — ` +
90
+ `${untranslated.length} untranslated string${untranslated.length !== 1 ? "s" : ""} found.`);
91
+ // --report: print JSON to stdout and exit
92
+ if (options.report) {
93
+ console.log(JSON.stringify(report, null, 2));
94
+ return;
95
+ }
96
+ // Default: human-readable table
97
+ printScanResults(report.results, cwd);
98
+ printNextSteps(options);
99
+ // --output: save JSON to file (.localize/scan/)
100
+ if (options.output === true || typeof options.output === "string") {
101
+ const sourceFile = options.file || options.dir || "";
102
+ const outputFilename = typeof options.output === "string" ? options.output : "";
103
+ const outputPath = await writeScanReport(outputFilename, report, sourceFile, cwd);
104
+ logger.success(`Report saved to ${relative(cwd, outputPath)}`);
105
+ }
106
+ }
107
+ export const scanCommand = new Command("scan")
108
+ .description("Scan a file or directory and show a detailed string-by-string report")
109
+ .option("--file <file>", "Scope to a single file")
110
+ .option("--dir <dir>", "Scope to a directory")
111
+ .option("--report", "Print JSON report to stdout (pipe-friendly)")
112
+ .option("--output [path]", "Save JSON report to .localize/scan/ (auto-generates filename if not provided)")
113
+ .action(async (options) => {
114
+ try {
115
+ await runScan(options);
116
+ }
117
+ catch (err) {
118
+ logger.fatal(err instanceof Error ? err.message : String(err));
119
+ }
120
+ });
121
+ //# sourceMappingURL=scan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.js","sourceRoot":"","sources":["../../src/commands/scan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare const statusCommand: Command;
3
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkJpC,eAAO,MAAM,aAAa,SAStB,CAAC"}
@@ -0,0 +1,128 @@
1
+ import { Command } from "commander";
2
+ import { join, resolve, relative } from "path";
3
+ import { stat } from "fs/promises";
4
+ import chalk from "chalk";
5
+ import ora from "ora";
6
+ import { validateCoverage, readCache, scanDirectory, } from "@saidksi/localizer-core";
7
+ import { logger, progressBar } from "../utils/logger.js";
8
+ import { loadConfig } from "../utils/config.js";
9
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
10
+ function formatAge(isoDate) {
11
+ const diffMs = Date.now() - new Date(isoDate).getTime();
12
+ const diffMin = Math.floor(diffMs / 60_000);
13
+ if (diffMin < 1)
14
+ return "just now";
15
+ if (diffMin < 60)
16
+ return `${diffMin}m ago`;
17
+ const diffH = Math.floor(diffMin / 60);
18
+ if (diffH < 24)
19
+ return `${diffH}h ago`;
20
+ const diffD = Math.floor(diffH / 24);
21
+ return `${diffD}d ago`;
22
+ }
23
+ async function runStatus(options) {
24
+ const cwd = process.cwd();
25
+ const config = await loadConfig(cwd).catch((err) => logger.fatal(err instanceof Error ? err.message : String(err)));
26
+ logger.header("Localize project status");
27
+ // ── Coverage
28
+ const coverageSpinner = ora("Computing key coverage…").start();
29
+ let results = [];
30
+ try {
31
+ const validateOpts = {};
32
+ if (options.lang !== undefined)
33
+ validateOpts.lang = options.lang;
34
+ results = await validateCoverage(config, validateOpts);
35
+ }
36
+ catch (err) {
37
+ coverageSpinner.fail("Coverage check failed.");
38
+ logger.fatal(err instanceof Error ? err.message : String(err));
39
+ }
40
+ coverageSpinner.succeed("Coverage computed.");
41
+ logger.blank();
42
+ logger.raw(chalk.bold(" Key coverage:\n"));
43
+ for (const r of results) {
44
+ console.log(progressBar(r.language, r.coveragePercent, r.totalKeys, r.missingKeys.length));
45
+ }
46
+ const incomplete = results.filter((r) => r.missingKeys.length > 0 && r.language !== config.defaultLanguage);
47
+ if (incomplete.length > 0) {
48
+ logger.blank();
49
+ logger.dim(` Run \`localize translate --missing-only\` to fill ${incomplete.reduce((n, r) => n + r.missingKeys.length, 0)} missing key${incomplete.reduce((n, r) => n + r.missingKeys.length, 0) !== 1 ? "s" : ""}.`);
50
+ }
51
+ // ── Scan summary (untranslated strings in source)
52
+ logger.blank();
53
+ const scanSpinner = ora("Scanning source files…").start();
54
+ let untranslatedCount = 0;
55
+ let untranslatedFiles = 0;
56
+ try {
57
+ const all = await Promise.all(config.include.map((d) => scanDirectory(resolve(cwd, d), config).catch(() => [])));
58
+ const flat = all.flat();
59
+ const untranslated = flat.filter((r) => !r.alreadyTranslated);
60
+ untranslatedCount = untranslated.length;
61
+ untranslatedFiles = new Set(untranslated.map((r) => r.file)).size;
62
+ scanSpinner.succeed(`Source scan complete.`);
63
+ }
64
+ catch {
65
+ scanSpinner.fail("Source scan failed (skipping).");
66
+ }
67
+ logger.blank();
68
+ logger.raw(chalk.bold(" Source scan:\n"));
69
+ if (untranslatedCount > 0) {
70
+ logger.raw(` ${chalk.yellow(untranslatedCount)} untranslated string${untranslatedCount !== 1 ? "s" : ""} in ` +
71
+ `${chalk.yellow(untranslatedFiles)} file${untranslatedFiles !== 1 ? "s" : ""}.`);
72
+ logger.dim(` Run \`localize translate\` to generate keys and translations.`);
73
+ }
74
+ else {
75
+ logger.raw(` ${chalk.green("✔")} All strings in source files are translated.`);
76
+ }
77
+ // ── Cache summary
78
+ logger.blank();
79
+ logger.raw(chalk.bold(" Cache:\n"));
80
+ const cache = await readCache(cwd);
81
+ const cachedEntries = Object.entries(cache.entries);
82
+ if (cachedEntries.length === 0) {
83
+ logger.dim(" No cache entries. Run `localize run` to populate.");
84
+ }
85
+ else {
86
+ const newest = cachedEntries.reduce((latest, [, entry]) => {
87
+ if (!latest)
88
+ return entry.processedAt;
89
+ return entry.processedAt > latest ? entry.processedAt : latest;
90
+ }, null);
91
+ const totalStrings = cachedEntries.reduce((n, [, e]) => n + e.stringCount, 0);
92
+ logger.raw(` ${chalk.cyan(cachedEntries.length)} file${cachedEntries.length !== 1 ? "s" : ""} cached · ${totalStrings} string${totalStrings !== 1 ? "s" : ""} processed.`);
93
+ if (newest) {
94
+ logger.dim(` Last run: ${formatAge(newest)}`);
95
+ }
96
+ logger.dim(` Cache path: ${relative(cwd, join(resolve(cwd), ".localize", "cache.json"))}`);
97
+ }
98
+ // ── Config snapshot
99
+ logger.blank();
100
+ logger.raw(chalk.bold(" Config:\n"));
101
+ logger.raw(` Provider: ${chalk.cyan(config.aiProvider)} (${config.aiModel})`);
102
+ logger.raw(` Languages: ${chalk.cyan(config.defaultLanguage)} (default) + ${config.languages.filter((l) => l !== config.defaultLanguage).join(", ") || chalk.dim("none")}`);
103
+ logger.raw(` Messages: ${config.messagesDir}`);
104
+ logger.raw(` Scan dirs: ${config.include.join(", ")}`);
105
+ if (config.strictMode) {
106
+ logger.raw(` Strict mode: ${chalk.yellow("on")}`);
107
+ }
108
+ logger.blank();
109
+ // Check config file age
110
+ try {
111
+ const configStat = await stat(join(cwd, ".localize", "config.json"));
112
+ logger.dim(` Config last modified: ${formatAge(configStat.mtime.toISOString())}`);
113
+ }
114
+ catch { /* config found elsewhere via cosmiconfig */ }
115
+ logger.blank();
116
+ }
117
+ export const statusCommand = new Command("status")
118
+ .description("Project health snapshot: coverage, last run, cost estimate")
119
+ .option("--lang <lang>", "Focus on a single language")
120
+ .action(async (options) => {
121
+ try {
122
+ await runStatus(options);
123
+ }
124
+ catch (err) {
125
+ logger.fatal(err instanceof Error ? err.message : String(err));
126
+ }
127
+ });
128
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare const translateCommand: Command;
3
+ //# sourceMappingURL=translate.d.ts.map