---
title: "Adaptadores, proveedores, redacción"
description: "Crea un adaptador de formato o un proveedor de traducción que verbatra no incluye y oculta secretos en cualquier texto que pases al SDK."
type: "reference"
---
# Adaptadores, proveedores, redacción

Crea un adaptador de formato o un proveedor de traducción que verbatra no incluye y oculta secretos en cualquier texto que pases al SDK.

## Adaptadores de formato [#adaptadores-de-formato]

<AvailableFrom version="0.11.0" />

Todo lo necesario para construir un adaptador de un formato que verbatra no trae y registrarlo junto a los integrados. Un formato externo a verbatra se nombra con un identificador `custom:`, como `custom:toml`, que la configuración acepta igual que un nombre integrado. En [Adaptadores de formato propios](https://verbatra.kreitz-webdev.de/es/docs/custom-format-adapters.md) tienes el recorrido completo, incluido en qué confías al instalar un adaptador de terceros: es código de plena confianza, y verbatra no lo aísla.

### createDefaultRegistry [#createdefaultregistry]

Construye una `AdapterRegistry` con todos los adaptadores que verbatra trae, en orden de detección. Empieza aquí en lugar de con un registro vacío, para que tu formato se sume a los integrados en vez de reemplazarlos, y pasa el resultado como `deps.adapterRegistry` a cualquier flujo. [`AdapterRegistry`](#adapterregistry) describe `register` y `resolve`.

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

**Devuelve:** `AdapterRegistry`

**Ejemplo:** [Registrarlo y ejecutar](https://verbatra.kreitz-webdev.de/es/docs/custom-format-adapters.md#registrarlo-y-ejecutar)

```ts title="Ejemplo"
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]

Guarda los adaptadores registrados y resuelve uno para un archivo. `new AdapterRegistry()` empieza
vacío, así que solo lee los formatos que registres; `createDefaultRegistry` es el punto de partida
habitual. `register(adapter)` añade un adaptador al final del orden de detección y devuelve el
registro, así que las altas se encadenan. Rechaza un formato que el registro ya tiene y un
identificador `custom:` mal formado, y envuelve un adaptador `custom:` para que una excepción
inesperada suya llegue como un `AdapterError` que nombra el formato. `resolve(filePath, options)`
elige el adaptador para un archivo: por `options.format` cuando se indica, si no por detección, a
la que `options.sample`, una muestra del inicio del contenido, puede ayudar.

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

`resolve` acepta estas opciones:

| 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. |

**Devuelve:** `register` devuelve el registro. `resolve` devuelve una `AdapterResolution`,
discriminada por `status`: `resolved` lleva el `adapter`, `no-match` el `filePath` y los
`triedFormats`, y `ambiguous` el `filePath` y los `candidates` en competencia, que resuelves
pasando uno como `format`. Un archivo que no se puede resolver es un estado, nunca una excepción.

**Lanza:** `AdapterError` [`DUPLICATE_FORMAT`](https://verbatra.kreitz-webdev.de/es/docs/error-codes.md#duplicate_format) y [`INVALID_FORMAT_ID`](https://verbatra.kreitz-webdev.de/es/docs/error-codes.md#invalid_format_id) desde `register`, y [`ADAPTER_FAILED`](https://verbatra.kreitz-webdev.de/es/docs/error-codes.md#adapter_failed) desde `resolve` cuando el propio `canHandle` de un adaptador `custom:` lanza durante la detección

### createFlatFileAdapter [#createflatfileadapter]

Construye un `FormatAdapter` para un formato plano de clave/valor, donde cada entrada se direcciona con una sola clave sin anidamiento. Tú aportas `parseEntries`, `serializeEntries` y `extractPlaceholders` de tu formato; la fábrica aporta la lectura acotada por tamaño, la escritura atómica, la detección por extensión y el manejo estructurado de errores. `parseEntries` recibe además el locale con el que se lee el archivo, para un formato cuyo único archivo contiene a la vez el texto de origen y el de destino, y `serializeEntries` recibe el contexto de escritura de quien llama, cuyo `sourcePath` opcional nombra el archivo del locale de origen, y, desde 0.12.0, el locale que se escribe como quinto argumento, para un formato que guarda su locale dentro del archivo. Dale un `sniff` siempre que tu extensión sea genérica, o tu adaptador reclamará todos los archivos que la compartan y la detección informará de una ambigüedad en lugar de elegir. Los hooks opcionales son `validateMessage`, `computeInvalidIcuKeys`, `comparePlaceholders` y, desde 0.12.0, `compareBranchArms`; `fs` sustituye el puerto del sistema de archivos, `nodeAdapterFs` por defecto.

```ts title="Firma"
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`. |

**Devuelve:** `FormatAdapter`

**Ejemplo:** [Construir el adaptador](https://verbatra.kreitz-webdev.de/es/docs/custom-format-adapters.md#construir-el-adaptador)

Un `FormatAdapter` lee un archivo como un `LocaleResource`, la forma neutra que comparten todos los adaptadores y flujos:

| 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]

Construye un `FormatAdapter` para un formato de árbol, cuyas entradas viven en rutas a través de objetos anidados. Toma `parse` y `serialize` sobre un árbol más un `deriveEntry` que informa de los placeholders de cada hoja y de si es una forma plural, además de `extractPlaceholders` para un solo valor, y se encarga por su cuenta de aplanar y volver a anidar. Igual que la fábrica plana, toma `format`, `extensions` y un `sniff` opcional. También lleva los hooks opcionales que necesita un formato estructurado: `validateTree`, `validateMessage`, `computeInvalidIcuKeys`, `comparePlaceholders`, `compareBranchArms` (desde 0.12.0), `deriveDescriptions`, `buildWriteTree` y `keyMode`, y `fs` sustituye el puerto del sistema de archivos, `nodeAdapterFs` por defecto.

```ts title="Firma"
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`. |

**Devuelve:** `FormatAdapter`

### nodeAdapterFs [#nodeadapterfs]

El puerto de sistema de archivos, de tipo `AdapterFs`, que usa cada fábrica de adaptadores cuando no se le pasa ninguno: una lectura acotada por tamaño que informa de un archivo ausente o demasiado grande como estado en lugar de lanzar, y una escritura que pasa por un archivo temporal y un renombrado, de modo que una escritura interrumpida nunca deja un archivo de locale a medias. Lee y escribe a través de este puerto en un adaptador propio en lugar de recurrir a `node:fs`. Es una convención, no una frontera, y verbatra no puede imponerla en código que no le pertenece.

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

### isCustomFormatId [#iscustomformatid]

Acota un identificador de formato a uno externo: true para un identificador `custom:` bien formado, false para cualquier nombre de formato integrado. Útil cuando tus propias herramientas tienen que distinguirlos, por ejemplo para decidir si hace falta aportar un registro antes de poder ejecutar una configuración.

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

**Devuelve:** `format is CustomFormatId`

## Redacción de secretos [#redacción-de-secretos]

<AvailableFrom version="0.10.0" />

### redact [#redact]

Elimina de una cadena las formas de clave de API de proveedor y el valor exacto actual de cualquier
variable de entorno de proveedor configurada, reemplazando cada coincidencia por `[REDACTED]`.
`@verbatra/studio` y `@verbatra/mcp` lo aplican los dos a todo valor que devuelven a quien llama y
que no han generado ellos mismos, como un término del glosario, una ruta de archivo o un mensaje de
error de un sistema previo, así que una clave ya presente en tu entorno o escrita en un archivo del
proyecto nunca puede llegar a una pestaña del navegador, un agente ni una línea de log. La CLI
`verbatra` pasa por él, como último paso, todo lo que escribe en stdout y stderr, incluido el
envoltorio `--json`.

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

**Devuelve:** `string`

Esto incluye la variable que un proveedor `openai-compatible` nombra con `apiKeyEnvVar`. Se declara
cuando `loadConfig` carga una config que la nombra o cuando se construye ese proveedor, y desde ese
momento su valor se elimina igual que el de las variables integradas. Las declaraciones nunca se
retiran. Un valor de menos de ocho caracteres no se elimina por su valor, así que una variable corta
nunca puede borrar texto ajeno.

Una clave de DeepL se reconoce solo por su forma únicamente cuando lleva el sufijo `:fx` de una
clave gratuita, o cuando un UUID suelto está en un contexto de clave: tras una cabecera
`DeepL-Auth-Key`, o tras un nombre como `auth_key`, `authKey`, `--auth-key`, `api_key`, `deeplKey`,
`DEEPL_API_KEY` o `DEEPL_AUTH_KEY` seguido de `:`, `=`, un `%3D` codificado en URL o un espacio, con
o sin comillas, también dentro de JSON serializado en una cadena. Cualquier otro UUID, como uno en
una ruta de archivo o un id de petición, sigue legible. Una clave Pro guardada en `DEEPL_API_KEY`
se sigue eliminando por su valor dondequiera que aparezca.

Un token `sk-` se redacta en cuanto le siguen al menos 32 letras y dígitos tras `sk-`, contados en
todos sus segmentos `-` y `_`, sea cual sea su prefijo. Esa única regla cubre todas las formas de
clave de OpenAI y Anthropic; un prefijo conocido como `sk-proj-` o `sk-ant-` no redacta por sí solo
un token más corto. El token solo cuenta cuando no le precede directamente una letra ni un dígito:
un escape JSON como `\n` o `\u0007`, un carácter codificado en porcentaje como `%3D`, una secuencia
ANSI CSI como `\x1b[31m`, `\x1b[2K` o `\x1b[?25h`, una selección de juego de caracteres ANSI como
`\x1b(B`, o cualquiera de las dos formas ANSI escapada en JSON como `\u001b` también es un límite.
Una ruta o clave de locale eslovaco como `locales/sk-SK.json` o `sk-banner_headline` sigue legible.
Pero una clave larga con aspecto eslovaco alcanza el umbral y se redacta:
`sk-SK_settings_notifications_email_digest` tiene 34 letras y dígitos tras `sk-`, igual que una
clave camelCase larga como `sk-onboardingWelcomeScreenPrimaryButton`.

Una clave de Gemini se reconoce solo por su forma: `AIza` seguido de 35 letras, dígitos, `_` o `-`.

### declareProviderKeyEnvVar [#declareproviderkeyenvvar]

<AvailableFrom version="0.12.0" />

Declara la variable que un bloque `provider` nombra como origen de la clave, para que `redact`
elimine su valor. Hoy solo un proveedor `openai-compatible` con `apiKeyEnvVar` nombra una; con
cualquier otro proveedor no hace nada. `loadConfig` y la construcción del proveedor ya lo hacen, y
Studio y el servidor MCP también lo hacen para la config que reciben, así que llámalo solo cuando
una config llegue a tu propio código por otra vía. La declaración vale para todo el proceso y la
comparten todas las copias del SDK cargadas en él.

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

**Devuelve:** `void`

## Proveedores propios [#proveedores-propios]

<AvailableFrom version="0.12.0" />

Un proveedor que el SDK no incluye se conecta mediante `deps.createProvider`, un `CreateProvider`
que devuelve un `TranslationProvider`. El SDK exporta el contrato completo como tipos, así que tu
proveedor se comprueba contra la misma interfaz que implementan los integrados. Eso incluye los
tipos de sus campos: `Tone`, `PluralCategories`, `PlaceholderExtractor` y `PlaceholderComparator`
en la petición, `Usage`, `ProviderNotice`, `ProviderNoticeCode` y `ReviewFlag` en el resultado, y
`NetworkPolicy` y `NetworkRule` en `ProviderNetwork`.

```ts title="Ejemplo"
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` es el único contrato: `id`, `kind` (un `ProviderKind`), `supportsGlossary`
  y `translateBatch(request)`. Trata cada valor de una solicitud como datos no confiables, lee
  cualquier clave de API del entorno y no de la solicitud, y lanza un `ProviderError` sin secretos
  cuando una llamada falla.
- `ProviderKind` es `"llm"` o `"machine-translation"`. El `kind` de tu objeto proveedor es solo
  descriptivo: el SDK decide la generación de plurales y qué analiza el guardián de datos sensibles
  según el tipo del `id` de proveedor configurado, no según este campo.
- `TranslateRequest` lleva `sourceLocale`, `targetLocale`, las `entries` que traducir y, de forma
  opcional, `glossary` (el resultado de [`glossaryForLocale`](https://verbatra.kreitz-webdev.de/es/docs/sdk/config.md#glossaryforlocale) para el locale de
  destino), `tone`, `maxLength` y `pluralCategories`. También lleva `extractPlaceholders` y, en
  algunos formatos, `comparePlaceholders`, que tu proveedor usa para la comprobación de integridad,
  además de `signal`, que cancela una llamada en curso, `onRepair` y
  `foreignPlaceholders`: por clave, los tokens de otra sintaxis de placeholder que hay que proteger
  como placeholders.
- `TranslateResult` devuelve `values` e `integrity` por clave, más `usage`, `notices` y
  `reviewFlags` opcionales. Una clave ausente de `values` cuenta como no traducida, así que un
  proveedor puede omitir un valor que no puede traducir con seguridad. En cualquier caso, el SDK
  pasa cada valor devuelto por su propia comprobación de integridad.
- `ProviderRetryListener` es el tipo de `CreateProviderHooks.onRetry`. Recibe un `ProviderRetry`,
  `{ attempt, delayMs?, status? }`, por reintento, y cada llamada se convierte en un
  [evento de progreso](https://verbatra.kreitz-webdev.de/es/docs/sdk/run.md#eventos-de-progreso) `provider-retry`.
- `ProviderNetwork` es lo que contiene `CreateProviderContext.network` cuando la política de red
  limita a dónde puede conectarse un proveedor: la `policy` efectiva, el `env` desde el que se
  resolvió el endpoint y `deps` opcionales para pruebas. Un proveedor propio puede ignorarlo, pero
  entonces a sus solicitudes solo se aplica la comprobación que hace el SDK antes de llamar a la
  fábrica.

**Ejemplo:** [Prueba tu integración sin clave](https://verbatra.kreitz-webdev.de/es/docs/programmatic-api.md#prueba-tu-integración-sin-clave)
