@babelize/cli 1.0.13 → 1.1.0
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 +200 -4
- package/README.md +44 -15
- package/dist/commands/check.d.ts +22 -0
- package/dist/commands/check.d.ts.map +1 -0
- package/dist/commands/check.js +134 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/glossary.d.ts.map +1 -1
- package/dist/commands/glossary.js +2 -1
- package/dist/commands/glossary.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +52 -52
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/tm.d.ts.map +1 -1
- package/dist/commands/tm.js +29 -11
- package/dist/commands/tm.js.map +1 -1
- package/dist/commands/translate.d.ts +24 -0
- package/dist/commands/translate.d.ts.map +1 -0
- package/dist/commands/translate.js +415 -0
- package/dist/commands/translate.js.map +1 -0
- package/dist/local/formatter.d.ts +27 -0
- package/dist/local/formatter.d.ts.map +1 -0
- package/dist/local/formatter.js +69 -0
- package/dist/local/formatter.js.map +1 -0
- package/dist/local/git.d.ts +16 -0
- package/dist/local/git.d.ts.map +1 -0
- package/dist/local/git.js +52 -0
- package/dist/local/git.js.map +1 -0
- package/dist/local/provider.d.ts +41 -0
- package/dist/local/provider.d.ts.map +1 -0
- package/dist/local/provider.js +87 -0
- package/dist/local/provider.js.map +1 -0
- package/dist/local/translate-core.d.ts +188 -0
- package/dist/local/translate-core.d.ts.map +1 -0
- package/dist/local/translate-core.js +401 -0
- package/dist/local/translate-core.js.map +1 -0
- package/dist/local/workspace-copy.d.ts +75 -0
- package/dist/local/workspace-copy.d.ts.map +1 -0
- package/dist/local/workspace-copy.js +263 -0
- package/dist/local/workspace-copy.js.map +1 -0
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +6 -0
- package/dist/program.js.map +1 -1
- package/dist/tui/repl.d.ts.map +1 -1
- package/dist/tui/repl.js +2 -0
- package/dist/tui/repl.js.map +1 -1
- package/dist/utils/zip-upload.d.ts +81 -0
- package/dist/utils/zip-upload.d.ts.map +1 -0
- package/dist/utils/zip-upload.js +277 -0
- package/dist/utils/zip-upload.js.map +1 -0
- package/package.json +6 -2
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import * as clack from '@clack/prompts';
|
|
2
|
+
import { isJsonMode, printJson } from '../formatters.js';
|
|
3
|
+
import { globalOptions, isNonInteractive } from '../global-options.js';
|
|
4
|
+
import { ExitCode } from '../exit-codes.js';
|
|
5
|
+
import { renderSimpleHelp } from '../tui/help.js';
|
|
6
|
+
import { brand, muted, success, warning, error as errorColor, sym } from '../tui/theme.js';
|
|
7
|
+
import { config } from '../config.js';
|
|
8
|
+
import * as path from 'node:path';
|
|
9
|
+
import { buildTranslateReport, DEFAULT_REPORT_FILE, HostedTranslationProvider, isGitIgnored, isHostedResolution, LOCAL_LOCKFILE, LocalTranslateError, parseLocales, PseudoTranslationProvider, resolveProvider, runLocalTranslate, writeTranslateReport, } from '@babelize/engine';
|
|
10
|
+
/**
|
|
11
|
+
* `--provider pseudo`: offline pseudo-localization ("Save" → "⟦Sávé⟧"), for
|
|
12
|
+
* checking i18n wiring and for CI without an API key. Explicit only (never
|
|
13
|
+
* auto-detected) and not listed in --help.
|
|
14
|
+
*/
|
|
15
|
+
export const PSEUDO_PROVIDER = 'pseudo';
|
|
16
|
+
export const PSEUDO_WARNING = 'Pseudo-localization — not real translations (fake text like ⟦Sávé⟧ for testing i18n wiring). A real run will replace them; never ship them.';
|
|
17
|
+
const FRAMEWORK_NAMES = {
|
|
18
|
+
react: 'React',
|
|
19
|
+
nextjs: 'Next.js',
|
|
20
|
+
vue: 'Vue',
|
|
21
|
+
static: 'Static HTML',
|
|
22
|
+
};
|
|
23
|
+
const EXIT_BY_CODE = {
|
|
24
|
+
not_a_repo: ExitCode.USAGE,
|
|
25
|
+
git_missing: ExitCode.CONFIG,
|
|
26
|
+
dirty: ExitCode.USAGE,
|
|
27
|
+
invalid_locale: ExitCode.USAGE,
|
|
28
|
+
no_translator: ExitCode.CONFIG,
|
|
29
|
+
unsupported_framework: ExitCode.CONFIG,
|
|
30
|
+
translator_unreachable: ExitCode.AUTH,
|
|
31
|
+
hosted_auth: ExitCode.AUTH,
|
|
32
|
+
hosted_credits: ExitCode.API_ERROR,
|
|
33
|
+
engine_failed: ExitCode.API_ERROR,
|
|
34
|
+
incomplete_translation: ExitCode.API_ERROR,
|
|
35
|
+
apply_failed: ExitCode.INTERNAL,
|
|
36
|
+
project_changed: ExitCode.USAGE,
|
|
37
|
+
};
|
|
38
|
+
/** How many keys to name inline before summarising. */
|
|
39
|
+
const KEY_PREVIEW = 5;
|
|
40
|
+
function fail(json, code, message, exitCode, changedFiles) {
|
|
41
|
+
if (json)
|
|
42
|
+
printJson({ ok: false, error: { code, message, ...(changedFiles?.length ? { changedFiles } : {}) } });
|
|
43
|
+
else
|
|
44
|
+
console.error(`\n ${errorColor(sym.cross)} ${message.split('\n').join('\n ')}\n`);
|
|
45
|
+
process.exit(exitCode);
|
|
46
|
+
}
|
|
47
|
+
/** True when animated terminal output is appropriate (a real TTY, not CI). */
|
|
48
|
+
export function isAnimatedOutput() {
|
|
49
|
+
return process.stdout.isTTY === true && !(process.env.CI && process.env.CI !== 'false');
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The clack spinner on a TTY; otherwise (piped output, CI logs) one plain
|
|
53
|
+
* line per pipeline phase instead of hundreds of spinner redraws.
|
|
54
|
+
*/
|
|
55
|
+
export function createProgress(animated = isAnimatedOutput()) {
|
|
56
|
+
if (animated) {
|
|
57
|
+
const s = clack.spinner();
|
|
58
|
+
return { start: (m) => s.start(m), message: (m) => s.message(m), stop: (m) => s.stop(m) };
|
|
59
|
+
}
|
|
60
|
+
let lastPhase;
|
|
61
|
+
let lastMessage;
|
|
62
|
+
const print = (m) => {
|
|
63
|
+
if (m !== lastMessage)
|
|
64
|
+
console.log(` ${m}`);
|
|
65
|
+
lastMessage = m;
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
start: (m) => print(m),
|
|
69
|
+
message: (m, phase) => {
|
|
70
|
+
// Phases report many sub-steps (per batch, per locale); one line per phase is enough.
|
|
71
|
+
if (phase !== undefined && phase === lastPhase)
|
|
72
|
+
return;
|
|
73
|
+
lastPhase = phase;
|
|
74
|
+
print(m);
|
|
75
|
+
},
|
|
76
|
+
stop: (m) => print(m),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function frameworkLine(framework, status) {
|
|
80
|
+
const name = FRAMEWORK_NAMES[framework] ?? framework;
|
|
81
|
+
return status === 'beta' ? `${name} ${warning('(beta)')}` : name;
|
|
82
|
+
}
|
|
83
|
+
function previewKeys(keys) {
|
|
84
|
+
const shown = keys.slice(0, KEY_PREVIEW).join(', ');
|
|
85
|
+
return keys.length > KEY_PREVIEW ? `${shown}, … +${keys.length - KEY_PREVIEW} more` : shown;
|
|
86
|
+
}
|
|
87
|
+
export function renderReport(report, verbose = false) {
|
|
88
|
+
const out = [];
|
|
89
|
+
const row = (k, v) => out.push(` ${brand(k.padEnd(12))} ${v}`);
|
|
90
|
+
const { changes, stats } = report;
|
|
91
|
+
out.push('');
|
|
92
|
+
row('Framework', frameworkLine(report.framework, report.frameworkStatus));
|
|
93
|
+
if (report.keptLocales.length > 0) {
|
|
94
|
+
row('Locales', `${report.targetLocales.join(', ')} ${muted(`(kept from earlier runs: ${report.keptLocales.join(', ')}; use --replace to drop them)`)}`);
|
|
95
|
+
}
|
|
96
|
+
if (report.provider) {
|
|
97
|
+
row('Provider', report.provider.model ? `${report.provider.name} ${muted(`· ${report.provider.model}`)}` : report.provider.name);
|
|
98
|
+
}
|
|
99
|
+
const sourceKeys = report.locales[0]?.total ?? Object.keys(report.results.source).length;
|
|
100
|
+
const produced = report.locales.reduce((n, l) => n + l.translated, 0);
|
|
101
|
+
const fresh = Math.min(stats.translatedThisRun ?? stats.keysSentToTranslator, produced);
|
|
102
|
+
row('Keys', `${sourceKeys} in ${report.sourceLocale}.json ${muted(`(${stats.keysExtracted} new this run)`)}`);
|
|
103
|
+
row('Translations', `${fresh} new ${muted(`· ${Math.max(0, produced - fresh)} reused (lockfile, memory or cache)`)}`);
|
|
104
|
+
row('Files', `${changes.modified.length} modified, ${changes.added.length} added, ${changes.deleted.length} deleted` +
|
|
105
|
+
(report.dryRun ? muted(' (dry run — nothing written)') : ''));
|
|
106
|
+
for (const f of changes.modified)
|
|
107
|
+
out.push(` ${warning('M')} ${f}`);
|
|
108
|
+
for (const f of changes.added)
|
|
109
|
+
out.push(` ${success('A')} ${f}`);
|
|
110
|
+
for (const f of changes.deleted)
|
|
111
|
+
out.push(` ${errorColor('D')} ${f}`);
|
|
112
|
+
out.push('');
|
|
113
|
+
for (const l of report.locales) {
|
|
114
|
+
const failedKeySet = new Set(l.failed.map((f) => f.key));
|
|
115
|
+
const needsReview = l.qa.length > 0 || l.missing.some((k) => !failedKeySet.has(k));
|
|
116
|
+
const mark = l.failed.length > 0 ? errorColor(sym.cross) : needsReview ? warning(sym.warn) : success(sym.check);
|
|
117
|
+
out.push(` ${mark} ${brand(l.locale.padEnd(8))} ${l.translated}/${l.total} translated`);
|
|
118
|
+
if (l.failed.length > 0) {
|
|
119
|
+
out.push(` ${errorColor(`${l.failed.length} failed`)} ${muted('(not written; the next run retries them)')}`);
|
|
120
|
+
const shown = verbose ? l.failed : l.failed.slice(0, KEY_PREVIEW);
|
|
121
|
+
for (const f of shown) {
|
|
122
|
+
out.push(` ${f.key} ${muted(`— ${f.reason}${f.message ? `: ${f.message}` : ''}`)}`);
|
|
123
|
+
}
|
|
124
|
+
if (shown.length < l.failed.length)
|
|
125
|
+
out.push(muted(` … ${l.failed.length - shown.length} more (use --verbose or --json)`));
|
|
126
|
+
}
|
|
127
|
+
const failedKeys = new Set(l.failed.map((f) => f.key));
|
|
128
|
+
const otherMissing = l.missing.filter((k) => !failedKeys.has(k));
|
|
129
|
+
if (otherMissing.length > 0) {
|
|
130
|
+
out.push(` ${warning(`${otherMissing.length} missing`)} ${muted(previewKeys(otherMissing))}`);
|
|
131
|
+
}
|
|
132
|
+
if (l.qa.length > 0) {
|
|
133
|
+
out.push(` ${warning(`${l.qa.length} with QA warnings`)}`);
|
|
134
|
+
const shown = verbose ? l.qa : l.qa.slice(0, KEY_PREVIEW);
|
|
135
|
+
for (const q of shown)
|
|
136
|
+
out.push(` ${q.key} ${muted(`— ${q.warnings.join('; ')}`)}`);
|
|
137
|
+
if (shown.length < l.qa.length)
|
|
138
|
+
out.push(muted(` … ${l.qa.length - shown.length} more (use --verbose or --json)`));
|
|
139
|
+
}
|
|
140
|
+
if (l.sameAsSource.length > 0) {
|
|
141
|
+
out.push(` ${muted(`unchanged: ${previewKeys(l.sameAsSource)} — fine for names and codes`)}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Keyed: translated but the code still shows the literal. Keyless: never
|
|
145
|
+
// given a key (e.g. a module-level constant that is also used as data), so
|
|
146
|
+
// not translated at all — saying "translated" there would be wrong.
|
|
147
|
+
const unwired = report.notInjected.filter((s) => s.key);
|
|
148
|
+
const untouched = report.notInjected.filter((s) => !s.key);
|
|
149
|
+
if (unwired.length > 0) {
|
|
150
|
+
const n = unwired.length;
|
|
151
|
+
out.push('', ` ${warning(`${sym.warn} ${n} string${n === 1 ? '' : 's'} extracted but not wired into code`)} ${muted('(translated, but the app still shows the literal):')}`);
|
|
152
|
+
const shown = verbose ? unwired : unwired.slice(0, 10);
|
|
153
|
+
for (const s of shown)
|
|
154
|
+
out.push(` ${s.file} — ${s.key} ${muted(`(${s.reason})`)}`);
|
|
155
|
+
if (shown.length < n)
|
|
156
|
+
out.push(muted(` … ${n - shown.length} more (use --verbose or --json)`));
|
|
157
|
+
}
|
|
158
|
+
if (untouched.length > 0) {
|
|
159
|
+
const n = untouched.length;
|
|
160
|
+
out.push('', ` ${muted(`${n} string${n === 1 ? '' : 's'} left as-is, not translated (possibly data; move UI text inside the component to translate it):`)}`);
|
|
161
|
+
const shown = verbose ? untouched : untouched.slice(0, 5);
|
|
162
|
+
for (const s of shown)
|
|
163
|
+
out.push(muted(` ${s.file} — ${JSON.stringify(s.text)} (${s.reason})`));
|
|
164
|
+
if (shown.length < n)
|
|
165
|
+
out.push(muted(` … ${n - shown.length} more (use --verbose or --json)`));
|
|
166
|
+
}
|
|
167
|
+
if (report.unusedKeys.length > 0) {
|
|
168
|
+
const n = report.unusedKeys.length;
|
|
169
|
+
out.push(muted(` ${n} key${n === 1 ? '' : 's'} in ${report.sourceLocale}.json look${n === 1 ? 's' : ''} unused (no t() reference found) — still translated; remove ${n === 1 ? 'it' : 'them'} from ${report.sourceLocale}.json if dead: ${previewKeys(report.unusedKeys)}`));
|
|
170
|
+
}
|
|
171
|
+
if (report.droppedLocales.length > 0) {
|
|
172
|
+
out.push(` ${brand(sym.pointer)} Removed ${report.droppedLocales.join(', ')} (--replace).`);
|
|
173
|
+
}
|
|
174
|
+
if (report.warnings.length > 0) {
|
|
175
|
+
out.push('', ` ${warning(`${sym.warn} ${report.warnings.length} warning${report.warnings.length === 1 ? '' : 's'}`)}`);
|
|
176
|
+
const shown = verbose ? report.warnings : report.warnings.slice(0, 10);
|
|
177
|
+
for (const w of shown) {
|
|
178
|
+
const where = w.file ? `${w.file}${w.line ? `:${w.line}` : ''} ` : '';
|
|
179
|
+
out.push(` ${muted(where)}${w.message || w.type}`);
|
|
180
|
+
}
|
|
181
|
+
if (shown.length < report.warnings.length) {
|
|
182
|
+
out.push(muted(` … ${report.warnings.length - shown.length} more (use --verbose or --json)`));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (report.formatter) {
|
|
186
|
+
const f = report.formatter;
|
|
187
|
+
out.push('', f.ok
|
|
188
|
+
? ` ${brand(sym.pointer)} Formatted ${f.files.length} changed file${f.files.length === 1 ? '' : 's'} with ${f.tool}.`
|
|
189
|
+
: ` ${warning(sym.warn)} ${f.message}`);
|
|
190
|
+
}
|
|
191
|
+
if (report.notes.length > 0) {
|
|
192
|
+
out.push('');
|
|
193
|
+
for (const n of report.notes)
|
|
194
|
+
out.push(` ${brand(sym.pointer)} ${n}`);
|
|
195
|
+
}
|
|
196
|
+
const logged = report.engineLog;
|
|
197
|
+
if (logged.length > 0) {
|
|
198
|
+
if (verbose) {
|
|
199
|
+
out.push('', ` ${muted('Engine log:')}`);
|
|
200
|
+
for (const e of logged)
|
|
201
|
+
out.push(` ${e.level === 'error' ? errorColor(e.level) : warning(e.level)} ${e.message}`);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
out.push('');
|
|
205
|
+
for (const e of logged.slice(0, 3))
|
|
206
|
+
out.push(` ${e.level === 'error' ? errorColor(sym.cross) : warning(sym.warn)} ${e.message}`);
|
|
207
|
+
if (logged.length > 3)
|
|
208
|
+
out.push(muted(` … ${logged.length - 3} more engine messages (see --verbose)`));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
out.push('');
|
|
212
|
+
if (report.provider?.provider === PSEUDO_PROVIDER) {
|
|
213
|
+
out.push(` ${warning(`${sym.warn} ${PSEUDO_WARNING}`)}`, ` ${muted('babelize check reports every pseudo value as an error until then.')}`);
|
|
214
|
+
}
|
|
215
|
+
if (report.translationFailures.length > 0) {
|
|
216
|
+
out.push(` ${errorColor(`${sym.cross} ${report.translationFailures.length} key(s) could not be translated.`)} ` +
|
|
217
|
+
(report.dryRun ? 'They would be left out.' : 'Everything else was written; re-run to retry them.'));
|
|
218
|
+
}
|
|
219
|
+
if (report.dryRun) {
|
|
220
|
+
out.push(muted(' Dry run: re-run without --dry-run to write these changes.'));
|
|
221
|
+
}
|
|
222
|
+
else if (report.applied) {
|
|
223
|
+
out.push(` ${brand('Next:')} review with ${brand('git diff')}, then commit — including ${brand(LOCAL_LOCKFILE)},`);
|
|
224
|
+
out.push(` so re-runs only translate new or changed strings.`);
|
|
225
|
+
if (report.lockfileIgnored) {
|
|
226
|
+
out.push(` ${warning(`${sym.warn} .babelize/ is gitignored`)} (babelize init adds it). Reruns here still reuse it, but to share it run`, ` ${brand(`git add -f ${LOCAL_LOCKFILE}`)} or add ${brand('!.babelize/local/')} to .gitignore.`);
|
|
227
|
+
}
|
|
228
|
+
if (changes.modified.includes('package.json')) {
|
|
229
|
+
out.push(` i18n dependencies were added to package.json — run your package manager's install.`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
out.push(muted(' Nothing to change: everything is already translated.'));
|
|
234
|
+
}
|
|
235
|
+
out.push('');
|
|
236
|
+
return out.join('\n');
|
|
237
|
+
}
|
|
238
|
+
export function registerTranslateCommand(program) {
|
|
239
|
+
const cmd = program
|
|
240
|
+
.command('translate')
|
|
241
|
+
.description('Translate this repo locally with your own LLM key or a Babelize API key')
|
|
242
|
+
.option('-t, --to <locales>', 'Target locales, comma-separated (e.g. fr,de,ja)')
|
|
243
|
+
.option('-f, --from <locale>', 'Source locale', 'en')
|
|
244
|
+
.option('--dry-run', 'Print the diff of what would change; write nothing')
|
|
245
|
+
.option('--force', 'Run even if the git working tree has uncommitted changes')
|
|
246
|
+
.option('--replace', 'Treat --to as the complete list: remove locales from earlier runs that are not listed')
|
|
247
|
+
.option('--provider <provider>', 'anthropic, openai, gemini, groq, openrouter, or babelize (hosted; needs BABELIZE_API_KEY). Default: first BYOK key in env, then BABELIZE_API_KEY')
|
|
248
|
+
.option('--model <id>', 'Model ID (default: the provider\'s recommended model)')
|
|
249
|
+
.option('-y, --yes', 'Skip the confirmation prompt')
|
|
250
|
+
.option('--report <path>', `Where to write the QA report (default: ${DEFAULT_REPORT_FILE}; not written on --dry-run unless given)`)
|
|
251
|
+
.option('--allow-partial', 'Exit 0 even when some keys failed (they are still listed in the report)')
|
|
252
|
+
.option('--json', 'Print the QA report as JSON (same schema as the report file)');
|
|
253
|
+
cmd.action(async (opts) => {
|
|
254
|
+
const json = isJsonMode() || opts.json === true;
|
|
255
|
+
const interactive = !json && !isNonInteractive() && !opts.yes;
|
|
256
|
+
const sourceLocale = (opts.from ?? 'en').trim();
|
|
257
|
+
let targets = parseLocales(opts.to, sourceLocale);
|
|
258
|
+
if (targets.length === 0) {
|
|
259
|
+
if (!interactive) {
|
|
260
|
+
fail(json, 'usage', 'Missing --to <locales>, e.g. `babelize translate --to fr,de`.', ExitCode.USAGE);
|
|
261
|
+
}
|
|
262
|
+
const answer = await clack.text({
|
|
263
|
+
message: 'Translate into which locales? (comma-separated)',
|
|
264
|
+
placeholder: 'fr,de,ja',
|
|
265
|
+
validate: (v) => (parseLocales(v, sourceLocale).length > 0 ? undefined : 'Enter at least one locale'),
|
|
266
|
+
});
|
|
267
|
+
if (clack.isCancel(answer))
|
|
268
|
+
process.exit(ExitCode.CANCEL);
|
|
269
|
+
targets = parseLocales(String(answer), sourceLocale);
|
|
270
|
+
}
|
|
271
|
+
const pseudo = opts.provider?.trim().toLowerCase() === PSEUDO_PROVIDER;
|
|
272
|
+
if (pseudo && opts.model)
|
|
273
|
+
fail(json, 'model_not_supported', '--model does not apply to --provider pseudo.', ExitCode.USAGE);
|
|
274
|
+
const resolution = pseudo ? null : resolveProvider({ provider: opts.provider, model: opts.model });
|
|
275
|
+
if (resolution && !resolution.ok) {
|
|
276
|
+
const usage = resolution.code === 'unknown_provider' || resolution.code === 'model_not_supported';
|
|
277
|
+
fail(json, resolution.code, resolution.message, usage ? ExitCode.USAGE : ExitCode.CONFIG);
|
|
278
|
+
}
|
|
279
|
+
const provider = resolution?.value ?? null;
|
|
280
|
+
const hosted = provider && isHostedResolution(provider)
|
|
281
|
+
? new HostedTranslationProvider({
|
|
282
|
+
apiKey: provider.apiKey,
|
|
283
|
+
apiUrl: globalOptions.apiUrl ?? config.getApiUrl(),
|
|
284
|
+
})
|
|
285
|
+
: undefined;
|
|
286
|
+
if (!json) {
|
|
287
|
+
console.log(`\n ${brand('Babelize translate')} ${muted(`${sourceLocale} ${sym.arrow} ${targets.join(', ')}`)}`);
|
|
288
|
+
if (!provider) {
|
|
289
|
+
console.log(` ${warning(`${sym.warn} ${PSEUDO_WARNING}`)}`);
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
const keyFrom = `key from ${provider.envVar}${provider.autoDetected ? ', auto-detected' : ''}`;
|
|
293
|
+
console.log(` ${muted(isHostedResolution(provider) ? `Using Babelize hosted (${keyFrom})` : `Using ${provider.provider} · ${provider.model} (${keyFrom})`)}`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (interactive && !opts.dryRun) {
|
|
297
|
+
const billed = !provider
|
|
298
|
+
? 'pseudo-localized test text, no API calls'
|
|
299
|
+
: isHostedResolution(provider) ? 'uses your Babelize AI credits' : `your ${provider.provider} key is billed`;
|
|
300
|
+
const ok = await clack.confirm({
|
|
301
|
+
message: `Translate into ${targets.join(', ')} and write the result to this working tree? (${billed})`,
|
|
302
|
+
});
|
|
303
|
+
if (clack.isCancel(ok) || !ok)
|
|
304
|
+
process.exit(ExitCode.CANCEL);
|
|
305
|
+
}
|
|
306
|
+
const spinner = json ? null : createProgress();
|
|
307
|
+
spinner?.start('Preparing a temporary copy of the project…');
|
|
308
|
+
let report;
|
|
309
|
+
try {
|
|
310
|
+
report = await runLocalTranslate({
|
|
311
|
+
projectDir: process.cwd(),
|
|
312
|
+
sourceLocale,
|
|
313
|
+
targetLocales: targets,
|
|
314
|
+
dryRun: opts.dryRun === true,
|
|
315
|
+
force: opts.force === true,
|
|
316
|
+
replace: opts.replace === true,
|
|
317
|
+
byok: !provider || isHostedResolution(provider) ? undefined : { provider: provider.provider, apiKey: provider.apiKey, model: provider.model },
|
|
318
|
+
}, {
|
|
319
|
+
hosted,
|
|
320
|
+
...(provider ? {} : { translator: new PseudoTranslationProvider(), translatorInfo: { provider: 'pseudo', name: 'Pseudo-localization' } }),
|
|
321
|
+
onFramework: (framework, status) => {
|
|
322
|
+
if (!spinner)
|
|
323
|
+
return;
|
|
324
|
+
spinner.message(`Detected ${FRAMEWORK_NAMES[framework] ?? framework}`);
|
|
325
|
+
if (status === 'beta') {
|
|
326
|
+
spinner.stop(warning(`${sym.warn} ${FRAMEWORK_NAMES[framework] ?? framework} support is in beta — review the diff carefully.`));
|
|
327
|
+
spinner.start('Running the pipeline…');
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
// Only show steps in progress; failures are reported once, below, in plain words.
|
|
331
|
+
onProgress: (message, _phase, status) => {
|
|
332
|
+
if (!status || status === 'running')
|
|
333
|
+
spinner?.message(message, _phase);
|
|
334
|
+
},
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
catch (err) {
|
|
338
|
+
spinner?.stop(errorColor('Translation did not complete.'));
|
|
339
|
+
if (err instanceof LocalTranslateError) {
|
|
340
|
+
fail(json, err.code, err.message, EXIT_BY_CODE[err.code], err.changedFiles);
|
|
341
|
+
}
|
|
342
|
+
// Errors before the apply step never touch the project, and the apply
|
|
343
|
+
// step reports its own failures, but don't promise what we can't know.
|
|
344
|
+
fail(json, 'internal', `Unexpected error: ${err.message}. Check \`git status\` for any changes.`, ExitCode.INTERNAL);
|
|
345
|
+
}
|
|
346
|
+
const elapsed = (report.durationMs / 1000).toFixed(1);
|
|
347
|
+
const partial = report.translationFailures.length > 0;
|
|
348
|
+
spinner?.stop(report.dryRun
|
|
349
|
+
? `Dry run finished in ${elapsed}s.`
|
|
350
|
+
: partial
|
|
351
|
+
? warning(`${sym.warn} Finished with failures in ${elapsed}s.`)
|
|
352
|
+
: success(`${sym.check} Done in ${elapsed}s.`));
|
|
353
|
+
// One schema for --json and the report file (TranslateReport).
|
|
354
|
+
const qa = buildTranslateReport(report);
|
|
355
|
+
// A dry run writes nothing unless a report path was asked for.
|
|
356
|
+
let reportFile;
|
|
357
|
+
let reportError;
|
|
358
|
+
if (!report.dryRun || opts.report) {
|
|
359
|
+
try {
|
|
360
|
+
reportFile = await writeTranslateReport(process.cwd(), qa, opts.report ?? DEFAULT_REPORT_FILE);
|
|
361
|
+
}
|
|
362
|
+
catch (err) {
|
|
363
|
+
reportError = err.message;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
// A --report path inside the repo that git doesn't ignore shows up as an
|
|
367
|
+
// untracked file (the next run skips it, but `git status` won't).
|
|
368
|
+
let reportHint;
|
|
369
|
+
if (reportFile && opts.report) {
|
|
370
|
+
const rel = path.relative(process.cwd(), reportFile).split(path.sep).join('/');
|
|
371
|
+
const inside = !rel.startsWith('..') && !path.isAbsolute(rel);
|
|
372
|
+
if (inside && !rel.startsWith('.babelize/local/') && !(await isGitIgnored(process.cwd(), rel))) {
|
|
373
|
+
reportHint = `${rel} is inside the repo and not git-ignored, so it shows up in git status (babelize translate ignores it). Prefer a path under .babelize/local/ or outside the repo.`;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
if (json) {
|
|
377
|
+
printJson(qa);
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
if (report.dryRun && report.diff) {
|
|
381
|
+
console.log('');
|
|
382
|
+
console.log(report.diff);
|
|
383
|
+
}
|
|
384
|
+
console.log(renderReport(report, globalOptions.verbose === true));
|
|
385
|
+
if (reportFile) {
|
|
386
|
+
const rel = path.relative(process.cwd(), reportFile).split(path.sep).join('/') || reportFile;
|
|
387
|
+
console.log(` ${brand('Report')} ${rel} ${muted(rel === DEFAULT_REPORT_FILE
|
|
388
|
+
? '(every key with its status; git-ignored via .babelize/.gitignore — safe to commit or ignore)'
|
|
389
|
+
: '(every key with its status)')}\n`);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (reportError)
|
|
393
|
+
console.error(` ${warning(sym.warn)} Could not write the report: ${reportError}`);
|
|
394
|
+
if (reportHint)
|
|
395
|
+
console.error(` ${warning(sym.warn)} ${reportHint}`);
|
|
396
|
+
// Successful translations are applied, but a partial run must not look
|
|
397
|
+
// like a clean one to scripts and CI, unless --allow-partial.
|
|
398
|
+
if (partial && !opts.allowPartial)
|
|
399
|
+
process.exit(ExitCode.API_ERROR);
|
|
400
|
+
});
|
|
401
|
+
cmd.helpInformation = () => renderSimpleHelp('translate', 'Translate this repo locally with your own LLM key (no account needed) or Babelize-hosted translation (BABELIZE_API_KEY). Writes locale files and i18n code into your working tree for review with `git diff`.', [
|
|
402
|
+
{ name: '-t, --to <locales>', desc: 'Target locales, comma-separated (e.g. fr,de,ja)' },
|
|
403
|
+
{ name: '-f, --from <locale>', desc: 'Source locale (default: en)' },
|
|
404
|
+
{ name: '--dry-run', desc: 'Print the diff of what would change; write nothing' },
|
|
405
|
+
{ name: '--force', desc: 'Run even with uncommitted changes' },
|
|
406
|
+
{ name: '--replace', desc: 'Remove earlier locales not listed in --to' },
|
|
407
|
+
{ name: '--provider <name>', desc: 'anthropic | openai | gemini | groq | openrouter | babelize' },
|
|
408
|
+
{ name: '--model <id>', desc: "Override the provider's default model" },
|
|
409
|
+
{ name: '-y, --yes', desc: 'Skip the confirmation prompt' },
|
|
410
|
+
{ name: '--report <path>', desc: `QA report location (default: ${DEFAULT_REPORT_FILE})` },
|
|
411
|
+
{ name: '--allow-partial', desc: 'Exit 0 even when some keys failed' },
|
|
412
|
+
{ name: '--json', desc: 'Print the QA report as JSON' },
|
|
413
|
+
]);
|
|
414
|
+
}
|
|
415
|
+
//# 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,27 @@
|
|
|
1
|
+
export type FormatterName = 'prettier' | 'biome';
|
|
2
|
+
export interface FormatResult {
|
|
3
|
+
tool: FormatterName;
|
|
4
|
+
/** Files handed to the formatter (relative to the project root). */
|
|
5
|
+
files: string[];
|
|
6
|
+
ok: boolean;
|
|
7
|
+
/** Why formatting was skipped or failed. */
|
|
8
|
+
message?: string;
|
|
9
|
+
}
|
|
10
|
+
declare const FORMATTERS: Array<{
|
|
11
|
+
name: FormatterName;
|
|
12
|
+
packages: string[];
|
|
13
|
+
args: (files: string[]) => string[];
|
|
14
|
+
}>;
|
|
15
|
+
/** The formatter the project declares in package.json (prettier first), or null. */
|
|
16
|
+
export declare function detectFormatter(projectRoot: string): Promise<(typeof FORMATTERS)[number] | null>;
|
|
17
|
+
/**
|
|
18
|
+
* Run the project's own prettier/biome on the files the engine changed, in
|
|
19
|
+
* the temp copy, using the binary from the project's node_modules (with the
|
|
20
|
+
* project's config, which was copied). Best effort: any problem is returned
|
|
21
|
+
* as a message and the run carries on with unformatted output.
|
|
22
|
+
*/
|
|
23
|
+
export declare function formatChangedFiles(projectDir: string, workDir: string, changedFiles: string[], opts?: {
|
|
24
|
+
timeoutMs?: number;
|
|
25
|
+
}): Promise<FormatResult | null>;
|
|
26
|
+
export {};
|
|
27
|
+
//# sourceMappingURL=formatter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formatter.d.ts","sourceRoot":"","sources":["../../src/local/formatter.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,OAAO,CAAC;AAEjD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,aAAa,CAAC;IACpB,oEAAoE;IACpE,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,EAAE,EAAE,OAAO,CAAC;IACZ,4CAA4C;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAKD,QAAA,MAAM,UAAU,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,MAAM,EAAE,CAAA;CAAE,CAGvG,CAAC;AAEF,oFAAoF;AACpF,wBAAsB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAWtG;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EAAE,EACtB,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAChC,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAmC9B"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { lstat, readFile, symlink, unlink } from 'node:fs/promises';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
const execFileAsync = promisify(execFile);
|
|
6
|
+
/** Files a JS formatter should touch; locale JSON and package.json are left as the engine wrote them. */
|
|
7
|
+
const FORMATTABLE = /\.(?:[cm]?[jt]sx?|vue)$/;
|
|
8
|
+
const FORMATTERS = [
|
|
9
|
+
{ name: 'prettier', packages: ['prettier'], args: (files) => ['--write', ...files] },
|
|
10
|
+
{ name: 'biome', packages: ['@biomejs/biome'], args: (files) => ['format', '--write', ...files] },
|
|
11
|
+
];
|
|
12
|
+
/** The formatter the project declares in package.json (prettier first), or null. */
|
|
13
|
+
export async function detectFormatter(projectRoot) {
|
|
14
|
+
try {
|
|
15
|
+
const pkg = JSON.parse(await readFile(path.join(projectRoot, 'package.json'), 'utf8'));
|
|
16
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
17
|
+
return FORMATTERS.find((f) => f.packages.some((p) => deps[p])) ?? null;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Run the project's own prettier/biome on the files the engine changed, in
|
|
25
|
+
* the temp copy, using the binary from the project's node_modules (with the
|
|
26
|
+
* project's config, which was copied). Best effort: any problem is returned
|
|
27
|
+
* as a message and the run carries on with unformatted output.
|
|
28
|
+
*/
|
|
29
|
+
export async function formatChangedFiles(projectDir, workDir, changedFiles, opts = {}) {
|
|
30
|
+
const formatter = await detectFormatter(workDir);
|
|
31
|
+
if (!formatter)
|
|
32
|
+
return null;
|
|
33
|
+
const files = changedFiles.filter((f) => FORMATTABLE.test(f));
|
|
34
|
+
if (files.length === 0)
|
|
35
|
+
return null;
|
|
36
|
+
const bin = path.join(projectDir, 'node_modules', '.bin', formatter.name);
|
|
37
|
+
if (!(await lstat(bin).catch(() => null))) {
|
|
38
|
+
return {
|
|
39
|
+
tool: formatter.name,
|
|
40
|
+
files,
|
|
41
|
+
ok: false,
|
|
42
|
+
message: `${formatter.name} is in package.json but not installed, so changed files were not formatted. Run your package manager install, then ${formatter.name} on them.`,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
// Let the formatter resolve its plugins/config packages from the project.
|
|
46
|
+
const link = path.join(workDir, 'node_modules');
|
|
47
|
+
const linked = !(await lstat(link).catch(() => null));
|
|
48
|
+
if (linked)
|
|
49
|
+
await symlink(path.join(projectDir, 'node_modules'), link, 'dir').catch(() => { });
|
|
50
|
+
try {
|
|
51
|
+
await execFileAsync(bin, formatter.args(files), {
|
|
52
|
+
cwd: workDir,
|
|
53
|
+
timeout: opts.timeoutMs ?? 60_000,
|
|
54
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
55
|
+
env: { ...process.env, NO_COLOR: '1' },
|
|
56
|
+
});
|
|
57
|
+
return { tool: formatter.name, files, ok: true };
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
const e = err;
|
|
61
|
+
const detail = (e.stderr || e.message || String(err)).trim().split('\n')[0];
|
|
62
|
+
return { tool: formatter.name, files, ok: false, message: `${formatter.name} failed (${detail}); files were left unformatted.` };
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
if (linked)
|
|
66
|
+
await unlink(link).catch(() => { });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=formatter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formatter.js","sourceRoot":"","sources":["../../src/local/formatter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare class GitError extends Error {
|
|
2
|
+
readonly code: 'not_a_repo' | 'git_missing' | 'dirty';
|
|
3
|
+
readonly dirtyFiles: string[];
|
|
4
|
+
constructor(message: string, code: 'not_a_repo' | 'git_missing' | 'dirty', dirtyFiles?: string[]);
|
|
5
|
+
}
|
|
6
|
+
/** Throws GitError('not_a_repo' | 'git_missing') unless `dir` is inside a git work tree. */
|
|
7
|
+
export declare function assertGitRepo(dir: string): Promise<void>;
|
|
8
|
+
/** Uncommitted changes under `dir` (tracked edits and untracked files), as porcelain lines. */
|
|
9
|
+
export declare function getUncommittedChanges(dir: string): Promise<string[]>;
|
|
10
|
+
/**
|
|
11
|
+
* Refuse to run on a dirty tree unless `force`: the result has to be reviewable
|
|
12
|
+
* as its own `git diff`, and a failed review has to be undoable with
|
|
13
|
+
* `git checkout . && git clean -fd` without losing the user's work.
|
|
14
|
+
*/
|
|
15
|
+
export declare function assertCleanTree(dir: string, force: boolean): Promise<string[]>;
|
|
16
|
+
//# sourceMappingURL=git.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/local/git.ts"],"names":[],"mappings":"AAKA,qBAAa,QAAS,SAAQ,KAAK;IAG/B,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,aAAa,GAAG,OAAO;IACrD,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE;gBAF7B,OAAO,EAAE,MAAM,EACN,IAAI,EAAE,YAAY,GAAG,aAAa,GAAG,OAAO,EAC5C,UAAU,GAAE,MAAM,EAAO;CAKrC;AAOD,4FAA4F;AAC5F,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAa9D;AAED,+FAA+F;AAC/F,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAG1E;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAapF"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
const execFileAsync = promisify(execFile);
|
|
4
|
+
export class GitError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
dirtyFiles;
|
|
7
|
+
constructor(message, code, dirtyFiles = []) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.dirtyFiles = dirtyFiles;
|
|
11
|
+
this.name = 'GitError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
async function git(cwd, args) {
|
|
15
|
+
const { stdout } = await execFileAsync('git', args, { cwd, maxBuffer: 64 * 1024 * 1024 });
|
|
16
|
+
return stdout;
|
|
17
|
+
}
|
|
18
|
+
/** Throws GitError('not_a_repo' | 'git_missing') unless `dir` is inside a git work tree. */
|
|
19
|
+
export async function assertGitRepo(dir) {
|
|
20
|
+
try {
|
|
21
|
+
const out = await git(dir, ['rev-parse', '--is-inside-work-tree']);
|
|
22
|
+
if (out.trim() !== 'true')
|
|
23
|
+
throw new Error('not inside a work tree');
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
if (err.code === 'ENOENT') {
|
|
27
|
+
throw new GitError('git is not installed or not on PATH.', 'git_missing');
|
|
28
|
+
}
|
|
29
|
+
throw new GitError('Not a git repository. `babelize translate` writes into your working tree so you can review the result with `git diff` — run it inside a git repo (or `git init` first).', 'not_a_repo');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** Uncommitted changes under `dir` (tracked edits and untracked files), as porcelain lines. */
|
|
33
|
+
export async function getUncommittedChanges(dir) {
|
|
34
|
+
const out = await git(dir, ['status', '--porcelain', '--untracked-files=all', '--', '.']);
|
|
35
|
+
return out.split('\n').filter((line) => line.trim().length > 0);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Refuse to run on a dirty tree unless `force`: the result has to be reviewable
|
|
39
|
+
* as its own `git diff`, and a failed review has to be undoable with
|
|
40
|
+
* `git checkout . && git clean -fd` without losing the user's work.
|
|
41
|
+
*/
|
|
42
|
+
export async function assertCleanTree(dir, force) {
|
|
43
|
+
const dirty = await getUncommittedChanges(dir);
|
|
44
|
+
if (dirty.length > 0 && !force) {
|
|
45
|
+
const preview = dirty.slice(0, 10).map((l) => ` ${l}`).join('\n');
|
|
46
|
+
const more = dirty.length > 10 ? `\n … and ${dirty.length - 10} more` : '';
|
|
47
|
+
throw new GitError(`Your working tree has uncommitted changes:\n${preview}${more}\n` +
|
|
48
|
+
'Commit or stash them first so the translation shows up as its own reviewable `git diff`, or re-run with --force.', 'dirty', dirty);
|
|
49
|
+
}
|
|
50
|
+
return dirty;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=git.js.map
|