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.
- package/LICENSE +133 -0
- package/README.md +387 -0
- package/bin/cli.js +278 -0
- package/index.js +135 -0
- package/lib/api-key.js +127 -0
- package/lib/autofix.js +432 -0
- package/lib/bridge/method_bridge.py +430 -0
- package/lib/card-source-resolution.mjs +284 -0
- package/lib/cards/cache.js +169 -0
- package/lib/cards/env.js +82 -0
- package/lib/cards/fetch-card-child.js +38 -0
- package/lib/cards/reader.js +435 -0
- package/lib/cards/refresh.js +111 -0
- package/lib/cards/remote.js +387 -0
- package/lib/cldf-export.mjs +540 -0
- package/lib/cldf-terms.mjs +62 -0
- package/lib/command-help.js +790 -0
- package/lib/commands/audit.js +49 -0
- package/lib/commands/card.js +454 -0
- package/lib/commands/doctor.js +559 -0
- package/lib/commands/fonts.js +489 -0
- package/lib/commands/help.js +91 -0
- package/lib/commands/init.js +1259 -0
- package/lib/commands/integrity.js +148 -0
- package/lib/commands/leaderboard.js +478 -0
- package/lib/commands/lint.js +30 -0
- package/lib/commands/models.js +177 -0
- package/lib/commands/plugin.js +103 -0
- package/lib/commands/provenance.js +45 -0
- package/lib/commands/recommend.js +75 -0
- package/lib/commands/register-corpus.js +678 -0
- package/lib/commands/repair-script.js +42 -0
- package/lib/commands/seal-corpus.js +355 -0
- package/lib/commands/seo.js +72 -0
- package/lib/commands/serve.js +147 -0
- package/lib/commands/status.js +265 -0
- package/lib/commands/submit.js +332 -0
- package/lib/commands/sync.js +89 -0
- package/lib/commands/tm.js +573 -0
- package/lib/commands/verify.js +39 -0
- package/lib/commands/watch.js +20 -0
- package/lib/commands/wrap.js +138 -0
- package/lib/commands/xliff.js +327 -0
- package/lib/commercial-eligibility.js +235 -0
- package/lib/concurrent.js +87 -0
- package/lib/config.js +523 -0
- package/lib/contamination-lane.js +76 -0
- package/lib/content-sync.js +731 -0
- package/lib/content.js +733 -0
- package/lib/corpus-registration.mjs +608 -0
- package/lib/cost-report.js +346 -0
- package/lib/diff.js +155 -0
- package/lib/docusaurus-sync.js +1256 -0
- package/lib/flatten.js +55 -0
- package/lib/format.js +954 -0
- package/lib/hash.js +159 -0
- package/lib/icu.js +473 -0
- package/lib/integrity.js +689 -0
- package/lib/license-gate.mjs +478 -0
- package/lib/license-identify.mjs +229 -0
- package/lib/lint.js +629 -0
- package/lib/method-manifest.js +60 -0
- package/lib/methods/anthropic.js +140 -0
- package/lib/methods/apertium.js +163 -0
- package/lib/methods/api.js +316 -0
- package/lib/methods/base.js +184 -0
- package/lib/methods/content-separator.js +45 -0
- package/lib/methods/deepl.js +426 -0
- package/lib/methods/direct-llm.js +586 -0
- package/lib/methods/external.js +332 -0
- package/lib/methods/fetch-with-retry.js +124 -0
- package/lib/methods/gemini.js +147 -0
- package/lib/methods/google-translate.js +402 -0
- package/lib/methods/http-utils.js +122 -0
- package/lib/methods/libretranslate.js +314 -0
- package/lib/methods/llm-coached.js +670 -0
- package/lib/methods/llm.js +592 -0
- package/lib/methods/local.js +76 -0
- package/lib/methods/microsoft-translator.js +331 -0
- package/lib/methods/openai.js +131 -0
- package/lib/methods/openrouter-client.js +327 -0
- package/lib/methods/openrouter-pricing.js +156 -0
- package/lib/methods/provider-env.js +115 -0
- package/lib/methods/provider-pricing.js +310 -0
- package/lib/methods/tilde.js +150 -0
- package/lib/methods/translated.js +229 -0
- package/lib/methods/translation-error.js +80 -0
- package/lib/models.js +258 -0
- package/lib/no-translate.js +233 -0
- package/lib/output.js +238 -0
- package/lib/pairs.js +547 -0
- package/lib/plugins.js +447 -0
- package/lib/provenance.js +323 -0
- package/lib/recommend.js +648 -0
- package/lib/registers.js +1185 -0
- package/lib/repair-script.js +266 -0
- package/lib/scripts.js +994 -0
- package/lib/seal.mjs +464 -0
- package/lib/sealed-qualifier.mjs +211 -0
- package/lib/security.js +59 -0
- package/lib/segment.js +369 -0
- package/lib/seo.js +275 -0
- package/lib/serve.js +854 -0
- package/lib/string-classify.js +85 -0
- package/lib/submit.mjs +344 -0
- package/lib/sync.js +969 -0
- package/lib/tags/bcp47.js +202 -0
- package/lib/tags/resolve.js +314 -0
- package/lib/terminology.js +111 -0
- package/lib/tm-seed.js +294 -0
- package/lib/tm.js +515 -0
- package/lib/translate-pair.js +197 -0
- package/lib/translate.js +203 -0
- package/lib/types.js +230 -0
- package/lib/validate.js +510 -0
- package/lib/verify.js +451 -0
- package/lib/watch.js +145 -0
- package/lib/xliff.js +184 -0
- package/package.json +93 -0
- package/shared/ATTRIBUTION.md +145 -0
- package/shared/CORPORA-CARDS.md +288 -0
- package/shared/DATA-SOVEREIGNTY.md +500 -0
- package/shared/LANGUAGE-CARD-FIELDS.md +532 -0
- package/shared/card-lint-baseline.json +3189 -0
- package/shared/cards-fallback.json +1 -0
- package/shared/catalogue/card-config.json +6091 -0
- package/shared/catalogue/external-results.json +3888 -0
- package/shared/catalogue/gender-guidance.json +1038 -0
- package/shared/catalogue/method-coverage.json +1751 -0
- package/shared/catalogue/metric-coverage.json +170 -0
- package/shared/catalogue/metric-reliability.json +1 -0
- package/shared/catalogue/register-presets.json +3180 -0
- package/shared/catalogue/vitality-scales.json +55 -0
- package/shared/cldr-index.json +1115 -0
- package/shared/code-bridge.json +253 -0
- package/shared/corpora-cards-v1-reference.md +281 -0
- package/shared/curated-dictionary-flags.json +35 -0
- package/shared/curated-endonyms.json +35 -0
- package/shared/curated-fsts.json +51 -0
- package/shared/curated-orthography-conventions.json +26 -0
- package/shared/curated-sil-resources.json +374 -0
- package/shared/curated-tools.json +41 -0
- package/shared/docent/corpus.json +11333 -0
- package/shared/docent/faq.en.json +564 -0
- package/shared/docent/register-blocks.json +60 -0
- package/shared/docent/system-prompt.md +144 -0
- package/shared/domain-taxonomy.json +35 -0
- package/shared/explainers/glossary.json +2975 -0
- package/shared/explainers/tc-features.json +20112 -0
- package/shared/explainers/term-watchlist.json +147 -0
- package/shared/human-services.json +59 -0
- package/shared/license-corrections.json +261 -0
- package/shared/license-evidence.json +13452 -0
- package/shared/licenses.json +6781 -0
- package/shared/method-registry.json +236 -0
- package/shared/metric-registry.json +620 -0
- package/shared/model-aliases.json +7 -0
- package/shared/schemas/champollion-plugin.schema.json +206 -0
- package/shared/schemas/corpora-card.schema.json +957 -0
- package/shared/schemas/domain-taxonomy.schema.json +64 -0
- package/shared/schemas/external-results.schema.json +314 -0
- package/shared/schemas/human-services.schema.json +90 -0
- package/shared/schemas/language-card.schema.json +1308 -0
- package/shared/schemas/licenses.schema.json +155 -0
- package/shared/schemas/method-card.schema.json +412 -0
- package/shared/schemas/method-registry.schema.json +85 -0
- package/shared/schemas/metric-registry.schema.json +96 -0
- package/shared/schemas/metric-reliability.schema.json +178 -0
- package/shared/schemas/model-aliases.schema.json +27 -0
- package/shared/schemas/source-snapshot.schema.json +96 -0
package/lib/seo.js
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SEO generators — deterministic, no LLM calls.
|
|
3
|
+
*
|
|
4
|
+
* Generates three types of SEO artifacts from the pair graph:
|
|
5
|
+
*
|
|
6
|
+
* 1. HREFLANG TAGS: HTML <link rel="alternate" hreflang="..."> elements
|
|
7
|
+
* for every page × locale combination. Includes x-default.
|
|
8
|
+
*
|
|
9
|
+
* 2. MULTILINGUAL SITEMAP: XML sitemap with <xhtml:link> alternates,
|
|
10
|
+
* following Google's multilingual sitemap spec.
|
|
11
|
+
*
|
|
12
|
+
* 3. JSON-LD SCHEMA: schema.org WebSite + SearchAction with
|
|
13
|
+
* @language annotations and workTranslation links.
|
|
14
|
+
*
|
|
15
|
+
* All output is deterministic: same config → same output, byte-for-byte.
|
|
16
|
+
* This means SEO files can be committed to git and diffed reliably.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import { parsePairKey } from './pairs.js';
|
|
21
|
+
|
|
22
|
+
// -----------------------------------------------------------------
|
|
23
|
+
// URL helpers
|
|
24
|
+
// -----------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Build a localized URL from a base URL, URL pattern, and locale.
|
|
28
|
+
*
|
|
29
|
+
* Supported patterns:
|
|
30
|
+
* - /:locale/:path → https://example.com/fr/about
|
|
31
|
+
* - /:path/:locale → https://example.com/about/fr
|
|
32
|
+
* - subdomain → https://fr.example.com/about
|
|
33
|
+
*
|
|
34
|
+
* @param {string} baseUrl - Site base URL (no trailing slash)
|
|
35
|
+
* @param {string} urlPattern - One of '/:locale/:path', '/:path/:locale', 'subdomain'
|
|
36
|
+
* @param {string} locale - Language code
|
|
37
|
+
* @param {string} pagePath - Page path (e.g., '/about')
|
|
38
|
+
* @returns {string} Full localized URL
|
|
39
|
+
*/
|
|
40
|
+
function buildLocalizedUrl(baseUrl, urlPattern, locale, pagePath) {
|
|
41
|
+
// Normalize: remove trailing slashes from baseUrl
|
|
42
|
+
const base = baseUrl.replace(/\/+$/, '');
|
|
43
|
+
// Normalize: ensure pagePath starts with /
|
|
44
|
+
const normalizedPath = pagePath.startsWith('/') ? pagePath : `/${pagePath}`;
|
|
45
|
+
// Root path special case
|
|
46
|
+
const cleanPath = normalizedPath === '/' ? '' : normalizedPath;
|
|
47
|
+
|
|
48
|
+
if (urlPattern === 'subdomain') {
|
|
49
|
+
// https://example.com → https://fr.example.com/about
|
|
50
|
+
const url = new URL(base);
|
|
51
|
+
url.hostname = `${locale}.${url.hostname}`;
|
|
52
|
+
return `${url.origin}${cleanPath}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (urlPattern === '/:path/:locale') {
|
|
56
|
+
// https://example.com/about/fr
|
|
57
|
+
return `${base}${cleanPath}/${locale}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Default: /:locale/:path
|
|
61
|
+
return `${base}/${locale}${cleanPath}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// -----------------------------------------------------------------
|
|
65
|
+
// Hreflang tags
|
|
66
|
+
// -----------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Generate hreflang <link> tags for a single page.
|
|
70
|
+
*
|
|
71
|
+
* @param {object} options
|
|
72
|
+
* @param {string} options.baseUrl - Site base URL
|
|
73
|
+
* @param {string} options.urlPattern - URL localization pattern
|
|
74
|
+
* @param {string} options.inputLocale - Source locale code
|
|
75
|
+
* @param {string[]} options.locales - All locale codes (including source)
|
|
76
|
+
* @param {string} options.pagePath - Page path
|
|
77
|
+
* @returns {string} HTML link tags (one per line)
|
|
78
|
+
*/
|
|
79
|
+
function generateHreflangTags({ baseUrl, urlPattern, inputLocale, locales, pagePath }) {
|
|
80
|
+
const allLocales = [...new Set([inputLocale, ...locales])];
|
|
81
|
+
const lines = [];
|
|
82
|
+
|
|
83
|
+
// Generate one <link> per locale
|
|
84
|
+
for (const locale of allLocales.sort()) {
|
|
85
|
+
const url = buildLocalizedUrl(baseUrl, urlPattern, locale, pagePath);
|
|
86
|
+
lines.push(`<link rel="alternate" hreflang="${locale}" href="${url}" />`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// x-default points to the input (source) locale
|
|
90
|
+
const defaultUrl = buildLocalizedUrl(baseUrl, urlPattern, inputLocale, pagePath);
|
|
91
|
+
lines.push(`<link rel="alternate" hreflang="x-default" href="${defaultUrl}" />`);
|
|
92
|
+
|
|
93
|
+
return lines.join('\n');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Generate hreflang tags for all configured pages.
|
|
98
|
+
*
|
|
99
|
+
* @param {import('./types.js').ChampollionConfig} config - Resolved config
|
|
100
|
+
* @returns {string} Full hreflang output (one block per page)
|
|
101
|
+
*/
|
|
102
|
+
function generateAllHreflangTags(config) {
|
|
103
|
+
const seo = config.seo || {};
|
|
104
|
+
const baseUrl = seo.baseUrl || config.baseUrl;
|
|
105
|
+
const urlPattern = seo.urlPattern || '/:locale/:path';
|
|
106
|
+
const pages = seo.pages || ['/'];
|
|
107
|
+
const inputLocale = config.inputLocale;
|
|
108
|
+
|
|
109
|
+
// Collect all target locales from config
|
|
110
|
+
const locales = [];
|
|
111
|
+
if (config.languages && Array.isArray(config.languages)) {
|
|
112
|
+
locales.push(...config.languages);
|
|
113
|
+
}
|
|
114
|
+
if (config.pairs) {
|
|
115
|
+
for (const [pairKey] of Object.entries(config.pairs)) {
|
|
116
|
+
const { target } = parsePairKey(pairKey);
|
|
117
|
+
if (target) locales.push(target);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!baseUrl) {
|
|
122
|
+
return '<!-- champollion: Set "baseUrl" in config to generate hreflang tags -->';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const blocks = [];
|
|
126
|
+
for (const pagePath of pages) {
|
|
127
|
+
blocks.push(`<!-- Page: ${pagePath} -->`);
|
|
128
|
+
blocks.push(generateHreflangTags({
|
|
129
|
+
baseUrl,
|
|
130
|
+
urlPattern,
|
|
131
|
+
inputLocale,
|
|
132
|
+
locales: [...new Set(locales)],
|
|
133
|
+
pagePath,
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return blocks.join('\n\n');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// -----------------------------------------------------------------
|
|
141
|
+
// Sitemap XML
|
|
142
|
+
// -----------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Generate a multilingual sitemap.xml with xhtml:link alternates.
|
|
146
|
+
*
|
|
147
|
+
* Follows Google's spec for multilingual sitemaps:
|
|
148
|
+
* https://developers.google.com/search/docs/advanced/crawling/localized-versions
|
|
149
|
+
*
|
|
150
|
+
* @param {import('./types.js').ChampollionConfig} config - Resolved config
|
|
151
|
+
* @returns {string} Complete XML sitemap
|
|
152
|
+
*/
|
|
153
|
+
function generateSitemap(config) {
|
|
154
|
+
const seo = config.seo || {};
|
|
155
|
+
const baseUrl = seo.baseUrl || config.baseUrl;
|
|
156
|
+
const urlPattern = seo.urlPattern || '/:locale/:path';
|
|
157
|
+
const pages = seo.pages || ['/'];
|
|
158
|
+
const inputLocale = config.inputLocale;
|
|
159
|
+
|
|
160
|
+
if (!baseUrl) {
|
|
161
|
+
return '<!-- champollion: Set "baseUrl" in config to generate sitemap -->';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Collect all locales
|
|
165
|
+
const targetLocales = [];
|
|
166
|
+
if (config.languages && Array.isArray(config.languages)) {
|
|
167
|
+
targetLocales.push(...config.languages);
|
|
168
|
+
}
|
|
169
|
+
if (config.pairs) {
|
|
170
|
+
for (const [pairKey] of Object.entries(config.pairs)) {
|
|
171
|
+
const { target } = parsePairKey(pairKey);
|
|
172
|
+
if (target) targetLocales.push(target);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const allLocales = [...new Set([inputLocale, ...targetLocales])].sort();
|
|
176
|
+
|
|
177
|
+
const lines = [];
|
|
178
|
+
lines.push('<?xml version="1.0" encoding="UTF-8"?>');
|
|
179
|
+
lines.push('<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"');
|
|
180
|
+
lines.push(' xmlns:xhtml="http://www.w3.org/1999/xhtml">');
|
|
181
|
+
|
|
182
|
+
for (const pagePath of pages) {
|
|
183
|
+
for (const locale of allLocales) {
|
|
184
|
+
const url = buildLocalizedUrl(baseUrl, urlPattern, locale, pagePath);
|
|
185
|
+
lines.push(' <url>');
|
|
186
|
+
lines.push(` <loc>${escapeXml(url)}</loc>`);
|
|
187
|
+
|
|
188
|
+
// Add xhtml:link alternates for all other locales
|
|
189
|
+
for (const altLocale of allLocales) {
|
|
190
|
+
const altUrl = buildLocalizedUrl(baseUrl, urlPattern, altLocale, pagePath);
|
|
191
|
+
lines.push(` <xhtml:link rel="alternate" hreflang="${altLocale}" href="${escapeXml(altUrl)}" />`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// x-default
|
|
195
|
+
const defaultUrl = buildLocalizedUrl(baseUrl, urlPattern, inputLocale, pagePath);
|
|
196
|
+
lines.push(` <xhtml:link rel="alternate" hreflang="x-default" href="${escapeXml(defaultUrl)}" />`);
|
|
197
|
+
|
|
198
|
+
lines.push(' </url>');
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
lines.push('</urlset>');
|
|
203
|
+
return lines.join('\n');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// -----------------------------------------------------------------
|
|
207
|
+
// JSON-LD Schema
|
|
208
|
+
// -----------------------------------------------------------------
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Generate JSON-LD schema.org WebSite markup with language annotations.
|
|
212
|
+
*
|
|
213
|
+
* @param {object} config - Resolved config
|
|
214
|
+
* @returns {string} JSON-LD script block
|
|
215
|
+
*/
|
|
216
|
+
function generateJsonLd(config) {
|
|
217
|
+
const seo = config.seo || {};
|
|
218
|
+
const baseUrl = seo.baseUrl || config.baseUrl;
|
|
219
|
+
const inputLocale = config.inputLocale;
|
|
220
|
+
|
|
221
|
+
if (!baseUrl) {
|
|
222
|
+
return '<!-- champollion: Set "baseUrl" in config to generate JSON-LD -->';
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Collect all locales
|
|
226
|
+
const targetLocales = [];
|
|
227
|
+
if (config.languages && Array.isArray(config.languages)) {
|
|
228
|
+
targetLocales.push(...config.languages);
|
|
229
|
+
}
|
|
230
|
+
if (config.pairs) {
|
|
231
|
+
for (const [pairKey] of Object.entries(config.pairs)) {
|
|
232
|
+
const { target } = parsePairKey(pairKey);
|
|
233
|
+
if (target) targetLocales.push(target);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const schema = {
|
|
238
|
+
'@context': 'https://schema.org',
|
|
239
|
+
'@type': 'WebSite',
|
|
240
|
+
'url': baseUrl,
|
|
241
|
+
'inLanguage': inputLocale,
|
|
242
|
+
'availableLanguage': [...new Set(targetLocales)].sort(),
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const scriptTag = `<script type="application/ld+json">\n${JSON.stringify(schema, null, 2)}\n</script>`;
|
|
246
|
+
return scriptTag;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// -----------------------------------------------------------------
|
|
250
|
+
// XML helpers
|
|
251
|
+
// -----------------------------------------------------------------
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Escape special characters for XML output.
|
|
255
|
+
*
|
|
256
|
+
* @param {string} str - Raw string
|
|
257
|
+
* @returns {string} XML-safe escaped string
|
|
258
|
+
*/
|
|
259
|
+
function escapeXml(str) {
|
|
260
|
+
return str
|
|
261
|
+
.replace(/&/g, '&')
|
|
262
|
+
.replace(/</g, '<')
|
|
263
|
+
.replace(/>/g, '>')
|
|
264
|
+
.replace(/"/g, '"')
|
|
265
|
+
.replace(/'/g, ''');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export {
|
|
269
|
+
buildLocalizedUrl,
|
|
270
|
+
generateHreflangTags,
|
|
271
|
+
generateAllHreflangTags,
|
|
272
|
+
generateSitemap,
|
|
273
|
+
generateJsonLd,
|
|
274
|
+
escapeXml,
|
|
275
|
+
};
|