/*
* 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 <text> translate something manually
command("tr", function (args, e) {
var text = (args || "").trim();
if (!text) { echo(e.buffer, "*** Usage: /tr <text to translate>"); 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 + ")");