---
title: "Écrire un adaptateur de format personnalisé"
description: "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."
type: "how-to"
---
# É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.

<AvailableFrom version="0.11.0" />

Sers-t'en quand tes fichiers de locale sont dans un format que verbatra ne lit pas d'origine (voir [Formats](https://verbatra.kreitz-webdev.de/fr/docs/formats.md)). 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](#ce-à-quoi-tu-accordes-ta-confiance)), donc un projet avec un format personnalisé passe par le SDK.

## Nommer le format [#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é :

```json title=".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`](https://verbatra.kreitz-webdev.de/fr/docs/error-codes.md#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 [#construire-ladaptateur]

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`](https://verbatra.kreitz-webdev.de/fr/docs/sdk/extend.md#createflatfileadapter) :

```ts title="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`](https://verbatra.kreitz-webdev.de/fr/docs/sdk/extend.md#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`](https://verbatra.kreitz-webdev.de/fr/docs/sdk/extend.md#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 [#lenregistrer-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 :

```ts title="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`](https://verbatra.kreitz-webdev.de/fr/docs/error-codes.md#duplicate_format) et un identifiant mal formé comme `custom:TOML` avec [`INVALID_FORMAT_ID`](https://verbatra.kreitz-webdev.de/fr/docs/error-codes.md#invalid_format_id), donc deux plugins qui ont choisi le même nom échouent au démarrage.

## Lever les bonnes erreurs [#lever-les-bonnes-erreurs]

Un adaptateur `custom:` est enveloppé à l'enregistrement, donc une exception inattendue de sa part apparaît comme [`ADAPTER_FAILED`](https://verbatra.kreitz-webdev.de/fr/docs/error-codes.md#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`](https://verbatra.kreitz-webdev.de/fr/docs/error-codes.md#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 [#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](https://verbatra.kreitz-webdev.de/fr/docs/translation-safety.md) 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 [#voir-aussi]

- [Adaptateurs, fournisseurs, expurgation](https://verbatra.kreitz-webdev.de/fr/docs/sdk/extend.md) : chaque option des fabriques et chaque export.
- [Codes d'erreur des adaptateurs de format](https://verbatra.kreitz-webdev.de/fr/docs/error-codes.md#adapter-error-codes) : ce que signifie chaque refus.
- [Formats](https://verbatra.kreitz-webdev.de/fr/docs/formats.md) : les formats que fournit verbatra.
