Files
ki-fwi/tools/data/check-i18n.mjs
T
OKIandClaude Opus 5 ccad7177b3 feat(session-1): socle SvelteKit et encyclopédie /zerbaj/ prérendue
SvelteKit 2 + Svelte 5 runes + TS strict, adapter-static en prérendu
intégral, trailingSlash always, paths.relative false.

Mesures de la porte de sortie
- 56 pages HTML prérendues (13 fiches × 3 langues + pages de service)
- 59 Ko de JS gzip (budget : 170) · build total 1,4 Mo (budget : 2 Mo)
- 0 chargement tiers, vérifié par grep du build
- fiche lisible sans JS : 1694 caractères de texte rendu
- npm run check : 248 fichiers, 0 erreur, 0 warning
- 0 erreur console au chargement
Lighthouse non exécuté : porte encore ouverte, à vérifier avant la S6.

Charte OKI
Tokens partagés, thème sombre par défaut, Archivo et Inter self-hébergées
(124 Ko de woff2), flag-bar à segments francs, bouton à balayage or,
gate prefers-reduced-motion unique. CSP stricte en .htaccess et _headers,
404 statique autonome, PWA avec registerSW à chemin absolu (playbook 2.9).

L'interface kréyòl n'est pas écrite par l'agent
Le catalogue gcf ne contient que les termes venant des documents du
porteur — 10 chaînes sur 132. Le reste vaut null et se replie sur le
français ; check:i18n produit la liste des 122 phrases à traduire, par
écran. <html lang> annonce la langue réellement écrite : fr tant que la
couverture reste sous 60 %, avec les fragments kréyòl marqués un par un.
Des tournures kréyòl que j'avais inventées ont été retirées.

Corrections en cours de build
- vite-plugin-pwa/sveltekit n'existe pas sans @vite-pwa/sveltekit :
  VitePWA en direct, comme le prescrivait déjà le playbook.
- @sveltejs/vite-plugin-svelte v7 n'exporte plus sveltekit : import
  depuis @sveltejs/kit/vite, vérifié sur les exports réels.
- Le sélecteur de langue fabriquait /fr/offline/, page inexistante.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 11:44:21 -04:00

122 lines
4.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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);
});