---
title: "Escribe un adaptador de formato propio"
description: "Aporta un formato que verbatra no trae: nómbralo con un identificador custom:, constrúyelo sobre una fábrica de adaptadores y entiende en qué confías."
type: "how-to"
---
# Escribe un adaptador de formato propio

Aporta un formato que verbatra no trae: nómbralo con un identificador custom:, constrúyelo sobre una fábrica de adaptadores y entiende en qué confías.

<AvailableFrom version="0.11.0" />

Úsalo cuando tus archivos de locale estén en un formato que verbatra no lee de serie (consulta [Formatos](https://verbatra.kreitz-webdev.de/es/docs/formats.md)). Construyes el adaptador en tu propio paquete, nombras el formato con un identificador `custom:` y le entregas a verbatra un registro que lo contenga. No se publica nada en verbatra ni hace falta ninguna versión nueva.

Necesitas `@verbatra/sdk` en el proyecto. La línea de comandos `verbatra` no carga ningún plugin, a propósito (consulta [En qué confías](#en-qué-confías)), así que un proyecto con un formato propio usa verbatra a través del SDK.

## Nombrar el formato [#nombrar-el-formato]

Un formato de fuera de verbatra es `custom:` seguido de un nombre en minúsculas separado por guiones, como `custom:toml` o `custom:my-format`. Ningún nombre integrado lleva dos puntos, así que nunca puede ocultar uno. Ponlo en la configuración como cualquier nombre integrado:

```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 configuración solo comprueba la forma del identificador, porque el adaptador vive en tu paquete. Si nada aporta un adaptador para él, la ejecución falla con [`UNKNOWN_FORMAT`](https://verbatra.kreitz-webdev.de/es/docs/error-codes.md#unknown_format). Studio y `project.snapshot` del servidor MCP informan del identificador tal cual, así que un agente distingue un formato propio de uno integrado.

## Construir el adaptador [#construir-el-adaptador]

No implementes el contrato del adaptador a mano. Dos fábricas aportan la lectura acotada, la escritura atómica, la detección por extensión y los errores estructurados, y solo te dejan el análisis de tu formato.

Si cada entrada se identifica con una sola clave sin anidamiento, usa [`createFlatFileAdapter`](https://verbatra.kreitz-webdev.de/es/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 las entradas viven en rutas dentro de objetos anidados, usa [`createTreeFileAdapter`](https://verbatra.kreitz-webdev.de/es/docs/sdk/extend.md#createtreefileadapter), que recibe `parse` y `serialize` sobre un árbol y un `deriveEntry` para cada hoja.

Después, según lo que necesite tu formato:

- Si tu extensión es genérica, dale a la fábrica un `sniff` que compruebe una muestra del contenido. Sin él, tu adaptador reclama todos los archivos con esa extensión y la detección informa de una ambigüedad.
- Si un archivo contiene a la vez el texto de origen y el de destino, o registra su locale, lee el argumento extra `locale` que reciben `parseEntries` y `serializeEntries`.
- Si una lista plana de tokens perdería la estructura de tus placeholders, añade `comparePlaceholders`; si tu formato tiene ramas de plural o select, añade `compareBranchArms`.
- Si tu formato marca contenido como no traducible, devuelve `{ entries, excludedLeafPaths }` desde `parseEntries` en lugar de descartarlo.
- Lee y escribe a través del puerto `fs` que te entrega la fábrica, [`nodeAdapterFs`](https://verbatra.kreitz-webdev.de/es/docs/sdk/extend.md#nodeadapterfs) por defecto, nunca directamente con `node:fs`. Así conservas la lectura acotada y la escritura atómica, y puedes probar el adaptador sin disco.

## Registrarlo y ejecutar [#registrarlo-y-ejecutar]

Parte del registro integrado para que tu formato se sume a los demás y pásalo como la dependencia `adapterRegistry` que acepta cada flujo del SDK:

```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` devuelve el registro, así que los registros se encadenan. Rechaza un formato que el registro ya tiene con [`DUPLICATE_FORMAT`](https://verbatra.kreitz-webdev.de/es/docs/error-codes.md#duplicate_format) y un identificador mal formado como `custom:TOML` con [`INVALID_FORMAT_ID`](https://verbatra.kreitz-webdev.de/es/docs/error-codes.md#invalid_format_id), así que dos plugins con el mismo nombre fallan al arrancar.

## Lanzar los errores adecuados [#lanzar-los-errores-adecuados]

Un adaptador `custom:` se envuelve al registrarlo, así que un fallo inesperado suyo aparece como [`ADAPTER_FAILED`](https://verbatra.kreitz-webdev.de/es/docs/error-codes.md#adapter_failed), con tu formato y el método, en lugar de culpar a verbatra. El mensaje nunca repite el tuyo, que podría citar el archivo; lee tu error original desde `cause`.

Dos tipos de error pasan sin cambios:

- Un `AdapterError` que lances tú conserva su código. Lanza [`INVALID_STRUCTURE`](https://verbatra.kreitz-webdev.de/es/docs/error-codes.md#invalid_structure) para contenido que tu formato no puede representar.
- Un error con un código errno como `ENOENT` lo conserva, así que un archivo que falta se sigue informando como tal. La comprobación solo mira la forma del error, así que no pongas un código errno en tus propios errores de validación.

## En qué confías [#en-qué-confías]

**Un adaptador de formato es código de plena confianza, al mismo nivel que cualquier paquete que instales. verbatra no lo aísla en ningún sandbox.**

Un adaptador puede leer `process.env`, donde vive cada clave de proveedor, leer y escribir cualquier archivo al que acceda el proceso y abrir conexiones de red. El puerto `fs` es una convención, no una frontera.

El adaptador también decide qué cuenta como placeholder. Si no informa de ninguno, la [comprobación de integridad de placeholders](https://verbatra.kreitz-webdev.de/es/docs/translation-safety.md) pasa siempre para su formato, y una traducción que destrozó una interpolación se publica sin aviso, tanto si el adaptador tiene un fallo como si es malicioso.

Lo que verbatra sí garantiza es que la carga nunca es implícita: no descubre plugins, no recorre directorios, no instala nada ni sigue convenciones de nombres. Un adaptador solo se ejecuta porque tu código lo importó y le entregó el registro a verbatra. Trátalo como cualquier dependencia que lee tus secretos: léelo, fija su versión y revisa cada actualización.

## Relacionado [#relacionado]

- [Adaptadores, proveedores, redacción](https://verbatra.kreitz-webdev.de/es/docs/sdk/extend.md): cada opción de las fábricas y cada export.
- [Códigos de error de los adaptadores de formato](https://verbatra.kreitz-webdev.de/es/docs/error-codes.md#adapter-error-codes): qué significa cada rechazo.
- [Formatos](https://verbatra.kreitz-webdev.de/es/docs/formats.md): los formatos que trae verbatra.
