离线场景下任意文档的在线预览及原样格式翻译,不依赖其他厂商接口非侵入式一行js代码实现网站的翻译及国际化,可配置使用多种翻译语言。
要实现翻译需要解决以下3个主要问题:
1)from:内容本身的语言类型是什么?
2)to:需要翻译为目标语言是什么?
3)text:需要翻译的文本内容是什么?
转化为:
1)首先,如何识别文档内容的语言?一篇文章中有多种语言混合的如何识别?
2)其次,用户使用的是什么语言?如何获取目标语言?
3)在文档或者网页中,所有内容都是带有格式的,如何翻译之后进行还原保证样式不丢失?
在网上查了很多资料,也下载了很多 收费 的资料,结果不尽人意。
获取用户的语言:
1)通过浏览器的默认语言判断用户使用的语言:
<script>
// 获取浏览器默认语言
const getBrowserLang = function () {
let browserLang = navigator.language ? navigator.language : navigator.browserLanguage;
let defaultBrowserLang = "";
if (
browserLang.toLowerCase() === "us" ||
browserLang.toLowerCase() === "en" ||
browserLang.toLowerCase() === "en_us"
) {
defaultBrowserLang = "en_US";
} else {
defaultBrowserLang = "zh_CN";
}
return defaultBrowserLang;
};
console.log(getBrowserLang());
</script>
或者:
<script>
var type = navigator.appName; //BOM对象获取浏览器名称
if (type == "Netscape") {
var lang = navigator.language.toLowerCase(); //获取浏览器配置语言,支持非IE浏览器
} else {
var lang = navigator.browserLanguage.toLowerCase(); //获取浏览器配置语言,支持IE5+
}
console.log(lang);
var lang = lang.substr(0, 5); //获取浏览器配置语言前4位
console.log(lang);
</script>
如何通过js在html中动态导入其他的js库:
<script>
var head = document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "http://localhost:8060/static/translate.min.js";
script.onload = script.onreadystatechange = function () {
translate.storage.set("to", "");
//设置使用v2.x 版本
translate.setUseVersion2();
//SELECT 修改 onchange 事件
translate.selectLanguageTag.selectOnChange = function (event) {
//判断是否是第一次翻译,如果是,那就不用刷新页面了。 true则是需要刷新,不是第一次翻译
var isReload = translate.to != null && translate.to.length > 0;
if (isReload) {
//如果要刷新页面的话,弹出友好提示
alert(
"您好,快速体验暂时只能切换其中一种语言进行体验,只是提供效果展示,您可参考接入文档来接入您的项目中进行完整体验及使用。",
);
} else {
var language = event.target.value;
console.log(language);
// translate.changeLanguage(language);
}
};
};
</script>
如何获取文档或者网页的需要翻译的内容?找了一个网页翻译助手实现的js插件代码进行参考,完整代码如下:
// ==UserScript==
// @name 网页翻译助手
// @version 1.3.3
// @namespace https://github.com/zyufstudio/TM/tree/master/webTranslate
// @description 支持划词翻译,输入文本翻译,谷歌整页翻译。可以自行选择谷歌翻译,有道字典翻译和百度翻译。
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAENklEQVRoQ+2ZTVITQRTH/28qyNIJFxCqnGwNJzCcwHACcelkIZxAPAG4IFkaTyCegHgC4jZjVfACzLBEUvOs7jCx57unZ6JSRZZJ9+v3e+/1++gQHviHHrj+eAT41x4s9YA99F5qK3nb+h4c7QTa6xtYmAuwNfLeg3Fc5QxmBGy1doO3O1dV9tVZmwmwdeYdg/DeTDB/vnY7B2Z7q+/KBhh6XF3UcgeDr3y3s2O6v+q+FIB9NutaRJdVBanrQ+bdYNCZ1pGhuzcNMPJ6FuNCV0DWuhA4ClzntI4M3b1rARBhRERjXSWK1oUhfy3y5loAmlBclcHAue86+1lyHwTAfXb4cD1wUmm9EgAzfhLoPLT4fGUNRpeAPgH6Bc/ARcw89Qed3eRWLQAGbhg4LrqY9sjrUYgxEZ7FDmH6GAPWUZ7JJvBh0ijXrpPStxRAKs/c00mLAiKewcyLmn0yt63Nha/yGgGEoP3Afb4KGSEYT+5eWxbZQniUJUT9INAFEeT3RXGr4wSxpj30JqoXKgMw8M13nV50oH02OyDQSUxJWX55zKB+8vu8uP1rAKr17dF8m8LFZUr5Em1Cau2ozZ09/NEn8Dum1puypq+2B1SXbQ1nY4Be61rvTxjxOPy1cRS12e2z2SURdcHITIuq/FoA4vL6rrOK56QwBn/n241eXv/fHs6mBHpRFViEHVsb+8I79QAYgT9w2pECSWFlFlxZuiqBSAz3YVcLQJxbFEJyeGEcwMJyAmO+UVPtltKSJzOZyiTuhAX+woyv/sDpNxZCMkUqKVReYl5MCXiaZdSQ+U0w6MgGLt2S59eD9tC7IKCn7s/zukkanfiusxdLo0SnaYi4gnkTXXLYiQqfLJa3re3kfaodQkkvSOuKQra5OLB4WbBCC5PgrTNZQZ7MbXqymKdqAnAjwZXsU5aRGgGQsQ7e02olpPJ3FzJNJj4ixonwSkIz71qgvpi7RYPoD5ztrLBsBCASHDIfBoPOx7yksmwl8ClLeXnHgW/ENAXxO2GUyENF42ejAEsl+Eq204DSTnOXiHqipS7LmOFtq02bi/OovykbPRsHKFNQ4/dTkXoj68ui9WtjL78Y1mzmNBQyWiKqOETPT3hWBPE/egBRwRLZjDbvJqLdyIP4LwFCwl6UdssgzAAaeNgqjKtEFxqDAGKF0whAHK72MUZBXrApOSRFxXGZnfhKfVc1B6j1uFuGrD8nGwNIL6wBIq/nyUOuBbDqbUbeaiYus230uxWil/U8X9RWZ7cSszmBVm2GVjeqq2TZuqwRtAqAeECwiD6p5/xVgGhQSTd1PAVRyd9QvK1a/r6Xio24kdzS/8jKLF30e3voBXkDUFW5WRObkLFWAOOXjASdvPzU6mY9w6wVYFmk/nSfla0O3IAxYat1mPeGtFaAqgqbrH8EMLFak3sePdCkNU1k/QadtchPhjx3/AAAAABJRU5ErkJggg==
// @author Johnny Li
// @license MIT
// @match *://*/*
// @grant GM_info
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @connect cdn.jsdelivr.net
// @connect cdn.bootcss.com
// @connect translate.google.com.hk
// @connect fanyi.youdao.com
// @connect dict.youdao.com
// @connect fanyi.baidu.com
// @connect shared.ydstatic.com
// @require https://cdn.jsdelivr.net/npm/jquery@2.2.3/dist/jquery.min.js
// @require https://cdn.jsdelivr.net/npm/jquery.md5@1.0.2/index.min.js
// @require https://cdn.jsdelivr.net/gh/zyufstudio/jQuery@3a09ff54b33fc2ae489b5083174698b3fa83f4a7/jPopBox/dist/jPopBox.min.js
// ==/UserScript==
//文件使用Rollup+Gulp编译而成,如需查看源码请转到GitHub项目。
(function () {
'use strict';
/**
* 字符串模板格式化
* @param {string} formatStr - 字符串模板
* @returns {string} 格式化后的字符串
* @example
* StringFormat("ab{0}c{1}ed",1,"q") output "ab1cqed"
*/
function StringFormat(formatStr) {
var args = arguments;
return formatStr.replace(/\{(\d+)\}/g, function (m, i) {
i = parseInt(i);
return args[i + 1];
});
}
/**
* 日期格式化
* @param {Date} date - 日期
* @param {string} formatStr - 格式化模板
* @returns {string} 格式化日期后的字符串
* @example
* DateFormat(new Date(),"yyyy-MM-dd") output "2020-03-23"
* @example
* DateFormat(new Date(),"yyyy/MM/dd hh:mm:ss") output "2020/03/23 10:30:05"
*/
function DateFormat(date, formatStr) {
var o = {
"M+": date.getMonth() + 1, //月份
"d+": date.getDate(), //日
"h+": date.getHours(), //小时
"m+": date.getMinutes(), //分
"s+": date.getSeconds(), //秒
"q+": Math.floor((date.getMonth() + 3) / 3), //季度
"S": date.getMilliseconds() //毫秒
};
if (/(y+)/.test(formatStr)) {
formatStr = formatStr.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
}
for (var k in o) {
if (new RegExp("(" + k + ")").test(formatStr)) {
formatStr = formatStr.replace(
RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
}
}
return formatStr;
}
/**
* 生成Guid
* @param {boolean} hasLine - guid字符串是否包含短横线
* @returns {string} guid
* @example
* Guid(false) output "b72f78a6cb88362c0784cb82afae450b"
* @example
* Guid(true) output "67b25d43-4cfa-3edb-40d7-89961ce7f388"
*/
function Guid(hasLine){
var guid="";
function S4() {
return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
}
if(hasLine){
guid=(S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
}
else {
guid=(S4()+S4()+S4()+S4()+S4()+S4()+S4()+S4());
}
return guid;
}
/**
* 清除dom元素默认事件
* @param {object} e - dom元素
*/
function ClearBubble(e) {
if (e.stopPropagation) {
e.stopPropagation();
} else {
e.cancelBubble = true;
}
if (e.preventDefault) {
e.preventDefault();
} else {
e.returnValue = false;
}
}
function ObjectToQueryString(object){
var querystring=Object.keys(object).map(function(key) {
return encodeURIComponent(key) + '=' + encodeURIComponent(object[key])
}).join('&');
return querystring;
}
/**
* 配置参数
*/
var options={
//默认翻译引擎
defaulttransengine:"yd"
};
/**
* 获取配置参数
*/
function GetSettingOptions(){
var optionsJson=GM_getValue("webtranslate-options")||"";
if(optionsJson!=""){
var optionsData=JSON.parse(optionsJson);
for (var key in options) {
if (options.hasOwnProperty(key) && optionsData.hasOwnProperty(key)) {
options[key]= optionsData[key];
}
}
}
return options;
}
/**
* 设置配置参数
*/
function SetSettingOptions(){
var optionsJson=JSON.stringify(options);
GM_setValue("webtranslate-options", optionsJson);
}
//谷歌翻译
var googleTrans = {
code: "ge",
codeText: "谷歌",
defaultOrigLang: "auto", //默认源语言
defaultTargetLang: "zh-CN", //默认目标语言
langList: {
"auto": "自动检测",
"zh-CN": "中文简体",
"zh-TW": "中文繁体",
"en": "英文",
"ja": "日文",
"ko": "韩文",
"fr": "法文",
"es": "西班牙文",
"pt": "葡萄牙文",
"it": "意大利文",
"ru": "俄文",
"vi": "越南文",
"de": "德文",
"ar": "阿拉伯文",
"id": "印尼文"
},
Execute: function (h_onloadfn) {
GM_xmlhttpRequest({
method: "POST",
url: "https://translate.google.com.hk/_/TranslateWebserverUi/data/batchexecute",
headers: {
"Referer": `https://translate.google.com.hk/`,
"Cache-Control": "max-age=0",
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
},
data: "f.req=" + encodeURIComponent(JSON.stringify([
[
["MkEWBc", JSON.stringify([
[Trans.transText, Trans.transOrigLang, Trans.transTargetLang, true],
[null]
]), null, "generic"]
]
])),
onload: function (r) {
setTimeout(function () {
var resData=r.responseText;
var transData=JSON.parse(JSON.parse(resData.match(/\[{2}.*\]{2}/g)[0])[0][2]);
var transList=transData[1][0][0][5];
var transTexts=[];
for (let index = 0; index < transList.length; index++) {
var transItem = transList[index];
transTexts.push(transItem[0]);
}
Trans.transResult.trans = transTexts;
Trans.transResult.orig = transData[1][4][0].split("\n");
Trans.transResult.origLang = transData[2];
h_onloadfn();
}, 300);
},
onerror: function (e) {
console.error(e);
}
});
},
};
//获取sign
function getSign() {
GM_xmlhttpRequest({
method: "GET",
url: "http://fanyi.youdao.com/",
timeout: 5000,
onload: function (ydRes) {
var fanyijsUrlMatch = /<script\s+type="text\/javascript"\s+src="([http|https]*?:\/\/shared.ydstatic.com\/fanyi\/newweb\/v[\d.]+\/scripts\/newweb\/fanyi.min.js)"><\/script>/g.exec(ydRes.responseText);
if (!fanyijsUrlMatch) {
console.log("获取fanyi.min.js失败!!!");
} else {
var fanyijsUrl = fanyijsUrlMatch[1];
if (typeof fanyijsUrl !== 'undefined') {
GM_xmlhttpRequest({
method: "GET",
url: fanyijsUrl,
timeout: 5000,
onload: function (r) {
var signMatch = /sign:[a-z]{1}\.md5\("fanyideskweb"\+[a-z]{1}\+[a-z]{1}\+"(.*)"\)}};/g.exec(r.responseText);
if (!signMatch) {
console.log("获取sign失败!!!");
} else {
var newSign = signMatch[1];
if (typeof newSign !== 'undefined') {
youdaoTrans.sign = newSign;
}
}
},
onerror: function (e) {
console.error(e);
}
});
}
}
},
onerror: function (e) {
console.error(e);
}
});
}
/**
* 获取有道翻译音标
* @param {String} transText
* @param {Function} callback
*/
function getYDSymbol(transText, callback) {
var url = StringFormat("http://dict.youdao.com/fsearch?client=fanyideskweb&keyfrom=fanyi.web&q={0}&doctype=xml&xmlVersion=3.2&dogVersion=1.0&appVer=3.1.17.4208", encodeURIComponent(transText));
GM_xmlhttpRequest({
method: "GET",
url: url,
timeout: 5000,
onload: function (ydRes) {
var xmlnode=ydRes.responseXML;
var symbol = {
uk:"",
us: ""
};
var root = xmlnode.getElementsByTagName("yodaodict")[0];
if ("" + root.getElementsByTagName("uk-phonetic-symbol")[0] != "undefined" && "" + root.getElementsByTagName("uk-phonetic-symbol")[0].childNodes[0] != "undefined") {
symbol.uk = root.getElementsByTagName("uk-phonetic-symbol")[0].childNodes[0].nodeValue;
}
if ("" + root.getElementsByTagName("us-phonetic-symbol")[0] != "undefined" && "" + root.getElementsByTagName("us-phonetic-symbol")[0].childNodes[0] != "undefined") {
symbol.us = root.getElementsByTagName("us-phonetic-symbol")[0].childNodes[0].nodeValue;
}
callback(symbol);
},
onerror: function (e) {
console.error(e);
}
});
}
//有道翻译
var youdaoTrans = {
code: "yd",
codeText: "有道",
sign: "",
defaultOrigLang: "AUTO", //默认源语言
defaultTargetLang: "ZH-CHS", //默认目标语言
langList: {
"AUTO": "自动检测",
"zh-CHS": "中文",
"en": "英文",
"ja": "日文",
"ko": "韩文",
"fr": "法文",
"es": "西班牙文",
"pt": "葡萄牙文",
"it": "意大利文",
"ru": "俄文",
"vi": "越南文",
"de": "德文",
"ar": "阿拉伯文",
"id": "印尼文"
},
Execute: function (h_onloadfn) {
var h_url = "",
h_headers = {},
h_data = "";
var youdaoTransApi = "http://fanyi.youdao.com/translate_o?client=fanyideskweb&keyfrom=fanyi.web&smartresult=dict&version=2.1&doctype=json";
var userAgent=$.md5(navigator.userAgent);
var currentTs="" + (new Date).getTime();
var salt=currentTs + parseInt(10 * Math.random(), 10);
var sign = this.sign != "" ? this.sign : "]BjuETDhU)zqSxf-=B#7m";
var signStr = $.md5("fanyideskweb" + Trans.transText + salt + sign);
h_url = youdaoTransApi;
h_headers = {
"Content-Type": "application/x-www-form-urlencoded",
"Referer": "http://fanyi.youdao.com/"
};
h_data = StringFormat("from={0}&to={1}&salt={2}&sign={3}&i={4}<s={5}&bv={6}", Trans.transOrigLang, Trans.transTargetLang, salt, signStr, encodeURIComponent(Trans.transText),currentTs,userAgent);
GM_xmlhttpRequest({
method: "POST",
url: h_url,
headers: h_headers,
data: h_data,
onload: function (r) {
setTimeout(function () {
var data = JSON.parse(r.responseText);
var trans = [],
origs = [],
src = "";
if (data.errorCode == 0) {
for (var j = 0; j < data.translateResult.length; j++) {
var ydTransCont = data.translateResult[j];
var ydtgt = "";
var ydsrc = "";
for (var k = 0; k < ydTransCont.length; k++) {
var ydcont = ydTransCont[k];
ydtgt += ydcont.tgt;
ydsrc += ydcont.src;
}
trans.push(ydtgt);
origs.push(ydsrc);
}
src = data.type;
Trans.transResult.trans = trans;
Trans.transResult.orig = origs;
Trans.transResult.origLang = src.split("2")[0];
var smartResult = data.smartResult;
if (smartResult && smartResult.entries.length > 0) {
getYDSymbol(Trans.transText, function (symbol) {
Trans.transResult.symbols.en = symbol.uk;
Trans.transResult.symbols.am = symbol.us;
h_onloadfn();
});
}else {
h_onloadfn();
}
}
}, 300);
},
onerror: function (e) {
console.error(e);
}
});
},
init: function () {
getSign();
}
};
function a(r) {
if (Array.isArray(r)) {
for (var o = 0, t = Array(r.length); o < r.length; o++)
t[o] = r[o];
return t
}
return Array.from(r)
}
function n(r, o) {
for (var t = 0; t < o.length - 2; t += 3) {
var a = o.charAt(t + 2);
a = a >= "a" ? a.charCodeAt(0) - 87 : Number(a),
a = "+" === o.charAt(t + 1) ? r >>> a : r << a,
r = "+" === o.charAt(t) ? r + a & 4294967295 : r ^ a;
}
return r
}
function e(r,gtk) {
var i = null;
var o = r.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g);
if (null === o) {
var t = r.length;
t > 30 && (r = "" + r.substr(0, 10) + r.substr(Math.floor(t / 2) - 5, 10) + r.substr(-10, 10));
} else {
for (var e = r.split(/[\uD800-\uDBFF][\uDC00-\uDFFF]/), C = 0, h = e.length, f = []; h > C; C++)
"" !== e[C] && f.push.apply(f, a(e[C].split(""))),
C !== h - 1 && f.push(o[C]);
var g = f.length;
g > 30 && (r = f.slice(0, 10).join("") + f.slice(Math.floor(g / 2) - 5, Math.floor(g / 2) + 5).join("") + f.slice(-10).join(""));
}
var u = void 0
;
u = null !== i ? i : (i = gtk || "") || "";
for (var d = u.split("."), m = Number(d[0]) || 0, s = Number(d[1]) || 0, S = [], c = 0, v = 0; v < r.length; v++) {
var A = r.charCodeAt(v);
128 > A ? S[c++] = A : (2048 > A ? S[c++] = A >> 6 | 192 : (55296 === (64512 & A) && v + 1 < r.length && 56320 === (64512 & r.charCodeAt(v + 1)) ? (A = 65536 + ((1023 & A) << 10) + (1023 & r.charCodeAt(++v)),
S[c++] = A >> 18 | 240,
S[c++] = A >> 12 & 63 | 128) : S[c++] = A >> 12 | 224,
S[c++] = A >> 6 & 63 | 128),
S[c++] = 63 & A | 128);
}
for (var p = m, F = "" + String.fromCharCode(43) + String.fromCharCode(45) + String.fromCharCode(97) + ("" + String.fromCharCode(94) + String.fromCharCode(43) + String.fromCharCode(54)), D = "" + String.fromCharCode(43) + String.fromCharCode(45) + String.fromCharCode(51) + ("" + String.fromCharCode(94) + String.fromCharCode(43) + String.fromCharCode(98)) + ("" + String.fromCharCode(43) + String.fromCharCode(45) + String.fromCharCode(102)), b = 0; b < S.length; b++)
p += S[b],
p = n(p, F);
return p = n(p, D),
p ^= s,
0 > p && (p = (2147483647 & p) + 2147483648),
p %= 1e6,
p.toString() + "." + (p ^ m)
}
/**
* @param {string} word
* @param {string} gtk
* @return {string}
*/
var calcSign =function(word,gtk){
return e(word,gtk);
};
//获取gtk和token
function GetToken(){
GM_xmlhttpRequest({
method: "GET",
url: "https://fanyi.baidu.com/",
timeout:5000,
onload: function (r) {
var gtkMatch = /window\.gtk = '(.*?)'/.exec(r.responseText);
var commonTokenMatch = /token: '(.*?)',/.exec(r.responseText);
if (!gtkMatch) {
console.log("获取gtk失败!!!");
}
if (!commonTokenMatch) {
console.log("获取token失败!!!");
}
var newGtk = gtkMatch[1];
var newCommonToken = commonTokenMatch[1];
if (typeof newGtk !== 'undefined') {
baiduTrans.gtk=newGtk;
}
if (typeof newCommonToken !== 'undefined') {
baiduTrans.token=newCommonToken;
}
},
onerror: function (e) {
console.error(e);
}
});
}
//百度翻译
var baiduTrans = {
code:"bd",
codeText:"百度",
gtk:"",
token:"",
defaultOrigLang:"auto", //默认源语言
defaultTargetLang:"zh", //默认目标语言
langList: {"auto": "自动检测","zh": "中文","cht": "繁体中文","en": "英语","jp": "日语","kor": "韩语","fra": "法语","spa": "西班牙语","pt": "葡萄牙语","it": "意大利语","ru": "俄语","vie": "越南语","de": "德语","ara": "阿拉伯语"},
Execute: function (h_onloadfn) {
if(Trans.transOrigLang=="auto")
this.AutoTrans(h_onloadfn);
else
this.ExecTrans(h_onloadfn);
},
AutoTrans:function(h_onloadfn){
var self=this;
var datas={
query:Trans.transText
};
GM_xmlhttpRequest({
method: "POST",
headers:{
"referer": 'https://fanyi.baidu.com',
"Content-Type": 'application/x-www-form-urlencoded; charset=UTF-8',
},
url: "https://fanyi.baidu.com/langdetect",
data: ObjectToQueryString(datas),
onload: function (r) {
var data = JSON.parse(r.responseText);
if(data.error===0){
Trans.transOrigLang=data.lan;
self.ExecTrans(h_onloadfn);
}
},
onerror: function (e) {
console.error(e);
}
});
},
ExecTrans:function(h_onloadfn){
var tempSign=calcSign(Trans.transText,this.gtk);
var datas={
from:Trans.transOrigLang,
to:Trans.transTargetLang,
query:Trans.transText,
transtype:"translang",
simple_means_flag:3,
sign:tempSign,
token:this.token
};
GM_xmlhttpRequest({
method: "POST",
headers:{
"referer": 'https://fanyi.baidu.com',
"Content-Type": 'application/x-www-form-urlencoded; charset=UTF-8',
//"User-Agent": window.navigator.userAgent,
},
url: "https://fanyi.baidu.com/v2transapi",
data: ObjectToQueryString(datas),
onload: function (r) {
setTimeout(function () {
var result= JSON.parse(r.responseText);
var trans_result=result.trans_result;
var dict_result=result.dict_result || null;
var transDatas = trans_result.data;
var trans = [],origs = [],src = "";
for (var i = 0; i < transDatas.length; i++) {
var getransCont = transDatas[i];
trans.push(getransCont.dst);
origs.push(getransCont.src);
}
src = trans_result.from;
Trans.transResult.trans = trans;
Trans.transResult.orig = origs;
Trans.transResult.origLang = src;
if(dict_result){
var symbols=dict_result.simple_means.symbols;
Trans.transResult.symbols.en=symbols[0].ph_en || "";
Trans.transResult.symbols.am=symbols[0].ph_am || "";
}
h_onloadfn();
}, 300);
},
onerror: function (e) {
console.error(e);
}
});
},
init:function(){
GetToken();
}
};
var Trans={
transEngineList:{}, //翻译引擎实例列表
transEngine:"", //当前翻译引擎。ge(谷歌)/yd(有道)
transEngineObj:{}, //当前翻译引擎实例
transTargetLang:"", //目标语言。
transOrigLang:"", //源语言
transType:"word", //翻译类型。word(划词翻译)/text(输入文本翻译)/page(整页翻译)
transText:"", //被翻译内容
transResult:{ //当前翻译内容
//译文
trans:[],
//原文
orig:[],
//原文语言
origLang:"",
//音标
symbols:{
//英标
en:"",
//美标
am:"",
}
},
Execute:function(h_onloadfn){
resetTransResult(this);
this.transEngineObj.Execute(h_onloadfn);
},
GetLangList:function(){
var langList={};
langList=this.transEngineObj.langList;
return langList;
},
Update:function(){
resetTransResult(this);
this.transEngineObj=this.transEngineList[this.transEngine];
this.transTargetLang=this.transEngineObj.defaultTargetLang;
this.transOrigLang=this.transEngineObj.defaultOrigLang;
},
Clear:function(){
this.transEngine=""; //当前翻译引擎。ge(谷歌)/yd(有道)
this.transTargetLang=""; //目标语言。
this.transOrigLang=""; //源语言
this.transText=""; //被翻译内容
this.transResult.trans=[];
this.transResult.orig=[];
this.transResult.origLang="";
},
//注册翻译引擎接口并执行翻译引擎的初始化接口
RegisterEngine:function(){
/**
* 翻译引擎必须提供以下接口
code:"", //代号
codeText:"", //代号描述
defaultOrigLang:"", //默认源语言
defaultTargetLang:"", //默认目标语言
langList: {}, //支持翻译语言列表
Execute: function (h_onloadfn) {}, //执行翻译
init:function(){}, //可选,初始化接口,在脚本创建时立即执行
*/
var transEngineListObj={};
transEngineListObj[googleTrans.code]=googleTrans;
transEngineListObj[youdaoTrans.code]=youdaoTrans;
transEngineListObj[baiduTrans.code]=baiduTrans;
this.transEngineList=transEngineListObj;
for (var key in this.transEngineList) {
if (this.transEngineList.hasOwnProperty(key) && this.transEngineList[key].hasOwnProperty("init")) {
this.transEngineList[key].init();
}
}
}
};
function resetTransResult(that){
that.transResult.trans=[];
that.transResult.orig=[];
that.transResult.origLang="";
that.transResult.symbols.en="";
that.transResult.symbols.am="";
}
//面板
var Panel={
popBoxEl:{},
randomCode:"",
Create:function(title,placement,isShowArrow,content,shownFn){
var self=this;
$(self.popBoxEl).jPopBox({
title: title,
className: 'JPopBox-tip-white',
placement: placement,
trigger: 'none',
isTipHover: true,
isShowArrow: isShowArrow,
content: function(){
return StringFormat('<div id="panelBody{0}">{1}</div>',self.randomCode,content);
}
});
$(self.popBoxEl).on("shown.jPopBox",function(){
var $panel=$("div.JPopBox-tip-white");
typeof shownFn === 'function' && shownFn($panel);
});
$(self.popBoxEl).jPopBox('show');
},
Update:function(Fn){
var $panel=$("div.JPopBox-tip-white");
Fn($panel);
},
Destroy:function(){
//$(this.popBoxEl).jPopBox("hideDelayed");
$(this.popBoxEl).jPopBox("destroy");
},
CreateStyle:function(){
var s="";
s+=StringFormat("#panelBody{0}>div input,#panelBody{0}>div select{padding: 3px; margin: 0; background: #fff; font-size: 14px; border: 1px solid #a9a9a9; color:black;width: auto;min-height: auto; }",this.randomCode);
s+=StringFormat("#panelBody{0}>div:first-child{padding-bottom: 5px;height:30px}",this.randomCode);
s+=StringFormat("#panelBody{0}>div:last-child hr{border: 1px inset #eeeeee;background: none;height: 0px;margin: 0px;}",this.randomCode);
return s;
}
};
//文本翻译面板
var TextTransPanel={
Create:function(popBoxEl,randomCode){
var self=this;
var html=this.GetHtml();
var transEngineOptionsHtml="";
//翻译引擎
for (var k in Trans.transEngineList) {
if (Trans.transEngineList.hasOwnProperty(k)) {
var v = Trans.transEngineList[k].codeText;
var selectOption="";
if(Trans.transEngine==k){
selectOption='selected="selected"';
}
transEngineOptionsHtml+=StringFormat('<option value="{0}" {2}>{1}</option>',k,v,selectOption);
}
}
var TextTransPanelHtml=StringFormat('<div style="padding-bottom: 5px;">'+
'翻译引擎:<select>{2}</select> '+
'翻译语言:<select>{4}</select> ⇨ '+
'<select>{3}</select> '+
'<button style="width:46px; height:26px; cursor: pointer;overflow: visible;color: inherit;margin: 0;padding: 1px 7px;background-color: #dddddd;border: 2px outset #dddddd;text-align: center;display: inline-block;font-size: 14px; font-weight: 400; ">翻译</button></div>'+
'<div style="word-wrap:break-word">'+
'<div style="padding-bottom: 5px;"><textarea placeholder="请输入你要翻译的文字" style="word-wrap: break-word;word-break: keep-all;overflow-y: auto;width:450px;height:85px;padding: 3px;line-height: 18px;font-size: 14px;font-family: arial,simsun;border: 1px solid #999;border-color: #999 #d8d8d8 #d8d8d8 #999;outline: 0;resize: none;">{5}</textarea></div><hr/>'+
'<div style="padding-top: 5px;">{6}</div>'+
'</div>',randomCode,"",transEngineOptionsHtml,html.targetLangListHtml,html.origLangListHtml,"","");
Panel.popBoxEl=popBoxEl;
Panel.randomCode=randomCode;
Panel.Create("文本翻译","auto bottom",false,TextTransPanelHtml,function($panel){
$panel.css({
position: "fixed",
top:"20px"
});
//翻译引擎
$panel.find(StringFormat("#panelBody{0} div:eq(0) select:eq(0)",randomCode)).change(function(e){
Trans.transEngine=$(this).find("option:selected").val();
Trans.Update();
Panel.Update(function($panel){
var html=self.GetHtml();
//翻译内容
$panel.find(StringFormat("#panelBody{0} div:eq(1) div:eq(1)",randomCode)).html("");
$panel.find(StringFormat("#panelBody{0} div:eq(0) select:eq(1)",randomCode)).html(html.origLangListHtml);
$panel.find(StringFormat("#panelBody{0} div:eq(0) select:eq(2)",randomCode)).html(html.targetLangListHtml);
});
});
//源语言
$panel.find(StringFormat("#panelBody{0} div:eq(0) select:eq(1)",randomCode)).change(function(e){
Trans.transOrigLang=$(this).find("option:selected").val();
Panel.Update(function($panel){
var html=self.GetHtml();
$panel.find(StringFormat("#panelBody{0} div:eq(0) select:eq(2)",randomCode)).html(html.targetLangListHtml);
});
});
//目标语言
$panel.find(StringFormat("#panelBody{0} div:eq(0) select:eq(2)",randomCode)).change(function(e){
Trans.transTargetLang=$(this).find("option:selected").val();
});
//翻译
$panel.find(StringFormat("#panelBody{0} div:eq(0) button:eq(0)",randomCode)).click(function(e){
var refTransText=$.trim($panel.find(StringFormat("#panelBody{0} div:eq(1) div:eq(0) textarea:eq(0)",randomCode)).val());
if(refTransText==""){
alert("请输入翻译文字!");
return;
}
Trans.transText=refTransText;
Trans.Execute(function(){
Panel.Update(function($panel){
var html=self.GetHtml();
//源语言
$panel.find(StringFormat("#panelBody{0} div:eq(0) select:eq(1)",randomCode)).html(html.origLangListHtml);
//翻译内容
$panel.find(StringFormat("#panelBody{0} div:eq(1) div:eq(1)",randomCode)).html(html.transHtml);
});
});
});
});
},
GetHtml:function(){
var origLangListHtml=[];
var targetLangListHtml=[];
var returnHtml={};
var transHtml=[];
var langList=Trans.GetLangList();
var origLang=Trans.transResult.origLang;
if(Trans.transResult.trans.length>0 && Trans.transResult.orig.length>0)
{
transHtml.push('<span>');
for (var i = 0; i < Trans.transResult.trans.length; i++) {
var transtxt = Trans.transResult.trans[i];
transHtml.push(transtxt);
}
transHtml.push("</span>");
Trans.transOrigLang=origLang;
}
else {
var txt="该翻译引擎不支持 "+langList[Trans.transOrigLang]+" 翻译成 "+langList[Trans.transTargetLang];
transHtml.push(StringFormat("<span>{0}</span>",txt));
}
//源语言
for (var origKey in langList) {
if (langList.hasOwnProperty(origKey)) {
var origVal = langList[origKey];
var origSelectOption="";
if(Trans.transOrigLang.toUpperCase()==origKey.toUpperCase()){
origSelectOption='selected="selected"';
}
origLangListHtml.push(StringFormat('<option value="{0}" {2}>{1}</option>',origKey,origVal,origSelectOption));
}
}
//目标语言
for (var targetKey in langList) {
if (langList.hasOwnProperty(targetKey) && targetKey!=Trans.transOrigLang && targetKey.toUpperCase()!="AUTO") {
var targetVal = langList[targetKey];
var targetSelectOption="";
targetLangListHtml.push(StringFormat('<option value="{0}" {2}>{1}</option>',targetKey,targetVal,targetSelectOption));
}
}
returnHtml.origLangListHtml=origLangListHtml.join("");
returnHtml.targetLangListHtml=targetLangListHtml.join("");
returnHtml.transHtml=transHtml.join("");
return returnHtml;
}
};
//划词翻译面板
var WordTransPanel = {
Create: function (popBoxEl, randomCode) {
var self = this;
var html = this.GetTransContHtml();
var transEngineOptionsHtml = "";
for (var k in Trans.transEngineList) {
if (Trans.transEngineList.hasOwnProperty(k)) {
var v = Trans.transEngineList[k].codeText;
var selectOption = "";
if (Trans.transEngine == k) {
selectOption = 'selected="selected"';
}
transEngineOptionsHtml += StringFormat('<option value="{0}" {2}>{1}</option>', k, v, selectOption);
}
}
var wordTransPanelHtml = StringFormat(
'<div>翻译引擎:<select>{2}</select> 翻译语言:<input type="text" value="{4}" readonly style="width:80px"/> ⇨ <select>{3}</select></div>' +
'<div style="word-wrap:break-word">{1}</div>', randomCode, html.transHtml, transEngineOptionsHtml, html.langListHtml, html.origLangName);
Panel.popBoxEl = popBoxEl;
Panel.randomCode = randomCode;
Panel.Create("", "auto bottom", false, wordTransPanelHtml, function ($panel) {
+
//目标语言
$panel.find(StringFormat("#panelBody{0} div:eq(0) select:eq(1)", randomCode)).change(function (e) {
Trans.transTargetLang = $(this).find("option:selected").val();
Trans.Execute(function () {
self.Update(randomCode);
});
});
//翻译引擎
$panel.find(StringFormat("#panelBody{0} div:eq(0) select:eq(0)", randomCode)).change(function (e) {
Trans.transEngine = $(this).find("option:selected").val();
Trans.Update();
Trans.Execute(function () {
self.Update(randomCode);
});
});
});
},
Update: function (randomCode) {
var self = this;
Panel.Update(function ($panel) {
var html = self.GetTransContHtml();
$panel.find(StringFormat("#panelBody{0} div:eq(0) input:eq(0)", randomCode)).val("").val(html.origLangName);
$panel.find(StringFormat("#panelBody{0} div:eq(0) select:eq(1)", randomCode)).html("").html(html.langListHtml);
$panel.find(StringFormat("#panelBody{0} div:eq(1)", randomCode)).html("").html(html.transHtml);
});
},
GetTransContHtml: function () {
var transObj = {};
var langListHtml = [];
var langList = Trans.GetLangList();
var origLang = Trans.transResult.origLang;
var transContHtml = "";
if (Trans.transResult.trans.length > 0 && Trans.transResult.orig.length > 0) {
//译文
var transHtml = [];
transHtml.push('<div style="padding-top: 5px;"><ul style="list-style: none;margin: 0;padding: 0;">');
for (var i = 0; i < Trans.transResult.trans.length; i++) {
var transtxt = Trans.transResult.trans[i];
transHtml.push(StringFormat('<li style="list-style: none;"><span>{0}</span></li>', transtxt));
}
transHtml.push("</ul></div>");
//原文
var origHtml = [];
//原文内容
origHtml.push('<div style="padding-bottom: 5px;"><ul style="list-style: none;margin: 0;padding: 0;">');
for (var j = 0; j < Trans.transResult.orig.length; j++) {
var origtxt = Trans.transResult.orig[j];
origHtml.push(StringFormat('<li style="list-style: none;"><span>{0}</span></li>', origtxt));
}
origHtml.push("</ul>");
//原文音标
if (Trans.transResult.symbols.en!="" || Trans.transResult.symbols.am!="") {
origHtml.push('<div>');
if(Trans.transResult.symbols.en!="")
origHtml.push(StringFormat('<span style="padding-right: 10px;">英 [{0}]</span>',Trans.transResult.symbols.en));
if(Trans.transResult.symbols.am!="")
origHtml.push(StringFormat('<span>美 [{0}]</span>',Trans.transResult.symbols.am));
origHtml.push('</div>');
}
origHtml.push("</div>");
transContHtml = origHtml.join("") + "<hr/>" + transHtml.join("");
Trans.transOrigLang = origLang;
} else {
var txt = "该翻译引擎不支持 " + langList[Trans.transOrigLang] + " 翻译成 " + langList[Trans.transTargetLang];
transContHtml = StringFormat("<div><span>{0}</span></div>", txt);
}
for (var k in langList) {
if (langList.hasOwnProperty(k) && k != Trans.transOrigLang && k.toUpperCase() != "AUTO") {
var v = langList[k];
var selectOption = "";
if (Trans.transTargetLang == k) {
selectOption = 'selected="selected"';
}
langListHtml.push(StringFormat('<option value="{0}" {2}>{1}</option>', k, v, selectOption));
}
}
transObj.origLangName = langList[Trans.transOrigLang];
transObj.transHtml = transContHtml;
transObj.langListHtml = langListHtml.join("");
return transObj;
}
};
//设置面板
var SettingPanel={
config:[{title:"",item:[{code:"",text:""}]}],
Create:function(popBoxEl,randomCode){
var self=this;
var settingHtml=[];
this.InitConfig();
settingHtml.push('<div style="padding-left: 15px;display: inline-block;">');
/*
settingHtml.push('<div style="padding-bottom: 30px; max-width: 600px;">');
settingHtml.push('<div style="font-size: 14px; padding-bottom: 3px;">默认翻译引擎:</div>');
settingHtml.push(StringFormat('<div style="padding-bottom: 3px; margin-left: 10px;"><label style="font-size: 14px; cursor: pointer;"><input type="radio" name="transEngine{0}" style="cursor: pointer;" value="yd">有道</label></div>',randomCode));
settingHtml.push(StringFormat('<div style="padding-bottom: 0px; margin-left: 10px;"><label style="font-size: 14px; cursor: pointer;"><input type="radio" name="transEngine{0}" style="cursor: pointer;" value="ge">谷歌</label></div>',randomCode));
settingHtml.push('</div>');
*/
for (var index = 0; index < this.config.length; index++) {
var configItem = this.config[index];
settingHtml.push('<div style="padding-bottom: 30px; max-width: 600px;">');
settingHtml.push(StringFormat('<div style="font-size: 14px; padding-bottom: 3px;">{0}</div>',configItem.title));
for (var itemIndex = 0; itemIndex < configItem.item.length; itemIndex++) {
var itemObj = configItem.item[itemIndex];
settingHtml.push(StringFormat('<div style="padding-bottom: 0px; margin-left: 10px;"><label style="font-size: 14px; cursor: pointer;"><input type="radio" name="transEngine{0}" style="cursor: pointer;" value="{1}">{2}</label></div>',randomCode,itemObj.code,itemObj.text));
}
settingHtml.push('</div>');
}
settingHtml.push('<div>');
settingHtml.push(StringFormat('<button id="saveBtn{0}">保存</button>',randomCode));
settingHtml.push(StringFormat('<span id="saveStatus{0}" style="display:none;margin-left:10px;background-color: #fff1a8;padding: 3px;">设置已保存。</span>',randomCode));
settingHtml.push('</div>');
settingHtml.push('</div>');
var settingHtmlStr=settingHtml.join("");
Panel.popBoxEl=popBoxEl;
Panel.randomCode=randomCode;
Panel.Create("网页翻译助手设置","auto bottom",false,settingHtmlStr,function($panel){
$panel.css({
position: "fixed",
top:"20px"
});
self.Update(randomCode);
//保存设置
$panel.find(StringFormat("#panelBody{0} #saveBtn{0}",randomCode)).click(function(e){
var defaultTransEngine=$panel.find(StringFormat("#panelBody{0} input[name='transEngine{0}']:checked",randomCode)).val();
options.defaulttransengine=defaultTransEngine;
SetSettingOptions();
$panel.find(StringFormat("#panelBody{0} #saveStatus{0}",randomCode)).fadeIn(function(){
setTimeout(function(){
$panel.find(StringFormat("#panelBody{0} #saveStatus{0}",randomCode)).fadeOut();
},1500);
});
});
});
},
Update:function(randomCode){
GetSettingOptions();
Panel.Update(function($panel){
$panel.find(StringFormat("#panelBody{0} input[name='transEngine{0}'][value='{1}']",randomCode,options.defaulttransengine)).prop("checked",true);
});
},
InitConfig:function(){
this.config=[];
var configObj={title:"",item:[{code:"",text:""}]};
configObj.title="默认翻译引擎:";
configObj.item=[];
for (var k in Trans.transEngineList) {
if (Trans.transEngineList.hasOwnProperty(k)) {
var v = Trans.transEngineList[k].codeText;
configObj.item.push({code:k,text:v});
}
}
this.config.push(configObj);
}
};
//主程序
var WebTranslate=function(){
var transIconBase64="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAENklEQVRoQ+2ZTVITQRTH/28qyNIJFxCqnGwNJzCcwHACcelkIZxAPAG4IFkaTyCegHgC4jZjVfACzLBEUvOs7jCx57unZ6JSRZZJ9+v3e+/1++gQHviHHrj+eAT41x4s9YA99F5qK3nb+h4c7QTa6xtYmAuwNfLeg3Fc5QxmBGy1doO3O1dV9tVZmwmwdeYdg/DeTDB/vnY7B2Z7q+/KBhh6XF3UcgeDr3y3s2O6v+q+FIB9NutaRJdVBanrQ+bdYNCZ1pGhuzcNMPJ6FuNCV0DWuhA4ClzntI4M3b1rARBhRERjXSWK1oUhfy3y5loAmlBclcHAue86+1lyHwTAfXb4cD1wUmm9EgAzfhLoPLT4fGUNRpeAPgH6Bc/ARcw89Qed3eRWLQAGbhg4LrqY9sjrUYgxEZ7FDmH6GAPWUZ7JJvBh0ijXrpPStxRAKs/c00mLAiKewcyLmn0yt63Nha/yGgGEoP3Afb4KGSEYT+5eWxbZQniUJUT9INAFEeT3RXGr4wSxpj30JqoXKgMw8M13nV50oH02OyDQSUxJWX55zKB+8vu8uP1rAKr17dF8m8LFZUr5Em1Cau2ozZ09/NEn8Dum1puypq+2B1SXbQ1nY4Be61rvTxjxOPy1cRS12e2z2SURdcHITIuq/FoA4vL6rrOK56QwBn/n241eXv/fHs6mBHpRFViEHVsb+8I79QAYgT9w2pECSWFlFlxZuiqBSAz3YVcLQJxbFEJyeGEcwMJyAmO+UVPtltKSJzOZyiTuhAX+woyv/sDpNxZCMkUqKVReYl5MCXiaZdSQ+U0w6MgGLt2S59eD9tC7IKCn7s/zukkanfiusxdLo0SnaYi4gnkTXXLYiQqfLJa3re3kfaodQkkvSOuKQra5OLB4WbBCC5PgrTNZQZ7MbXqymKdqAnAjwZXsU5aRGgGQsQ7e02olpPJ3FzJNJj4ixonwSkIz71qgvpi7RYPoD5ztrLBsBCASHDIfBoPOx7yksmwl8ClLeXnHgW/ENAXxO2GUyENF42ejAEsl+Eq204DSTnOXiHqipS7LmOFtq02bi/OovykbPRsHKFNQ4/dTkXoj68ui9WtjL78Y1mzmNBQyWiKqOETPT3hWBPE/egBRwRLZjDbvJqLdyIP4LwFCwl6UdssgzAAaeNgqjKtEFxqDAGKF0whAHK72MUZBXrApOSRFxXGZnfhKfVc1B6j1uFuGrD8nGwNIL6wBIq/nyUOuBbDqbUbeaiYus230uxWil/U8X9RWZ7cSszmBVm2GVjeqq2TZuqwRtAqAeECwiD6p5/xVgGhQSTd1PAVRyd9QvK1a/r6Xio24kdzS/8jKLF30e3voBXkDUFW5WRObkLFWAOOXjASdvPzU6mY9w6wVYFmk/nSfla0O3IAxYat1mPeGtFaAqgqbrH8EMLFak3sePdCkNU1k/QadtchPhjx3/AAAAABJRU5ErkJggg==";
var $doc=$(document);
var $body=$("html body");
var $head=$("html head");
var randomCode="yyMM000000"; //属性随机码,年月加六位随机码。用于元素属性后缀,以防止属性名称重复。
var createHtml=function(){
var wordTransIconHtml=StringFormat('<div id="wordTrans{0}" class="wordTrans{0}"><div class="wordTransIcon{0}"></div></div>',randomCode,transIconBase64);
$body.append(StringFormat('<div id="webTrans{0}">',randomCode)+wordTransIconHtml+'</div>');
};
var createStyle=function(){
//尽可能避开csp认证
GM_xmlhttpRequest({
method:"get",
url:"https://cdn.jsdelivr.net/gh/zyufstudio/jQuery@master/jPopBox/dist/jPopBox.min.css",
onload:function(r){
GM_addStyle(r.responseText+".JPopBox-tip-white{width: 482px;max-width: 550px;min-width: 450px;}");
}
});
var s="";
s+=StringFormat(".wordTrans{0}{background-color: rgb(245, 245, 245);box-sizing: content-box;cursor: pointer;z-index: 2147483647;border-width: 1px;border-style: solid;border-color: rgb(220, 220, 220);border-image: initial;border-radius: 5px;padding: 0.5px;position: absolute;display: none}",randomCode);
s+=StringFormat(".wordTransIcon{0}{background-image: url({1});background-size: 25px;height: 25px;width: 25px;}",randomCode,transIconBase64);
s+=Panel.CreateStyle();
GM_addStyle(s);
};
var ShowWordTransIcon=function(){
var $wordTransIcon=$("div#wordTrans"+randomCode);
var isSelect=false;
var isPanel=false;
var isWordTransIcon=false;
$doc.on({
"selectionchange":function(e){
isSelect=true;
},
"mousedown":function(e){
var $targetEl=$(e.target);
isPanel=$targetEl.parents().is("div.JPopBox-tip-white");
isWordTransIcon=$targetEl.parents().is(StringFormat("div#wordTrans{0}",randomCode));
//点击翻译图标外域和翻译面板外域时,隐藏图标和翻译面板
if(!isWordTransIcon && !isPanel){
$wordTransIcon.hide();
Trans.Clear();
Panel.Destroy();
}
else {
//点击翻译图标,取消鼠标默认事件,防止选中的文本消失
if(isWordTransIcon){
ClearBubble(e);
}
}
},
"mouseup":function(e){
var selectText = window.getSelection().toString().trim();
if(!isPanel&&isSelect&&selectText){
$wordTransIcon.show().css({
left: e.pageX + 'px',
top : e.pageY + 12 + 'px'
});
isSelect=false;
}
}
});
$wordTransIcon.click(function(e){
Trans.Clear();
Panel.Destroy();
var selecter=window.getSelection();
var selectText = selecter.toString().trim();
GetSettingOptions();
Trans.transText=selectText;
Trans.transType="word";
Trans.transEngine=options.defaulttransengine;//defaultTransEngine;
Trans.Update();
Trans.Execute(function(){
WordTransPanel.Create($wordTransIcon,randomCode);
$wordTransIcon.hide();
});
});
};
var guid="";
var RegMenu=function(){
GM_registerMenuCommand("文本翻译",function(){
var $body=$("html body");
$("div#wordTrans"+randomCode).hide();
Trans.Clear();
Panel.Destroy();
GetSettingOptions();
Trans.transEngine=options.defaulttransengine;//defaultTransEngine;
Trans.Update();
TextTransPanel.Create($body,randomCode);
});
GM_registerMenuCommand("Google整页翻译",function(){
if(guid=="")
guid=Guid();
var cbscript=StringFormat('!function(){!function(){function e(){window.setTimeout(function(){window[t].showBanner(!0)},10)}function n(){return new google.translate.TranslateElement({autoDisplay:!1,floatPosition:0,multilanguagePage:!0,includedLanguages:"zh-CN,zh-TW,en",pageLanguage:"auto"})}var t=(document.documentElement.lang,"TE_{0}"),o="TECB_{0}";if(window[t])e();else if(!window.google||!google.translate||!google.translate.TranslateElement){window[o]||(window[o]=function(){window[t]=n(),e()});var a=document.createElement("script");a.src="https://translate.google.com.hk/translate_a/element.js?cb="+encodeURIComponent(o)+"&client=tee",document.getElementsByTagName("head")[0].appendChild(a)}}()}();',guid);
$head.append(StringFormat('<script>{0}</script>',cbscript));
});
GM_registerMenuCommand("设置",function(){
$("div#wordTrans"+randomCode).hide();
Trans.Clear();
Panel.Destroy();
SettingPanel.Create($body,randomCode);
});
};
this.init=function(){
randomCode=DateFormat(new Date(),"yyMM").toString()+(Math.floor(Math.random() * (999999 - 100000 + 1) ) + 100000).toString();
Trans.RegisterEngine();
createStyle();
createHtml();
ShowWordTransIcon();
RegMenu();
};
};
var webTrans=new WebTranslate();
webTrans.init();
})();
如何判断文本内容的语言语种?
遍历所有字符,统计频率最高的语种为文档或者网页的语种,核心代码如下:
function isChinese(temp)
{
var re = /[^\u4e00-\u9fa5]/;
if(re.test(temp)) return false;
return true;
}
function isJapanese(temp)
{
var re = /[^\u0800-\u4e00]/;
if(re.test(temp)) return false;
return true;
}
function isKoera(chr) {
if(((chr > 0x3130 && chr < 0x318F) ||
(chr >= 0xAC00 && chr <= 0xD7A3)))
{
return true;
}
return false;
}
function isContainKoera(temp)
{
var cnt = 0;
for(var i=0;i < temp.length ; i++)
{
if(isKoera(temp.charAt(i)))
cnt++;
}
if (cnt > 0) return true;
return false;
}
function isContainChinese(temp)
{
var cnt = 0;
for(var i=0;i < temp.length ; i++)
{
if(isChinese(temp.charAt(i)))
cnt++;
}
if (cnt > 5) return true;
return false;
}
function isContainChinese2(temp)
{
var cnt = 0;
for(var i=0;i < temp.length ; i++)
{
if(isChinese(temp.charAt(i)))
cnt++;
}
if (cnt > 0 && temp.length<=3) return true;
return false;
}
function isContainJapanese(temp)
{
var cnt = 0;
for(var i=0;i < temp.length ; i++)
{
if(isJapanese(temp.charAt(i)))
cnt++;
}
if (cnt > 2) return true;
return false;
}
当然也可以 截取 一段文本内容发送给 语言 模型 进行识别,但是对于混合文档还是不准确,还不如放在前端做节省计算资源。
看下谷歌翻译之前的实现方式:
<script type="text/javascript" src="http://www.google.com/jsapi"></script >
<script type="text/javascript">
google.load("language", "1");
function initialize()
{
var text = document.getElementById("text").innerHTML;
google.language.detect(text,
function(result)
{
if(!result.error && result.language)
{
google.language.translate(text, result.language, "en",
function(result)
{
var translated = document.getElementById("translation");
if(result.translation)
{
translated.innerHTML = result.translation;
}
});
}
});
}
google.setOnLoadCallback(initialize);
</script>
或者:
<script src="https://translate.google.cn/translate_a/element.js?cb=googleTranslateElementInit"></script>
还有网友改进后的一种方式:
<script src="./el_main.js"></script>
<script src="./el_main.css"></script>
<script>
function googleTranslateElementInit() {
new google.translate.TranslateElement(
{
//这个参数不起作用,看文章底部更新,翻译面板的语言
//pageLanguage: 'zh-CN',
//这个是你需要翻译的语言,比如你只需要翻译成越南和英语,这里就只写en,vi
includedLanguages: 'en,zh-CN,hr,cs,da,nl,fr,de,el,iw,hu,ga,it,ja,ko,pt,ro,ru,sr,es,th,vi',
//选择语言的样式,这个是面板,还有下拉框的样式,具体的记不到了,找不到api~~
layout: google.translate.TranslateElement.InlineLayout.SIMPLE,
//自动显示翻译横幅,就是翻译后顶部出现的那个,有点丑,这个属性没有用的话,请看文章底部的其他方法
autoDisplay: true,
//还有些其他参数,由于原插件不再维护,找不到详细api了,将就了,实在不行直接上dom操作
},
'google_translate_element'//触发按钮的id
);
}
</script>
还用另一个 js 库 去实现的:
<script src="./franc.js">
// import {franc, francAll} from './franc.js'
var ll=franc('Alle menneske er fødde til fridom') //=> 'nno'
console.log(ll);
</script>
依赖的 js 库太大了,不贴出来了,有需要可以留言。
查资料看到还有这种用法的:
<script>
javascript: void((function () {
var script = document.createElement('script');
script.src = '//translate.google.cn/translate_a/element.js?cb=googleTranslateElementInit';
document.getElementsByTagName('head')[0].appendChild(script);
var google_translate_element = document.createElement('div');
google_translate_element.id = 'google_translate_element';
google_translate_element.style = 'position:fixed; bottom:10px; right:10px; cursor:pointer;';
document.documentElement.appendChild(google_translate_element);
script = document.createElement('script');
script.innerHTML = "function googleTranslateElementInit() {" +
"new google.translate.TranslateElement({" +
"layout: google.translate.TranslateElement.InlineLayout.SIMPLE," +
"multilanguagePage: true," +
"pageLanguage: 'auto'," +
"includedLanguages: 'zh-CN,zh-TW,en'" +
"}, 'google_translate_element');}";
document.getElementsByTagName('head')[0].appendChild(script);
})());
</script>
获取网页内容的所有标签的文本内容,过滤非文本标签,实现如下:
<script>
function listen(callback) {
// 获取 HTML 文档中的所有元素,但不包括 下列 选择器的元素
var exclude = ['head', 'pre', 'script', 'textarea']//排除名单
var selectors = []
exclude.forEach((item, index) => {
selectors.push(item)//排除该元素
selectors.push(item + ' *')//排除该元素后代
})
get(document.querySelectorAll('*:not(' + selectors.join(',') + ')'))//*:not(pre,pre *)
// 创建 MutationObserver 对象
let observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
// 遍历新添加的节点
for (let i = 0; i < mutation.addedNodes.length; i++) {
let node = mutation.addedNodes[i];
// 如果节点是元素节点,就调用 get 函数
if (node.nodeType === 1) {
callMyFunction(node)
function callMyFunction(param1) {
setTimeout(function () {
get([...param1.querySelectorAll('*'), param1])
}, 300);
}
}
}
});
});
// 设置 MutationObserver 的参数,表示监听所有元素的变化
let config = {
childList: true,
subtree: true
};
// 启动 MutationObserver
observer.observe(document, config);
function get(elements) {
// 遍历所有元素
for (let i = 0; i < elements.length; i++) {
let element = elements[i];
// 遍历元素的 childNodes
for (let j = 0; j < element.childNodes.length; j++) {
let node = element.childNodes[j];
// 如果当前节点是一个文本节点(nodeType 为 3)且不包含子节点(nodeName 为 '#text'),就将文本添加到数组中
if (node.nodeType === 3 && node.nodeName.toLowerCase() === '#text') {
// 过滤掉文本中的换行符
let text = node.nodeValue
var v = { a: false, b: false }
text.slice(0, 1) == " " ? v.a = true : v.a = false
text.slice(-1) == " " ? v.b = true : v.b = false
text = text.replace(/[\n\t\r]/g, '').trim();
// 如果文本不仅包含空白字符,就将它添加到数组中
if (/\S/.test(text)) {
//不处理只有数字和符号的文本
if (!/^[0-9\+\-\*\/\=><&\!@#\$%\^\*\\(\)\[\]\{\}_,.;',。、;’、]{1,}$/.test(text)) {
//---------------处理
//翻译text
//text = "$" + text
//---------------处理结束--显示
v.a == true ? text = " " + text : text
v.b == true ? text = text + " " : text
if (!element.matches('script,textarea')) {//单元素阻断,白名单
node.nodeValue = text
callback.call({ text: text, node: node, element: element })
} else {
//console.log("位于排除标签列表", element);
}
} else {
//console.log("只有数字和符号的文本", text);
}
}
}
}
}
}
}
let time = null;
var data = []
listen(
function () {
if (time !== null) {
clearTimeout(time);
}
time = setTimeout(async () => {
console.log(data);//抖动结束,开始翻译
var sl = []
data.forEach((item, index) => {//取text
sl.push(item['text'])
});
// var tl = await translation_arr(sl) //返回一个数组[[翻译结果,源语言类型],...*]//使用的谷歌批量翻译API,这里就不提供了
var tl = []
sl.forEach((item, index) => {
tl.push('[ 编辑:' + item + ',' + index + '] ')
});
tl.forEach((item, index) => {
data[index]['node'].origText = data[index]['node'].nodeValue
data[index]['node'].nodeValue = item//更改文本
});
//这里的this指向的是input
}, 500)
data.push(this)
}
)
/* 监听文本节点被点击
document.onselectstart = function (e) {
console.log(e.target,e.target.origText);
}*/
</script>
在网页中自动插入一个 下拉框 用于展示支持的翻译语种,并根据网页内容识别的语种自动选中语种类型:
<script>
// 获取浏览器默认语言
const getBrowserLang = function() {
let browserLang = navigator.language
? navigator.language
: navigator.browserLanguage;
let defaultBrowserLang = "";
if (
browserLang.toLowerCase() === "us" ||
browserLang.toLowerCase() === "en" ||
browserLang.toLowerCase() === "en_us"
) {
defaultBrowserLang = "en_US";
} else {
defaultBrowserLang = "zh_CN";
}
return defaultBrowserLang;
};
console.log(getBrowserLang());
</script>
<script>
var type = navigator.appName; //BOM对象获取浏览器名称
if (type == "Netscape") {
var lang = navigator.language.toLowerCase(); //获取浏览器配置语言,支持非IE浏览器
} else {
var lang = navigator.browserLanguage.toLowerCase(); //获取浏览器配置语言,支持IE5+
};
console.log(lang);
var lang = lang.substr(0, 5); //获取浏览器配置语言前4位
console.log(lang);
/*
极速测试体验,用于审查元素时直接执行的
1. 随便打开一个网页
2. 右键-审查元素
3. 粘贴入一下代码:
var head= document.getElementsByTagName('head')[0]; var script= document.createElement('script'); script.type= 'text/javascript'; script.src= 'http://localhost:8060/static/inspector.js'; head.appendChild(script);
4. Enter 回车键 , 执行
5. 在当前网页的左上角,就出现了一个大大的切换语言了
使用的是 v2.x 版本进行的翻译
*/
var head= document.getElementsByTagName('head')[0];
var script= document.createElement('script');
script.type= 'text/javascript';
script.src= 'http://localhost:8060/static/translate.js';
script.onload = script.onreadystatechange = function() {
translate.storage.set('to','');
//设置使用v2.x 版本
translate.setUseVersion2();
//SELECT 修改 onchange 事件
translate.selectLanguageTag.selectOnChange = function(event){
//判断是否是第一次翻译,如果是,那就不用刷新页面了。 true则是需要刷新,不是第一次翻译
var isReload = translate.to != null && translate.to.length > 0;
if(isReload){
//如果要刷新页面的话,弹出友好提示
alert('您好,快速体验暂时只能切换其中一种语言进行体验,只是提供效果展示,您可参考接入文档来接入您的项目中进行完整体验及使用。');
}else{
var language = event.target.value;
translate.changeLanguage(language);
console.log("ttttt");
console.log(language);
}
}
translate.listener.start(); //开启html页面变化的监控,对变化部分会进行自动翻译。注意,这里变化区域,是指使用 translate.setDocuments(...) 设置的区域。如果未设置,那么为监控整个网页的变化
translate.execute();
document.getElementById('translate').style.position = 'fixed';
document.getElementById('translate').style.color = 'red';
document.getElementById('translate').style.left = '10px';
document.getElementById('translate').style.top = '10px';
document.getElementById('translate').style.zIndex = '9999999999999';
setInterval(function() {
try{
if(document.getElementById('translateSelectLanguage') == null){
return;
}
document.getElementById('translateSelectLanguage').style.fontSize = '2rem';
document.getElementById('translateSelectLanguage').style.borderWidth = '0.5rem';
document.getElementById('translateSelectLanguage').style.borderColor = 'red';
}catch(e){
//select数据是通过接口返回的
}
},1000);
}
head.appendChild(script);
</script>
不同页面或者不同 js 之间传递数据,粗暴简单一点可以通过如下方式:
window.localStorage.setItem('local_language',translate.language.local);
通过对前端js和后端翻译服务进行了长时间的研究和测试,最终实现的效果可以达到:
1)如果是自己的网站
在网页最末尾, 之前,加入以下代码,一般在页面的最底部就出现了选择语言的 select 切换标签。 其实就这么简单:
<script src="http://localhost:8060/static/translate.min.js"></script>
<script>
// translate.language.setLocal('chinese_simplified');
translate.request.api.host='http://localhost:8060/';
translate.execute();
</script>
或者:
<script src="http://localhost:8060/static/i18n.js"></script>
2)如果是第三方的网站
随便打开一个网页
右键 - 检查(审查元素)〉控制台:
粘贴入以下代码:
var head= document.getElementsByTagName('head')[0]; var script= document.createElement('script'); script.type= 'text/javascript'; script.src= 'http://localhost:8060/static/inspector.js'; head.appendChild(script);
或者:
var head= document.getElementsByTagName('head')[0]; var script= document.createElement('script'); script.type= 'text/javascript'; script.src= 'http://localhost:8060/static/inspector.js'; head.appendChild(script);
Enter 回车键 , 执行
在当前网页的左上角,就出现了一个大大的切换语言,切换试试看。
效果参考:
https://blog.csdn.net/u014374009/article/details/135401003
源码及后端翻译服务支持docker一键部署:
https://blog.csdn.net/u014374009/article/details/135370222
有任何定制化的需求可以联系作者完成开发。