From 0ce2ab95dd4c7092ad2793f4c0368838d0510a2f Mon Sep 17 00:00:00 2001 From: Silent Mode Date: Sun, 4 Oct 2026 14:20:18 +0200 Subject: [PATCH] =?UTF-8?q?Theseus:=20translator=20provider=20setting=20?= =?UTF-8?q?=E2=80=94=20LibreTranslate=20or=20Bergamot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A new row in Settings › Language, Translator engine: a radio between the hosted LibreTranslate backend (the current default) and the on-device Bergamot engine. The chip and auto-translate route through a single dispatcher (translatorCall) that picks the provider by setting; adding or removing an engine only touches the dispatcher. Bergamot's WASM runtime and model manager are not bundled yet, so the Bergamot provider falls back to LibreTranslate for now and prints a one-time console notice. The setting itself is real today — a user can declare their preference and the real engine lands in a later release without the user touching Settings again. See bergamot.x for status. --- main.js | 43 ++++++++++++++++++++++++++++++++++++++++++- settings.html | 28 ++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) 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 @@

Page translation

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.

+
+
Translator engine
+
Where the translation runs. Both read the chip the same way; the difference is who sees the page's text. Details at bergamot.x.
+
+ + +
+
Translate automatically
When the page's declared language is different from yours (and both are supported by the translator), Theseus translates it in place as soon as it loads. Turn this off to leave pages in their original language — the globe chip's menu still offers a one-click translation.
@@ -1225,6 +1233,26 @@ }; if (C.onSettingsUpdate) C.onSettingsUpdate((next) => render(next.translateEndpoints || [])); })(); + // ---- Translator engine (Language) — radio between the hosted + // LibreTranslate backend and the on-device Bergamot engine. Bergamot + // is wired as a provider but its WASM runtime is not bundled yet; + // picking it today still routes through LibreTranslate (see main.js + // translatorBergamot stub). Lives in the same Language section so + // the user sees the two choices beside each other and side effects + // (chip tooltip) stay in context. + (function () { + const radios = document.querySelectorAll('input[name="translateProvider"]'); + if (!radios.length) return; + const current = s.translateProvider || "libretranslate"; + radios.forEach((r) => { + r.checked = (r.value === current); + r.addEventListener("change", () => { if (r.checked) C.set("translateProvider", r.value); }); + }); + if (C.onSettingsUpdate) C.onSettingsUpdate((next) => { + const v = next.translateProvider || "libretranslate"; + radios.forEach((r) => { r.checked = (r.value === v); }); + }); + })(); // ---- website language (General page) — friendly wrapper over the // same languageMode/languageValue setting the Anti-fingerprinting Language // row edits. "Auto" = languageMode="show" (follow OS); anything else