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/translate.js
CHANGED
|
@@ -41,6 +41,8 @@ import { ExternalMethod } from './methods/external.js';
|
|
|
41
41
|
import { DEFAULT_OPENROUTER_MODEL } from './config.js';
|
|
42
42
|
import { manifestEntries, cliNameFor } from './method-manifest.js';
|
|
43
43
|
import { assertRoutable } from './commercial-eligibility.js';
|
|
44
|
+
import { CONTENT_SEPARATOR, parseContentPrompt } from './methods/content-separator.js';
|
|
45
|
+
import { SEGMENT_MARKER_PREFIX, SEGMENT_MARKER_SUFFIX } from './segment.js';
|
|
44
46
|
|
|
45
47
|
/**
|
|
46
48
|
* Registry of available translation methods.
|
|
@@ -69,6 +71,27 @@ const METHOD_REGISTRY = {
|
|
|
69
71
|
'external': ExternalMethod,
|
|
70
72
|
};
|
|
71
73
|
|
|
74
|
+
/**
|
|
75
|
+
* ExternalMethod instances by plugin path. Each instance owns a Python bridge
|
|
76
|
+
* process, so building one per call (getMethod runs for every batch, every
|
|
77
|
+
* preflight, every content file) started a new process each time and never
|
|
78
|
+
* stopped any. One instance per plugin is reused for the whole run and
|
|
79
|
+
* stopped by shutdownMethods().
|
|
80
|
+
*/
|
|
81
|
+
const externalMethods = new Map();
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Stop every bridge process started through getMethod(). Call when a run is
|
|
85
|
+
* finished; a later getMethod() starts a fresh bridge on demand.
|
|
86
|
+
*
|
|
87
|
+
* @returns {Promise<void>}
|
|
88
|
+
*/
|
|
89
|
+
async function shutdownMethods() {
|
|
90
|
+
const instances = [...externalMethods.values()];
|
|
91
|
+
externalMethods.clear();
|
|
92
|
+
await Promise.all(instances.map(m => m.shutdown()));
|
|
93
|
+
}
|
|
94
|
+
|
|
72
95
|
/**
|
|
73
96
|
* Find a shared-registry entry that exists but is NOT served in the CLI runtime.
|
|
74
97
|
*
|
|
@@ -153,14 +176,27 @@ function getMethod(methodName, pluginContext) {
|
|
|
153
176
|
methodVersion: pluginContext.pluginVersion,
|
|
154
177
|
qualityTier: pluginContext.qualityTier,
|
|
155
178
|
provenance: pluginContext.pluginProvenance,
|
|
179
|
+
acceptsInstructions: pluginContext.acceptsInstructions,
|
|
156
180
|
});
|
|
157
181
|
}
|
|
158
182
|
|
|
159
|
-
//
|
|
183
|
+
// LLMCoachedMethod needs the pair's transport so preflight checks the
|
|
184
|
+
// credential the run will actually use (OPENAI_API_KEY, not OpenRouter's).
|
|
185
|
+
if (methodName === 'llm-coached') {
|
|
186
|
+
return new MethodClass({ provider: pluginContext?.provider });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ExternalMethod needs methodPath to find the Python plugin directory.
|
|
190
|
+
// Shared per plugin so the run reuses one bridge process (see above).
|
|
160
191
|
if (methodName === 'external') {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
192
|
+
const methodPath = pluginContext?.methodPath || null;
|
|
193
|
+
if (!methodPath) return new MethodClass({ methodPath });
|
|
194
|
+
let instance = externalMethods.get(methodPath);
|
|
195
|
+
if (!instance) {
|
|
196
|
+
instance = new MethodClass({ methodPath });
|
|
197
|
+
externalMethods.set(methodPath, instance);
|
|
198
|
+
}
|
|
199
|
+
return instance;
|
|
164
200
|
}
|
|
165
201
|
|
|
166
202
|
return new MethodClass();
|
|
@@ -197,7 +233,44 @@ async function translateRawContent(prompt, options) {
|
|
|
197
233
|
// (api → endpoint, external → methodPath) are constructed the same
|
|
198
234
|
// way here as on the key-value path in translateBatch.
|
|
199
235
|
const method = getMethod(pairConfig.method, pairConfig);
|
|
236
|
+
if (method.translatesRawText) {
|
|
237
|
+
return translateBlocksForRawTextEngine(method, prompt, pairConfig, options);
|
|
238
|
+
}
|
|
200
239
|
return method.translateContent(prompt, pairConfig, options);
|
|
201
240
|
}
|
|
202
241
|
|
|
203
|
-
|
|
242
|
+
/**
|
|
243
|
+
* A raw-text engine gets only Markdown, one block at a time.
|
|
244
|
+
*
|
|
245
|
+
* Those engines read the body after "\n---\n"; the default BLOCK-batch
|
|
246
|
+
* prompt has no such separator, so they were sent — and billed for — the
|
|
247
|
+
* whole LLM instruction prompt with every block in it, and the ⟦SEG_N⟧
|
|
248
|
+
* markers survived only by luck (found 2026-10-03). Each block now goes as
|
|
249
|
+
* its own page-shaped prompt and the reply is reassembled under its marker;
|
|
250
|
+
* a block the engine fails is simply absent, which the content gate reports.
|
|
251
|
+
*/
|
|
252
|
+
async function translateBlocksForRawTextEngine(method, prompt, pairConfig, options) {
|
|
253
|
+
const request = parseContentPrompt(prompt);
|
|
254
|
+
if (!request) return null; // nothing but instructions — never send those
|
|
255
|
+
if (request.mode === 'page') {
|
|
256
|
+
return method.translateContent(`${CONTENT_SEPARATOR}${request.keys.body}`, pairConfig, options);
|
|
257
|
+
}
|
|
258
|
+
const translated = new Map();
|
|
259
|
+
const queue = [...request.ids];
|
|
260
|
+
const worker = async () => {
|
|
261
|
+
while (queue.length > 0) {
|
|
262
|
+
const id = queue.shift();
|
|
263
|
+
const text = request.keys[`segment.${id}`];
|
|
264
|
+
if (!text || !text.trim()) { translated.set(id, text ?? ''); continue; }
|
|
265
|
+
const out = await method.translateContent(`${CONTENT_SEPARATOR}${text}`, pairConfig, options);
|
|
266
|
+
if (typeof out === 'string') translated.set(id, out);
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
await Promise.all(Array.from({ length: Math.min(4, request.ids.length) }, worker));
|
|
270
|
+
const parts = request.ids
|
|
271
|
+
.filter((id) => translated.has(id))
|
|
272
|
+
.map((id) => `${SEGMENT_MARKER_PREFIX}${id}${SEGMENT_MARKER_SUFFIX}\n${translated.get(id)}`);
|
|
273
|
+
return parts.length > 0 ? parts.join('\n\n') : null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export { translateBatch, translateRawContent, translateBlocksForRawTextEngine, buildPrompt, isUnsafeKey, inferKeyTypes, getMethod, shutdownMethods, METHOD_REGISTRY };
|
package/lib/types.js
CHANGED
|
@@ -23,8 +23,11 @@
|
|
|
23
23
|
* @property {number} version - Config schema version (currently 3)
|
|
24
24
|
* @property {string} inputLocale - Source locale code (e.g., 'en')
|
|
25
25
|
* @property {string} baseUrl - Site base URL for SEO commands
|
|
26
|
-
* @property {string} localesDir - Absolute path to locale files directory
|
|
27
|
-
* @property {string|null}
|
|
26
|
+
* @property {string} localesDir - Absolute path to locale files directory (derived from localesPattern when that is set)
|
|
27
|
+
* @property {string|null} [localesPattern] - Absolute locale-file pattern with {lang} and optional {ns} (lib/locale-layout.js)
|
|
28
|
+
* @property {'flat'|'dir'|null} [localesLayout] - Forces the layout auto-detection's answer
|
|
29
|
+
* @property {string|null} [defaultNamespace] - Namespace file `wrap` adds keys to in a multi-file locale
|
|
30
|
+
* @property {string|null} contentDir - Folder of Markdown/MDX to translate (a Hugo content/ dir or any folder), or null if disabled
|
|
28
31
|
* @property {string[]|null} translatableFields - Override for content translatable fields, or null for defaults
|
|
29
32
|
* @property {Array<string>|object} languages - Target languages (array of codes or object with config)
|
|
30
33
|
* @property {Object<string, LanguageConfig>} [resolvedLanguages] - Fully resolved language map (code → config). Set by resolveConfig.
|
|
@@ -33,6 +36,7 @@
|
|
|
33
36
|
* @property {object|null} pairs - Advanced per-pair overrides, or null
|
|
34
37
|
* @property {string} model - Default translation model (e.g., 'openai/gpt-4o-mini')
|
|
35
38
|
* @property {string} defaultMethod - Global default method: 'llm', 'google-translate', 'api'
|
|
39
|
+
* @property {string|null} [provider] - LLM-lane transport: 'openrouter' (default), 'openai', 'anthropic', 'gemini', 'local'
|
|
36
40
|
* @property {number} batchSize - Default batch size for translation calls
|
|
37
41
|
* @property {string} fallbackPrefix - Prefix for untranslated fallback values (default: '[EN] ')
|
|
38
42
|
* @property {string} apiKeyEnvVar - Environment variable name for the API key
|
|
@@ -54,6 +58,7 @@
|
|
|
54
58
|
* @property {string} source - Source locale code (e.g., 'en')
|
|
55
59
|
* @property {string} target - Target locale code (e.g., 'fr')
|
|
56
60
|
* @property {string} method - Translation method name: 'llm', 'llm-coached', 'google-translate', 'api'
|
|
61
|
+
* @property {string|null} provider - Resolved transport for llm-coached / a rewritten llm pair; null for engine methods
|
|
57
62
|
* @property {string} model - Model identifier (e.g., 'openai/gpt-4o-mini')
|
|
58
63
|
* @property {string} qualityTier - Quality tier: 'standard', 'high', 'research', 'verified'
|
|
59
64
|
* @property {number} batchSize - Max keys per API call
|
|
@@ -70,6 +75,8 @@
|
|
|
70
75
|
* @property {string|null} formalitySystem - Formality system name (e.g., 'T-V', 'speech-levels'). Used by DeepL for structured formality mapping.
|
|
71
76
|
* @property {string|null} genderGuidance - Language-specific gender guidance for LLM prompts, or null
|
|
72
77
|
* @property {Set<string>} _defaults - Fields that were filled from system defaults (used by plugin precedence)
|
|
78
|
+
* @property {PairConfig} [fallback] - The second method for what this pair's own method cannot translate safely (set by resolvePairs when configured; see pairs.js resolveFallbackForPair). Itself a full PairConfig with `isFallback: true` and no `fallback` of its own.
|
|
79
|
+
* @property {boolean} [isFallback] - True on a resolved fallback config
|
|
73
80
|
*
|
|
74
81
|
* --- Plugin-injected fields (present after resolvePluginForPair) ---
|
|
75
82
|
* @property {string} [endpoint] - Plugin API endpoint URL
|
|
@@ -100,9 +107,18 @@
|
|
|
100
107
|
* @property {string} [dir] - Text direction: 'ltr' or 'rtl'
|
|
101
108
|
* @property {string|null} [formalitySystem] - Formality system name (e.g., 'T-V', 'speech-levels', 'keigo')
|
|
102
109
|
* @property {string} [model] - Per-language model override
|
|
110
|
+
* @property {string} [provider] - Per-language LLM-lane transport override
|
|
103
111
|
* @property {number} [batchSize] - Per-language batch size override
|
|
104
112
|
* @property {number} [maxRetries] - Per-language max retries override
|
|
105
113
|
* @property {string} [script] - Script system override (e.g., 'syllabics' for Cree)
|
|
114
|
+
* @property {string} [method] - Per-language translation method
|
|
115
|
+
* @property {string} [endpoint] - API endpoint for the `api` method
|
|
116
|
+
* @property {number} [temperature] - Per-language temperature
|
|
117
|
+
* @property {string} [coachingFile] - Per-language coaching prompt file
|
|
118
|
+
* @property {string} [coachingPrompt] - Per-language coaching prompt text
|
|
119
|
+
* @property {string} [promptContext] - Per-language application context
|
|
120
|
+
* @property {'block'|'page'} [contentSegmentation] - Markdown body granularity
|
|
121
|
+
* @property {object|null} [fallback] - Raw `fallback` (a second method), resolved by pairs.js
|
|
106
122
|
*/
|
|
107
123
|
|
|
108
124
|
// -----------------------------------------------------------------
|
|
@@ -218,10 +234,13 @@
|
|
|
218
234
|
* @property {string} [format] - Locale file format override
|
|
219
235
|
* @property {string} [out] - Output file path (seo sitemap)
|
|
220
236
|
* @property {string} [src] - Source directory for lint/wrap
|
|
221
|
-
* @property {string} ['content-dir'] -
|
|
237
|
+
* @property {string} ['content-dir'] - Folder of Markdown/MDX to translate (overrides contentDir)
|
|
222
238
|
* @property {string} ['base-url'] - Site base URL override
|
|
223
239
|
* @property {string} ['min-length'] - Minimum string length to flag
|
|
224
240
|
* @property {string} ['force-keys'] - Comma-separated keys to force re-translate
|
|
241
|
+
* @property {boolean} ['fresh-on-model-change'] - Serve only exact-model TM hits (default reuses other models' cached translations)
|
|
242
|
+
* @property {string[]} [files] - sync: only these content files (globs)
|
|
243
|
+
* @property {string[]} [retranslate] - sync: translate these content files fresh (globs)
|
|
225
244
|
* @property {boolean} ['warn-only'] - Exit 0 even with issues
|
|
226
245
|
*/
|
|
227
246
|
|