Files
ki-fwi/tools/data/check-i18n.mjs
T

122 lines
4.1 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
/**
* check-i18n.mjs — inventaire des chaînes d'interface non traduites.
*
* Le kréyòl est la langue principale du projet, et l'interface est écrite en français.
* C'est une contradiction assumée : aucun agent de code n'écrit de kréyòl (ADR 0005),
* et un catalogue rempli par un automate serait pire que vide.
*
* Ce n'est donc pas une dette technique mais une tâche de participation : ce script
* produit la liste, phrase par phrase, avec la version française à traduire.
*
* Sortie : docs/i18n-a-traduire.md
*/
import { readFile, writeFile } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
const I18N = join(ROOT, 'src', 'lib', 'i18n');
const LOCALES = ['gcf', 'en'];
/** Regroupe les clés par préfixe pour que la traduction se fasse par écran, pas en vrac. */
const SECTIONS = {
site: 'Identité du site',
nav: 'Navigation',
home: 'Accueil',
zerbaj: 'Encyclopédie',
fiche: 'Fiche despèce',
manques: 'Ce qui manque',
kreyol: 'Questions de langue',
kontribiye: 'Participation',
jeu: 'Le jeu',
patouche: 'Module « Pa touché »',
footer: 'Pied de page',
credits: 'Crédits',
erreur: 'Erreurs',
offline: 'Hors ligne',
i18n: 'Bandeau de traduction'
};
async function main() {
const fr = JSON.parse(await readFile(join(I18N, 'fr.json'), 'utf8'));
const cles = Object.keys(fr).filter((c) => !c.startsWith('$'));
const l = [
'# Interface à traduire',
'',
`> Généré par \`npm run check:i18n\` le ${new Date().toISOString().slice(0, 10)}.`,
'> Sortie de script : ne pas éditer à la main.',
'',
'Le kréyòl est la langue principale du projet, et pourtant ce site affiche du français.',
'Ce nest pas un oubli : **aucun agent de code n’écrit de kréyòl** (ADR 0005), et un',
'catalogue rempli par une machine serait pire que vide — il figerait des tournures que',
'personne nemploie.',
'',
'La traduction est donc une contribution, au même titre quune photographie ou un',
'enregistrement. Marche à suivre : [`kreyol-participatif.md`](kreyol-participatif.md).',
''
];
const resume = [];
for (const locale of LOCALES) {
const cat = JSON.parse(await readFile(join(I18N, `${locale}.json`), 'utf8'));
const manquantes = cles.filter((c) => !cat[c]);
const couverture = Math.round(((cles.length - manquantes.length) / cles.length) * 100);
resume.push({ locale, manquantes: manquantes.length, total: cles.length, couverture });
l.push(`## ${locale === 'gcf' ? 'Kréyòl gwadloupéyen' : 'English'}${couverture} % traduit`, '');
if (!manquantes.length) {
l.push('_Rien à traduire._', '');
continue;
}
l.push(`**${manquantes.length} phrases sur ${cles.length}.**`, '');
const parSection = new Map();
for (const c of manquantes) {
const prefixe = c.split('.')[0];
if (!parSection.has(prefixe)) parSection.set(prefixe, []);
parSection.get(prefixe).push(c);
}
for (const [prefixe, liste] of parSection) {
l.push(`### ${SECTIONS[prefixe] ?? prefixe} (${liste.length})`, '');
l.push('| clé | français | traduction |', '|---|---|---|');
for (const c of liste) {
const source = String(fr[c]).replace(/\|/g, '\\|');
l.push(`| \`${c}\` | ${source} | |`);
}
l.push('');
}
}
l.push(
'## Comment rendre une traduction',
'',
'Remplir la colonne « traduction », ou envoyer la liste par tout moyen — un message,',
'un fichier, une note vocale. La forme na aucune importance ; la relecture par un',
'locuteur en a.',
'',
'Les valeurs traduites se reportent dans `src/lib/i18n/gcf.json`. Une valeur `null`',
'se replie automatiquement sur le français, avec lattribut `lang` correct : rien ne',
'casse tant que la traduction manque.',
''
);
await writeFile(join(ROOT, 'docs', 'i18n-a-traduire.md'), l.join('\n'));
for (const r of resume) {
console.log(`${r.locale} : ${r.total - r.manquantes}/${r.total} traduites (${r.couverture} %)`);
}
console.log('\n docs/i18n-a-traduire.md');
}
main().catch((e) => {
console.error('✘', e.stack ?? e.message);
process.exit(1);
});