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>
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);
|
||
});
|