champollion 0.3.4 → 0.4.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 +83 -36
- 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 +1689 -87
- 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 +196 -14
- package/lib/content-estimate.js +96 -0
- package/lib/content-refusals.js +270 -0
- package/lib/content-review.js +372 -0
- package/lib/content-sync.js +1127 -344
- 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 +726 -78
- package/lib/diff.js +38 -4
- package/lib/docusaurus-sync.js +965 -253
- package/lib/edit-distance.js +31 -0
- package/lib/fallback.js +964 -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 +571 -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 +252 -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 +68 -128
- package/lib/methods/llm.js +80 -31
- 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 +11 -0
- 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 +2 -1
- 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 +880 -17
- package/lib/verify.js +1296 -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/method-registry.json +2 -0
- package/shared/metric-registry.json +96 -18
- package/shared/schemas/champollion-plugin.schema.json +4 -0
- package/shared/schemas/corpora-card.schema.json +8 -2
- 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/docent/corpus.json +0 -11739
package/lib/methods/external.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* This is the core Champollion deployment pipeline for custom methods:
|
|
5
5
|
*
|
|
6
|
-
* 1. pip install crk-translate (or quechua-translate, or any module)
|
|
6
|
+
* 1. python3 -m pip install crk-translate (or quechua-translate, or any module)
|
|
7
7
|
* 2. champollion init → wizard detects the module, offers it
|
|
8
8
|
* 3. champollion sync → bridge spawns, translates, done
|
|
9
9
|
*
|
|
@@ -51,6 +51,55 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
51
51
|
*/
|
|
52
52
|
const BRIDGE_SCRIPT = resolve(__dirname, '..', 'bridge', 'method_bridge.py');
|
|
53
53
|
|
|
54
|
+
/** Interpreter names tried in order (python3 on macOS/Linux, python on Windows). */
|
|
55
|
+
const PYTHON_CANDIDATES = ['python3', 'python'];
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Spawn the first Python interpreter that exists.
|
|
59
|
+
*
|
|
60
|
+
* spawn() reports a missing executable ASYNCHRONOUSLY, as an 'error' event
|
|
61
|
+
* with code ENOENT — a try/catch around spawn() never sees it. So wait for
|
|
62
|
+
* 'spawn' (it started) or 'error' (it didn't), and move to the next name only
|
|
63
|
+
* on ENOENT. Rejects with an actionable message when none exists.
|
|
64
|
+
*
|
|
65
|
+
* @param {string[]} args
|
|
66
|
+
* @param {import('node:child_process').SpawnOptions} options
|
|
67
|
+
* @returns {Promise<import('node:child_process').ChildProcess>}
|
|
68
|
+
*/
|
|
69
|
+
function spawnPython(args, options) {
|
|
70
|
+
return new Promise((resolvePromise, reject) => {
|
|
71
|
+
const attempt = (i) => {
|
|
72
|
+
const proc = spawn(PYTHON_CANDIDATES[i], args, options);
|
|
73
|
+
const onSpawn = () => {
|
|
74
|
+
proc.removeListener('error', onError);
|
|
75
|
+
resolvePromise(proc);
|
|
76
|
+
};
|
|
77
|
+
const onError = (err) => {
|
|
78
|
+
proc.removeListener('spawn', onSpawn);
|
|
79
|
+
if (err.code !== 'ENOENT') return reject(err);
|
|
80
|
+
if (i + 1 < PYTHON_CANDIDATES.length) return attempt(i + 1);
|
|
81
|
+
reject(new Error(
|
|
82
|
+
`Python is required for external method modules but was not found ` +
|
|
83
|
+
`(tried ${PYTHON_CANDIDATES.join(', ')} on PATH).\n` +
|
|
84
|
+
' Install Python 3.10+ from https://python.org'
|
|
85
|
+
));
|
|
86
|
+
};
|
|
87
|
+
proc.once('spawn', onSpawn);
|
|
88
|
+
proc.once('error', onError);
|
|
89
|
+
};
|
|
90
|
+
attempt(0);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Bridges alive in this process. Killed on exit as a backstop, so no Python
|
|
96
|
+
* child outlives the CLI even on a path that never called shutdown().
|
|
97
|
+
*/
|
|
98
|
+
const liveBridges = new Set();
|
|
99
|
+
process.once('exit', () => {
|
|
100
|
+
for (const proc of liveBridges) proc.kill();
|
|
101
|
+
});
|
|
102
|
+
|
|
54
103
|
class ExternalMethod extends TranslationMethod {
|
|
55
104
|
constructor(options = {}) {
|
|
56
105
|
super('external', options);
|
|
@@ -58,6 +107,13 @@ class ExternalMethod extends TranslationMethod {
|
|
|
58
107
|
this._process = null;
|
|
59
108
|
this._rl = null;
|
|
60
109
|
this._methodName = null;
|
|
110
|
+
// In-flight bridge start, shared by concurrent callers so they don't each
|
|
111
|
+
// spawn a process.
|
|
112
|
+
this._starting = null;
|
|
113
|
+
// The bridge answers one JSON line per request, in order. Requests are
|
|
114
|
+
// chained so concurrent callers (pairs translating in parallel on one
|
|
115
|
+
// shared instance) can never read each other's response line.
|
|
116
|
+
this._queue = Promise.resolve();
|
|
61
117
|
}
|
|
62
118
|
|
|
63
119
|
/**
|
|
@@ -69,7 +125,13 @@ class ExternalMethod extends TranslationMethod {
|
|
|
69
125
|
*/
|
|
70
126
|
async _ensureBridge(cwd) {
|
|
71
127
|
if (this._process) return;
|
|
128
|
+
if (!this._starting) {
|
|
129
|
+
this._starting = this._startBridge(cwd).finally(() => { this._starting = null; });
|
|
130
|
+
}
|
|
131
|
+
await this._starting;
|
|
132
|
+
}
|
|
72
133
|
|
|
134
|
+
async _startBridge(cwd) {
|
|
73
135
|
const methodPath = this._methodPath
|
|
74
136
|
? resolve(cwd || '.', this._methodPath)
|
|
75
137
|
: null;
|
|
@@ -77,42 +139,43 @@ class ExternalMethod extends TranslationMethod {
|
|
|
77
139
|
const args = [BRIDGE_SCRIPT];
|
|
78
140
|
if (methodPath) args.push('--method', methodPath);
|
|
79
141
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if (err.code === 'ENOENT') {
|
|
98
|
-
throw new Error(
|
|
99
|
-
'Python is required for external method modules but was not found.\n' +
|
|
100
|
-
' Install Python 3.10+ from https://python.org'
|
|
101
|
-
);
|
|
142
|
+
const proc = await spawnPython(args, {
|
|
143
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
144
|
+
cwd: cwd || process.cwd(),
|
|
145
|
+
});
|
|
146
|
+
this._process = proc;
|
|
147
|
+
liveBridges.add(proc);
|
|
148
|
+
|
|
149
|
+
// A later process error (e.g. EPIPE) must not become an uncaught
|
|
150
|
+
// exception; pending reads reject via _readResponse's listeners.
|
|
151
|
+
proc.on('error', (err) => output.warn(` [method] bridge error: ${err.message}`));
|
|
152
|
+
// A dead bridge is forgotten so the next request starts a fresh one
|
|
153
|
+
// instead of writing into a closed pipe.
|
|
154
|
+
proc.once('exit', () => {
|
|
155
|
+
liveBridges.delete(proc);
|
|
156
|
+
if (this._process === proc) {
|
|
157
|
+
this._process = null;
|
|
158
|
+
this._rl = null;
|
|
102
159
|
}
|
|
103
|
-
throw err;
|
|
104
160
|
});
|
|
105
161
|
|
|
106
162
|
// Python logging → CLI info channel (stderr is for logs, not errors)
|
|
107
|
-
const stderrRl = createInterface({ input:
|
|
163
|
+
const stderrRl = createInterface({ input: proc.stderr });
|
|
108
164
|
stderrRl.on('line', (line) => output.info(` [method] ${line}`));
|
|
109
165
|
|
|
110
166
|
// stdout → JSON-lines protocol
|
|
111
|
-
this._rl = createInterface({ input:
|
|
167
|
+
this._rl = createInterface({ input: proc.stdout });
|
|
112
168
|
|
|
113
169
|
// Wait for the ready signal (first line of output)
|
|
114
|
-
|
|
170
|
+
let ready;
|
|
171
|
+
try {
|
|
172
|
+
ready = await this._readResponse();
|
|
173
|
+
} catch (err) {
|
|
174
|
+
proc.kill();
|
|
175
|
+
throw err;
|
|
176
|
+
}
|
|
115
177
|
if (!ready.ok) {
|
|
178
|
+
proc.kill();
|
|
116
179
|
throw new Error(ready.error);
|
|
117
180
|
}
|
|
118
181
|
this._methodName = ready.name || 'External Method';
|
|
@@ -123,41 +186,62 @@ class ExternalMethod extends TranslationMethod {
|
|
|
123
186
|
* Send a JSON request line and read the response line.
|
|
124
187
|
*/
|
|
125
188
|
async _request(obj) {
|
|
126
|
-
|
|
189
|
+
const send = () => new Promise((resolve, reject) => {
|
|
190
|
+
if (!this._process) {
|
|
191
|
+
reject(new Error('Method bridge is not running.'));
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
// Arm the reader before writing so the response line can't slip past.
|
|
195
|
+
const response = this._readResponse();
|
|
196
|
+
response.then(resolve, reject);
|
|
127
197
|
const line = JSON.stringify(obj) + '\n';
|
|
128
198
|
this._process.stdin.write(line, 'utf-8', (err) => {
|
|
129
|
-
if (err)
|
|
130
|
-
this._readResponse().then(resolve).catch(reject);
|
|
199
|
+
if (err) reject(err);
|
|
131
200
|
});
|
|
132
201
|
});
|
|
202
|
+
const result = this._queue.then(send, send);
|
|
203
|
+
this._queue = result.catch(() => {});
|
|
204
|
+
return result;
|
|
133
205
|
}
|
|
134
206
|
|
|
135
207
|
/**
|
|
136
208
|
* Read one JSON-lines response from the bridge subprocess.
|
|
137
209
|
*/
|
|
138
210
|
_readResponse() {
|
|
211
|
+
const rl = this._rl;
|
|
212
|
+
const proc = this._process;
|
|
139
213
|
return new Promise((resolve, reject) => {
|
|
214
|
+
const cleanup = () => {
|
|
215
|
+
rl.removeListener('line', onLine);
|
|
216
|
+
proc.removeListener('exit', onExit);
|
|
217
|
+
proc.removeListener('error', onError);
|
|
218
|
+
};
|
|
140
219
|
const onLine = (line) => {
|
|
220
|
+
cleanup();
|
|
141
221
|
try {
|
|
142
222
|
resolve(JSON.parse(line));
|
|
143
223
|
} catch (e) {
|
|
144
224
|
reject(new Error(`Invalid response from method bridge: ${line}`));
|
|
145
225
|
}
|
|
146
|
-
this._rl.removeListener('line', onLine);
|
|
147
|
-
this._process.removeListener('exit', onExit);
|
|
148
226
|
};
|
|
149
227
|
|
|
150
228
|
const onExit = (code) => {
|
|
151
|
-
|
|
229
|
+
cleanup();
|
|
152
230
|
reject(new Error(
|
|
153
231
|
`Method bridge exited unexpectedly (code ${code}).\n` +
|
|
154
232
|
' Check that the method module is installed correctly.\n' +
|
|
155
|
-
' Verify the method module is
|
|
233
|
+
' Verify the method module is installed (e.g., python3 -m pip install <package-name>).'
|
|
156
234
|
));
|
|
157
235
|
};
|
|
158
236
|
|
|
159
|
-
|
|
160
|
-
|
|
237
|
+
const onError = (err) => {
|
|
238
|
+
cleanup();
|
|
239
|
+
reject(err);
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
rl.on('line', onLine);
|
|
243
|
+
proc.once('exit', onExit);
|
|
244
|
+
proc.once('error', onError);
|
|
161
245
|
});
|
|
162
246
|
}
|
|
163
247
|
|
|
@@ -173,26 +257,18 @@ class ExternalMethod extends TranslationMethod {
|
|
|
173
257
|
* @returns {Promise<Array<{name, method_id, supported_pairs, entry_point, plugin_path}>>}
|
|
174
258
|
*/
|
|
175
259
|
static async discoverMethods(cwd) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
187
|
-
cwd: cwd || process.cwd(),
|
|
188
|
-
});
|
|
189
|
-
} catch {
|
|
190
|
-
// Python not available — no method modules discoverable
|
|
191
|
-
resolve([]);
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
260
|
+
let proc;
|
|
261
|
+
try {
|
|
262
|
+
proc = await spawnPython([BRIDGE_SCRIPT, '--discover'], {
|
|
263
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
264
|
+
cwd: cwd || process.cwd(),
|
|
265
|
+
});
|
|
266
|
+
} catch {
|
|
267
|
+
// Python not available — no method modules discoverable
|
|
268
|
+
return [];
|
|
269
|
+
}
|
|
195
270
|
|
|
271
|
+
return new Promise((resolve) => {
|
|
196
272
|
let data = '';
|
|
197
273
|
proc.stdout.on('data', (chunk) => { data += chunk; });
|
|
198
274
|
|
|
@@ -307,7 +383,7 @@ class ExternalMethod extends TranslationMethod {
|
|
|
307
383
|
getSetupHelp() {
|
|
308
384
|
return [
|
|
309
385
|
' External method module.',
|
|
310
|
-
' 1. Install a method module (e.g., pip install <module-name>)',
|
|
386
|
+
' 1. Install a method module (e.g., python3 -m pip install <module-name>)',
|
|
311
387
|
' 2. Run: champollion init',
|
|
312
388
|
' 3. The wizard will detect and configure it automatically.',
|
|
313
389
|
];
|
|
@@ -317,14 +393,21 @@ class ExternalMethod extends TranslationMethod {
|
|
|
317
393
|
* Clean up: send shutdown command and kill the subprocess.
|
|
318
394
|
*/
|
|
319
395
|
async shutdown() {
|
|
320
|
-
if (this.
|
|
321
|
-
try {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
this.
|
|
396
|
+
if (this._starting) {
|
|
397
|
+
try { await this._starting; } catch { /* never started — nothing to stop */ }
|
|
398
|
+
}
|
|
399
|
+
const proc = this._process;
|
|
400
|
+
if (!proc) return;
|
|
401
|
+
try {
|
|
402
|
+
await this._request({ action: 'shutdown' });
|
|
403
|
+
} catch {
|
|
404
|
+
// Process may already be dead — that's fine
|
|
405
|
+
}
|
|
406
|
+
proc.kill();
|
|
407
|
+
liveBridges.delete(proc);
|
|
408
|
+
if (this._process === proc) {
|
|
327
409
|
this._process = null;
|
|
410
|
+
this._rl = null;
|
|
328
411
|
}
|
|
329
412
|
}
|
|
330
413
|
}
|
package/lib/methods/gemini.js
CHANGED
|
@@ -32,6 +32,7 @@ class GeminiMethod extends DirectLLMMethod {
|
|
|
32
32
|
_getApiKeyOptionsKey() { return 'geminiApiKey'; }
|
|
33
33
|
_getDefaultModel() { return DEFAULT_MODEL; }
|
|
34
34
|
_getProviderLabel() { return 'Gemini'; }
|
|
35
|
+
_getModelVendor() { return 'google'; }
|
|
35
36
|
|
|
36
37
|
// ── API request/response shape ───────────────────────────────────
|
|
37
38
|
|
|
@@ -43,6 +43,7 @@ const GOOGLE_REQUEST_TIMEOUT_MS = 15000;
|
|
|
43
43
|
class GoogleTranslateMethod extends TranslationMethod {
|
|
44
44
|
constructor(options = {}) {
|
|
45
45
|
super('google-translate', options);
|
|
46
|
+
this.translatesRawText = true; // see base.js
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
// ── API resolution helpers ──────────────────────────────────────
|
|
@@ -111,7 +111,48 @@ function stripCodeFences(text) {
|
|
|
111
111
|
.trim();
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
/**
|
|
115
|
+
* True only when an endpoint provably stays on this machine — the same rule
|
|
116
|
+
* as the harness's LocalProvider.is_loopback_endpoint(): unix sockets,
|
|
117
|
+
* `localhost` (and `*.localhost`), loopback IPs (127.0.0.0/8, ::1) and the
|
|
118
|
+
* unspecified addresses 0.0.0.0 / :: (a client connecting to them reaches
|
|
119
|
+
* this host). A LAN or private-range address is another machine: false.
|
|
120
|
+
*
|
|
121
|
+
* Used to price a model served here as "$0 API cost (runs on this machine)"
|
|
122
|
+
* instead of "unknown" — never for an endpoint that only might be local.
|
|
123
|
+
*
|
|
124
|
+
* @param {string|null|undefined} url
|
|
125
|
+
* @returns {boolean}
|
|
126
|
+
*/
|
|
127
|
+
function isLoopbackEndpoint(url) {
|
|
128
|
+
if (!url || typeof url !== 'string') return false;
|
|
129
|
+
if (/^(?:http\+)?unix:/i.test(url)) return true;
|
|
130
|
+
let host;
|
|
131
|
+
try { host = new URL(url.includes('//') ? url : `http://${url}`).hostname; } catch { return false; }
|
|
132
|
+
host = host.replace(/^\[|\]$/g, '').toLowerCase();
|
|
133
|
+
if (!host) return false;
|
|
134
|
+
if (host === 'localhost' || host.endsWith('.localhost')) return true;
|
|
135
|
+
if (/^127(?:\.\d{1,3}){3}$/.test(host)) return host.split('.').every(o => Number(o) <= 255);
|
|
136
|
+
if (host === '0.0.0.0' || host === '::1' || host === '::') return true;
|
|
137
|
+
// IPv4-mapped IPv6 loopback (::ffff:127.0.0.1 → URL writes ::ffff:7f00:1).
|
|
138
|
+
if (/^::ffff:(?:127(?:\.\d{1,3}){3}|7f[0-9a-f]{2}:[0-9a-f]{1,4})$/.test(host)) return true;
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** The cost estimate for a model served on this machine (see isLoopbackEndpoint). */
|
|
143
|
+
function localMachineCost(endpoint) {
|
|
144
|
+
return {
|
|
145
|
+
estimatedCost: 0,
|
|
146
|
+
currency: 'USD',
|
|
147
|
+
source: 'local-machine',
|
|
148
|
+
local: true,
|
|
149
|
+
note: `$0 API cost — runs on this machine (${endpoint}); your own hardware and power are not counted.`,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
114
153
|
export {
|
|
154
|
+
isLoopbackEndpoint,
|
|
155
|
+
localMachineCost,
|
|
115
156
|
MAX_RETRIES,
|
|
116
157
|
BASE_DELAY_MS,
|
|
117
158
|
REQUEST_TIMEOUT_MS,
|
|
@@ -12,6 +12,7 @@ const LIBRETRANSLATE_REQUEST_TIMEOUT_MS = 15000;
|
|
|
12
12
|
class LibreTranslateMethod extends TranslationMethod {
|
|
13
13
|
constructor(options = {}) {
|
|
14
14
|
super('libretranslate', options);
|
|
15
|
+
this.translatesRawText = true; // see base.js
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
// ── API resolution helpers ──────────────────────────────────────
|
|
@@ -24,7 +25,6 @@ class LibreTranslateMethod extends TranslationMethod {
|
|
|
24
25
|
*/
|
|
25
26
|
_resolveApiEndpoint(options) {
|
|
26
27
|
return options.libretranslateApiUrl
|
|
27
|
-
|| getEnvOrFileVar('LIBRETRANSLATE_API_URL')
|
|
28
28
|
|| getEnvOrFileVar('LIBRETRANSLATE_API_URL', options.cwd)
|
|
29
29
|
|| 'http://localhost:5000/translate';
|
|
30
30
|
}
|
|
@@ -36,7 +36,6 @@ class LibreTranslateMethod extends TranslationMethod {
|
|
|
36
36
|
*/
|
|
37
37
|
_resolveApiKey(options) {
|
|
38
38
|
return options.libretranslateApiKey
|
|
39
|
-
|| getEnvOrFileVar('LIBRETRANSLATE_API_KEY')
|
|
40
39
|
|| getEnvOrFileVar('LIBRETRANSLATE_API_KEY', options.cwd);
|
|
41
40
|
}
|
|
42
41
|
|
|
@@ -181,6 +180,10 @@ class LibreTranslateMethod extends TranslationMethod {
|
|
|
181
180
|
if (!response.ok) {
|
|
182
181
|
return {
|
|
183
182
|
ready: false,
|
|
183
|
+
// A server that is down or failing: sync decides after its plan
|
|
184
|
+
// whether this run needs it (lib/sync.js deferred probes).
|
|
185
|
+
unreachable: true,
|
|
186
|
+
endpoint: baseUrl,
|
|
184
187
|
reason:
|
|
185
188
|
`LibreTranslate server at ${baseUrl} responded with ${response.status}.\n` +
|
|
186
189
|
` Check that LibreTranslate is running and accessible.`,
|
|
@@ -190,6 +193,8 @@ class LibreTranslateMethod extends TranslationMethod {
|
|
|
190
193
|
} catch (err) {
|
|
191
194
|
return {
|
|
192
195
|
ready: false,
|
|
196
|
+
unreachable: true,
|
|
197
|
+
endpoint: baseUrl,
|
|
193
198
|
reason:
|
|
194
199
|
`Cannot reach LibreTranslate server at ${baseUrl}.\n` +
|
|
195
200
|
` ${err.name === 'AbortError' ? 'Connection timed out.' : err.message}\n` +
|