/* * translate.js * A HexDroid script * * Auto-translates incoming messages and adds a /tr command for manual one-off translation. * * Backend: a LibreTranslate instance (https://libretranslate.com or self-hosted). * LibreTranslate is FOSS and has a /detect + /translate JSON API * Point TR_ENDPOINT at your own instance for privacy + no rate limits. If your instance needs a key, set TR_KEY. * * The host must permit network to TR_ENDPOINT (ScriptHost.isNetworkAllowed). */ var TR_ENDPOINT = "https://libretranslate.com"; var TR_KEY = ""; // API key for your instance var SHOW_ORIGINAL_LANG = true; // prefix the translated line with the detected lang function appLang() { // e.g. "en". Falls back to English if the host doesn't expose the setting. return (setting("appLanguage") || "en").toLowerCase().slice(0, 2); } // POST JSON and parse the reply. cb(err, obj). function postJson(path, payload, cb) { http.post(TR_ENDPOINT + path, JSON.stringify(payload), function (res) { if (!res.ok) { cb(res.error || ("HTTP " + res.status), null); return; } try { cb(null, JSON.parse(res.body)); } catch (e) { cb("bad JSON from server", null); } }); } function detect(text, cb) { postJson("/detect", { q: text, api_key: TR_KEY }, function (err, arr) { if (err) { cb(err, null); return; } // /detect returns [{ confidence, language }], best first. if (arr && arr.length && arr[0].language) cb(null, arr[0].language); else cb("no language detected", null); }); } function translate(text, source, target, cb) { postJson("/translate", { q: text, source: source, target: target, format: "text", api_key: TR_KEY }, function (err, obj) { if (err) { cb(err, null); return; } if (obj && obj.translatedText) cb(null, obj.translatedText); else cb("no translation returned", null); }); } // auto-translate incoming foreign messages on("TEXT", function (e) { if (e.isMine) return; // don't translate our own echo var text = e.text; if (!text || text.length < 2) return; var want = appLang(); detect(text, function (err, src) { if (err) { log("detect failed:", err); return; } src = src.toLowerCase().slice(0, 2); if (src === want) return; translate(text, src, want, function (err2, out) { if (err2) { log("translate failed:", err2); return; } if (!out || out === text) return; var prefix = SHOW_ORIGINAL_LANG ? ("\u0002[" + src + "\u2192" + want + "]\u0002 ") : ""; // Inject a line under the original, attributed to the same nick. echo(e.buffer, e.from, prefix + out); }); }); }); // /tr translate something manually command("tr", function (args, e) { var text = (args || "").trim(); if (!text) { echo(e.buffer, "*** Usage: /tr "); return; } var want = appLang(); detect(text, function (err, src) { src = (err ? want : src).toLowerCase().slice(0, 2); translate(text, src === want ? "auto" : src, want, function (err2, out) { if (err2) { echo(e.buffer, "*** /tr failed: " + err2); return; } echo(e.buffer, "*** (" + src + "\u2192" + want + ") " + out); }); }); }); log("translate.js loaded (endpoint " + TR_ENDPOINT + ")");