├── LICENSE ├── README.md ├── TranslationHandler ├── index.js └── translateAction.js ├── components ├── Indicator.jsx ├── QuickSettings.jsx ├── Settings.jsx ├── SettingsButton.jsx ├── SettingsModal.jsx └── TranslateButton.jsx ├── constants.js ├── i18n ├── en-US.json ├── index.js └── ru.json ├── index.js ├── manifest.json ├── node_modules ├── .package-lock.json └── @iamtraction │ └── google-translate │ ├── .eslintrc.yml │ ├── .gitattributes │ ├── .github │ ├── FUNDING.yml │ └── workflows │ │ ├── nodejs.yml │ │ └── npm-publish.yml │ ├── LICENSE │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── src │ ├── index.js │ └── languages.js │ └── typings │ └── index.d.ts ├── package-lock.json ├── package.json ├── style.css └── utils.js /LICENSE: -------------------------------------------------------------------------------- 1 | --- ORIGINAL LICENSE 2 | --- 3 | 4 | MIT License 5 | 6 | Copyright (c) 2020 Kyza 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | 26 | --- 27 | --- FORKED WORK IS LICENSED UNDER THE FOLLOWING LICENSE 28 | --- 29 | 30 | Open Software License ("OSL") v. 3.0 31 | 32 | This Open Software License (the "License") applies to any original work of 33 | authorship (the "Original Work") whose owner (the "Licensor") has placed the 34 | following licensing notice adjacent to the copyright notice for the Original 35 | Work: 36 | 37 | Licensed under the Open Software License version 3.0 38 | 39 | 1) Grant of Copyright License. Licensor grants You a worldwide, royalty-free, 40 | non-exclusive, sublicensable license, for the duration of the copyright, to do 41 | the following: 42 | 43 | a) to reproduce the Original Work in copies, either alone or as part of a 44 | collective work; 45 | 46 | b) to translate, adapt, alter, transform, modify, or arrange the Original 47 | Work, thereby creating derivative works ("Derivative Works") based upon the 48 | Original Work; 49 | 50 | c) to distribute or communicate copies of the Original Work and Derivative 51 | Works to the public, with the proviso that copies of Original Work or 52 | Derivative Works that You distribute or communicate shall be licensed under 53 | this Open Software License; 54 | 55 | d) to perform the Original Work publicly; and 56 | 57 | e) to display the Original Work publicly. 58 | 59 | 2) Grant of Patent License. Licensor grants You a worldwide, royalty-free, 60 | non-exclusive, sublicensable license, under patent claims owned or controlled 61 | by the Licensor that are embodied in the Original Work as furnished by the 62 | Licensor, for the duration of the patents, to make, use, sell, offer for sale, 63 | have made, and import the Original Work and Derivative Works. 64 | 65 | 3) Grant of Source Code License. The term "Source Code" means the preferred 66 | form of the Original Work for making modifications to it and all available 67 | documentation describing how to modify the Original Work. Licensor agrees to 68 | provide a machine-readable copy of the Source Code of the Original Work along 69 | with each copy of the Original Work that Licensor distributes. Licensor 70 | reserves the right to satisfy this obligation by placing a machine-readable 71 | copy of the Source Code in an information repository reasonably calculated to 72 | permit inexpensive and convenient access by You for as long as Licensor 73 | continues to distribute the Original Work. 74 | 75 | 4) Exclusions From License Grant. Neither the names of Licensor, nor the names 76 | of any contributors to the Original Work, nor any of their trademarks or 77 | service marks, may be used to endorse or promote products derived from this 78 | Original Work without express prior permission of the Licensor. Except as 79 | expressly stated herein, nothing in this License grants any license to 80 | Licensor's trademarks, copyrights, patents, trade secrets or any other 81 | intellectual property. No patent license is granted to make, use, sell, offer 82 | for sale, have made, or import embodiments of any patent claims other than the 83 | licensed claims defined in Section 2. No license is granted to the trademarks 84 | of Licensor even if such marks are included in the Original Work. Nothing in 85 | this License shall be interpreted to prohibit Licensor from licensing under 86 | terms different from this License any Original Work that Licensor otherwise 87 | would have a right to license. 88 | 89 | 5) External Deployment. The term "External Deployment" means the use, 90 | distribution, or communication of the Original Work or Derivative Works in any 91 | way such that the Original Work or Derivative Works may be used by anyone 92 | other than You, whether those works are distributed or communicated to those 93 | persons or made available as an application intended for use over a network. 94 | As an express condition for the grants of license hereunder, You must treat 95 | any External Deployment by You of the Original Work or a Derivative Work as a 96 | distribution under section 1(c). 97 | 98 | 6) Attribution Rights. You must retain, in the Source Code of any Derivative 99 | Works that You create, all copyright, patent, or trademark notices from the 100 | Source Code of the Original Work, as well as any notices of licensing and any 101 | descriptive text identified therein as an "Attribution Notice." You must cause 102 | the Source Code for any Derivative Works that You create to carry a prominent 103 | Attribution Notice reasonably calculated to inform recipients that You have 104 | modified the Original Work. 105 | 106 | 7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that 107 | the copyright in and to the Original Work and the patent rights granted herein 108 | by Licensor are owned by the Licensor or are sublicensed to You under the 109 | terms of this License with the permission of the contributor(s) of those 110 | copyrights and patent rights. Except as expressly stated in the immediately 111 | preceding sentence, the Original Work is provided under this License on an "AS 112 | IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without 113 | limitation, the warranties of non-infringement, merchantability or fitness for 114 | a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK 115 | IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this 116 | License. No license to the Original Work is granted by this License except 117 | under this disclaimer. 118 | 119 | 8) Limitation of Liability. Under no circumstances and under no legal theory, 120 | whether in tort (including negligence), contract, or otherwise, shall the 121 | Licensor be liable to anyone for any indirect, special, incidental, or 122 | consequential damages of any character arising as a result of this License or 123 | the use of the Original Work including, without limitation, damages for loss 124 | of goodwill, work stoppage, computer failure or malfunction, or any and all 125 | other commercial damages or losses. This limitation of liability shall not 126 | apply to the extent applicable law prohibits such limitation. 127 | 128 | 9) Acceptance and Termination. If, at any time, You expressly assented to this 129 | License, that assent indicates your clear and irrevocable acceptance of this 130 | License and all of its terms and conditions. If You distribute or communicate 131 | copies of the Original Work or a Derivative Work, You must make a reasonable 132 | effort under the circumstances to obtain the express assent of recipients to 133 | the terms of this License. This License conditions your rights to undertake 134 | the activities listed in Section 1, including your right to create Derivative 135 | Works based upon the Original Work, and doing so without honoring these terms 136 | and conditions is prohibited by copyright law and international treaty. 137 | Nothing in this License is intended to affect copyright exceptions and 138 | limitations (including "fair use" or "fair dealing"). This License shall 139 | terminate immediately and You may no longer exercise any of the rights granted 140 | to You by this License upon your failure to honor the conditions in Section 141 | 1(c). 142 | 143 | 10) Termination for Patent Action. This License shall terminate automatically 144 | and You may no longer exercise any of the rights granted to You by this 145 | License as of the date You commence an action, including a cross-claim or 146 | counterclaim, against Licensor or any licensee alleging that the Original Work 147 | infringes a patent. This termination provision shall not apply for an action 148 | alleging patent infringement by combinations of the Original Work with other 149 | software or hardware. 150 | 151 | 11) Jurisdiction, Venue and Governing Law. Any action or suit relating to this 152 | License may be brought only in the courts of a jurisdiction wherein the 153 | Licensor resides or in which Licensor conducts its primary business, and under 154 | the laws of that jurisdiction excluding its conflict-of-law provisions. The 155 | application of the United Nations Convention on Contracts for the 156 | International Sale of Goods is expressly excluded. Any use of the Original 157 | Work outside the scope of this License or after its termination shall be 158 | subject to the requirements and penalties of copyright or patent law in the 159 | appropriate jurisdiction. This section shall survive the termination of this 160 | License. 161 | 162 | 12) Attorneys' Fees. In any action to enforce the terms of this License or 163 | seeking damages relating thereto, the prevailing party shall be entitled to 164 | recover its costs and expenses, including, without limitation, reasonable 165 | attorneys' fees and costs incurred in connection with such action, including 166 | any appeal of such action. This section shall survive the termination of this 167 | License. 168 | 169 | 13) Miscellaneous. If any provision of this License is held to be 170 | unenforceable, such provision shall be reformed only to the extent necessary 171 | to make it enforceable. 172 | 173 | 14) Definition of "You" in This License. "You" throughout this License, 174 | whether in upper or lower case, means an individual or a legal entity 175 | exercising rights under, and complying with all of the terms of, this License. 176 | For legal entities, "You" includes any entity that controls, is controlled by, 177 | or is under common control with you. For purposes of this definition, 178 | "control" means (i) the power, direct or indirect, to cause the direction or 179 | management of such entity, whether by contract or otherwise, or (ii) ownership 180 | of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial 181 | ownership of such entity. 182 | 183 | 15) Right to Use. You may use the Original Work in all ways not otherwise 184 | restricted or conditioned by this License or by law, and Licensor promises not 185 | to interfere with or be responsible for such uses by You. 186 | 187 | 16) Modification of This License. This License is Copyright © 2005 Lawrence 188 | Rosen. Permission is granted to copy, distribute, or communicate this License 189 | without modification. Nothing in this License permits You to modify this 190 | License as applied to the Original Work or to Derivative Works. However, You 191 | may modify the text of this License and copy, distribute or communicate your 192 | modified version (the "Modified License") and apply it to other original works 193 | of authorship subject to the following conditions: (i) You may not indicate in 194 | any way that your Modified License is the "Open Software License" or "OSL" and 195 | you may not use those names in the name of your Modified License; (ii) You 196 | must replace the notice specified in the first paragraph above with the notice 197 | "Licensed under " or with a notice of your own 198 | that is not confusingly similar to the notice in this License; and (iii) You 199 | may not claim that your original works are open source software unless your 200 | Modified License has been approved by Open Source Initiative (OSI) and You 201 | comply with its license review and certification process. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Message Translate libless edition 2 | 3 | Translates messages to and from languages semi-automagically. Forked edition without the shitty lib and which will 4 | be more compatible with Powercord features. 5 | -------------------------------------------------------------------------------- /TranslationHandler/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Bowser65 3 | * Licensed under the Open Software License version 3.0 4 | * Original work under MIT; See LICENSE. 5 | */ 6 | 7 | const { getModule, FluxDispatcher } = require("powercord/webpack"); 8 | 9 | const { Engines } = require("../constants"); 10 | const googleTranslate = require("../node_modules/@iamtraction/google-translate"); 11 | 12 | const { getMessage } = getModule([ "getMessages" ], false); 13 | 14 | class Translator { 15 | constructor() { 16 | this.cache = {}; 17 | this.rateLimitCache = {} 18 | } 19 | 20 | removeExceptions = (text) => { 21 | let exceptions = []; 22 | 23 | let counter = 0; 24 | 25 | let replaceMatches = (matches) => { 26 | if (!matches) return; 27 | for (let match of matches) { 28 | exceptions.push(match); 29 | text = text.replace( 30 | new RegExp( 31 | match.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"), 32 | "g" 33 | ), 34 | `[/////${counter}]` 35 | ); 36 | counter++; 37 | } 38 | }; 39 | 40 | // User and role tags. 41 | replaceMatches(text.match(/<@(!?|&?)\d+>/gi)); 42 | // Channel tags. 43 | replaceMatches(text.match(/<#\d+>/gi)); 44 | // Emojis. 45 | replaceMatches(text.match(/<(\w+)?:\w+:\d+>/gi)); 46 | // Codeblocks. 47 | replaceMatches(text.match(/```(.+?|\n+)```/gis)); 48 | // Code snippets. 49 | // Important to do this after codeblocks. 50 | replaceMatches(text.match(/`(.+?|\n+)`/gis)); 51 | 52 | return { text, exceptions }; 53 | }; 54 | 55 | addExceptions = (text, exceptions) => { 56 | for (let i = 0; i < exceptions.length; i++) { 57 | let exception = exceptions[i]; 58 | text = text.replace( 59 | new RegExp( 60 | `(\\[\\/\\/\\/\\/\\/${i}+\\]|\\[\\/\\/\\/\\/\\/ ${i}+\\])`, 61 | "gi" 62 | ), 63 | exception 64 | ); 65 | } 66 | return text; 67 | }; 68 | 69 | translate = async (text, language, engine, async = true) => { 70 | if (!Engines[engine]) return; 71 | if (!this.rateLimitCache[engine]) { 72 | this.rateLimitCache[engine] = { 73 | lastTime: 0, 74 | lastRatelimit: 0, 75 | }; 76 | } 77 | 78 | const started = Date.now(); 79 | 80 | const lastTime = this.rateLimitCache[engine].lastTime; 81 | this.rateLimitCache[engine].lastTime = started; 82 | 83 | const timeout = 84 | lastTime + 85 | Engines[engine].ratelimit + 86 | this.rateLimitCache[engine].lastRatelimit < 87 | started 88 | ? 0 89 | : Engines[engine].ratelimit + 90 | this.rateLimitCache[engine].lastRatelimit - 91 | (started - lastTime); 92 | 93 | this.rateLimitCache[engine].lastRatelimit = timeout; 94 | 95 | const translateFunctions = { 96 | google: (text, language) => { 97 | return new Promise((resolve, reject) => { 98 | googleTranslate(text, { to: language }) 99 | .then((res) => { 100 | resolve({ 101 | text: res.text, 102 | originalLanguage: res.from.language.iso, 103 | }); 104 | }) 105 | .catch((err) => { 106 | reject(err); 107 | }); 108 | }); 109 | } 110 | }; 111 | 112 | let translatePromise = (resolve, reject) => { 113 | setTimeout(async () => { 114 | const finished = Date.now(); 115 | 116 | const excepted = this.removeExceptions(text); 117 | let translationResults; 118 | let tries = 0; 119 | 120 | while (!translationResults && tries < 3) { 121 | tries++; 122 | try { 123 | translationResults = await translateFunctions[engine]( 124 | excepted.text, 125 | language 126 | ); 127 | } catch (e) {} 128 | } 129 | 130 | if (!translationResults) reject(); 131 | 132 | translationResults.text = this.addExceptions( 133 | translationResults.text, 134 | excepted.exceptions 135 | ); 136 | 137 | resolve(translationResults); 138 | }, timeout); 139 | }; 140 | 141 | if (async) { 142 | return await new Promise(translatePromise); 143 | } 144 | return new Promise(translatePromise); 145 | }; 146 | 147 | translateMessage = async (message, language, engine, async = true) => { 148 | if (!message.content || message.content.length === 0) { 149 | return ""; 150 | } 151 | // If any of the properties don't exist, make them. 152 | if (!this.cache[message.channel_id]) { 153 | this.cache[message.channel_id] = {}; 154 | } 155 | let alreadyTranslated = true; 156 | if (!this.cache[message.channel_id][message.id]) { 157 | alreadyTranslated = false; 158 | this.cache[message.channel_id][message.id] = { 159 | channelID: message.channel_id, 160 | currentLanguage: "original", 161 | originalLanguage: null, 162 | engines: {}, 163 | originalContent: message.content, 164 | }; 165 | } 166 | if (!this.cache[message.channel_id][message.id].engines[engine]) { 167 | this.cache[message.channel_id][message.id].engines[engine] = {}; 168 | } 169 | if ( 170 | !this.cache[message.channel_id][message.id].engines[engine][ 171 | language 172 | ] 173 | ) { 174 | this.cache[message.channel_id][message.id].engines[engine][ 175 | language 176 | ] = {}; 177 | } 178 | 179 | // If the target language is the original language, set it back. 180 | 181 | // If the target language is cached, use it. 182 | if (language == "original") { 183 | this.cache[message.channel_id][message.id].currentLanguage = 184 | "original"; 185 | message.content = this.cache[message.channel_id][ 186 | message.id 187 | ].originalContent; 188 | } else if ( 189 | this.cache[message.channel_id][message.id].engines[engine][language] 190 | .content 191 | ) { 192 | this.cache[message.channel_id][ 193 | message.id 194 | ].currentLanguage = language; 195 | message.content = this.cache[message.channel_id][ 196 | message.id 197 | ].engines[engine][language].content; 198 | } else if (!alreadyTranslated) { 199 | const translatePromise = (resolve, reject) => { 200 | this.translate(message.content, language, engine, async) 201 | .then((results) => { 202 | message.content = results.text; 203 | if ( 204 | this.cache[message.channel_id][message.id] 205 | .originalLanguage === null 206 | ) 207 | this.cache[message.channel_id][ 208 | message.id 209 | ].originalLanguage = results.originalLanguage; 210 | 211 | this.cache[message.channel_id][ 212 | message.id 213 | ].currentLanguage = language; 214 | this.cache[message.channel_id][message.id].engines[ 215 | engine 216 | ][language].content = results.text; 217 | 218 | this.updateMessage(message); 219 | 220 | resolve(message); 221 | }) 222 | .catch((e) => { 223 | reject(e); 224 | }); 225 | }; 226 | 227 | if (async) { 228 | return await new Promise(translatePromise); 229 | } 230 | return new Promise(translatePromise); 231 | } 232 | 233 | if (async) { 234 | return await new Promise((resolve) => { 235 | this.updateMessage(message); 236 | resolve(message); 237 | }); 238 | } 239 | return new Promise((resolve) => { 240 | this.updateMessage(message); 241 | resolve(message); 242 | }); 243 | }; 244 | 245 | updateMessage(message) { 246 | FluxDispatcher.dirtyDispatch({ 247 | translation: true, 248 | type: "MESSAGE_UPDATE", 249 | message, 250 | }); 251 | } 252 | 253 | clearCache = () => { 254 | for (let channelID in this.cache) { 255 | for (let messageID in this.cache[channelID]) { 256 | this.removeMessage(channelID, messageID); 257 | } 258 | } 259 | this.cache = {}; 260 | }; 261 | 262 | removeMessage = (channelID, messageID, reset = true) => { 263 | let message = getMessage(channelID, messageID); 264 | if (reset) { 265 | message.content = this.cache[channelID][messageID].originalContent; 266 | this.updateMessage(message); 267 | } 268 | delete this.cache[channelID][messageID]; 269 | }; 270 | } 271 | 272 | module.exports = Translator; 273 | -------------------------------------------------------------------------------- /TranslationHandler/translateAction.js: -------------------------------------------------------------------------------- 1 | module.exports = async function(message, userLang, langEngine, Translator, openSettings, failedTranslate){ 2 | if (!langEngine || !userLang) { 3 | openSettings() 4 | } else { 5 | let targetLanguage = userLang; 6 | if ( 7 | Translator.cache[message.channel_id] && 8 | Translator.cache[message.channel_id][message.id] && 9 | Translator.cache[message.channel_id][message.id].currentLanguage != "original" 10 | ) { 11 | targetLanguage = "original"; 12 | } 13 | 14 | Translator.translateMessage( 15 | message, 16 | targetLanguage, 17 | langEngine 18 | ).catch(failedTranslate); 19 | } 20 | }; -------------------------------------------------------------------------------- /components/Indicator.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Bowser65 3 | * Licensed under the Open Software License version 3.0 4 | * Original work under MIT; See LICENSE. 5 | */ 6 | 7 | const { 8 | React, 9 | getModule, 10 | getModuleByDisplayName, 11 | i18n: { Messages } 12 | } = require("powercord/webpack"); 13 | 14 | const { IsoLangs } = require("../constants"); 15 | 16 | const Tooltip = getModuleByDisplayName("Tooltip", false) 17 | const classes = getModule([ "edited" ], false); 18 | 19 | class Indicator extends React.Component { 20 | constructor(props) { 21 | super(props); 22 | } 23 | 24 | render() { 25 | const langFrom = this.props.targetLanguage; 26 | const langTo = this.props.currentLanguage; 27 | let tooltip = "Unknown"; 28 | 29 | try { 30 | // tooltip = ` 31 | // From: ${IsoLangs[langFrom].name} | ${IsoLangs[langFrom].nativeName} 32 | // To: ${IsoLangs[langTo].name} | ${IsoLangs[langTo].nativeName} 33 | // `; 34 | tooltip = `From: ${IsoLangs[langFrom].name} | To: ${IsoLangs[langTo].name}`; 35 | } catch {} 36 | 37 | return ( 38 | 39 | {({ onMouseLeave, onMouseEnter }) => ( 40 | 45 | {(this.props.currentLanguage === "original") ? "": `(${Messages.TRANSLATED})`} 46 | 47 | )} 48 | 49 | ); 50 | } 51 | } 52 | 53 | module.exports = Indicator; 54 | -------------------------------------------------------------------------------- /components/QuickSettings.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Bowser65 3 | * Licensed under the Open Software License version 3.0 4 | */ 5 | 6 | const { 7 | React, 8 | contextMenu: { closeContextMenu }, 9 | i18n: { Messages } 10 | } = require("powercord/webpack"); 11 | const { Menu } = require("powercord/components"); 12 | 13 | module.exports = React.memo( 14 | ({ openSettings, getSetting, toggleSetting }) => ( 15 | 16 | 17 | toggleSetting("translate_sent_messages")} 22 | /> 23 | toggleSetting("translate_received_messages")} 28 | /> 29 | 30 | 31 | openSettings()} 35 | /> 36 | 37 | 38 | ) 39 | ); 40 | -------------------------------------------------------------------------------- /components/Settings.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Bowser65 3 | * Licensed under the Open Software License version 3.0 4 | * Original work under MIT; See LICENSE. 5 | */ 6 | 7 | const { React, i18n: { Messages } } = require("powercord/webpack"); 8 | const { settings: { SelectInput, SwitchItem } } = require("powercord/components"); 9 | 10 | const { Engines } = require("../constants"); 11 | const { languagesForEngine } = require("../utils"); 12 | 13 | class Settings extends React.Component { 14 | render() { 15 | const engineOptions = Object.keys(Engines).map( 16 | (engine) => { 17 | return { 18 | label: Engines[engine].name, 19 | value: Engines[engine].name.toLowerCase(), 20 | }; 21 | } 22 | ); 23 | let engineLanguages = []; 24 | 25 | if (this.props.getSetting("translation_engine")) { 26 | engineLanguages = languagesForEngine(this.props.getSetting("translation_engine")); 27 | } 28 | 29 | return ( 30 | 31 | this.props.toggleSetting("translate_sent_messages")} 36 | /> 37 | this.props.toggleSetting("translate_received_messages")} 42 | /> 43 | this.props.updateSetting("translation_engine", e.value)} 50 | /> 51 | this.props.updateSetting("user_language", e.value)} 58 | /> 59 | this.props.updateSetting("target_language", e.value)} 66 | /> 67 | 68 | ); 69 | } 70 | } 71 | 72 | module.exports = Settings; 73 | -------------------------------------------------------------------------------- /components/SettingsButton.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Bowser65 3 | * Licensed under the Open Software License version 3.0 4 | * Original work under MIT; See LICENSE. 5 | */ 6 | 7 | const { 8 | React, 9 | getModule, 10 | getModuleByDisplayName, 11 | i18n: { Messages } 12 | } = require("powercord/webpack"); 13 | const { Button } = require("powercord/components"); 14 | const Tooltip = getModuleByDisplayName("Tooltip", false); 15 | 16 | const buttonClasses = getModule([ "button" ], false); 17 | const buttonWrapperClasses = getModule([ "buttonWrapper", "pulseButton" ], false); 18 | const buttonTextAreaClasses = getModule([ "button", "textArea" ], false); 19 | 20 | class SettingsButton extends React.Component { 21 | render() { 22 | return ( 23 | 28 | {({ onMouseLeave, onMouseEnter }) => ( 29 | 51 | )} 52 | 53 | ); 54 | } 55 | } 56 | 57 | module.exports = SettingsButton; 58 | -------------------------------------------------------------------------------- /components/SettingsModal.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Bowser65 3 | * Licensed under the Open Software License version 3.0 4 | * Original work under MIT; See LICENSE. 5 | */ 6 | 7 | const { React, i18n: { Messages } } = require("powercord/webpack"); 8 | const { Button } = require("powercord/components"); 9 | const { Modal } = require("powercord/components/modal"); 10 | const { FormTitle } = require("powercord/components"); 11 | const { close: closeModal } = require("powercord/modal"); 12 | const Settings = require("./Settings"); 13 | 14 | class SettingsModal extends React.Component { 15 | render() { 16 | return ( 17 | 18 | 19 | {Messages.MESSAGE_TRANSLATE_SETTINGS} 20 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | ); 32 | } 33 | } 34 | 35 | module.exports = SettingsModal; 36 | -------------------------------------------------------------------------------- /components/TranslateButton.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Bowser65 3 | * Licensed under the Open Software License version 3.0 4 | * Original work under MIT; See LICENSE. 5 | */ 6 | 7 | const { React, getModule, getModuleByDisplayName, i18n: { Messages } } = require("powercord/webpack"); 8 | const translateAction = require("../TranslationHandler/translateAction"); 9 | 10 | const Tooltip = getModuleByDisplayName("Tooltip", false); 11 | const classes = getModule([ "icon", "isHeader" ], false); 12 | const { Button } = getModule( 13 | (m) => m.default && m.default.displayName === "MiniPopover", false 14 | ); 15 | 16 | class TranslateButton extends React.Component { 17 | constructor(props) { 18 | super(props); 19 | } 20 | 21 | render() { 22 | return ( 23 | 24 | {({ onMouseLeave, onMouseEnter }) => ( 25 | 54 | )} 55 | 56 | ); 57 | } 58 | } 59 | 60 | module.exports = TranslateButton; 61 | -------------------------------------------------------------------------------- /constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Bowser65 3 | * Licensed under the Open Software License version 3.0 4 | */ 5 | 6 | module.exports = { 7 | IsoLangs: { 8 | ab: { 9 | name: "Abkhaz", 10 | nativeName: "аҧсуа", 11 | }, 12 | aa: { 13 | name: "Afar", 14 | nativeName: "Afaraf", 15 | }, 16 | af: { 17 | name: "Afrikaans", 18 | nativeName: "Afrikaans", 19 | }, 20 | ak: { 21 | name: "Akan", 22 | nativeName: "Akan", 23 | }, 24 | sq: { 25 | name: "Albanian", 26 | nativeName: "Shqip", 27 | }, 28 | am: { 29 | name: "Amharic", 30 | nativeName: "አማርኛ", 31 | }, 32 | ar: { 33 | name: "Arabic", 34 | nativeName: "العربية", 35 | }, 36 | an: { 37 | name: "Aragonese", 38 | nativeName: "Aragonés", 39 | }, 40 | hy: { 41 | name: "Armenian", 42 | nativeName: "Հայերեն", 43 | }, 44 | as: { 45 | name: "Assamese", 46 | nativeName: "অসমীয়া", 47 | }, 48 | av: { 49 | name: "Avaric", 50 | nativeName: "авар мацӀ, магӀарул мацӀ", 51 | }, 52 | ae: { 53 | name: "Avestan", 54 | nativeName: "avesta", 55 | }, 56 | ay: { 57 | name: "Aymara", 58 | nativeName: "aymar aru", 59 | }, 60 | az: { 61 | name: "Azerbaijani", 62 | nativeName: "azərbaycan dili", 63 | }, 64 | bm: { 65 | name: "Bambara", 66 | nativeName: "bamanankan", 67 | }, 68 | ba: { 69 | name: "Bashkir", 70 | nativeName: "башҡорт теле", 71 | }, 72 | eu: { 73 | name: "Basque", 74 | nativeName: "euskara, euskera", 75 | }, 76 | be: { 77 | name: "Belarusian", 78 | nativeName: "Беларуская", 79 | }, 80 | bn: { 81 | name: "Bengali", 82 | nativeName: "বাংলা", 83 | }, 84 | bh: { 85 | name: "Bihari", 86 | nativeName: "भोजपुरी", 87 | }, 88 | bi: { 89 | name: "Bislama", 90 | nativeName: "Bislama", 91 | }, 92 | bs: { 93 | name: "Bosnian", 94 | nativeName: "bosanski jezik", 95 | }, 96 | br: { 97 | name: "Breton", 98 | nativeName: "brezhoneg", 99 | }, 100 | bg: { 101 | name: "Bulgarian", 102 | nativeName: "български език", 103 | }, 104 | my: { 105 | name: "Burmese", 106 | nativeName: "ဗမာစာ", 107 | }, 108 | ca: { 109 | name: "Catalan; Valencian", 110 | nativeName: "Català", 111 | }, 112 | ceb: { 113 | name: "Cebuano", 114 | nativeName: "Cebuano", 115 | }, 116 | ch: { 117 | name: "Chamorro", 118 | nativeName: "Chamoru", 119 | }, 120 | ce: { 121 | name: "Chechen", 122 | nativeName: "нохчийн мотт", 123 | }, 124 | ny: { 125 | name: "Chichewa; Chewa; Nyanja", 126 | nativeName: "chiCheŵa, chinyanja", 127 | }, 128 | zh: { 129 | name: "Chinese", 130 | nativeName: "中文 (Zhōngwén), 汉语, 漢語", 131 | }, 132 | cv: { 133 | name: "Chuvash", 134 | nativeName: "чӑваш чӗлхи", 135 | }, 136 | kw: { 137 | name: "Cornish", 138 | nativeName: "Kernewek", 139 | }, 140 | co: { 141 | name: "Corsican", 142 | nativeName: "corsu, lingua corsa", 143 | }, 144 | cr: { 145 | name: "Cree", 146 | nativeName: "ᓀᐦᐃᔭᐍᐏᐣ", 147 | }, 148 | hr: { 149 | name: "Croatian", 150 | nativeName: "hrvatski", 151 | }, 152 | cs: { 153 | name: "Czech", 154 | nativeName: "česky, čeština", 155 | }, 156 | da: { 157 | name: "Danish", 158 | nativeName: "dansk", 159 | }, 160 | dv: { 161 | name: "Divehi; Dhivehi; Maldivian;", 162 | nativeName: "ދިވެހި", 163 | }, 164 | nl: { 165 | name: "Dutch", 166 | nativeName: "Nederlands, Vlaams", 167 | }, 168 | en: { 169 | name: "English", 170 | nativeName: "English", 171 | }, 172 | eo: { 173 | name: "Esperanto", 174 | nativeName: "Esperanto", 175 | }, 176 | et: { 177 | name: "Estonian", 178 | nativeName: "eesti, eesti keel", 179 | }, 180 | ee: { 181 | name: "Ewe", 182 | nativeName: "Eʋegbe", 183 | }, 184 | fo: { 185 | name: "Faroese", 186 | nativeName: "føroyskt", 187 | }, 188 | fj: { 189 | name: "Fijian", 190 | nativeName: "vosa Vakaviti", 191 | }, 192 | fi: { 193 | name: "Finnish", 194 | nativeName: "suomi, suomen kieli", 195 | }, 196 | fr: { 197 | name: "French", 198 | nativeName: "français, langue française", 199 | }, 200 | ff: { 201 | name: "Fula; Fulah; Pulaar; Pular", 202 | nativeName: "Fulfulde, Pulaar, Pular", 203 | }, 204 | gl: { 205 | name: "Galician", 206 | nativeName: "Galego", 207 | }, 208 | ka: { 209 | name: "Georgian", 210 | nativeName: "ქართული", 211 | }, 212 | de: { 213 | name: "German", 214 | nativeName: "Deutsch", 215 | }, 216 | el: { 217 | name: "Greek, Modern", 218 | nativeName: "Ελληνικά", 219 | }, 220 | gn: { 221 | name: "Guaraní", 222 | nativeName: "Avañeẽ", 223 | }, 224 | gu: { 225 | name: "Gujarati", 226 | nativeName: "ગુજરાતી", 227 | }, 228 | ht: { 229 | name: "Haitian; Haitian Creole", 230 | nativeName: "Kreyòl ayisyen", 231 | }, 232 | ha: { 233 | name: "Hausa", 234 | nativeName: "Hausa, هَوُسَ", 235 | }, 236 | haw: { 237 | name: "Hawaiian", 238 | nativeName: "Hawaiian", 239 | }, 240 | iw: { 241 | name: "Hebrew (modern)", 242 | nativeName: "עברית", 243 | }, 244 | hz: { 245 | name: "Herero", 246 | nativeName: "Otjiherero", 247 | }, 248 | hi: { 249 | name: "Hindi", 250 | nativeName: "हिन्दी, हिंदी", 251 | }, 252 | ho: { 253 | name: "Hiri Motu", 254 | nativeName: "Hiri Motu", 255 | }, 256 | hmn: { 257 | name: "Hmong, Mong", 258 | nativeName: "Hmong, Moob (Suav, Laos)", 259 | }, 260 | hu: { 261 | name: "Hungarian", 262 | nativeName: "Magyar", 263 | }, 264 | ia: { 265 | name: "Interlingua", 266 | nativeName: "Interlingua", 267 | }, 268 | id: { 269 | name: "Indonesian", 270 | nativeName: "Bahasa Indonesia", 271 | }, 272 | ie: { 273 | name: "Interlingue", 274 | nativeName: 275 | "Originally called Occidental; then Interlingue after WWII", 276 | }, 277 | ga: { 278 | name: "Irish", 279 | nativeName: "Gaeilge", 280 | }, 281 | ig: { 282 | name: "Igbo", 283 | nativeName: "Asụsụ Igbo", 284 | }, 285 | ik: { 286 | name: "Inupiaq", 287 | nativeName: "Iñupiaq, Iñupiatun", 288 | }, 289 | io: { 290 | name: "Ido", 291 | nativeName: "Ido", 292 | }, 293 | is: { 294 | name: "Icelandic", 295 | nativeName: "Íslenska", 296 | }, 297 | it: { 298 | name: "Italian", 299 | nativeName: "Italiano", 300 | }, 301 | iu: { 302 | name: "Inuktitut", 303 | nativeName: "ᐃᓄᒃᑎᑐᑦ", 304 | }, 305 | ja: { 306 | name: "Japanese", 307 | nativeName: "日本語 (にほんご/にっぽんご)", 308 | }, 309 | jv: { 310 | name: "Javanese", 311 | nativeName: "basa Jawa", 312 | }, 313 | kl: { 314 | name: "Kalaallisut, Greenlandic", 315 | nativeName: "kalaallisut, kalaallit oqaasii", 316 | }, 317 | kn: { 318 | name: "Kannada", 319 | nativeName: "ಕನ್ನಡ", 320 | }, 321 | kr: { 322 | name: "Kanuri", 323 | nativeName: "Kanuri", 324 | }, 325 | ks: { 326 | name: "Kashmiri", 327 | nativeName: "कश्मीरी, كشميري‎", 328 | }, 329 | kk: { 330 | name: "Kazakh", 331 | nativeName: "Қазақ тілі", 332 | }, 333 | km: { 334 | name: "Khmer", 335 | nativeName: "ភាសាខ្មែរ", 336 | }, 337 | ki: { 338 | name: "Kikuyu, Gikuyu", 339 | nativeName: "Gĩkũyũ", 340 | }, 341 | rw: { 342 | name: "Kinyarwanda", 343 | nativeName: "Ikinyarwanda", 344 | }, 345 | ky: { 346 | name: "Kirghiz, Kyrgyz", 347 | nativeName: "кыргыз тили", 348 | }, 349 | kv: { 350 | name: "Komi", 351 | nativeName: "коми кыв", 352 | }, 353 | kg: { 354 | name: "Kongo", 355 | nativeName: "KiKongo", 356 | }, 357 | ko: { 358 | name: "Korean", 359 | nativeName: "한국어 (韓國語), 조선말 (朝鮮語)", 360 | }, 361 | ku: { 362 | name: "Kurdish", 363 | nativeName: "Kurdî, كوردی‎", 364 | }, 365 | kj: { 366 | name: "Kwanyama, Kuanyama", 367 | nativeName: "Kuanyama", 368 | }, 369 | la: { 370 | name: "Latin", 371 | nativeName: "latine, lingua latina", 372 | }, 373 | lb: { 374 | name: "Luxembourgish, Letzeburgesch", 375 | nativeName: "Lëtzebuergesch", 376 | }, 377 | lg: { 378 | name: "Luganda", 379 | nativeName: "Luganda", 380 | }, 381 | li: { 382 | name: "Limburgish, Limburgan, Limburger", 383 | nativeName: "Limburgs", 384 | }, 385 | ln: { 386 | name: "Lingala", 387 | nativeName: "Lingála", 388 | }, 389 | lo: { 390 | name: "Lao", 391 | nativeName: "ພາສາລາວ", 392 | }, 393 | lt: { 394 | name: "Lithuanian", 395 | nativeName: "lietuvių kalba", 396 | }, 397 | lu: { 398 | name: "Luba-Katanga", 399 | nativeName: "", 400 | }, 401 | lv: { 402 | name: "Latvian", 403 | nativeName: "latviešu valoda", 404 | }, 405 | gv: { 406 | name: "Manx", 407 | nativeName: "Gaelg, Gailck", 408 | }, 409 | mk: { 410 | name: "Macedonian", 411 | nativeName: "македонски јазик", 412 | }, 413 | mg: { 414 | name: "Malagasy", 415 | nativeName: "Malagasy fiteny", 416 | }, 417 | ms: { 418 | name: "Malay", 419 | nativeName: "bahasa Melayu, بهاس ملايو‎", 420 | }, 421 | ml: { 422 | name: "Malayalam", 423 | nativeName: "മലയാളം", 424 | }, 425 | mt: { 426 | name: "Maltese", 427 | nativeName: "Malti", 428 | }, 429 | mi: { 430 | name: "Māori", 431 | nativeName: "te reo Māori", 432 | }, 433 | mr: { 434 | name: "Marathi (Marāṭhī)", 435 | nativeName: "मराठी", 436 | }, 437 | mh: { 438 | name: "Marshallese", 439 | nativeName: "Kajin M̧ajeļ", 440 | }, 441 | mn: { 442 | name: "Mongolian", 443 | nativeName: "монгол", 444 | }, 445 | na: { 446 | name: "Nauru", 447 | nativeName: "Ekakairũ Naoero", 448 | }, 449 | nv: { 450 | name: "Navajo, Navaho", 451 | nativeName: "Diné bizaad, Dinékʼehǰí", 452 | }, 453 | nb: { 454 | name: "Norwegian Bokmål", 455 | nativeName: "Norsk bokmål", 456 | }, 457 | nd: { 458 | name: "North Ndebele", 459 | nativeName: "isiNdebele", 460 | }, 461 | ne: { 462 | name: "Nepali", 463 | nativeName: "नेपाली", 464 | }, 465 | ng: { 466 | name: "Ndonga", 467 | nativeName: "Owambo", 468 | }, 469 | nn: { 470 | name: "Norwegian Nynorsk", 471 | nativeName: "Norsk nynorsk", 472 | }, 473 | no: { 474 | name: "Norwegian", 475 | nativeName: "Norsk", 476 | }, 477 | ii: { 478 | name: "Nuosu", 479 | nativeName: "ꆈꌠ꒿ Nuosuhxop", 480 | }, 481 | nr: { 482 | name: "South Ndebele", 483 | nativeName: "isiNdebele", 484 | }, 485 | oc: { 486 | name: "Occitan", 487 | nativeName: "Occitan", 488 | }, 489 | oj: { 490 | name: "Ojibwe, Ojibwa", 491 | nativeName: "ᐊᓂᔑᓈᐯᒧᐎᓐ", 492 | }, 493 | cu: { 494 | name: 495 | "Old Church Slavonic, Church Slavic, Church Slavonic, Old Bulgarian, Old Slavonic", 496 | nativeName: "ѩзыкъ словѣньскъ", 497 | }, 498 | om: { 499 | name: "Oromo", 500 | nativeName: "Afaan Oromoo", 501 | }, 502 | or: { 503 | name: "Oriya", 504 | nativeName: "ଓଡ଼ିଆ", 505 | }, 506 | os: { 507 | name: "Ossetian, Ossetic", 508 | nativeName: "ирон æвзаг", 509 | }, 510 | pa: { 511 | name: "Panjabi, Punjabi", 512 | nativeName: "ਪੰਜਾਬੀ, پنجابی‎", 513 | }, 514 | pi: { 515 | name: "Pāli", 516 | nativeName: "पाऴि", 517 | }, 518 | fa: { 519 | name: "Persian", 520 | nativeName: "فارسی", 521 | }, 522 | pl: { 523 | name: "Polish", 524 | nativeName: "polski", 525 | }, 526 | ps: { 527 | name: "Pashto, Pushto", 528 | nativeName: "پښتو", 529 | }, 530 | pt: { 531 | name: "Portuguese", 532 | nativeName: "Português", 533 | }, 534 | qu: { 535 | name: "Quechua", 536 | nativeName: "Runa Simi, Kichwa", 537 | }, 538 | rm: { 539 | name: "Romansh", 540 | nativeName: "rumantsch grischun", 541 | }, 542 | rn: { 543 | name: "Kirundi", 544 | nativeName: "kiRundi", 545 | }, 546 | ro: { 547 | name: "Romanian, Moldavian, Moldovan", 548 | nativeName: "română", 549 | }, 550 | ru: { 551 | name: "Russian", 552 | nativeName: "русский язык", 553 | }, 554 | sa: { 555 | name: "Sanskrit (Saṁskṛta)", 556 | nativeName: "संस्कृतम्", 557 | }, 558 | sc: { 559 | name: "Sardinian", 560 | nativeName: "sardu", 561 | }, 562 | sd: { 563 | name: "Sindhi", 564 | nativeName: "सिन्धी, سنڌي، سندھی‎", 565 | }, 566 | se: { 567 | name: "Northern Sami", 568 | nativeName: "Davvisámegiella", 569 | }, 570 | sm: { 571 | name: "Samoan", 572 | nativeName: "gagana faa Samoa", 573 | }, 574 | sg: { 575 | name: "Sango", 576 | nativeName: "yângâ tî sängö", 577 | }, 578 | sr: { 579 | name: "Serbian", 580 | nativeName: "српски језик", 581 | }, 582 | gd: { 583 | name: "Scottish Gaelic; Gaelic", 584 | nativeName: "Gàidhlig", 585 | }, 586 | sn: { 587 | name: "Shona", 588 | nativeName: "chiShona", 589 | }, 590 | si: { 591 | name: "Sinhala, Sinhalese", 592 | nativeName: "සිංහල", 593 | }, 594 | sk: { 595 | name: "Slovak", 596 | nativeName: "slovenčina", 597 | }, 598 | sl: { 599 | name: "Slovene", 600 | nativeName: "slovenščina", 601 | }, 602 | so: { 603 | name: "Somali", 604 | nativeName: "Soomaaliga, af Soomaali", 605 | }, 606 | st: { 607 | name: "Southern Sotho", 608 | nativeName: "Sesotho", 609 | }, 610 | es: { 611 | name: "Spanish; Castilian", 612 | nativeName: "español, castellano", 613 | }, 614 | su: { 615 | name: "Sundanese", 616 | nativeName: "Basa Sunda", 617 | }, 618 | sw: { 619 | name: "Swahili", 620 | nativeName: "Kiswahili", 621 | }, 622 | ss: { 623 | name: "Swati", 624 | nativeName: "SiSwati", 625 | }, 626 | sv: { 627 | name: "Swedish", 628 | nativeName: "svenska", 629 | }, 630 | ta: { 631 | name: "Tamil", 632 | nativeName: "தமிழ்", 633 | }, 634 | te: { 635 | name: "Telugu", 636 | nativeName: "తెలుగు", 637 | }, 638 | tg: { 639 | name: "Tajik", 640 | nativeName: "тоҷикӣ, toğikī, تاجیکی‎", 641 | }, 642 | th: { 643 | name: "Thai", 644 | nativeName: "ไทย", 645 | }, 646 | ti: { 647 | name: "Tigrinya", 648 | nativeName: "ትግርኛ", 649 | }, 650 | bo: { 651 | name: "Tibetan Standard, Tibetan, Central", 652 | nativeName: "བོད་ཡིག", 653 | }, 654 | tk: { 655 | name: "Turkmen", 656 | nativeName: "Türkmen, Түркмен", 657 | }, 658 | tl: { 659 | name: "Tagalog", 660 | nativeName: "Wikang Tagalog, ᜏᜒᜃᜅ᜔ ᜆᜄᜎᜓᜄ᜔", 661 | }, 662 | tn: { 663 | name: "Tswana", 664 | nativeName: "Setswana", 665 | }, 666 | to: { 667 | name: "Tonga (Tonga Islands)", 668 | nativeName: "faka Tonga", 669 | }, 670 | tr: { 671 | name: "Turkish", 672 | nativeName: "Türkçe", 673 | }, 674 | ts: { 675 | name: "Tsonga", 676 | nativeName: "Xitsonga", 677 | }, 678 | tt: { 679 | name: "Tatar", 680 | nativeName: "татарча, tatarça, تاتارچا‎", 681 | }, 682 | tw: { 683 | name: "Twi", 684 | nativeName: "Twi", 685 | }, 686 | ty: { 687 | name: "Tahitian", 688 | nativeName: "Reo Tahiti", 689 | }, 690 | ug: { 691 | name: "Uighur, Uyghur", 692 | nativeName: "Uyƣurqə, ئۇيغۇرچە‎", 693 | }, 694 | uk: { 695 | name: "Ukrainian", 696 | nativeName: "українська", 697 | }, 698 | ur: { 699 | name: "Urdu", 700 | nativeName: "اردو", 701 | }, 702 | uz: { 703 | name: "Uzbek", 704 | nativeName: "zbek, Ўзбек, أۇزبېك‎", 705 | }, 706 | ve: { 707 | name: "Venda", 708 | nativeName: "Tshivenḓa", 709 | }, 710 | vi: { 711 | name: "Vietnamese", 712 | nativeName: "Tiếng Việt", 713 | }, 714 | vo: { 715 | name: "Volapük", 716 | nativeName: "Volapük", 717 | }, 718 | wa: { 719 | name: "Walloon", 720 | nativeName: "Walon", 721 | }, 722 | cy: { 723 | name: "Welsh", 724 | nativeName: "Cymraeg", 725 | }, 726 | wo: { 727 | name: "Wolof", 728 | nativeName: "Wollof", 729 | }, 730 | fy: { 731 | name: "Western Frisian", 732 | nativeName: "Frysk", 733 | }, 734 | xh: { 735 | name: "Xhosa", 736 | nativeName: "isiXhosa", 737 | }, 738 | yi: { 739 | name: "Yiddish", 740 | nativeName: "ייִדיש", 741 | }, 742 | yo: { 743 | name: "Yoruba", 744 | nativeName: "Yorùbá", 745 | }, 746 | za: { 747 | name: "Zhuang, Chuang", 748 | nativeName: "Saɯ cueŋƅ, Saw cuengh", 749 | }, 750 | zu: { 751 | name: "Zulu", 752 | nativeName: "Zulu", 753 | }, 754 | }, 755 | 756 | Engines: { 757 | google: { 758 | name: "Google", 759 | languages: [ 760 | "af", 761 | "am", 762 | "ar", 763 | "az", 764 | "be", 765 | "bg", 766 | "bn", 767 | "bs", 768 | "ca", 769 | "ceb", 770 | "co", 771 | "cs", 772 | "cy", 773 | "da", 774 | "de", 775 | "el", 776 | "en", 777 | "eo", 778 | "es", 779 | "et", 780 | "eu", 781 | "fa", 782 | "fi", 783 | "fr", 784 | "fy", 785 | "ga", 786 | "gd", 787 | "gl", 788 | "gu", 789 | "ha", 790 | "haw", 791 | "hi", 792 | "hmn", 793 | "hr", 794 | "ht", 795 | "hu", 796 | "hy", 797 | "id", 798 | "ig", 799 | "is", 800 | "it", 801 | "iw", 802 | "ja", 803 | "jv", 804 | "ka", 805 | "kk", 806 | "km", 807 | "kn", 808 | "ko", 809 | "ku", 810 | "ky", 811 | "la", 812 | "lb", 813 | "lo", 814 | "lt", 815 | "lv", 816 | "mg", 817 | "mi", 818 | "mk", 819 | "ml", 820 | "mn", 821 | "mr", 822 | "ms", 823 | "mt", 824 | "my", 825 | "ne", 826 | "nl", 827 | "no", 828 | "ny", 829 | "or", 830 | "pa", 831 | "pl", 832 | "ps", 833 | "pt", 834 | "ro", 835 | "ru", 836 | "rw", 837 | "sd", 838 | "si", 839 | "sk", 840 | "sl", 841 | "sm", 842 | "sn", 843 | "so", 844 | "sq", 845 | "sr", 846 | "st", 847 | "su", 848 | "sv", 849 | "sw", 850 | "ta", 851 | "te", 852 | "tg", 853 | "th", 854 | "tk", 855 | "tl", 856 | "tr", 857 | "tt", 858 | "ug", 859 | "uk", 860 | "ur", 861 | "uz", 862 | "vi", 863 | "xh", 864 | "yi", 865 | "yo", 866 | "zu", 867 | ], 868 | ratelimit: 1000, 869 | }, 870 | } 871 | } 872 | -------------------------------------------------------------------------------- /i18n/en-US.json: -------------------------------------------------------------------------------- 1 | { 2 | "TRANSLATE":"Translate", 3 | "TRANSLATE_MESSAGE": "Translate message", 4 | "SHOW_ORIGINAL_MESSAGE": "Show original message", 5 | "FAILED_TRANSLATE": "Failed to translate your message.", 6 | "TRANSLATED": "translated", 7 | "TRANSLATE_SENT_MESSAGES": "Translate Sent Messages", 8 | "TRANSLATE_SENT_MESSAGES_NOTE": "Whether or not to translate messages you send.", 9 | "TRANSLATE_RECEIVED_MESSAGES": "Translate Received Messages", 10 | "TRANSLATE_RECEIVED_MESSAGES_NOTE": "Whether or not to translate messages you receive automagically. This only works in the current channel.", 11 | "TRANSLATION_ENGINE": "Translation Engine", 12 | "TRANSLATION_ENGINE_NOTE": "The translation engine you want to use.", 13 | "YOUR_LANGUAGE": "Your Language", 14 | "YOUR_LANGUAGE_NOTE": "The language you speak. Incoming messages will be translated to this language.", 15 | "TARGET_LANGUAGE": "Target Language", 16 | "TARGET_LANGUAGE_NOTE": "The language you wish you could speak. Outgoing messages will be translated to this language.", 17 | "OPEN_SETTINGS": "Open Settings", 18 | "MESSAGE_TRANSLATE_SETTINGS": "Message Translate Settings", 19 | "CLOSE_SETTINGS":"Close Settings" 20 | } -------------------------------------------------------------------------------- /i18n/index.js: -------------------------------------------------------------------------------- 1 | require("fs") 2 | .readdirSync(__dirname) 3 | .filter(file => file !== "index.js") 4 | .forEach(filename => { 5 | const moduleName = filename.split(".")[0]; 6 | module.exports[moduleName] = require(`${__dirname}/${filename}`); 7 | }); -------------------------------------------------------------------------------- /i18n/ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "TRANSLATE":"Переводчик", 3 | "TRANSLATE_MESSAGE": "Перевести сообщение", 4 | "SHOW_ORIGINAL_MESSAGE": "Показать исходное сообщение", 5 | "FAILED_TRANSLATE": "Не удалось перевести сообщение.", 6 | "TRANSLATED": "переведено", 7 | "TRANSLATE_SENT_MESSAGES": "Переводить отправленные сообщения", 8 | "TRANSLATE_SENT_MESSAGES_NOTE": "Нужно ли переводить сообщения, которые вы отправляете.", 9 | "TRANSLATE_RECEIVED_MESSAGES": "Переводить получаемые сообщения", 10 | "TRANSLATE_RECEIVED_MESSAGES_NOTE": "Следует ли автоматически переводить сообщения, которые вы получаете. Это работает только в текущем канале.", 11 | "TRANSLATION_ENGINE": "Служба перевода", 12 | "TRANSLATION_ENGINE_NOTE": "Служба перевода, которую вы хотите использовать.", 13 | "YOUR_LANGUAGE": "Ваш язык", 14 | "YOUR_LANGUAGE_NOTE": "Ваш язык. Входящие сообщения будут переведены на этот язык.", 15 | "TARGET_LANGUAGE": "Переводимый язык", 16 | "TARGET_LANGUAGE_NOTE": "Язык, на котором вы хотите говорить. Исходящие сообщения будут переведены на этот язык.", 17 | "OPEN_SETTINGS": "Открыть настройки", 18 | "MESSAGE_TRANSLATE_SETTINGS": "Настройки переводчика", 19 | "CLOSE_SETTINGS":"Закрыть настройки" 20 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Bowser65 3 | * Licensed under the Open Software License version 3.0 4 | * Original work under MIT; See LICENSE. 5 | */ 6 | 7 | const { Plugin } = require("powercord/entities"); 8 | 9 | const TranslateButton = require("./components/TranslateButton"); 10 | const Indicator = require("./components/Indicator"); 11 | const i18n = require("./i18n"); 12 | 13 | const { open: openModal } = require("powercord/modal"); 14 | const { findInReactTree } = require("powercord/util"); 15 | const { inject, uninject } = require("powercord/injector"); 16 | 17 | const SettingsModal = require("./components/SettingsModal"); 18 | const SettingsButton = require("./components/SettingsButton"); 19 | const QuickSettings = require("./components/QuickSettings"); 20 | const Translator = new (require("./TranslationHandler"))(); 21 | const translateAction = require("./TranslationHandler/translateAction"); 22 | 23 | const { 24 | React, 25 | FluxDispatcher, 26 | getModule, 27 | messages: MessageEvents, 28 | channels: { getChannelId }, 29 | contextMenu: { openContextMenu }, 30 | i18n: { Messages } 31 | } = require("powercord/webpack"); 32 | 33 | const MiniPopover = getModule( 34 | (m) => m.default && m.default.displayName === "MiniPopover", false 35 | ); 36 | const ChannelTextAreaButtons = getModule( 37 | (m) => m.type && m.type.displayName === "ChannelTextAreaButtons", false 38 | ); 39 | const MessageContent = getModule( 40 | (m) => m.type && m.type.displayName === "MessageContent", false 41 | ); 42 | const { MenuGroup, MenuItem } = getModule(["MenuGroup", "MenuGroup"], false); 43 | 44 | const generateToastID = () => "message-translate-translating-" + 45 | Math.random() 46 | .toString(36) 47 | .replace(/[^a-z]+/g, "") 48 | .substring(0, 5); 49 | 50 | module.exports = class MessageTranslate extends Plugin { 51 | constructor () { 52 | super() 53 | this.ConnectedSettingsButton = this.settings.connectStore(SettingsButton); 54 | this.ConnectedSettingsModal = this.settings.connectStore(SettingsModal); 55 | this.ConnectedTranslateButton = this.settings.connectStore(TranslateButton); 56 | this.ConnectedQuickSettings = this.settings.connectStore(QuickSettings); 57 | } 58 | 59 | async startPlugin() { 60 | this.loadStylesheet("style.css"); 61 | powercord.api.i18n.loadAllStrings(i18n); 62 | 63 | inject( // todo: use proper subscribe instead 64 | "message-translate-dispatcher", 65 | FluxDispatcher, 66 | "dispatch", 67 | (args) => { 68 | if ( 69 | args[0].type == "MESSAGE_UPDATE" && 70 | !args[0].translation && 71 | Translator.cache[args[0].message.channel_id] && 72 | Translator.cache[args[0].message.channel_id][args[0].message.id] 73 | ) { 74 | const currentLanguage = Translator.cache[args[0].message.channel_id][args[0].message.id].currentLanguage; 75 | Translator.removeMessage( 76 | args[0].message.channel_id, 77 | args[0].message.id, 78 | false 79 | ); 80 | 81 | Translator.translateMessage( 82 | args[0].message, 83 | currentLanguage, 84 | this.settings.get("translation_engine") 85 | ).catch(this.failedTranslate); 86 | } else if ( 87 | args[0].type == "MESSAGE_CREATE" && 88 | args[0].message && 89 | args[0].message.state !== "SENDING" && 90 | args[0].message.channel_id == getChannelId() && 91 | this.settings.get("translate_received_messages") 92 | ) { 93 | Translator.translateMessage( 94 | args[0].message, 95 | this.settings.get("target_language"), 96 | this.settings.get("translation_engine") 97 | ).catch(this.failedTranslate); 98 | } 99 | return args; 100 | } 101 | ); 102 | 103 | inject( 104 | "message-translate-send-message", 105 | MessageEvents, 106 | "sendMessage", 107 | (args) => { 108 | if (this.settings.get("translate_sent_messages") && !args[1].translation) { 109 | // Make a copy of the original text. 110 | const originalContent = (args[1].content + " ").trim(); 111 | 112 | Translator.translate( 113 | args[1].content, 114 | this.settings.get("target_language"), 115 | this.settings.get("translation_engine") 116 | ) 117 | .then((message) => { 118 | args[1].content = message.text; 119 | args[1].translation = true; 120 | MessageEvents.sendMessage(...args); 121 | }) 122 | .catch((e) => { 123 | // Send the original text if it couldn't be translated. 124 | console.error(e); 125 | powercord.api.notices.sendToast( 126 | this.generateToastID(), 127 | { 128 | header: Messages.TRANSLATE, 129 | content: Messages.FAILED_TRANSLATE, 130 | buttons: [ 131 | { 132 | text: "Send Original Text", 133 | color: "green", 134 | look: "outlined", 135 | onClick: () => { 136 | args[1].content = originalContent; 137 | args[1].translation = true; 138 | MessageEvents.sendMessage( 139 | ...args 140 | ); 141 | }, 142 | }, 143 | { 144 | text: "Do Nothing", 145 | color: "blue", 146 | look: "ghost", 147 | }, 148 | ], 149 | icon: "exclamation-triangle", 150 | timeout: 10e3, 151 | } 152 | ); 153 | }); 154 | 155 | return false; 156 | } 157 | 158 | return args; 159 | }, 160 | true 161 | ); 162 | 163 | inject( 164 | "message-translate-translate-button", 165 | MiniPopover, 166 | "default", 167 | (args, res) => { 168 | const props = findInReactTree(res, (r) => r && r.message); 169 | if (!props) return res; 170 | 171 | res.props.children.unshift( 172 | React.createElement(this.ConnectedTranslateButton, { 173 | openSettings: () => this.openSettings(), 174 | failedTranslate: () => this.failedTranslate(), 175 | message: props.message, 176 | Translator, 177 | }) 178 | ); 179 | return res; 180 | } 181 | ); 182 | 183 | MiniPopover.default.displayName = "MiniPopover"; 184 | 185 | inject( 186 | "message-translate-settings-button", 187 | ChannelTextAreaButtons, 188 | "type", 189 | (args, res) => { 190 | // Add to the buttons. 191 | res.props.children.unshift( 192 | React.createElement(this.ConnectedSettingsButton, { 193 | onClick: () => this.openSettings(), 194 | onContextMenu: (e) => openContextMenu(e, () => 195 | React.createElement(this.ConnectedQuickSettings, { 196 | openSettings: () => this.openSettings() 197 | }) 198 | ) 199 | }) 200 | ); 201 | 202 | return res; 203 | } 204 | ); 205 | 206 | ChannelTextAreaButtons.type.displayName = "ChannelTextAreaButtons"; 207 | 208 | inject( 209 | "message-translate-message-content", 210 | MessageContent, 211 | "type", 212 | (args, res) => { 213 | try { 214 | res.props.children.push( 215 | React.createElement(Indicator, { 216 | originalLanguage: Translator.cache[args[0].message.channel_id][args[0].message.id].originalLanguage, 217 | currentLanguage: Translator.cache[args[0].message.channel_id][args[0].message.id].currentLanguage, 218 | targetLanguage: this.settings.get("target_language"), 219 | }) 220 | ); 221 | } catch {} 222 | 223 | return res; 224 | } 225 | ); 226 | 227 | MessageContent.type.displayName = "MessageContent"; 228 | 229 | this.lazyPatchContextMenu("MessageContextMenu", (MessageContextMenu) => { 230 | inject( 231 | "message-translate-contextmenu", 232 | MessageContextMenu, 233 | "default", 234 | (args, res) => { 235 | if (!args[0]?.message || !res?.props?.children) return res; 236 | const message = args[0].message; 237 | let isTranslated = false; 238 | 239 | try { 240 | isTranslated = Translator.cache[message.channel_id][message.id].currentLanguage != "original" 241 | } catch {} 242 | 243 | res.props.children.splice( 244 | 4, 245 | 0, 246 | React.createElement( 247 | MenuGroup, 248 | null, 249 | React.createElement(MenuItem, { 250 | action: () => { 251 | translateAction( 252 | message, 253 | this.settings.get("user_language"), 254 | this.settings.get("translation_engine"), 255 | Translator, 256 | this.openSettings, 257 | this.failedTranslate 258 | ) 259 | }, 260 | disabled: !message.content, 261 | id: "translate-message", 262 | label: (isTranslated) ? Messages.SHOW_ORIGINAL_MESSAGE : Messages.TRANSLATE_MESSAGE 263 | }) 264 | ) 265 | ); 266 | return res; 267 | } 268 | ); 269 | 270 | MessageContextMenu.default.displayName = "MessageContextMenu"; 271 | }); 272 | } 273 | 274 | pluginWillUnload() { 275 | uninject("message-translate-dispatcher"); 276 | uninject("message-translate-send-message"); 277 | uninject("message-translate-edit-message"); 278 | uninject("message-translate-translate-button"); 279 | uninject("message-translate-settings-button"); 280 | uninject("message-translate-message-content"); 281 | uninject("message-translate-contextmenu"); 282 | uninject("message-translate-lazy-contextmenu"); 283 | Translator.clearCache(); 284 | } 285 | 286 | generateToastID() { 287 | return ( 288 | "message-translate-translating-" + 289 | Math.random() 290 | .toString(36) 291 | .replace(/[^a-z]+/g, "") 292 | .substring(0, 5) 293 | ); 294 | } 295 | 296 | failedTranslate(e) { 297 | console.error(e); 298 | powercord.api.notices.sendToast( 299 | generateToastID(), 300 | { 301 | header: Messages.TRANSLATE, 302 | content: Messages.FAILED_TRANSLATE, 303 | icon: "exclamation-triangle", 304 | timeout: 3e3, 305 | } 306 | ); 307 | } 308 | 309 | openSettings() { 310 | openModal(() => React.createElement(this.ConnectedSettingsModal)); 311 | } 312 | 313 | // Credit to SammCheese: 314 | // https://github.com/SammCheese/holy-notes/blob/e9157324c3d210f1f177c14c6f08ab0580b62dad/index.js#L70 315 | async lazyPatchContextMenu(displayName, patch) { 316 | const filter = (m) => m.default && m.default.displayName === displayName; 317 | const m = getModule(filter, false); 318 | if (m) patch(m); 319 | else { 320 | inject( 321 | "message-translate-lazy-contextmenu", 322 | getModule(["openContextMenuLazy"], false), 323 | "openContextMenuLazy", 324 | (args) => { 325 | const lazyRender = args[1]; 326 | args[1] = async () => { 327 | const render = await lazyRender(args[0]); 328 | return (config) => { 329 | const menu = render(config); 330 | if (menu?.type?.displayName === displayName && patch) { 331 | uninject("message-translate-lazy-contextmenu"); 332 | patch(getModule(filter, false)); 333 | patch = false; 334 | } 335 | return menu; 336 | }; 337 | } 338 | return args; 339 | }, 340 | true 341 | ); 342 | } 343 | } 344 | }; 345 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Message Translate", 3 | "description": "Translates messages to and from languages semi-automagically.", 4 | "author": "Куzа#1794; Bowser65#0001", 5 | "version": "0.2.0-fork1", 6 | "license": "MIT; OSL-3.0" 7 | } 8 | -------------------------------------------------------------------------------- /node_modules/.package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "message-translate", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "node_modules/@iamtraction/google-translate": { 8 | "version": "1.1.3", 9 | "resolved": "git+ssh://git@github.com/FifiTheBulldog/google-translate-api-powercord.git#499cb194f56ea7696bdd0e373c9240149727975f", 10 | "license": "MIT", 11 | "engines": { 12 | "node": ">=8.0.0" 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /node_modules/@iamtraction/google-translate/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | browser: true 3 | commonjs: true 4 | es6: true 5 | node: true 6 | extends: "eslint:recommended" 7 | globals: 8 | Atomics: readonly 9 | SharedArrayBuffer: readonly 10 | parserOptions: 11 | ecmaVersion: 2020 12 | rules: 13 | indent: 14 | - error 15 | - 4 16 | linebreak-style: 17 | - error 18 | - unix 19 | quotes: 20 | - error 21 | - double 22 | semi: 23 | - error 24 | - always 25 | -------------------------------------------------------------------------------- /node_modules/@iamtraction/google-translate/.gitattributes: -------------------------------------------------------------------------------- 1 | *.* text eol=lf 2 | -------------------------------------------------------------------------------- /node_modules/@iamtraction/google-translate/.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: iamtraction 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | custom: https://paypal.me/snkrsnkampa 10 | -------------------------------------------------------------------------------- /node_modules/@iamtraction/google-translate/.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | 3 | name: Node.js CI 4 | 5 | on: 6 | push: 7 | branches: [ master ] 8 | pull_request: 9 | branches: [ master ] 10 | 11 | jobs: 12 | build: 13 | 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | matrix: 18 | node-version: [ 10.x, 12.x ] 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | - run: npm install 27 | - run: npm run build --if-present 28 | - run: npm test 29 | -------------------------------------------------------------------------------- /node_modules/@iamtraction/google-translate/.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to npm when a release is created 2 | 3 | name: Node.js Package 4 | 5 | on: 6 | release: 7 | types: [ created ] 8 | 9 | jobs: 10 | test: 11 | name: Test 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions/setup-node@v1 16 | with: 17 | node-version: 12 18 | - run: npm install 19 | - run: npm test 20 | 21 | publish-npm: 22 | name: Publish to npm 23 | needs: test 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v2 27 | - uses: actions/setup-node@v1 28 | with: 29 | node-version: 12 30 | registry-url: https://registry.npmjs.org/ 31 | - run: npm publish 32 | env: 33 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 34 | -------------------------------------------------------------------------------- /node_modules/@iamtraction/google-translate/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Sankarsan Kampa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /node_modules/@iamtraction/google-translate/README.md: -------------------------------------------------------------------------------- 1 | # Google Translate API 2 | A [Node.JS](https://nodejs.org) library to consume Google Translate for free. 3 | 4 | > This version is modified by [FifiTheBulldog](https://github.com/FifiTheBulldog) for use in [Powercord](https://powercord.dev) plugins, particularly [Message Translate](https://github.com/cyyynthia/message-translate). It uses Powercord's HTTP library instead of `got`, and it strips out the token generator, which apparently doesn't work anyway and isn't needed to get results from Google Translate. The API described in the original README below ("Usage") has not changed, however. 5 | > 6 | > To download this Powercord-ified version from GitHub, run the following command in a terminal: 7 | > 8 | > ```bash 9 | > npm install FifiTheBulldog/google-translate-api-powercord 10 | > ``` 11 | 12 | [![GitHub release](https://img.shields.io/github/release/iamtraction/google-translate.svg?style=flat)](https://github.com/iamtraction/google-translate/releases) 13 | [![Dependencies](https://david-dm.org/iamtraction/google-translate.svg)](https://david-dm.org/iamtraction/google-translate) 14 | [![Known Vulnerabilities](https://snyk.io/test/github/iamtraction/google-translate/badge.svg?targetFile=package.json)](https://snyk.io/test/github/iamtraction/google-translate?targetFile=package.json) 15 | [![license](https://img.shields.io/github/license/iamtraction/google-translate.svg)](LICENSE) 16 | 17 | ### Feature Highlights 18 | * Automatically detect source language 19 | * Automatic spelling corrections 20 | * Automatic language correction 21 | * Fast and reliable 22 | 23 | ## Table of Contents 24 | * [Installation](#installation) 25 | * [Usage](#usage) 26 | * [Examples](#examples) 27 | * [Credits, etc](#extras) 28 | 29 | ## Installation 30 | ```bash 31 | # Stable version, from npm repository 32 | npm install --save @iamtraction/google-translate 33 | 34 | # Latest version, from GitHub repository 35 | npm install --save iamtraction/google-translate 36 | ``` 37 | 38 | ## Usage 39 | ```js 40 | // If you've installed from npm, do: 41 | const translate = require('@iamtraction/google-translate'); 42 | 43 | // If you've installed from GitHub, do: 44 | const translate = require('google-translate'); 45 | ``` 46 | 47 | #### Method: `translate(text, options)` 48 | ```js 49 | translate(text, options).then(console.log).catch(console.error); 50 | ``` 51 | | Parameter | Type | Optional | Default | Description | 52 | |-|-|-|-|-| 53 | | `text` | `String` | No | - | The text you want to translate. | 54 | | `options` | `Object` | - | - | The options for translating. | 55 | | `options.from` | `String` | Yes | `'auto'` | The language name/ISO 639-1 code to translate from. If none is given, it will auto detect the source language. | 56 | | `options.to` | `String` | Yes | `'en'` | The language name/ISO 639-1 code to translate to. If none is given, it will translate to English. | 57 | | `options.raw` | `Boolean` | Yes | `false` | If `true`, it will return the raw output that was received from Google Translate. | 58 | 59 | #### Returns: `Promise` 60 | **Response Object:** 61 | 62 | | Key | Type | Description | 63 | |-|-|-| 64 | | `text` | `String` | The translated text. | 65 | | `from` | `Object` | - | 66 | | `from.language` | `Object` | - | 67 | | `from.language.didYouMean` | `Boolean` | Whether or not the API suggest a correction in the source language. | 68 | | `from.language.iso` | `String` | The ISO 639-1 code of the language that the API has recognized in the text. | 69 | | `from.text` | `Object` | - | 70 | | `from.text.autoCorrected` | `Boolean` | Whether or not the API has auto corrected the original text. | 71 | | `from.text.value` | `String` | The auto corrected text or the text with suggested corrections. Only returned if `from.text.autoCorrected` or `from.text.didYouMean` is `true`. | 72 | | `from.text.didYouMean` | `Boolean` | Wherether or not the API has suggested corrections to the text | 73 | | `raw` | `String` | The raw response from Google Translate servers. Only returned if `options.raw` is `true` in the request options. | 74 | 75 | 76 | ## Examples 77 | #### From automatic language detection to English: 78 | ```js 79 | translate('Tu es incroyable!', { to: 'en' }).then(res => { 80 | console.log(res.text); // OUTPUT: You are amazing! 81 | }).catch(err => { 82 | console.error(err); 83 | }); 84 | ``` 85 | 86 | #### From English to French, with a typo: 87 | ```js 88 | translate('Thank you', { from: 'en', to: 'fr' }).then(res => { 89 | console.log(res.text); // OUTPUT: Je vous remercie 90 | console.log(res.from.autoCorrected); // OUTPUT: true 91 | console.log(res.from.text.value); // OUTPUT: [Thank] you 92 | console.log(res.from.text.didYouMean); // OUTPUT: false 93 | }).catch(err => { 94 | console.error(err); 95 | }); 96 | ``` 97 | 98 | #### Sometimes Google Translate won't auto correct: 99 | ```js 100 | translate('Thank you', { from: 'en', to: 'fr' }).then(res => { 101 | console.log(res.text); // OUTPUT: '' 102 | console.log(res.from.autoCorrected); // OUTPUT: false 103 | console.log(res.from.text.value); // OUTPUT: [Thank] you 104 | console.log(res.from.text.didYouMean); // OUTPUT: true 105 | }).catch(err => { 106 | console.error(err); 107 | }); 108 | ``` 109 | 110 | ## Extras 111 | If you liked this project, please give it a ⭐ in [**GitHub**](https://github.com/iamtraction/google-translate). 112 | 113 | > Credits to [matheuss](https://github.com/matheuss) for writing the original version of this library. I rewrote this, with improvements and without using many external libraries, as his library was not actively developed and had vulnerabilities. 114 | -------------------------------------------------------------------------------- /node_modules/@iamtraction/google-translate/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@iamtraction/google-translate", 3 | "version": "1.1.3", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@iamtraction/google-translate", 9 | "version": "1.1.3", 10 | "license": "MIT", 11 | "engines": { 12 | "node": ">=8.0.0" 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /node_modules/@iamtraction/google-translate/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@iamtraction/google-translate", 3 | "version": "1.1.3", 4 | "description": "A Node.JS library to consume Google Translate API for free.", 5 | "main": "src/index.js", 6 | "typings": "typings/index.d.ts", 7 | "scripts": { 8 | "test:lint": "./node_modules/.bin/eslint .", 9 | "test": "npm run test:lint", 10 | "start": "node ." 11 | }, 12 | "repository": "https://github.com/iamtraction/google-translate", 13 | "keywords": [ 14 | "google translate api", 15 | "google translate", 16 | "google api", 17 | "translate api", 18 | "google", 19 | "translate", 20 | "api" 21 | ], 22 | "author": "Sankarsan Kampa (iamtraction)", 23 | "license": "MIT", 24 | "engines": { 25 | "node": ">=8.0.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /node_modules/@iamtraction/google-translate/src/index.js: -------------------------------------------------------------------------------- 1 | const { get, post } = require("powercord/http"); 2 | const languages = require("./languages"); 3 | const { stringify: qsEncode } = require("querystring"); 4 | 5 | const BASE_URL = "https://translate.google.com/translate_a/single"; 6 | 7 | /** 8 | * @function translate 9 | * @param {String} text The text to be translated. 10 | * @param {Object} options The options object for the translator. 11 | * @returns {Object} The result containing the translation. 12 | */ 13 | async function translate(text, options) { 14 | if (typeof options !== "object") options = {}; 15 | text = String(text); 16 | 17 | // Check if a lanugage is in supported; if not, throw an error object. 18 | for (const lang of [ options.from, options.to ]) { 19 | if (lang && !languages.isSupported(lang)) { 20 | throw new Error(`The language '${lang}' is not supported.`); 21 | } 22 | } 23 | 24 | // If options object doesn"t have "from" language, set it to "auto". 25 | if (!Object.prototype.hasOwnProperty.call(options, "from")) options.from = "auto"; 26 | // If options object doesn"t have "to" language, set it to "en". 27 | if (!Object.prototype.hasOwnProperty.call(options, "to")) options.to = "en"; 28 | // If options object has a "raw" property evaluating to true, set it to true. 29 | options.raw = Boolean(options.raw); 30 | 31 | // Get ISO 639-1 codes for the languages. 32 | options.from = languages.getISOCode(options.from); 33 | options.to = languages.getISOCode(options.to); 34 | 35 | // URL & query string required by Google Translate. 36 | const data = { 37 | client: "gtx", 38 | sl: options.from, 39 | tl: options.to, 40 | hl: options.to, 41 | dt: [ "at", "bd", "ex", "ld", "md", "qca", "rw", "rm", "ss", "t" ], 42 | ie: "UTF-8", 43 | oe: "UTF-8", 44 | otf: 1, 45 | ssel: 0, 46 | tsel: 0, 47 | kc: 7, 48 | q: text 49 | }; 50 | 51 | // Append query string to the request URL. 52 | let url = `${BASE_URL}?${qsEncode(data)}`; 53 | let req; 54 | // If request URL is greater than 2048 characters, use POST method. 55 | if (url.length > 2048) { 56 | delete data.q; 57 | url = `${BASE_URL}?${qsEncode(data)}`; 58 | req = post(url) 59 | .set("Content-Type", "application/x-www-form-urlencoded") 60 | .send({ q: text }); 61 | } else { 62 | req = get(url); 63 | } 64 | 65 | // Request translation from Google Translate. 66 | const { body } = await req.execute(); 67 | 68 | const result = { 69 | text: "", 70 | from: { 71 | language: { 72 | didYouMean: false, 73 | iso: "" 74 | }, 75 | text: { 76 | autoCorrected: false, 77 | value: "", 78 | didYouMean: false 79 | } 80 | }, 81 | raw: "" 82 | }; 83 | 84 | // If user requested a raw output, add the raw response to the result 85 | if (options.raw) { 86 | result.raw = body; 87 | } 88 | 89 | // Add JSON body to result object. 90 | for (const obj of body[0]) { 91 | if (obj[0]) { 92 | result.text += obj[0]; 93 | } 94 | }; 95 | 96 | if (body[2] === body[8][0][0]) { 97 | result.from.language.iso = body[2]; 98 | } else { 99 | result.from.language.didYouMean = true; 100 | result.from.language.iso = body[8][0][0]; 101 | } 102 | 103 | if (body[7] && body[7][0]) { 104 | result.from.text.value = body[7][0] 105 | .replace(//g, "[") 106 | .replace(/<\/i><\/b>/g, "]"); 107 | 108 | if (body[7][5] === true) { 109 | result.from.text.autoCorrected = true; 110 | } else { 111 | result.from.text.didYouMean = true; 112 | } 113 | } 114 | 115 | return result; 116 | } 117 | 118 | module.exports = translate; 119 | module.exports.languages = languages; 120 | -------------------------------------------------------------------------------- /node_modules/@iamtraction/google-translate/src/languages.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Generated from https://translate.google.com 3 | * 4 | * The languages that Google Translate supports (as of 7/5/2020) alongside 5 | * their ISO 639-1 codes 6 | * @see https://cloud.google.com/translate/docs/languages 7 | * @see https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes 8 | */ 9 | 10 | const languages = { 11 | "auto": "Automatic", 12 | "af": "Afrikaans", 13 | "sq": "Albanian", 14 | "am": "Amharic", 15 | "ar": "Arabic", 16 | "hy": "Armenian", 17 | "az": "Azerbaijani", 18 | "eu": "Basque", 19 | "be": "Belarusian", 20 | "bn": "Bengali", 21 | "bs": "Bosnian", 22 | "bg": "Bulgarian", 23 | "ca": "Catalan", 24 | "ceb": "Cebuano", 25 | "ny": "Chichewa", 26 | "zh-cn": "Chinese Simplified", 27 | "zh-tw": "Chinese Traditional", 28 | "co": "Corsican", 29 | "hr": "Croatian", 30 | "cs": "Czech", 31 | "da": "Danish", 32 | "nl": "Dutch", 33 | "en": "English", 34 | "eo": "Esperanto", 35 | "et": "Estonian", 36 | "tl": "Filipino", 37 | "fi": "Finnish", 38 | "fr": "French", 39 | "fy": "Frisian", 40 | "gl": "Galician", 41 | "ka": "Georgian", 42 | "de": "German", 43 | "el": "Greek", 44 | "gu": "Gujarati", 45 | "ht": "Haitian Creole", 46 | "ha": "Hausa", 47 | "haw": "Hawaiian", 48 | "iw": "Hebrew", 49 | "hi": "Hindi", 50 | "hmn": "Hmong", 51 | "hu": "Hungarian", 52 | "is": "Icelandic", 53 | "ig": "Igbo", 54 | "id": "Indonesian", 55 | "ga": "Irish", 56 | "it": "Italian", 57 | "ja": "Japanese", 58 | "jw": "Javanese", 59 | "kn": "Kannada", 60 | "kk": "Kazakh", 61 | "km": "Khmer", 62 | "ko": "Korean", 63 | "ku": "Kurdish (Kurmanji)", 64 | "ky": "Kyrgyz", 65 | "lo": "Lao", 66 | "la": "Latin", 67 | "lv": "Latvian", 68 | "lt": "Lithuanian", 69 | "lb": "Luxembourgish", 70 | "mk": "Macedonian", 71 | "mg": "Malagasy", 72 | "ms": "Malay", 73 | "ml": "Malayalam", 74 | "mt": "Maltese", 75 | "mi": "Maori", 76 | "mr": "Marathi", 77 | "mn": "Mongolian", 78 | "my": "Myanmar (Burmese)", 79 | "ne": "Nepali", 80 | "no": "Norwegian", 81 | "ps": "Pashto", 82 | "fa": "Persian", 83 | "pl": "Polish", 84 | "pt": "Portuguese", 85 | "pa": "Punjabi", 86 | "ro": "Romanian", 87 | "ru": "Russian", 88 | "sm": "Samoan", 89 | "gd": "Scots Gaelic", 90 | "sr": "Serbian", 91 | "st": "Sesotho", 92 | "sn": "Shona", 93 | "sd": "Sindhi", 94 | "si": "Sinhala", 95 | "sk": "Slovak", 96 | "sl": "Slovenian", 97 | "so": "Somali", 98 | "es": "Spanish", 99 | "su": "Sundanese", 100 | "sw": "Swahili", 101 | "sv": "Swedish", 102 | "tg": "Tajik", 103 | "ta": "Tamil", 104 | "te": "Telugu", 105 | "th": "Thai", 106 | "tr": "Turkish", 107 | "uk": "Ukrainian", 108 | "ur": "Urdu", 109 | "uz": "Uzbek", 110 | "vi": "Vietnamese", 111 | "cy": "Welsh", 112 | "xh": "Xhosa", 113 | "yi": "Yiddish", 114 | "yo": "Yoruba", 115 | "zu": "Zulu" 116 | }; 117 | 118 | /** 119 | * Returns the ISO 639-1 code of the desiredLang – if it is supported by 120 | * Google Translate 121 | * @param {string} language The name or the code of the desired language 122 | * @returns {string|boolean} The ISO 639-1 code of the language or null if the 123 | * language is not supported 124 | */ 125 | function getISOCode(language) { 126 | if (!language) return false; 127 | language = language.toLowerCase(); 128 | if (language in languages) return language; 129 | 130 | const keys = Object.keys(languages).filter((key) => { 131 | if (typeof languages[key] !== "string") return false; 132 | 133 | return languages[key].toLowerCase() === language; 134 | }); 135 | 136 | return keys[0] || null; 137 | } 138 | 139 | /** 140 | * Returns true if the desiredLang is supported by Google Translate and false otherwise 141 | * @param {String} language The ISO 639-1 code or the name of the desired language. 142 | * @returns {boolean} If the language is supported or not. 143 | */ 144 | function isSupported(language) { 145 | return Boolean(getISOCode(language)); 146 | } 147 | 148 | module.exports = languages; 149 | module.exports.isSupported = isSupported; 150 | module.exports.getISOCode = getISOCode; 151 | -------------------------------------------------------------------------------- /node_modules/@iamtraction/google-translate/typings/index.d.ts: -------------------------------------------------------------------------------- 1 | interface TranslateOption { 2 | /** The language name/ISO 639-1 code to translate from. If none is given, it will auto detect the source language. */ 3 | from?: string; 4 | /** The language name/ISO 639-1 code to translate to. If none is given, it will translate to English. */ 5 | to?: string; 6 | /** If `true`, it will return the raw output that was received from Google Translate. */ 7 | raw?: boolean; 8 | } 9 | 10 | interface TranslateResponse { 11 | /** The translated text */ 12 | text: string; 13 | from: { 14 | language: { 15 | /** Whether or not the API suggest a correction in the source language. */ 16 | didYouMean: boolean; 17 | /** The ISO 639-1 code of the language that the API has recognized in the text. */ 18 | iso: string; 19 | }; 20 | text: { 21 | /** Whether or not the API has auto corrected the original text. */ 22 | autoCorrected: boolean; 23 | /** The auto corrected text or the text with suggested corrections. Only returned if `from.text.autoCorrected` or `from.text.didYouMean` is `true`. */ 24 | value: string; 25 | /** Wherether or not the API has suggested corrections to the text. */ 26 | didYouMean: boolean; 27 | }; 28 | }; 29 | /** The raw response from Google Translate servers. Only returned if `options.raw` is `true` in the request options. */ 30 | raw: string; 31 | } 32 | 33 | 34 | /** 35 | * @param {String} text The text you want to translate. 36 | * @param {String} text The options for translating. 37 | */ 38 | declare function translate(text: string, options?: TranslateOption): Promise; 39 | 40 | export = translate; 41 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "message-translate", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "message-translate", 9 | "version": "1.0.0", 10 | "license": "(MIT OR OSL-3.0)", 11 | "dependencies": { 12 | "@iamtraction/google-translate": "github:FifiTheBulldog/google-translate-api-powercord" 13 | } 14 | }, 15 | "node_modules/@iamtraction/google-translate": { 16 | "version": "1.1.3", 17 | "resolved": "git+ssh://git@github.com/FifiTheBulldog/google-translate-api-powercord.git#499cb194f56ea7696bdd0e373c9240149727975f", 18 | "license": "MIT", 19 | "engines": { 20 | "node": ">=8.0.0" 21 | } 22 | } 23 | }, 24 | "dependencies": { 25 | "@iamtraction/google-translate": { 26 | "version": "git+ssh://git@github.com/FifiTheBulldog/google-translate-api-powercord.git#499cb194f56ea7696bdd0e373c9240149727975f", 27 | "from": "@iamtraction/google-translate@github:FifiTheBulldog/google-translate-api-powercord" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "message-translate", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "(MIT OR OSL-3.0)", 6 | "dependencies": { 7 | "@iamtraction/google-translate": "github:FifiTheBulldog/google-translate-api-powercord" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | .message-translate-settings-button.active * { 2 | color: #43b581 !important; 3 | } 4 | 5 | .message-translate-settings-button.active *:hover { 6 | color: #66c79b !important; 7 | } 8 | -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Bowser65 3 | * Licensed under the Open Software License version 3.0 4 | */ 5 | 6 | const { Engines, IsoLangs } = require("./constants"); 7 | 8 | module.exports = { 9 | languagesForEngine(engine) { 10 | if (!Engines[engine]) return null 11 | 12 | return Engines[engine].languages.map((language) => { 13 | if (IsoLangs[language]) { 14 | return { 15 | label: IsoLangs[language].nativeName !== IsoLangs[language].name 16 | ? `${IsoLangs[language].name} | ${IsoLangs[language].nativeName}` 17 | : IsoLangs[language].nativeName, 18 | value: language, 19 | }; 20 | } 21 | return { 22 | label: language, 23 | value: language, 24 | }; 25 | }); 26 | } 27 | }; 28 | --------------------------------------------------------------------------------