NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name DiscordAutoTranslator // @namespace http://tampermonkey.net/ // @version 1.14 // @description Automatically translates messages in channels/private messages into the selected language in Discord Web. // @match *://discord.com/* // @author Timka251 & eretly // @grant GM_xmlhttpRequest // @icon https://i.pinimg.com/236x/68/95/31/689531dc04ba222ab7af0fa34dc63644.jpg // @run-at document-end // @license BSD-3-Clause // @downloadURL https://update.greasyfork.org/scripts/506133/DiscordAutoTranslator.user.js // @updateURL https://update.greasyfork.org/scripts/506133/DiscordAutoTranslator.meta.js // ==/UserScript== /* * Copyright 2024 eretly * Licensed under the BSD 3-Clause License. */ (function () { 'use strict'; const languageSelector = document.createElement('div'); languageSelector.style.position = 'fixed'; languageSelector.style.bottom = '10px'; languageSelector.style.right = '10px'; languageSelector.style.backgroundColor = '#2f3136'; languageSelector.style.padding = '10px'; languageSelector.style.zIndex = '1000'; languageSelector.style.border = '1px solid #ccc'; languageSelector.style.borderRadius = '5px'; languageSelector.style.display = 'none'; const sourceLangSelect = document.createElement('select'); const targetLangSelect = document.createElement('select'); const toggleButton = document.createElement('button'); toggleButton.style.backgroundColor = '#7289da'; toggleButton.style.color = 'white'; toggleButton.style.border = 'none'; toggleButton.style.borderRadius = '5px'; toggleButton.style.cursor = 'pointer'; toggleButton.style.marginTop = '5px'; toggleButton.style.padding = '5px'; toggleButton.style.display = 'block'; toggleButton.textContent = 'Enable Translator'; const languages = { 'af': 'Afrikaans', 'sq': 'Albanian', 'am': 'Amharic', 'ar': 'Arabic', 'hy': 'Armenian', 'as': 'Assamese', 'ay': 'Aymara', 'az': 'Azerbaijani', 'bm': 'Bambara', 'eu': 'Basque', 'be': 'Belarusian', 'bn': 'Bengali', 'bho': 'Bhojpuri', 'bs': 'Bosnian', 'bg': 'Bulgarian', 'ca': 'Catalan', 'ceb': 'Cebuano', 'zh-CN': 'Chinese (Simplified)', 'zh-TW': 'Chinese (Traditional)', 'co': 'Corsican', 'hr': 'Croatian', 'cs': 'Czech', 'da': 'Danish', 'dv': 'Dhivehi', 'doi': 'Dogri', 'nl': 'Dutch', 'en': 'English', 'eo': 'Esperanto', 'et': 'Estonian', 'ee': 'Ewe', 'fil': 'Filipino (Tagalog)', 'fi': 'Finnish', 'fr': 'French', 'fy': 'Frisian', 'gl': 'Galician', 'ka': 'Georgian', 'de': 'German', 'el': 'Greek', 'gn': 'Guarani', 'gu': 'Gujarati', 'ht': 'Haitian Creole', 'ha': 'Hausa', 'haw': 'Hawaiian', 'he': 'Hebrew', 'hi': 'Hindi', 'hmn': 'Hmong', 'hu': 'Hungarian', 'is': 'Icelandic', 'ig': 'Igbo', 'ilo': 'Ilocano', 'id': 'Indonesian', 'ga': 'Irish', 'it': 'Italian', 'ja': 'Japanese', 'jv': 'Javanese', 'kn': 'Kannada', 'kk': 'Kazakh', 'km': 'Khmer', 'rw': 'Kinyarwanda', 'gom': 'Konkani', 'ko': 'Korean', 'kri': 'Krio', 'ku': 'Kurdish', 'ckb': 'Kurdish (Sorani)', 'ky': 'Kyrgyz', 'lo': 'Lao', 'la': 'Latin', 'lv': 'Latvian', 'ln': 'Lingala', 'lt': 'Lithuanian', 'lg': 'Luganda', 'lb': 'Luxembourgish', 'mk': 'Macedonian', 'mai': 'Maithili', 'mg': 'Malagasy', 'ms': 'Malay', 'ml': 'Malayalam', 'mt': 'Maltese', 'mi': 'Maori', 'mr': 'Marathi', 'mni-Mtei': 'Meiteilon (Manipuri)', 'lus': 'Mizo', 'mn': 'Mongolian', 'my': 'Myanmar (Burmese)', 'ne': 'Nepali', 'no': 'Norwegian', 'ny': 'Nyanja (Chichewa)', 'or': 'Odia (Oriya)', 'om': 'Oromo', 'ps': 'Pashto', 'fa': 'Persian', 'pl': 'Polish', 'pt': 'Portuguese (Portugal, Brazil)', 'pa': 'Punjabi', 'qu': 'Quechua', 'ro': 'Romanian', 'ru': 'Russian', 'sm': 'Samoan', 'sa': 'Sanskrit', 'gd': 'Scots Gaelic', 'nso': 'Sepedi', 'sr': 'Serbian', 'st': 'Sesotho', 'sn': 'Shona', 'sd': 'Sindhi', 'si': 'Sinhala (Sinhalese)', 'sk': 'Slovak', 'sl': 'Slovenian', 'so': 'Somali', 'es': 'Spanish', 'su': 'Sundanese', 'sw': 'Swahili', 'sv': 'Swedish', 'tl': 'Tagalog (Filipino)', 'tg': 'Tajik', 'ta': 'Tamil', 'tt': 'Tatar', 'te': 'Telugu', 'th': 'Thai', 'ti': 'Tigrinya', 'ts': 'Tsonga', 'tr': 'Turkish', 'tk': 'Turkmen', 'ak': 'Twi (Akan)', 'uk': 'Ukrainian', 'ur': 'Urdu', 'ug': 'Uyghur', 'uz': 'Uzbek', 'vi': 'Vietnamese', 'cy': 'Welsh', 'xh': 'Xhosa', 'yi': 'Yiddish', 'yo': 'Yoruba', 'zu': 'Zulu' }; const sortedLanguages = Object.entries(languages).sort((a, b) => a[1].localeCompare(b[1])); sortedLanguages.forEach(([code, name]) => { const option1 = document.createElement('option'); option1.value = code; option1.textContent = name; sourceLangSelect.appendChild(option1); const option2 = document.createElement('option'); option2.value = code; option2.textContent = name; targetLangSelect.appendChild(option2); }); const style = document.createElement('style'); style.textContent = ` select { color: white; background-color: #2f3136; border: 1px solid #ccc; border-radius: 5px; padding: 5px; margin: 5px 0; } .source-lang-label { color: white; margin-left: 5px; } .target-lang-label { color: white; margin-left: 5px; } `; document.head.appendChild(style); const sourceLangLabel = document.createElement('span'); sourceLangLabel.classList.add('source-lang-label'); sourceLangLabel.textContent = 'Source Language'; const targetLangLabel = document.createElement('span'); targetLangLabel.classList.add('target-lang-label'); targetLangLabel.textContent = 'Target Language'; const sourceLangDiv = document.createElement('div'); sourceLangDiv.appendChild(sourceLangSelect); sourceLangDiv.appendChild(sourceLangLabel); const targetLangDiv = document.createElement('div'); targetLangDiv.appendChild(targetLangSelect); targetLangDiv.appendChild(targetLangLabel); languageSelector.appendChild(sourceLangDiv); languageSelector.appendChild(targetLangDiv); languageSelector.appendChild(toggleButton); document.body.appendChild(languageSelector); const savedSourceLang = localStorage.getItem('sourceLang') || 'en'; const savedTargetLang = localStorage.getItem('targetLang') || 'ru'; let isTranslatorActive = localStorage.getItem('isTranslatorActive') === 'true'; sourceLangSelect.value = savedSourceLang; targetLangSelect.value = savedTargetLang; let sourceLang = savedSourceLang; let targetLang = savedTargetLang; let activeRequests = []; if (isTranslatorActive) { toggleButton.textContent = 'Disable Translator'; translateAllMessages(); } function updateLanguages() { sourceLang = sourceLangSelect.value; targetLang = targetLangSelect.value; localStorage.setItem('sourceLang', sourceLang); localStorage.setItem('targetLang', targetLang); if (isTranslatorActive) { translateAllMessages(); } } sourceLangSelect.addEventListener('change', updateLanguages); targetLangSelect.addEventListener('change', updateLanguages); function updateTranslatorState() { isTranslatorActive = !isTranslatorActive; localStorage.setItem('isTranslatorActive', isTranslatorActive); toggleButton.textContent = isTranslatorActive ? 'Disable Translator' : 'Enable Translator'; if (isTranslatorActive) { translateAllMessages(); } else { resetTranslations(); cancelActiveRequests(); } } toggleButton.addEventListener('click', updateTranslatorState); function translateText(text, callback) { const url = `https://translate.google.com/m?hl=${targetLang}&sl=${sourceLang}&tl=${targetLang}&ie=UTF-8&prev=_m&q=${encodeURIComponent(text)}`; const request = GM_xmlhttpRequest({ method: "GET", url: url, onload: function (response) { if (response.status === 200) { const parser = new DOMParser(); const doc = parser.parseFromString(response.responseText, "text/html"); const translatedTextElement = doc.querySelector('.result-container'); if (translatedTextElement) { callback(translatedTextElement.textContent.trim()); } else { console.error("Translation failed"); } } else { console.error("Error when receiving transfer, status: " + response.status); } activeRequests = activeRequests.filter(req => req !== request); }, onerror: function () { console.error("Network error during transfer request"); activeRequests = activeRequests.filter(req => req !== request); } }); activeRequests.push(request); } function cancelActiveRequests() { activeRequests.forEach(request => { if (request && request.abort) { request.abort(); } }); activeRequests = []; } function annotateMessage(div) { const originalText = div.textContent.trim(); const container = document.createElement('div'); container.style.position = 'relative'; const translatedDiv = document.createElement('div'); translatedDiv.classList.add('translated-message'); translatedDiv.style.color = 'rgb(135, 155, 164)'; translatedDiv.style.marginTop = '0px'; translatedDiv.style.paddingLeft = '8px'; translateText(originalText, function (translatedText) { translatedDiv.textContent = translatedText; container.appendChild(translatedDiv); div.parentNode.insertBefore(container, div.nextSibling); }); } function checkNewDiv() { const divs = document.querySelectorAll('div[id^="message-content-"]'); divs.forEach(div => { if (!div.dataset.processed) { const text = div.textContent; const lang = detectLanguage(text); if (lang === sourceLang && isTranslatorActive) { annotateMessage(div); } div.dataset.processed = 'true'; } }); } function detectLanguage(text) { return text.match(/[a-zA-Z]/) ? 'en' : 'ru'; } function resetTranslations() { const translatedMessages = document.querySelectorAll('.translated-message'); translatedMessages.forEach(msg => msg.remove()); } function translateAllMessages() { resetTranslations(); const divs = document.querySelectorAll('div[id^="message-content-"]'); divs.forEach(div => { const text = div.textContent; const lang = detectLanguage(text); if (lang === sourceLang && isTranslatorActive) { annotateMessage(div); } }); } document.addEventListener('keydown', (event) => { if (event.altKey && (event.key === 't' || event.key === 'е')) { // Alt + T or Alt + Е event.preventDefault(); languageSelector.style.display = languageSelector.style.display === 'none' ? 'block' : 'none'; } }); setInterval(checkNewDiv, 1000); })();