champollion 0.3.4 → 0.5.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/README.md +41 -26
- package/bin/cli.js +53 -5
- package/index.js +63 -2
- package/lib/api-key.js +17 -4
- package/lib/autofix.js +82 -37
- package/lib/bridge/method_bridge.py +15 -3
- package/lib/cards/reader.js +34 -0
- package/lib/cards/remote.js +15 -0
- package/lib/cards/search-names.js +178 -0
- package/lib/command-help.js +286 -85
- package/lib/commands/audit.js +10 -3
- package/lib/commands/card.js +583 -226
- package/lib/commands/doctor.js +54 -18
- package/lib/commands/help.js +37 -32
- package/lib/commands/init.js +1709 -101
- package/lib/commands/integrity.js +127 -40
- package/lib/commands/leaderboard.js +187 -67
- package/lib/commands/models.js +9 -2
- package/lib/commands/provenance.js +7 -2
- package/lib/commands/recommend.js +43 -14
- package/lib/commands/register-corpus.js +632 -125
- package/lib/commands/seal-corpus.js +1 -1
- package/lib/commands/status.js +564 -27
- package/lib/commands/submit.js +17 -12
- package/lib/commands/sync.js +31 -7
- package/lib/commands/tm.js +15 -9
- package/lib/commands/verify.js +27 -3
- package/lib/commands/wrap.js +63 -5
- package/lib/commands/xliff.js +135 -64
- package/lib/commercial-eligibility.js +1 -1
- package/lib/config.js +230 -17
- package/lib/content-estimate.js +96 -0
- package/lib/content-refusals.js +284 -0
- package/lib/content-review.js +372 -0
- package/lib/content-sync.js +1136 -345
- package/lib/content.js +94 -7
- package/lib/corpus-registration.mjs +194 -35
- package/lib/cost-label.js +29 -0
- package/lib/cost-report.js +727 -79
- package/lib/diff.js +38 -4
- package/lib/docusaurus-sync.js +975 -262
- package/lib/edit-distance.js +31 -0
- package/lib/fallback.js +1102 -0
- package/lib/file-scope.js +106 -0
- package/lib/flatten.js +80 -3
- package/lib/flutter-locales.js +124 -0
- package/lib/format.js +266 -12
- package/lib/hash.js +146 -21
- package/lib/icu-structure.js +929 -0
- package/lib/integrity.js +223 -75
- package/lib/language-pair.js +157 -0
- package/lib/lint.js +78 -16
- package/lib/local-only-marks.js +106 -0
- package/lib/locale-layout.js +1103 -0
- package/lib/locale-state.js +575 -0
- package/lib/methods/anthropic.js +5 -0
- package/lib/methods/apertium.js +6 -3
- package/lib/methods/api.js +138 -25
- package/lib/methods/base.js +17 -0
- package/lib/methods/coaching-data.js +153 -0
- package/lib/methods/content-separator.js +43 -0
- package/lib/methods/deepl.js +1 -1
- package/lib/methods/direct-llm.js +257 -103
- package/lib/methods/external.js +146 -63
- package/lib/methods/gemini.js +1 -0
- package/lib/methods/google-translate.js +1 -0
- package/lib/methods/http-utils.js +41 -0
- package/lib/methods/libretranslate.js +7 -2
- package/lib/methods/llm-coached.js +71 -129
- package/lib/methods/llm.js +87 -33
- package/lib/methods/local.js +93 -10
- package/lib/methods/microsoft-translator.js +1 -2
- package/lib/methods/openai.js +4 -2
- package/lib/methods/openrouter-client.js +20 -19
- package/lib/methods/openrouter-pricing.js +150 -13
- package/lib/methods/prompt-methods.js +20 -0
- package/lib/methods/provider-pricing.js +42 -1
- package/lib/methods/request-capture.js +104 -0
- package/lib/methods/tilde.js +1 -1
- package/lib/methods/translated.js +1 -2
- package/lib/missing-key.js +93 -0
- package/lib/models.js +102 -55
- package/lib/name-rules.js +32 -0
- package/lib/named-keys.js +172 -0
- package/lib/no-translate.js +4 -3
- package/lib/output.js +160 -19
- package/lib/pairs.js +586 -30
- package/lib/placeholders.js +394 -0
- package/lib/plugins.js +8 -0
- package/lib/plural-gap-redo.js +109 -0
- package/lib/plurals.js +323 -0
- package/lib/po.js +1187 -0
- package/lib/public-catalogue.js +74 -0
- package/lib/recommend.js +527 -32
- package/lib/redo.js +95 -0
- package/lib/refusal-category.js +44 -0
- package/lib/registers.js +255 -11
- package/lib/repair-script.js +20 -13
- package/lib/scripts.js +6 -1
- package/lib/seal.mjs +4 -3
- package/lib/sealed-qualifier.mjs +1 -1
- package/lib/segment.js +24 -8
- package/lib/seo.js +19 -9
- package/lib/serve.js +43 -6
- package/lib/shared-output-seed.js +164 -0
- package/lib/source-contexts.js +39 -0
- package/lib/submit.mjs +57 -5
- package/lib/sync.js +2923 -474
- package/lib/terminology.js +13 -4
- package/lib/tm-evict.js +179 -0
- package/lib/tm-seed.js +5 -2
- package/lib/tm.js +818 -36
- package/lib/translate-pair.js +639 -34
- package/lib/translate.js +78 -5
- package/lib/types.js +22 -3
- package/lib/validate.js +996 -21
- package/lib/verify.js +1332 -104
- package/lib/watch.js +32 -13
- package/lib/xliff.js +44 -3
- package/package.json +1 -1
- package/shared/CORPORA-CARDS.md +2 -0
- package/shared/cards-fallback.json +1 -1
- package/shared/curated-orthography-conventions.json +26 -8
- package/shared/gettext-plural-forms.json +45 -0
- package/shared/licenses.json +99 -3
- package/shared/method-registry.json +2 -0
- package/shared/metric-registry.json +96 -18
- package/shared/retired-model-aliases.json +9 -0
- package/shared/schemas/champollion-plugin.schema.json +4 -0
- package/shared/schemas/corpora-card.schema.json +8 -2
- package/shared/schemas/human-services.schema.json +1 -1
- package/shared/schemas/method-index-record.schema.json +67 -0
- package/shared/schemas/method-registry.schema.json +4 -0
- package/shared/schemas/metric-registry.schema.json +55 -1
- package/shared/schemas/retired-model-aliases.schema.json +32 -0
- package/shared/docent/corpus.json +0 -11739
- package/shared/model-aliases.json +0 -7
- package/shared/schemas/model-aliases.schema.json +0 -27
package/lib/output.js
CHANGED
|
@@ -31,6 +31,44 @@
|
|
|
31
31
|
*/
|
|
32
32
|
let mode = 'default';
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Keys as people can copy them. A gettext key with a context is
|
|
36
|
+
* `msgctxt + U+0004 + msgid` — gettext's own encoding — and U+0004 is an
|
|
37
|
+
* invisible control character on a terminal: a key copied from a report
|
|
38
|
+
* could not be pasted back into `--redo keys:`. Every message printed here
|
|
39
|
+
* shows it as "␄" (U+2404), which `--redo keys:` / `--force-keys` accept
|
|
40
|
+
* back (lib/locale-layout.js keysForNamespace). Structured `data` fields in
|
|
41
|
+
* --json keep the exact key.
|
|
42
|
+
*
|
|
43
|
+
* @param {*} msg
|
|
44
|
+
* @returns {*} msg with U+0004 shown as ␄ (non-strings unchanged)
|
|
45
|
+
*/
|
|
46
|
+
function showKey(msg) {
|
|
47
|
+
return typeof msg === 'string' ? msg.replace(/\u0004/g, '\u2404') : msg;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* LINE-ATOMIC OUTPUT. Locales are translated in parallel, and a progress
|
|
52
|
+
* bar used to be a partial line (`\r…` with no newline) that any other
|
|
53
|
+
* locale's message was appended to mid-line ("Translating 3 key(s) to
|
|
54
|
+
* French (local)...[INFO] ru/django.po — 3 missing"). Now:
|
|
55
|
+
* - every message is one complete line;
|
|
56
|
+
* - a progress bar names its file, and is redrawn in place only on a
|
|
57
|
+
* terminal and only while nothing else has printed since — any other
|
|
58
|
+
* output first ends the open bar line, so nothing is lost or overwritten;
|
|
59
|
+
* - off a terminal (CI logs, pipes) every bar update is its own line.
|
|
60
|
+
*
|
|
61
|
+
* `openBar` is the item whose bar line is currently unterminated.
|
|
62
|
+
*/
|
|
63
|
+
let openBar = null;
|
|
64
|
+
|
|
65
|
+
function endOpenBar() {
|
|
66
|
+
if (openBar !== null) {
|
|
67
|
+
process.stdout.write('\n');
|
|
68
|
+
openBar = null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
34
72
|
/**
|
|
35
73
|
* Set the output mode. Call once during CLI bootstrap.
|
|
36
74
|
*
|
|
@@ -64,10 +102,11 @@ function getMode() {
|
|
|
64
102
|
function info(msg, data) {
|
|
65
103
|
if (mode === 'quiet') return;
|
|
66
104
|
if (mode === 'json') {
|
|
67
|
-
console.log(JSON.stringify({ level: 'info', message: msg, ...data }));
|
|
105
|
+
console.log(JSON.stringify({ level: 'info', message: showKey(msg), ...data }));
|
|
68
106
|
return;
|
|
69
107
|
}
|
|
70
|
-
|
|
108
|
+
endOpenBar();
|
|
109
|
+
console.log(`[INFO] ${showKey(msg)}`);
|
|
71
110
|
}
|
|
72
111
|
|
|
73
112
|
/**
|
|
@@ -80,10 +119,11 @@ function info(msg, data) {
|
|
|
80
119
|
function ok(msg, data) {
|
|
81
120
|
if (mode === 'quiet') return;
|
|
82
121
|
if (mode === 'json') {
|
|
83
|
-
console.log(JSON.stringify({ level: 'ok', message: msg, ...data }));
|
|
122
|
+
console.log(JSON.stringify({ level: 'ok', message: showKey(msg), ...data }));
|
|
84
123
|
return;
|
|
85
124
|
}
|
|
86
|
-
|
|
125
|
+
endOpenBar();
|
|
126
|
+
console.log(`[OK] ${showKey(msg)}`);
|
|
87
127
|
}
|
|
88
128
|
|
|
89
129
|
/**
|
|
@@ -95,10 +135,11 @@ function ok(msg, data) {
|
|
|
95
135
|
*/
|
|
96
136
|
function warn(msg, data) {
|
|
97
137
|
if (mode === 'json') {
|
|
98
|
-
console.error(JSON.stringify({ level: 'warn', message: msg, ...data }));
|
|
138
|
+
console.error(JSON.stringify({ level: 'warn', message: showKey(msg), ...data }));
|
|
99
139
|
return;
|
|
100
140
|
}
|
|
101
|
-
|
|
141
|
+
endOpenBar();
|
|
142
|
+
console.error(`[WARN] ${showKey(msg)}`);
|
|
102
143
|
}
|
|
103
144
|
|
|
104
145
|
/**
|
|
@@ -110,21 +151,48 @@ function warn(msg, data) {
|
|
|
110
151
|
*/
|
|
111
152
|
function error(msg, data) {
|
|
112
153
|
if (mode === 'json') {
|
|
113
|
-
console.error(JSON.stringify({ level: 'error', message: msg, ...data }));
|
|
154
|
+
console.error(JSON.stringify({ level: 'error', message: showKey(msg), ...data }));
|
|
114
155
|
return;
|
|
115
156
|
}
|
|
116
|
-
|
|
157
|
+
endOpenBar();
|
|
158
|
+
console.error(`[ERR] ${showKey(msg)}`);
|
|
117
159
|
}
|
|
118
160
|
|
|
119
161
|
/**
|
|
120
|
-
* Progress message —
|
|
162
|
+
* Progress message — status for long-running operations, one complete line
|
|
163
|
+
* (a trailing newline is added; see LINE-ATOMIC OUTPUT above). Callers name
|
|
164
|
+
* what the line is about: with locales running in parallel, a bare
|
|
165
|
+
* " [OK]" could belong to any of them.
|
|
121
166
|
* Suppressed in quiet and json modes.
|
|
122
167
|
*
|
|
123
168
|
* @param {string} msg - Progress description
|
|
124
169
|
*/
|
|
125
170
|
function progress(msg) {
|
|
126
171
|
if (mode === 'quiet' || mode === 'json') return;
|
|
127
|
-
|
|
172
|
+
const line = showKey(String(msg)).replace(/\n+$/, '');
|
|
173
|
+
if (line.trim() === '') return;
|
|
174
|
+
endOpenBar();
|
|
175
|
+
process.stdout.write(`${line}\n`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Finish an item's progress: " [OK]" after its own bar when that bar is the
|
|
180
|
+
* line still open on a terminal, else a complete line naming the item.
|
|
181
|
+
* Suppressed in quiet and json modes.
|
|
182
|
+
*
|
|
183
|
+
* @param {string} item - What finished (the file name the bar showed)
|
|
184
|
+
* @param {string} status - e.g. "[OK]", "[ERR] all translations failed quality gate"
|
|
185
|
+
*/
|
|
186
|
+
function progressDone(item, status) {
|
|
187
|
+
if (mode === 'quiet' || mode === 'json') return;
|
|
188
|
+
const label = showKey(String(item));
|
|
189
|
+
if (openBar === label) {
|
|
190
|
+
process.stdout.write(` ${status}\n`);
|
|
191
|
+
openBar = null;
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
endOpenBar();
|
|
195
|
+
process.stdout.write(` ${label} ${status}\n`);
|
|
128
196
|
}
|
|
129
197
|
|
|
130
198
|
/**
|
|
@@ -135,7 +203,24 @@ function progress(msg) {
|
|
|
135
203
|
*/
|
|
136
204
|
function debug(msg, data) {
|
|
137
205
|
if (mode !== 'verbose') return;
|
|
138
|
-
|
|
206
|
+
endOpenBar();
|
|
207
|
+
console.log(`[DEBUG] ${showKey(msg)}`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Event — one machine-readable progress record (json mode only).
|
|
212
|
+
* Emits `{level:'event', event, ...fields}` on stdout; silent otherwise —
|
|
213
|
+
* the human output already says the same thing in prose.
|
|
214
|
+
*
|
|
215
|
+
* Event types used by sync: 'cost' (the pre-run estimate, before the
|
|
216
|
+
* --max-cost gate) and 'file' (one per content file × locale processed).
|
|
217
|
+
*
|
|
218
|
+
* @param {string} event - Event type
|
|
219
|
+
* @param {object} [fields] - Structured data
|
|
220
|
+
*/
|
|
221
|
+
function event(eventType, fields) {
|
|
222
|
+
if (mode !== 'json') return;
|
|
223
|
+
console.log(JSON.stringify({ level: 'event', event: eventType, ...fields }));
|
|
139
224
|
}
|
|
140
225
|
|
|
141
226
|
/**
|
|
@@ -153,6 +238,7 @@ function summary(data) {
|
|
|
153
238
|
return;
|
|
154
239
|
}
|
|
155
240
|
if (data.title) {
|
|
241
|
+
endOpenBar();
|
|
156
242
|
console.log(`\n${data.title}`);
|
|
157
243
|
}
|
|
158
244
|
}
|
|
@@ -165,14 +251,18 @@ function summary(data) {
|
|
|
165
251
|
*/
|
|
166
252
|
function banner(version) {
|
|
167
253
|
if (mode === 'quiet' || mode === 'json') return;
|
|
254
|
+
endOpenBar();
|
|
168
255
|
console.log(`champollion v${version}\n`);
|
|
169
256
|
}
|
|
170
257
|
|
|
171
258
|
/**
|
|
172
|
-
* Progress bar —
|
|
259
|
+
* Progress bar — key-count progress for one item (a locale file).
|
|
173
260
|
*
|
|
174
|
-
* Renders a
|
|
175
|
-
*
|
|
261
|
+
* Renders a line like: ` fr.json ████████░░░░░░░░ 1,440/2,847 keys`
|
|
262
|
+
* On a terminal the line is redrawn in place (\r) while it is still the
|
|
263
|
+
* last thing printed and belongs to the same item; otherwise (another
|
|
264
|
+
* locale's bar, any other message, or output that is not a terminal) each
|
|
265
|
+
* update is its own complete line — see LINE-ATOMIC OUTPUT.
|
|
176
266
|
* Call with `done = true` to finalize the line with a newline.
|
|
177
267
|
*
|
|
178
268
|
* Suppressed in quiet and json modes.
|
|
@@ -180,12 +270,14 @@ function banner(version) {
|
|
|
180
270
|
* @param {number} completed - Number of items completed
|
|
181
271
|
* @param {number} total - Total number of items
|
|
182
272
|
* @param {object} [opts] - Options
|
|
273
|
+
* @param {string} [opts.item=''] - What the bar is for (e.g. the locale file)
|
|
183
274
|
* @param {string} [opts.label='keys'] - Unit label
|
|
184
275
|
* @param {boolean} [opts.done=false] - Whether this is the final update (appends newline)
|
|
185
276
|
*/
|
|
186
277
|
function progressBar(completed, total, opts = {}) {
|
|
187
278
|
if (mode === 'quiet' || mode === 'json') return;
|
|
188
279
|
const { label = 'keys', done = false } = opts;
|
|
280
|
+
const item = showKey(String(opts.item || ''));
|
|
189
281
|
|
|
190
282
|
// Calculate bar width based on terminal width, with sane defaults
|
|
191
283
|
const termWidth = process.stdout.columns || 80;
|
|
@@ -195,12 +287,34 @@ function progressBar(completed, total, opts = {}) {
|
|
|
195
287
|
const filled = Math.round(ratio * barWidth);
|
|
196
288
|
const bar = '█'.repeat(filled) + '░'.repeat(barWidth - filled);
|
|
197
289
|
const nums = `${completed.toLocaleString()}/${total.toLocaleString()} ${label}`;
|
|
290
|
+
const line = ` ${item ? `${item} ` : ''}${bar} ${nums}`;
|
|
198
291
|
|
|
199
|
-
if (
|
|
200
|
-
|
|
292
|
+
if (!process.stdout.isTTY) {
|
|
293
|
+
endOpenBar();
|
|
294
|
+
process.stdout.write(`${line}\n`);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
if (openBar === item) {
|
|
298
|
+
process.stdout.write(`\r${line}`);
|
|
201
299
|
} else {
|
|
202
|
-
|
|
300
|
+
endOpenBar();
|
|
301
|
+
process.stdout.write(line);
|
|
203
302
|
}
|
|
303
|
+
openBar = item;
|
|
304
|
+
if (done) endOpenBar();
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* A warning block: pre-formatted lines on stderr (the quality-gate and
|
|
309
|
+
* glossary reports), each a complete line with keys shown copyably.
|
|
310
|
+
* Emitted in every human mode, like warn(); callers emit structured
|
|
311
|
+
* records themselves in json mode.
|
|
312
|
+
*
|
|
313
|
+
* @param {string[]} lines
|
|
314
|
+
*/
|
|
315
|
+
function block(lines) {
|
|
316
|
+
endOpenBar();
|
|
317
|
+
for (const line of lines) console.error(showKey(line));
|
|
204
318
|
}
|
|
205
319
|
|
|
206
320
|
/**
|
|
@@ -217,7 +331,8 @@ function progressBar(completed, total, opts = {}) {
|
|
|
217
331
|
*/
|
|
218
332
|
function raw(msg) {
|
|
219
333
|
if (mode === 'quiet' || mode === 'json') return;
|
|
220
|
-
|
|
334
|
+
endOpenBar();
|
|
335
|
+
console.log(showKey(msg));
|
|
221
336
|
}
|
|
222
337
|
|
|
223
338
|
const output = {
|
|
@@ -228,11 +343,37 @@ const output = {
|
|
|
228
343
|
warn,
|
|
229
344
|
error,
|
|
230
345
|
progress,
|
|
346
|
+
progressDone,
|
|
231
347
|
debug,
|
|
232
348
|
summary,
|
|
349
|
+
event,
|
|
233
350
|
banner,
|
|
234
351
|
progressBar,
|
|
235
352
|
raw,
|
|
353
|
+
block,
|
|
236
354
|
};
|
|
237
355
|
|
|
238
|
-
|
|
356
|
+
/**
|
|
357
|
+
* A stored ISO timestamp as this machine's local date and time, with the
|
|
358
|
+
* zone named: "2026-10-03 18:20 MDT". The caches store UTC (`…Z`); printing
|
|
359
|
+
* the UTC date bare read as a day ahead of local time in the evening
|
|
360
|
+
* (Round 5, Next.js persona: `tm stats` "Created"). Zones without an
|
|
361
|
+
* abbreviation print as an offset ("GMT+5:30"). An unparsable value is
|
|
362
|
+
* returned unchanged; a missing one as "unknown".
|
|
363
|
+
*
|
|
364
|
+
* @param {string|null|undefined} iso
|
|
365
|
+
* @param {{ timeZone?: string }} [opts] - For tests; defaults to the machine's zone
|
|
366
|
+
* @returns {string}
|
|
367
|
+
*/
|
|
368
|
+
function localTimestamp(iso, opts = {}) {
|
|
369
|
+
if (!iso) return 'unknown';
|
|
370
|
+
const d = new Date(iso);
|
|
371
|
+
if (Number.isNaN(d.getTime())) return String(iso);
|
|
372
|
+
const parts = Object.fromEntries(new Intl.DateTimeFormat('en-US', {
|
|
373
|
+
year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit',
|
|
374
|
+
hourCycle: 'h23', timeZoneName: 'short', ...(opts.timeZone && { timeZone: opts.timeZone }),
|
|
375
|
+
}).formatToParts(d).map(p => [p.type, p.value]));
|
|
376
|
+
return `${parts.year}-${parts.month}-${parts.day} ${parts.hour}:${parts.minute} ${parts.timeZoneName}`;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export { output, showKey, localTimestamp };
|