Automatically translates Chinese input to English in real-time
Size
8.2 KB
Version
1.0.1
Created
Nov 9, 2025
Updated
about 1 month ago
1// ==UserScript==
2// @name Chinese to English Input Translator
3// @description Automatically translates Chinese input to English in real-time
4// @version 1.0.1
5// @match https://*.robomonkey.io/*
6// @icon https://robomonkey.io/icon.png?adc3438f5fbb5315
7// ==/UserScript==
8(function() {
9 'use strict';
10
11 console.log('Chinese to English Input Translator: Extension loaded');
12
13 // Debounce function to avoid excessive API calls
14 function debounce(func, wait) {
15 let timeout;
16 return function executedFunction(...args) {
17 const later = () => {
18 clearTimeout(timeout);
19 func(...args);
20 };
21 clearTimeout(timeout);
22 timeout = setTimeout(later, wait);
23 };
24 }
25
26 // Check if text contains Chinese characters
27 function containsChinese(text) {
28 return /[\u4e00-\u9fa5]/.test(text);
29 }
30
31 // Show loading indicator
32 function showLoadingIndicator(element) {
33 const existingIndicator = element.parentElement.querySelector('.translation-loading');
34 if (existingIndicator) return;
35
36 const indicator = document.createElement('div');
37 indicator.className = 'translation-loading';
38 indicator.textContent = '🔄 Translating...';
39 indicator.style.cssText = `
40 position: absolute;
41 background: #007bff;
42 color: white;
43 padding: 4px 8px;
44 border-radius: 4px;
45 font-size: 12px;
46 z-index: 10000;
47 pointer-events: none;
48 box-shadow: 0 2px 8px rgba(0,0,0,0.2);
49 `;
50
51 const rect = element.getBoundingClientRect();
52 indicator.style.top = (rect.top + window.scrollY - 30) + 'px';
53 indicator.style.left = (rect.left + window.scrollX) + 'px';
54
55 document.body.appendChild(indicator);
56 }
57
58 // Hide loading indicator
59 function hideLoadingIndicator() {
60 const indicators = document.querySelectorAll('.translation-loading');
61 indicators.forEach(indicator => indicator.remove());
62 }
63
64 // Show translation result
65 function showTranslationResult(element, originalText, translatedText) {
66 hideLoadingIndicator();
67
68 const result = document.createElement('div');
69 result.className = 'translation-result';
70 result.innerHTML = `
71 <div style="margin-bottom: 4px;">
72 <strong>Original:</strong> ${originalText}
73 </div>
74 <div style="margin-bottom: 8px;">
75 <strong>Translation:</strong> ${translatedText}
76 </div>
77 <button class="use-translation-btn" style="
78 background: #28a745;
79 color: white;
80 border: none;
81 padding: 6px 12px;
82 border-radius: 4px;
83 cursor: pointer;
84 font-size: 12px;
85 margin-right: 8px;
86 ">Use Translation</button>
87 <button class="close-translation-btn" style="
88 background: #dc3545;
89 color: white;
90 border: none;
91 padding: 6px 12px;
92 border-radius: 4px;
93 cursor: pointer;
94 font-size: 12px;
95 ">Close</button>
96 `;
97 result.style.cssText = `
98 position: absolute;
99 background: white;
100 border: 2px solid #007bff;
101 padding: 12px;
102 border-radius: 8px;
103 font-size: 13px;
104 z-index: 10001;
105 box-shadow: 0 4px 12px rgba(0,0,0,0.3);
106 max-width: 400px;
107 color: #333;
108 `;
109
110 const rect = element.getBoundingClientRect();
111 result.style.top = (rect.bottom + window.scrollY + 5) + 'px';
112 result.style.left = (rect.left + window.scrollX) + 'px';
113
114 document.body.appendChild(result);
115
116 // Use translation button
117 result.querySelector('.use-translation-btn').addEventListener('click', () => {
118 element.value = translatedText;
119 element.dispatchEvent(new Event('input', { bubbles: true }));
120 element.dispatchEvent(new Event('change', { bubbles: true }));
121 result.remove();
122 console.log('Translation applied:', translatedText);
123 });
124
125 // Close button
126 result.querySelector('.close-translation-btn').addEventListener('click', () => {
127 result.remove();
128 });
129
130 // Auto-close after 15 seconds
131 setTimeout(() => {
132 if (result.parentElement) {
133 result.remove();
134 }
135 }, 15000);
136 }
137
138 // Translate text using AI
139 async function translateText(text, element) {
140 try {
141 console.log('Translating Chinese text:', text);
142 showLoadingIndicator(element);
143
144 const translation = await RM.aiCall(
145 `Translate the following Chinese text to English. Only return the English translation, nothing else: "${text}"`,
146 {
147 type: "json_schema",
148 json_schema: {
149 name: "translation",
150 schema: {
151 type: "object",
152 properties: {
153 translation: { type: "string" }
154 },
155 required: ["translation"]
156 }
157 }
158 }
159 );
160
161 console.log('Translation result:', translation);
162 showTranslationResult(element, text, translation.translation);
163
164 } catch (error) {
165 console.error('Translation failed:', error);
166 hideLoadingIndicator();
167
168 // Show error message
169 const errorMsg = document.createElement('div');
170 errorMsg.textContent = '❌ Translation failed. Please try again.';
171 errorMsg.style.cssText = `
172 position: absolute;
173 background: #dc3545;
174 color: white;
175 padding: 8px 12px;
176 border-radius: 4px;
177 font-size: 12px;
178 z-index: 10000;
179 `;
180 const rect = element.getBoundingClientRect();
181 errorMsg.style.top = (rect.top + window.scrollY - 30) + 'px';
182 errorMsg.style.left = (rect.left + window.scrollX) + 'px';
183 document.body.appendChild(errorMsg);
184 setTimeout(() => errorMsg.remove(), 3000);
185 }
186 }
187
188 // Handle input events
189 const handleInput = debounce(async (event) => {
190 const element = event.target;
191 const text = element.value.trim();
192
193 // Remove any existing translation results
194 document.querySelectorAll('.translation-result').forEach(el => el.remove());
195
196 // Check if text contains Chinese and is not empty
197 if (text && containsChinese(text)) {
198 console.log('Chinese input detected:', text);
199 await translateText(text, element);
200 }
201 }, 1000); // Wait 1 second after user stops typing
202
203 // Attach listeners to all input fields
204 function attachListeners() {
205 const inputs = document.querySelectorAll('input[type="text"], input:not([type]), textarea');
206
207 inputs.forEach(input => {
208 if (!input.dataset.translatorAttached) {
209 input.dataset.translatorAttached = 'true';
210 input.addEventListener('input', handleInput);
211 console.log('Attached translator to input field');
212 }
213 });
214 }
215
216 // Initialize
217 function init() {
218 console.log('Initializing Chinese to English Input Translator');
219
220 // Attach to existing inputs
221 attachListeners();
222
223 // Watch for new input fields being added to the page
224 const observer = new MutationObserver(debounce(() => {
225 attachListeners();
226 }, 500));
227
228 observer.observe(document.body, {
229 childList: true,
230 subtree: true
231 });
232
233 console.log('Chinese to English Input Translator: Ready');
234 }
235
236 // Start when DOM is ready
237 if (document.readyState === 'loading') {
238 document.addEventListener('DOMContentLoaded', init);
239 } else {
240 init();
241 }
242})();