Files
OKIandClaude Opus 5 4f78321195 feat(kreyol): ouvrir la relecture et la prononciation aux locuteurs
Amendement de l'ADR 0005 : la relecture ne repose plus sur un relecteur
unique. La porte ne s'abaisse pas — l'ouverture porte sur qui peut
proposer, jamais sur qui peut valider.

La communauté existait déjà
- Lingua Libre compte 456 enregistrements en kréyòl gwadloupéyen, versés
  sur Commons sous licence libre.
- Trois de nos treize noms y figuraient déjà — gouyav, kénèt, zikak — en
  CC BY-SA 4.0 et AVEC EXACTEMENT NOTRE GRAPHIE. Trois entrées sortent du
  doute par corroboration externe, sans que le projet décide quoi que ce
  soit. La question n'était pas qui trancherait, mais d'aller regarder.
- En regard : 0 lexème Wikidata en gcf, 0 étiquette gcf sur les 13 items.

Modèle de données
- orthographe.statut : a_relire | en_discussion | relu. Un doute signalé
  sans question posée est refusé par le validateur — on ne décourage pas
  le lecteur, on l'appelle. 4 entrées en discussion : ake, medisinye,
  lorye-jonn, pom-dlo.
- noms.variantes_gcf : les usages proposés sont conservés avec leur source
  et leur île, jamais écrasés. Une graphie identique au nom principal est
  refusée comme variante : c'est une corroboration, pas un désaccord.
- audio : source, licence, url_description, locuteur crédité, valide.
  check:audio interroge Lingua Libre et trouve seul les prises existantes.

Porte : 14/14 (5 cas ajoutés sur la langue et la prononciation).

Pis-aller assumé : les 4 questions sont rédigées en français. Les écrire
en kréyòl reviendrait à produire du kréyòl non relu au moment même où l'on
reconnaît ne pas savoir le faire. Leur traduction est la première
contribution demandée.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 10:40:23 -04:00

