@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 @@
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,3 @@
1
+ import { Command } from "commander";
2
+ export declare const validateCommand: Command;
3
+ //# sourceMappingURL=validate.d.ts.map
@@ -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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"}
@@ -0,0 +1,2 @@
1
+ export declare function main(): Promise<void>;
2
+ //# sourceMappingURL=index.d.ts.map
@@ -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"}