您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Extract, translate, and display Spotify lyrics with a language selector and manual translation trigger
// ==UserScript== // @name Cigi Spotify Translator // @namespace http://tampermonkey.net/ // @version 1.0 // @description Extract, translate, and display Spotify lyrics with a language selector and manual translation trigger // @author Raiwulf // @match *://*.spotify.com/* // @grant none // @license MIT // ==/UserScript== (function () { 'use strict'; const DEFAULT_LANGUAGE = 'en'; let isTranslating = false; const languages = { // Popular languages en: 'English', es: 'Spanish', fr: 'French', de: 'German', it: 'Italian', pt: 'Portuguese', ru: 'Russian', ja: 'Japanese', ko: 'Korean', zh: 'Chinese', ar: 'Arabic', hi: 'Hindi', tr: 'Turkish', // Rest in alphabetical order af: 'Afrikaans', sq: 'Albanian', am: 'Amharic', hy: 'Armenian', az: 'Azerbaijani', eu: 'Basque', be: 'Belarusian', bn: 'Bengali', bs: 'Bosnian', bg: 'Bulgarian', ca: 'Catalan', ceb: 'Cebuano', co: 'Corsican', hr: 'Croatian', cs: 'Czech', da: 'Danish', nl: 'Dutch', eo: 'Esperanto', et: 'Estonian', fi: 'Finnish', fy: 'Frisian', gl: 'Galician', ka: 'Georgian', el: 'Greek', gu: 'Gujarati', ht: 'Haitian Creole', ha: 'Hausa', haw: 'Hawaiian', he: 'Hebrew', hmn: 'Hmong', hu: 'Hungarian', is: 'Icelandic', ig: 'Igbo', id: 'Indonesian', ga: 'Irish', jv: 'Javanese', kn: 'Kannada', kk: 'Kazakh', km: 'Khmer', rw: 'Kinyarwanda', ku: 'Kurdish', ky: 'Kyrgyz', lo: 'Lao', la: 'Latin', lv: 'Latvian', lt: 'Lithuanian', lb: 'Luxembourgish', mk: 'Macedonian', mg: 'Malagasy', ms: 'Malay', ml: 'Malayalam', mt: 'Maltese', mi: 'Maori', mr: 'Marathi', mn: 'Mongolian', my: 'Myanmar (Burmese)', ne: 'Nepali', no: 'Norwegian', ny: 'Nyanja (Chichewa)', or: 'Odia (Oriya)', ps: 'Pashto', fa: 'Persian', pl: 'Polish', pa: 'Punjabi', ro: 'Romanian', sm: 'Samoan', gd: 'Scots Gaelic', sr: 'Serbian', st: 'Sesotho', sn: 'Shona', sd: 'Sindhi', si: 'Sinhala', sk: 'Slovak', sl: 'Slovenian', so: 'Somali', su: 'Sundanese', sw: 'Swahili', sv: 'Swedish', tl: 'Tagalog (Filipino)', tg: 'Tajik', ta: 'Tamil', tt: 'Tatar', te: 'Telugu', th: 'Thai', tk: 'Turkmen', uk: 'Ukrainian', ur: 'Urdu', ug: 'Uyghur', uz: 'Uzbek', vi: 'Vietnamese', cy: 'Welsh', xh: 'Xhosa', yi: 'Yiddish', yo: 'Yoruba', zu: 'Zulu' }; function getSavedLanguage() { return localStorage.getItem('spotifyLyricsTranslationLang') || DEFAULT_LANGUAGE; } function saveLanguage(lang) { localStorage.setItem('spotifyLyricsTranslationLang', lang); } async function translateText(text, targetLang) { const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=${targetLang}&dt=t&q=${encodeURIComponent(text)}`; try { const response = await fetch(url); const data = await response.json(); return data[0][0][0]; } catch (error) { console.error('Translation failed:', error); return '[Translation Error]'; } } async function translateLyrics() { if (isTranslating) return; isTranslating = true; const targetLang = getSavedLanguage(); const lyricsDivs = document.querySelectorAll('[data-testid="fullscreen-lyric"] div'); document.querySelectorAll('[data-translated="true"]').forEach(el => el.remove()); const originalLines = []; lyricsDivs.forEach((div, index) => { const originalText = div.textContent.trim(); if (originalText && originalText !== "♪") { originalLines.push({ index, text: originalText }); } }); const translatedLines = await Promise.all(originalLines.map(async (line) => { const translatedText = await translateText(line.text, targetLang); return { index: line.index, translatedText }; })); translatedLines.forEach(({ index, translatedText }) => { const targetDiv = lyricsDivs[index]; const translationDiv = document.createElement('div'); translationDiv.style.color = 'gray'; translationDiv.style.fontStyle = 'italic'; translationDiv.textContent = translatedText; translationDiv.setAttribute('data-translated', 'true'); targetDiv.parentNode.insertBefore(translationDiv, targetDiv.nextSibling); }); isTranslating = false; } function observeLyrics() { const targetNode = document.querySelector('[data-testid="lyrics-container"]') || document.querySelector('[data-testid="fullscreen-lyric"]'); if (!targetNode) return; const observer = new MutationObserver(() => { translateLyrics(); }); observer.observe(targetNode, { childList: true, subtree: true, }); } function createHeader() { const header = document.createElement('div'); header.style.cssText = ` display: flex; justify-content: center; align-items: center; padding: 12px 0; background: rgba(40, 40, 40, 0.95); width: 100%; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); margin: 0; `; const controlsContainer = document.createElement('div'); controlsContainer.style.cssText = ` display: flex; align-items: center; justify-content: center; gap: 15px; max-width: 600px; width: 90%; `; const selectContainer = document.createElement('div'); selectContainer.style.cssText = ` position: relative; flex: 0 1 200px; min-width: 120px; `; const selectButton = document.createElement('button'); selectButton.style.cssText = ` width: 100%; padding: 8px; border: none; border-radius: 4px; background: rgba(80, 80, 80, 1); color: white; font-size: 14px; text-align: left; cursor: pointer; display: flex; justify-content: space-between; align-items: center; `; selectButton.textContent = languages[getSavedLanguage()]; const dropdown = document.createElement('div'); dropdown.style.cssText = ` display: none; position: absolute; top: 100%; left: 0; right: 0; background: rgba(40, 40, 40, 0.98); border-radius: 4px; margin-top: 4px; max-height: 300px; overflow-y: auto; z-index: 1000; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); `; const searchInput = document.createElement('input'); searchInput.style.cssText = ` width: calc(100% - 16px); margin: 8px; padding: 8px; border: none; border-radius: 4px; background: rgba(255, 255, 255, 0.1); color: white; font-size: 14px; `; searchInput.placeholder = 'Search language...'; const optionsContainer = document.createElement('div'); optionsContainer.style.cssText = ` padding: 8px 0; `; function createLanguageOptions(filter = '') { optionsContainer.innerHTML = ''; // Separate popular and other languages const popularLanguages = [ 'en', 'tr', 'pl', 'es', 'fr', 'de', 'pt', 'ja', 'it', 'nl' ]; const entries = Object.entries(languages); const filteredEntries = entries.filter(([_, name]) => name.toLowerCase().includes(filter.toLowerCase()) ); // Separate and sort entries const popularEntries = filteredEntries.filter(([code]) => popularLanguages.includes(code) ).sort((a, b) => popularLanguages.indexOf(a[0]) - popularLanguages.indexOf(b[0]) ); const otherEntries = filteredEntries.filter(([code]) => !popularLanguages.includes(code) ); // Create divider if both sections have items if (popularEntries.length > 0 && otherEntries.length > 0) { const divider = document.createElement('div'); divider.style.cssText = ` padding: 8px 16px; color: #888; font-size: 12px; text-transform: uppercase; letter-spacing: 1px; `; divider.textContent = 'Other Languages'; // Create and append all options [...popularEntries, divider, ...otherEntries].forEach(entry => { if (entry instanceof HTMLElement) { optionsContainer.appendChild(entry); return; } const [code, name] = entry; const option = document.createElement('div'); option.style.cssText = ` padding: 8px 16px; cursor: pointer; color: white; &:hover { background: rgba(255, 255, 255, 0.1); } `; option.textContent = name; option.addEventListener('click', () => { selectButton.textContent = name; dropdown.style.display = 'none'; saveLanguage(code); document.querySelectorAll('[data-translated="true"]').forEach(el => el.remove()); translateLyrics(); }); optionsContainer.appendChild(option); }); } } selectButton.addEventListener('click', () => { dropdown.style.display = dropdown.style.display === 'none' ? 'block' : 'none'; if (dropdown.style.display === 'block') { searchInput.focus(); } }); searchInput.addEventListener('input', (e) => { createLanguageOptions(e.target.value); }); document.addEventListener('click', (e) => { if (!selectContainer.contains(e.target)) { dropdown.style.display = 'none'; } }); createLanguageOptions(); dropdown.appendChild(searchInput); dropdown.appendChild(optionsContainer); selectContainer.appendChild(selectButton); selectContainer.appendChild(dropdown); const translateButton = document.createElement('button'); translateButton.textContent = 'Translate'; translateButton.style.cssText = ` padding: 8px 16px; background-color: #1db954; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; font-weight: 500; min-width: 100px; `; translateButton.addEventListener('click', () => { document.querySelectorAll('[data-translated="true"]').forEach(el => el.remove()); translateLyrics(); }); controlsContainer.appendChild(selectContainer); controlsContainer.appendChild(translateButton); header.appendChild(controlsContainer); const mainView = document.querySelector('.main-view-container__scroll-node-child'); if (mainView) { mainView.insertBefore(header, mainView.firstChild); } } function waitForLyrics() { const lyricsContainer = document.querySelector('[data-testid="lyrics-container"]') || document.querySelector('[data-testid="fullscreen-lyric"]'); if (lyricsContainer) { createHeader(); observeLyrics(); translateLyrics(); } else { setTimeout(waitForLyrics, 1000); } } function checkForTranslation() { setInterval(() => { if (!document.querySelector('[data-translated="true"]') && !isTranslating) { translateLyrics(); } }, 2000); } window.addEventListener('load', function () { waitForLyrics(); checkForTranslation(); }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址