您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Overlay to translate on musixmatch (Ctrl +O)
// ==UserScript== // @name MusixmatchToolToTranslate // @namespace http://tampermonkey.net/ // @version 1.3.3 // @description Overlay to translate on musixmatch (Ctrl +O) // @author nicola02nb (https://gist.github.com/nicola02nb) // @match https://curators.musixmatch.com/tool*mode=translate* // @match https://curators-beta.musixmatch.com/tool*mode=translate* // @icon https://www.google.com/s2/favicons?sz=64&domain=musixmatch.com // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // ==/UserScript== (function() { 'use strict'; // Function to decode HTML entities function decodeHTMLEntities(text) { const textArea = document.createElement('textarea'); textArea.innerHTML = text; return textArea.value; } // Function to translate text using Google Translate API async function translateText(text, targetLang = 'en') { const url = `https://translation.googleapis.com/language/translate/v2?key=${settings.apiKey}`; const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ q: text, target: targetLang, }), }); const data = await response.json(); return decodeHTMLEntities(data.data.translations[0].translatedText); } async function translateLine(source, target){ try { const tr = await translateText(source.textContent, settings.trLang); // Capitalize the first letter let formatted = tr.charAt(0).toUpperCase() + tr.slice(1); // Remove the last character if it's a period or comma if (formatted.endsWith('.') || formatted.endsWith(',')) { formatted = formatted.slice(0, -1); } target.removeAttribute('readonly'); target.innerText = formatted; var event = new window.Event('change', { bubbles: true }); target.dispatchEvent(event); return 1; } catch (error) { console.error(`Error translating line with id ${target.id}:`, error); return 0; } } async function translateLines(){ console.log('Translating all lines!'); var translated=0; var promises = []; lines.forEach((value, key) => { //console.log(`Translating line ${key}`); if(value.needTranslation || settings.overrideAlreadyPresent){ promises.push(translateLine(value.source, value.target)); } else{ console.log(`Skipping line ${key}`); } }); // Wait for all the promises to resolve const results = await Promise.all(promises); // Sum the results (1 for success, 0 for failure) translated = results.reduce((sum, result) => sum + result, 0); return translated; } function detectLines(){ info = { totalLines: 0, sourceEmpty: 0, needsTranslation: 0 } let sourceLines = document.querySelectorAll('label[for^="input-"]'); sourceLines.forEach(label => { // Extract the value of the 'for' attribute const forAttribute = label.getAttribute('for'); // Get the part of the 'for' attribute after "input-" const id = forAttribute.slice(6); // "input-" is 6 characters long // Find the first <span> inside the label const firstSpan = label.querySelector('span'); // Check if the <span> exists, then log both values var emptyS = label.textContent === ""; var target = document.getElementById("input-" + id); var emptyT = target.textContent === ""; lines.set(id, { source: label, target: target, needTranslation: emptyT}); info.totalLines = lines.size; if(emptyS){ info.sourceEmpty++; } if(emptyT){ info.needsTranslation++; } }); } async function translate(){ var translated = await translateLines(); console.log(`Translated ${translated} lines of the Total Lines(${info.totalLines})!`); } document.addEventListener('keydown', function(event) { if (event.ctrlKey && event.key === 'o') { event.preventDefault(); console.log('Ctrl + O was pressed!'); detectLines(); console.log(info); showOverlay(); } }); let settings = { apiKey: undefined, trLang: "en", overrideAlreadyPresent: false } if(GM_getValue("mx-tr-settings")){ settings = GM_getValue("mx-tr-settings"); } let lines = new Map(); let info = { totalLines: 0, sourceEmpty: 0, needsTranslation: 0 } // Create overlay element const overlay = document.createElement('div'); overlay.style.position = 'fixed'; overlay.style.top = '0'; overlay.style.left = '0'; overlay.style.width = '100%'; overlay.style.height = '100%'; overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.7)'; overlay.style.zIndex = '9999'; overlay.style.display = 'none'; // Hidden by default // Add overlay to the body document.body.appendChild(overlay); // Create button container const buttonContainer = document.createElement('div'); buttonContainer.style.position = 'absolute'; buttonContainer.style.top = '50%'; buttonContainer.style.left = '50%'; buttonContainer.style.transform = 'translate(-50%, -50%)'; buttonContainer.style.textAlign = 'center'; buttonContainer.style.display = 'flex'; buttonContainer.style.flexDirection = 'column'; buttonContainer.style.gap = '15px'; buttonContainer.style.fontFamily = 'Arial, Helvetica, sans-serif'; overlay.appendChild(buttonContainer); // Create a button const button = document.createElement('button'); button.textContent = 'Translate'; button.style.padding = '10px 20px'; button.style.fontSize = '16px'; button.style.cursor = 'pointer'; button.style.border = 'none'; button.style.borderRadius = '5px'; button.style.backgroundColor = '#4CAF50'; button.style.color = 'white'; button.addEventListener('click', () => { if(requestApiKey()){ translate(); hideOverlay(); } }); buttonContainer.appendChild(button); // Create radio buttons const radioContainer = document.createElement('div'); radioContainer.style.color = 'white'; const radioLabel = document.createElement('label'); radioLabel.textContent = 'Translate lines already translated: '; radioContainer.appendChild(radioLabel); const radio1 = document.createElement('input'); radio1.type = 'radio'; radio1.name = 'option'; const label1 = document.createElement('label'); label1.textContent = 'Yes'; const radio2 = document.createElement('input'); radio2.type = 'radio'; radio2.name = 'option'; radio2.checked = true; const label2 = document.createElement('label'); label2.textContent = 'No'; // Event listener for radio buttons (when user selects a radio option) radio1.addEventListener('change', () => { if (radio1.checked) { settings.overrideAlreadyPresent = true; } }); radio2.addEventListener('change', () => { if (radio2.checked) { settings.overrideAlreadyPresent = false; } }); radioContainer.appendChild(radio1); radioContainer.appendChild(label1); radioContainer.appendChild(radio2); radioContainer.appendChild(label2); buttonContainer.appendChild(radioContainer); // Create language list (dropdown) const languageList = document.createElement('select'); languageList.style.padding = '10px'; languageList.style.fontSize = '16px'; // Language options: (Example with short codes like 'EN', 'FR', etc.) // https://support.musixmatch.com/en/articles/224883-localization-currently-supported-languages const languages = [ { code: 'af', name: 'Afrikaans' }, { code: 'ar', name: 'Arabic' }, { code: 'ca', name: 'Catalan' }, { code: 'zh-CN', name: 'Chinese Simplified' }, { code: 'zh-TW', name: 'Chinese Traditional' }, { code: 'cs', name: 'Czech' }, { code: 'da', name: 'Danish' }, { code: 'nl', name: 'Dutch' }, { code: 'en', name: 'English' }, { code: 'fi', name: 'Finnish' }, { code: 'fr', name: 'French' }, { code: 'de', name: 'German' }, { code: 'el', name: 'Greek' }, { code: 'he', name: 'Hebrew' }, { code: 'hi', name: 'Hindi' }, { code: 'hu', name: 'Hungarian' }, { code: 'id', name: 'Indonesian' }, { code: 'it', name: 'Italian' }, { code: 'ja', name: 'Japanese' }, { code: 'ko', name: 'Korean' }, { code: 'no', name: 'Norwegian' }, { code: 'pl', name: 'Polish' }, { code: 'pt', name: 'Portuguese' }, { code: 'pt-BR', name: 'Portuguese (Brazilian)' }, { code: 'ro', name: 'Romanian' }, { code: 'ru', name: 'Russian' }, { code: 'sr', name: 'Serbian' }, { code: 'es', name: 'Spanish' }, { code: 'sv', name: 'Swedish' }, { code: 'tr', name: 'Turkish' }, { code: 'uk', name: 'Ukrainian' }, { code: 'vi', name: 'Vietnamese' } ]; languages.forEach(language => { const option = document.createElement('option'); option.value = language.code; option.textContent = `${language.name} (${language.code})`; languageList.appendChild(option); }); languageList.value = settings.trLang; // Event listener for language list change languageList.addEventListener('change', (event) => { const selectedLanguage = event.target.value; settings.trLang = selectedLanguage; GM_setValue("mx-tr-settings", settings); //console.log('Selected language code:', selectedLanguage); }); buttonContainer.appendChild(languageList); // Add a button to clear apiKey const apiButton = document.createElement('button'); apiButton.textContent = 'Clear Api Key'; apiButton.style.marginTop = '20px'; apiButton.style.padding = '10px 20px'; apiButton.style.fontSize = '16px'; apiButton.style.cursor = 'pointer'; apiButton.style.border = 'none'; apiButton.style.borderRadius = '5px'; apiButton.style.backgroundColor = '#f2a63a'; apiButton.style.color = 'white'; apiButton.addEventListener('click', () => { settings.apiKey = undefined; GM_setValue("mx-tr-settings", settings); }); buttonContainer.appendChild(apiButton); // Add a close button to hide the overlay const closeButton = document.createElement('button'); closeButton.textContent = 'Close Overlay'; closeButton.style.marginTop = '20px'; closeButton.style.padding = '10px 20px'; closeButton.style.fontSize = '16px'; closeButton.style.cursor = 'pointer'; closeButton.style.border = 'none'; closeButton.style.borderRadius = '5px'; closeButton.style.backgroundColor = '#f44336'; closeButton.style.color = 'white'; closeButton.addEventListener('click', hideOverlay); buttonContainer.appendChild(closeButton); function hideOverlay(){ overlay.style.display = 'none'; } function showOverlay() { overlay.style.display = 'block'; requestApiKey(); } function requestApiKey(){ if (!settings.apiKey) { const enteredKey = prompt("Please enter your API key for Google Translate:"); if (enteredKey) { settings.apiKey = enteredKey; GM_setValue("mx-tr-settings", settings); return true; } console.error("MISSING GOOGLE TRANSLATE API KEY"); return false; } return true; } console.log('Translate and copy script loaded'); /*Old // Function to copy text to clipboard function copyToClipboard(text) { navigator.clipboard.writeText(text).then(() => { console.log('Text copied to clipboard'); }).catch(err => { console.error('Failed to copy text: ', err); }); } // Function to handle text selection async function handleSelection() { console.log('Mouseup!'); const selection = window.getSelection(); const selectedText = selection.toString().trim(); if (selectedText && selection.anchorNode) { const span = selection.anchorNode.parentElement.closest('span.css-1qaijid.r-fdjqy7.r-1inkyih.r-135wba7.r-13awgt0'); if (span) { console.log('Selected text:', selectedText); try { const translatedText = await translateText(selectedText); copyToClipboard(translatedText); console.log('Translated and copied to clipboard:', translatedText); } catch (error) { console.error('Error translating or copying text:', error); } } else { console.log('Selected text is not within the target span'); } } } document.addEventListener('mouseup', handleSelection); */ })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址