ChatGPT Translator

The ChatGPT Translator is a userscript that automatically translates messages in the chat and user's messages before submitting them in English.

当前为 2023-01-20 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name ChatGPT Translator
  3. // @description The ChatGPT Translator is a userscript that automatically translates messages in the chat and user's messages before submitting them in English.
  4. // @namespace ChatGPTTranslator
  5. // @version 1.8.1
  6. // @author CriDos
  7. // @match https://chat.openai.com/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=chat.openai.com
  9. // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.3/jquery.min.js
  10. // @grant GM_xmlhttpRequest
  11. // @run-at document-end
  12. // @license MIT
  13. // ==/UserScript==
  14.  
  15. 'use strict';
  16.  
  17. console.log(`ChatGPT Translator initializing...`);
  18.  
  19. let debug = false;
  20.  
  21. setInterval(() => {
  22. try {
  23. addAutoTranslate();
  24. } catch (error) {
  25. console.error(error);
  26. }
  27.  
  28. try {
  29. findAndHookTextareaElement();
  30. } catch (error) {
  31. console.error(error);
  32. }
  33. }, 100);
  34.  
  35. function addAutoTranslate() {
  36. var messages = document.querySelectorAll(".markdown.prose");
  37.  
  38. for (var i = 0; i < messages.length; i++) {
  39. const msgMarkdownNode = messages[i];
  40. const parentMsgMarkdown = msgMarkdownNode.parentElement;
  41.  
  42. if (parentMsgMarkdown.isAutoTranslate) {
  43. continue;
  44. }
  45. parentMsgMarkdown.isAutoTranslate = true;
  46.  
  47. setInterval(async () => {
  48. await translateNode(msgMarkdownNode);
  49. }, 500);
  50. }
  51. }
  52.  
  53. function findAndHookTextareaElement() {
  54. const targetElement = document.querySelector("textarea");
  55. if (targetElement === null) {
  56. return;
  57. }
  58.  
  59. if (targetElement.isAddHookKeydownEvent === true) {
  60. return;
  61. }
  62.  
  63. targetElement.isAddHookKeydownEvent = true;
  64.  
  65. console.log(`Textarea element found. Adding keydown event listener.`);
  66. targetElement.addEventListener("keydown", async event => await handleSubmit(event, targetElement), true);
  67. }
  68.  
  69. async function handleSubmit(event, targetElement) {
  70. console.log(`Keydown event detected: type - ${event.type}, key - ${event.key}`);
  71.  
  72. if (event.shiftKey && event.key === "Enter") {
  73. return;
  74. }
  75.  
  76. if (window.isActiveOnSubmit === true) {
  77. return;
  78. }
  79.  
  80. if (event.key === "Enter") {
  81. window.isActiveOnSubmit = true;
  82. event.stopImmediatePropagation();
  83.  
  84. const request = targetElement.value;
  85. targetElement.value = "";
  86.  
  87. const translatedText = await translate(request, navigator.language, "en", "text");
  88.  
  89. targetElement.focus();
  90. targetElement.value = translatedText;
  91. const enterEvent = new KeyboardEvent("keydown", {
  92. bubbles: true,
  93. cancelable: true,
  94. key: "Enter",
  95. code: "Enter"
  96. });
  97. targetElement.dispatchEvent(enterEvent);
  98.  
  99. window.isActiveOnSubmit = false;
  100. }
  101. }
  102.  
  103. async function translateNode(node) {
  104. const translateClassName = "translate-markdown";
  105. const parentNode = node.parentElement;
  106. const nodeContent = $(node).html();
  107.  
  108. if (node.storeContent == nodeContent) {
  109. return;
  110. }
  111. node.storeContent = nodeContent;
  112.  
  113. var translateNode = parentNode.querySelector(`.${translateClassName}`);
  114. if (translateNode == null) {
  115. translateNode = node.cloneNode(true);
  116. translateNode.classList.add(translateClassName);
  117. parentNode.insertBefore(translateNode, parentNode.firstChild);
  118. }
  119.  
  120. var translatedContent = await translateHTML(nodeContent, "auto", navigator.language)
  121. translatedContent = translatedContent.replace(/<\/div>$/, '');
  122.  
  123. const endOfTranslationText = await translate("end of translation", "en", navigator.language, "text");
  124.  
  125. $(translateNode).html(translatedContent + `<p>.......... ${endOfTranslationText} ..........</p></div>`);
  126. }
  127.  
  128. async function translateHTML(html, sLang, tLang) {
  129. const excludeTagRegex = /<(pre|code)[^>]*>([\s\S]*?)<\/(pre|code)>/g;
  130. const excludeTags = [];
  131. const excludePlaceholder = 'e0x1c';
  132.  
  133. let htmlContent = html;
  134.  
  135. let excludeTagsMatch;
  136. while (excludeTagsMatch = excludeTagRegex.exec(html)) {
  137. excludeTags.push(excludeTagsMatch[0]);
  138. htmlContent = htmlContent.replace(excludeTagsMatch[0], `<${excludePlaceholder}${excludeTags.length - 1}>`);
  139. }
  140.  
  141. if (debug) {
  142. console.log(`preTranslateHTML: ${html}`);
  143. }
  144.  
  145. htmlContent = await translate(htmlContent, sLang, tLang);
  146.  
  147. for (let i = 0; i < excludeTags.length; i++) {
  148. htmlContent = htmlContent.replace(`<${excludePlaceholder}${i}>`, excludeTags[i]);
  149. }
  150.  
  151. if (debug) {
  152. console.log(`postTranslateHTML: ${htmlContent}`);
  153. }
  154.  
  155. return htmlContent;
  156. }
  157.  
  158. const cache = {};
  159.  
  160. async function translate(text, sLang, tLang, format, key) {
  161. try {
  162. if (debug) {
  163. console.log(`preTranslate: ${text}`);
  164. }
  165.  
  166. if (format == null) {
  167. format = "html";
  168. }
  169.  
  170. if (key == null) {
  171. key = "AIzaSyBOti4mM-6x9WDnZIjIeyEU21OpBXqWBgw";
  172. }
  173.  
  174. const cacheKey = text + '_' + format;
  175.  
  176. if (cache[cacheKey]) {
  177. return cache[cacheKey];
  178. }
  179.  
  180. const response = await new Promise((resolve, reject) => {
  181. GM_xmlhttpRequest({
  182. method: "POST",
  183. url: `https://translate.googleapis.com/translate_a/t?client=gtx&format=${format}&sl=${sLang}&tl=${tLang}&key=${key}`,
  184. data: `q=${encodeURIComponent(text)}`,
  185. headers: {
  186. "Content-Type": "application/x-www-form-urlencoded"
  187. },
  188. onload: response => {
  189. const json = JSON.parse(response.responseText);
  190. if (Array.isArray(json[0])) {
  191. resolve(json[0][0]);
  192. } else {
  193. resolve(json[0]);
  194. }
  195. },
  196. onerror: response => {
  197. reject(response.statusText);
  198. }
  199. });
  200. });
  201.  
  202. cache[cacheKey] = response;
  203.  
  204. if (debug) {
  205. console.log(`postTranslate: ${response}`);
  206. }
  207.  
  208. return response;
  209. } catch (error) {
  210. console.error(error);
  211. }
  212. }