您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
使用 Cmd+L(Mac)或 Ctrl+L(Windows)可即时翻译所选文本。支持所有语言,自动检测所选语言,并翻译为浏览器的默认语言。简单、快速、高效。
- // ==UserScript==
- // @name Ultimate Text Selection Translator – Instantly Translate Any Selected Text
- // @name:fr Ultimate Text Selection Translator – Traduis instantanément n’importe quel texte sélectionné
- // @name:es Ultimate Text Selection Translator – Traduce al instante cualquier texto seleccionado
- // @name:de Ultimate Text Selection Translator – Übersetze sofort ausgewählten Text
- // @name:ru Ultimate Text Selection Translator – Мгновенный перевод выделенного текста
- // @name:zh-CN Ultimate Text Selection Translator – 即时翻译所选文本
- // @name:zh-TW Ultimate Text Selection Translator – 即時翻譯所選文字
- // @name:ja Ultimate Text Selection Translator – 選択テキストを即座に翻訳
- // @namespace http://tampermonkey.net/
- // @version 1.2.1
- // @description Translate selected text instantly using Cmd+L (Mac) or Ctrl+L (Windows). Supports all languages and automatically detects the selected language, translating it into your browser's default language. Simple, fast, and efficient.
- // @description:fr Traduis instantanément n’importe quel texte sélectionné avec Cmd+L (Mac) ou Ctrl+L (Windows). Prend en charge toutes les langues, détecte automatiquement la langue sélectionnée et la traduit dans la langue par défaut de ton navigateur. Simple, rapide et efficace.
- // @description:es Traduce al instante cualquier texto seleccionado con Cmd+L (Mac) o Ctrl+L (Windows). Compatible con todos los idiomas, detecta automáticamente el idioma seleccionado y lo traduce al idioma predeterminado de tu navegador. Simple, rápido y eficiente.
- // @description:de Übersetze ausgewählten Text sofort mit Cmd+L (Mac) oder Ctrl+L (Windows). Unterstützt alle Sprachen, erkennt automatisch die ausgewählte Sprache und übersetzt sie in die Standardsprache deines Browsers. Einfach, schnell und effizient.
- // @description:ru Мгновенно переводите выделенный текст с помощью Cmd+L (Mac) или Ctrl+L (Windows). Поддерживает все языки, автоматически определяет выделенный язык и переводит его на язык по умолчанию вашего браузера. Просто, быстро и эффективно.
- // @description:zh-CN 使用 Cmd+L(Mac)或 Ctrl+L(Windows)可即时翻译所选文本。支持所有语言,自动检测所选语言,并翻译为浏览器的默认语言。简单、快速、高效。
- // @description:zh-TW 使用 Cmd+L(Mac)或 Ctrl+L(Windows)可即時翻譯所選文字。支援所有語言,自動偵測所選語言,並翻譯為瀏覽器的預設語言。簡單、快速、高效。
- // @description:ja Cmd+L(Mac)または Ctrl+L(Windows)で選択したテキストを即座に翻訳。すべての言語に対応し、選択された言語を自動的に検出して、ブラウザのデフォルト言語に翻訳。シンプル、スピーディー、効率的。
- // @author Dℝ∃wX
- // @copyright 2025 DℝᴇwX
- // @license Apache-2.0
- // @match *://*/*
- // @grant GM_xmlhttpRequest
- // @icon https://raw.githubusercontent.com/DREwX-code/Ultimate-Text-Selection-Translator/refs/heads/main/Icon_Translate_Script.png
- // @connect translate.googleapis.com
- // @tag translation
- // @tag text selection
- // @tag translate
- // @tag google translate
- // @tag shortcut
- // @tag productivity
- // @tag accessibility
- // @tag language
- // @tag multilingual
- // ==/UserScript==
- /*
- Copyright 2025 Dℝ∃wX
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- https://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- (function() {
- 'use strict';
- const browserLang = navigator.language.split('-')[0];
- const languageNames = {
- 'en': {
- 'auto': 'Detect',
- 'en': 'English',
- 'fr': 'French',
- 'es': 'Spanish',
- 'de': 'German',
- 'it': 'Italian',
- 'pt': 'Portuguese',
- 'ru': 'Russian',
- 'zh-CN': 'Chinese (Simplified)',
- 'ja': 'Japanese',
- 'errors': {
- 'noText': 'No text selected',
- 'translation': 'Translation error',
- 'connection': 'Connection error'
- },
- 'tooltips': {
- 'listenTranslated': 'Listen to translated text',
- 'listenOriginal': 'Listen to original text'
- }
- },
- 'fr': {
- 'auto': 'Détecter',
- 'en': 'Anglais',
- 'fr': 'Français',
- 'es': 'Espagnol',
- 'de': 'Allemand',
- 'it': 'Italien',
- 'pt': 'Portugais',
- 'ru': 'Russe',
- 'zh-CN': 'Chinois (Simplifié)',
- 'ja': 'Japonais',
- 'errors': {
- 'noText': 'Aucun texte sélectionné',
- 'translation': 'Erreur de traduction',
- 'connection': 'Erreur de connexion'
- },
- 'tooltips': {
- 'listenTranslated': 'Écoute le texte traduit',
- 'listenOriginal': 'Écoute le texte original'
- }
- },
- 'es': {
- 'auto': 'Detectar',
- 'en': 'Inglés',
- 'fr': 'Francés',
- 'es': 'Español',
- 'de': 'Alemán',
- 'it': 'Italiano',
- 'pt': 'Portugués',
- 'ru': 'Ruso',
- 'zh-CN': 'Chino (Simplificado)',
- 'ja': 'Japonés',
- 'errors': {
- 'noText': 'No hay texto seleccionado',
- 'translation': 'Error de traducción',
- 'connection': 'Error de conexión'
- },
- 'tooltips': {
- 'listenTranslated': 'Escuchar el texto traducido',
- 'listenOriginal': 'Escuchar el texto original'
- }
- },
- 'de': {
- 'auto': 'Erkennen',
- 'en': 'Englisch',
- 'fr': 'Französisch',
- 'es': 'Spanisch',
- 'de': 'Deutsch',
- 'it': 'Italienisch',
- 'pt': 'Portugiesisch',
- 'ru': 'Russisch',
- 'zh-CN': 'Chinesisch (Vereinfacht)',
- 'ja': 'Japanisch',
- 'errors': {
- 'noText': 'Kein Text ausgewählt',
- 'translation': 'Übersetzungsfehler',
- 'connection': 'Verbindungsfehler'
- },
- 'tooltips': {
- 'listenTranslated': 'Übersetzten Text anhören',
- 'listenOriginal': 'Originaltext anhören'
- }
- },
- 'it': {
- 'auto': 'Rileva',
- 'en': 'Inglese',
- 'fr': 'Francese',
- 'es': 'Spagnolo',
- 'de': 'Tedesco',
- 'it': 'Italiano',
- 'pt': 'Portoghese',
- 'ru': 'Russo',
- 'zh-CN': 'Cinese (Semplificato)',
- 'ja': 'Giapponese',
- 'errors': {
- 'noText': 'Nessun testo selezionato',
- 'translation': 'Errore di traduzione',
- 'connection': 'Errore di connessione'
- },
- 'tooltips': {
- 'listenTranslated': 'Ascolta il testo tradotto',
- 'listenOriginal': 'Ascolta il testo originale'
- }
- },
- 'pt': {
- 'auto': 'Detectar',
- 'en': 'Inglês',
- 'fr': 'Francês',
- 'es': 'Espanhol',
- 'de': 'Alemão',
- 'it': 'Italiano',
- 'pt': 'Português',
- 'ru': 'Russo',
- 'zh-CN': 'Chinês (Simplificado)',
- 'ja': 'Japonês',
- 'errors': {
- 'noText': 'Nenhum texto selecionado',
- 'translation': 'Erro de tradução',
- 'connection': 'Erro de conexão'
- },
- 'tooltips': {
- 'listenTranslated': 'Ouvir o texto traduzido',
- 'listenOriginal': 'Ouvir o texto original'
- }
- },
- 'ru': {
- 'auto': 'Определить',
- 'en': 'Английский',
- 'fr': 'Французский',
- 'es': 'Испанский',
- 'de': 'Немецкий',
- 'it': 'Итальянский',
- 'pt': 'Португальский',
- 'ru': 'Русский',
- 'zh-CN': 'Китайский (упрощённый)',
- 'ja': 'Японский',
- 'errors': {
- 'noText': 'Текст не выделен',
- 'translation': 'Ошибка перевода',
- 'connection': 'Ошибка соединения'
- },
- 'tooltips': {
- 'listenTranslated': 'Прослушать переведённый текст',
- 'listenOriginal': 'Прослушать оригинальный текст'
- }
- },
- 'zh-CN': {
- 'auto': '检测',
- 'en': '英语',
- 'fr': '法语',
- 'es': '西班牙语',
- 'de': '德语',
- 'it': '意大利语',
- 'pt': '葡萄牙语',
- 'ru': '俄语',
- 'zh-CN': '中文(简体)',
- 'ja': '日语',
- 'errors': {
- 'noText': '未选择文本',
- 'translation': '翻译错误',
- 'connection': '连接错误'
- },
- 'tooltips': {
- 'listenTranslated': '聆听翻译文本',
- 'listenOriginal': '聆听原文'
- }
- },
- 'ja': {
- 'auto': '検出',
- 'en': '英語',
- 'fr': 'フランス語',
- 'es': 'スペイン語',
- 'de': 'ドイツ語',
- 'it': 'イタリア語',
- 'pt': 'ポルトガル語',
- 'ru': 'ロシア語',
- 'zh-CN': '中国語(簡体)',
- 'ja': '日本語',
- 'errors': {
- 'noText': 'テキストが選択されていません',
- 'translation': '翻訳エラー',
- 'connection': '接続エラー'
- },
- 'tooltips': {
- 'listenTranslated': '翻訳されたテキストを聞く',
- 'listenOriginal': '元のテキストを聞く'
- }
- }
- };
- const uiLang = languageNames[browserLang] ? browserLang : 'en';
- const langNames = languageNames[uiLang];
- const errors = langNames.errors;
- const tooltips = langNames.tooltips;
- const languages = [
- { code: 'auto', name: langNames.auto },
- { code: 'en', name: langNames.en },
- { code: 'fr', name: langNames.fr },
- { code: 'es', name: langNames.es },
- { code: 'de', name: langNames.de },
- { code: 'it', name: langNames.it },
- { code: 'pt', name: langNames.pt },
- { code: 'ru', name: langNames.ru },
- { code: 'zh-CN', name: langNames['zh-CN'] },
- { code: 'ja', name: langNames.ja }
- ];
- const defaultTargetLang = languages.some(lang => lang.code === browserLang && lang.code !== 'auto') ? browserLang : 'en';
- const translationBox = document.createElement('div');
- translationBox.style.cssText = `
- position: fixed;
- background: linear-gradient(135deg, #1e1e2f 0%, #2a2a4a 100%);
- color: #ffffff;
- padding: 20px;
- border-radius: 12px;
- z-index: 9999;
- display: none;
- max-width: 350px;
- min-width: 250px;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
- font-size: 14px;
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
- backdrop-filter: blur(10px);
- border: 1px solid rgba(255, 255, 255, 0.1);
- `;
- document.body.appendChild(translationBox);
- translationBox.innerHTML = `
- <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
- <select id="sourceLang" style="background: rgba(255, 255, 255, 0.1); color: #fff; border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 6px; padding: 6px; font-size: 13px; cursor: pointer;">
- ${languages.map(lang => `<option value="${lang.code}">${lang.name}</option>`).join('')}
- </select>
- <span style="color: #a0a0c0; margin: 0 8px;">→</span>
- <select id="targetLang" style="background: rgba(255, 255, 255, 0.1); color: #fff; border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 6px; padding: 6px; font-size: 13px; cursor: pointer;">
- ${languages.filter(lang => lang.code !== 'auto').map(lang => `<option value="${lang.code}">${lang.name}</option>`).join('')}
- </select>
- </div>
- <div id="translationText" style="background: rgba(255, 255, 255, 0.05); padding: 12px; border-radius: 8px; min-height: 50px; line-height: 1.5; white-space: pre-wrap;"></div>
- <div style="display: flex; justify-content: flex-end; margin-top: 12px; gap: 10px;">
- <div id="speakButton" style="position: relative; cursor: pointer;">
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
- <path d="M11 5L6 9H2v6h4l5 4V5z"></path>
- <path d="M19.07 4.93a10 10 0 0 1 0 14.14"></path>
- <path d="M15.54 8.46a5 5 0 0 1 0 7.07"></path>
- </svg>
- <div id="speakTooltip" style="display: none; position: absolute; bottom: 100%; right: 0; background: rgba(0, 0, 0, 0.8); color: #fff; padding: 8px; border-radius: 4px; font-size: 12px; white-space: nowrap; z-index: 10000;">
- <div id="speakTranslated" style="padding: 4px 0; cursor: pointer;">${tooltips.listenTranslated}</div>
- <div id="speakOriginal" style="padding: 4px 0; cursor: pointer;">${tooltips.listenOriginal}</div>
- </div>
- </div>
- <div id="copyButton" style="cursor: pointer;" title="Copy translation">
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
- <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
- <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
- </svg>
- </div>
- </div>
- `;
- const sourceLangSelect = translationBox.querySelector('#sourceLang');
- const targetLangSelect = translationBox.querySelector('#targetLang');
- const translationText = translationBox.querySelector('#translationText');
- const speakButton = translationBox.querySelector('#speakButton');
- const speakTooltip = translationBox.querySelector('#speakTooltip');
- const speakTranslated = translationBox.querySelector('#speakTranslated');
- const speakOriginal = translationBox.querySelector('#speakOriginal');
- const copyButton = translationBox.querySelector('#copyButton');
- sourceLangSelect.value = 'auto';
- targetLangSelect.value = defaultTargetLang;
- let currentSelectedText = '';
- let currentTranslatedText = '';
- let detectedSourceLang = 'en';
- function getSelectedText() {
- return window.getSelection().toString().trim();
- }
- function splitSentences(text) {
- const regex = /(\.\s+|\.\n|\.)/;
- let parts = text.split(regex);
- let sentences = [];
- let currentSentence = '';
- for (let i = 0; i < parts.length; i++) {
- currentSentence += parts[i];
- if (parts[i].match(regex) || i === parts.length - 1) {
- if (currentSentence.trim()) {
- sentences.push(currentSentence.trim());
- }
- currentSentence = '';
- }
- }
- return sentences.length ? sentences : [text];
- }
- function translateSentence(text, sourceLang, targetLang, callback) {
- if (!text.trim()) {
- callback(text);
- return;
- }
- const match = text.match(/^(.*?)(?:(\.\s+|\.\n|\.)|$)/);
- const textToTranslate = match[1] || text;
- const delimiter = match[2] || '';
- GM_xmlhttpRequest({
- method: 'GET',
- url: `https://translate.googleapis.com/translate_a/single?client=gtx&sl=${sourceLang}&tl=${targetLang}&dt=t&q=${encodeURIComponent(textToTranslate.trim())}`,
- onload: function(response) {
- try {
- const data = JSON.parse(response.responseText);
- const detected = sourceLang === 'auto' ? data[2] : sourceLang;
- detectedSourceLang = detected;
- const translation = data[0][0][0] + delimiter;
- callback(translation);
- } catch (e) {
- callback(errors.translation + delimiter);
- }
- },
- onerror: function() {
- callback(errors.connection + delimiter);
- }
- });
- }
- function translateText(text, sourceLang, targetLang, callback, position) {
- if (!text) {
- callback(errors.noText, position);
- return;
- }
- const sentences = splitSentences(text);
- let translatedSentences = [];
- let completed = 0;
- sentences.forEach((sentence, index) => {
- translateSentence(sentence, sourceLang, targetLang, (translation) => {
- translatedSentences[index] = translation;
- completed++;
- if (completed === sentences.length) {
- const fullTranslation = translatedSentences.join('');
- callback(fullTranslation, position);
- }
- });
- });
- }
- function speak(text, lang) {
- const utterance = new SpeechSynthesisUtterance(text);
- utterance.lang = lang;
- window.speechSynthesis.speak(utterance);
- }
- document.addEventListener('keydown', (e) => {
- if (e.metaKey && e.key === 'l' && !e.altKey && !e.ctrlKey && !e.shiftKey) {
- e.preventDefault();
- const selectedText = getSelectedText();
- currentSelectedText = selectedText;
- const selection = window.getSelection();
- let position = { x: 0, y: 0 };
- if (selection.rangeCount > 0) {
- const range = selection.getRangeAt(0);
- const rect = range.getBoundingClientRect();
- position = {
- x: rect.left + window.scrollX,
- y: rect.bottom + window.scrollY
- };
- }
- translateText(selectedText, sourceLangSelect.value, targetLangSelect.value, (translation, pos) => {
- currentTranslatedText = translation;
- translationText.textContent = translation;
- translationBox.style.display = 'block';
- translationBox.style.left = `${pos.x + 10}px`;
- translationBox.style.top = `${pos.y + 10}px`;
- translationBox.style.opacity = '1';
- translationBox.style.transform = 'translateY(0)';
- }, position);
- }
- });
- function handleLanguageChange() {
- if (currentSelectedText) {
- translateText(currentSelectedText, sourceLangSelect.value, targetLangSelect.value, (translation, pos) => {
- currentTranslatedText = translation;
- translationText.textContent = translation;
- }, { x: parseFloat(translationBox.style.left), y: parseFloat(translationBox.style.top) });
- }
- }
- sourceLangSelect.addEventListener('change', handleLanguageChange);
- targetLangSelect.addEventListener('change', handleLanguageChange);
- speakButton.addEventListener('mouseenter', () => {
- speakTooltip.style.display = 'block';
- });
- speakButton.addEventListener('mouseleave', () => {
- speakTooltip.style.display = 'none';
- });
- speakTranslated.addEventListener('click', () => {
- if (currentTranslatedText) {
- speak(currentTranslatedText, targetLangSelect.value);
- }
- });
- speakOriginal.addEventListener('click', () => {
- if (currentSelectedText) {
- speak(currentSelectedText, detectedSourceLang);
- }
- });
- copyButton.addEventListener('click', () => {
- if (currentTranslatedText) {
- navigator.clipboard.writeText(currentTranslatedText);
- copyButton.querySelector('svg').style.stroke = '#00ff00';
- setTimeout(() => {
- copyButton.querySelector('svg').style.stroke = '#ffffff';
- }, 1000);
- }
- });
- document.addEventListener('mousedown', (e) => {
- if (!translationBox.contains(e.target)) {
- translationBox.style.display = 'none';
- translationBox.style.opacity = '0';
- translationBox.style.transform = 'translateY(10px)';
- }
- });
- function adjustBoxPosition() {
- const rect = translationBox.getBoundingClientRect();
- if (rect.right > window.innerWidth) {
- translationBox.style.left = `${window.innerWidth - rect.width - 10}px`;
- }
- if (rect.bottom > window.innerHeight) {
- translationBox.style.top = `${window.innerHeight - rect.height - 10}px`;
- }
- }
- translationBox.addEventListener('transitionend', adjustBoxPosition);
- })();