miraku 最近的时间轴更新
miraku

miraku

V2EX 第 383600 号会员,加入于 2019-02-13 19:24:50 +08:00
今日活跃度排名 54
miraku 最近回复了

整页翻译有点慢
```javascript
// ==UserScript==
// @name 网页翻译 + 划词翻译气泡( Translator API )
// @namespace http://tampermonkey.net/
// @version 0.3
// @description 使用浏览器内置 Translator API 翻译网页或选中文本,并缓存语言设置(默认 英→中)
// @author mirakyux
// @match *://*/*
// @grant none
// @run-at document_idle
// ==/UserScript==

(async function() {
'use strict';

// ======== 配置与缓存 ========
const cacheKey = 'translator_langs';
const saved = JSON.parse(localStorage.getItem(cacheKey) || '{}');
let sourceLang = saved.sourceLang || 'en';
let targetLang = saved.targetLang || 'zh';

function saveLang() {
localStorage.setItem(cacheKey, JSON.stringify({ sourceLang, targetLang }));
}

// ======== 样式 ========
function style(el, css) {
Object.assign(el.style, css);
}

// ======== 悬浮按钮(整页) ========
const pageBtn = document.createElement('button');
pageBtn.textContent = '🌐 翻译网页';
style(pageBtn, {
position: 'fixed',
bottom: '20px',
right: '20px',
zIndex: 9999,
padding: '10px 16px',
backgroundColor: '#007bff',
color: '#fff',
border: 'none',
borderRadius: '8px',
cursor: 'pointer',
boxShadow: '0 2px 6px rgba(0,0,0,0.3)',
fontSize: '14px',
opacity: '0.85',
transition: 'opacity 0.3s'
});
pageBtn.onmouseenter = () => (pageBtn.style.opacity = '1');
pageBtn.onmouseleave = () => (pageBtn.style.opacity = '0.85');
document.body.appendChild(pageBtn);

// ======== 检测 Translator API ========
async function isTranslatorAvailable() {
if (!('Translator' in self)) {
console.error('❌ 当前浏览器不支持 Translator API 。请启用 chrome://flags/#translation-api');
return false;
}
try {
const avail = await self.Translator.availability({
sourceLanguage: sourceLang,
targetLanguage: targetLang
});
if (avail === 'unavailable') {
console.error(`❌ 不支持语言对 ${sourceLang} → ${targetLang}`);
return false;
}
console.log(`✅ Translator API 可用 (${avail})`);
return true;
} catch (err) {
console.error('❌ Translator API 检测失败:', err);
return false;
}
}

async function createTranslator() {
return await self.Translator.create({
sourceLanguage: sourceLang,
targetLanguage: targetLang
});
}

// ======== 整页翻译逻辑 ========
async function translatePage() {
const available = await isTranslatorAvailable();
if (!available) {
alert('当前浏览器不支持内置翻译 API ,请查看控制台提示。');
return;
}
pageBtn.disabled = true;
pageBtn.textContent = '翻译中…';
const translator = await createTranslator();

const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, {
acceptNode: (node) => {
if (!node.nodeValue.trim()) return NodeFilter.FILTER_REJECT;
if (node.parentElement && ['SCRIPT', 'STYLE', 'NOSCRIPT'].includes(node.parentElement.tagName))
return NodeFilter.FILTER_REJECT;
return NodeFilter.FILTER_ACCEPT;
}
});

const textNodes = [];
while (walker.nextNode()) textNodes.push(walker.currentNode);

for (const node of textNodes) {
const text = node.nodeValue.trim();
if (!text || text.length > 2000) continue;
try {
const translated = await translator.translate(text);
node.nodeValue = translated;
} catch (err) {
console.warn('跳过节点:', err);
}
}

pageBtn.textContent = '✅ 已翻译';
setTimeout(() => {
pageBtn.textContent = '🌐 翻译网页';
pageBtn.disabled = false;
}, 3000);
}

pageBtn.addEventListener('click', translatePage);

// ======== 划词翻译气泡 ========
const bubble = document.createElement('div');
style(bubble, {
position: 'absolute',
background: '#007bff',
color: '#fff',
padding: '6px 10px',
borderRadius: '6px',
fontSize: '13px',
cursor: 'pointer',
display: 'none',
zIndex: 99999,
userSelect: 'none',
whiteSpace: 'nowrap',
boxShadow: '0 2px 6px rgba(0,0,0,0.3)'
});
bubble.textContent = '翻译';
document.body.appendChild(bubble);

let currentSelection = '';
document.addEventListener('selectionchange', () => {
const sel = window.getSelection();
const text = sel.toString().trim();
if (text.length > 0) {
const range = sel.getRangeAt(0);
const rect = range.getBoundingClientRect();
bubble.style.left = `${rect.right + window.scrollX + 10}px`;
bubble.style.top = `${rect.top + window.scrollY - 10}px`;
bubble.style.display = 'block';
currentSelection = text;
} else {
bubble.style.display = 'none';
}
});

bubble.addEventListener('click', async () => {
if (!currentSelection) return;
const available = await isTranslatorAvailable();
if (!available) return alert('当前浏览器不支持 Translator API');
const translator = await createTranslator();
bubble.textContent = '翻译中…';
try {
const translated = await translator.translate(currentSelection);
alert(` [${sourceLang} → ${targetLang}] \n\n${translated}`);
} catch (e) {
alert('翻译失败:' + e.message);
} finally {
bubble.textContent = '翻译';
bubble.style.display = 'none';
}
});

// ======== 设置按钮 ========
const configBtn = document.createElement('button');
configBtn.textContent = '⚙️ 设置';
style(configBtn, {
position: 'fixed',
bottom: '60px',
right: '20px',
zIndex: 9999,
padding: '8px 12px',
backgroundColor: '#28a745',
color: '#fff',
border: 'none',
borderRadius: '8px',
cursor: 'pointer',
fontSize: '13px',
opacity: '0.85'
});
configBtn.onmouseenter = () => (configBtn.style.opacity = '1');
configBtn.onmouseleave = () => (configBtn.style.opacity = '0.85');
document.body.appendChild(configBtn);

configBtn.addEventListener('click', async () => {
const src = prompt('源语言(如 en, zh, ja ):', sourceLang);
const tgt = prompt('目标语言(如 zh, en, ko ):', targetLang);
if (src && tgt) {
sourceLang = src;
targetLang = tgt;
saveLang();
alert(`语言设置已保存:${sourceLang} → ${targetLang}`);
}
});
})();

```
猜到了哈哈
湖南的我也穿羽绒服了
@mobinf #13 声音有点小, 戴耳机开最大音量在地铁上听不清, 最大音量听歌的话能吵得耳朵疼
@mobinf #13 ok, 我今天试试
额我自营买的数据线, 防拆贴掀起来了, 和客服说了声, 客服秒退款不退货
ajU0MDU0NDUyN0BnbWFpbC5jb20=
@mobinf #8 对的
感觉网页没有适配好移动端
关于   ·   帮助文档   ·   自助推广系统   ·   博客   ·   API   ·   FAQ   ·   Solana   ·   905 人在线   最高记录 6679   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 12ms · UTC 20:41 · PVG 04:41 · LAX 13:41 · JFK 16:41
♥ Do have faith in what you're doing.