---
title: "Adaptateurs, fournisseurs, expurgation"
description: "Construis un adaptateur de format ou un fournisseur de traduction que verbatra ne fournit pas, et expurge les secrets de tout texte passé au SDK."
type: "reference"
---
# Adaptateurs, fournisseurs, expurgation

Construis un adaptateur de format ou un fournisseur de traduction que verbatra ne fournit pas, et expurge les secrets de tout texte passé au SDK.

## Adaptateurs de format [#adaptateurs-de-format]

<AvailableFrom version="0.11.0" />

Tout ce qu'il faut pour construire un adaptateur de format que verbatra ne fournit pas et l'enregistrer à côté des adaptateurs intégrés. Un format qui ne vient pas de verbatra se nomme avec un identifiant `custom:`, par exemple `custom:toml`, que la configuration accepte exactement comme un nom intégré. Voir [Adaptateurs de format personnalisés](https://verbatra.kreitz-webdev.de/fr/docs/custom-format-adapters.md) pour le parcours complet, y compris ce à quoi tu accordes ta confiance en installant un adaptateur tiers : c'est du code pleinement de confiance, et verbatra ne le met pas en bac à sable.

### createDefaultRegistry [#createdefaultregistry]

Construit une `AdapterRegistry` contenant tous les adaptateurs fournis par verbatra, dans l'ordre de détection. Pars de là plutôt que d'un registre vide, pour que ton format rejoigne les adaptateurs intégrés au lieu de les remplacer, puis passe le résultat comme `deps.adapterRegistry` à n'importe quel flux. [`AdapterRegistry`](#adapterregistry) décrit `register` et `resolve`.

```ts title="Signature"
function createDefaultRegistry(fs?: AdapterFs): AdapterRegistry;
```

**Renvoie :** `AdapterRegistry`

**Exemple :** [L'enregistrer et lancer](https://verbatra.kreitz-webdev.de/fr/docs/custom-format-adapters.md#lenregistrer-et-lancer)

```ts title="Exemple"
import {
  createDefaultRegistry,
  translate,
  type FormatAdapter,
  type VerbatraConfig,
} from "@verbatra/sdk";

declare const config: VerbatraConfig;
declare const tomlAdapter: FormatAdapter;

const adapterRegistry = createDefaultRegistry().register(tomlAdapter);
const summary = await translate({ config }, { adapterRegistry });
```

### AdapterRegistry [#adapterregistry]

Contient les adaptateurs enregistrés et en résout un pour un fichier. `new AdapterRegistry()` démarre vide, il ne lit donc que les formats que tu enregistres ; `createDefaultRegistry` est le point de départ habituel. `register(adapter)` ajoute un adaptateur à la fin de l'ordre de détection et renvoie le registre, les enregistrements s'enchaînent donc. Il refuse un format que le registre détient déjà et un identifiant `custom:` mal formé, et enveloppe un adaptateur `custom:` pour qu'une exception inattendue de sa part arrive comme une `AdapterError` qui nomme le format. `resolve(filePath, options)` choisit l'adaptateur d'un fichier : par `options.format` s'il est donné, sinon par détection, que `options.sample`, un extrait du début du contenu, peut aider.

```ts title="Signature"
class AdapterRegistry {
  register(adapter: FormatAdapter): this;
  resolve(filePath: string, options?: ResolveOptions): AdapterResolution;
}
```

`resolve` accepte ces options :

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `sample` | `string` | no | A leading content sample to aid detection. Ignored when `format` is given. |
| `format` | `FormatId` | no | Bypass detection and select this format explicitly. |

**Renvoie :** `register` renvoie le registre. `resolve` renvoie une `AdapterResolution`, discriminée par `status` : `resolved` porte l'`adapter`, `no-match` le `filePath` et les `triedFormats`, et `ambiguous` le `filePath` et les `candidates` en concurrence, que tu départages en en passant un comme `format`. Un fichier impossible à résoudre est un statut, jamais une exception.

**Lève :** `AdapterError` [`DUPLICATE_FORMAT`](https://verbatra.kreitz-webdev.de/fr/docs/error-codes.md#duplicate_format) et [`INVALID_FORMAT_ID`](https://verbatra.kreitz-webdev.de/fr/docs/error-codes.md#invalid_format_id) depuis `register`, et [`ADAPTER_FAILED`](https://verbatra.kreitz-webdev.de/fr/docs/error-codes.md#adapter_failed) depuis `resolve` quand le propre `canHandle` d'un adaptateur `custom:` lève pendant la détection

### createFlatFileAdapter [#createflatfileadapter]

Construit un `FormatAdapter` pour un format plat clé/valeur, où chaque entrée est adressée par une seule clé sans imbrication. Tu fournis les `parseEntries`, `serializeEntries` et `extractPlaceholders` propres à ton format ; la fabrique fournit la lecture bornée en taille, l'écriture atomique, la détection par extension et la gestion structurée des erreurs. `parseEntries` reçoit aussi la locale sous laquelle le fichier est lu, pour un format dont l'unique fichier contient à la fois le texte source et le texte cible, et `serializeEntries` reçoit le contexte d'écriture de l'appelant, dont le `sourcePath` optionnel nomme le fichier de la locale source, et, à partir de 0.12.0, la locale écrite comme cinquième argument, pour un format qui inscrit sa locale dans le fichier. Donne-lui un `sniff` dès que ton extension est générique, sinon ton adaptateur revendique tous les fichiers qui la partagent et la détection signale une ambiguïté au lieu de choisir. Les hooks optionnels sont `validateMessage`, `computeInvalidIcuKeys`, `comparePlaceholders` et, à partir de 0.12.0, `compareBranchArms` ; `fs` remplace le port de système de fichiers, `nodeAdapterFs` par défaut.

```ts title="Signature"
function createFlatFileAdapter(
  options: FlatFileAdapterOptions,
): FormatAdapter;
```

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `format` | `FormatId` | yes | The format this adapter claims: a built-in name, or a `custom:` identifier of your own. |
| `extensions` | `readonly string[]` | yes | The file extensions this format owns, each including the leading dot. Matched against a path's extension case-insensitively. |
| `sniff` | `Sniff` | no | Optional content check, consulted when a sample is available, so this adapter does not claim every file that merely shares one of its extensions. |
| `parseEntries` | `function` | yes | Parse one file's text into entries keyed by entry key, in document order. Also receives the locale the file is read as, for a format whose one file carries both source and target text and has to know which side to read. Throw an `AdapterError` for content this format cannot represent; anything else thrown is reported as a structural failure. |
| `serializeEntries` | `function` | yes | Render entries back to the format's text, preserving key order. Receives the destination path, the port, the caller's write context and the locale being written, for a format that has to consult the existing file, or the source-locale file, to write in place, or that records the locale inside the file. |
| `extractPlaceholders` | `ExtractPlaceholders` | yes | Find this format's placeholder tokens in one value. |
| `validateMessage` | `ValidateMessage` | no | Optional message-syntax check applied to a candidate value before it is written. |
| `computeInvalidIcuKeys` | `ComputeInvalidIcuKeys` | no | Optional per-file report of the keys whose values are invalid for this format's message syntax. |
| `comparePlaceholders` | `ComparePlaceholders` | no | Optional whole-value placeholder comparison, for a format whose structure a flat token list would lose. Omitted adapters are compared by `extractPlaceholders` plus a flat multiset check. |
| `compareBranchArms` | `CompareBranchArms` | no | Optional check of a translation's plural and select branch arms against its source. |
| `fs` | `AdapterFs` | no | The file-system port to read and write through. Defaults to `nodeAdapterFs`. |

**Renvoie :** `FormatAdapter`

**Exemple :** [Construire l'adaptateur](https://verbatra.kreitz-webdev.de/fr/docs/custom-format-adapters.md#construire-ladaptateur)

Un `FormatAdapter` lit un fichier en un `LocaleResource`, la forme neutre que partagent tous les adaptateurs et flux :

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `locale` | `string` | yes | The locale these entries belong to (for example, "en" or "de"). |
| `namespace` | `string` | yes | The namespace these entries belong to. Every built-in adapter sets it to the locale file's base name without its extension; an adapter you write may choose another value. |
| `format` | `FormatId` | yes | The source format the resource came from, retained for round-trip fidelity on write. |
| `entries` | `object` | yes | Entries addressable by key, in the order the source file defined them. |

### createTreeFileAdapter [#createtreefileadapter]

Construit un `FormatAdapter` pour un format arborescent, dont les entrées vivent sur des chemins à travers des objets imbriqués. Il prend `parse` et `serialize` sur un arbre plus un `deriveEntry` qui indique les placeholders de chaque feuille et si elle est une forme plurielle, ainsi que `extractPlaceholders` pour une valeur, et gère lui-même l'aplatissement et la reconstruction. Comme la fabrique plate, il prend `format`, `extensions` et un `sniff` optionnel. Il porte aussi les hooks optionnels dont un format structuré a besoin : `validateTree`, `validateMessage`, `computeInvalidIcuKeys`, `comparePlaceholders`, `compareBranchArms` (à partir de 0.12.0), `deriveDescriptions`, `buildWriteTree` et `keyMode`, et `fs` remplace le port de système de fichiers, `nodeAdapterFs` par défaut.

```ts title="Signature"
function createTreeFileAdapter(
  options: TreeFileAdapterOptions,
): FormatAdapter;
```

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `format` | `FormatId` | yes | The format this adapter claims: a built-in name, or a `custom:` identifier of your own. |
| `extensions` | `readonly string[]` | yes | The file extensions this format owns, each including the leading dot. Matched against a path's extension case-insensitively. |
| `sniff` | `Sniff` | no | Optional content check, consulted when a sample is available, so this adapter does not claim every file that merely shares one of its extensions. |
| `parse` | `(content: string) => JsonRecord` | yes | Parse one file's text into a nested tree, preserving key order at every level. Throw an `AdapterError` for content this format cannot represent; anything else thrown is reported as a structural failure. |
| `serialize` | `(tree: OrderedRecord) => string` | yes | Render a nested tree back to the format's text, preserving key order at every level. |
| `deriveEntry` | `DeriveEntry` | yes | Derive the placeholder tokens and the plural flag for one leaf. |
| `extractPlaceholders` | `ExtractPlaceholders` | yes | Find this format's placeholder tokens in one value. |
| `computeInvalidIcuKeys` | `ComputeInvalidIcuKeys` | no | Optional per-file report of the keys whose values are invalid for this format's message syntax. |
| `validateMessage` | `ValidateMessage` | no | Optional message-syntax check applied to a candidate value before it is written. |
| `validateTree` | `ValidateTree` | no | Optional structural check on the parsed tree, throwing an `AdapterError` for a shape this format forbids. |
| `buildWriteTree` | `BuildWriteTree` | no | Optional override for how the tree to write is assembled, for a format that patches the existing document. |
| `keyMode` | `KeyMode` | no | How a dotted key is addressed. Defaults to `"literal-leaf"`. |
| `comparePlaceholders` | `ComparePlaceholders` | no | Optional whole-value placeholder comparison, for a format whose structure flattening would lose. |
| `compareBranchArms` | `CompareBranchArms` | no | Optional check of a translation's plural and select branch arms against its source. |
| `deriveDescriptions` | `DeriveDescriptions` | no | Optional extraction of per-key translator descriptions from the raw file text. |
| `fs` | `AdapterFs` | no | The file-system port to read and write through. Defaults to `nodeAdapterFs`. |

**Renvoie :** `FormatAdapter`

### nodeAdapterFs [#nodeadapterfs]

Le port système de fichiers, de type `AdapterFs`, que chaque fabrique d'adaptateur utilise quand aucun n'est fourni : une lecture bornée en taille qui signale un fichier absent ou trop grand comme un état plutôt qu'en levant, et une écriture qui passe par un fichier temporaire puis un renommage, de sorte qu'une écriture interrompue ne laisse jamais un fichier de locale à moitié fini. Dans un adaptateur à toi, lis et écris par ce port plutôt que d'aller chercher `node:fs`. C'est une convention, pas une frontière, et verbatra ne peut pas l'imposer à du code qui ne lui appartient pas.

```ts title="Signature"
const nodeAdapterFs: AdapterFs;
```

### isCustomFormatId [#iscustomformatid]

Affine le type d'un identifiant de format en un identifiant tiers : true pour un identifiant `custom:` bien formé, false pour tout nom de format intégré. Utile quand ton propre outillage doit distinguer les deux, par exemple pour décider s'il faut fournir un registre avant qu'une configuration puisse tourner.

```ts title="Signature"
function isCustomFormatId(
  format: string,
): format is CustomFormatId;
```

**Renvoie :** `format is CustomFormatId`

## Expurgation des secrets [#expurgation-des-secrets]

<AvailableFrom version="0.10.0" />

### redact [#redact]

Retire d'une chaîne les formes de clé d'API de fournisseur et la valeur exacte actuelle de toute
variable d'environnement de fournisseur configurée, en remplaçant chaque correspondance par
`[REDACTED]`. `@verbatra/studio` et `@verbatra/mcp` l'appliquent tous deux à chaque valeur qu'ils
n'ont pas eux-mêmes générée et qu'ils renvoient à un appelant, comme un terme de glossaire, un
chemin de fichier ou un message d'erreur d'un système en amont, afin qu'une clé déjà présente dans
ton environnement ou écrite dans un fichier du projet ne puisse jamais atteindre un onglet de
navigateur, un agent ou une ligne de log. La CLI `verbatra` y fait passer, en dernière étape, tout
ce qu'elle écrit sur stdout et stderr, enveloppe `--json` comprise.

```ts title="Signature"
function redact(text: string): string;
```

**Renvoie :** `string`

Cela inclut la variable qu'un fournisseur `openai-compatible` désigne via `apiKeyEnvVar`. Elle est
déclarée quand `loadConfig` charge une config qui la nomme ou quand ce fournisseur est construit, et
dès lors sa valeur est retirée exactement comme celle des variables intégrées. Les déclarations ne
sont jamais supprimées. Une valeur de moins de huit caractères n'est pas retirée par sa valeur, afin
qu'une variable courte ne puisse jamais effacer du texte sans rapport.

Une clé DeepL n'est reconnue à sa seule forme que lorsqu'elle porte le suffixe `:fx` d'une clé
gratuite, ou quand un UUID nu se trouve dans un contexte de clé : après un en-tête `DeepL-Auth-Key`,
ou après un nom comme `auth_key`, `authKey`, `--auth-key`, `api_key`, `deeplKey`, `DEEPL_API_KEY`
ou `DEEPL_AUTH_KEY` suivi de `:`, `=`, d'un `%3D` encodé en URL ou d'un espace, avec ou sans
guillemets, y compris dans du JSON sérialisé dans une chaîne. Tout autre UUID, par exemple dans un
chemin de fichier ou un identifiant de requête, reste lisible. Une clé Pro placée dans
`DEEPL_API_KEY` est toujours retirée par sa valeur, où qu'elle apparaisse.

Un token `sk-` est masqué dès qu'au moins 32 lettres et chiffres suivent `sk-`, comptés sur tous
ses segments `-` et `_`, quel que soit son préfixe. Cette seule règle couvre toutes les formes de clé
d'OpenAI et d'Anthropic ; un préfixe connu comme `sk-proj-` ou `sk-ant-` ne masque pas à lui seul un
token plus court. Le token ne compte que si aucune lettre ni aucun chiffre ne le précède directement :
un échappement JSON comme `\n` ou `\u0007`, un caractère encodé en pourcentage comme `%3D`, une
séquence ANSI CSI comme `\x1b[31m`, `\x1b[2K` ou `\x1b[?25h`, une désignation de jeu de caractères
ANSI comme `\x1b(B`, ou l'une de ces deux formes ANSI échappée en JSON comme `\u001b` sert aussi de
limite. Un chemin ou une clé de locale slovaque comme `locales/sk-SK.json` ou `sk-banner_headline`
reste lisible. Mais une longue clé qui ressemble à du slovaque atteint le seuil et est masquée :
`sk-SK_settings_notifications_email_digest` contient 34 lettres et chiffres après `sk-`, tout comme
une longue clé camelCase comme `sk-onboardingWelcomeScreenPrimaryButton`.

Une clé Gemini est reconnue par sa seule forme : `AIza` suivi de 35 lettres, chiffres, `_` ou `-`.

### declareProviderKeyEnvVar [#declareproviderkeyenvvar]

<AvailableFrom version="0.12.0" />

Déclare la variable qu'un bloc `provider` désigne comme source de clé, pour que `redact` retire sa
valeur. Aujourd'hui, seul un fournisseur `openai-compatible` avec `apiKeyEnvVar` en désigne une ;
pour tout autre fournisseur, rien ne se passe. `loadConfig` et la construction du fournisseur le
font déjà, et Studio comme le serveur MCP le font aussi pour la config qu'ils reçoivent : appelle-la
donc seulement quand une config arrive dans ton propre code par un autre chemin. La déclaration vaut
pour tout le processus et elle est partagée par chaque copie du SDK chargée dans celui-ci.

```ts title="Signature"
function declareProviderKeyEnvVar(
  provider: ProviderConfig,
): void;
```

**Renvoie :** `void`

## Fournisseurs personnalisés [#fournisseurs-personnalisés]

<AvailableFrom version="0.12.0" />

Un fournisseur que le SDK ne livre pas se branche via `deps.createProvider`, un `CreateProvider`
qui renvoie un `TranslationProvider`. Le SDK exporte tout le contrat sous forme de types, donc ton
fournisseur est vérifié contre la même interface que celle des fournisseurs intégrés. Cela
inclut les types de ses champs : `Tone`, `PluralCategories`, `PlaceholderExtractor` et
`PlaceholderComparator` dans la requête, `Usage`, `ProviderNotice`, `ProviderNoticeCode` et
`ReviewFlag` dans le résultat, et `NetworkPolicy` et `NetworkRule` dans `ProviderNetwork`.

```ts title="Exemple"
import {
  translate,
  type CreateProvider,
  type TranslateRequest,
  type TranslateResult,
  type TranslationProvider,
  type VerbatraConfig,
} from "@verbatra/sdk";

declare const config: VerbatraConfig;
declare function callInHouseService(
  locale: string,
  entries: TranslateRequest["entries"],
): Promise<ReadonlyMap<string, string>>;

const provider: TranslationProvider = {
  id: "in-house",
  kind: "machine-translation",
  supportsGlossary: false,
  async translateBatch(request: TranslateRequest): Promise<TranslateResult> {
    const values = await callInHouseService(request.targetLocale, request.entries);
    return { values, integrity: new Map(), notices: [] };
  },
};

const createProvider: CreateProvider = () => provider;
const summary = await translate({ config }, { createProvider });
```

- `TranslationProvider` est l'unique contrat : `id`, `kind` (un `ProviderKind`), `supportsGlossary`
  et `translateBatch(request)`. Traite chaque valeur d'une requête comme une donnée non fiable, lis
  toute clé d'API depuis l'environnement plutôt que depuis la requête, et lève un `ProviderError`
  sans secret quand un appel échoue.
- `ProviderKind` vaut `"llm"` ou `"machine-translation"`. Le `kind` de ton objet fournisseur est
  purement descriptif : le SDK décide de la génération des pluriels et de ce que scanne la garde de
  données sensibles d'après le type de l'`id` de fournisseur configuré, pas d'après ce champ.
- `TranslateRequest` porte `sourceLocale`, `targetLocale`, les `entries` à traduire et, en option,
  `glossary` (le résultat de [`glossaryForLocale`](https://verbatra.kreitz-webdev.de/fr/docs/sdk/config.md#glossaryforlocale) pour la locale cible),
  `tone`, `maxLength` et `pluralCategories`. Il porte aussi `extractPlaceholders` et, pour certains
  formats, `comparePlaceholders`, que ton fournisseur utilise pour le contrôle d'intégrité, ainsi
  que `signal`, qui interrompt un appel en cours, `onRepair` et
  `foreignPlaceholders` : par clé, les jetons d'une autre syntaxe de placeholder à protéger comme
  des placeholders.
- `TranslateResult` renvoie `values` et `integrity` par clé, plus `usage`, `notices` et
  `reviewFlags` en option. Une clé absente de `values` compte comme non traduite, donc un
  fournisseur peut omettre une valeur qu'il ne sait pas traduire sans risque. Le SDK fait passer
  chaque valeur renvoyée par son propre contrôle d'intégrité dans tous les cas.
- `ProviderRetryListener` est le type de `CreateProviderHooks.onRetry`. Il reçoit un
  `ProviderRetry`, `{ attempt, delayMs?, status? }`, par nouvelle tentative, et chaque appel devient
  un [événement de progression](https://verbatra.kreitz-webdev.de/fr/docs/sdk/run.md#événements-de-progression) `provider-retry`.
- `ProviderNetwork` est ce que contient `CreateProviderContext.network` quand la politique réseau
  restreint les destinations d'un fournisseur : la `policy` effective, l'`env` depuis lequel le
  point de terminaison a été résolu et des `deps` de test optionnels. Un fournisseur personnalisé
  peut l'ignorer, mais seule la vérification que le SDK fait avant d'appeler la fabrique s'applique
  alors à ses requêtes.

**Exemple :** [Tester ton intégration sans clé](https://verbatra.kreitz-webdev.de/fr/docs/programmatic-api.md#tester-ton-intégration-sans-clé)
