New:Socket for Asana Is Now Available.Learn more
Get Started

OpenImmersive Translator

Package Overview
Versions
2
Alerts
File Explorer
Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

yulingling@openimmersive.github.io - firefox Package Compare versions

Comparing version
1.5.0
to
1.6.0
+36
-29
content.css
/* 语灵灵 Content Styles */
/* 翻译段落 */
/* 翻译段落:样式贴原文——字体/颜色由 content.js 从原块计算样式复制,
CSS 只负责节奏(间距/透明度),不再画色条色块,让译文像同一篇文档。 */
.yll-tr {
display: block;
margin: 4px 0 12px 0;
padding: 6px 10px;
color: #1a5276;
font-size: 0.96em;
line-height: 1.65;
border-left: 3px solid #5dade2;
background: rgba(93, 173, 226, 0.06);
border-radius: 0 4px 4px 0;
margin: 0.3em 0 0.75em 0;
opacity: 0.92;
font-family: inherit;
word-wrap: break-word;
transition: background 0.2s;
}
.yll-tr:hover {
background: rgba(93, 173, 226, 0.12);
/* 标题行内接排("Machine translation 机器翻译"式),继承标题自身样式 */
.yll-tr.yll-tr-inline {
display: inline;
margin: 0;
opacity: 0.8;
font: inherit;
color: inherit;
}
/* 深色适配:①系统深色模式 ②按元素实际背景判定的 .yll-tr-dark(网站自带深色主题) */
@media (prefers-color-scheme: dark) {
.yll-tr {
color: #85c1e9;
background: rgba(93, 173, 226, 0.08);
border-left-color: #2e86c1;
}
.yll-tr:hover {
background: rgba(93, 173, 226, 0.14);
}
/* 深色主题下无需变色:颜色直接继承自原文块的计算样式 */
.yll-tr.yll-tr-dark {
opacity: 0.9;
}
.yll-tr.yll-tr-dark {
color: #85c1e9;
background: rgba(93, 173, 226, 0.10);
border-left-color: #2e86c1;
/* 悬浮球:点一下整页翻译/还原 */
#yll-float-ball {
position: fixed;
right: 18px;
bottom: 96px;
width: 40px;
height: 40px;
border-radius: 50%;
background: #0d9488;
color: #ffffff;
font-size: 18px;
line-height: 40px;
text-align: center;
cursor: pointer;
z-index: 2147483646;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.25);
user-select: none;
transition: transform 0.15s, opacity 0.15s;
opacity: 0.85;
}
.yll-tr.yll-tr-dark:hover {
background: rgba(93, 173, 226, 0.16);
#yll-float-ball:hover {
transform: scale(1.1);
opacity: 1;
}

@@ -45,0 +52,0 @@

+118
-11

@@ -244,7 +244,28 @@ 'use strict';

let inputTargetLang = 'en';
// 悬浮球(默认开):点一下整页翻译/还原
let floatBallEnabled = true;
let floatBall = null;
function ensureFloatBall() {
if (!floatBallEnabled || floatBall || !document.body) return;
floatBall = document.createElement('div');
floatBall.id = 'yll-float-ball';
floatBall.setAttribute('data-yll-ui', 'true');
floatBall.textContent = '译';
floatBall.title = 'OpenImmersive:翻译此页(Alt+T)';
floatBall.addEventListener('click', () => {
if (state.isTranslated) removeTranslations();
else translatePage();
});
document.body.appendChild(floatBall);
}
function removeFloatBall() {
if (floatBall) { floatBall.remove(); floatBall = null; }
}
// 状态与设置同步:hover/输入框走 state.engine/targetLang,先从存储初始化,再随改动更新。
// 注意:必须显式列出所有键——getSettings() 只取 targetLang/engine,读不到行为开关。
storageGet(['targetLang', 'engine', 'hoverTranslate', 'hoverKey',
'selectionTranslate', 'inputTranslate', 'inputTargetLang', 'autoTranslateSites']).then(s => {
'selectionTranslate', 'inputTranslate', 'inputTargetLang', 'autoTranslateSites', 'floatBall']).then(s => {
if (s.engine) state.engine = s.engine;

@@ -257,2 +278,4 @@ if (s.targetLang) state.targetLang = s.targetLang;

if (typeof s.inputTargetLang === 'string') inputTargetLang = s.inputTargetLang;
if (s.floatBall === false) floatBallEnabled = false;
ensureFloatBall();
// 自动翻译此网站:命中名单则整页翻译(等动态内容稍稳后触发)

@@ -275,2 +298,6 @@ if (Array.isArray(s.autoTranslateSites) && s.autoTranslateSites.includes(location.hostname)) {

if (changes.inputTargetLang) inputTargetLang = changes.inputTargetLang.newValue || inputTargetLang;
if (changes.floatBall) {
floatBallEnabled = changes.floatBall.newValue !== false;
if (floatBallEnabled) ensureFloatBall(); else removeFloatBall();
}
});

@@ -339,2 +366,9 @@ }

// 视口边界(带余量)内的块优先翻译,感知速度对齐"滚到哪翻到哪"类插件;
// 视口外的块仍全部排队后台补全——整页完整性是我们相对的差异点,不牺牲。
function inViewport(el, margin = 600) {
const r = el.getBoundingClientRect();
return r.bottom > -margin && r.top < window.innerHeight + margin;
}
async function translatePage() {

@@ -356,16 +390,44 @@ if (state.isTranslating) return;

if (blocks.length === 0) {
state.isTranslating = false;
hideProgressBar();
return;
}
// 并发翻译,限制3个并发
const CONCURRENCY = 3;
for (let i = 0; i < blocks.length; i += CONCURRENCY) {
await Promise.allSettled(
blocks.slice(i, i + CONCURRENCY).map(el => translateBlock(el))
);
updateProgressBar();
// 两级队列:prioritized=视口内(先翻),rest=其余(按文档序垫底);
// IntersectionObserver 把滚动临近的块从 rest 提升进 prioritized。
const prioritized = [];
const rest = [];
const waiting = new Set();
for (const b of blocks) {
if (inViewport(b)) prioritized.push(b);
else { rest.push(b); waiting.add(b); }
}
let observer = null;
if (typeof IntersectionObserver === 'function' && waiting.size) {
observer = new IntersectionObserver((entries) => {
for (const en of entries) {
if (en.isIntersecting && waiting.has(en.target)) {
waiting.delete(en.target);
prioritized.push(en.target);
observer.unobserve(en.target);
}
}
}, { rootMargin: '600px' });
for (const b of rest) observer.observe(b);
}
const CONCURRENCY = 4;
const workers = Array.from({ length: CONCURRENCY }, async () => {
for (;;) {
let el = prioritized.shift();
if (!el) {
do { el = rest.shift(); } while (el && !waiting.has(el));
if (!el) { if (prioritized.length) continue; break; }
waiting.delete(el);
}
await translateBlock(el);
updateProgressBar();
}
});
await Promise.allSettled(workers);
if (observer) observer.disconnect();
state.isTranslated = true;

@@ -422,3 +484,3 @@ } finally {

const text = extractText(el);
if (!text || text.trim().length < 15) continue;
if (!text || text.trim().length < 12) continue;

@@ -429,2 +491,22 @@ seen.add(el);

// 兜底:语义选择器几乎没命中时(极简/非语义化页面,如纯 div 文案的站点),
// 补捞"叶子块"——不含其它块级候选后代、但直接文本足够长的容器。
let coveredLen = 0;
for (const el of results) coveredLen += extractText(el).trim().length;
const bodyLen = ((document.body && (document.body.innerText || document.body.textContent)) || '').replace(/\s+/g, ' ').trim().length;
if (bodyLen >= 40 && coveredLen / bodyLen < 0.5) {
for (const el of document.querySelectorAll('div, section, article, span')) {
if (seen.has(el)) continue;
if (!shouldTranslate(el)) continue;
if (el.querySelector('p,div,section,article,ul,ol,table,h1,h2,h3,h4,h5,h6,li,blockquote,td')) continue;
const text = extractText(el);
if (!text || text.trim().length < 12) continue;
let anc = el.parentElement, nested = false;
while (anc) { if (seen.has(anc)) { nested = true; break; } anc = anc.parentElement; }
if (nested) continue;
seen.add(el);
results.push(el);
}
}
return results;

@@ -489,5 +571,19 @@ }

// 标题走行内接排("Machine translation 机器翻译"式),正文块另起一行;
// 两者都直接继承原文的计算样式——译文和原文看起来是同一篇文档。
const INLINE_APPEND_TAGS = new Set(['H1', 'H2', 'H3', 'H4', 'H5', 'H6']);
function injectTranslation(el, translation) {
el.dataset.yllDone = '1';
if (INLINE_APPEND_TAGS.has(el.tagName)) {
const span = document.createElement('span');
span.className = 'yll-tr yll-tr-inline';
span.setAttribute('data-yll-inject', '1');
span.setAttribute('data-yll-ui', 'true');
span.textContent = ' ' + translation;
el.appendChild(span);
return;
}
const div = document.createElement('div');

@@ -499,2 +595,13 @@ div.className = 'yll-tr';

div.setAttribute('data-yll-ui', 'true');
// 样式贴原文:字体/字号/行高/颜色/对齐全部取自原块的计算样式
try {
const cs = getComputedStyle(el);
div.style.fontSize = cs.fontSize;
div.style.fontFamily = cs.fontFamily;
div.style.fontWeight = cs.fontWeight;
div.style.fontStyle = cs.fontStyle;
div.style.lineHeight = cs.lineHeight;
div.style.color = cs.color;
div.style.textAlign = cs.textAlign;
} catch (_) { /* jsdom 等环境下取不到就走 CSS 默认 */ }
div.textContent = translation;

@@ -501,0 +608,0 @@

{
"manifest_version": 3,
"name": "OpenImmersive Translator",
"version": "1.5.0",
"version": "1.6.0",
"description": "\u5f00\u6e90\u3001\u9690\u79c1\u4f18\u5148\u7684\u53cc\u8bed\u7ffb\u8bd1\u63d2\u4ef6\uff0c\u88c5\u5b8c\u5373\u7528\u3002\u6574\u9875\u53cc\u8bed\u5bf9\u7167\u3001\u6309\u4f4f Alt \u60ac\u505c\u7ffb\u8bd1\u5355\u6bb5\u3001\u9009\u4e2d\u5212\u8bcd\u7ffb\u8bd1\u3001\u8f93\u5165\u6846\u5185 Alt+Enter \u628a\u4f60\u5199\u7684\u5185\u5bb9\u8bd1\u6210\u5916\u8bed\u3001\u53f3\u952e\u83dc\u5355\u4e00\u952e\u7ffb\u8bd1\u3001\u53ef\u8bbe\u300c\u603b\u662f\u7ffb\u8bd1\u6b64\u7f51\u7ad9\u300d\u81ea\u52a8\u53cc\u8bed\u3002\u591a\u5f15\u64ce\uff08\u514d\u8d39 Google \u9ed8\u8ba4 / \u81ea\u5e26 DeepL\u00b7OpenAI \u7b49 Key\uff09\u3002\u6240\u6709\u7ffb\u8bd1\u8bf7\u6c42\u76f4\u63a5\u4ece\u6d4f\u89c8\u5668\u53d1\u5f80\u6240\u9009\u5f15\u64ce\uff0c\u65e0\u4e2d\u95f4\u670d\u52a1\u5668\u3001\u96f6\u6570\u636e\u6536\u96c6\u3002\u53ef\u9009\u654f\u611f\u4fe1\u606f\u8131\u654f\uff1a\u90ae\u7bb1/\u7535\u8bdd/\u5361\u53f7/API Key \u672c\u5730\u6253\u7801\u540e\u624d\u53d1\u51fa\uff0c\u7ffb\u8bd1\u540e\u81ea\u52a8\u8fd8\u539f\u3002",

@@ -6,0 +6,0 @@ "author": "OpenImmersive",

@@ -148,2 +148,6 @@ <!DOCTYPE html>

</div>
<div class="row">
<span class="row-label">页面悬浮球</span>
<label class="switch"><input type="checkbox" id="swFloatBall" /><span class="slider"></span></label>
</div>
</div>

@@ -150,0 +154,0 @@

@@ -9,2 +9,3 @@ 'use strict';

const swAutoSite = document.getElementById('swAutoSite');
const swFloatBall = document.getElementById('swFloatBall');
const statusDot = document.getElementById('statusDot');

@@ -20,6 +21,7 @@ const statusText = document.getElementById('statusText');

async function init() {
const data = await getStorage(['targetLang', 'engine', 'hoverTranslate', 'autoTranslateSites']);
const data = await getStorage(['targetLang', 'engine', 'hoverTranslate', 'autoTranslateSites', 'floatBall']);
selLang.value = data.targetLang || 'zh-CN';
engine = data.engine || 'google';
swHover.checked = data.hoverTranslate !== false; // 默认开
swFloatBall.checked = data.floatBall !== false; // 默认开

@@ -65,2 +67,3 @@ const [tab] = await api.tabs.query({ active: true, currentWindow: true });

swHover.addEventListener('change', () => setStorage({ hoverTranslate: swHover.checked }));
swFloatBall.addEventListener('change', () => setStorage({ floatBall: swFloatBall.checked }));

@@ -67,0 +70,0 @@ // 总是翻译此网站:增删当前 host;勾选后立即翻译当前页

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet