theseus/bundled-addons/translate/index.js

33 lines
1.3 KiB
JavaScript
Raw Normal View History

feat(theseus/translate): new sidebar add-on — right-click Translate selection Adds a bundled add-on `translate` with a sidebar panel + a right-click "Translate selection" menu item. Two swappable backends: - LibreTranslate (default) — free MIT engine; the panel's Settings tab lets the user point at any instance (public or self-hosted) and drop in an API key if one's required. - Google (unofficial free endpoint at translate.googleapis.com/ translate_a/single) — no key, wide coverage, but unofficial and Google can break it any time. Opt-in fallback. Flow: user selects text on a page, right-clicks -> "Translate selection". Add-on's context-menu handler stashes the selection under storage.__pending and calls api.revealSidebar("main"); the panel loads, drains __pending on first paint, and translates. Ctrl/Cmd+Enter in the input textarea also translates. Source + target language choices, browser-language default target, swap button, copy-to- clipboard on the output, settings gear. Depends on a new "context-menu-item" capability + api.revealSidebar hook in addons-host.js / main.js. Those wiring changes are prepared but not committed here — a parallel session is refactoring the same functions concurrently, so the safe path is to land translate/ first and let the wiring go in alongside the next host-facing commit. Until the wiring lands, the manifest's "context-menu-item" cap is silently dropped (per validateManifest's unknown-caps policy) and the sidebar panel + the panel's translation UI still work standalone — the right-click entry point is what's gated.
2026-09-20 17:50:06 +02:00
// Translate — right-click a selection, get a translation in the sidebar.
//
// One sidebar panel; the panel HTML does the actual translation (LibreTranslate
// or Google unofficial free endpoint, user's pick). When the user picks
// "Translate selection" from a page's right-click menu we stash the selection
// under storage.__pending and reveal our sidebar; the panel reads __pending on
// load / on visibility change and translates immediately.
module.exports = {
activate(api) {
api.registerSidebarPanel({
id: "main",
title: "Translate",
icon: "🌐",
page: "panel.html",
});
api.onMessage("context-menu", async (payload) => {
const text = String(payload && payload.selectionText || "").trim();
if (!text) { api.log("context-menu fired with no selection"); return; }
// Cap what we stash to keep storage tiny; the address bar and menu already
// truncate visually, but the raw selection can be huge.
const clip = text.length > 12_000 ? text.slice(0, 12_000) : text;
api.storage.set("__pending", { text: clip, host: payload.host || "", at: Date.now() });
api.revealSidebar("main");
api.log(`context-menu → translate ${clip.length} chars from ${payload.host || "?"}`);
return { ok: true };
});
api.log("registered translate panel + context-menu item");
},
};