@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.
- package/LICENSE +21 -0
- package/README.md +99 -0
- package/dist/bin/localize.d.ts +3 -0
- package/dist/bin/localize.d.ts.map +1 -0
- package/dist/bin/localize.js +7 -0
- package/dist/bin/localize.js.map +1 -0
- package/dist/commands/add-lang.d.ts +3 -0
- package/dist/commands/add-lang.d.ts.map +1 -0
- package/dist/commands/add-lang.js +103 -0
- package/dist/commands/add-lang.js.map +1 -0
- package/dist/commands/audit.d.ts +3 -0
- package/dist/commands/audit.d.ts.map +1 -0
- package/dist/commands/audit.js +174 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/diff.d.ts +3 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +76 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +427 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/rewrite.d.ts +3 -0
- package/dist/commands/rewrite.d.ts.map +1 -0
- package/dist/commands/rewrite.js +140 -0
- package/dist/commands/rewrite.js.map +1 -0
- package/dist/commands/run.d.ts +3 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +324 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/scan.d.ts +3 -0
- package/dist/commands/scan.d.ts.map +1 -0
- package/dist/commands/scan.js +121 -0
- package/dist/commands/scan.js.map +1 -0
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +128 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/translate.d.ts +3 -0
- package/dist/commands/translate.d.ts.map +1 -0
- package/dist/commands/translate.js +233 -0
- package/dist/commands/translate.js.map +1 -0
- package/dist/commands/validate.d.ts +3 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +121 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/config.d.ts +30 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +94 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/diff.d.ts +10 -0
- package/dist/utils/diff.d.ts.map +1 -0
- package/dist/utils/diff.js +31 -0
- package/dist/utils/diff.js.map +1 -0
- package/dist/utils/json.d.ts +3 -0
- package/dist/utils/json.d.ts.map +1 -0
- package/dist/utils/json.js +15 -0
- package/dist/utils/json.js.map +1 -0
- package/dist/utils/logger.d.ts +28 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +48 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/prompt.d.ts +18 -0
- package/dist/utils/prompt.d.ts.map +1 -0
- package/dist/utils/prompt.js +65 -0
- package/dist/utils/prompt.js.map +1 -0
- package/dist/utils/reporter.d.ts +17 -0
- package/dist/utils/reporter.d.ts.map +1 -0
- package/dist/utils/reporter.js +87 -0
- package/dist/utils/reporter.js.map +1 -0
- package/package.json +62 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"translate.d.ts","sourceRoot":"","sources":["../../src/commands/translate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAwTpC,eAAO,MAAM,gBAAgB,SAezB,CAAC"}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { resolve, join, basename, extname } from "path";
|
|
3
|
+
import { readFile, readdir, writeFile, mkdir } from "fs/promises";
|
|
4
|
+
import ora from "ora";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import { scanFile, scanDirectory, translateStrings, translateExistingKeys, } from "@saidksi/localizer-core";
|
|
7
|
+
import { logger } from "../utils/logger.js";
|
|
8
|
+
import { loadConfig, requireApiKey } from "../utils/config.js";
|
|
9
|
+
import { flattenJson } from "../utils/json.js";
|
|
10
|
+
/**
|
|
11
|
+
* Write translate results to a JSON file for debugging.
|
|
12
|
+
* Includes scan results with resolved keys, dedup info, and AI calls made.
|
|
13
|
+
* Outputs to .localize/translate/{pageName}_translate.json
|
|
14
|
+
*/
|
|
15
|
+
async function writeDebugOutput(cwd, file, translateResult, scanResults, uniqueStrings) {
|
|
16
|
+
// Create .localize/translate directory
|
|
17
|
+
const translateDir = resolve(cwd, ".localize", "translate");
|
|
18
|
+
await mkdir(translateDir, { recursive: true });
|
|
19
|
+
// Group scan results by file and page name
|
|
20
|
+
const byFile = new Map();
|
|
21
|
+
for (const result of scanResults) {
|
|
22
|
+
const existing = byFile.get(result.file);
|
|
23
|
+
if (existing) {
|
|
24
|
+
existing.push(result);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
byFile.set(result.file, [result]);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const writtenPaths = [];
|
|
31
|
+
// Write debug output for each file scanned
|
|
32
|
+
for (const [sourceFile, fileResults] of byFile) {
|
|
33
|
+
const pageName = basename(sourceFile, extname(sourceFile));
|
|
34
|
+
const debugFileName = `${pageName}_translate.json`;
|
|
35
|
+
const debugOutputPath = join(translateDir, debugFileName);
|
|
36
|
+
// Group results by resolved key to show deduplication
|
|
37
|
+
const byKey = new Map();
|
|
38
|
+
for (const result of fileResults) {
|
|
39
|
+
const key = result.resolvedKey ?? "(NOT TRANSLATED)";
|
|
40
|
+
const existing = byKey.get(key);
|
|
41
|
+
if (existing) {
|
|
42
|
+
existing.push(result);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
byKey.set(key, [result]);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Build debug output
|
|
49
|
+
const debugOutput = {
|
|
50
|
+
metadata: {
|
|
51
|
+
generatedAt: new Date().toISOString(),
|
|
52
|
+
sourceFile: sourceFile,
|
|
53
|
+
outputPath: debugOutputPath,
|
|
54
|
+
},
|
|
55
|
+
summary: {
|
|
56
|
+
totalScanned: fileResults.length,
|
|
57
|
+
uniqueStrings: uniqueStrings,
|
|
58
|
+
translated: translateResult.uniqueStrings,
|
|
59
|
+
aiCalls: translateResult.aiCalls,
|
|
60
|
+
costUsd: translateResult.aiCostUsd,
|
|
61
|
+
filesWritten: translateResult.messagesWritten,
|
|
62
|
+
},
|
|
63
|
+
details: {
|
|
64
|
+
byResolvedKey: Array.from(byKey.entries()).map(([key, results]) => ({
|
|
65
|
+
resolvedKey: key,
|
|
66
|
+
count: results.length,
|
|
67
|
+
examples: results.slice(0, 3).map(r => ({
|
|
68
|
+
file: r.file,
|
|
69
|
+
line: r.line,
|
|
70
|
+
value: r.value,
|
|
71
|
+
context: r.context,
|
|
72
|
+
})),
|
|
73
|
+
})),
|
|
74
|
+
},
|
|
75
|
+
missingTranslations: fileResults
|
|
76
|
+
.filter(r => !r.resolvedKey)
|
|
77
|
+
.map(r => ({
|
|
78
|
+
file: r.file,
|
|
79
|
+
line: r.line,
|
|
80
|
+
value: r.value,
|
|
81
|
+
context: r.context,
|
|
82
|
+
})),
|
|
83
|
+
};
|
|
84
|
+
await writeFile(debugOutputPath, JSON.stringify(debugOutput, null, 2) + "\n", "utf-8");
|
|
85
|
+
writtenPaths.push(debugOutputPath);
|
|
86
|
+
}
|
|
87
|
+
return writtenPaths;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Read all pages from messages/{defaultLang}/ and return ExistingKeyEntry[].
|
|
91
|
+
* If `page` is provided, only read that page file.
|
|
92
|
+
*/
|
|
93
|
+
async function readExistingEntries(messagesDir, defaultLang, page) {
|
|
94
|
+
const langDir = join(resolve(messagesDir), defaultLang);
|
|
95
|
+
let files;
|
|
96
|
+
try {
|
|
97
|
+
const all = await readdir(langDir);
|
|
98
|
+
files = all.filter((f) => f.endsWith(".json"));
|
|
99
|
+
if (page)
|
|
100
|
+
files = files.filter((f) => basename(f, ".json") === page);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
const entries = [];
|
|
106
|
+
for (const file of files) {
|
|
107
|
+
const pageName = basename(file, extname(file));
|
|
108
|
+
const raw = await readFile(join(langDir, file), "utf-8").catch(() => "{}");
|
|
109
|
+
const flat = flattenJson(JSON.parse(raw));
|
|
110
|
+
for (const [key, value] of Object.entries(flat)) {
|
|
111
|
+
entries.push({ key, value, pageName });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return entries;
|
|
115
|
+
}
|
|
116
|
+
// ─── Output ───────────────────────────────────────────────────────────────────
|
|
117
|
+
function printTranslateResult(translated, aiCalls, messagesWritten, aiCostUsd, dryRun) {
|
|
118
|
+
logger.blank();
|
|
119
|
+
if (dryRun) {
|
|
120
|
+
logger.warn("Dry run — no files written.");
|
|
121
|
+
logger.dim(`Would translate ${translated} unique string${translated !== 1 ? "s" : ""} via ${aiCalls} AI call${aiCalls !== 1 ? "s" : ""}.`);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
logger.success(`Translated ${chalk.yellow(translated)} string${translated !== 1 ? "s" : ""} via ${aiCalls} AI call${aiCalls !== 1 ? "s" : ""}.`);
|
|
125
|
+
const uniqueFiles = [...new Set(messagesWritten)];
|
|
126
|
+
for (const f of uniqueFiles) {
|
|
127
|
+
logger.dim(`Updated ${f}`);
|
|
128
|
+
}
|
|
129
|
+
if (aiCostUsd > 0) {
|
|
130
|
+
logger.dim(`Estimated cost: ~$${aiCostUsd.toFixed(4)}`);
|
|
131
|
+
}
|
|
132
|
+
logger.blank();
|
|
133
|
+
}
|
|
134
|
+
async function runTranslate(options) {
|
|
135
|
+
const cwd = process.cwd();
|
|
136
|
+
const config = await loadConfig(cwd).catch((err) => logger.fatal(err instanceof Error ? err.message : String(err)));
|
|
137
|
+
const apiKey = await requireApiKey(config.aiProvider).catch((err) => logger.fatal(err instanceof Error ? err.message : String(err)));
|
|
138
|
+
// --lang override: replace config.languages for this run
|
|
139
|
+
const langs = options.lang
|
|
140
|
+
? options.lang.split(",").map((l) => l.trim()).filter(Boolean)
|
|
141
|
+
: config.languages;
|
|
142
|
+
const effectiveConfig = { ...config, languages: langs };
|
|
143
|
+
// ── Path A: --from-existing
|
|
144
|
+
if (options.fromExisting) {
|
|
145
|
+
const page = options.file
|
|
146
|
+
? basename(options.file, extname(options.file)).toLowerCase()
|
|
147
|
+
: undefined;
|
|
148
|
+
const spinner = ora("Reading existing translation keys...").start();
|
|
149
|
+
const entries = await readExistingEntries(config.messagesDir, config.defaultLanguage, page);
|
|
150
|
+
spinner.succeed(`Found ${entries.length} existing key${entries.length !== 1 ? "s" : ""}.`);
|
|
151
|
+
if (entries.length === 0) {
|
|
152
|
+
logger.warn("No existing keys found. Run `localize translate --file <file>` first.");
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const aiSpinner = ora(`Translating ${entries.length} keys into ${langs.join(", ")}...`).start();
|
|
156
|
+
const existingOpts = {
|
|
157
|
+
overwrite: !options.missingOnly,
|
|
158
|
+
};
|
|
159
|
+
if (options.dryRun !== undefined)
|
|
160
|
+
existingOpts.dryRun = options.dryRun;
|
|
161
|
+
const result = await translateExistingKeys(entries, effectiveConfig, apiKey, existingOpts);
|
|
162
|
+
aiSpinner.succeed("Translation complete.");
|
|
163
|
+
printTranslateResult(result.translated, result.aiCalls, result.messagesWritten, result.aiCostUsd, options.dryRun ?? false);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
// ── Path B: scan-based translation
|
|
167
|
+
const scanSpinner = ora("Scanning for untranslated strings...").start();
|
|
168
|
+
let results = [];
|
|
169
|
+
try {
|
|
170
|
+
if (options.file) {
|
|
171
|
+
results = await scanFile(resolve(cwd, options.file), effectiveConfig);
|
|
172
|
+
}
|
|
173
|
+
else if (options.dir) {
|
|
174
|
+
results = await scanDirectory(resolve(cwd, options.dir), effectiveConfig);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
const all = await Promise.all(config.include.map((d) => scanDirectory(resolve(cwd, d), effectiveConfig).catch(() => [])));
|
|
178
|
+
results = all.flat();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
scanSpinner.fail("Scan failed.");
|
|
183
|
+
logger.fatal(err instanceof Error ? err.message : String(err));
|
|
184
|
+
}
|
|
185
|
+
const untranslated = results.filter((r) => !r.alreadyTranslated);
|
|
186
|
+
scanSpinner.succeed(`Found ${untranslated.length} untranslated string${untranslated.length !== 1 ? "s" : ""}.`);
|
|
187
|
+
if (untranslated.length === 0) {
|
|
188
|
+
logger.success("Nothing to translate.");
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const aiSpinner = ora(`Calling ${effectiveConfig.aiProvider} (${effectiveConfig.aiModel})...`).start();
|
|
192
|
+
const translateOpts = {
|
|
193
|
+
overwrite: !options.missingOnly,
|
|
194
|
+
};
|
|
195
|
+
if (options.dryRun !== undefined)
|
|
196
|
+
translateOpts.dryRun = options.dryRun;
|
|
197
|
+
const translateResult = await translateStrings(results, effectiveConfig, apiKey, translateOpts);
|
|
198
|
+
aiSpinner.succeed("Translation complete.");
|
|
199
|
+
// Write debug output to .localize/translate/ only if --output is specified
|
|
200
|
+
if (options.output) {
|
|
201
|
+
const debugSpinner = ora("Writing debug output...").start();
|
|
202
|
+
try {
|
|
203
|
+
const debugPaths = await writeDebugOutput(cwd, options.file, translateResult, translateResult.results, translateResult.uniqueStrings);
|
|
204
|
+
debugSpinner.succeed(`Debug output written to .localize/translate/`);
|
|
205
|
+
for (const path of debugPaths) {
|
|
206
|
+
logger.dim(` ${path}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
catch (err) {
|
|
210
|
+
debugSpinner.fail("Failed to write debug output.");
|
|
211
|
+
logger.warn(err instanceof Error ? err.message : String(err));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
printTranslateResult(translateResult.uniqueStrings, translateResult.aiCalls, translateResult.messagesWritten, translateResult.aiCostUsd, options.dryRun ?? false);
|
|
215
|
+
}
|
|
216
|
+
export const translateCommand = new Command("translate")
|
|
217
|
+
.description("Run AI to generate keys and translations, write to messages JSON")
|
|
218
|
+
.option("--file <file>", "Scope to a single file")
|
|
219
|
+
.option("--dir <dir>", "Scope to a directory")
|
|
220
|
+
.option("--lang <langs>", "Target languages override (comma-separated, e.g. fr,ar)")
|
|
221
|
+
.option("--from-existing", "Translate all keys in the default language JSON (no scan)")
|
|
222
|
+
.option("--missing-only", "Only translate keys missing from target language JSONs")
|
|
223
|
+
.option("--dry-run", "Preview AI output without writing files")
|
|
224
|
+
.option("--output", "Write debug output to .localize/translate/")
|
|
225
|
+
.action(async (options) => {
|
|
226
|
+
try {
|
|
227
|
+
await runTranslate(options);
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
logger.fatal(err instanceof Error ? err.message : String(err));
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
//# sourceMappingURL=translate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"translate.js","sourceRoot":"","sources":["../../src/commands/translate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkJpC,eAAO,MAAM,eAAe,SAWxB,CAAC"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { basename, extname } from "path";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { validateCoverage } from "@saidksi/localizer-core";
|
|
6
|
+
import { logger, progressBar } from "../utils/logger.js";
|
|
7
|
+
import { loadConfig } from "../utils/config.js";
|
|
8
|
+
// ─── Output ───────────────────────────────────────────────────────────────────
|
|
9
|
+
const MISSING_PREVIEW = 10;
|
|
10
|
+
function printCoverageTable(results) {
|
|
11
|
+
logger.blank();
|
|
12
|
+
logger.raw(chalk.bold(" Key coverage report:\n"));
|
|
13
|
+
for (const r of results) {
|
|
14
|
+
console.log(progressBar(r.language, r.coveragePercent, r.totalKeys, r.missingKeys.length));
|
|
15
|
+
}
|
|
16
|
+
logger.blank();
|
|
17
|
+
}
|
|
18
|
+
function printMissingKeys(results) {
|
|
19
|
+
const incomplete = results.filter((r) => r.missingKeys.length > 0);
|
|
20
|
+
if (incomplete.length === 0)
|
|
21
|
+
return;
|
|
22
|
+
for (const r of incomplete) {
|
|
23
|
+
const total = r.missingKeys.length;
|
|
24
|
+
const preview = r.missingKeys.slice(0, MISSING_PREVIEW);
|
|
25
|
+
const more = total - preview.length;
|
|
26
|
+
logger.raw(` ${chalk.bold(`Missing keys in ${chalk.red(r.language)}`)} ` +
|
|
27
|
+
chalk.dim(`(${total > MISSING_PREVIEW ? `first ${MISSING_PREVIEW} of ` : ""}${total}):`));
|
|
28
|
+
for (const key of preview) {
|
|
29
|
+
logger.raw(` ${chalk.dim("·")} ${key}`);
|
|
30
|
+
}
|
|
31
|
+
if (more > 0) {
|
|
32
|
+
logger.dim(` … and ${more} more`);
|
|
33
|
+
}
|
|
34
|
+
logger.blank();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function printNextSteps(results, config, lang) {
|
|
38
|
+
const incomplete = results.filter((r) => r.missingKeys.length > 0 && r.language !== config.defaultLanguage);
|
|
39
|
+
if (incomplete.length === 0)
|
|
40
|
+
return;
|
|
41
|
+
const langFlag = lang ? ` --lang ${lang}` : "";
|
|
42
|
+
logger.raw(chalk.dim(` Run \`localize translate --missing-only${langFlag}\` to fill missing keys.`));
|
|
43
|
+
logger.blank();
|
|
44
|
+
}
|
|
45
|
+
// ─── CI output ────────────────────────────────────────────────────────────────
|
|
46
|
+
/**
|
|
47
|
+
* Machine-readable output for CI environments.
|
|
48
|
+
* Prints a JSON summary to stdout.
|
|
49
|
+
*/
|
|
50
|
+
function printCiOutput(results) {
|
|
51
|
+
const output = results.map((r) => ({
|
|
52
|
+
language: r.language,
|
|
53
|
+
coveragePercent: r.coveragePercent,
|
|
54
|
+
totalKeys: r.totalKeys,
|
|
55
|
+
missingCount: r.missingKeys.length,
|
|
56
|
+
missingKeys: r.missingKeys,
|
|
57
|
+
}));
|
|
58
|
+
console.log(JSON.stringify(output, null, 2));
|
|
59
|
+
}
|
|
60
|
+
async function runValidate(options) {
|
|
61
|
+
const cwd = process.cwd();
|
|
62
|
+
const config = await loadConfig(cwd).catch((err) => logger.fatal(err instanceof Error ? err.message : String(err)));
|
|
63
|
+
// Derive page name from --file if provided.
|
|
64
|
+
// Source file path maps directly to its per-page JSON:
|
|
65
|
+
// src/pages/Login.tsx → messages/{lang}/login.json
|
|
66
|
+
// app/checkout.tsx → messages/{lang}/checkout.json
|
|
67
|
+
const page = options.file
|
|
68
|
+
? basename(options.file, extname(options.file)).toLowerCase()
|
|
69
|
+
: undefined;
|
|
70
|
+
if (page) {
|
|
71
|
+
const langs = options.lang ? [options.lang] : config.languages;
|
|
72
|
+
const files = langs.map((l) => `${config.messagesDir}/${l}/${page}.json`).join(", ");
|
|
73
|
+
logger.dim(` Scoped to page: ${chalk.cyan(`${page}.json`)} (${files})`);
|
|
74
|
+
logger.blank();
|
|
75
|
+
}
|
|
76
|
+
const spinner = ora("Checking key coverage...").start();
|
|
77
|
+
let results = [];
|
|
78
|
+
try {
|
|
79
|
+
const validateOpts = {};
|
|
80
|
+
if (options.lang !== undefined)
|
|
81
|
+
validateOpts.lang = options.lang;
|
|
82
|
+
if (page !== undefined)
|
|
83
|
+
validateOpts.page = page;
|
|
84
|
+
results = await validateCoverage(config, validateOpts);
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
spinner.fail("Validation failed.");
|
|
88
|
+
logger.fatal(err instanceof Error ? err.message : String(err));
|
|
89
|
+
}
|
|
90
|
+
const totalMissing = results.reduce((n, r) => n + r.missingKeys.length, 0);
|
|
91
|
+
const allCovered = totalMissing === 0;
|
|
92
|
+
spinner.succeed(allCovered
|
|
93
|
+
? "All keys present across all languages."
|
|
94
|
+
: `${totalMissing} missing key${totalMissing !== 1 ? "s" : ""} found.`);
|
|
95
|
+
if (options.ci) {
|
|
96
|
+
printCiOutput(results);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
printCoverageTable(results);
|
|
100
|
+
printMissingKeys(results);
|
|
101
|
+
printNextSteps(results, config, options.lang);
|
|
102
|
+
}
|
|
103
|
+
// Exit 1 in CI mode or strict mode when keys are missing
|
|
104
|
+
if (!allCovered && (options.ci || config.strictMode)) {
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export const validateCommand = new Command("validate")
|
|
109
|
+
.description("Check key coverage across all language JSON files")
|
|
110
|
+
.option("--lang <lang>", "Check a single language only")
|
|
111
|
+
.option("--file <file>", "Scope to keys from one source file (by page name)")
|
|
112
|
+
.option("--ci", "Machine-readable JSON output, exit 1 on any missing keys")
|
|
113
|
+
.action(async (options) => {
|
|
114
|
+
try {
|
|
115
|
+
await runValidate(options);
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
logger.fatal(err instanceof Error ? err.message : String(err));
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAkBA,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAiC1C"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { createRequire } from "module";
|
|
3
|
+
import { initCommand } from "./commands/init.js";
|
|
4
|
+
import { auditCommand } from "./commands/audit.js";
|
|
5
|
+
import { scanCommand } from "./commands/scan.js";
|
|
6
|
+
import { translateCommand } from "./commands/translate.js";
|
|
7
|
+
import { rewriteCommand } from "./commands/rewrite.js";
|
|
8
|
+
import { validateCommand } from "./commands/validate.js";
|
|
9
|
+
import { runCommand } from "./commands/run.js";
|
|
10
|
+
import { addLangCommand } from "./commands/add-lang.js";
|
|
11
|
+
import { statusCommand } from "./commands/status.js";
|
|
12
|
+
import { diffCommand } from "./commands/diff.js";
|
|
13
|
+
// Read version from package.json without importing JSON (ESM-safe)
|
|
14
|
+
const require = createRequire(import.meta.url);
|
|
15
|
+
const { version } = require("../package.json");
|
|
16
|
+
export async function main() {
|
|
17
|
+
const program = new Command();
|
|
18
|
+
program
|
|
19
|
+
.name("localize")
|
|
20
|
+
.description("Automate the full i18n workflow for JavaScript and TypeScript codebases")
|
|
21
|
+
.version(version, "-v, --version", "Print the current version")
|
|
22
|
+
.helpOption("-h, --help", "Show help")
|
|
23
|
+
// Show help when called with no arguments
|
|
24
|
+
.addHelpCommand("help [command]", "Show help for a command")
|
|
25
|
+
.configureOutput({
|
|
26
|
+
// Route errors through our logger style
|
|
27
|
+
outputError: (str, write) => write(`\n ${str}`),
|
|
28
|
+
});
|
|
29
|
+
// Register all commands
|
|
30
|
+
program.addCommand(initCommand);
|
|
31
|
+
program.addCommand(auditCommand);
|
|
32
|
+
program.addCommand(scanCommand);
|
|
33
|
+
program.addCommand(translateCommand);
|
|
34
|
+
program.addCommand(rewriteCommand);
|
|
35
|
+
program.addCommand(validateCommand);
|
|
36
|
+
program.addCommand(runCommand);
|
|
37
|
+
program.addCommand(addLangCommand);
|
|
38
|
+
program.addCommand(statusCommand);
|
|
39
|
+
program.addCommand(diffCommand);
|
|
40
|
+
// Show help when called with no arguments
|
|
41
|
+
if (process.argv.length <= 2) {
|
|
42
|
+
program.help();
|
|
43
|
+
}
|
|
44
|
+
await program.parseAsync(process.argv);
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAEvC,OAAO,EAAE,WAAW,EAAE,MAAW,oBAAoB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAU,qBAAqB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAW,oBAAoB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAQ,uBAAuB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAO,wBAAwB,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAY,mBAAmB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAQ,wBAAwB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAS,sBAAsB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAW,oBAAoB,CAAC;AAEtD,mEAAmE;AACnE,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,iBAAiB,CAAwB,CAAC;AAEtE,MAAM,CAAC,KAAK,UAAU,IAAI;IACxB,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;IAE9B,OAAO;SACJ,IAAI,CAAC,UAAU,CAAC;SAChB,WAAW,CAAC,yEAAyE,CAAC;SACtF,OAAO,CAAC,OAAO,EAAE,eAAe,EAAE,2BAA2B,CAAC;SAC9D,UAAU,CAAC,YAAY,EAAE,WAAW,CAAC;QACtC,0CAA0C;SACzC,cAAc,CAAC,gBAAgB,EAAE,yBAAyB,CAAC;SAC3D,eAAe,CAAC;QACf,wCAAwC;QACxC,WAAW,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC;KACjD,CAAC,CAAC;IAEL,wBAAwB;IACxB,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;IAChC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;IACjC,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;IAChC,OAAO,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;IACrC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;IACnC,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;IACpC,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;IAC/B,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;IACnC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;IAClC,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;IAEhC,0CAA0C;IAC1C,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;QAC7B,OAAO,CAAC,IAAI,EAAE,CAAC;IACjB,CAAC;IAED,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { LocalizeConfig, AIProvider } from "@saidksi/localizer-core";
|
|
2
|
+
/**
|
|
3
|
+
* Load the project config from `{cwd}/.localize/config.json`.
|
|
4
|
+
* Throws a user-friendly error if no config is found.
|
|
5
|
+
*/
|
|
6
|
+
export declare function loadConfig(cwd?: string): Promise<LocalizeConfig>;
|
|
7
|
+
/**
|
|
8
|
+
* Write a full project config to `{cwd}/.localize/config.json`.
|
|
9
|
+
* Creates the `.localize/` directory if needed and writes a `.gitignore` inside it.
|
|
10
|
+
*/
|
|
11
|
+
export declare function writeProjectConfig(config: LocalizeConfig, cwd?: string): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* Save an API key to `{cwd}/.localize/.keys`.
|
|
14
|
+
* Merges into existing entries (does not overwrite other provider keys).
|
|
15
|
+
*/
|
|
16
|
+
export declare function saveApiKey(provider: AIProvider, key: string, cwd?: string): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Retrieve the API key for the configured provider.
|
|
19
|
+
* Checks in order:
|
|
20
|
+
* 1. Environment variable (ANTHROPIC_API_KEY / OPENAI_API_KEY)
|
|
21
|
+
* 2. {cwd}/.localize/.keys
|
|
22
|
+
*
|
|
23
|
+
* Returns null if no key is found.
|
|
24
|
+
*/
|
|
25
|
+
export declare function getApiKey(provider: AIProvider, cwd?: string): Promise<string | null>;
|
|
26
|
+
/**
|
|
27
|
+
* Like getApiKey but throws a user-friendly error if no key is found.
|
|
28
|
+
*/
|
|
29
|
+
export declare function requireApiKey(provider: AIProvider, cwd?: string): Promise<string>;
|
|
30
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/utils/config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAW1E;;;GAGG;AACH,wBAAsB,UAAU,CAAC,GAAG,SAAgB,GAAG,OAAO,CAAC,cAAc,CAAC,CAU7E;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,cAAc,EACtB,GAAG,SAAgB,GAClB,OAAO,CAAC,IAAI,CAAC,CAef;AAsBD;;;GAGG;AACH,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,UAAU,EACpB,GAAG,EAAE,MAAM,EACX,GAAG,SAAgB,GAClB,OAAO,CAAC,IAAI,CAAC,CAOf;AAED;;;;;;;GAOG;AACH,wBAAsB,SAAS,CAC7B,QAAQ,EAAE,UAAU,EACpB,GAAG,SAAgB,GAClB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAUxB;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,UAAU,EACpB,GAAG,SAAgB,GAClB,OAAO,CAAC,MAAM,CAAC,CAWjB"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
2
|
+
import { join, resolve } from "path";
|
|
3
|
+
// ─── Paths ─────────────────────────────────────────────────────────────────────
|
|
4
|
+
const LOCALIZE_DIR = ".localize";
|
|
5
|
+
const CONFIG_FILENAME = "config.json";
|
|
6
|
+
const KEYS_FILENAME = ".keys";
|
|
7
|
+
const GITIGNORE_ENTRY = ".keys\ncache.json\n";
|
|
8
|
+
// ─── Project config (.localize/config.json) ───────────────────────────────────
|
|
9
|
+
/**
|
|
10
|
+
* Load the project config from `{cwd}/.localize/config.json`.
|
|
11
|
+
* Throws a user-friendly error if no config is found.
|
|
12
|
+
*/
|
|
13
|
+
export async function loadConfig(cwd = process.cwd()) {
|
|
14
|
+
const configPath = resolve(cwd, LOCALIZE_DIR, CONFIG_FILENAME);
|
|
15
|
+
try {
|
|
16
|
+
const content = await readFile(configPath, "utf-8");
|
|
17
|
+
return JSON.parse(content);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
throw new Error("No config found. Run `localize init` to create .localize/config.json");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Write a full project config to `{cwd}/.localize/config.json`.
|
|
25
|
+
* Creates the `.localize/` directory if needed and writes a `.gitignore` inside it.
|
|
26
|
+
*/
|
|
27
|
+
export async function writeProjectConfig(config, cwd = process.cwd()) {
|
|
28
|
+
const localizeDir = resolve(cwd, LOCALIZE_DIR);
|
|
29
|
+
await mkdir(localizeDir, { recursive: true });
|
|
30
|
+
const configPath = join(localizeDir, CONFIG_FILENAME);
|
|
31
|
+
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
32
|
+
// Ensure .gitignore inside .localize/ to protect .keys and cache
|
|
33
|
+
const gitignorePath = join(localizeDir, ".gitignore");
|
|
34
|
+
try {
|
|
35
|
+
await readFile(gitignorePath, "utf-8");
|
|
36
|
+
// Already exists — leave it
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
await writeFile(gitignorePath, GITIGNORE_ENTRY, "utf-8");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function keysFilePath(cwd) {
|
|
43
|
+
return resolve(cwd, LOCALIZE_DIR, KEYS_FILENAME);
|
|
44
|
+
}
|
|
45
|
+
async function loadKeysFile(cwd) {
|
|
46
|
+
try {
|
|
47
|
+
const content = await readFile(keysFilePath(cwd), "utf-8");
|
|
48
|
+
return JSON.parse(content);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Save an API key to `{cwd}/.localize/.keys`.
|
|
56
|
+
* Merges into existing entries (does not overwrite other provider keys).
|
|
57
|
+
*/
|
|
58
|
+
export async function saveApiKey(provider, key, cwd = process.cwd()) {
|
|
59
|
+
const localizeDir = resolve(cwd, LOCALIZE_DIR);
|
|
60
|
+
await mkdir(localizeDir, { recursive: true });
|
|
61
|
+
const existing = await loadKeysFile(cwd);
|
|
62
|
+
const updated = { ...existing, [provider]: key };
|
|
63
|
+
await writeFile(keysFilePath(cwd), JSON.stringify(updated, null, 2) + "\n", "utf-8");
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Retrieve the API key for the configured provider.
|
|
67
|
+
* Checks in order:
|
|
68
|
+
* 1. Environment variable (ANTHROPIC_API_KEY / OPENAI_API_KEY)
|
|
69
|
+
* 2. {cwd}/.localize/.keys
|
|
70
|
+
*
|
|
71
|
+
* Returns null if no key is found.
|
|
72
|
+
*/
|
|
73
|
+
export async function getApiKey(provider, cwd = process.cwd()) {
|
|
74
|
+
const envKey = provider === "anthropic"
|
|
75
|
+
? process.env["ANTHROPIC_API_KEY"]
|
|
76
|
+
: process.env["OPENAI_API_KEY"];
|
|
77
|
+
if (envKey)
|
|
78
|
+
return envKey;
|
|
79
|
+
const keys = await loadKeysFile(cwd);
|
|
80
|
+
return keys[provider] ?? null;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Like getApiKey but throws a user-friendly error if no key is found.
|
|
84
|
+
*/
|
|
85
|
+
export async function requireApiKey(provider, cwd = process.cwd()) {
|
|
86
|
+
const key = await getApiKey(provider, cwd);
|
|
87
|
+
if (!key) {
|
|
88
|
+
const envVar = provider === "anthropic" ? "ANTHROPIC_API_KEY" : "OPENAI_API_KEY";
|
|
89
|
+
throw new Error(`No API key found for ${provider}.\n` +
|
|
90
|
+
` Set ${envVar} in your environment, or run \`localize init\` to save one.`);
|
|
91
|
+
}
|
|
92
|
+
return key;
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/utils/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Print a colorized diff to the terminal.
|
|
3
|
+
* - Lines starting with "-" → red
|
|
4
|
+
* - Lines starting with "+" → green
|
|
5
|
+
* - "---" / "+++" headers → bold dim
|
|
6
|
+
* - "@@" separators → cyan
|
|
7
|
+
* - Context lines → dim
|
|
8
|
+
*/
|
|
9
|
+
export declare function printDiff(diff: string): void;
|
|
10
|
+
//# sourceMappingURL=diff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/utils/diff.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAe5C"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
/**
|
|
3
|
+
* Print a colorized diff to the terminal.
|
|
4
|
+
* - Lines starting with "-" → red
|
|
5
|
+
* - Lines starting with "+" → green
|
|
6
|
+
* - "---" / "+++" headers → bold dim
|
|
7
|
+
* - "@@" separators → cyan
|
|
8
|
+
* - Context lines → dim
|
|
9
|
+
*/
|
|
10
|
+
export function printDiff(diff) {
|
|
11
|
+
if (!diff)
|
|
12
|
+
return;
|
|
13
|
+
for (const line of diff.split("\n")) {
|
|
14
|
+
if (line.startsWith("---") || line.startsWith("+++")) {
|
|
15
|
+
console.log(chalk.bold.dim(line));
|
|
16
|
+
}
|
|
17
|
+
else if (line.startsWith("@@")) {
|
|
18
|
+
console.log(chalk.cyan(line));
|
|
19
|
+
}
|
|
20
|
+
else if (line.startsWith("- ")) {
|
|
21
|
+
console.log(chalk.red(line));
|
|
22
|
+
}
|
|
23
|
+
else if (line.startsWith("+ ")) {
|
|
24
|
+
console.log(chalk.green(line));
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
console.log(chalk.dim(line));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/utils/diff.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,IAAI,CAAC,IAAI;QAAE,OAAO;IAClB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;YACrD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;QACpC,CAAC;aAAM,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACjC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAChC,CAAC;aAAM,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACjC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;QAC/B,CAAC;aAAM,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACjC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QACjC,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;AACH,CAAC"}
|