为网站增加全站即时翻译功能
本文是《技术相关(共60篇)》目录的第 60 篇。阅读本文前,建议先阅读本文前3篇文章:
前2天在群里,石塘义工队兄弟搞了个全站翻译的功能,核心是引用了translate.js来实现即时翻译,官网是这里。
我感觉这个功能很有用,就查看了他的源代码,以及参考官网的说明,给我的网站也做了js配置,但是始终页面显示不了语言选择框。后来,在群里我看到其它博友也反馈部署以后显示不了,但是我创建一个DEMO文件,只把官方的DEMO代码放进去的时候可以显示,说明官网的方法和部分主题CSS不兼容,可能是主题的CSS优先级较高,覆盖了这个功能。
把问题交给元宝AI,官方的文件肯定是修改不了的,只有修改调用方式了,同时为了好看便利,我仿照了一下360安全卫士等桌面软件悬浮球及随意拖动吸附模式,经过几个小时的调试,居然成功了。原则上支持wordpress\typecho所有PHP程序,支持苹果安卓浏览器和电脑手机平板样式。
一共2个文件,floating-translator.js和translate.js,其中floating-translator.js是引用文件,它代码中引用translate.js,我们只需要把两个上传,引用floating-translator.js就行。floating-translator.js里面的代码,把translate.js路径改为实际地址。
floating-translator.js全部代码如下:
(function() {
const CONFIG = {
ballSize: 50,
snappedSize: 25,
edgeThreshold: 20,
panelOffset: 60,
translateDelay: 500, // 延长初始化延迟确保DOM加载
touchHoldDelay: 150, // 优化触摸延迟
colors: {
primary: '#4a6cf7',
error: '#ff4444'
}
};
class FloatingTranslator {
constructor() {
this.floatBall = null;
this.ball = null;
this.panel = null;
this.isDragging = false;
this.touchTimer = null;
this.initElements();
}
initElements() {
this.floatBall = document.createElement('div');
this.floatBall.id = 'translate-float-ball';
this.floatBall.className = 'translate-ball';
this.floatBall.innerHTML = `
<div class="ball">⏳</div>
<div class="language-panel" style="display:none"></div>
`;
this.ball = this.floatBall.querySelector('.ball');
this.panel = this.floatBall.querySelector('.language-panel');
document.body.appendChild(this.floatBall);
}
initTranslation() {
try {
// 隐藏默认工具栏但保留DOM结构
const container = document.querySelector('.translate-container');
if (container) {
container.style.cssText = `
display: none !important;
position: absolute !important;
width: 0 !important;
height: 0 !important;
overflow: hidden !important;
`;
}
translate.setAutoDiscriminateLocalLanguage('chinese_simplified');
translate.service.use('client.edge');
translate.listener.start();
translate.execute();
this.ball.textContent = '🌐';
// 强化语言选择器移植
setTimeout(() => {
const translateDiv = document.getElementById('translate');
if (translateDiv) {
translateDiv.style.cssText = `
display: block !important;
position: static !important;
width: 100% !important;
opacity: 1 !important;
border: none !important;
background: transparent !important;
`;
this.panel.appendChild(translateDiv);
this.panel.style.display = 'none'; // 保持初始隐藏
}
}, CONFIG.translateDelay);
} catch (e) {
this.showError('翻译初始化失败');
}
}
setupInteractions() {
// 桌面端事件
this.ball.addEventListener('mousedown', this.handleStart.bind(this));
document.addEventListener('mousemove', this.handleMove.bind(this));
document.addEventListener('mouseup', this.handleEnd.bind(this));
// 移动端事件 (重点修复触摸问题)
this.ball.addEventListener('touchstart', (e) => {
this.touchTimer = setTimeout(() => {
this.handleStart(e);
}, CONFIG.touchHoldDelay);
}, { passive: false });
this.ball.addEventListener('touchend', (e) => {
clearTimeout(this.touchTimer);
if (!this.isDragging) {
this.togglePanel();
e.preventDefault();
}
this.handleEnd();
});
document.addEventListener('touchmove', (e) => {
if (this.isDragging) {
this.handleMove(e);
e.preventDefault();
}
}, { passive: false });
// 点击事件统一处理
this.ball.addEventListener('click', (e) => {
if (!this.isDragging) {
this.togglePanel();
e.stopPropagation();
}
});
// 点击外部关闭
document.addEventListener('click', (e) => {
if (!this.panel.contains(e.target) && !this.ball.contains(e.target)) {
this.hidePanel();
}
});
window.addEventListener('resize', () => this.checkEdgeSnap());
}
togglePanel() {
this.panel.style.display = this.panel.style.display === 'none' ? 'block' : 'none';
}
hidePanel() {
this.panel.style.display = 'none';
}
handleStart(e) {
this.isDragging = true;
this.hidePanel();
this.floatBall.style.transition = 'none';
const clientX = e.type.includes('touch') ? e.touches[0].clientX : e.clientX;
const clientY = e.type.includes('touch') ? e.touches[0].clientY : e.clientY;
this.startPos = {
x: clientX - this.floatBall.offsetLeft,
y: clientY - this.floatBall.offsetTop
};
this.floatBall.classList.remove('edge-left', 'edge-right');
if (e.cancelable) e.preventDefault();
}
handleMove(e) {
if (!this.isDragging) return;
const clientX = e.type.includes('touch') ? e.touches[0].clientX : e.clientX;
const clientY = e.type.includes('touch') ? e.touches[0].clientY : e.clientY;
requestAnimationFrame(() => {
this.floatBall.style.left = (clientX - this.startPos.x) + 'px';
this.floatBall.style.top = (clientY - this.startPos.y) + 'px';
this.floatBall.style.right = 'auto';
});
if (e.cancelable) e.preventDefault();
}
handleEnd() {
if (this.isDragging) {
this.isDragging = false;
this.floatBall.style.transition = 'all 0.3s';
this.checkEdgeSnap();
}
}
checkEdgeSnap() {
const rect = this.floatBall.getBoundingClientRect();
const viewportWidth = window.innerWidth;
this.floatBall.classList.remove('edge-left', 'edge-right');
if (rect.left <= CONFIG.edgeThreshold) {
this.floatBall.classList.add('edge-left');
this.floatBall.style.left = '0';
}
else if (rect.right >= viewportWidth - CONFIG.edgeThreshold) {
this.floatBall.classList.add('edge-right');
this.floatBall.style.right = '0';
this.floatBall.style.left = 'auto';
}
}
showError(message) {
this.ball.textContent = '❌';
const errorBall = document.createElement('div');
errorBall.className = 'error-balloon';
errorBall.textContent = message;
document.body.appendChild(errorBall);
setTimeout(() => errorBall.remove(), 3000);
}
}
function init() {
const script = document.createElement('script');
script.src = '修改这里填入translate.js的实际网址';
const translator = new FloatingTranslator();
script.onload = () => {
translator.initTranslation();
translator.setupInteractions();
};
script.onerror = () => translator.showError('翻译加载失败');
document.head.appendChild(script);
injectStyles();
}
function injectStyles() {
const style = document.createElement('style');
style.textContent = `
:root {
--ball-size: ${CONFIG.ballSize}px;
--snapped-size: ${CONFIG.snappedSize}px;
--edge-threshold: ${CONFIG.edgeThreshold}px;
--panel-offset: ${CONFIG.panelOffset}px;
--primary-color: ${CONFIG.colors.primary};
--error-color: ${CONFIG.colors.error};
}
.translate-ball {
position: fixed;
z-index: 99999;
right: 20px;
top: 50%;
transform: translateY(-50%);
width: var(--ball-size);
height: var(--ball-size);
cursor: move;
touch-action: none;
-webkit-tap-highlight-color: transparent;
}
.translate-ball .ball {
width: 100%;
height: 100%;
background: var(--primary-color);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 24px;
transition: all 0.3s;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
user-select: none;
}
/* 边缘吸附效果 */
.translate-ball.edge-left {
left: 0 !important;
right: auto !important;
}
.translate-ball.edge-left .ball {
border-radius: 0 50% 50% 0;
width: var(--snapped-size);
}
.translate-ball.edge-right {
right: 0 !important;
left: auto !important;
}
.translate-ball.edge-right .ball {
border-radius: 50% 0 0 50%;
width: var(--snapped-size);
}
/* 语言面板修复 */
.translate-ball .language-panel {
position: absolute;
top: 50%;
transform: translateY(-50%);
background: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
padding: 12px;
z-index: 99998;
min-width: 160px;
}
.translate-ball:not(.edge-left) .language-panel {
right: var(--panel-offset);
}
.translate-ball.edge-left .language-panel {
left: var(--panel-offset);
}
/* 语言选择器强化样式 */
#translate {
display: block !important;
position: static !important;
width: 100% !important;
opacity: 1 !important;
border: none !important;
background: transparent !important;
}
.translateSelectLanguage {
display: block !important;
width: 100% !important;
min-width: 140px !important;
padding: 10px !important;
font-size: 15px !important;
border: 1px solid #eee !important;
border-radius: 4px !important;
}
/* 移动端专属优化 */
@media (hover: none) {
.translate-ball .ball {
font-size: 28px;
}
.translateSelectLanguage {
font-size: 16px !important;
padding: 12px !important;
}
}
.error-balloon {
position: fixed;
right: 20px;
top: 50%;
transform: translateY(-50%);
background: var(--error-color);
color: white;
padding: 12px 18px;
border-radius: 25px;
z-index: 99999;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
animation: fadeIn 0.3s;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-50%) scale(0.9); }
to { opacity: 1; transform: translateY(-50%) scale(1); }
}
`;
document.head.appendChild(style);
}
// 确保DOM完全加载后初始化
if (document.readyState === 'complete') {
init();
} else {
document.addEventListener('DOMContentLoaded', init);
}
})();
下面是translate.js的压缩包,解压上传取得具体网址后,修改上面代码中的js网址。
这样,在网站右侧就会出现一个地球的图标,可以随意拖动,左右吸附,以及即时翻译全站。
彬红茶
这个有个缺点,JavaScript实时更新时没法翻译,内容多的话有一点点延迟,不过也是目前挺好的方案
似水流年
它毕竟是机器翻译,需要翻译的时间。
彬红茶
这个还是开源的,可以自建API,拿个100核100g的服务器自建翻译系统,直接秒出
但是JavaScript实时更新内容还是没法翻译的😀
似水流年
这太夸张了。😂
obaby
这功能高级,问题是,我直接屏蔽老外,哈哈哈
似水流年
这不行啊,得走国际化。🤭
李的日志
大家都还没有来,那首评就是我的了哈哈哈。支持一下好功能
似水流年
首评必须顶!🤭