Saltar al contenido
Saltar al contenido

Referencia / SDK

Página traducida automáticamente

Esta página se tradujo automáticamente, así que puede contener errores o sonar un poco rara. La versión en inglés es la fuente de la verdad. Leer el original en inglés.

Referencia del SDK

Instala @verbatra/sdk y encuentra la página de referencia de cada punto de entrada. Los archivos que no están en disco llegan al SDK por tu sistema de archivos.

@verbatra/sdk es el motor sobre el que corre el comando verbatra. La CLI es una envoltura fina, así que todo lo que hace la línea de comandos lo puedes hacer en código.

Empieza aquí

¿Eres nuevo en el SDK? Empieza por una de estas páginas:

Páginas de referencia

Estas páginas catalogan toda la superficie pública, agrupada por la tarea que hace cada punto de entrada.

Ejecuta traducciones

translate, Eventos de progreso, resolveDryRun, watch, releaseHeldLocks, pseudolocalize, generateTypes

Inspecciona un proyecto

doctor, dataFlow, dataFlowManifestSchema, extract, check, checkFile, diff, keyIntegrity, localeIntegrity, lockState, loadLockFile, loadProvenance, runStatus, reviewQueue, provenanceReport, provenanceReportPage, previewIcuMessage

Editar

editEntry, previewValueReplace, applyValueReplace

Revisa y retraduce

keyValue, keyContext, localeHistory, localeGitStatus, localeGitDiff, retranslateEntry, approveEntry, rejectEntry, createValueMarker, localeValues, localeValuesPage, approveEntries, rejectEntries, approveLocale, retranslateEntries, BatchInterruptedError

Historial de una clave

keyHistory, runHistory

Entrega e intercambio

readLocaleFileSnapshot, diffLocaleSnapshots, createLocalePathResolver, localeWatchPaths, exportWorkbook, importWorkbook, importTmx, tmxErrorLocation, exportTmx

Configuración y glosario

loadConfig, loadConfigWithMeta, configCandidatePaths, resolveProjectRoot, readGlossaryFile, updateGlossaryTerm, glossaryForLocale, glossaryHits, glossaryDraftCheck, normalizeGlossary, sharedGlossaryTranslations, redactGlossary, readCurrentGlossary, editConfiguredGlossaryTerm, setKeyLengthLimit, defineConfig, verbatraConfigSchema, scaffoldingMetadata, detectProject, isMachineTranslationEnabled, assertMachineTranslationEnabled, assertTargetLocale

Adaptadores, proveedores, redacción

createDefaultRegistry, AdapterRegistry, createFlatFileAdapter, createTreeFileAdapter, nodeAdapterFs, isCustomFormatId, redact, declareProviderKeyEnvVar

RunSummary

budgetStanding

JSON Schemas

runSummarySchema, checkSummarySchema, checkFileSummarySchema, diffSummarySchema, doctorResultSchema, provenanceReportResultSchema, pseudolocalizeResultSchema, generateTypesResultSchema, extractResultSchema, exportWorkbookResultSchema, importTmxResultSchema, exportTmxResultSchema, runHistoryResultSchema, localeSummarySchema, keyProvenanceSchema, provenanceSummarySchema, keyOriginSchema, progressEventSchema, lockWaitEventSchema, renderJsonSchemas, renderOutputJsonSchema, jsonSchemaUrl

Errores

SdkError, ProviderError, AdapterError, projectRelativeMessage, errorHint

Instalación

npm install --save-dev @verbatra/sdk

Requiere Node.js ^22.22.0 || >=24.

Con pnpm, pnpm add --save-dev @verbatra/sdk instala bien, pero sale con 1 y ERR_PNPM_IGNORED_BUILDS: pnpm bloquea los scripts de instalación del SDK de Gemini incluido y de su protobufjs, y deja un pnpm-workspace.yaml sin responder con el que cualquier comando pnpm posterior del proyecto falla igual. Ejecuta pnpm approve-builds una vez y no apruebes ninguna de las dos entradas, o consulta la Solución de problemas para la solución no interactiva. Aquí no hay atajo con npx como en la CLI, porque estás importando una biblioteca, no ejecutando un binario.

Las claves de API y el entorno

El SDK nunca lee, guarda ni acepta una clave de API. El proveedor lee su clave del entorno (por ejemplo ANTHROPIC_API_KEY) cuando se construye. A diferencia de la CLI, el SDK no carga archivos .env: en tu propio script, define tú la variable, por ejemplo con node --env-file=.env script.js.

Cada punto de entrada toma un objeto de entrada cuyo primer campo es la config validada (de loadConfig), salvo donde se indica. La mayoría también acepta un segundo argumento opcional deps que inyecta un registro, un constructor de proveedor o un sistema de archivos; puedes ignorarlo en el uso normal. Consulta El punto de inyección de dependencias para ver hasta dónde llega deps.fs.

El punto de inyección de dependencias

