diff --git a/main.js b/main.js index 196e25e5..7719145c 100644 --- a/main.js +++ b/main.js @@ -548,6 +548,18 @@ const SETTINGS_DEFAULTS = { "https://libretranslate.com/translate", ], translateApiKey: "", + // Which translator provider the chip and auto-translate use. + // "libretranslate" — POST text to a hosted LibreTranslate peer (the + // translateEndpoints list). Fast, no setup, but the peer sees the page. + // "bergamot" — Mozilla's WASM translator running on the user's own + // computer; the page's text never leaves the device. Models mirror + // from bergamot.x on first use. The WASM runtime + model manager are + // NOT bundled yet (2026-10-04); picking this today still routes + // through LibreTranslate with a one-time console notice, so the user + // can declare their preference now and get the real engine when it + // ships without changing the setting again. + // The setting lives in Settings › Language. + translateProvider: "libretranslate", // Light up the URL-bar translate chip when the loaded page's language // differs from the user's preferred one. Clicking the chip translates // the page in place; clicking it again reverts. @@ -5067,6 +5079,35 @@ async function translatorLibreTranslate(texts, from, to) { } throw new Error(`all ${peers.length} translator peer(s) failed — ${errors[errors.length - 1] || "no detail"}`); } +// Bergamot (on-device) provider. The runtime is Mozilla's WASM port of +// Marian NMT; models mirror from `bergamot.x` to the user's Theseus +// profile on first use. Neither the runtime nor any model is bundled in +// Theseus yet (2026-10-04) — the plumbing exists so the setting is real +// today and the engine can land in a later release without churning the +// chip, the picker or the chunking. +// +// Behaviour until the runtime lands: +// - Log a one-time per-session notice that we're falling back. +// - Delegate to translatorLibreTranslate so the user still gets a +// translated page, exactly as if they'd left the provider set to +// LibreTranslate. The setting carries forward. +let _bergamotFallbackNoticed = false; +async function translatorBergamot(texts, from, to) { + if (!_bergamotFallbackNoticed) { + console.warn("[translate] Bergamot provider picked, but the on-device runtime is not bundled yet — falling back to LibreTranslate. See bergamot.x for status."); + _bergamotFallbackNoticed = true; + } + return translatorLibreTranslate(texts, from, to); +} +// Pick the provider by setting. Keep this the only place that reads +// `settings.translateProvider` — `translateAll` and `translateActiveTab` +// stay provider-agnostic, so adding or removing engines only touches +// this dispatcher. +async function translatorCall(texts, from, to) { + const provider = String(settings.translateProvider || "libretranslate").toLowerCase(); + if (provider === "bergamot") return translatorBergamot(texts, from, to); + return translatorLibreTranslate(texts, from, to); +} // Chunk a texts array so each POST stays under a reasonable size — LibreTranslate // instances vary (3-5 KB is a safe shared floor), and a monolithic 50-page POST // is also slower to recover from an upstream drop than four 12-page POSTs. @@ -5088,7 +5129,7 @@ async function translateAll(texts, from, to) { const chunks = chunkTexts(texts); const out = []; for (const chunk of chunks) { - const got = await translatorLibreTranslate(chunk, from, to); + const got = await translatorCall(chunk, from, to); for (const s of got) out.push(s); } return out; diff --git a/settings.html b/settings.html index 062cca01..064955cf 100644 --- a/settings.html +++ b/settings.html @@ -372,6 +372,14 @@
When the page's declared language is different from yours, Theseus can translate its visible text in place. The source stays untouched — click the chip again to revert.
+