champollion 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/LICENSE +133 -0
  2. package/README.md +387 -0
  3. package/bin/cli.js +278 -0
  4. package/index.js +135 -0
  5. package/lib/api-key.js +127 -0
  6. package/lib/autofix.js +432 -0
  7. package/lib/bridge/method_bridge.py +430 -0
  8. package/lib/card-source-resolution.mjs +284 -0
  9. package/lib/cards/cache.js +169 -0
  10. package/lib/cards/env.js +82 -0
  11. package/lib/cards/fetch-card-child.js +38 -0
  12. package/lib/cards/reader.js +435 -0
  13. package/lib/cards/refresh.js +111 -0
  14. package/lib/cards/remote.js +387 -0
  15. package/lib/cldf-export.mjs +540 -0
  16. package/lib/cldf-terms.mjs +62 -0
  17. package/lib/command-help.js +790 -0
  18. package/lib/commands/audit.js +49 -0
  19. package/lib/commands/card.js +454 -0
  20. package/lib/commands/doctor.js +559 -0
  21. package/lib/commands/fonts.js +489 -0
  22. package/lib/commands/help.js +91 -0
  23. package/lib/commands/init.js +1259 -0
  24. package/lib/commands/integrity.js +148 -0
  25. package/lib/commands/leaderboard.js +478 -0
  26. package/lib/commands/lint.js +30 -0
  27. package/lib/commands/models.js +177 -0
  28. package/lib/commands/plugin.js +103 -0
  29. package/lib/commands/provenance.js +45 -0
  30. package/lib/commands/recommend.js +75 -0
  31. package/lib/commands/register-corpus.js +678 -0
  32. package/lib/commands/repair-script.js +42 -0
  33. package/lib/commands/seal-corpus.js +355 -0
  34. package/lib/commands/seo.js +72 -0
  35. package/lib/commands/serve.js +147 -0
  36. package/lib/commands/status.js +265 -0
  37. package/lib/commands/submit.js +332 -0
  38. package/lib/commands/sync.js +89 -0
  39. package/lib/commands/tm.js +573 -0
  40. package/lib/commands/verify.js +39 -0
  41. package/lib/commands/watch.js +20 -0
  42. package/lib/commands/wrap.js +138 -0
  43. package/lib/commands/xliff.js +327 -0
  44. package/lib/commercial-eligibility.js +235 -0
  45. package/lib/concurrent.js +87 -0
  46. package/lib/config.js +523 -0
  47. package/lib/contamination-lane.js +76 -0
  48. package/lib/content-sync.js +731 -0
  49. package/lib/content.js +733 -0
  50. package/lib/corpus-registration.mjs +608 -0
  51. package/lib/cost-report.js +346 -0
  52. package/lib/diff.js +155 -0
  53. package/lib/docusaurus-sync.js +1256 -0
  54. package/lib/flatten.js +55 -0
  55. package/lib/format.js +954 -0
  56. package/lib/hash.js +159 -0
  57. package/lib/icu.js +473 -0
  58. package/lib/integrity.js +689 -0
  59. package/lib/license-gate.mjs +478 -0
  60. package/lib/license-identify.mjs +229 -0
  61. package/lib/lint.js +629 -0
  62. package/lib/method-manifest.js +60 -0
  63. package/lib/methods/anthropic.js +140 -0
  64. package/lib/methods/apertium.js +163 -0
  65. package/lib/methods/api.js +316 -0
  66. package/lib/methods/base.js +184 -0
  67. package/lib/methods/content-separator.js +45 -0
  68. package/lib/methods/deepl.js +426 -0
  69. package/lib/methods/direct-llm.js +586 -0
  70. package/lib/methods/external.js +332 -0
  71. package/lib/methods/fetch-with-retry.js +124 -0
  72. package/lib/methods/gemini.js +147 -0
  73. package/lib/methods/google-translate.js +402 -0
  74. package/lib/methods/http-utils.js +122 -0
  75. package/lib/methods/libretranslate.js +314 -0
  76. package/lib/methods/llm-coached.js +670 -0
  77. package/lib/methods/llm.js +592 -0
  78. package/lib/methods/local.js +76 -0
  79. package/lib/methods/microsoft-translator.js +331 -0
  80. package/lib/methods/openai.js +131 -0
  81. package/lib/methods/openrouter-client.js +327 -0
  82. package/lib/methods/openrouter-pricing.js +156 -0
  83. package/lib/methods/provider-env.js +115 -0
  84. package/lib/methods/provider-pricing.js +310 -0
  85. package/lib/methods/tilde.js +150 -0
  86. package/lib/methods/translated.js +229 -0
  87. package/lib/methods/translation-error.js +80 -0
  88. package/lib/models.js +258 -0
  89. package/lib/no-translate.js +233 -0
  90. package/lib/output.js +238 -0
  91. package/lib/pairs.js +547 -0
  92. package/lib/plugins.js +447 -0
  93. package/lib/provenance.js +323 -0
  94. package/lib/recommend.js +648 -0
  95. package/lib/registers.js +1185 -0
  96. package/lib/repair-script.js +266 -0
  97. package/lib/scripts.js +994 -0
  98. package/lib/seal.mjs +464 -0
  99. package/lib/sealed-qualifier.mjs +211 -0
  100. package/lib/security.js +59 -0
  101. package/lib/segment.js +369 -0
  102. package/lib/seo.js +275 -0
  103. package/lib/serve.js +854 -0
  104. package/lib/string-classify.js +85 -0
  105. package/lib/submit.mjs +344 -0
  106. package/lib/sync.js +969 -0
  107. package/lib/tags/bcp47.js +202 -0
  108. package/lib/tags/resolve.js +314 -0
  109. package/lib/terminology.js +111 -0
  110. package/lib/tm-seed.js +294 -0
  111. package/lib/tm.js +515 -0
  112. package/lib/translate-pair.js +197 -0
  113. package/lib/translate.js +203 -0
  114. package/lib/types.js +230 -0
  115. package/lib/validate.js +510 -0
  116. package/lib/verify.js +451 -0
  117. package/lib/watch.js +145 -0
  118. package/lib/xliff.js +184 -0
  119. package/package.json +93 -0
  120. package/shared/ATTRIBUTION.md +145 -0
  121. package/shared/CORPORA-CARDS.md +288 -0
  122. package/shared/DATA-SOVEREIGNTY.md +500 -0
  123. package/shared/LANGUAGE-CARD-FIELDS.md +532 -0
  124. package/shared/card-lint-baseline.json +3189 -0
  125. package/shared/cards-fallback.json +1 -0
  126. package/shared/catalogue/card-config.json +6091 -0
  127. package/shared/catalogue/external-results.json +3888 -0
  128. package/shared/catalogue/gender-guidance.json +1038 -0
  129. package/shared/catalogue/method-coverage.json +1751 -0
  130. package/shared/catalogue/metric-coverage.json +170 -0
  131. package/shared/catalogue/metric-reliability.json +1 -0
  132. package/shared/catalogue/register-presets.json +3180 -0
  133. package/shared/catalogue/vitality-scales.json +55 -0
  134. package/shared/cldr-index.json +1115 -0
  135. package/shared/code-bridge.json +253 -0
  136. package/shared/corpora-cards-v1-reference.md +281 -0
  137. package/shared/curated-dictionary-flags.json +35 -0
  138. package/shared/curated-endonyms.json +35 -0
  139. package/shared/curated-fsts.json +51 -0
  140. package/shared/curated-orthography-conventions.json +26 -0
  141. package/shared/curated-sil-resources.json +374 -0
  142. package/shared/curated-tools.json +41 -0
  143. package/shared/docent/corpus.json +11333 -0
  144. package/shared/docent/faq.en.json +564 -0
  145. package/shared/docent/register-blocks.json +60 -0
  146. package/shared/docent/system-prompt.md +144 -0
  147. package/shared/domain-taxonomy.json +35 -0
  148. package/shared/explainers/glossary.json +2975 -0
  149. package/shared/explainers/tc-features.json +20112 -0
  150. package/shared/explainers/term-watchlist.json +147 -0
  151. package/shared/human-services.json +59 -0
  152. package/shared/license-corrections.json +261 -0
  153. package/shared/license-evidence.json +13452 -0
  154. package/shared/licenses.json +6781 -0
  155. package/shared/method-registry.json +236 -0
  156. package/shared/metric-registry.json +620 -0
  157. package/shared/model-aliases.json +7 -0
  158. package/shared/schemas/champollion-plugin.schema.json +206 -0
  159. package/shared/schemas/corpora-card.schema.json +957 -0
  160. package/shared/schemas/domain-taxonomy.schema.json +64 -0
  161. package/shared/schemas/external-results.schema.json +314 -0
  162. package/shared/schemas/human-services.schema.json +90 -0
  163. package/shared/schemas/language-card.schema.json +1308 -0
  164. package/shared/schemas/licenses.schema.json +155 -0
  165. package/shared/schemas/method-card.schema.json +412 -0
  166. package/shared/schemas/method-registry.schema.json +85 -0
  167. package/shared/schemas/metric-registry.schema.json +96 -0
  168. package/shared/schemas/metric-reliability.schema.json +178 -0
  169. package/shared/schemas/model-aliases.schema.json +27 -0
  170. package/shared/schemas/source-snapshot.schema.json +96 -0
