Persistent Translator Tooltip with Sidebar and Local Storage

Translate words on click, show in a tooltip, add to a sidebar list, and store in local storage

  1. // ==UserScript==
  2. // @name Persistent Translator Tooltip with Sidebar and Local Storage
  3. // @namespace http://tampermonkey.net/
  4. // @author Zabkas
  5. // @version 1.1
  6. // @description Translate words on click, show in a tooltip, add to a sidebar list, and store in local storage
  7. // @include *://*/*
  8. // @grant GM_xmlhttpRequest
  9. // @license MIT
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. // Add CSS for the tooltip, sidebar, clear button, and entry layout
  16. const styleElement = document.createElement('style');
  17. styleElement.type = 'text/css';
  18. styleElement.innerHTML = `
  19. .translator-tooltip {
  20. font-weight: 700;
  21. color: #000000;
  22. position: absolute;
  23. z-index: 10000;
  24. padding: 2px;
  25. max-width: 300px;
  26. border-radius: 0.3em;
  27. background-color: #ffffdb;
  28. border: 1px solid #ccc;
  29. box-shadow: 0 2px 4px rgba(0,0,0,0.2);
  30. text-align: center;
  31. font-size: 18px;
  32. line-height: 1.2;
  33. visibility: hidden;
  34. opacity: 0;
  35. transition: visibility 0s linear 300ms, opacity 300ms;
  36. }
  37.  
  38. .translator-tooltip.visible {
  39. visibility: visible;
  40. opacity: 1;
  41. }
  42.  
  43. .translator-sidebar {
  44. position: fixed;
  45. top: 0;
  46. right: 0;
  47. width: 250px;
  48. height: 100%;
  49. background-color: #ffffdb;
  50. overflow-y: auto;
  51. border-left: 1px solid #ccc;
  52. padding: 10px;
  53. z-index: 10000;
  54. font-size: 16px;
  55. }
  56.  
  57. .translator-entry {
  58. display: flex;
  59. justify-content: space-between;
  60. align-items: center;
  61. margin-bottom: 5px;
  62. }
  63.  
  64. .translator-entry span:first-child {
  65. flex: 1;
  66. text-align: left;
  67. }
  68.  
  69. .translator-entry span:last-child {
  70. flex: 1;
  71. text-align: right;
  72. }
  73.  
  74. .translator-entry hr {
  75. width: 100%;
  76. margin-top: 5px;
  77. }
  78.  
  79. .clear-button {
  80. position: fixed;
  81. bottom: 20px;
  82. right: 20px;
  83. z-index: 10001;
  84. cursor: pointer;
  85. padding: 5px 10px;
  86. background-color: #f5f5f5;
  87. border: 1px solid #ccc;
  88. border-radius: 5px;
  89. box-shadow: 0 2px 4px rgba(0,0,0,0.2);
  90. font-size: 16px;
  91. }
  92. `;
  93. document.head.appendChild(styleElement);
  94.  
  95. // Create tooltip element
  96. const tooltip = document.createElement('div');
  97. tooltip.className = 'translator-tooltip';
  98. document.body.appendChild(tooltip);
  99.  
  100. // Create sidebar element
  101. const sidebar = document.createElement('div');
  102. sidebar.className = 'translator-sidebar';
  103. document.body.appendChild(sidebar);
  104.  
  105. // Create clear button element
  106. const clearButton = document.createElement('button');
  107. clearButton.innerHTML = '🗑️ Clear Translations';
  108. clearButton.className = 'clear-button';
  109. document.body.appendChild(clearButton);
  110.  
  111. // Function to show the tooltip
  112. function showTooltip(text, x, y) {
  113. tooltip.textContent = text;
  114. tooltip.style.left = `${x}px`;
  115. tooltip.style.top = `${y - 30}px`;
  116. tooltip.classList.add('visible');
  117. }
  118.  
  119. // Function to hide the tooltip
  120. function hideTooltip() {
  121. tooltip.classList.remove('visible');
  122. }
  123.  
  124. // Function to add word to sidebar list and store in local storage
  125. function addToSidebar(originalWord, translatedWord) {
  126. const entry = document.createElement('div');
  127. entry.className = 'translator-entry';
  128. entry.innerHTML = `
  129. <span>${originalWord}</span>
  130. <span>${translatedWord}</span>
  131. `;
  132.  
  133. sidebar.appendChild(entry);
  134.  
  135. // Add a horizontal line after each entry
  136. const separator = document.createElement('hr');
  137. sidebar.appendChild(separator);
  138.  
  139. // Store in local storage
  140. const translations = JSON.parse(localStorage.getItem('translations') || '{}');
  141. translations[originalWord] = translatedWord;
  142. localStorage.setItem('translations', JSON.stringify(translations));
  143. }
  144.  
  145. // Function to translate word
  146. function translateWord(word, x, y) {
  147. GM_xmlhttpRequest({
  148. method: "GET",
  149. url: "https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=fa&dj=1&dt=t&dt=rm&q=" + encodeURIComponent(word),
  150. onload: function(response) {
  151. const data = JSON.parse(response.responseText);
  152. const translatedText = data.sentences[0].trans;
  153. showTooltip(translatedText, x, y);
  154. addToSidebar(word, translatedText);
  155. }
  156. });
  157. }
  158.  
  159. // Load translations from local storage and add to sidebar
  160. function loadTranslations() {
  161. const translations = JSON.parse(localStorage.getItem('translations') || '{}');
  162. Object.keys(translations).forEach(word => {
  163. const translatedWord = translations[word];
  164. if (typeof translatedWord === 'string') {
  165. const entry = document.createElement('div');
  166. entry.className = 'translator-entry';
  167. entry.innerHTML = `
  168. <span>${word}</span>
  169. <span>${translatedWord}</span>
  170. `;
  171. sidebar.appendChild(entry);
  172.  
  173. // Add a horizontal line after each entry
  174. const separator = document.createElement('hr');
  175. sidebar.appendChild(separator);
  176. }
  177. });
  178. }
  179.  
  180. // Function to clear translations from sidebar and local storage
  181. function clearTranslations() {
  182. sidebar.innerHTML = '';
  183. localStorage.removeItem('translations');
  184. }
  185.  
  186. // Event listener for clear button
  187. clearButton.addEventListener('click', function() {
  188. clearTranslations();
  189. });
  190.  
  191. // Event listener for mouseup to detect text selection
  192. document.addEventListener('mouseup', function(event) {
  193. const selection = window.getSelection().toString().trim();
  194. if (selection) { // Remove the condition that checks for a single word
  195. const rect = window.getSelection().getRangeAt(0).getBoundingClientRect();
  196. translateWord(selection, rect.left + window.scrollX, rect.top + window.scrollY);
  197. }
  198. });
  199.  
  200. // Event listener to hide tooltip when clicking anywhere on the page
  201. document.addEventListener('mousedown', function(event) {
  202. if (!tooltip.contains(event.target)) {
  203. hideTooltip();
  204. }
  205. }, true);
  206.  
  207. // Load stored translations on page load
  208. loadTranslations();
  209. })();