From 663b97ae39a8975caf3bf32e94079473cdc03381 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Wed, 30 Jul 2025 20:13:37 -0600 Subject: [PATCH] translate command --- src/modules/misc/translate.js | 204 ++++++++++++++++++++++++++++++++ src/modules/utility/charinfo.js | 5 +- 2 files changed, 206 insertions(+), 3 deletions(-) create mode 100644 src/modules/misc/translate.js diff --git a/src/modules/misc/translate.js b/src/modules/misc/translate.js new file mode 100644 index 0000000..7823f9f --- /dev/null +++ b/src/modules/misc/translate.js @@ -0,0 +1,204 @@ +const Command = require("#lib/command.js"); +const InteractionCommand = require("#lib/interactionCommand.js"); + +const {ApplicationCommandOptionTypes} = require("#util/dconstants.js"); +const {Icons} = require("#util/constants.js"); + +const languageCodeToName = { + en: "English", + sq: "Albanian", + ar: "Arabic", + az: "Azerbaijani", + eu: "Basque", + bn: "Bengali", + bg: "Bulgarian", + ca: "Catalan", + "zh-Hans": "Chinese", + "zh-Hant": "Chinese (traditional)", + cs: "Czech", + da: "Danish", + nl: "Dutch", + eo: "Esperanto", + et: "Estonian", + fi: "Finnish", + fr: "French", + gl: "Galician", + de: "German", + el: "Greek", + he: "Hebrew", + hi: "Hindi", + hu: "Hungarian", + id: "Indonesian", + ga: "Irish", + it: "Italian", + ja: "Japanese", + ko: "Korean", + ky: "Kyrgyz", + lv: "Latvian", + lt: "Lithuanian", + ms: "Malay", + nb: "Norwegian", + fa: "Persian", + pl: "Polish", + pt: "Portuguese", + "pt-BR": "Portuguese (Brazil)", + ro: "Romanian", + ru: "Russian", + sk: "Slovak", + sl: "Slovenian", + es: "Spanish", + sv: "Swedish", + tl: "Tagalog", + th: "Thai", + tr: "Turkish", + uk: "Ukrainian", + ur: "Urdu", + auto: "Auto Detect", +}; +const languageCodeToFlag = { + en: "\ud83c\uddfa\ud83c\uddf8", + sq: "\ud83c\udde6\ud83c\uddf1", + ar: "\ud83c\uddea\ud83c\uddec", + az: "\ud83c\udde6\ud83c\uddff", + eu: "\ud83d\udfe5", + bn: "\ud83c\udde7\ud83c\udde9", + bg: "\ud83c\udde7\ud83c\uddec", + ca: "\ud83c\udde6\ud83c\udde9", + "zh-Hans": "\ud83c\udde8\ud83c\uddf3", + "zh-Hant": "\ud83c\udde8\ud83c\uddf3", + cs: "\ud83c\udde8\ud83c\uddff", + da: "\ud83c\udde9\ud83c\uddf0", + nl: "\ud83c\uddf3\ud83c\uddf1", + eo: "\ud83d\udfe9", + et: "\ud83c\uddea\ud83c\uddea", + fi: "\ud83c\uddeb\ud83c\uddee", + fr: "\ud83c\uddeb\ud83c\uddf7", + gl: "\ud83d\udfe6", + de: "\ud83c\udde9\ud83c\uddea", + el: "\ud83c\uddec\ud83c\uddf7", + he: "\ud83c\uddee\ud83c\uddf1", + hi: "\ud83c\uddee\ud83c\uddf3", + hu: "\ud83c\udded\ud83c\uddfa", + id: "\ud83c\uddee\ud83c\udde9", + ga: "\ud83c\uddee\ud83c\uddea", + it: "\ud83c\uddee\ud83c\uddf9", + ja: "\ud83c\uddef\ud83c\uddf5", + ko: "\ud83c\uddf0\ud83c\uddf7", + ky: "\ud83c\uddf0\ud83c\uddec", + lv: "\ud83c\uddf1\ud83c\uddfb", + lt: "\ud83c\uddf1\ud83c\uddf9", + ms: "\ud83c\uddf2\ud83c\uddfe", + nb: "\ud83c\uddf3\ud83c\uddf4", + fa: "\ud83c\uddee\ud83c\uddf7", + pl: "\ud83c\uddf5\ud83c\uddf1", + pt: "\ud83c\uddf5\ud83c\uddf9", + "pt-BR": "\ud83c\udde7\ud83c\uddf7", + ro: "\ud83c\uddf7\ud83c\uddf4", + ru: "\ud83c\uddf7\ud83c\uddfa", + sk: "\ud83c\uddf8\ud83c\uddf0", + sl: "\ud83c\uddf8\ud83c\uddee", + es: "\ud83c\uddea\ud83c\uddf8", + sv: "\ud83c\uddf8\ud83c\uddfb", + tl: "\ud83c\uddf5\ud83c\udded", + th: "\ud83c\uddf9\ud83c\udded", + tr: "\ud83c\uddf9\ud83c\uddf7", + uk: "\ud83c\uddfa\ud83c\udde6", + ur: "\ud83c\uddf5\ud83c\uddf0", +}; + +const translate = new Command("translate"); +translate.category = "misc"; +translate.helpText = "Translate"; +translate.usage = "<--from=lang> <--to=lang> [string]"; +translate.addAlias("tr"); +translate.callback = async function (msg, line, args, {from, to, languages}) { + if (!hf.config.libretranslate) return "No translation provider is set on this instance."; + + if (languages) { + return Object.keys(languageCodeToName) + .map((code) => `${languageCodeToFlag} ${languageCodeToName[code]} - \`${code}\``) + .join("\n"); + } + + let text = args?.join(" ") ?? ""; + if (text == null || text == "") { + if (msg.messageReference?.messageID) { + const reply = await msg.channel.getMessage(msg.messageReference.messageID); + text = reply.content; + } else { + return "Arguments or reply required."; + } + } + + if (text == null || text == "") return "No text provided."; + + if (from == null) from = "auto"; + if (to == null) to = "en"; + if (languageCodeToName[from] == null) { + from = "auto"; + } + if (languageCodeToName[to] == null) { + to = "en"; + } + + const res = await fetch(`${hf.config.libretranslate}/translate`, { + method: "POST", + headers: {"Content-Type": "application/json"}, + body: JSON.stringify({ + q: text, + source: from, + target: to, + alternatives: 0, + api_key: "", + }), + }).then((res) => res.json()); + + const fromLanguage = res.detectedLanguage?.language ?? from; + const confidence = res.detectedLanguage ? ` (${res.detectedLanguage.confidence}%)` : ""; + + return `-# Translated from ${languageCodeToFlag[fromLanguage] ?? Icons.blank} **${ + languageCodeToName[fromLanguage] ?? `` + }**${confidence} to ${languageCodeToFlag[to] ?? Icons.blank} **${ + languageCodeToName[to] ?? `` + }**\n${res.translatedText + .replaceAll("`", "\\`") + .replaceAll("*", "\\*") + .replaceAll("_", "\\_") + .replaceAll("||", "|\u200b|") + .replaceAll("~~", "~\u200b~")}`; +}; +hf.registerCommand(translate); + +const translateInteraction = new InteractionCommand("translate"); +translateInteraction.helpText = "Translate"; +translateInteraction.options.content = { + name: "text", + type: ApplicationCommandOptionTypes.STRING, + description: "Text to translate", + required: true, + default: "", +}; +translateInteraction.options.from = { + name: "from", + type: ApplicationCommandOptionTypes.STRING, + description: "Language to translate from (default: Auto Detect)", + default: "auto", + choices: Object.entries(languageCodeToName).map(([code, name]) => ({name, value: code})), +}; +translateInteraction.options.to = { + name: "to", + type: ApplicationCommandOptionTypes.STRING, + description: "Language to translate to (default: English)", + default: "en", + choices: Object.entries(languageCodeToName) + .filter(([code]) => code !== "auto") + .map(([code, name]) => ({name, value: code})), +}; +translateInteraction.callback = async function (interaction) { + const text = this.getOption(interaction, "text"); + const from = this.getOption(interaction, "from"); + const to = this.getOption(interaction, "to"); + + return translate.callback(interaction, text, text.split(" "), {from, to}); +}; +hf.registerCommand(translateInteraction); diff --git a/src/modules/utility/charinfo.js b/src/modules/utility/charinfo.js index c136451..dff7027 100644 --- a/src/modules/utility/charinfo.js +++ b/src/modules/utility/charinfo.js @@ -35,8 +35,8 @@ charinfo.callback = async function (msg, line) { .replace("_", "\\_") .replace("`", "\\`") .replace("*", "\\*") - .replace("<", "\\<") - .replace(">", "\\>")}` + .replace("<", "\\<") + .replace(">", "\\>")}` ) .join("\n"); @@ -71,4 +71,3 @@ charinfoInteraction.callback = async function (interaction) { return charinfo.callback(interaction, content); }; hf.registerCommand(charinfoInteraction); -