deps.fs sustituye el puerto de sistema de archivos por el que viaja la propia I/O del SDK, con el tipo SdkFs. El punto de inyección es completo: el archivo de estado de la ejecución, el archivo de bloqueo, las herramientas del archivo de glosario, la I/O de libros e intercambio y los propios archivos de locale pasan todos por él, porque los adaptadores de formato leen y escriben a través de un puerto construido con ese mismo objeto. Así una ejecución entera se puede mantener en memoria, que es como las pruebas del propio SDK evitan tocar el disco y como una aplicación anfitriona puede respaldar parte de un proyecto con algo que no sea un disco local.

Propiedad

Tipo

Una implementación tiene que respetar dos puntos del contrato. Las lecturas están acotadas por tamaño: readFileBounded y readBytesBounded reciben un límite de bytes e informan de missing o too-large como estado en lugar de lanzar, que es lo que impide que un archivo hostil o accidentalmente enorme agote la memoria. Se espera que las escrituras sean atómicas, de modo que una caída a mitad de escritura nunca deje un archivo a medio escribir, y createExclusive debe ser atómica frente a otros procesos porque es la primitiva detrás del bloqueo de escritura por locale. Antes de escribir un archivo de locale, el archivo de estado de la ejecución o un archivo de salida como un libro, el SDK llama al mkdir opcional con el directorio padre, así que writeFile y writeBytes no tienen que crearlo; omite mkdir cuando el almacén no tenga directorios. createExclusive no recibe esa llamada y crea él mismo un directorio padre que falte. Los demás miembros obligatorios son fileExists y deleteFile, que tiene éxito aunque el archivo ya no exista.

readDirectory es opcional, para que una implementación escrita antes de que existiera la extracción desde código siga compilando. extract es el único punto de entrada que lo necesita, e informa de su ausencia como EXTRACT_FS_UNSUPPORTED. Lista las entradas inmediatas de un directorio como { name, kind }, donde kind es "file", "directory" u "other". Un enlace simbólico pertenece a "other": el análisis nunca lo sigue, y eso es lo que lo mantiene dentro de las raíces que recibió.

realpath también es opcional. Resuelve todos los enlaces simbólicos de una ruta existente, y las guardas de salida de generateTypes, exportTmx, exportWorkbook y pseudolocalize lo usan para ver dónde cae de verdad una ruta de salida, así que un enlace no puede llevar un archivo escrito fuera de cwd ni sobre un archivo del que depende el proyecto. Sin él, esas guardas comparan las rutas tal como están escritas. Un enlace en la ruta de salida cuyo destino no existe no se puede resolver, así que las guardas dan por hecho que writeFile y writeBytes reemplazan ese enlace en lugar de escribir a través de él, como hace la escritura atómica por defecto.

rename también es opcional.

Mueve un archivo a un nombre nuevo en el mismo directorio en un solo paso atómico, reemplazando cualquier archivo que ya esté allí, y rechaza con un error cuyo code es ENOENT cuando falta el origen. El bloqueo de escritura lo usa para quitar un bloqueo o una guarda de recuperación que dejó un proceso que ya no existe, y para liberar su propio bloqueo: el archivo se aparta con un nombre único <name>.<pid>.<tag>.<uuid>.stale y solo se borra si sigue siendo el registro que esperaba el bloqueo. Cualquier otra cosa, como un bloqueo que otro proceso tomó entretanto, se devuelve a su sitio con createExclusive. Un renombrado que falla con EPERM, EBUSY o EACCES, como informa Windows de un archivo que otro proceso tiene abierto, cuenta como "no recuperado en este sondeo", y el bloqueo sigue sondeando hasta su tiempo límite. Cuando el renombrado que libera un bloqueo falla por cualquier motivo, la liberación recurre a leer el archivo de bloqueo y borrarlo mientras siga llevando el token de quien lo tiene, y reintenta unas cuantas veces, con un sondeo de separación, un fallo con EPERM, EBUSY o EACCES. Un registro que debería devolverse a su sitio pero no puede, porque un tercer proceso tomó la ruta entretanto, se borra, ya que la comprobación del token de quien lo tenía falla a partir de ese momento. Sin rename, el bloqueo lee el archivo y lo borra si no ha cambiado, lo que deja una ventana más amplia en la que se borra un bloqueo que otro proceso acaba de tomar.

Apartar un archivo y devolverlo son dos pasos, así que en intercalados poco frecuentes dos procesos pueden creer durante un momento que tienen el mismo bloqueo. Por eso cada bloqueo registra un token de propiedad aleatorio, y antes de cada escritura que protege el bloqueo (el archivo del locale, el archivo de bloqueo, el archivo de procedencia, el glosario) quien lo tiene comprueba que el archivo de bloqueo sigue llevando su token. Si no es así, la operación se detiene con LOCK_CONTENDED y no escribe nada. Un archivo de bloqueo que falta o lleva otro token al liberar se informa igual cuando la operación terminó bien, porque el otro proceso puede haber cambiado los mismos archivos desde entonces; una operación que falló lanza su propio error. La comprobación y la escritura siguen siendo dos pasos, y nada limita el tiempo entre ellos: quien tiene el bloqueo y se pausa justo después de la comprobación (una máquina suspendida, un proceso detenido, un depurador, un salto del reloj o un bucle de eventos bloqueado mucho tiempo) deja de latir, así que cuando su bloqueo tiene tres intervalos (30 segundos por defecto) otro proceso de la misma máquina lo toma, y quien estaba pausado todavía puede escribir una vez al continuar. La comprobación del token da por hecho que createExclusive crea un archivo que readFileBounded lee de vuelta, así que una implementación propia tiene que cumplirlo.

