// ==UserScript==
// @name 网页翻译——自动翻译为中文
// @namespace https://github.com/GeekScript/WebTranslator
// @version 1.0
// @description 给每个非中文的网页右下角(可以调整到左下角)添加一个google翻译图标,该版本为中文翻译版本,只把外语翻译为中文 | 更多实用脚本👉关注公众号:【极客脚本库】
// @author 大角牛软件/u2222223(极客脚本库)
// @match *://*/*
// @connect translate.google.com
// @connect translate.googleapis.com
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @grant GM_notification
// @grant GM_openInTab
// @grant unsafeWindow
// @license MIT
// @noframes
// ==/UserScript==
(function () {
'use strict';
// --- Configuration & Constants ---
const CONFIG = {
excludeSites: [
/^(http|https).*((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}/,
/.*duyaoss\.com/,
/.*lanzous\.com/,
/.*w3school.*cn/,
/.*iqiyi\.com/,
/.*baidu.*/,
/.*cnblogs\.com/,
/.*csdn\.net/,
/.*zhku\.edu\.cn/,
/.*zhihuishu\.com/,
/.*aliyuncs\.com/,
/.*chaoxing\.com/,
/.*youku\.com/,
/.*examcoo\.com/,
/.*mooc\.com/,
/.*bilibili\.com/,
/.*qq\.com/,
/.*yy\.com/,
/.*huya\.com/,
/localhost/,
/.*acfun\.cn/,
/.*eleme\.cn/,
/.*douyin\.com/
],
noTranslateSelectors: [
'.bbCodeCode', 'tt', 'pre[translate="no"]', 'pre', '.post_spoiler_show',
'.c-article-section__content sub', '.c-article-section__content sup',
'.c-article-equation', '.mathjax-tex'
],
specialSiteFixes: [
{ domain: 'curseforge.com', style: 'html { height: auto!important; }' },
{ domain: 'gatesnotes.com', style: '.TGN_site { z-index: 0!important; }' }
]
};
const LOGGER = {
info: (msg) => { }, // Quiet
warn: (msg) => console.warn(`⚠️ [网页翻译] ${msg}`),
error: (msg) => console.error(`❌ [网页翻译] ${msg}`),
promo: () => { } // Quiet
};
// --- Trusted Types Policy (CSP Support) ---
let policy;
if (window.trustedTypes && window.trustedTypes.createPolicy) {
try {
policy = window.trustedTypes.createPolicy('geek_script_policy', {
createHTML: (string) => string
});
} catch (e) {
LOGGER.warn('无法创建 Trusted Types 策略: ' + e);
}
}
const getTrustedHTML = (html) => policy ? policy.createHTML(html) : html;
// --- UI & Modal Logic (Spec Compliant) ---
function showContactModal(isFirstRun = false) {
const oldModal = document.getElementById('geek-modal');
if (oldModal) oldModal.remove();
GM_addStyle(`
#geek-modal {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0, 0, 0, 0.5); z-index: 999999999;
display: flex; justify-content: center; align-items: center;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
}
.geek-modal-content {
background: #fff; padding: 25px; border-radius: 12px;
width: 380px; max-width: 90vw;
box-shadow: 0 4px 20px rgba(0,0,0,0.2);
text-align: center; position: relative;
animation: geek-fadein 0.3s ease-out;
}
.geek-modal-title { font-size: 20px; font-weight: bold; color: #333; margin-bottom: 10px; }
.geek-modal-subtitle { font-size: 14px; color: #666; margin-bottom: 15px; }
.geek-modal-tip-box {
background: #fff8e1; border: 1px solid #ffe0b2; border-radius: 8px;
padding: 12px; margin: 15px 0; text-align: left;
}
.geek-modal-text { font-size: 14px; line-height: 1.6; color: #333; margin-bottom: 5px; }
.geek-highlight { color: #e67e22; font-weight: bold; }
.geek-footer-tip {
font-size: 12px; color: #999; margin-top: 10px; padding-top: 8px;
border-top: 1px dashed #ffe0b2;
}
.geek-qr-container { margin: 15px 0; position: relative; min-height: 150px; }
.geek-qr-img { width: 200px; height: 200px; border-radius: 8px; }
.geek-error-box {
display: none; border: 1px solid #ffcdd2; background: #ffebee;
color: #c62828; padding: 10px; border-radius: 8px; font-size: 13px;
}
.geek-btn {
display: block; width: 100%; padding: 10px 0; margin-top: 15px;
border: none; border-radius: 6px; cursor: pointer;
font-size: 14px; font-weight: 500; transition: all 0.2s;
}
.geek-btn-primary { background: #2196f3; color: white; }
.geek-btn-primary:hover { background: #1976d2; }
.geek-btn-secondary { background: transparent; color: #999; }
.geek-btn-secondary:hover { color: #666; }
@keyframes geek-fadein {
from { opacity: 0; transform: translateY(-20px); }
to { opacity: 1; transform: translateY(0); }
}
`);
const modal = document.createElement('div');
modal.id = 'geek-modal';
let title = isFirstRun ? '🚀 脚本运行成功' : '加入油猴爱好者群 / 反馈BUG';
let subtitle = isFirstRun ? '
本弹窗仅首次显示,后续不再打扰
' : '';
let tipBoxContent = isFirstRun ? `
🔥 关注公众号【极客脚本库】
回复 “交流群” 加入油猴爱好者群 & 获取更多脚本
` : `
关注公众号【极客脚本库】
回复 “交流群” 获取群号 & 反馈BUG
`;
const btnClass = isFirstRun ? 'geek-btn-secondary' : 'geek-btn-primary';
const btnText = isFirstRun ? '我知道了' : '关闭';
modal.innerHTML = getTrustedHTML(`
${title}
${subtitle}
⚠️ 图片加载失败,请手动搜索关注公众号:极客脚本库,回复“交流群”即可
${tipBoxContent}
`);
document.body.appendChild(modal);
modal.querySelector('#geek-close-btn').onclick = () => modal.remove();
modal.onclick = (e) => { if (e.target === modal) modal.remove(); };
const img = modal.querySelector('.geek-qr-img');
img.onerror = () => { img.style.display = 'none'; modal.querySelector('.geek-error-box').style.display = 'block'; };
}
// --- Main Logic Class ---
class WebTranslator {
constructor() {
this.menuIds = [];
this.settings = {
position: GM_getValue('position', true), // true=left, false=right
autoCheck: GM_getValue('autoCheck', true),
showTip: GM_getValue('showTip', false),
hotkey: GM_getValue('hotkey', [])
};
this.init();
}
init() {
// Exclude check
const currentUrl = window.location.href;
LOGGER.info(`检查当前网址: ${currentUrl}`);
if (CONFIG.excludeSites.some(regex => {
const match = regex.test(currentUrl);
if (match) LOGGER.warn(`当前网站在排除列表中: ${regex}`);
return match;
})) return;
// Language check
LOGGER.info('开始检查语言环境...');
if (!this.shouldEnable()) {
LOGGER.info('当前网页被判定为无需翻译,停止运行');
return;
}
LOGGER.info('环境检查通过,准备启动');
LOGGER.promo();
this.registerMenu();
this.checkFirstRun();
this.injectStyles();
this.injectGoogleTranslate();
this.setupHotkeys();
this.applySiteFixes();
}
shouldEnable() {
const lang = document.documentElement.lang;
const mainLang = document.characterSet.toLowerCase();
const pageTitle = document.title;
LOGGER.info(`页面信息: lang=[${lang}], charset=[${mainLang}], title=[${pageTitle}]`);
const isChinese = (
(lang && lang.startsWith('zh')) ||
(mainLang && mainLang.startsWith('gb')) ||
/[\u4E00-\u9FFF]/.test(pageTitle)
);
if (isChinese) {
LOGGER.info(`判定结果: 中文网站 (Reason: ${lang && lang.startsWith('zh') ? 'lang=zh' : (mainLang && mainLang.startsWith('gb') ? 'charset=gb' : 'title包含中文')})`);
} else {
LOGGER.info('判定结果: 非中文网站,允许翻译');
}
return !isChinese;
}
checkFirstRun() {
if (!GM_getValue('has_run_v1', false)) {
setTimeout(() => {
showContactModal(true);
GM_setValue('has_run_v1', true);
}, 2000);
}
}
registerMenu() {
// Contact & Bug Report (Spec Requirement)
try {
if (typeof GM_registerMenuCommand !== 'undefined') {
GM_registerMenuCommand("💬 反馈BUG & 加入油猴爱好者群", () => showContactModal(false));
}
} catch (e) { LOGGER.warn('菜单注册失败: ' + e); }
// Settings Menu
const menus = [
{
name: '悬浮球',
key: 'position',
value: this.settings.position,
tip: { open: '⬅️ 居左', close: '➡️ 居右' },
callback: () => this.toggleSetting('position', () => this.updateButtonPosition())
},
{
name: '悬停显示原文',
key: 'showTip',
value: this.settings.showTip,
tip: { open: '✅ 开启', close: '⬜ 关闭' },
callback: () => this.toggleSetting('showTip', () => this.updateTipDisplay())
},
{
name: '设置快捷键',
key: 'hotkey',
value: false,
tip: { open: '', close: '' },
callback: () => this.showHotkeySettings()
}
];
// Re-register helper
this.updateMenus = () => {
this.menuIds.forEach(id => GM_unregisterMenuCommand(id));
this.menuIds = [];
// Re-add Contact
GM_registerMenuCommand("💬 反馈BUG & 加入油猴爱好者群", () => showContactModal(false));
menus.forEach(m => {
m.value = this.settings[m.key]; // Update local value ref
const title = `${m.tip ? (m.value ? m.tip.open : m.tip.close) : ''} ${m.name}`;
const id = GM_registerMenuCommand(title, m.callback);
this.menuIds.push(id);
});
};
// Initial register
this.updateMenus();
}
toggleSetting(key, callback) {
this.settings[key] = !this.settings[key];
GM_setValue(key, this.settings[key]);
GM_notification({ text: `已${this.settings[key] ? '开启' : '关闭'} [${key}]`, timeout: 1000 });
if (callback) callback();
this.updateMenus();
}
injectStyles() {
// General Styles
GM_addStyle(`
.geek-trans-btn-container {
position: fixed; bottom: 50px; z-index: 2147483647;
display: flex; align-items: center; gap: 10px;
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
padding: 10px 14px;
border-radius: 50px;
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
border: 1px solid rgba(255,255,255,0.5);
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0.0, 0.2, 1);
user-select: none;
}
.geek-trans-btn-container:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0,0,0,0.25);
background: #fff;
}
.geek-switch-label {
font-size: 16px; font-weight: bold; color: #555;
font-family: system-ui, -apple-system, sans-serif;
}
.geek-switch {
width: 44px; height: 24px;
background: #e0e0e0;
border-radius: 20px;
position: relative;
transition: background 0.3s ease;
box-shadow: inset 0 1px 3px rgba(0,0,0,0.1);
}
.geek-switch-on {
background: #2196f3;
}
.geek-switch-handle {
width: 20px; height: 20px;
background: #fff;
border-radius: 50%;
position: absolute;
top: 2px; left: 2px;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
transition: transform 0.3s cubic-bezier(0.4, 0.0, 0.2, 1);
}
.geek-switch-on .geek-switch-handle {
transform: translateX(20px);
}
/* Hide Google's default bar */
.skiptranslate iframe, .goog-te-banner-frame { display: none !important; }
body { top: 0 !important; }
#google_translate_element { display: none; }
`);
this.updateButtonPosition();
this.updateTipDisplay();
}
updateButtonPosition() {
const isLeft = this.settings.position;
const styleId = 'geek-trans-pos-style';
const oldStyle = document.getElementById(styleId);
if (oldStyle) oldStyle.remove();
const css = `
.geek-trans-btn-container {
${isLeft ? 'left: 20px;' : 'right: 20px;'}
}
`;
const style = document.createElement('style');
style.id = styleId;
style.innerHTML = getTrustedHTML(css);
document.head.appendChild(style);
}
updateTipDisplay() {
const show = this.settings.showTip;
const styleId = 'geek-trans-tip-style';
const oldStyle = document.getElementById(styleId);
if (oldStyle) oldStyle.remove();
let css = '';
if (show) {
// 开启:保留高亮,允许显示 Tooltip (不添加隐藏样式)
css = `
.goog-text-highlight {
background-color: #c9d7f1 !important;
box-shadow: 2px 2px 4px #99a !important;
}
`;
} else {
// 关闭:隐藏 Tooltip 和去除高亮
css = `
#goog-gt-tt, .goog-te-balloon-frame, .goog-tooltip, .goog-tooltip-active {
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
pointer-events: none !important;
}
.goog-text-highlight {
background: none !important;
box-shadow: none !important;
border-bottom: none !important;
}
`;
}
const style = document.createElement('style');
style.id = styleId;
style.innerHTML = getTrustedHTML(css);
document.head.appendChild(style);
}
injectGoogleTranslate() {
LOGGER.info('开始注入 Google 翻译脚本...');
// 强制清除 Google 翻译的 Cookie,防止自动翻译
this.clearTranslateCookie();
// 注入初始化函数到页面环境中 (完全模仿源文件,避免沙箱问题)
const initScriptContent = `
function googleTranslateElementInit() {
new google.translate.TranslateElement({
pageLanguage: 'auto',
includedLanguages: 'zh-CN',
layout: /mobile/i.test(navigator.userAgent) ? 0 : 2,
autoDisplay: false
}, 'google_translate_element');
}
`;
const initScript = document.createElement('script');
initScript.innerHTML = getTrustedHTML(initScriptContent);
document.head.appendChild(initScript);
// Container
const div = document.createElement('div');
div.id = 'google_translate_element';
div.style.display = 'none'; // 保持隐藏
document.body.appendChild(div);
// Inject Google Script
const script = document.createElement('script');
script.src = 'https://translate.google.com/translate_a/element.js?cb=googleTranslateElementInit';
script.onload = () => {
LOGGER.info('Google 翻译脚本加载完成 (onload)');
// 脚本加载完成后,我们可以尝试创建按钮
// 注意:这里不需要等待 googleTranslateElementInit 执行,因为按钮点击时才去查找元素
this.createTranslateButtons();
};
script.onerror = () => {
LOGGER.error('Google翻译脚本加载失败,请检查网络或代理设置');
};
document.head.appendChild(script);
}
clearTranslateCookie() {
const domain = window.location.hostname;
const domains = domain.split('.');
const cookieName = 'googtrans';
// 清除当前域
document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=${domain}`;
// 清除父域
while (domains.length > 1) {
const d = domains.join('.');
document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=.${d}`;
domains.shift();
}
}
createTranslateButtons() {
// LOGGER.info('正在创建翻译按钮 UI...');
const oldContainer = document.querySelector('.geek-trans-btn-container');
if (oldContainer) oldContainer.remove();
const container = document.createElement('div');
container.className = 'geek-trans-btn-container notranslate';
const label = document.createElement('span');
label.className = 'geek-switch-label';
label.textContent = '中文';
const switchBg = document.createElement('div');
switchBg.className = 'geek-switch';
const handle = document.createElement('div');
handle.className = 'geek-switch-handle';
switchBg.appendChild(handle);
container.appendChild(label);
container.appendChild(switchBg);
// Initial State Check
if (document.documentElement.classList.contains('translated-ltr')) {
switchBg.classList.add('geek-switch-on');
}
this.switchBg = switchBg; // Store ref
container.onclick = () => {
const isOn = switchBg.classList.contains('geek-switch-on');
if (isOn) {
switchBg.classList.remove('geek-switch-on');
this.doRecover();
} else {
switchBg.classList.add('geek-switch-on');
this.doTranslate();
}
};
document.body.appendChild(container);
}
doTranslate() {
LOGGER.info('尝试执行翻译...');
// 如果已经在处理中,避免重复
if (this.isTranslating) return;
this.isTranslating = true;
const maxRetries = 20; // 6 seconds max
let retries = 0;
const attemptTranslate = () => {
LOGGER.info(`查找翻译组件 (尝试 ${retries + 1}/${maxRetries})...`);
// 1. Mobile / Simple Layout (Select Element)
const combo = document.querySelector('.goog-te-combo');
if (combo) {
LOGGER.info('找到翻译下拉框 (.goog-te-combo),正在切换语言...');
combo.value = 'zh-CN';
combo.dispatchEvent(new Event('change'));
combo.dispatchEvent(new Event('input'));
this.isTranslating = false;
return;
}
// 2. PC / Iframe Layout
// 查找可能的 iframe
const frames = [
document.querySelector('.goog-te-menu-frame'),
document.querySelector('iframe[title="语言翻译微件"]'),
document.querySelector('iframe[title="Language Translate Widget"]')
];
let foundIframe = null;
for (let f of frames) {
if (f) {
foundIframe = f;
break;
}
}
if (foundIframe) {
try {
const doc = foundIframe.contentDocument || foundIframe.contentWindow.document;
const links = doc.querySelectorAll('a, span.text, div.text');
let clicked = false;
for (let link of links) {
const text = link.textContent || link.innerText;
if (text.includes('Chinese') || text.includes('中文') || text.includes('简体中文')) {
LOGGER.info('在 iframe 中找到中文选项,正在点击...');
link.click();
clicked = true;
break;
}
}
if (!clicked) {
// Fallback: click the second link (usually Simplified Chinese in sorted lists if detected)
// 源文件逻辑: document.querySelectorAll('table a')[1]
const tableLinks = doc.querySelectorAll('table a');
if (tableLinks.length > 1) {
LOGGER.info('点击 iframe 表格中的第二个链接 (Fallback)...');
tableLinks[1].click();
clicked = true;
}
}
if (clicked) {
this.isTranslating = false;
return;
}
} catch (e) {
LOGGER.warn('访问 iframe 内容出错: ' + e);
}
}
// Retry logic
retries++;
if (retries < maxRetries) {
setTimeout(attemptTranslate, 300);
} else {
LOGGER.error('超时:未找到可用的翻译组件。');
this.isTranslating = false;
GM_notification({ text: '未找到翻译组件,可能是 Google 翻译加载失败', timeout: 3000 });
}
};
attemptTranslate();
}
doRecover() {
// Check for Mobile iframe or PC iframe
const frames = [
document.getElementById(':1.container'),
document.getElementById(':2.container'),
document.querySelector('.goog-te-banner-frame')
];
let found = false;
for (let iframe of frames) {
if (iframe) {
try {
const doc = iframe.contentDocument || iframe.contentWindow.document;
// Mobile often uses :1.restore, PC often :2.restore
// Or general ID containing 'restore'
const btn = doc.getElementById(':1.restore') ||
doc.getElementById(':2.restore') ||
doc.querySelector('[id*="restore"]') ||
doc.querySelector('button'); // Sometimes it's just a button? No, be specific.
if (btn) {
LOGGER.info('找到恢复按钮,正在点击...');
btn.click();
found = true;
return;
}
} catch (e) {
LOGGER.warn('尝试在 iframe 中查找恢复按钮失败: ' + e);
}
}
}
if (!found) {
LOGGER.warn('未找到恢复按钮,尝试备用恢复方法 (iframe 之外)...');
// 某些情况下,restore 按钮可能在主页面(极少见)
// 或者我们可以尝试触发 google 的关闭事件
// 再次尝试清空 cookie 并刷新页面? 不,这太激进了。
// 提示用户手动刷新
GM_notification({ text: '恢复失败,请刷新页面', timeout: 2000 });
}
}
setupHotkeys() {
document.addEventListener('keydown', (e) => {
const savedKeys = this.settings.hotkey;
if (!savedKeys || savedKeys.length === 0) return;
// 避免在输入框中触发
if (['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName) || document.activeElement.isContentEditable) return;
const pressed = [];
if (e.ctrlKey) pressed.push('Ctrl');
if (e.altKey) pressed.push('Alt');
if (e.shiftKey) pressed.push('Shift');
if (e.metaKey) pressed.push('Meta');
if (!['Control', 'Alt', 'Shift', 'Meta'].includes(e.key)) {
pressed.push(e.key.toUpperCase());
}
// 比较按键组合
if (pressed.length > 0 && pressed.join('+') === savedKeys.join('+')) {
e.preventDefault(); // 阻止默认行为
// 判断当前状态
const isTranslated = document.documentElement.classList.contains('translated-ltr');
if (isTranslated) {
this.doRecover();
if (this.switchBg) this.switchBg.classList.remove('geek-switch-on');
} else {
this.doTranslate();
if (this.switchBg) this.switchBg.classList.add('geek-switch-on');
}
}
});
}
showHotkeySettings() {
const oldModal = document.getElementById('geek-hotkey-modal');
if (oldModal) oldModal.remove();
GM_addStyle(`
#geek-hotkey-modal {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0, 0, 0, 0.5); z-index: 2147483647;
display: flex; justify-content: center; align-items: center;
font-family: system-ui, -apple-system, sans-serif;
}
.geek-hotkey-display {
background: #f8f9fa; border: 2px dashed #ccc; border-radius: 8px;
padding: 20px; margin: 20px 0; min-height: 60px;
display: flex; align-items: center; justify-content: center;
font-size: 24px; font-weight: bold; color: #555;
transition: all 0.2s;
}
.geek-hotkey-display.recording {
border-color: #2196f3; background: #e3f2fd; color: #1976d2;
}
.geek-btn-group { display: flex; gap: 10px; justify-content: center; margin-top: 10px; }
`);
const modal = document.createElement('div');
modal.id = 'geek-hotkey-modal';
let currentKeys = [...this.settings.hotkey];
const displayKeys = currentKeys.length ? currentKeys.join(' + ') : '点击此处按下快捷键';
modal.innerHTML = getTrustedHTML(`
⌨️ 设置快捷键
请直接在键盘上按下您想设置的组合键
${displayKeys}
`);
document.body.appendChild(modal);
const display = modal.querySelector('#geek-hotkey-display');
let isRecording = false;
// 激活录制状态
display.onclick = () => {
isRecording = true;
display.classList.add('recording');
display.textContent = '请按键...';
};
// 监听按键
const keyHandler = (e) => {
if (!isRecording && document.activeElement !== display) {
// 如果没点击录制,且当前不是在录制中,直接监听全局可能不太好,这里建议强制点击后录制
// 或者直接监听整个modal的keydown
return;
}
// 只要modal存在,就拦截按键
e.preventDefault();
e.stopPropagation();
const keys = [];
if (e.ctrlKey) keys.push('Ctrl');
if (e.altKey) keys.push('Alt');
if (e.shiftKey) keys.push('Shift');
if (e.metaKey) keys.push('Meta');
if (!['Control', 'Alt', 'Shift', 'Meta'].includes(e.key)) {
keys.push(e.key.toUpperCase());
}
if (keys.length > 0) {
currentKeys = keys;
display.textContent = keys.join(' + ');
// 录制完成一个组合后,可以暂时保持录制状态或者结束
// 这里保持录制,直到用户点击保存
}
};
// 绑定到 document,因为 div 无法直接 focus 接收 keydown (除非 tabIndex)
// 为了体验,点击 display 后,我们设置一个标志位,拦截 document 的 keydown
document.addEventListener('keydown', (e) => {
if (document.getElementById('geek-hotkey-modal')) {
keyHandler(e);
}
});
// 保存
modal.querySelector('#geek-hotkey-save').onclick = () => {
this.settings.hotkey = currentKeys;
GM_setValue('hotkey', currentKeys);
GM_notification({ text: `快捷键已保存: ${currentKeys.join(' + ')}`, timeout: 2000 });
modal.remove();
this.updateMenus();
};
// 清除
modal.querySelector('#geek-hotkey-clear').onclick = () => {
currentKeys = [];
display.textContent = '未设置';
display.classList.remove('recording');
};
// 取消
modal.querySelector('#geek-hotkey-cancel').onclick = () => {
modal.remove();
};
// 自动聚焦录制
display.click();
}
applySiteFixes() {
CONFIG.specialSiteFixes.forEach(fix => {
if (window.location.hostname.includes(fix.domain)) {
GM_addStyle(fix.style);
}
});
// Prevent translate on code blocks
CONFIG.noTranslateSelectors.forEach(sel => {
document.querySelectorAll(sel).forEach(el => el.classList.add('notranslate'));
});
}
}
// Run
new WebTranslator();
})();