232 lines
8.0 KiB
JavaScript
Raw Permalink 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-audio.mjs — état des prononciations kréyòl.
*
* Le brief §6 le demande : « prévois le contrat (audio.gcf, format .opus, repli silencieux
* si le fichier manque) et un script de vérification listant les enregistrements manquants ».
*
* Ce script fait deux choses :
* 1. il liste ce qui manque localement ;
* 2. il cherche sur **Lingua Libre** si quelqu'un a déjà enregistré le mot.
*
* Le second point est le cœur de la démarche : Lingua Libre est l'atelier d'enregistrement
* de Wikimedia, et ses prises sont versées sur Commons sous licence libre. Un nom enregistré
* là-bas sert à ce jeu, mais aussi au Wiktionnaire, aux lexiques, aux synthèses vocales
* futures — bref, à la langue, pas seulement à nous.
*
* Pas de synthèse vocale : ce script n'en fabrique aucune, et le schéma n'en accepte pas.
* Ce script n'échoue jamais : un enregistrement manquant est un travail à faire.
*
* Sortie : docs/audio-a-enregistrer.md
*/
import { readFile, readdir, writeFile, access } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
const DATA = join(ROOT, 'src', 'lib', 'data', 'fruits');
const STATIC = join(ROOT, 'static');
const UA = 'ki-fwi-dataset/0.1 (https://o-k-i.net ; projet educatif ORGANISATION KA INTERNATIONALE)';
/** Élément Wikidata du kréyòl gwadloupéyen — sert de code de langue à Lingua Libre. */
const Q_GWADLOUPEYEN = 'Q3006280';
const CATEGORIE_LL = 'Lingua Libre pronunciation-gcf';
const pause = (ms) => new Promise((r) => setTimeout(r, ms));
async function existe(p) {
try {
await access(p);
return true;
} catch {
return false;
}
}
async function commons(params) {
const u = new URL('https://commons.wikimedia.org/w/api.php');
for (const [k, v] of Object.entries({ format: 'json', formatversion: '2', ...params })) u.searchParams.set(k, v);
const res = await fetch(u, { headers: { 'User-Agent': UA, Accept: 'application/json' } });
if (!res.ok) throw new Error(`Commons ${res.status}`);
return res.json();
}
const sansBalises = (s) =>
s
? String(s)
.replace(/<[^>]+>/g, ' ')
.replace(/\s+/g, ' ')
.trim()
: null;
/** Cherche un enregistrement du mot dans la catégorie Lingua Libre du kréyòl gwadloupéyen. */
async function chercherLinguaLibre(mot) {
const j = await commons({
action: 'query',
list: 'search',
srnamespace: '6',
srlimit: '3',
srsearch: `incategory:"${CATEGORIE_LL}" ${mot}`
});
const trouves = j.query?.search ?? [];
if (!trouves.length) return null;
const detail = await commons({
action: 'query',
prop: 'imageinfo',
iiprop: 'url|mime|extmetadata',
iiextmetadatafilter: 'LicenseShortName|Artist',
titles: trouves[0].title
});
const p = detail.query?.pages?.[0];
const i = p?.imageinfo?.[0];
return {
titre: p?.title ?? trouves[0].title,
licence: sansBalises(i?.extmetadata?.LicenseShortName?.value),
auteur: sansBalises(i?.extmetadata?.Artist?.value),
url_fichier: i?.url ?? null,
url_description: i?.descriptionurl ?? null,
autres: trouves.length - 1
};
}
/** Combien d'enregistrements existent en tout dans cette langue ? Mesure l'état de la communauté. */
async function tailleCategorie() {
const j = await commons({ action: 'query', prop: 'categoryinfo', titles: `Category:${CATEGORIE_LL}` });
return j.query?.pages?.[0]?.categoryinfo?.files ?? 0;
}
async function main() {
const horsLigne = process.argv.includes('--hors-ligne');
const fichiers = (await readdir(DATA)).filter((f) => f.endsWith('.json')).sort();
const manquants = [];
const absents = [];
const presents = [];
const dejaEnregistres = [];
let totalLL = 0;
if (!horsLigne) {
try {
totalLL = await tailleCategorie();
} catch {
console.warn('⚠ Lingua Libre injoignable — rapport local uniquement.');
}
}
for (const f of fichiers) {
const e = JSON.parse(await readFile(join(DATA, f), 'utf8'));
if (e.audio.gcf) {
if (await existe(join(STATIC, e.audio.gcf))) presents.push(e);
else absents.push(e);
} else {
manquants.push(e);
}
if (!horsLigne && !e.audio.gcf) {
try {
process.stdout.write(`→ Lingua Libre : ${e.noms.gcf}… `);
const trouve = await chercherLinguaLibre(e.noms.gcf);
console.log(trouve ? '✔ déjà enregistré' : '—');
if (trouve) dejaEnregistres.push({ e, trouve });
await pause(400);
} catch {
console.log('(échec de la requête)');
}
}
}
const l = [
'# Prononciations kréyòl — état et travail à faire',
'',
'> Généré par `npm run check:audio`. **Aucune synthèse vocale** : ces noms doivent être',
'> prononcés par une voix humaine locale (brief §6). Le locuteur conserve ses droits',
'> voisins et est crédité nominativement.',
'',
'Format embarqué : `.opus`, un fichier par entrée dans `static/audio/<id>.opus`.',
''
];
if (totalLL) {
l.push(
'## Il existe déjà une communauté',
'',
`**${totalLL} enregistrements en kréyòl gwadloupéyen** sont déjà publiés sur`,
`[Lingua Libre](https://lingualibre.org), l'atelier d'enregistrement de Wikimedia, et versés`,
`sur Commons sous licence libre (langue \`gcf\`, [${Q_GWADLOUPEYEN}](https://www.wikidata.org/wiki/${Q_GWADLOUPEYEN})).`,
'',
'Enregistrer là-bas plutôt que dans notre coin, cest faire servir sa voix au',
'Wiktionnaire, aux lexiques et à tous les projets à venir — pas seulement à ce jeu.',
''
);
}
if (dejaEnregistres.length) {
l.push(
`## ✔ Déjà enregistrés par la communauté (${dejaEnregistres.length})`,
'',
'Ces noms nont pas besoin d’être réenregistrés : la prise existe, sous licence libre.',
'Il reste à vérifier quil sagit bien du fruit, puis à renseigner le bloc `audio`.',
'',
'**Cest aussi une corroboration de notre graphie** : le nom enregistré porte',
'lorthographe employée par le locuteur.',
'',
'| entrée | notre graphie | enregistrement | licence | locuteur |',
'|---|---|---|---|---|'
);
for (const { e, trouve } of dejaEnregistres) {
const t = trouve.titre.replace('File:', '').replace(/\|/g, '\\|');
l.push(
`| \`${e.id}\` | ${e.noms.gcf} | [${t.slice(0, 52)}](${trouve.url_description}) | ${trouve.licence ?? '?'} | ${(trouve.auteur ?? '?').slice(0, 40)} |`
);
}
l.push('');
}
const restants = manquants.filter((e) => !dejaEnregistres.some((d) => d.e.id === e.id));
l.push(`## 🎙️ À enregistrer (${restants.length})`, '');
if (!restants.length) l.push('_Rien à enregistrer._', '');
for (const e of restants) {
const q = e.orthographe.question_ouverte;
l.push(`- [ ] **${e.noms.gcf}** — \`static/audio/${e.id}.opus\` _(${e.noms.fr ?? ''})_`);
if (q) l.push(` ⚠ Orthographe en discussion : ${q}`);
}
l.push('');
if (absents.length) {
l.push(`## ⚠ Déclarés mais fichier introuvable (${absents.length})`, '');
for (const e of absents) l.push(`- \`${e.id}\` → \`${e.audio.gcf}\` absent de \`static/\``);
l.push('');
}
if (presents.length) {
l.push(`## En place (${presents.length})`, '');
for (const e of presents)
l.push(`- \`${e.id}\` — ${e.audio.locuteur ?? 'locuteur non renseigné'} (${e.audio.licence ?? 'licence ?'})`);
l.push('');
}
l.push(
'## Comment enregistrer',
'',
'Marche à suivre détaillée dans [`kreyol-participatif.md`](kreyol-participatif.md).',
'En deux lignes : une pièce calme, un téléphone, le mot dit deux fois, naturellement.',
'Une voix locale vaut mieux quun studio.',
''
);
await writeFile(join(ROOT, 'docs', 'audio-a-enregistrer.md'), l.join('\n'));
console.log(`\nDéjà enregistrés par la communauté : ${dejaEnregistres.length}`);
console.log(`À enregistrer : ${restants.length}`);
console.log(`Introuvables : ${absents.length}`);
console.log(`En place : ${presents.length}`);
console.log('\n docs/audio-a-enregistrer.md');
}
main().catch((e) => {
console.error('✘', e.stack ?? e.message);
process.exit(1);
});