Aller au contenu
Aller au contenu

Docs / Guides

Page traduite automatiquement

Cette page a été traduite automatiquement, elle peut donc contenir des erreurs ou se lire un peu bizarrement. La version anglaise fait foi. Lire l'original en anglais.

Écrire un adaptateur de format personnalisé

Apporte un format que verbatra ne fournit pas : nomme-le avec un identifiant custom:, construis-le sur une fabrique d'adaptateurs et sache à quoi tu te fies.

Sers-t'en quand tes fichiers de locale sont dans un format que verbatra ne lit pas d'origine (voir Formats). Tu construis l'adaptateur dans ton propre paquet, tu nommes le format avec un identifiant custom: et tu confies à verbatra un registre qui le contient. Rien n'est publié chez verbatra, et aucune version n'est nécessaire.

Il te faut @verbatra/sdk dans le projet. La ligne de commande verbatra ne charge aucun plugin, c'est voulu (voir Ce à quoi tu accordes ta confiance), donc un projet avec un format personnalisé passe par le SDK.

Nommer le format

Un format venu de l'extérieur s'écrit custom: suivi d'un nom en minuscules séparé par des tirets, comme custom:toml ou custom:my-format. Aucun nom intégré ne contient de deux-points, donc il ne peut jamais en masquer un. Mets-le dans la config comme n'importe quel nom intégré :

.verbatrarc.json
{
  "sourceLocale": "en",
  "targetLocales": ["de"],
  "format": "custom:toml",
  "files": { "pattern": "locales/{locale}.toml" },
  "provider": { "id": "gemini", "options": { "model": "gemini-2.5-flash", "maxOutputTokens": 4096 } }
}

La config ne vérifie que la forme de l'identifiant, car l'adaptateur vit dans ton paquet. Si rien ne fournit d'adaptateur pour lui, l'exécution échoue avec UNKNOWN_FORMAT. Studio et project.snapshot du serveur MCP rapportent l'identifiant tel quel, donc un agent distingue un format personnalisé d'un format intégré.

Construire l'adaptateur

N'implémente pas le contrat d'adaptateur à la main. Deux fabriques fournissent la lecture bornée, l'écriture atomique, la détection par extension et les erreurs structurées, et ne te laissent que l'analyse de ton format.

Si chaque entrée est désignée par une seule clé sans imbrication, utilise createFlatFileAdapter :

toml-adapter.ts
import { createFlatFileAdapter, type FormatAdapter } from "@verbatra/sdk";

const TOKEN = /\{[a-z]+\}/g;

const tokensIn = (value: string): readonly string[] =>
  [...value.matchAll(TOKEN)].map((match) => match[0]);

export const tomlAdapter: FormatAdapter = createFlatFileAdapter({
  format: "custom:toml",
  extensions: [".toml"],
  parseEntries: (content, namespace) => parseToml(content, namespace),
  serializeEntries: (entries) => serializeToml(entries),
  extractPlaceholders: tokensIn,
});

Si les entrées se trouvent sur des chemins dans des objets imbriqués, utilise createTreeFileAdapter, qui prend parse et serialize sur un arbre plus un deriveEntry pour chaque feuille.

Ensuite, selon les besoins de ton format :

  • Si ton extension est générique, donne à la fabrique un sniff qui vérifie un échantillon du contenu. Sans lui, ton adaptateur revendique chaque fichier ayant cette extension, et la détection signale une ambiguïté.
  • Si un fichier contient à la fois le texte source et le texte cible, ou enregistre sa locale, lis l'argument supplémentaire locale que reçoivent parseEntries et serializeEntries.
  • Si une liste plate de tokens perdrait la structure de tes placeholders, ajoute comparePlaceholders ; si ton format a des branches de pluriel ou de select, ajoute compareBranchArms.
  • Si ton format marque du contenu comme non traduisible, renvoie { entries, excludedLeafPaths } depuis parseEntries au lieu de l'écarter.
  • Lis et écris via le port fs que te donne la fabrique, nodeAdapterFs par défaut, jamais directement avec node:fs. Tu gardes ainsi la lecture bornée et l'écriture atomique, et tu peux tester l'adaptateur sans disque.

L'enregistrer et lancer

Pars du registre intégré pour que ton format s'ajoute aux autres, puis passe-le comme dépendance adapterRegistry, que chaque flux du SDK accepte :

translate.ts
import { createDefaultRegistry, translate, loadConfig } from "@verbatra/sdk";
import { tomlAdapter } from "./toml-adapter.js";

const config = await loadConfig({ cwd: process.cwd() });
const adapterRegistry = createDefaultRegistry().register(tomlAdapter);

const summary = await translate({ config }, { adapterRegistry });

register renvoie le registre, donc les enregistrements s'enchaînent. Il refuse un format que le registre possède déjà avec DUPLICATE_FORMAT et un identifiant mal formé comme custom:TOML avec INVALID_FORMAT_ID, donc deux plugins qui ont choisi le même nom échouent au démarrage.

Lever les bonnes erreurs

Un adaptateur custom: est enveloppé à l'enregistrement, donc une exception inattendue de sa part apparaît comme ADAPTER_FAILED, avec ton format et la méthode, au lieu d'accuser verbatra. Le message ne reprend jamais le tien, qui pourrait citer le fichier ; lis ton erreur d'origine dans cause.

Deux types d'erreurs passent sans changement :

  • Une AdapterError que tu lèves toi-même garde son code. Lève INVALID_STRUCTURE pour un contenu que ton format ne peut pas représenter.
  • Une erreur portant un code errno comme ENOENT le garde, donc un fichier manquant reste signalé comme tel. Le contrôle ne regarde que la forme de l'erreur, alors ne mets pas de code errno sur tes propres erreurs de validation.

Ce à quoi tu accordes ta confiance

Un adaptateur de format est du code de pleine confiance, au même niveau que n'importe quel paquet que tu installes. verbatra ne l'isole dans aucun bac à sable.

Un adaptateur peut lire process.env, où se trouve chaque clé de fournisseur, lire et écrire tout fichier accessible au processus et ouvrir des connexions réseau. Le port fs est une convention, pas une frontière.

L'adaptateur décide aussi de ce qui compte comme placeholder. S'il n'en signale aucun, le contrôle d'intégrité des placeholders passe toujours pour son format, et une traduction qui a abîmé une interpolation part sans alerte, que l'adaptateur soit bogué ou malveillant.

Ce que verbatra garantit, c'est que le chargement n'est jamais implicite : il ne découvre aucun plugin, ne parcourt aucun répertoire, n'installe rien et ne suit aucune convention de nommage. Un adaptateur ne tourne que parce que ton code l'a importé et a confié le registre à verbatra. Traite-le comme toute dépendance qui lit tes secrets : lis-le, fige sa version et examine chaque mise à jour.

Voir aussi

Modifier sur GitHub

Sur cette page