Translator
Building block for the Web’s Built-in Translator API (on-demand language packs). Block-level translation with inline placeholder serialization, casing restoration, and a snapshot-based restore. See useTranslator for React.
Usage
import { translate } from "@web-ai-sdk/translator";
const controller = translate({ sourceLanguage: "en", targetLanguage: "pt", roots: "[data-translate-root]", onProgress: (event) => console.log(event),});
const { blocksTranslated } = await controller.done;console.log(`Translated ${blocksTranslated} blocks.`);
// To roll back:controller.restore();translate() returns a controller synchronously. Await controller.done for completion; call controller.cancel() to abort or controller.restore() to snap every translated block back to its original children.
How it works
The translator walks each root (default [data-translate-root]), finds block-level elements (default selector covers p, h1-h6, li, blockquote, figcaption, dt, dd, summary), and serializes their children into a single token string. Inline elements like <a>, <strong>, <code> become opaque placeholders that round-trip through the model without being translated.
After the model returns translated text, the wrapper rebuilds the block: re-inserts the placeholders, restores their original markup, and applies casing rules so the translated text matches the source’s capitalization pattern.
A snapshot of each block’s original children is kept in memory; controller.restore() swaps them back in. Calling restore() twice on the same block is a no-op.
Block-level vs string-level
The wrapper is block-level by design. For a one-shot string translation, use getOrCreateTranslator(api, opts) directly:
import { getOrCreateTranslator, getTranslatorApi } from "@web-ai-sdk/translator";
const api = getTranslatorApi();if (!api) return; // unavailable
const translator = await getOrCreateTranslator(api, { sourceLanguage: "en", targetLanguage: "pt",});
const output = await translator.translate("Hello, world.");Sessions are cached per (sourceLanguage, targetLanguage) pair, so switching back and forth is instant.
Untranslatable tokens
Some content shouldn’t be translated: emoji, URLs, numbers, single-letter words. The wrapper detects standalone tokens of this kind and skips the block (onProgress: { phase: "block-skipped", reason: "untranslatable-token" }). Override via opaqueInlineTags to add tags that should be kept verbatim.
Errors and unavailability
The translator is feature-detected. On browsers without Translator, translate() returns a controller whose done resolves immediately with { blocksTranslated: 0 }; no error is thrown. Branch on isTranslatorAvailable() if you want to render a fallback explicitly.
controller.cancel() is supported at any time. Cancelling mid-translation throws AbortError from controller.done.