touch y mtimeMs también son opcionales.

touch(path) pone la fecha de modificación de un archivo existente en el momento actual, y mtimeMs(path) la devuelve en milisegundos, o undefined si falta el archivo. Con touch, quien tiene un bloqueo registra en él un intervalo de latido de 10 segundos y refresca la fecha de modificación del archivo a ese ritmo mientras lo tiene. También la refresca justo después de crearlo, para que la antigüedad del archivo se mida con el reloj de este proceso y no con el del servidor de archivos. Con mtimeMs, un proceso en espera trata como abandonado un bloqueo de su propia máquina cuyo proceso sigue en marcha pero cuyo archivo no se ha refrescado durante tres de los intervalos registrados (30 segundos por defecto). Eso cubre un registro de bloqueo que sobrevivió a su uso, por ejemplo uno devuelto a su sitio para un proceso de Studio, watch o del servidor MCP de larga duración que ya lo había liberado. Una guarda de recuperación no registra latido: se quita cuando su proceso ya no existe de forma demostrable y se ha visto sin cambios en dos sondeos. Sin estos miembros, un bloqueo solo se considera abandonado cuando su proceso ya no existe de forma demostrable.

Un proceso que se cae mientras tiene un bloqueo apartado puede dejar un archivo .stale en .verbatra-local/locks/. Puedes borrar esos archivos a mano sin riesgo, y la siguiente adquisición de un bloqueo los borra cuando el proceso que los movió ya no existe o, si touch está implementado, cuando superan el umbral de caducidad, contado desde que se apartaron. Un sistema de archivos sin touch los conserva hasta que su proceso ya no existe.

deps.fs no alcanza un deps.adapterRegistry suministrado por quien llama. Esos adaptadores los construyó quien llama, así que su acceso a archivos es el que quien llama haya cableado en ellos. Suministrar ambos significa que quien llama es dueño de ese cableado.

La carga de la configuración también queda fuera del punto de inyección. loadConfig y loadConfigWithMeta no reciben deps: leen el propio archivo de configuración del disco real, y un archivo de glosario que nombre la configuración a través de su propia opción fs, así que el deps.fs que recibe un flujo posterior no alcanza ninguna de las dos lecturas. Para mantener un proyecto entero en memoria, pasa el mismo SdkFs como loadConfig({ fs }) y entrega la configuración como configOverride en lugar de como archivo. Los flujos resuelven las rutas contra su propio cwd, así que pásalo también ahí.

El ejemplo mantiene un proyecto entero en memoria solo con los miembros obligatorios. Con el proveedor none la ejecución no necesita clave y deja greeting para que lo traduzca una persona.

Ejemplo
import { check, loadConfig, type SdkFs, translate } from "@verbatra/sdk";

const files = new Map<string, Uint8Array>();
const encoder = new TextEncoder();
const decoder = new TextDecoder();

function bounded(path: string, maxBytes: number) {
  const bytes = files.get(path);
  if (bytes === undefined) return { kind: "missing" } as const;
  if (bytes.byteLength > maxBytes) return { kind: "too-large" } as const;
  return { kind: "ok", bytes } as const;
}

const memoryFs: SdkFs = {
  fileExists: async (path) => files.has(path),
  readFileBounded: async (path, maxBytes) => {
    const read = bounded(path, maxBytes);
    return read.kind === "ok" ? { kind: "ok", content: decoder.decode(read.bytes) } : read;
  },
  readBytesBounded: async (path, maxBytes) => bounded(path, maxBytes),
  writeFile: async (path, data) => {
    files.set(path, encoder.encode(data));
  },
  writeBytes: async (path, data) => {
    files.set(path, data);
  },
  createExclusive: async (path, data) => {
    if (files.has(path)) return false;
    files.set(path, encoder.encode(data));
    return true;
  },
  deleteFile: async (path) => {
    files.delete(path);
  },
};

files.set("/project/locales/en.json", encoder.encode('{ "greeting": "Hello, {{name}}!" }'));

const config = await loadConfig({
  cwd: "/project",
  fs: memoryFs,
  configOverride: {
    sourceLocale: "en",
    targetLocales: ["de"],
    format: "i18next-json",
    files: { pattern: "locales/{locale}.json" },
    provider: { id: "none" },
  },
});

const summary = await translate({ config, cwd: "/project" }, { fs: memoryFs });
const status = await check({ config, cwd: "/project" }, { fs: memoryFs });

console.log(summary.locales[0]?.unfilled, status.inSync); // [ 'greeting' ] false
console.log([...files.keys()]); // the source, verbatra.lock.json, .verbatra-local/run-status.json
Editar en GitHub

En esta página