122 lines
4.1 KiB
JavaScript
122 lines
4.1 KiB
JavaScript
#!/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 d’espè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 n’est 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 n’emploie.',
|
|||
|
|
'',
|
|||
|
|
'La traduction est donc une contribution, au même titre qu’une 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 n’a 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 l’attribut `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);
|
|||
|
|
});
|