#!/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); });