Files
collective-lists/scripts/check-locales.mjs
Oier Bravo Urtasun 39ec7996c5 feat(fase-20): eu.json seed + Paraglide config + locale parity check
Seeds apps/web/messages/eu.json with hand-translated Basque values for
every key in en.json/es.json (362 keys, $schema excluded). Each value
carries the ` [needs review]` suffix marker per plan §20.2.1 so a
native speaker can sweep through and remove markers in a post-release
pass. ICU placeholders ({name}, {count}, {n}, {version}, …) preserved
verbatim — none move position relative to the source value.

Adds `eu` to project.inlang/settings.json so the Paraglide vite plugin
emits `src/lib/paraglide/messages/eu.js` on next build, and the runtime
`availableLanguageTags` widens to ["en","es","eu"]. The compile output
is gitignored as before; verified by re-running the compile CLI locally.

New `scripts/check-locales.mjs` (dependency-free Node script) compares
every non-source bundle's key set against en.json and fails on any
missing/extra keys. Currently: eu = 362 keys, es = 362 keys, both match.

Note on translation provenance: machine/LLM seed at mid-2026 quality.
The plan calls out euskera SOV ordering risks around placeholders —
spot-checked the 12 keys with placeholders ({name}, {count}, {n},
{version}, {date}, {commit}, {word}, {title}, {days}, {lists}, {tasks},
{notes}, {section}); placeholders stayed in source-comparable positions.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 03:52:36 +02:00

91 lines
2.8 KiB
JavaScript

#!/usr/bin/env node
/**
* Locale key parity check (Fase 20.2.2).
*
* Compares the JSON message bundles under apps/web/messages/ against the
* source-of-truth bundle (`en.json`). Fails (exit 1) if any of the
* non-source locales has missing or extra keys.
*
* The `$schema` property is intentionally ignored — it's a JSON-Schema
* pointer, not a translatable message.
*
* Usage:
* node scripts/check-locales.mjs # check all
* node scripts/check-locales.mjs eu # check only eu
*
* Run via the Justfile recipe `check-locales` or `pnpm -F @colectivo/web …`
* — kept dependency-free on purpose so it works at any tooling level.
*/
import { readFileSync, readdirSync } from 'node:fs';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const MESSAGES_DIR = join(__dirname, '..', 'apps', 'web', 'messages');
const SOURCE_LOCALE = 'en';
const IGNORED_KEYS = new Set(['$schema']);
function loadLocale(name) {
const path = join(MESSAGES_DIR, `${name}.json`);
const raw = readFileSync(path, 'utf8');
const obj = JSON.parse(raw);
const keys = new Set(Object.keys(obj).filter((k) => !IGNORED_KEYS.has(k)));
return { name, path, keys, obj };
}
function diffSets(reference, candidate) {
const missing = [];
const extra = [];
for (const k of reference) {
if (!candidate.has(k)) missing.push(k);
}
for (const k of candidate) {
if (!reference.has(k)) extra.push(k);
}
return { missing, extra };
}
function main() {
const filter = process.argv.slice(2);
const allFiles = readdirSync(MESSAGES_DIR).filter((f) => f.endsWith('.json'));
const allLocales = allFiles.map((f) => f.replace(/\.json$/, ''));
if (!allLocales.includes(SOURCE_LOCALE)) {
console.error(`Source locale ${SOURCE_LOCALE}.json not found in ${MESSAGES_DIR}`);
process.exit(2);
}
const source = loadLocale(SOURCE_LOCALE);
const targets = allLocales
.filter((l) => l !== SOURCE_LOCALE)
.filter((l) => filter.length === 0 || filter.includes(l))
.map(loadLocale);
let failed = false;
for (const t of targets) {
const { missing, extra } = diffSets(source.keys, t.keys);
if (missing.length === 0 && extra.length === 0) {
console.log(`${t.name}: ${t.keys.size} keys match ${SOURCE_LOCALE}`);
continue;
}
failed = true;
console.error(`${t.name}: key drift vs ${SOURCE_LOCALE}`);
if (missing.length > 0) {
console.error(` missing (${missing.length}):`);
for (const k of missing) console.error(` - ${k}`);
}
if (extra.length > 0) {
console.error(` extra (${extra.length}):`);
for (const k of extra) console.error(` + ${k}`);
}
}
if (failed) {
process.exit(1);
}
}
main();