您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
将 MidJourney 网站英文界面翻译为中文,稳定增强版(支持简繁切换、缓存、自动更新、动态监听增强)
当前为
// ==UserScript== // @name MidJourneyCN // @namespace http://tampermonkey.net/ // @version 1.0.0 // @description 将 MidJourney 网站英文界面翻译为中文,稳定增强版(支持简繁切换、缓存、自动更新、动态监听增强) // @author G哥 // @match https://www.midjourney.com/* // @grant none // @run-at document-end // ==/UserScript== (function () { 'use strict'; const config = JSON.parse(localStorage.getItem('mj-trans-config')) || { enabled: true, lang: 'zh-Hans' }; let dictHans = {}; let dictHant = {}; async function loadDictionary(forceReload = false) { const cacheKey = 'mj-trans-dict-cache'; const cache = JSON.parse(localStorage.getItem(cacheKey) || '{}'); const now = Date.now(); if (!forceReload && cache.timestamp && now - cache.timestamp < 6 * 60 * 60 * 1000) { dictHans = cache.dictHans || {}; dictHant = cache.dictHant || {}; return; } const resHans = await fetch('https://cdn.jsdelivr.net/gh/cwser/midjourney-chinese-plugin@main/lang/zh-CN.json'); dictHans = await resHans.json(); const resHant = await fetch('https://cdn.jsdelivr.net/gh/cwser/midjourney-chinese-plugin@main/lang/zh-TW.json'); dictHant = await resHant.json(); localStorage.setItem(cacheKey, JSON.stringify({ timestamp: now, dictHans, dictHant })); } function getDict() { return config.lang === 'zh-Hant' ? dictHant : dictHans; } function translateText(text) { const dict = getDict(); const cleaned = text.trim(); return dict[cleaned] || text; } function processNode(node) { if (!config.enabled || !node || !document.body.contains(node)) return; const translateAttributes = (el) => { const dict = getDict(); ['title', 'aria-label', 'alt'].forEach(attr => { const val = el.getAttribute(attr); if (val && dict[val.trim()]) { el.setAttribute(attr, dict[val.trim()]); el.dataset.translated = 'true'; } }); }; if (node.nodeType === 3) { const translated = translateText(node.textContent); if (translated && translated !== node.textContent) node.textContent = translated; } else if (node.nodeType === 1 && !node.dataset.translated) { translateAttributes(node); if (node.childNodes.length === 1 && node.firstChild.nodeType === 3) { const translated = translateText(node.textContent); if (translated && translated !== node.textContent) { node.textContent = translated; node.dataset.translated = 'true'; } } Array.from(node.childNodes).forEach(processNode); } } function translateAll() { if (!config.enabled) return; processNode(document.body); } const observer = new MutationObserver(mutations => { mutations.forEach(m => { m.addedNodes.forEach(n => processNode(n)); if (m.type === 'characterData') processNode(m.target); }); }); function initObserver() { observer.observe(document.body, { childList: true, subtree: true, characterData: true, attributes: true }); } function createControlPanel() { const btn = document.createElement('div'); btn.id = 'mj-trans-btn'; btn.innerText = '🌐'; btn.style.cssText = ` position: fixed; bottom: 20px; right: 20px; z-index: 9999; width: 40px; height: 40px; background: #000c; color: #fff; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; opacity: 0.6; backdrop-filter: blur(4px);` const panel = document.createElement('div'); panel.id = 'mj-trans-panel'; panel.style.cssText = ` position: fixed; bottom: 20px; right: 70px; z-index: 9998; background: #fffefeee; padding: 10px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.15); display: none; flex-direction: column; font-size: 14px; gap: 6px;` panel.innerHTML = ` <label><input type="checkbox" id="mj-enable"> 启用翻译</label> <label><input type="radio" name="mj-lang" value="zh-Hans"> 简体</label> <label><input type="radio" name="mj-lang" value="zh-Hant"> 繁體</label> <button id="mj-clear-cache" style="margin-top: 8px;">清除缓存</button>` document.body.appendChild(btn); document.body.appendChild(panel); let autoCloseTimer = null; function schedulePanelClose(delay = 3000) { clearTimeout(autoCloseTimer); autoCloseTimer = setTimeout(() => { panel.style.display = 'none'; }, delay); } btn.addEventListener('click', () => { panel.style.display = panel.style.display === 'none' ? 'flex' : 'none'; }); document.addEventListener('click', (e) => { if (!panel.contains(e.target) && !btn.contains(e.target)) panel.style.display = 'none'; }); panel.addEventListener('mouseenter', () => clearTimeout(autoCloseTimer)); panel.addEventListener('mouseleave', () => schedulePanelClose()); document.getElementById('mj-clear-cache').addEventListener('click', () => { localStorage.removeItem('mj-trans-dict-cache'); alert('缓存已清除,将重新加载词典'); location.reload(); }); const enableCheckbox = document.getElementById('mj-enable'); enableCheckbox.checked = config.enabled; enableCheckbox.addEventListener('change', (e) => { config.enabled = e.target.checked; localStorage.setItem('mj-trans-config', JSON.stringify(config)); location.reload(); }); const langRadios = document.querySelectorAll('input[name="mj-lang"]'); langRadios.forEach(radio => { if (radio.value === config.lang) radio.checked = true; radio.addEventListener('change', (e) => { config.lang = e.target.value; localStorage.setItem('mj-trans-config', JSON.stringify(config)); location.reload(); }); }); } window.addEventListener('load', async () => { await loadDictionary(); createControlPanel(); if (config.enabled) { translateAll(); initObserver(); } }); })();