Les adaptateurs
Chaque projet de la flotte range ses chaînes à sa façon, et chaque source du monde a la sienne. Voici comment ce projet absorbe cette diversité sans jamais forcer un projet à changer sa forme, ce que ça coûte réellement d’ajouter une langue, une source ou un projet de plus, et ce qui fait refuser un adaptateur mal écrit.
Une source du monde
sa forme à elle
La mémoire
une source, un contexte, une langue, une valeur
Un projet de la flotte
sa forme à lui
Le problème
14 projets de la flotte portent aujourd’hui un pack de chaînes identifiable, répartis en six familles de forme distinctes, relevées projet par projet dans l’inventaire de la flotte. Trois écarts, en particulier, montrent qu’aucune convention commune ne s’impose d’elle-même.
-
Éducation financière est seule dans la flotte à écrire ses paramètres avec une double accolade, à même le corps HTML de ses pages :
<h3>Le lexique en {{n:lexique}} mots</h3>quand le reste de la flotte, tous les autres projets à sept langues compris, écrit ses paramètres avec une seule accolade,
{mot}. Un adaptateur qui ne reconnaît qu’une des deux syntaxes casse l’autre : neutraliser{{n:lexique}}comme un bloc unique perd le nomlexique; neutraliser chaque accolade séparément rend deux jetons ouverts sans valeur. -
L’Établi range ses sept langues DANS un seul fichier,
build/content/matrice.json, une matrice de compatibilité où chaque colonne porte un sous-objetlabel.fr,label.en,label.es, au lieu d’un dossier par langue. 954 chaînes, la plus grosse pièce à traduire de tout ce projet selon l’inventaire de la flotte, vivent dans ce fichier unique. Un adaptateur qui suppose systématiquement qu’un fichier vaut une langue, hypothèse qui tient pourtant pour leui.jsonde ce même projet comme pour six autres projets à sept langues de la flotte, manque cette matrice en entier. -
Clic par clic pose
build/moteur-textes.jsonhors du miroircontent/<langue>/, en français seul, avec un mécanisme de repli et de surcharge silencieux : tant qu’un blocmoteurn’existe pas dans leui.jsond’une langue, cette langue reçoit le français à sa place, et le build se contente d’avertir. Un adaptateur qui ne parcourt quecontent/<langue>/ne verra jamais les 15 chaînes de ce fichier, relevées dans l’inventaire, ni l’avertissement qui les accompagne.
Trois projets, trois écarts différents, et aucune règle commune qui les aurait prévus à l’avance. C’est ce constat, chiffré et daté, qui justifie une architecture par adaptateurs plutôt qu’une convention unique décrétée après coup : décréter une convention après coup casserait ce qui tourne déjà en production dans toute la flotte.
La réponse : tout passe par un adaptateur
Rien n’entre dans la mémoire et rien n’en ressort vers un projet autrement que par un adaptateur. Une source du monde, une terminologie publique, un corpus ouvert, la bannière officielle de l’ODERSA elle-même, entre par un ADAPTATEUR D’ENTRÉE, qui la lit dans sa forme à elle et rend une liste plate, déjà dans la convention canonique du contrat : une source, un contexte, une langue, une valeur. Un projet de la flotte se lit et se sert par un ADAPTATEUR DE PROJET, qui parle SA convention à lui : il lit ce que le projet possède déjà, tel qu’il l’a écrit, et lui rend son delta dans SA forme à lui, jamais dans une forme neutre qu’il faudrait retraduire pour l’y faire entrer.
Les deux sens, sur des exemples réels. L’adaptateur d’entrée banniere-odersa.js lit _SKILLS/odersa-design/references/banniere-langues.json, un fichier où c’est la LANGUE qui est à la racine et les clés qui sont dessous : l’inverse de la convention de tous les projets de la flotte. L’adaptateur de projet odersa-vitrine.js lit les ui.json d’ODERSA-VITRINE, qui nomme ses blocs en anglais, footer, common, help, là où le reste de la flotte les nomme en français, pied, commun, aide. Deux formes opposées, deux adaptateurs, et la mémoire au milieu ne voit jamais que des chaînes et des contextes, jamais un dialecte de fichier ou un autre.
La promesse : un adaptateur, et rien d’autre
Ajouter une langue, une source ou un projet est censé coûter UN adaptateur, jamais une refonte, et sans aucun registre à tenir à côté. Ce n’est pas une intention : c’est ce que fait réellement le générateur au moment de construire l’API.
build/tools/build-endpoint.js ne connaît par leur nom ni « avant-les-secours » ni « odersa-vitrine ». Il lit le contenu de deux dossiers, build/tools/adaptateurs/entree/ et build/tools/adaptateurs/projet/, retient tout fichier .js qui n’y commence pas par un souligné, et charge chacun. Un fichier posé dans le bon dossier est un adaptateur de plus dès le prochain build ; le retirer retire l’adaptateur de la même façon. Aucune liste à mettre à jour ailleurs.
Ce que le générateur exige en échange est vérifié à chaque build, pas simplement espéré. Un adaptateur d’entrée doit exporter id, libelle, licence, langues et une fonction lire ; un adaptateur de projet, slug, libelle, racine, id, lire et rendre. Il manque un seul de ces champs, le build refuse et nomme le champ manquant : il ne tourne jamais avec un adaptateur amputé.
Cinq des sept adaptateurs livrés partagent en plus un même outil commun, build/tools/adaptateurs/commun/ui-flotte.js, qui sait déjà lire un ui.json par langue, aplatir son arbre, exclure les blocs qui ne sont pas du texte et rendre le delta dans la bonne forme. Un sixième projet qui suivrait la même famille, un fichier par langue et les mêmes clés, n’aurait donc pas à écrire un adaptateur entier : une petite fiche déclarative d’une trentaine de lignes, comme celle d’avant-les-secours.js, suffit, et le reste vient de ce socle commun.
Ce mécanisme n’est pas seulement décrit, il tourne déjà : les 5 adaptateurs de projet lisent les chaînes de leurs voisins à chaque passage du build, et ce qu’ils rapportent, une fois recoupé, tient dans les 674 entrées de la mémoire.
La frontière entre le vérifié et la machine
La version 2 ajoute une boîte de traduction automatique dans le navigateur et un atelier en ligne de commande qui fait tourner les mêmes modèles hors navigateur. Ni l’un ni l’autre n’est une source de plus pour la mémoire : la frontière entre ce qui est vérifié et ce qui sort d’un calcul passe exactement à leur sortie. C’est une règle d’architecture, pas une prudence éditoriale.
Vérifié
Un adaptateur d’entrée ne lit jamais ce que rend la boîte. Chaque résultat qu’elle affiche porte, dans son propre balisage, l’étiquette qui le désigne pour ce qu’il est :
Machine
sortie.setAttribute("data-provenance", "machine");
sortie.setAttribute("data-chemin", m.chemin);
Rien n’écrit cette sortie dans un fichier qu’un adaptateur pourrait parcourir : il n’y a rien à lire, pas un filtre qui l’écarterait après coup.
L’atelier en ligne de commande pose la même limite autrement. Ce qu’il produit est une pré-traduction, soumise à une relecture humaine obligatoire avant publication, jamais sur le canon médical, juridique ou financier. Tant que cette relecture n’a pas eu lieu, la chaîne n’existe dans aucun ui.json de projet : elle attend, hors du miroir de langue que lit l’adaptateur de ce projet. Relue et posée par une personne à sa place normale, elle entre ensuite par ce même adaptateur, sans traitement particulier : ce n’est plus une sortie de machine, c’est une chaîne que quelqu’un a validée.
Aucun adaptateur ne fait entrer une sortie de machine dans le corpus. Ce que la boîte calcule reste dans la boîte ; ce que l’atelier dégrossit reste une pré-traduction jusqu’à validation humaine.
Écrire un adaptateur d’entrée
Un adaptateur d’entrée vit dans build/tools/adaptateurs/entree/. Exemple complet : une terminologie publique à trois langues, déjà vérifiée et licenciée.
"use strict";
/*
* Exemple : une terminologie publique, deja verifiee et licenciee,
* que l'on verse dans la memoire.
*/
module.exports = {
id: "mon-glossaire-public", // DOIT exister dans sources.json,
// sinon le build refuse et nomme
// l'id manquant.
libelle: "Un glossaire public, trois langues",
licence: "CC-BY-4.0",
langues: ["fr", "en", "es"],
niveau: "institution", // un des quatre niveaux du contrat
date: "2026-08-19", // AAAA-MM-JJ, la date du releve
// lire(contexte) rend une LISTE PLATE, deja dans la forme
// canonique attendue : rien d'autre n'est demande a l'adaptateur.
lire: function (contexte) {
return [
{ source: "Confidentialite", contexte: "pied", langue: "en",
valeur: "Privacy", date: "2026-08-19" },
{ source: "Confidentialite", contexte: "pied", langue: "es",
valeur: "Privacidad", date: "2026-08-19" }
];
}
};
L’identifiant id n’est pas décoratif. La provenance de chaque valeur le cite, et le générateur vérifie qu’il figure dans sources.json avant de déposer la moindre ligne. Un identifiant absent fait REFUSER le build entier ; il ne fait pas passer la source sous silence, et il ne la dépose pas non plus à moitié.
Écrire un adaptateur de projet
Un adaptateur de projet vit dans build/tools/adaptateurs/projet/. Exemple complet : un projet qui suit le patron le plus courant de la flotte, un fichier ui.json par langue, mêmes clés.
"use strict";
var commun = require("../commun/ui-flotte.js");
module.exports = commun.adaptateurUi({
slug: "mon-projet",
libelle: "Mon projet",
id: "odersa-mon-projet", // DOIT exister dans sources.json
racine: "MON-PROJET", // relatif a la racine de la flotte
dossier: "build/content",
langueSource: "fr",
langues: ["fr", "en", "es", "ar", "hi", "bn", "zh"],
date: "2026-08-19",
// le nom de bloc du projet vers le contexte du contrat ; ce qui
// n'est pas nomme ici prend le contexte vide, le rendu neutre.
contextes: { nav: "nav", pied: "pied", banniere: "banniere" },
// les blocs qui ne sont PAS du texte traduisible.
exclure: ["dir"]
});
lire(racineFlotte) dit ce que le projet possède déjà, dans SA convention : il relit ses ui.json, aplatit leurs clés en chemins, et rend une liste de couples cle_projet, source, contexte, langue, valeur. rendre(delta) fait l’inverse : il reçoit le delta, ce que la mémoire sait et que le projet n’a pas encore, et le rend dans la forme exacte du projet, un ui.json complet par langue, prêt à être posé.
Le point de sûreté. rendre() ne fait qu’un RETOUR de couples chemin et contenu. Il n’écrit JAMAIS de fichier lui-même, et ne touche donc jamais au dossier d’un autre projet : c’est un humain qui lit ce retour et qui pose les fichiers. Un adaptateur qui écrirait chez le voisin serait un adaptateur capable de casser ce voisin sans que personne ne l’ait vu passer : le bug d’un projet resterait dans son projet, une écriture directe l’exporterait vers treize autres. En rendant un couple chemin et contenu plutôt qu’un fichier déjà posé, chaque changement proposé à un projet de la flotte passe par un diff qu’une personne choisit d’appliquer, ou pas.
Ce qui fait refuser un adaptateur
- Un fichier illisible : si le chargement de l’adaptateur échoue, le build REFUSE et nomme le fichier fautif, il ne le retire pas en silence de la liste.
- Une forme incomplète : il manque un des champs attendus,
id,libelle,licence,langues,lirecôté entrée, ouslug,libelle,racine,id,lire,rendrecôté projet. lire()qui échoue à l’exécution : un adaptateur cassé fait refuser le build, il ne rend pas zéro entrée en silence. Un adaptateur honnêtement muet et un adaptateur cassé ne se ressemblent pas, et seul ce refus les distingue.- Une ligne sans source ni valeur utilisable, ou une langue qui n’est pas déclarée au manifeste.
- Un niveau de provenance qui n’est pas l’un des quatre du contrat, ou une date absente ou mal formée.
- Un identifiant qui ne figure pas dans
sources.json: la provenance qu’il cite n’existe nulle part, donc elle ne se vérifie pas. - Une clé
__proto__,constructorouprototypedans un objet rendu. - Une collision : deux couples source et contexte distincts qui donneraient la même empreinte.
Chaque refus nomme le fichier fautif et ce qu’il faut y écrire à la place. Jamais un simple échec silencieux, jamais un adaptateur qui tourne quand même à moitié.
Les adaptateurs réellement livrés
2 adaptateurs d’entrée et 5 adaptateurs de projet composent aujourd’hui la liste complète, celle qu’un simple ls du dossier confirmerait.
| Adaptateur | Sens | Ce qu’il lit | Licence |
|---|---|---|---|
| odersa-banniere | Entrée | La bannière de confiance officielle, sept langues (_SKILLS/odersa-design/references/banniere-langues.json) | CC BY 4.0 |
| odersa-flotte-pied | Entrée | Les cinq intitulés du pied de page, sept langues, transcrits de references/composants.md | CC BY 4.0 |
| avant-les-secours | Projet | AVANT-LES-SECOURS/build/content/<langue>/ui.json | CC BY 4.0 |
| de-plein-droit | Projet | DE-PLEIN-DROIT/build/content/<langue>/ui.json | CC BY 4.0 |
| l-etabli | Projet | L-ETABLI/build/content/<langue>/ui.json | CC BY 4.0 |
| la-bibliotheque | Projet | LA-BIBLIOTHEQUE/build/content/<langue>/ui.json | CC BY 4.0 |
| odersa-vitrine | Projet | ODERSA-VITRINE/build/content/<langue>/ui.json | CC BY 4.0 |
Les deux adaptateurs d’entrée déclarent leur licence dans leur propre code. Les cinq adaptateurs de projet n’ont pas à le refaire : ils lisent des sites déjà publiés par l’ODERSA, et c’est la licence CC BY 4.0 de la maison qui s’applique à ce qu’ils rapportent.
Écrire le prochain
Une langue de plus, une source de plus, un projet de plus qui rejoint la flotte : chacun se règle par un fichier posé au bon endroit, jamais par une refonte de ce qui existe déjà. Écrire un adaptateur, proposer une source à ajouter à sources.json, ou simplement signaler qu’un projet manque encore à l’appel : voir contribuer.