L’API
Une API de traduction doit traduire. Celle-ci s’importe et s’appelle, dans un navigateur comme en ligne de commande, et elle rend le texte traduit. Elle n’est pas une porte où nous recevrions vos phrases : le moteur et les modèles descendent chez vous comme de simples fichiers, et le processeur qui travaille est le vôtre. C’est la seule raison pour laquelle elle est illimitée et gratuite, et cette raison est mécanique, pas commerciale.
- Sans compte
- Sans clé
- Sans quota
- Le calcul chez l’appelant
- Appelable depuis n’importe quel domaine
Les deux appels
Ce qui marche, et rien d’autre
Ces deux exemples ont été exécutés avant d’être écrits ici. Le premier dans un onglet, le second en ligne de commande. Ils ne sont pas les mêmes, et la section suivante dit pourquoi il serait malhonnête de les écrire pareil.
Dans un navigateur
const t = await import(
"https://translate.odersa.org/api/v2/client/sdk.js"
);
const r = await t.traduire("Bonjour le monde.", "fr", "bn");
r.texte; // le bengali
r.chemin; // ["fr", "en", "bn"] : le pivot par l'anglais, nommé
r.moteur; // qui a calculé, et c'était chez vousUn module ES, importé par son adresse. Le calcul part dans un worker que le SDK crée lui-même : le moteur est bloquant, et geler l’onglet de qui nous appelle serait impardonnable.
En Node
Le fichier se prend une fois, parce que Node ne charge pas un module par une adresse http : la raison est juste en dessous.
curl -O https://translate.odersa.org/api/v2/client/odersa-translate.jsconst T = require("./odersa-translate.js");
const r = await T.traduire("Bonjour le monde.", "fr", "bn", {
site: "https://translate.odersa.org"
});
r.texte; // le bengali, calculé sur votre machineLe même fichier, chargé par require. Aucune dépendance, aucun npm install, aucun compte. Le calcul tient le fil principal pendant qu’il travaille, ce qui convient à un outil de build et ne conviendrait pas à une page.
Deux entrées, une seule implémentation. sdk.js est un module ES et ne fait que republier ce que porte odersa-translate.js, qui vit dans le même dossier. Rien n’est écrit deux fois, donc rien ne peut diverger.
Pourquoi deux entrées
L’appel élégant serait la même ligne des deux côtés. Il ne marche pas, et le publier quand même reviendrait à faire perdre une heure au premier développeur qui le copierait.
Node ne charge pas un module par une adresse http ou https. Il ne s’agit pas d’un réglage à trouver : le drapeau expérimental qui l’autorisait autrefois a été retiré, et le passer aujourd’hui empêche Node de démarrer. Voici ce que Node répond, mot pour mot, quand on essaie :
await import("https://translate.odersa.org/api/v2/client/sdk.js");
// ERR_UNSUPPORTED_ESM_URL_SCHEME
// Only URLs with a scheme in: file and data are supported
// by the default ESM loader. Received protocol 'https:'D’où la forme retenue. Le navigateur importe sdk.js par son adresse, parce qu’un navigateur sait faire cela et que c’est sa façon normale de charger un module. Node prend odersa-translate.js une fois, puis l’appelle du disque, parce que c’est la façon normale de charger un fichier en Node.
Le message d’erreur nomme lui-même la seconde voie : data: est acceptée. Elle sert quand on ne veut rien poser sur le disque du tout, et elle est mesurée comme le reste.
// Node, sans rien installer et sans rien écrire sur le disque.
const source = await (await fetch(
"https://translate.odersa.org/api/v2/client/odersa-translate.js"
)).text();
await import("data:text/javascript," + encodeURIComponent(source));
const T = globalThis.OdersaTranslate; // l'UMD se pose là, comme sous une balise script
const r = await T.traduire("Bonjour le monde.", "fr", "es");Le fichier de calcul reste en UMD, et ce n’est pas de la nostalgie : le générateur de cet endpoint et deux de ses verrous le chargent par require pour refaire les empreintes du disque et comparer le contrat au client au caractère près. Un module ES ne se charge pas par require. La porte en module ES est donc un fichier à côté, et non une réécriture de celui-là.
Pourquoi elle est illimitée, et gratuite
Ce n’est pas une générosité, c’est une conséquence. Suivez la chaîne :
-
Vous demandez une direction de traduction
Le SDK lit
/modeles/registre.json, la liste de ce qui existe réellement chez nous, et calcule le chemin : un saut quand l’anglais est d’un côté, deux sinon. -
Le moteur descend chez vous
Un fichier WebAssembly, servi comme une image ou une feuille de style. Notre serveur ne fait rien d’autre que le tendre.
-
Les modèles descendent chez vous
Des fichiers compressés, un par pièce du modèle, servis tels quels. Là encore : aucun calcul de notre côté, aucune session, aucune trace de ce que vous allez traduire.
-
Votre processeur travaille
La traduction se calcule chez vous, dans un worker si vous êtes dans un navigateur, sur le fil principal si vous êtes en ligne de commande. Votre texte n’a pas d’adresse à laquelle partir : il n’y a personne au bout.
-
Il n’y a rien à facturer, donc rien à rationner
Un service qui calcule doit compter, donc identifier, donc plafonner. Celui-ci ne calcule pas. Pas de compte, pas de clé, pas de quota : il n’y a rien à compter.
Chaque appelant apporte son propre processeur. C’est cela, et rien d’autre, qui rend cette API illimitée.
La contrepartie est réelle et se dit dans le même souffle : ce que nous ne payons pas en calcul, vous le payez en téléchargement et en mémoire. La section suivante le chiffre au lieu de l’adoucir.
Ce que cela vous coûte
Un appelant qui découvre ce coût en production a été mal renseigné. Voici ce qu’il faut savoir avant d’écrire la première ligne.
| Ce qui est en jeu | Ce qu’il faut prévoir |
|---|---|
| Le moteur | Un fichier WebAssembly, téléchargé une fois par onglet ou par processus, puis gardé. |
| Une paire de langues | En moyenne 20 Mo compressés. Un pivot en demande deux, donc le double avant la première phrase. |
| La mémoire vive | Un modèle vivant occupe une grande part du tas que le binaire du moteur s’alloue, et ce tas est plafonné en dur. Le SDK ne garde donc que deux paires en vie, et évacue la moins récemment servie. |
| Le premier appel | Lent : il télécharge, il décompresse, il construit le modèle. Les suivants sur la même direction sont immédiats. |
| Le fil d’exécution | Le moteur est bloquant. Dans un navigateur, le SDK le met dans un worker. En Node, il tient le fil principal. |
Le plafond de deux paires vivantes n’est pas un réglage prudent, c’est une mesure : au-delà, le moteur épuise le tas que son binaire lui alloue et abandonne. Deux est aussi le plancher, puisqu’un pivot demande deux modèles en même temps. Changer de langue d’arrivée ne reconstruit donc que le second saut, et les octets viennent du cache du navigateur, pas du réseau.
Un appel qui télécharge suit son avancement, et le SDK dit honnêtement ce qu’il sait : le poids des modèles est au registre, donc il rend une part réelle ; le poids du moteur n’y est pas, donc il n’en invente pas.
const t = OdersaTranslate.traducteur({ site: "https://translate.odersa.org" });
const r = await t.traduire("Bonjour le monde.", "fr", "bn", {
avancement: (e) => console.log(e.etape, e.part)
// "moteur" null : le poids du moteur n'est pas annoncé, donc aucune part
// "modele" 7 … 100 : le registre donne le poids des modèles, donc une vraie part
});
await t.fermer(); // rend la mémoire tout de suite au lieu d'attendre l'ongletTout ce que le SDK expose
Deux façons d’appeler, et elles ne servent pas la même chose. traduire se suffit quand on veut une phrase et qu’on ne veut rien tenir : il garde un traducteur pour lui, sinon chaque phrase retéléchargerait son modèle. traducteur rend un objet à soi, dont on tient la durée de vie et qu’on ferme.
| Appel | Ce qu’il rend |
|---|---|
traduire(texte, de, vers, options) | le résultat, avec son chemin et le nom du moteur qui a calculé |
traducteur(options) | un traducteur à soi : mêmes appels, plus fermer() |
langues() | les codes utilisables, lus au registre servi |
paires() | les paires de modèles réellement servies |
chemin(de, vers) | les sauts, ou un refus nommé quand la direction n’existe pas |
fermer() | libère les modèles et arrête le worker |
| Clé | Ce qu’elle porte |
|---|---|
site | la racine qui sert le moteur et les modèles ; le domaine du service par défaut |
html | conserve les balises à leur place plutôt que de les traduire |
avancement | une fonction appelée pendant les téléchargements |
fil | "principal" pour refuser le worker, en sachant que le calcul gèlera la page |
texte | la traduction ; une liste si on a passé une liste |
chemin | les langues traversées, dans l’ordre |
pivot | vrai quand la traduction a passé par l’anglais |
moteur | qui a calculé. Une sortie de machine se dit, elle ne se laisse pas confondre avec une phrase relue par une personne |
Un lot se traduit en un appel, et c’est deux fois moins de travail qu’une boucle : le modèle est construit une fois, et le moteur découpe lui-même en phrases. Les balises restent à leur place quand on le demande.
const t = OdersaTranslate.traducteur({ site: "https://translate.odersa.org" });
await t.traduire(["Bonjour.", "Merci beaucoup."], "fr", "en");
// { texte: ["Hello.", "Thank you very much."], chemin: ["fr", "en"], … }
await t.traduire("<p>Bonjour <b>le monde</b>.</p>", "fr", "en", { html: true });
// { texte: "<p>Hello <b>the world</b>.</p>", … }Les langues, et le pivot par l’anglais
Le SDK sert 112 paires de modèles, soit 59 langues. La liste ne se recopie pas ici : elle se lit au registre, qui est le seul endroit où elle est vraie.
const t = await import("https://translate.odersa.org/api/v2/client/sdk.js");
await t.langues(); // les codes servis, lus au registre
// Ou, sans le SDK du tout :
await (await fetch("https://translate.odersa.org/modeles/registre.json")).json();L’anglais est le pivot, et il n’y a aucune paire directe entre deux langues tierces. Une traduction du français vers le bengali passe donc par deux modèles, et la qualité s’en ressent : ce qui est perdu au premier saut ne se retrouve pas au second. Le SDK ne le cache pas, il le nomme dans chemin et dans pivot, pour qu’un appelant puisse décider de prévenir ses lecteurs.
Le pivot passe par la fonction que le moteur expose pour cela, jamais par deux appels enchaînés. L’enchaînement manuel repasse par une chaîne intermédiaire et perd le découpage en phrases ; le pivot du moteur garde l’alignement d’un bout à l’autre.
Quatre langues ne vont que vers l’anglais et pas dans l’autre sens. Ce n’est pas un oubli de notre part : c’est ce que publie Mozilla, dont viennent ces modèles, et nous n’inventons pas un modèle qui n’existe pas. Une direction impossible est refusée par une phrase qui dit laquelle, plutôt que par un résultat vide.
await t.chemin("fr", "bn");
// ["fr", "en", "bn"]
await t.chemin("fr", "af");
// ODERSA Translate : il n'existe pas de modèle « en-af ». Cette direction
// n'est pas publiée par Mozilla et nous n'en inventons pas.Le worker, et votre politique de sécurité
Le service de traduction du moteur est synchrone : il tient le fil d’exécution jusqu’au bout du calcul. Appelé depuis le fil d’une page, il gèle l’onglet pendant la construction du modèle comme pendant la traduction. Le SDK met donc le moteur dans un worker, sans exception, et la page ne lui parle que par messages.
Ce worker naît d’une adresse blob:, et ce n’est pas un détour : un worker ne se construit pas directement sur une adresse d’une autre origine. Le blob porte l’origine de votre page, et c’est de là qu’il va chercher notre fichier, notre moteur et nos modèles. Une page qui porte une politique de sécurité de contenu stricte doit donc l’autoriser.
Content-Security-Policy:
worker-src blob: ;
script-src 'self' https://translate.odersa.org ;
connect-src 'self' https://translate.odersa.orgLe wasm-unsafe-eval est réclamé par tout chargement de WebAssembly, quel qu’il soit. Le moteur, lui, est compilé sans exécution dynamique : ni eval, ni new Function. Il n’a pas non plus besoin des en-têtes d’isolation d’origine, parce que sa mémoire n’est pas partagée : il tourne sur de l’hébergement statique nu.
Si votre politique interdit les workers de blob et que vous préférez assumer le gel, { fil: "principal" } le dit explicitement. Le SDK obéit et ne le fait jamais dans votre dos.
Ce qu’il ne sait pas faire
Une page qui ne dit que du bien de son sujet ne se croit pas. Voici ce que ce SDK ne fait pas, et n’a pas l’intention de faire.
| Ce qui manque | Pourquoi |
|---|---|
| Un appel par le réseau | Il n’y a aucune adresse à qui envoyer du texte pour recevoir une traduction, et il n’y en aura pas : ce serait la fin de la promesse et la fin de l’illimité. |
| Un score de confiance | Le moteur accepte de calculer une estimation de qualité, mais ce binaire n’expose aucun moyen de la lire. La demander ne servirait à rien, donc nous ne la demandons pas. |
| Une traduction publiable sans relecture | C’est une sortie de machine. Elle est étiquetée comme telle, et ce projet existe précisément contre la traduction automatique publiée sans relecture. |
| Un poids léger | Le premier appel sur une direction télécharge un modèle. Il n’existe pas de version allégée : ce qui rend la promesse vraie est exactement ce qui la rend lourde. |
| Un très vieux navigateur | Le moteur exige les instructions vectorielles de WebAssembly, présentes dans tous les navigateurs courants depuis plusieurs années. Un navigateur trop ancien échoue au chargement, et le dit. |
Une panne du moteur ne remonte jamais telle quelle. Ce qui sort du SDK est une phrase que nous avons écrite, avec une porte de sortie : la trace technique va à la console, où un développeur ira la lire, et nulle part ailleurs.
Appelable depuis n’importe quel domaine
Sans en-tête d’accès, une API publique n’est pas publique : un navigateur tiers refuse de lire nos fichiers, et la promesse est fausse. Le SDK, le moteur et les modèles répondent donc Access-Control-Allow-Origin: *, avec GET, HEAD et OPTIONS pour méthodes. Sans cela, le worker créé dans la page d’un tiers ne pourrait charger ni notre fichier ni nos modèles, et le SDK ne fonctionnerait que sur nos propres pages.
Cette ouverture ne donne rien d’autre : il n’y a ni session, ni cookie, ni compte, et le SDK demande explicitement qu’aucune information d’identification ne soit envoyée. Ces en-têtes sont écrits à la source, dans le gabarit du build, et reposés à chaque passage : un fichier d’en-têtes déposé à la main dans un dossier de sortie disparaît à la première régénération, sans que personne le voie partir.
La mémoire de phrases, à côté
Le même fichier ouvre une seconde porte, et il faut la présenter pour ce qu’elle est : ce n’est pas la traduction, c’est une pièce des données ouvertes. La mémoire porte ce que la maison a déjà traduit et relu, avec sa provenance et ses trous nommés. On y vient pour ne pas refaire un travail humain déjà fait, jamais pour traduire.
const M = OdersaTranslate.ouvrir({ base: "https://translate.odersa.org/api/v2" });
const [r] = await M.chercher([{ source: "Menu", contexte: "nav" }], { langues: ["en", "ar"] });
r.correspondance; // "exacte"
r.contexte_trouve; // "nav" : d'où vient ce qu'on vous rend
r.traductions; // { en: "Menu", ar: "…" }Elle compte 674 entrées en 7 langues, et ces sept langues ne sont pas celles du moteur : le moteur en sert bien plus, la mémoire est celle de l’atelier. Elle ne devine rien et n’invente rien : ce qu’elle ne connaît pas, elle le nomme dans un bon de travail plutôt que de le combler.
Tout ce qui la concerne, la normalisation, les empreintes, les seaux, le format des réponses, ce qu’un appel laisse sortir et les deux exports ouverts, est sur la page de la mémoire et détaillé appel par appel sur le fichier client. Le manifeste /api/v2/manifeste.json reste ce qui fait foi, jamais une page.
La version, et ce qui ne bougera pas
Le v2 dans l’adresse fait partie du contrat, au même titre que le format des fichiers ou le calcul des empreintes : il ne bouge pas en silence. Une seule version est servie, et c’est vérifié plutôt que supposé : cet endpoint n’a jamais été mis en ligne, donc aucun appelant extérieur ne dépend d’une version antérieure.
Ce qui ne bougera pas, quoi qu’il arrive : le calcul reste chez l’appelant. Une porte en HTTP où nous recevrions le texte ne serait pas une fonctionnalité de plus, ce serait un autre projet.
Le fichier de calcul vérifie lui-même son accord avec le manifeste à chaque lecture : si la version d’API ou la version de normalisation qu’il porte diverge de celle que le manifeste annonce, il refuse de répondre plutôt que de rendre des empreintes qui ne trouveraient rien.
Les portes suivantes
La boîte
Le même moteur, avec une interface : coller un texte, choisir une langue, et voir que rien ne part.
OuvrirCe qui se passe chez vous
Le moteur, les modèles, la mémoire de l’appareil : ce qui descend, ce qui reste, ce qui ne part jamais.
OuvrirLa mémoire de traduction
Ce que la maison a déjà traduit et vérifié, dédupliqué, avec sa provenance et ses trous nommés.
OuvrirLes données ouvertes
Tout ce que ce site publie en lecture libre : comptes, exports, manifeste, plan et flux, sous licence CC BY 4.0.
Ouvrir