@@ -0,0 +1,266 @@
1
+ /**
2
+ * repair-script.js — undo script conversions that should never have happened.
3
+ *
4
+ * THE DAMAGE THIS REPAIRS: before 0.3.0, script conversion was unconditional,
5
+ * so every tlh / x-elvish-s / x-kryptonian project had its translations
6
+ * rewritten into Private Use Area codepoints — scripts that are NOT in
7
+ * Unicode and render as nothing without a purpose-built font. A project
8
+ * shipping Latin-transliteration fonts (the common case) displayed blank
9
+ * strings. Worse, the converters silently passed through letters they could
10
+ * not map, leaving values that are half PUA and half Latin.
11
+ *
12
+ * WHAT IT DOES: for every configured target locale whose converter emits PUA
13
+ * and whose script resolution says conversion is OFF, scan the locale files
14
+ * for PUA codepoints and reverse them back to the working script
15
+ * (romanization) via the converter's own table. Values without PUA are never
16
+ * touched.
17
+ *
18
+ * WHAT IT DOES NOT TOUCH:
19
+ * - The Translation Memory — it stores PRE-conversion values, so it was
20
+ * never damaged (verified: translate-pair.js stores before sync converts).
21
+ * - The hash manifest — it hashes SOURCE values, so a repair never
22
+ * triggers re-translation.
23
+ * - Locales whose conversion is opted ON (`script: "Piqd"` etc.) — there
24
+ * the PUA is the deliverable; the scan reports them as skipped.
25
+ * - Real-Unicode conversions (crk Syllabics, sr Cyrillic) — those render
26
+ * everywhere, and "reversing" them would destroy legitimate content.
27
+ *
28
+ * REVERSAL FIDELITY: pIqaD reverses exactly (the map is injective up to the
29
+ * straight/curly apostrophe, both of which restore as '). Tengwar, Cree
30
+ * syllabics and Kryptonian normalise case on the way in, so their reversals
31
+ * are case-lossy — reported per file, never silent. Codepoints from a PUA
32
+ * block the converter does not own are left in place and reported as
33
+ * unreversible (they are damage from something else, and inventing a reading
34
+ * for them would be fabrication).
35
+ */
36
+
37
+ import fs from 'node:fs';
38
+ import path from 'node:path';
39
+ import { resolveConfig } from './config.js';
40
+ import { resolvePairs } from './pairs.js';
41
+ import { getLanguageCard } from './registers.js';
42
+ import {
43
+ SCRIPT_CONVERTERS, reverseScript, isPrivateUse, converterKeyForLocale, getConverterInfo,
44
+ } from './scripts.js';
45
+ import {
46
+ readLocaleFile, writeLocaleFile, detectFormatFromDir, detectYAMLStyle, getExtension,
47
+ } from './format.js';
48
+ import { discoverDocusaurusJSONFiles } from './docusaurus-sync.js';
49
+ import { visualize } from './integrity.js';
50
+ import { output } from './output.js';
51
+
52
+ /** Does this string contain any Private Use Area codepoint? */
53
+ function hasPua(value) {
54
+ if (typeof value !== 'string') return false;
55
+ for (const ch of value) {
56
+ if (isPrivateUse(ch.codePointAt(0))) return true;
57
+ }
58
+ return false;
59
+ }
60
+
61
+ /**
62
+ * Repair every PUA-bearing string in a nested JSON structure, in place.
63
+ *
64
+ * Generic deep walk rather than Docusaurus-message surgery: only strings that
65
+ * actually contain PUA are touched, so descriptions, URLs, and every other
66
+ * field pass through byte-identical whatever the file's shape.
67
+ *
68
+ * @param {*} node - Parsed JSON value (object/array/primitive)
69
+ * @param {string} converterKey - Converter whose table reverses the PUA
70
+ * @param {object} stats - Mutated: { repaired, caseLossy, unreversed:Set, samples:[] }
71
+ * @returns {*} The repaired node
72
+ */
73
+ function repairNode(node, converterKey, stats) {
74
+ if (typeof node === 'string') {
75
+ if (!hasPua(node)) return node;
76
+ const { reversed, caseLossy, unreversed } = reverseScript(node, converterKey);
77
+ stats.repaired++;
78
+ if (caseLossy) stats.caseLossy++;
79
+ for (const ch of unreversed) stats.unreversed.add(ch);
80
+ if (stats.samples.length < 3) {
81
+ stats.samples.push({ before: node, after: reversed });
82
+ }
83
+ return reversed;
84
+ }
85
+ if (Array.isArray(node)) {
86
+ return node.map(v => repairNode(v, converterKey, stats));
87
+ }
88
+ if (node && typeof node === 'object') {
89
+ for (const k of Object.keys(node)) {
90
+ node[k] = repairNode(node[k], converterKey, stats);
91
+ }
92
+ return node;
93
+ }
94
+ return node;
95
+ }
96
+
97
+ /**
98
+ * Run the repair across a project's configured locales.
99
+ *
100
+ * @param {object} options
101
+ * @param {string} options.cwd - Project root
102
+ * @param {object} options.cliArgs - Parsed CLI args (dry, locale, json)
103
+ * @returns {{ locales: Array, totals: object, exitCode: number }}
104
+ */
105
+ function runRepairScript({ cwd = process.cwd(), cliArgs = {} } = {}) {
106
+ const config = resolveConfig(cliArgs, cwd);
107
+ const dry = !!cliArgs.dry;
108
+ const localeFilter = cliArgs.locale || null;
109
+
110
+ const pairs = resolvePairs(config);
111
+ const report = [];
112
+ const totals = { filesScanned: 0, valuesRepaired: 0, caseLossy: 0, unreversedCodepoints: 0 };
113
+ let hadWriteFailure = false;
114
+
115
+ for (const [, pairConfig] of pairs) {
116
+ const locale = pairConfig.target;
117
+ if (localeFilter && locale !== localeFilter) continue;
118
+
119
+ const card = getLanguageCard(locale);
120
+ const converterKey = converterKeyForLocale(locale, card);
121
+ if (!converterKey) continue;
122
+ const conv = SCRIPT_CONVERTERS[converterKey];
123
+ // Real-Unicode converters (crk, sr) never emit PUA — nothing to repair.
124
+ if (!conv.puaRange) continue;
125
+
126
+ const resolution = pairConfig.scriptResolution || {};
127
+ if (resolution.converterKey) {
128
+ report.push({
129
+ locale,
130
+ converter: getConverterInfo(converterKey).to,
131
+ skipped: 'conversion enabled — PUA is the configured output for this locale',
132
+ files: [],
133
+ });
134
+ continue;
135
+ }
136
+
137
+ // Discover this locale's files. Docusaurus keeps a directory per locale;
138
+ // everything else is one flat file per locale.
139
+ const files = [];
140
+ let flatFormat = null;
141
+ if (config.format === 'docusaurus') {
142
+ files.push(...discoverDocusaurusJSONFiles(path.join(config.localesDir, locale)));
143
+ } else {
144
+ flatFormat = config.format !== 'auto' ? config.format : detectFormatFromDir(config.localesDir);
145
+ const filePath = path.join(config.localesDir, `${locale}${getExtension(flatFormat)}`);
146
+ if (fs.existsSync(filePath)) files.push(filePath);
147
+ }
148
+
149
+ const localeReport = {
150
+ locale,
151
+ converter: getConverterInfo(converterKey).to,
152
+ files: [],
153
+ };
154
+
155
+ for (const filePath of files) {
156
+ totals.filesScanned++;
157
+ const stats = { repaired: 0, caseLossy: 0, unreversed: new Set(), samples: [] };
158
+
159
+ try {
160
+ if (config.format === 'docusaurus' || flatFormat === 'json') {
161
+ const parsed = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
162
+ const repaired = repairNode(parsed, converterKey, stats);
163
+ if (stats.repaired > 0 && !dry) {
164
+ fs.writeFileSync(filePath, JSON.stringify(repaired, null, 2) + '\n', 'utf-8');
165
+ }
166
+ } else {
167
+ // TOML/YAML: readLocaleFile returns a flat map; repair values and
168
+ // write back through the format-preserving serializer.
169
+ const flat = readLocaleFile(filePath, flatFormat);
170
+ const yamlStyle = flatFormat === 'yaml'
171
+ ? detectYAMLStyle(fs.readFileSync(filePath, 'utf-8'))
172
+ : null;
173
+ for (const [k, v] of Object.entries(flat)) {
174
+ flat[k] = repairNode(v, converterKey, stats);
175
+ }
176
+ if (stats.repaired > 0 && !dry) {
177
+ writeLocaleFile(filePath, flat, flatFormat, flat, yamlStyle);
178
+ }
179
+ }
180
+ } catch (err) {
181
+ hadWriteFailure = true;
182
+ localeReport.files.push({ file: filePath, error: err.message });
183
+ output.error(`[repair-script] ${filePath}: ${err.message}`);
184
+ continue;
185
+ }
186
+
187
+ if (stats.repaired > 0 || stats.unreversed.size > 0) {
188
+ totals.valuesRepaired += stats.repaired;
189
+ totals.caseLossy += stats.caseLossy;
190
+ totals.unreversedCodepoints += stats.unreversed.size;
191
+ localeReport.files.push({
192
+ file: path.relative(cwd, filePath),
193
+ repaired: stats.repaired,
194
+ caseLossy: stats.caseLossy,
195
+ unreversedCodepoints: [...stats.unreversed].map(
196
+ ch => 'U+' + ch.codePointAt(0).toString(16).toUpperCase().padStart(4, '0'),
197
+ ),
198
+ samples: stats.samples,
199
+ });
200
+ }
201
+ }
202
+
203
+ if (localeReport.files.length > 0 || files.length > 0) {
204
+ report.push(localeReport);
205
+ }
206
+ }
207
+
208
+ // Unreversible PUA means the file STILL cannot render — the repair is
209
+ // incomplete and the caller must know. Write failures likewise.
210
+ const exitCode = (hadWriteFailure || totals.unreversedCodepoints > 0) ? 1 : 0;
211
+ return { locales: report, totals, exitCode, dry };
212
+ }
213
+
214
+ /**
215
+ * Print the human-readable repair report.
216
+ *
217
+ * @param {{locales: Array, totals: object, dry: boolean}} result
218
+ */
219
+ function printRepairReport(result) {
220
+ const { locales, totals, dry } = result;
221
+ const verb = dry ? 'would repair' : 'repaired';
222
+
223
+ output.raw('\n champollion repair-script — reverse unintended script conversion\n');
224
+
225
+ if (locales.length === 0) {
226
+ output.raw(' No configured locale uses a PUA script converter — nothing to scan.');
227
+ }
228
+
229
+ for (const loc of locales) {
230
+ if (loc.skipped) {
231
+ output.raw(` ── ${loc.locale} (${loc.converter}) — skipped: ${loc.skipped}`);
232
+ continue;
233
+ }
234
+ if (loc.files.length === 0) {
235
+ output.raw(` ── ${loc.locale} (${loc.converter}) — clean, no PUA found`);
236
+ continue;
237
+ }
238
+ output.raw(` ── ${loc.locale} (${loc.converter}) ─────────────────────`);
239
+ for (const f of loc.files) {
240
+ if (f.error) {
241
+ output.error(` ${f.file}: ${f.error}`);
242
+ continue;
243
+ }
244
+ const lossy = f.caseLossy > 0 ? ` (${f.caseLossy} case-lossy — reversal cannot recover capitalisation; review)` : '';
245
+ output.raw(` ${f.file}: ${verb} ${f.repaired} value(s)${lossy}`);
246
+ for (const s of f.samples) {
247
+ output.raw(` ${visualize(s.before)}`);
248
+ output.raw(` → ${visualize(s.after)}`);
249
+ }
250
+ if (f.unreversedCodepoints.length > 0) {
251
+ output.warn(` ${f.file}: ${f.unreversedCodepoints.length} PUA codepoint(s) not in this converter's table, left in place: ${f.unreversedCodepoints.join(', ')}`);
252
+ }
253
+ }
254
+ }
255
+
256
+ output.raw('');
257
+ const summary = `${totals.valuesRepaired} value(s) ${verb} across ${totals.filesScanned} file(s); ${totals.caseLossy} case-lossy; ${totals.unreversedCodepoints} unreversible codepoint(s).`;
258
+ if (totals.unreversedCodepoints > 0) output.error(` ${summary}`);
259
+ else output.raw(` ${summary}`);
260
+ if (dry && totals.valuesRepaired > 0) {
261
+ output.raw(' Dry run — nothing was written. Re-run without --dry to apply.');
262
+ }
263
+ output.raw('');
264
+ }
265
+
266
+ export { runRepairScript, printRepairReport, hasPua };