Greasy Fork is available in English.
支持通过菜单配置中文字体大小
当前为
// ==UserScript==
// @name YouTube 中英双语字幕
// @version 3.2
// @author 4Aiur
// @namespace http://greasyfork.icu/zh-CN/users/394849-4aiur
// @description 支持通过菜单配置中文字体大小
// @match *://www.youtube.com/*
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @grant GM_openInTab
// @run-at document-start
// @license MIT
// ==/UserScript==
(function() {
'use strict';
const TARGET_LANG = 'zh-CN';
const translationCache = new Map();
const timers = new Map();
// -------------------- 0. Trusted Types 兼容处理 --------------------
let ttPolicy;
if (window.trustedTypes && window.trustedTypes.createPolicy) {
ttPolicy = window.trustedTypes.createPolicy('youtube-dual-subtitles-policy', {
createHTML: (string) => string
});
}
function setSafeContent(el, content) {
if (!el) return;
el.innerText = content;
}
// -------------------- 1. 配置管理 & 菜单 --------------------
let fontSize = GM_getValue('subtitle_font_size', 1.5); // 默认 1.5 比较适中
function registerMenu() {
GM_registerMenuCommand(`⚙️ 设置中文字体大小 (当前: ${fontSize}em)`, () => {
let newSize = prompt("请输入字体大小 (建议 1.1 - 2.0):", fontSize);
if (newSize !== null) {
newSize = parseFloat(newSize);
if (!isNaN(newSize) && newSize > 0) {
fontSize = newSize;
GM_setValue('subtitle_font_size', newSize);
updateStyle();
location.reload();
}
}
});
GM_registerMenuCommand('💬 反馈 & 建议 (联系作者)', () => {
GM_openInTab('http://greasyfork.icu/zh-CN/scripts/567512-youtube-%E4%B8%AD%E8%8B%B1%E5%8F%8C%E8%AF%AD%E5%AD%97%E5%B9%95/feedback', { active: true });
});
}
// -------------------- 2. 注入 CSS (优化布局逻辑) --------------------
function updateStyle() {
const oldStyle = document.getElementById('my-subtitle-style');
if (oldStyle) oldStyle.remove();
const cssText = `
/* 强制容器垂直排列 */
.ytp-caption-window-container .caption-visual-line {
display: flex !important;
flex-direction: column !important;
align-items: center !important;
justify-content: flex-end !important;
background: transparent !important; /* 去掉容器背景防止遮挡 */
}
/* 英文字幕样式微调 */
.ytp-caption-segment {
display: inline-block !important;
white-space: pre-wrap !important;
}
/* 中文字幕样式 (显示在上方) */
.my-trans-line {
display: block !important; /* 强制独占一行 */
color: #FFCC00 !important;
font-size: ${fontSize}em !important;
font-weight: bold !important;
line-height: 1.2 !important;
margin-top: 4px !important; /* 在中英文之间留出空隙 */
padding: 2px 8px !important;
background: rgba(0, 0, 0, 0.7) !important;
border-radius: 4px !important;
text-shadow: 2px 2px 3px rgba(0,0,0,1) !important;
text-align: center !important;
order: -1 !important; /* 确保在 flex 容器中排在最前面(最上方) */
}
`;
const style = document.createElement('style');
style.id = 'my-subtitle-style';
if (ttPolicy && style.hasOwnProperty('innerHTML')) {
style.innerHTML = ttPolicy.createHTML(cssText);
} else {
style.textContent = cssText;
}
document.head.appendChild(style);
}
// -------------------- 3. 翻译引擎 --------------------
async function translate(text) {
if (!text || text.length < 2) return null;
if (translationCache.has(text)) return translationCache.get(text);
try {
const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=${TARGET_LANG}&dt=t&q=${encodeURIComponent(text)}`;
const res = await fetch(url);
const data = await res.json();
if (data && data[0]) {
const result = data[0].map(x => x[0]).join('');
translationCache.set(text, result);
return result;
}
} catch (e) {
console.error('Translation Error:', e);
}
return null;
}
// -------------------- 4. 处理逻辑 --------------------
function processLine(line) {
const segments = line.querySelectorAll('.ytp-caption-segment');
if (!segments.length) return;
const fullText = Array.from(segments)
.map(s => s.innerText)
.join(' ')
.replace(/\s+/g, ' ')
.trim();
if (!fullText) return;
let transDiv = line.querySelector('.my-trans-line');
// 如果已有翻译且文本未变,直接确保位置正确
if (translationCache.has(fullText)) {
const cachedText = translationCache.get(fullText);
if (!transDiv) {
transDiv = document.createElement('div');
transDiv.className = 'my-trans-line';
line.prepend(transDiv); // 使用 prepend 插入到最上方
}
if (transDiv.innerText !== cachedText) setSafeContent(transDiv, cachedText);
return;
}
// 防抖处理
clearTimeout(timers.get(line));
const timer = setTimeout(async () => {
if (!line.isConnected) return;
const result = await translate(fullText);
if (result && line.isConnected) {
let currentTransDiv = line.querySelector('.my-trans-line');
if (!currentTransDiv) {
currentTransDiv = document.createElement('div');
currentTransDiv.className = 'my-trans-line';
line.prepend(currentTransDiv); // 翻译后也插入到最上方
}
setSafeContent(currentTransDiv, result);
}
}, 200);
timers.set(line, timer);
}
// -------------------- 5. 初始化 --------------------
const observer = new MutationObserver(() => {
const lines = document.querySelectorAll('.caption-visual-line');
lines.forEach(processLine);
});
function init() {
const container = document.querySelector('.ytp-caption-window-container');
if (container) {
observer.observe(container, { childList: true, subtree: true });
} else {
setTimeout(init, 1000);
}
}
registerMenu();
updateStyle();
init();
window.addEventListener('yt-navigate-finish', () => {
timers.clear();
init();
});
})();