Skip to content
runonweb

Lingo: Translation

Translate text entirely in the browser. 21–55 MB per language pair, 58 languages to and from English, plus pairs like Spanish → Italian or Japanese → French through English. Private, offline after the first load, and free of per-character pricing.

Try Lingo

Runs in this tab · nothing uploaded

Pick two languages and a sample, or type your own
Try with
Cached after the first load · 106 pairs in the SDK
Translation will appear here…

What it does

Translate text without a server.

  • Marian NMT students distilled from a larger teacher, run through the Bergamot WASM runtime (int8 GEMM, single worker, no COOP/COEP headers needed).
  • base-memory when available (≈37 MB, on par with cloud translators within ~5% COMET), tiny for the rest (≈22 MB). PAIRS lists every pair with size and COMET score.
  • Pairs without a direct model pivot through English: es → fr loads es-en and en-fr.
  • Pass html: true to translate markup while keeping the tags in place.
  • Codes are BCP 47: zh is Simplified Chinese, zh-Hant Traditional. One Translator can serve many pairs; new pairs download on demand.
  • Prefer one model for 100 languages? Pass model: "Xenova/m2m100_418M" (MIT, ~640 MB, Transformers.js, slower).

Specifications

Package
runonweb/translate
License
MPL-2.0
Download
~22 MB (tiny)~37 MB (base-memory)~50–55 MB (ja, ko, zh)
Quantization
int8 on WASM (Bergamot)
Backend
WASM only
Input
Text

Get started with runonweb/translate

pnpm add runonweb
runonweb/translatets
import { Translator, PAIRS } from 'runonweb/translate'

// en → es: base-memory, ~37 MB
const translator = new Translator({ from: 'en', to: 'es' })
await translator.load()

const { text } = await translator.translate('Hello world')
// "Hola mundo"

// same instance, another pair (downloaded on demand)
await translator.translate('Bonjour', { from: 'fr', to: 'en' })

// no direct es-it model: pivots through English (es-en + en-it)
await translator.translate('¿Dónde está la estación?', { from: 'es', to: 'it' })

// keep the markup, translate the text nodes
await translator.translate('<b>Hello</b> world', { html: true })

translator.dispose()

PAIRS['en-ja'] // { architecture: 'base-memory', bytes: 43849787, comet: 0.90, … }

Same shape everywhere: construct, load(), run, dispose(). Pass onProgressto show download progress on first use.

Questions about Lingo

Does Lingo run on device?

Yes. Weights download once and stay in the browser. Nothing is sent to a server.

Which model does Lingo use?

Firefox Translations (Marian NMT) by Mozilla, under MPL-2.0. runonweb loads the midudev/firefox-translations weights and runs int8 on WASM (Bergamot).

How big is the Lingo download?

~22 MB (tiny) · ~37 MB (base-memory) · ~50–55 MB (ja, ko, zh), fetched once on first use. The browser caches the weights, so later loads skip the network and keep working offline.

What does Lingo cost?

Nothing. runonweb is free and MIT-licensed, with no device limits, tokens or sign-in. The weights keep their own license: MPL-2.0.

Which browsers support Lingo?

Any modern browser with WebAssembly. This model does not use WebGPU yet: it fails at session creation in ONNX Runtime Web, so runonweb pins it to WASM.

Can I use a different model with runonweb/translate?

Yes. See each module’s docs for the options it accepts (model, size, language…). Check the license and test both backends before shipping.