PageSourceSearch

https://hotelsilverland.com/zb_users/theme/zbseven/script/translate.js

js hotelsilverland.com collected 2026-10-05 04:11:03 UTC 233,709 bytes, 5,512 lines download raw bytes

1/*
2
3	国际化,网页自动翻译。
4	作者:管雷鸣
5	开原仓库:https://github.com/xnx3/translate
6
7 */ 
8var translate = {
9	/**
10	 * 当前的版本
11	 * 由 npm 脚本自动更新,无需手动修改
12	 * 格式:major.minor.patch.date
13	 */
14	// AUTO_VERSION_START
15  version: '3.12.0.20241210',
16  // AUTO_VERSION_END
17	/*
18		当前使用的版本,默认使用v2. 可使用 setUseVersion2(); 
19		来设置使用v2 ,已废弃,主要是区分是否是v1版本来着,v2跟v3版本是同样的使用方式
20	*/
21	useVersion:'v2',	
22	setUseVersion2:function(){
23		translate.useVersion = 'v2';
24		console.log('提示:自 v2.10 之后的版本默认就是使用V2版本(当前版本为:'+translate.version+'), translate.setUseVersion2() 可以不用再加这一行了。当然加了也无所谓,只是加了跟不加是完全一样的。');
25	},
26	/*
27	 * 翻译的对象,也就是 new google.translate.TranslateElement(...)
28	 * 已废弃,v1使用的
29	 */
30	translate:null,
31	/*
32	 * 支持哪些语言切换,包括:de,hi,lt,hr,lv,ht,hu,zh-CN,hy,uk,mg,id,ur,mk,ml,mn,af,mr,uz,ms,el,mt,is,it,my,es,et,eu,ar,pt-PT,ja,ne,az,fa,ro,nl,en-GB,no,be,fi,ru,bg,fr,bs,sd,se,si,sk,sl,ga,sn,so,gd,ca,sq,sr,kk,st,km,kn,sv,ko,sw,gl,zh-TW,pt-BR,co,ta,gu,ky,cs,pa,te,tg,th,la,cy,pl,da,tr
33	 * 已废弃,请使用 translate.selectLanguageTag.languages 
34	 */
35	includedLanguages:'zh-CN,zh-TW,en',
36	/*
37	 * 资源文件url的路径
38	 * 已废弃,v1使用的
39 	 */
40	resourcesUrl:'//res.zvo.cn/translate',
41
42	/**
43	 * 默认出现的选择语言的 select 选择框,可以通过这个选择切换语言。
44	 */
45	selectLanguageTag:{
46		/*
47			v3.1 增加,将 select切换语言的选择框赋予哪个id,这里是具体的id的名字。
48			如果这个id不存在,会创建这个id的元素
49		*/
50		documentId:'translate',
51		/* 是否显示 select选择语言的选择框,true显示; false不显示。默认为true */
52		show:true,
53		/* 
54			支持哪些语言切换
55			v1.x 版本包括:de,hi,lt,hr,lv,ht,hu,zh-CN,hy,uk,mg,id,ur,mk,ml,mn,af,mr,uz,ms,el,mt,is,it,my,es,et,eu,ar,pt-PT,ja,ne,az,fa,ro,nl,en-GB,no,be,fi,ru,bg,fr,bs,sd,se,si,sk,sl,ga,sn,so,gd,ca,sq,sr,kk,st,km,kn,sv,ko,sw,gl,zh-TW,pt-BR,co,ta,gu,ky,cs,pa,te,tg,th,la,cy,pl,da,tr 
56			v2.x 版本根据后端翻译服务不同,支持的语言也不同。具体支持哪些,可通过 http://api.translate.zvo.cn/doc/language.json.html 获取 (如果您私有部署的,将请求域名换为您自己私有部署的域名)
57		*/
58		languages:'',
59		alreadyRender:false, //当前是否已渲染过了 true为是 v2.2增加
60		selectOnChange:function(event){
61			var language = event.target.value;
62			translate.changeLanguage(language);
63		},
64		//重新绘制 select 语种下拉选择。比如进行二次开发过translate.js,手动进行了设置 translate.to ,但是手动改动后的,在select语种选择框中并不会自动进行改变,这是就需要手动重新绘制一下 select语种选择的下拉选择框
65		refreshRender:function(){
66			// 获取元素
67			let element = document.getElementById(translate.selectLanguageTag.documentId+"SelectLanguage");
68
69			// 删除元素
70			if (element) {
71				element.parentNode.removeChild(element);
72			}
73
74			//设置为未 render 状态,允许进行 render
75			translate.selectLanguageTag.alreadyRender = false;	
76
77			translate.selectLanguageTag.render();
78		},
79
80		/*
81			自定义切换语言的样式渲染 v3.2.4 增加
82			
83		*/
84		customUI:function(languageList){
85			//select的onchange事件
86			var onchange = function(event){ translate.selectLanguageTag.selectOnChange(event); }
87			
88			//创建 select 标签
89			var selectLanguage = document.createElement("select"); 
90			selectLanguage.id = translate.selectLanguageTag.documentId+'SelectLanguage';
91			selectLanguage.className = translate.selectLanguageTag.documentId+'SelectLanguage';
92			for(var i = 0; i<languageList.length; i++){
93				var option = document.createElement("option"); 
94			    option.setAttribute("value",languageList[i].id);
95
96			    //判断 selectLanguageTag.languages 中允许使用哪些
97
98				if(translate.selectLanguageTag.languages.length > 0){
99					//设置了自定义显示的语言
100
101					//都转小写判断
102					var langs_indexof = (','+translate.selectLanguageTag.languages+',').toLowerCase();
103					//console.log(langs_indexof)
104					if(langs_indexof.indexOf(','+languageList[i].id.toLowerCase()+',') < 0){
105						//没发现,那不显示这个语种,调出
106						continue
107					}
108				}
109
110				/*判断默认要选中哪个语言*/
111			    if(translate.to != null && typeof(translate.to) != 'undefined' && translate.to.length > 0){
112					//设置了目标语言,那就进行判断显示目标语言
113					
114					if(translate.to == languageList[i].id){
115						option.setAttribute("selected",'selected');
116					}
117			    }else{
118					//没设置目标语言,那默认选中当前本地的语种
119					if(languageList[i].id == translate.language.getLocal()){
120						option.setAttribute("selected",'selected');
121					}
122				}
123				
124			    option.appendChild(document.createTextNode(languageList[i].name)); 
125			    selectLanguage.appendChild(option);
126			}
127			//增加 onchange 事件
128			if(window.addEventListener){ // Mozilla, Netscape, Firefox 
129				selectLanguage.addEventListener('change', onchange,false); 
130			}else{ // IE 
131				selectLanguage.attachEvent('onchange',onchange); 
132			} 
133
134			//将select加入进网页显示
135			document.getElementById(translate.selectLanguageTag.documentId).appendChild(selectLanguage);
136				
137		},
138		render:function(){ //v2增加
139			if(translate.selectLanguageTag.alreadyRender){
140				return;
141			}
142			translate.selectLanguageTag.alreadyRender = true;
143			
144			//判断如果不显示select选择语言,直接就隐藏掉
145			if(!translate.selectLanguageTag.show){
146				return;
147			}
148			
149			//判断translate 的id是否存在,不存在就创建一个
150			if(document.getElementById(translate.selectLanguageTag.documentId) == null){
151				var body_trans = document.getElementsByTagName('body')[0];
152				var div = document.createElement("div");  //创建一个script标签
153				div.id=translate.selectLanguageTag.documentId;
154				body_trans.appendChild(div);
155			}else{
156				//存在,那么判断一下 select是否存在,要是存在就不重复创建了
157				if(document.getElementById(translate.selectLanguageTag.documentId+'SelectLanguage') != null){
158					//select存在了,就不重复创建了
159					return;
160				}
161			}
162
163			//从服务器加载支持的语言库
164			translate.request.post(translate.request.api.language, {}, function(data){
165				if(data.result == 0){
166					console.log('load language list error : '+data.info);
167					return;
168				}
169				
170				translate.selectLanguageTag.customUI(data.list);
171
172				/*
173				try{
174					document.getElementById('translateSelectLanguage').style.width = '94px';
175				}catch(e){ console.log(e);} 
176				*/
177			});
178			
179			
180		}
181	},
182	
183	/*
184	 * 当前本地语言
185	 * 已废弃,v1使用的
186	 */
187	//localLanguage:'zh-CN',
188	localLanguage:'zh-CN',
189	
190	/**
191	 * google翻译执行的
192	 * 已废弃,v1使用的
193	 */
194	googleTranslateElementInit:function(){
195		var selectId = '';
196		if(document.getElementById('translate') != null){	// && document.getElementById('translate').innerHTML.indexOf('translateSelectLanguage') > 0
197			//已经创建过了,存在
198			selectId = 'translate';
199		}
200		
201		translate.translate = new google.translate.TranslateElement(
202			{
203				//这参数没用,请忽略
204				pageLanguage: 'zh-CN',
205				//一共80种语言选择,这个是你需要翻译的语言,比如你只需要翻译成越南和英语,这里就只写en,vi
206				//includedLanguages: 'de,hi,lt,hr,lv,ht,hu,zh-CN,hy,uk,mg,id,ur,mk,ml,mn,af,mr,uz,ms,el,mt,is,it,my,es,et,eu,ar,pt-PT,ja,ne,az,fa,ro,nl,en-GB,no,be,fi,ru,bg,fr,bs,sd,se,si,sk,sl,ga,sn,so,gd,ca,sq,sr,kk,st,km,kn,sv,ko,sw,gl,zh-TW,pt-BR,co,ta,gu,ky,cs,pa,te,tg,th,la,cy,pl,da,tr',
207	            includedLanguages: translate.selectLanguageTag.languages,
208				//选择语言的样式,这个是面板,还有下拉框的样式,具体的记不到了,找不到api~~  
209				layout: 0,
210				//自动显示翻译横幅,就是翻译后顶部出现的那个,有点丑,设置这个属性不起作用的话,请看文章底部的其他方法
211				//autoDisplay: false, 
212				//disableAutoTranslation:false,
213				//还有些其他参数,由于原插件不再维护,找不到详细api了,将就了,实在不行直接上dom操作
214			}, 
215			selectId //触发按钮的id
216		);
217	},
218	
219	/**
220	 * 初始化,如加载js、css资源
221	 * 已废弃,v1使用的
222	 */
223	/* v2.11.11.20240124 彻底注释掉,有新的init方法替代
224	init:function(){
225		var protocol = window.location.protocol;
226		if(window.location.protocol == 'file:'){
227			//本地的,那就用http
228			protocol = 'http:';
229		}
230		if(this.resourcesUrl.indexOf('://') == -1){
231			//还没设置过,进行设置
232			this.resourcesUrl = protocol + this.resourcesUrl;
233		}
234		
235		//this.resourcesUrl = 'file://G:/git/translate';
236		
237	},
238	*/
239	/**
240	 * 执行翻译操作
241	 * 已废弃,v1使用的
242	 */
243	execute_v1:function(){
244		console.log('=====ERROR======');
245		console.log('The v1 version has been discontinued since 2022. Please use the latest V3 version and refer to: http://translate.zvo.cn/41162.html');
246	},
247	
248	/**
249	 * 设置Cookie,失效时间一年。
250	 * @param name
251	 * @param value
252	 * * 已废弃,v1使用的
253	 */
254	setCookie:function (name,value){
255		var cookieString=name+"="+escape(value); 
256		document.cookie=cookieString; 
257	},
258
259	//获取Cookie。若是不存再,返回空字符串
260	//* 已废弃,v1使用的
261	getCookie:function (name){ 
262		var strCookie=document.cookie; 
263		var arrCookie=strCookie.split("; "); 
264		for(var i=0;i<arrCookie.length;i++){ 
265			var arr=arrCookie[i].split("="); 
266			if(arr[0]==name){
267				return unescape(arr[1]);
268			}
269		}
270		return "";
271	},
272	
273	/**
274	 * 获取当前页面采用的是什么语言
275	 * 返回值如 en、zh-CN、zh-TW (如果是第一次用,没有设置过,那么返回的是 translate.localLanguage 设置的值)		
276	 * * 已废弃,v1使用的
277	 */
278	currentLanguage:function(){
279		//translate.check();
280		var cookieValue = translate.getCookie('googtrans');
281		if(cookieValue.length > 0){
282			return cookieValue.substr(cookieValue.lastIndexOf('/')+1,cookieValue.length-1);
283		}else{
284			return translate.localLanguage;
285		}
286	},
287	
288	/**
289	 * 切换语言,比如切换为英语、法语
290 	 * @param languageName 要切换的语言语种。传入如 english
291	 * 				会自动根据传入的语言来判断使用哪种版本。比如传入 en、zh-CN 等,则会使用v1.x版本
292	 * 														传入 chinese_simplified 、english 等,则会使用 v2.x版本
293	 */
294	changeLanguage:function(languageName){
295		//判断使用的是否是v1.x
296		var v1 = ',en,de,hi,lt,hr,lv,ht,hu,zh-CN,hy,uk,mg,id,ur,mk,ml,mn,af,mr,uz,ms,el,mt,is,it,my,es,et,eu,ar,pt-PT,ja,ne,az,fa,ro,nl,en-GB,no,be,fi,ru,bg,fr,bs,sd,se,si,sk,sl,ga,sn,so,gd,ca,sq,sr,kk,st,km,kn,sv,ko,sw,gl,zh-TW,pt-BR,co,ta,gu,ky,cs,pa,te,tg,th,la,cy,pl,da,tr,';
297		if(v1.indexOf(','+languageName+',') > -1){
298			//用的是v1.x
299			console.log('您使用的是v1版本的切换语种方式,v1已在2021年就以废弃,请更换为v2,参考文档: http://translate.zvo.cn/41549.html');
300			translate.check();
301			
302			var googtrans = '/'+translate.localLanguage+'/'+languageName;
303			
304			//先清空泛解析域名的设置
305			var s = document.location.host.split('.');
306			if(s.length > 2){
307				var fanDomain = s[s.length-2]+'.'+s[s.length-1];
308				document.cookie = 'googtrans=;expires='+(new Date(1))+';domain='+fanDomain+';path=/';
309				document.cookie = 'googtrans='+googtrans+';domain='+fanDomain+';path=/';
310			}
311			
312			translate.setCookie('googtrans', ''+googtrans);
313			translate.refreshCurrentPage();
314			return;
315		}
316		
317		//用的是v2.x或更高
318		//translate.setUseVersion2();
319		translate.useVersion = 'v2';
320		//判断是否是第一次翻译,如果是,那就不用刷新页面了。 true则是需要刷新,不是第一次翻译
321		if(translate.to != null && translate.to.length > 0){
322			//当前目标值有值,且目标语言跟当前语言不一致,那当前才是已经被翻译过的
323			if(translate.to != translate.language.getLocal()){
324				var isReload = true; //标记要刷新页面
325			}
326		}
327		
328		
329		translate.to = languageName;
330		translate.storage.set('to',languageName);	//设置目标翻译语言
331		
332		if(isReload){
333			location.reload(); //刷新页面
334		}else{
335			//不用刷新,直接翻译
336			translate.execute(); //翻译
337		}
338	},
339	
340	/**
341	 * 自检提示,适用于 v1.x, 在 v2.x中已废弃
342	 * english
343	 * 已废弃,v1使用的
344	 */
345	check:function(){
346		if(window.location.protocol == 'file:'){
347			console.log('\r\n---WARNING----\r\ntranslate.js 主动翻译组件自检异常,当前协议是file协议,翻译组件要在正常的线上http、https协议下才能正常使用翻译功能\r\n------------');
348		}
349	},
350	
351	
352	/**************************** v2.0 */
353	to:'', //翻译为的目标语言,如 english 、chinese_simplified
354	//用户第一次打开网页时,自动判断当前用户所在国家使用的是哪种语言,来自动进行切换为用户所在国家的语种。
355	//如果使用后,第二次在用,那就优先以用户所选择的为主,这个就不管用了
356	//默认是false,不使用,可设置true:使用
357	//使用 setAutoDiscriminateLocalLanguage 进行设置
358	autoDiscriminateLocalLanguage:false,
359	documents:[], //指定要翻译的元素的集合,可设置多个,如设置: document.getElementsByTagName('DIV')
360	/*
361		v2.11.5增加
362		正在进行翻译的节点,会记录到此处。
363		这里是最底的节点了,不会再有下级了。这也就是翻译的最终节点,也就是 translate.element.findNode() 发现的节点
364		也就是扫描到要进行翻译的节点,在翻译前,加入到这里,在这个节点翻译结束后,将这里面记录的节点删掉。
365		
366		格式如 
367			[
368				{
369					node: node节点的对象
370					number: 2 (当前正在翻译进行中的次数,比如一个节点有中英文混合的文本,那么中文、英文 会同时进行两次翻译,也就是最后要进行两次替换,会导致这个node产生两次改动。每次便是+1、-1)
371				},
372				{
373					......
374				}
375			]
376
377		生命周期:
378		
379		translate.execute() 执行后,会扫描要翻译的字符,扫描完成后首先会判断缓存中是否有,是否会命中缓存,如果缓存中有,那么在加入 task.add 之前就会将这个进行记录 ++ 
380		在浏览器缓存没有命中后,则会通过网络api请求进行翻译,此时在发起网络请求前,会进行记录 ++
381		当使用 translate.listener.start() 后,网页中动态渲染的部分会触发监听,触发监听后首先会判断这个节点是否存在于这里面正在被翻译,如果存在里面,那么忽略, 如果不存在里面,那么再进行 translate.execute(变动的节点) 进行翻译 (当然执行这个翻译后,自然也就又把它加入到此处进行记录 ++)
382		【唯一的减去操作】 在task.execute() 中,翻译完成并且渲染到页面执行完成后,会触发延迟50毫秒后将这个翻译的节点从这里减去
383	*/
384	inProgressNodes:[], 
385	//翻译时忽略的一些东西,比如忽略某个tag、某个class等
386	ignore:{
387		tag:['style', 'script', 'link', 'pre', 'code'],
388		class:['ignore','translateSelectLanguage'],
389		id:[],
390		/*
391			传入一个元素,判断这个元素是否是被忽略的元素。 这个会找父类,看看父类中是否包含在忽略的之中。
392			return true是在忽略的之中,false不再忽略的之中
393		*/
394		isIgnore:function(ele){
395			if(ele == null || typeof(ele) == 'undefined'){
396				return false;
397			}
398
399			var parentNode = ele;
400			var maxnumber = 100;	//最大循环次数,避免死循环
401			while(maxnumber-- > 0){
402				if(parentNode == null || typeof(parentNode) == 'undefined'){
403					//没有父元素了
404					return false;
405				}
406
407				//判断Tag
408				//var tagName = parentNode.nodeName.toLowerCase(); //tag名字,小写
409				var nodename = translate.element.getNodeName(parentNode).toLowerCase(); //tag名字,小写
410				if(nodename.length > 0){
411					//有nodename
412					if(nodename == 'body' || nodename == 'html' || nodename == '#document'){
413						//上层元素已经是顶级元素了,那肯定就不是了
414						return false;
415					}
416					if(translate.ignore.tag.indexOf(nodename) > -1){
417						//发现ignore.tag 当前是处于被忽略的 tag
418						return true;
419					}
420				}
421				
422
423				//判断class name
424				if(parentNode.className != null){
425					var classNames = parentNode.className;
426					if(classNames == null || typeof(classNames) != 'string'){
427						continue;
428					}
429					//console.log('className:'+typeof(classNames));
430					//console.log(classNames);
431					classNames = classNames.trim().split(' ');
432					for(var c_index = 0; c_index < classNames.length; c_index++){
433						if(classNames[c_index] != null && classNames[c_index].trim().length > 0){
434							//有效的class name,进行判断
435							if(translate.ignore.class.indexOf(classNames[c_index]) > -1){
436								//发现ignore.class 当前是处于被忽略的 class
437								return true;
438							}
439						}
440					}					
441				}
442
443				//判断id
444				if(parentNode.id != null && typeof(parentNode.id) != 'undefined'){
445					//有效的class name,进行判断
446					if(translate.ignore.id.indexOf(parentNode.id) > -1){
447						//发现ignore.id 当前是处于被忽略的 id
448						return true;
449					}
450				}
451
452				//赋予判断的元素向上一级
453				parentNode = parentNode.parentNode;
454			}
455
456			return false;
457		},
458
459		/*
460		 * 忽略不被翻译的文本,这里出现的文本将不会被翻译。
461		 * 这个其实是借用了 自定义术语 的能力,设置了自定义术语的原字符等于翻译后的字符, 于是这个字符就不会被翻译了
462		 * 这里可以是多个,数组,如 ['你好','世界']
463		 */
464		text:[]
465
466	},
467	//刷新页面,你可以自定义刷新页面的方式,比如在 uniapp 打包生成 apk 时,apk中的刷新页面就不是h5的这个刷新,而是app的刷新方式,就需要自己进行重写这个刷新页面的方法了
468	refreshCurrentPage:function(){
469		location.reload();
470	},
471	//自定义翻译术语
472	nomenclature:{
473		/*
474			术语表
475			一维:要转换的语种,如 english
476			二维:翻译至的目标语种,如 english
477			三维:要转换的字符串,如 "你好"
478			结果:自定义的翻译结果,如 “Hallo”
479		*/
480		data:new Array(),
481		
482		/*
483			原始术语表,可编辑的
484			一维:要自定义目标词
485			二维:针对的是哪个语种
486			值:要翻译为什么内容
487
488			其设置如 
489			var data = new Array();
490			data['版本'] = {
491				english : 'banben',
492				korean : 'BanBen'
493			};
494			data['国际化'] = {
495				english : 'guojihua',
496				korean : 'GuoJiHua'
497			};
498			
499			【已过时】
500		*/
501		old_Data:[],
502		/*
503		set:function(data){
504			translate.nomenclature.data = data;
505		},
506		*/
507		set:function(data){
508			alert('请将 translate.nomenclature.set 更换为 append,具体使用可参考: https://github.com/xnx3/translate ');
509		},
510		/*
511			向当前术语库中追加自定义术语。如果追加的数据重复,会自动去重
512			传入参数:
513				from 要转换的语种
514				to 翻译至的目标语种
515				properties 属于配置表,格式如:
516						你好=Hello
517						世界=ShiJie
518
519		*/
520		append:function(from, to, properties){
521			if(typeof(translate.nomenclature.data[from]) == 'undefined'){
522				translate.nomenclature.data[from] = new Array();
523			}
524			if(typeof(translate.nomenclature.data[from][to]) == 'undefined'){
525				translate.nomenclature.data[from][to] = new Array();
526			}
527			
528			//将properties进行分析
529			//按行拆分
530			var line = properties.split('\n');
531			//console.log(line)
532			for(var line_index = 0; line_index < line.length; line_index++){
533				var item = line[line_index].trim();
534				if(item.length < 1){
535					//空行,忽略
536					continue;
537				}
538				var kvs = item.split('=');
539				//console.log(kvs)
540				if(kvs.length != 2){
541					//不是key、value构成的,忽略
542					continue;
543				}
544				var key = kvs[0].trim();
545				var value = kvs[1].trim();
546				//console.log(key)
547				if(key.length == 0 || value.length == 0){
548					//其中某个有空,则忽略
549					continue;
550				}
551
552
553				//加入,如果之前有加入,则会覆盖
554				translate.nomenclature.data[from][to][key] = value;
555				//console.log(local+', '+target+', key:'+key+', value:'+value);
556			}
557
558			//追加完后,对整个对象数组进行排序,key越大越在前面
559			translate.nomenclature.data[from][to] = translate.util.objSort(translate.nomenclature.data[from][to]);
560
561		},
562		//获取当前定义的术语表
563		get:function(){
564			return translate.nomenclature.data;
565		},
566		//对传入的str字符进行替换,将其中的自定义术语提前进行替换,然后将替换后的结果返回
567		//v3.11 后此方法已废弃,不再使用
568		dispose:function(str){
569			if(str == null || str.length == 0){
570				return str;
571			}
572			//if(translate.nomenclature.data.length == 0){
573			//	return str;
574			//}
575			//判断当前翻译的两种语种是否有自定义术语库
576			//console.log(typeof(translate.nomenclature.data[translate.language.getLocal()][translate.to]))
577			if(typeof(translate.nomenclature.data[translate.language.getLocal()]) == 'undefined' || typeof(translate.nomenclature.data[translate.language.getLocal()][translate.to]) == 'undefined'){
578				return str;
579			}
580			//console.log(str)
581			for(var originalText in translate.nomenclature.data[translate.language.getLocal()][translate.to]){
582				var translateText = translate.nomenclature.data[translate.language.getLocal()][translate.to][originalText];
583				if(typeof(translateText) == 'function'){
584					//进行异常的预处理调出
585					continue;
586				}
587
588				var index = str.indexOf(originalText);
589				if(index > -1){
590					//console.log('find -- '+originalText+', \t'+translateText);
591					if(translate.language.getLocal() == 'english'){
592						//如果本地语种是英文,那么还要判断它的前后,避免比如要替换 is 将 display 中的is给替换,将单词给强行拆分了
593						
594						//判断这个词前面是否符合
595						var beforeChar = '';	//前面的字符
596						if(index == 0){
597							//前面没别的字符了,那前面合适
598						}else{
599							//前面有别的字符,判断是什么字符,如果是英文,那么这个是不能被拆分的,要忽略
600							beforeChar = str.substr(index-1,1);
601							//console.log('beforeChar:'+beforeChar+', str:'+str)
602							var lang = translate.language.getCharLanguage(beforeChar);
603							//console.log(lang);
604							if(lang == 'english'){
605								//调出,不能强拆
606								continue;
607							}
608						}
609
610						//判断这个词的后面是否符合
611						var afterChar = ''; //后面的字符
612						if(index + originalText.length == str.length ){
613							//后面没别的字符了,那前面合适
614							//console.log(originalText+', meile '+str)
615						}else{
616							//后面有别的字符,判断是什么字符,如果是英文,那么这个是不能被拆分的,要忽略
617							afterChar = str.substr(index+originalText.length,1);
618							var lang = translate.language.getCharLanguage(afterChar);
619							if(lang == 'english'){
620								//跳出,不能强拆
621								continue;
622							}
623						}
624
625						str = str.replace(new RegExp(beforeChar+originalText+afterChar,'g'), beforeChar+translateText+afterChar);
626					}else{
627						//其他情况,如汉语、汉语等语种
628						str = str.replace(new RegExp(originalText,'g'), translateText);
629					}
630
631				}
632			}
633
634			return str;
635
636			/*
637			//遍历一维
638			for(var originalText in translate.nomenclature.data){
639				var languageResult = translate.nomenclature.data[originalText];
640				if(typeof(languageResult) == 'function'){
641					//进行异常的预处理调出
642					continue;
643				}
644
645				if(typeof(languageResult[translate.to]) == 'undefined'){
646					//console.log('und');
647					continue;
648				}
649
650				//var hash = translate.util.hash(originalText);
651
652				//console.log(originalText+',\t'+str);
653				if(str.indexOf(originalText) > -1){
654					//console.log('find -- '+originalText+', \t'+languageResult[translate.to]);
655					str = str.replace(new RegExp(originalText,'g'),languageResult[translate.to]);
656				}
657			}
658			
659			
660			return str;
661			*/
662		},
663		
664	},
665
666	office:{
667		/*
668			网页上翻译之后,自动导出当前页面的术语库
669			
670			需要先指定本地语种,会自动将本地语种进行配置术语库
671			
672		*/
673		export:function(){
674			if(translate.language.getLocal() == translate.language.getCurrent()){
675				alert('本地语种跟要翻译的语种一致,无需导出');
676				return;
677			}
678
679			var text = '';
680			for(var uuid in translate.nodeQueue){
681				var queueValue = translate.nodeQueue[uuid];
682				for(var lang in translate.nodeQueue[uuid].list){
683					//console.log('------'+lang)
684					if(typeof(lang) != 'string' || lang.length < 1){
685						continue;
686					}
687					//if(translate.language.getLocal() == lang){
688						//console.log(translate.nodeQueue[uuid].list[lang]);
689						for(var hash in translate.nodeQueue[uuid].list[lang]){
690							//console.log(translate.nodeQueue[uuid].list[lang][hash].original);
691							//console.log(translate.nodeQueue[uuid].list[lang][hash].original);
692							text = text + '\n' + translate.nodeQueue[uuid].list[lang][hash].original + '='+translate.storage.get('hash_'+translate.language.getCurrent()+'_'+hash
692);
693							
694						}
695					//}
696				}
697				
698			}
699
700			if(text.length > 0){
701				//有内容
702				text = 'translate.office.append(\''+translate.language.getCurrent()+'\',`'+text+'\n`);';
703				//console.log(text);
704				translate.util.loadMsgJs();
705				msg.popups({
706				    text:'<textarea id="msgPopupsTextarea" style="width:100%; height:100%; color: black; padding: 8px;">loaing...</textarea>',
707				    width:'750px',
708				    height:'600px',
709				    padding:'1px',
710				});	
711				document.getElementById('msgPopupsTextarea').value = text;
712			}else{
713				msg.alert('无有效内容!');
714			}
715
716
717		},
718		//显示导出面板
719		showPanel:function(){
720			let panel = document.createElement('div');
721			panel.setAttribute('id', 'translate_export');
722			panel.setAttribute('class','ignore');
723
724			//导出按钮
725			let button = document.createElement('button');
726			button.onclick = function() {
727			  translate.office.export();
728			};
729			button.innerHTML = '导出配置信息';
730			button.setAttribute('style', 'margin-left: 72px; margin-top: 30px; margin-bottom: 20px; font-size: 25px; background-color: blue; padding: 15px; padding-top: 3px; padding-bottom: 3px; border-radius: 3px;');
731			panel.appendChild(button);
732
733			//说明文字
734			let textdiv = document.createElement('div');
735			textdiv.innerHTML = '1. 首先将当前语种切换为你要翻译的语种<br/>2. 点击导出按钮,将翻译的配置信息导出<br/>3. 将导出的配置信息粘贴到代码中,即可完成<br/><a href="asd" target="_black" style="color: aliceblue;">点此进行查阅详细使用说明</a>';
736			textdiv.setAttribute('style','font-size: 14px; padding: 12px;');
737
738			panel.appendChild(textdiv);			
739			
740			panel.setAttribute('style', 'background-color: black; color: #fff; width: 320px; height: 206px; position: fixed; bottom: 50px; right: 50px;');
741			//把元素节点添加到body元素节点中成为其子节点,放在body的现有子节点的最后
742			document.body.appendChild(panel);
743
744			translate.util.loadMsgJs();
745		},
746		/*
747			追加离线翻译数据。如果追加的数据重复,会自动去重
748			传入参数:
749				from 要转换的语种
750				to 翻译至的目标语种
751				properties 属于配置表,格式如:
752						你好=Hello
753						世界=ShiJie
754			这个传入参数跟 translate.nomenclature.append 的传入参数格式是一致的			
755		*/
756		append:function(to, properties){
757			//console.log(properties)
758			//将properties进行分析
759			//按行拆分
760			var line = properties.split('\n');
761			//console.log(line)
762			for(var line_index = 0; line_index < line.length; line_index++){
763				var item = line[line_index].trim();
764				if(item.length < 1){
765					//空行,忽略
766					continue;
767				}
768				var kvs = item.split('=');
769				//console.log(kvs)
770				if(kvs.length != 2){
771					//不是key、value构成的,忽略
772					continue;
773				}
774				var key = kvs[0];
775				var value = kvs[1];
776				//console.log(key)
777				if(key.length == 0 || value.length == 0){
778					//其中某个有空,则忽略
779					continue;
780				}
781				//console.log('set---'+key);
782				//加入 storate
783				translate.storage.set('hash_'+to+'_'+translate.util.hash(key), value);
784			}
785		},
786	},
787	setAutoDiscriminateLocalLanguage:function(){
788		translate.autoDiscriminateLocalLanguage = true;
789	},
790	/*
791		待翻译的页面的node队列
792		一维:key:uuid,也就是execute每次执行都会创建一个翻译队列,这个是翻译队列的唯一标识。   
793			 value:
794				k/v 
795		二维:对象形态,具体有:
796			 key:expireTime 当前一维数组key的过期时间,到达过期时间会自动删除掉这个一维数组。如果<0则代表永不删除,常驻内存
797			 value:list 待翻译的页面的node队列
798		三维:针对二维的value,  key:english、chinese_simplified等语种,这里的key便是对value的判断,取value中的要翻译的词是什么语种,对其进行了语种分类    value: k/v
799		四维:针对三维的value,  key:要翻译的词(经过语种分割的)的hash,   value: node数组
800		五维:针对四维的value,  这是个对象, 其中
801				original: 是三维的key的hash的原始文字,也就是 node 中的原始文字。
802				cacheHash: 如果翻译时匹配到了自定义术语库中的词,那么翻译完后存入到缓存中时,其缓存的翻译前字符串已经不是original,而是匹配完术语库后的文本的hash了。所以这里额外多增加了这个属性。如果匹配了术语库,那这里就是要进行缓存的翻译前文本的hash,如果未使用术语库,这里就跟其key-hash 相同。
803				translateText: 针对 original 的经过加工过的文字,比如经过自定义术语操作后的,待翻译的文字。
804				nodes: 有哪些node元素中包含了这个词,都会在这里记录
805		六维:针对五维的 nodes,将各个具体的 node 以及 其操作的 attribute 以数组形式列出
806		七维:针对六维列出的nodes数组,其中包含:
807				node: 具体操作的node元素
808				attribute: 也就是翻译文本针对的是什么,是node本身(nodeValue),还是 node 的某个属性,比如title属性,这则是设置为 "title"。如果这里不为空,那就是针对的属性操作的。 如果这里为空或者undefined ,那就是针对node本身,也就是 nodeValue 的字符串操作的
809				beforeText: node元素中进行翻译结果赋予时,额外在翻译结果的前面加上的字符串。其应用场景为,如果中英文混合场景下,避免中文跟英文挨着导致翻译为英语后,连到一块了。默认是空字符串 ''
810				afterText:  node元素中进行翻译结果赋予时,额外在翻译结果的后面加上的字符串。其应用场景为,如果中英文混合场景下,避免中文跟英文挨着导致翻译为英语后,连到一块了。默认是空字符串 ''
811
812		生命周期: 当execute()执行时创建,  当execute结束(其中的所有request接收到响应并渲染完毕)时销毁(当前暂时不销毁,以方便调试)
813	*/
814	nodeQueue:{},
815	//指定要翻译的元素的集合,可传入一个元素或多个元素
816	//如设置一个元素,可传入如: document.getElementById('test')
817	//如设置多个元素,可传入如: document.getElementsByTagName('DIV')
818	setDocuments:function(documents){
819		if (documents == null || typeof(documents) == 'undefined') {
820			return;
821		}
822		
823		if(typeof(documents.length) == 'undefined'){
824			//不是数组,是单个元素
825			translate.documents[0] = documents;
826		}else{
827			//是数组,直接赋予
828			translate.documents = documents;
829		}
830		//清空翻译队列,下次翻译时重新检索
831		translate.nodeQueue = {};
832		//console.log('set documents , clear translate.nodeQueue');
833	},
834	//获取当前指定翻译的元素(数组形式 [document,document,...])
835	//如果用户未使用setDocuments 指定的,那么返回整个网页
836	//它返回的永远是个数组形式
837	getDocuments:function(){
838		if(translate.documents != null && typeof(translate.documents) != 'undefined' && translate.documents.length > 0){
839			// setDocuments 指定的
840			return translate.documents;
841		}else{
842			//未使用 setDocuments指定,那就是整个网页了
843			//return document.all; //翻译所有的  这是 v3.5.0之前的
844			//v3.5.0 之后采用 拿 html的最上层的demo,而不是 document.all 拿到可能几千个dom
845			var doms = new Array();
846			doms[0] = document.documentElement;
847			return doms;
848		}
849	},
850	listener:{
851		//当前页面打开后,是否已经执行完execute() 方法进行翻译了,只要执行完一次,这里便是true。 (多种语言的API请求完毕并已渲染html)
852		//isExecuteFinish:false,
853		//是否已经使用了 translate.listener.start() 了,如果使用了,那这里为true,多次调用 translate.listener.start() 只有第一次有效
854		isStart:false,
855		//translate.listener.start();	//开启html页面变化的监控,对变化部分会进行自动翻译。注意,这里变化区域,是指使用 translate.setDocuments(...) 设置的区域。如果未设置,那么为监控整个网页的变化
856		start:function(){
857			
858			translate.temp_linstenerStartInterval = setInterval(function(){
859				if(document.readyState == 'complete'){
860					//dom加载完成,进行启动
861					clearInterval(translate.temp_linstenerStartInterval);//停止
862					translate.listener.addListener();
863				}
864				
865				//if(translate.listener.isExecuteFinish){ //执行完过一次,那才能使用
866					/*if(translate.listener.isStart){
867						//已开启了
868						return;
869					}*/
870					
871					//console.log('translate.temp_linstenerStartInterval Finish!');
872				//}
873	        }, 300);
874			
875			
876		},
877		//增加监听,开始监听。这个不要直接调用,需要使用上面的 start() 开启
878		addListener:function(){
879			translate.listener.isStart = true; //记录已执行过启动方法了
880			
881			// 观察器的配置(需要观察什么变动)
882			translate.listener.config = { attributes: true, childList: true, subtree: true, characterData: true, attributeOldValue:true, characterDataOldValue:true };
883			// 当观察到变动时执行的回调函数
884			translate.listener.callback = function(mutationsList, observer) {
885				var documents = []; //有变动的元素
886				//console.log('--------- lisetner 变动');
887			    // Use traditional 'for loops' for IE 11
888			    for(let mutation of mutationsList) {
889			    	let addNodes = [];
890					if (mutation.type === 'childList') {
891						if(mutation.addedNodes.length > 0){
892							//多了组件
893							addNodes = mutation.addedNodes;
894							//documents.push.apply(documents, mutation.addedNodes);
895						}else if(mutation.removedNodes.length > 0){
896							//console.log('remove:');
897							//console.log(mutation.removedNodes);
898						}else{
899							//console.log('not find:');
900							//console.log(mutation);
901						}
902					}else if (mutation.type === 'attributes') {
903						//console.log('The ' + mutation.attributeName + ' attribute was modified.');
904					}else if(mutation.type === 'characterData'){
905						//内容改变
906						addNodes = [mutation.target];
907						//documents.push.apply(documents, [mutation.target]);
908					}
909
910					//去重并加入 documents
911					for(let item of addNodes){
912						//console.log(item);
913
914						//判断是否已经加入过了,如果已经加入过了,就不重复加了
915						var isFind = false;
916						for(var di = 0; di < documents.length; di++){
917							if(documents[di].isSameNode(item)){
918								isFind = true;
919								break;
920							}
921						}
922						if(isFind){
923							break;
924						}
925						documents.push.apply(documents, [item]);
926					}
927	          	}
928			    
929				//console.log(documents.length);
930				if(documents.length > 0){
931					//有变动,需要看看是否需要翻译,延迟10毫秒执行
932					
933					//判断是否属于在正在翻译的节点,重新组合出新的要翻译的node集合
934					var translateNodes = [];
935					//console.log(translate.inProgressNodes.length);
936					for(let ipnode of documents){
937						//console.log('---type:'+ipnode.nodeType);
938
939						var find = false;
940						for(var ini = 0; ini < translate.inProgressNodes.length; ini++){
941							if(translate.inProgressNodes[ini].node.isSameNode(ipnode)){
942								//有记录了,那么忽略这个node,这个node是因为翻译才导致的变动
943								//console.log('发现相同');
944								find = true;
945								break;
946							}
947						}
948						if(find){
949							continue;
950						}
951
952						//不相同,才追加到新的 translateNodes
953						translateNodes.push(ipnode);
954						//console.log('listener ++ '+ipnode.nodeValue);
955						//console.log(ipnode);
956					}
957					if(translateNodes.length < 1){
958						return;
959					}
960					//console.log('translateNodeslength: '+translateNodes.length);
961
962
963					setTimeout(function() {
964						//console.log(translateNodes);
965						translate.execute(translateNodes); //指定要翻译的元素的集合,可传入一个或多个元素。如果不设置,默认翻译整个网页
966					}, 10); //这个要比 task.execute() 中的 settimeout 延迟执行删除 translate.inpr.....nodes 的时间要小,目的是前一个发生变动后,记入 inpr...nodes 然后翻译完成后节点发生变化又触发了listener,此时 inpr....nodes 还有,那么这个变化将不做处理,然后 inp.....nodes 再删除这个标记
967				}
968			};
969			// 创建一个观察器实例并传入回调函数
970			translate.listener.observer = new MutationObserver(translate.listener.callback);
971			// 以上述配置开始观察目标节点
972			var docs = translate.getDocuments();
973			for(var docs_index = 0; docs_index < docs.length; docs_index++){
974				var doc = docs[docs_index];
975				if(doc != null){
976					translate.listener.observer.observe(doc, translate.listener.config);
977				}
978			}
979		},
980		/*
981			每当执行完一次渲染任务(翻译)时会触发此。注意页面一次翻译会触发多个渲染任务。普通情况下,一次页面的翻译可能会触发两三次渲染任务。
982			另外如果页面中有ajax交互方面的信息,时,每次ajax信息刷新后,也会进行翻译,也是一次渲染任务。
983			这个是为了方便扩展使用。比如在layui中扩展,监控 select 的渲染
984		*/
985		renderTaskFinish:function(renderTask){
986			//console.log(renderTask);
987		}
988	},
989	//对翻译结果进行替换渲染的任务,将待翻译内容替换为翻译内容的过程
990	renderTask:class{
991		constructor(){
992			/*
993			 * 任务列表
994			 * 一维数组 [hash] = tasks;  tasks 是多个task的数组集合
995			 * 二维数组 [task,task,...],存放多个 task,每个task是一个替换。这里的数组是同一个nodeValue的多个task替换
996			 * 三维数组 task['originalText'] 、 task['resultText'] 存放要替换的字符串
997			 		   task['attribute'] 存放要替换的属性,比如 a标签的title属性。 如果是直接替换node.nodeValue ,那这个没有
998			 */
999			this.taskQueue = [];
1000			
1001			/*
1002			 * 要进行翻译的node元素,
1003			 * 一维数组 key:node.nodeValue 的 hash , value:node的元素数组
1004			 * 二维数组,也就是value中包含的node集合 [node,node,...]
1005	 		 */
1006			this.nodes = [];
1007		}
1008		
1009		/**
1010		 * 向替换队列中增加替换任务
1011		 * node:要替换的字符属于那个node元素
1012		 * originalText:待翻译的字符
1013		 * resultText:翻译后的结果字符
1014		 * attribute: 要替换的是哪个属性,比如 a标签的title属性,这里便是传入title。如果不是替换属性,这里不用传入,或者传入null
1015		 */
1016		add(node, originalText, resultText, attribute){
1017			var nodeAnaly = translate.element.nodeAnalyse.get(node, attribute); //node解析
1018			//var hash = translate.util.hash(translate.element.getTextByNode(node)); 	//node中内容的hash
1019			var hash = translate.util.hash(nodeAnaly['text']);
1020			//console.log('--------------'+hash);
1021			//console.log(nodeAnaly);
1022			//console.log(node);
1023			//console.log('originalText:'+originalText+', resultText:'+resultText+', attribute:'+attribute);
1024			/****** 加入翻译的元素队列  */
1025			if(typeof(this.nodes[hash]) == 'undefined'){
1026				this.nodes[hash] = new Array();
1027			}
1028			this.nodes[hash].push(node);
1029			//console.log(node)
1030			
1031			/****** 加入翻译的任务队列  */
1032			var tasks = this.taskQueue[hash];
1033			if(tasks == null || typeof(tasks) == 'undefined'){
1034				//console.log(node.nodeValue);
1035				tasks = new Array(); //任务列表,存放多个 task,每个task是一个替换。这里的数组是同一个nodeValue的多个task替换
1036			}
1037			var task = new Array();
1038			
1039			//v2.3.3 增加 -- 开始
1040			//这里要进行处理,因为有时候翻译前,它前或者后是有空格的,但是翻译后会把前或者后的空格给自动弄没了,如果是这种情况,要手动补上
1041			if (originalText.substr(0, 1) == ' ') {
1042				//console.log('第一个字符是空格');
1043				if(resultText.substr(0, 1) != ' '){
1044					//翻译结果的第一个字符不是空格,那么补上
1045					resultText = ' ' + resultText;
1046				}
1047			}
1048			if (originalText.substr(originalText.length - 1, 1) === ' ') {
1049				//console.log('最后一个字符是空格');
1050				if(resultText.substr(0, 1) != ' '){
1051					//翻译结果的最后一个字符不是空格,那么补上
1052					resultText = resultText + ' ';
1053				}
1054			}
1055			//v2.3.3 增加 -- 结束
1056
1057			task['originalText'] = originalText;
1058			task['resultText'] = resultText;
1059			task['attribute'] = attribute;
1060
1061			//console.log(task);
1062			tasks.push(task);
1063			this.taskQueue[hash] = tasks;
1064			/****** 加入翻译的任务队列 end  */
1065		}
1066		//进行替换渲染任务,对页面进行渲染替换翻译
1067		execute(){
1068			//先对tasks任务队列的替换词进行排序,将同一个node的替换词有大到小排列,避免先替换了小的,大的替换时找不到
1069			for(var hash in this.taskQueue){
1070				var tasks = this.taskQueue[hash];
1071				if(typeof(tasks) == 'function'){
1072					//进行异常的预处理调出
1073					continue;
1074				}
1075
1076				//进行排序,将原字符串长的放前面,避免造成有部分不翻译的情况(bug是先翻译了短的,导致长的被打断而无法进行适配)
1077				tasks.sort((a, b) => b.originalText.length - a.originalText.length);
1078				
1079				this.taskQueue[hash] = tasks;
1080			}
1081			
1082			//console.log('===========task=========');
1083			//console.log(this.taskQueue);
1084			//console.log(this.nodes);
1085			//console.log('===========task======end===');
1086
1087			//对nodeQueue进行翻译
1088			for(var hash in this.nodes){
1089				var tasks = this.taskQueue[hash]; //取出当前node元素对应的替换任务
1090				//var tagName = this.nodes[hash][0].nodeName; //以下节点的tag name
1091				//console.log(tasks);
1092				for(var node_index = 0; node_index < this.nodes[hash].length; node_index++){
1093					//对这个node元素进行替换翻译字符
1094					for(var task_index=0; task_index<tasks.length; task_index++){
1095						var task = tasks[task_index];
1096						if(typeof(tasks) == 'function'){
1097							//进行异常的预处理调出
1098							continue;
1099						}
1100						
1101						//翻译完毕后,再将这个翻译的目标node从 inPro....
1101Nodes 中去掉
1102						var ipnode = this.nodes[hash][task_index];
1103						//console.log('int-----++'+ipnode.nodeValue);
1104						setTimeout(function(ipnode){
1105							//console.log('int-----'+ipnode.nodeValue);
1106							for(var ini = 0; ini < translate.inProgressNodes.length; ini++){
1107								if(translate.inProgressNodes[ini].node.isSameNode(ipnode)){
1108									//console.log('in progress --');
1109									//console.log(ipnode);
1110									//有记录了,那么出现次数 +1
1111									translate.inProgressNodes[ini].number = translate.inProgressNodes[ini].number - 1;
1112									//console.log("inProgressNodes -- number: "+translate.inProgressNodes[ini].number+', text:'+ipnode.nodeValue);
1113									if(translate.inProgressNodes[ini].number < 1){
1114										
1115										
1116											//console.log('ini-'+ini)
1117											translate.inProgressNodes.splice(ini,1);	
1118											//console.log("inProgressNodes -- 减去node length: "+translate.inProgressNodes.length+', text:'+ipnode.nodeValue);
1119										//
1120									}
1121									break;
1122								}
1123							}
1124						}, 50, ipnode);
1125
1126						translate.element.nodeAnalyse.set(this.nodes[hash][task_index], task.originalText, task.resultText, task['attribute']);
1127
1128
1129
1130
1131
1132						/*
1133						//var tagName = translate.element.getTagNameByNode(this.nodes[hash][task_index]);//节点的tag name
1134						//console.log(tagName)
1135						//console.log(this.nodes[hash][task_index])
1136						//var tagName = this.nodes[hash][task_index].nodeName; //节点的tag name
1137						var nodename = translate.element.getNodeName(this.nodes[hash][task_index]);
1138						
1139						//console.log(this.nodes[hash][task_index]+', '+task.originalText+', '+task.resultText+', tagName:'+tagName);
1140						if(nodename == 'META'){
1141							if(typeof(this.nodes[hash][task_index].name) != 'undefined' && this.nodes[hash][task_index].name != null){
1142								//var nodeName = this.nodes[hash][task_index].name.toLowerCase();  //取meta 标签的name 属性
1143								
1144								this.nodes[hash][task_index].content = this.nodes[hash][task_index].content.replace(new RegExp(translate.util.regExp.pattern(task.originalText),'g'), translate.util.regExp.resultText(task.resultText));
1145							}
1146						}else if(nodename == 'IMG'){
1147							this.nodes[hash][task_index].alt = this.nodes[hash][task_index].alt.replace(new RegExp(translate.util.regExp.pattern(task.originalText),'g'), translate.util.regExp.resultText(task.resultText));
1148						}else{
1149							//普通的
1150							//console.log('task.originalText : '+task.originalText);
1151							//console.log(translate.util.regExp.pattern(task.originalText))
1152							//console.log('task.resultText : '+task.resultText);
1153							this.nodes[hash][task_index].nodeValue = this.nodes[hash][task_index].nodeValue.replace(new RegExp(translate.util.regExp.pattern(task.originalText),'g'), translate.util.regExp.resultText(task.resultText));
1154						}
1155						*/
1156					}
1157				}
1158			}
1159			
1160			//console.log('---listen');
1161
1162			//监听 - 增加到翻译历史里面 nodeHistory
1163			if(typeof(this.taskQueue) != 'undefined' && Object.keys(this.taskQueue).length > 0){
1164				//50毫秒后执行,以便页面渲染完毕
1165				var renderTask = this;
1166				setTimeout(function() {
1167
1168					/** 执行完成后,保存翻译的历史node **/
1169					//将当前翻译完成的node进行缓存记录,以node唯一标识为key,  node、以及node当前翻译之后的内容为值进行缓存。方便下一次执行 translate.execute() 时,若值未变化则不进行翻译
1170					for(var hash in renderTask.nodes){
1171						//console.log(translate.nodeQueue[uuid].list[lang][hash])
1172						for(var nodeindex in renderTask.nodes[hash]){
1173							//console.log(translate.nodeQueue[uuid].list[lang][hash].original);
1174							//var nodename = translate.element.getNodeName(translate.nodeQueue[uuid].list[lang][hash].nodes[0].node);
1175							//console.log("nodename:"+nodename);
1176							var analyse = translate.element.nodeAnalyse.get(renderTask.nodes[hash][0]);
1177							//analyse.text  analyse.node
1178							var nodeid = nodeuuid.uuid(analyse.node);
1179							
1180							if(nodeid.length == 0){
1181								//像是input的placeholder 暂时没考虑进去,这种就直接忽略了
1182								continue;
1183							}
1184
1185							//加入
1186							/*
1187							if(typeof(translate.nodeHistory[nodeid]) == 'object'){
1188								//已经加入过了,判断它的值是否有发生过变化
1189
1190								if(translate.nodeHistory[nodeid].translateText == analyse.text){
1191									//值相同,就不用再加入了
1192									continue;
1193								}
1194							}
1195							这里就不用判断了,直接同步到最新的,因为同一个node,可能有本地缓存直接更新,这样会非常快,网络的会慢2秒,
1195因时间导致同步不是最新的
1196							*/
1197							//console.log(analyse);
1198							//console.log('add-----'+analyse.text +', uuid:'+nodeid);
1199							//console.log(analyse.node);
1200							translate.nodeHistory[nodeid] = {};
1201							translate.nodeHistory[nodeid].node = analyse.node;
1202							translate.nodeHistory[nodeid].translateText = analyse.text;
1203						}
1204						
1205					}
1206					//console.log(translate.nodeHistory);
1207
1208					/** 执行完成后,触发用户自定义的翻译完成执行函数 **/
1209					translate.listener.renderTaskFinish(renderTask);
1210
1211				}, 50);
1212				
1213			}else{
1214				//console.log(this.taskQueue);
1215				//console.log('---this.taskQueue is null');
1216			}
1217		}
1218	},
1219	
1220	//执行翻译操作。翻译的是 nodeQueue 中的
1221	//docs 如果传入,那么翻译的只是传入的这个docs的。传入如 [document.getElementById('xxx'),document.getElementById('xxx'),...]
1222	execute:function(docs){
1223		if(typeof(docs) != 'undefined'){
1224			//execute传入参数,只有v2版本才支持
1225			translate.useVersion = 'v2';
1226		}
1227		
1228		if(translate.useVersion == 'v1'){
1229		//if(this.to == null || this.to == ''){
1230			//采用1.x版本的翻译,使用google翻译
1231			//translate.execute_v1();
1232			//return;
1233			//v2.5.1增加
1234			console.log('提示:https://github.com/xnx3/translate 在 v2.5 版本之后,由于谷歌翻译调整,免费翻译通道不再支持,所以v1版本的翻译接口不再被支持,v1全线下架。考虑到v1已不能使用,当前已自动切换到v2版本。如果您使用中发现什么异常,请针对v2版本进行适配。');
1235			translate.useVersion = 'v2';
1236		}
1237		
1238		//版本检测
1239		try{
1240			translate.init();
1241		}catch(e){  }
1242
1243		/****** 采用 2.x 版本的翻译,使用自有翻译算法 */
1244		
1245
1246		//每次执行execute,都会生成一个唯一uuid,也可以叫做队列的唯一标识,每一次执行execute都会创建一个独立的翻译执行队列
1247		var uuid = translate.util.uuid();
1248		//console.log('=====')
1249		//console.log(translate.nodeQueue);
1250		
1251		/* v2.4.3 将初始化放到了 translate.element.whileNodes 中,如果uuid对应的没有,则自动创建
1252
1253		translate.nodeQueue[uuid] = new Array(); //创建
1254		translate.nodeQueue[uuid]['expireTime'] = Date.now() + 120*1000; //删除时间,10分钟后删除
1255		translate.nodeQueue[uuid]['list'] = new Array(); 
1256		*/
1257		//console.log(translate.nodeQueue);
1258		//console.log('=====end')
1259		
1260		//如果页面打开第一次使用,先判断缓存中有没有上次使用的语种,从缓存中取出
1261		if(translate.to == null || translate.to == ''){
1262			var to_storage = translate.storage.get('to');
1263			if(to_storage != null && typeof(to_storage) != 'undefined' && to_storage.length > 0){
1264				translate.to = to_storage;
1265			}
1266		}
1267		
1268		//渲染select选择语言
1269		try{
1270			translate.selectLanguageTag.render();	
1271		}catch(e){
1272			console.log(e);
1273		}
1274		
1275		//判断是否还未指定翻译的目标语言
1276		if(translate.to == null || typeof(translate.to) == 'undefined' || translate.to.length == 0){
1277			//未指定,判断如果指定了自动获取用户本国语种了,那么进行获取
1278			if(translate.autoDiscriminateLocalLanguage){
1279				translate.executeByLocalLanguage();
1280			}
1281			
1282			//没有指定翻译目标语言、又没自动获取用户本国语种,则不翻译
1283			return;
1284		}
1285		
1286		//判断本地语种跟要翻译的目标语种是否一样,如果是一样,那就不需要进行任何翻译
1287		if(translate.to == translate.language.getLocal()){
1288			return;
1289		}
1290		
1291		/********** 翻译进行 */
1292		
1293		//先进行图片的翻译替换,毕竟图片还有加载的过程
1294		translate.images.execute();
1295		
1296		/*
1297			进行翻译指定的node操作。优先级为:
1298			1. 这个方法已经指定的翻译 nodes
1299			2. setDocuments 指定的 
1300			3. 整个网页 
1301			其实2、3都是通过 getDocuments() 取,在getDocuments() 就对2、3进行了判断
1302		*/
1303		var all;
1304		if(typeof(docs) != 'undefined'){
1305			//1. 这个方法已经指定的翻译 nodes
1306			
1307			if(docs == null){
1308				//要翻译的目标区域不存在
1309				console.log('translate.execute(...) 中传入的要翻译的目标区域不存在。');
1310				return;
1311			}
1312			
1313			if(typeof(docs.length) == 'undefined'){
1314				//不是数组,是单个元素
1315				all = new Array();
1316				all[0] = docs;
1317			}else{
1318				//是数组,直接赋予
1319				all = docs;
1320			}
1321			
1322		}else{
1323			//2、3
1324			all = translate.getDocuments();
1325		}
1326		//console.log('----要翻译的目标元素-----');
1327		//console.log(all)
1328		
1329		if(all.length > 500){
1330			console.log('------tip------');
1331			console.log('translate.execute( docs ) 传入的docs.length 过大,超过500,这很不正常,当前 docs.length : '+all.length+' ,如果你感觉真的没问题,请联系作者 http://translate.zvo.cn/43006.html 说明情况,根据你的情况进行分析。 当前只取前500个元素进行翻译');
1332		}
1333
1334		//检索目标内的node元素
1335		for(var i = 0; i< all.length & i < 500; i++){
1336			var node = all[i];
1337			translate.element.whileNodes(uuid, node);	
1338		}
1339
1340		/***** translate.language.translateLanguagesRange 开始 *****/
1341		if(translate.language.translateLanguagesRange.length > 0){
1342			//如果大于0,则是有设置,那么只翻译有设置的语种,不在设置中的语种不会参与翻译
1343			for(var lang in translate.nodeQueue[uuid].list){
1344				if(translate.language.translateLanguagesRange.indexOf(lang) < 0){
1345					//删除这个语种
1346					delete translate.nodeQueue[uuid].list[lang];
1347				}
1348			}
1349		}
1350		
1351		/***** translate.language.translateLanguagesRange 结束 *****/
1352		
1353		//修复如果translate放在了页面最顶部,此时执行肯定扫描不到任何东西的,避免这种情况出现报错
1354		if(typeof(translate.nodeQueue[uuid]) == 'undefined'){
1355			translate.nodeQueue[uuid] = new Array();
1356			translate.nodeQueue[uuid].list = [];
1357			console.log('--- translate.js warn tip 警告!! ---');
1358			console.log('您使用translate.js时可能放的位置不对,不要吧 translate.js 放在网页最顶部,这样当 translate.js 进行执行,也就是 translate.execute() 执行时,因为网页是从上往下加载,它放在网页最顶部,那么它执行时网页后面的内容都还没加载出来,这个是不会获取到网页任何内容的,也就是它是不起任何作用的');
1359		}
1360		for(var lang in translate.nodeQueue[uuid].list){
1361			//console.log('lang:'+lang)
1362			for(var hash in translate.nodeQueue[uuid].list[lang]){
1363				//console.log(hash)
1364				if(typeof(translate.nodeQueue[uuid].list[lang][hash]) == 'function'){
1365					//v2.10增加,避免hash冒出个 Contains 出来导致for中的.length 出错
1366					continue;
1367				}
1368				for(var nodeindex = translate.nodeQueue[uuid].list[lang][hash].nodes.length-1; nodeindex > -1; nodeindex--){
1369					//console.log(translate.nodeQueue[uuid].list[lang][hash].nodes);
1370					var analyse = translate.element.nodeAnalyse.get(translate.nodeQueue[uuid].list[lang][hash].nodes[nodeindex].node);
1371					//analyse.text  analyse.node
1372					var nodeid = nodeuuid.uuid(analyse.node);
1373					//translate.nodeQueue[uuid].list[lang][hash].nodes.splice(nodeindex, 1);
1374					//console.log(nodeid+'\t'+analyse.text);
1375					if(typeof(translate.nodeHistory[nodeid]) != 'undefined'){
1376						//存在,判断其内容是否发生了改变
1377						//console.log('比较---------');
1378						//console.log(translate.nodeHistory[nodeid].translateText);
1379						//console.log(analyse.text);
1380						if(translate.nodeHistory[nodeid].translateText == analyse.text){
1381							//内容未发生改变,那么不需要再翻译了,从translate.nodeQueue中删除这个node
1382							translate.nodeQueue[uuid].list[lang][hash].nodes.splice(nodeindex, 1);
1383							//console.log('发现相等的node,删除 '+analyse.text+'\t'+hash);
1384						}else{
1385							//console.log("发现变化的node =======nodeid:"+nodeid);
1386							//console.log(translate.nodeHistory[nodeid].translateText == analyse.text);
1387							//console.log(translate.nodeHistory[nodeid].node);
1388							//console.log(translate.nodeHistory[nodeid].translateText);
1389							//console.log(analyse.text);
1390							
1391						}
1392					}else{
1393						//console.log('未在 nodeHistory 中发现,新的node  nodeid:'+nodeid);
1394						//console.log(analyse.node)
1395					}
1396				}
1397				if(translate.nodeQueue[uuid].list[lang][hash].nodes.length == 0){
1398					//如果node数组中已经没有了,那么直接把这个hash去掉
1399					delete translate.nodeQueue[uuid].list[lang][hash];
1400				}
1401			}
1402			if(Object.keys(translate.nodeQueue[uuid].list[lang]).length == 0){
1403				//如果这个语言中没有要翻译的node了,那么删除这个语言
1404				delete translate.nodeQueue[uuid].list[lang];
1405			}
1406		}
1407		//console.log('new queuq');
1408		//console.log(translate.nodeQueue[uuid])
1409		//translate.nodeHistory[nodeid]
1410
1411		
1412		//console.log('-----待翻译:----');
1413		//console.log(translate.nodeQueue);
1414		
1415		//translateTextArray[lang][0]
1416		var translateTextArray = {};	//要翻译的文本的数组,格式如 ["你好","欢迎"]
1417		var translateHashArray = {};	//要翻译的文本的hash,跟上面的index是一致的,只不过上面是存要翻译的文本,这个存hash值
1418		
1419
1420		/*
1421				要进行第二次扫描的node - 2023.8.22 解决缓存会打散扫描到的翻译文本,导致翻译结束后找寻不到而导致不翻译的问题
1422				一维 key: lang
1423				二维 key: hash
1424				三维 key: 
1425						node: 当前的node元素
1426				四维		array: 当前缓存中进行翻译的文本数组:
1427							cacheOriginal: 已缓存被替换前的文本
1428							cacheTranslateText: 已缓存被替换后的翻译文本
1429					
1430		*/
1431		var twoScanNodes = {};
1432		var cacheScanNodes = []; //同上面的 twoScanNodes,只不过 twoScanNodes 是按照lang存的,而这个不再有lang区分
1433		for(var lang in translate.nodeQueue[uuid]['list']){ //二维数组中,取语言
1434			//console.log('lang:'+lang); //lang为english这种语言标识
1435			if(lang == null || typeof(lang) == 'undefined' || lang.length == 0 || lang == 'undefined'){
1436				//console.log('lang is null : '+lang);
1437				continue;
1438			}
1439
1440			translateTextArray[lang] = [];
1441			translateHashArray[lang] = [];
1442			
1443			let task = new translate.renderTask();
1444			//console.log(translate.nodeQueue);
1445			
1446			twoScanNodes[lang] = [];
1447			//二维数组,取hash、value
1448			for(var hash in translate.nodeQueue[uuid]['list'][lang]){
1449				if(typeof(translate.nodeQueue[uuid]['list'][lang][hash]) == 'function'){
1450					//跳出,增加容错。  正常情况下应该不会这样
1451					continue;
1452				}
1453
1454				//取原始的词,还未经过翻译的,需要进行翻译的词
1455				//var originalWord = translate.nodeQueue[uuid]['list'][lang][hash]['original'];	
1456
1457				//原始的node中的词
1458				var originalWord = translate.nodeQueue[uuid]['list'][lang][hash]['original'];	
1459				//要翻译的词
1460				var translateText = translate.nodeQueue[uuid]['list'][lang][hash]['translateText'];
1461				//console.log(originalWord);
1462/*
1463				//自定义术语后的。如果
1464				var nomenclatureOriginalWord = translate.nomenclature.dispose(cache);
1465				if(nomenclatureOriginalWord != originalWord){
1466					has
1467				}
1468*/				
1469				//console.log(originalWord == translateText ? '1':'xin:'+translateText);
1470				//根据hash,判断本地是否有缓存了
1471				var cacheHash = originalWord == translateText ? hash:translate.util.hash(translateText); //如果匹配到了自定义术语库,那翻译前的hash是被改变了
1472				translate.nodeQueue[uuid]['list'][lang][hash]['cacheHash'] = cacheHash; //缓存的hash。 缓存时,其hash跟翻译的语言是完全对应的,缓存的hash就是翻译的语言转换来的
1473				var cache = translate.storage.get('hash_'+translate.to+'_'+cacheHash);
1474				//console.log(cacheHash+', '+cache);
1475
1476				//var twoScanNodes[] = [];
1476	//要进行第二次扫描的node
1477				if(cache != null && cache.length > 0){
1478					//有缓存了
1479					//console.log('find cache:'+cache);
1480					//直接将缓存赋予
1481					//for(var index = 0; index < this.nodeQueue[lang][hash].length; index++){
1482						//this.nodeQueue[lang][hash][index].nodeValue = cache;
1483
1484						for(var node_index = 0; node_index < translate.nodeQueue[uuid]['list'][lang][hash]['nodes'].length; node_index++){
1485							//console.log(translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]);
1486
1487
1488							//加入 translate.inProgressNodes
1489							//取得这个翻译的node
1490							var ipnode = translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]['node'];
1491
1492							//判断这个node是否已经在 inProgressNodes 记录了
1493							var isFind = false;
1494							for(var ini = 0; ini < translate.inProgressNodes.length; ini++){
1495								if(translate.inProgressNodes[ini].node.isSameNode(ipnode)){
1496									//有记录了,那么出现次数 +1
1497									translate.inProgressNodes[ini].number++;
1498									isFind = true;
1499									//console.log('cache - find - ++ ');
1500									//console.log(ipnode);
1501								}
1502							}
1503							//未发现,那么还要将这个node加入进去
1504							if(!isFind){
1505								//console.log('cache - find - add -- lang:'+lang+', hash:'+hash+' node_index:'+node_index);
1506								//console.log(ipnode.nodeValue);
1507								translate.inProgressNodes.push({node: ipnode, number:1});
1508							}
1509
1510							//console.log(translate.inProgressNodes);
1511							//加入 translate.inProgressNodes -- 结束
1512
1513							//翻译结果的文本,包含了before  、 after 了
1514							var translateResultText = translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]['beforeText']+cache+translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]['afterText'];
1515							task.add(translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]['node'], originalWord, translateResultText, translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]['attribute']);
1516							//this.nodeQueue[lang][hash]['nodes'][node_index].nodeValue = this.nodeQueue[lang][hash]['nodes'][node_index].nodeValue.replace(new RegExp(originalWord,'g'), cache);
1517							//console.log(translateResultText);
1518
1519							//重新扫描这个node,避免这种情况:
1520							//localstorage缓存中有几个词的缓存了,但是从缓存中使用时,把原本识别的要翻译的数据给打散了,导致翻译结果没法赋予,导致用户展示时有些句子没成功翻译的问题 -- 2023.8.22
1521							//console.log('继续扫描 + 1 - '+twoScanNodes.length);
1522							var twoScanIndex = -1; //当前元素是否在 twoScan 中已经加入了,如果已经加入了,那么这里赋予当前所在的下标
1523							for(var i = 0; i<twoScanNodes[lang].length; i++){
1524								if(translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]['node'].isSameNode(twoScanNodes[lang][i]['node'])){
1525								//if(translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]['node'].isSameNode(cacheScanNodes[i]['node'])){
1526									//如果已经加入过了,那么跳过
1527									twoScanIndex = i;
1528									break;
1529								}
1530							}
1531							var twoScanIndex_cache = -1; //当前元素是否在 twoScan 中已经加入了,如果已经加入了,那么这里赋予当前所在的下标
1532							for(var i = 0; i<cacheScanNodes.length; i++){
1533								//if(translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]['node'].isSameNode(twoScanNodes[lang][i]['node'])){
1534								if(translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]['node'].isSameNode(cacheScanNodes[i]['node'])){
1535									//如果已经加入过了,那么跳过
1536									twoScanIndex_cache = i;
1537									break;
1538								}
1539							}
1540
1541							if(twoScanIndex == -1){
1542								//console.log(translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]['node']);
1543								twoScanIndex = twoScanNodes[lang].length;
1544								twoScanNodes[lang][twoScanIndex] = {};
1545								twoScanNodes[lang][twoScanIndex]['node'] = translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]['node'];
1546								twoScanNodes[lang][twoScanIndex]['array'] = [];
1547							}
1548
1549							if(twoScanIndex_cache == -1){
1550								twoScanIndex_cache = cacheScanNodes.length;
1551								cacheScanNodes[twoScanIndex_cache] = {};
1552								cacheScanNodes[twoScanIndex_cache]['node'] = translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]['node'];
1553								cacheScanNodes[twoScanIndex_cache]['array'] = [];
1554							}
1555
1556							//未加入过,那么加入
1557							var arrayIndex = twoScanNodes[lang][twoScanIndex]['array'].length;
1558							twoScanNodes[lang][twoScanIndex]['array'][arrayIndex] = translateResultText;
1559							
1560							var arrayIndex_cache = cacheScanNodes[twoScanIndex_cache]['array'].length;
1561							cacheScanNodes[twoScanIndex_cache]['array'][arrayIndex_cache] = translateResultText;
1562							
1563							//twoScanNodes[lang][twoScanIndex]['array'][arrayIndex] = translate.nodeQueue[uuid]['list'][lang][hash]['beforeText']+cache+translate.nodeQueue[uuid]['list'][lang][hash]['afterText'];
1564						}
1565					//}
1566
1567
1568						
1569					continue;	//跳出,不用在传入下面的翻译接口了
1570				}
1571				
1572				/*
1573				//取出数组
1574				var queueNodes = this.nodeQueue[lang][hash];
1575				if(queueNodes.length > 0){
1576					//因为在这个数组中的值都是一样的,那么只需要取出第一个就行了
1577					var valueStr = queueNodes[0].nodeValue;
1578					valueStr = this.util.charReplace(valueStr);
1579
1580					translateTextArray[lang].push(valueStr);
1581					translateHashArray[lang].push(hash);
1582				}
1583				*/
1584				
1585				//加入待翻译数组
1586				translateTextArray[lang].push(translateText);
1587				translateHashArray[lang].push(hash); //这里存入的依旧还是用原始hash,未使用自定义术语库前的hash,目的是不破å
1587 nodeQueue 的 key
1588			}
1589
1590			task.execute(); //执行渲染任务
1591		}
1592		//console.log(twoScanNodes);
1593		//console.log('cacheScanNodes:');
1594		//console.log(cacheScanNodes);
1595
1596		/******* 进行第二次扫描、追加入翻译队列。目的是防止缓存打散扫描的待翻译文本 ********/
1597		for(var lang in twoScanNodes){
1598			//记录第一次扫描的数据,以便跟第二次扫描后的进行对比
1599			var firstScan = Object.keys(translate.nodeQueue[uuid]['list'][lang]);
1600			var firstScan_lang_langth = firstScan.length; //第一次扫描后的数组长度
1601
1602			//console.log(twoScanNodes[lang]);
1603			for(var i = 0; i<twoScanNodes[lang].length; i++){
1604				
1605				//找到这个node元素命中缓存后的翻译记录
1606				for(var ci = 0; ci<cacheScanNodes.length; ci++){
1607					if(twoScanNodes[lang][i].node.isSameNode(cacheScanNodes[ci]['node'])){
1608						//如果发现,那么赋予
1609						twoScanNodes[lang][i].array = cacheScanNodes[ci].array;
1610						break;
1611					}
1612				}
1613
1614				twoScanNodes[lang][i].array.sort(function(a, b) { return b.length - a.length; });
1615				//console.log(twoScanNodes[lang][i].array);
1616
1617				var nodeAnaly = translate.element.nodeAnalyse.get(twoScanNodes[lang][i].node);
1618				//console.log(nodeAnaly);
1619				var text = nodeAnaly.text;
1620				//console.log(text.indexOf(twoScanNodes[lang][i].array[0]));
1621
1622				for(var ai = 0; ai < twoScanNodes[lang][i].array.length; ai++){
1623					if(twoScanNodes[lang][i].array[ai] < 1){
1624						continue;
1625					}
1626					text = text.replace(new RegExp(translate.util.regExp.pattern(twoScanNodes[lang][i].array[ai]),'g'), translate.util.regExp.resultText('\n'));
1627				}
1628				
1629				//console.log(text);
1630				var textArray = text.split('\n');
1631				//console.log(textArray);
1632				for(var tai = 0; tai < textArray.length; tai++){
1633					if(textArray[tai] < 1){
1634						continue;
1635					}
1636					//console.log(textArray[tai]);
1637					//将新增的追加到 translate.nodeQueue 中
1638					translate.addNodeToQueue(uuid, nodeAnaly['node'], textArray[tai]);
1639				}
1640			}
1641
1642
1643			//取第二次扫描追加后的数据
1644			var twoScan = Object.keys(translate.nodeQueue[uuid]['list'][lang]);
1645			var twoScan_lang_langth = twoScan.length; //第二次扫描后的数组长度
1646			//console.log(firstScan_lang_langth+ '=='+twoScan_lang_langth);
1647			if(firstScan_lang_langth - twoScan_lang_langth == 0){
1648				//一致,没有新增,那么直接跳出,忽略
1649				continue;
1650			}
1651
1652			//console.log(translate.nodeQueue[uuid]['list'][lang]);
1653			//console.log(firstScan);
1654			for(var ti=0; ti<twoScan.length; ti++){
1655				twoHash = twoScan[ti];
1656				//console.log(twoHash + '-- '+firstScan.indexOf(twoHash));
1657				if(firstScan.indexOf(twoHash) == -1){
1658					//需要追加了
1659					var item = translate.nodeQueue[uuid]['list'][lang][twoHash];
1660
1661					var cacheHash = item.original == item.translateText ? twoHash:translate.util.hash(item.translateText); //如果匹配到了自定义术语库,那翻译前的hash是被改变了
1662					translate.nodeQueue[uuid]['list'][lang][twoHash]['cacheHash'] = cacheHash; //缓存的hash。 缓存时,其hash跟翻译的语言是完全对应的,缓存的hash就是翻译的语言转换来的
1663					
1664					translateTextArray[lang].push(item.translateText);
1665					translateHashArray[lang].push(twoHash);
1666				}
1667			}
1668			
1669		}
1670		/******* 进行第二次扫描、追加入翻译队列  -- 结束 ********/
1671
1672		
1673		//window.translateHashArray = translateHashArray;
1674		
1675		//统计出要翻译哪些语种 ,这里面的语种会调用接口进行翻译。其内格式如 english
1676		var fanyiLangs = []; 
1677		//console.log(translateTextArray)
1678		for(var lang in translate.nodeQueue[uuid]['list']){ //二维数组中取语言
1679			if(typeof(translateTextArray[lang]) == 'undefined'){
1680				continue;
1681			}
1682			if(translateTextArray[lang].length < 1){
1683				continue;
1684			}
1685
1686			//如果当前语种就是需要显示的语种(也就是如果要切换的语种),那么也不会进行翻译,直接忽略
1687			if(lang == translate.to){
1688				continue;
1689			}
1690			fanyiLangs.push(lang);
1691		}
1692		
1693
1694		/******* 用以记录当前是否进行完第一次翻译了 *******/
1695		/*
1696		if(!translate.listener.isExecuteFinish){
1697			translate.temp_executeFinishNumber = 0;	//下面请求接口渲染,翻译执行完成的次数	
1698			//判断是否是执行完一次了
1699	        translate.temp_executeFinishInterval = setInterval(function(){
1700				if(translate.temp_executeFinishNumber == fanyiLangs.length){
1701					translate.listener.isExecuteFinish = true; //记录当前已执行完第一次了
1702					clearInterval(translate.temp_executeFinishInterval);//停止
1703					console.log('translate.execute() Finish!');
1704					//console.log(uuid);
1705					
1706				}
1707	        }, 50);
1708		}
1709		*/
1710
1711		//console.log(translate.nodeQueue[uuid]['list'])
1712		if(fanyiLangs.length == 0){
1713			//没有需要翻译的,直接退出
1714			return;
1715		}
1716		
1717		//加入 translate.inProgressNodes -- start
1718		for(var lang in translateHashArray){
1719			if(typeof(translateHashArray[lang]) == 'undefined'){
1720				continue;
1721			}
1722			if(translateHashArray[lang].length < 1){
1723				continue;
1724			}
1725			for(var hai = 0; hai<translateHashArray[lang].length; hai++){
1726				var thhash = translateHashArray[lang][hai];
1727				//取得这个翻译的node
1728				//var ipnode = translate.nodeQueue[uuid]['list'][lang][thhash].nodes[ipni].node;
1729				//console.log('translate.nodeQueue[\''+uuid+'\'][\'list\'][\'chinese_simplified\'][\''+thhash+'\']');
1730				//console.log(lang);
1731				//console.log(translate.nodeQueue[uuid]['list'][lang][thhash].nodes);
1732				for(var ipni = 0; ipni < translate.nodeQueue[uuid]['list'][lang][thhash].nodes.length; ipni++){
1733					//取得这个翻译的node
1734					var ipnode = translate.nodeQueue[uuid]['list'][lang][thhash].nodes[ipni].node;
1735
1736					//判断这个node是否已经在 inProgressNodes 记录了
1737					var isFind = false;
1738					for(var ini = 0; ini < translate.inProgressNodes.length; ini++){
1739						if(translate.inProgressNodes[ini].node.isSameNode(ipnode)){
1740							//有记录了,那么出现次数 +1
1741							//console.log('net request ++');
1742							//console.log(ipnode);
1743							translate.inProgressNodes[ini].number++;
1744							isFind = true;
1745						}
1746					}
1747					//未发现,那么还要将这个node加入进去
1748					if(!isFind){
1749						//console.log('net request add');
1750						//console.log(ipnode);
1751						translate.inProgressNodes.push({node: ipnode, number:1});
1752					}
1753
1754				}
1755
1756			}
1757		}
1758		//加入 translate.inProgressNodes -- end
1759
1760
1761
1762		//进行掉接口翻译
1763		for(var lang_index in fanyiLangs){ //一维数组,取语言
1764			var lang = fanyiLangs[lang_index];
1765			//console.log(typeof(translateTextArray[lang]))
1766			
1767			if(typeof(translateTextArray[lang]) == 'undefined' || translateTextArray[lang].length < 1){
1768				return;
1769			}
1770
1771			//自定义术语
1772			/*var nomenclatureCache = translate.nomenclature.dispose(cache);
1773			for(var ttr_index = 0; ttr_index<translateTextArray[lang].length; ttr_index++){
1774				console.log(translateTextArray[lang][ttr_index])
1775			}*/
1776
1777			/*** 翻译开始 ***/
1778			var url = translate.request.api.translate;
1779			var data = {
1780				from:lang,
1781				to:translate.to,
1782				//text:JSON.stringify(translateTextArray[lang])
1783				text:encodeURIComponent(JSON.stringify(translateTextArray[lang]))
1784			};
1785			//console.log(data);
1786			translate.request.post(url, data, function(data){
1787				//console.log(data); 
1788				//console.log(translateTextArray[data.from]);
1789				if(data.result == 0){
1790					console.log('=======ERROR START=======');
1791					console.log(translateTextArray[data.from]);
1792					//console.log(encodeURIComponent(JSON.stringify(translateTextArray[data.from])));
1793					
1794					console.log('response : '+data.info);
1795					console.log('=======ERROR END  =======');
1796					//translate.temp_executeFinishNumber++; //记录执行完的次数
1797					return;
1798				}
1799				
1800				//console.log('-----待翻译3:----');
1801				//console.log(translate.nodeQueue);
1802				
1803				//console.log('response:'+uuid);
1804				let task = new translate.renderTask();
1805				//遍历 translateHashArray
1806				for(var i=0; i<translateHashArray[data.from].length; i++){
1807					//翻译前的语种,如 english
1808					var lang = data.from;	
1809					//翻译后的内容
1810					var text = data.text[i];	
1811					//如果text为null,那么这个可能是一次翻译字数太多,为了保持数组长度,拼上的null
1812					if(text == null){
1813						continue;
1814					}
1815
1816					// v3.0.3 添加,避免像是 JavaScript 被错误翻译为 “JavaScript的” ,然后出现了多个句子中都出现了Javascript时,会出现翻译后文本重复的问题
1817					// 这里就是验证一下,翻译后的文本,是否会完全包含翻以前的文本,如果包含了,那么强制将翻译后的文本赋予翻译前的原始文本(也就是不被翻译)
1818					if(text.toLowerCase().indexOf(translateTextArray[data.from][i].toLowerCase()) > -1){
1819						//发现了,那么强制赋予翻以前内容
1820						text = translateTextArray[data.from][i];
1821					}
1822
1823
1824					//翻译前的hash对应下标
1825					var hash = translateHashArray[data.from][i];	
1826					var cacheHash = translate.nodeQueue[uuid]['list'][lang][hash]['cacheHash'];
1827
1828
1829					
1830					//取原始的词,还未经过翻译的,需要进行翻译的词
1831					var originalWord = '';
1832					try{
1833						originalWord = translate.nodeQueue[uuid]['list'][lang][hash]['original'];
1834						//console.log('bef:'+translate.nodeQueue[uuid]['list'][lang][hash]['beforeText']);
1835					}catch(e){
1836						console.log('uuid:'+uuid+', originalWord:'+originalWord+', lang:'+lang+', hash:'+hash+', text:'+text+', queue:'+translate.nodeQueue[uuid]);
1837						console.log(e);
1838						continue;
1839					}
1840					
1841					//for(var index = 0; index < translate.nodeQueue[lang][hash].length; index++){
1842					for(var node_index = 0; node_index < translate.nodeQueue[uuid]['list'][lang][hash]['nodes'].length; node_index++){
1843						//translate.nodeQueue[lang][hash]['nodes'][node_index].nodeValue = translate.nodeQueue[lang][hash]['nodes'][node_index].nodeValue.replace(new RegExp(originalWord,'g'), text);
1844						//加入任务
1845						task.add(translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]['node'], originalWord, translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]['beforeText']+text+translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]['afterText'], translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]['attribute']);
1846					}
1847					//}
1848					/*
1849					for(var index = 0; index < translate.nodeQueue[data.from][hash].length; index++){
1850						translate.nodeQueue[data.from][hash][index].nodeValue = text;
1851					}
1852					*/
1853					
1854					//将翻译结果以 key:hash  value翻译结果的形式缓存
1855					translate.storage.set('hash_'+data.to+'_'+cacheHash,text);
1856				}
1857				task.execute(); //执行渲染任务
1858				//translate.temp_executeFinishNumber++; //记录执行完的次数
1859
1860			});
1861			/*** 翻译end ***/
1862
1863			
1864		}
1865	},
1866	/*
1867		将已成功翻译并渲染的node节点进行缓存记录
1868		key: node节点的唯一标识符,通过 nodeuuid.uuid() 生成
1869		value: 
1870			node: node节点
1871			translateText: 翻译完成后,当前node节点的内容文本(是已经翻译渲染过的)
1872	*/
1873	nodeHistory:{},
1874	element:{
1875
1876		/*
1877			注意,里面全部的必须小写。
1878			第一个是tag,第二个是tag的属性。比如要翻译 input 的 value 属性,那么如下:
1879				translate.element.tagAttribute['input']=['value'];
1880			比如要翻译 input 的 value 、 data-value 这两个属性,那么如下:
1881				translate.element.tagAttribute['input']=['value','data-value'];
1882			有几个要翻译的属性,就写上几个。
1883			同样,有几个要额外翻译的tag,就加上几行。  
1884			详细文档参考:  http://translate.zvo.cn/231504.html
1885		*/
1886		tagAttribute : {},
1887
1888		//对翻译前后的node元素的分析(翻以前)及渲染(翻译后)
1889		nodeAnalyse:{
1890			/*
1891				获取node中的要进行翻译的文本内容、以及要操作的实际node对象(这个node对象很可能是传入的node中的某个子node)
1892				node 
1893				attribute 要获取的是某个属性的值,还是node本身的值。比如 a标签的title属性的值,则传入 title。  如果是直接获取node.nodeValue ,那这个没有
1894
1895				返回结果是一个数组。其中:
1896					['text']:要进行翻译的text内容文本
1897					['node']:要进行翻译的目标node
1898
1899			*/
1900			get:function(node, attribute){
1901				return translate.element.nodeAnalyse.analyse(node,'','', attribute);
1902			},
1903			/*
1904				进行翻译之后的渲染显示
1905				参数:
1906					node 当前翻译的node元素
1907					originalText 翻译之前的内容文本
1908					resultText 翻译之后的内容文本
1909					attribute 存放要替换的属性,比如 a标签的title属性。 如果是直接替换node.nodeValue ,那这个没有
1910			*/
1911			set:function(node, originalText, resultText, attribute){
1912				translate.element.nodeAnalyse.analyse(node,originalText,resultText, attribute);
1913			},
1914			/*	
1915				
1916				注意,这个不使用,只是服务于上面的get、set使用。具体使用用上面的get、set
1917
1918				1. 只传入 node:
1919					获取node中的要进行翻译的文本内容、以及要操作的实际node对象(这个node对象很可能是传入的node中的某个子node)
1920					返回结果是一个数组。其中:
1921						['text']:要进行翻译的text内容文本
1922						['node']:要进行翻译的目标node
1923				2. 传入 node、originalText、 resultText
1924					则是进行翻译之后的渲染显示
1925
1926				attribute : 进行替换渲染时使用,存放要替换的属性,比如 a标签的title属性。 如果是直接替换node.nodeValue ,那这个没有
1927			*/
1928			analyse:function(node, originalText, resultText, attribute){
1929				var result = new Array(); //返回的结果
1930				result['node'] = node;
1931				result['text'] = '';
1932
1933				var nodename = translate.element.getNodeName(node);
1934
1935				if(attribute != null && typeof(attribute) == 'string' && attribute.length > 0){
1936					//这个node有属性,替换的是node的属性,而不是nodeValue
1937					result['text'] = node[attribute];
1938
1939					//替换渲染
1940					if(typeof(originalText) != 'undefined' && originalText.length > 0){
1941						if(typeof(node[attribute]) != 'undefined'){
1942							//这种将在 v3.9.2 之后废弃,有下面的setAttribute的方式取代
1943							node[attribute] = node[attribute].replace(new RegExp(translate.util.regExp.pattern(originalText),'g'), translate.util.regExp.resultText(resultText));	
1944						}
1945						if(typeof(node.getAttribute(attribute)) != 'undefined'){
1946							//这个才是在v3.9.2 后要用的,上面的留着只是为了适配以前的
1947							node.setAttribute(attribute, node.getAttribute(attribute).replace(new RegExp(translate.util.regExp.pattern(originalText),'g'), translate.util.regExp.resultText(resultText)));
1948						}
1949					}
1950					return result;
1951				}
1952
1953				//正常的node ,typeof 都是 object
1954
1955				//console.log(typeof(node)+node);
1956				if(nodename == '#text'){
1957					//如果是普通文本,判断一下上层是否是包含在textarea标签中
1958					if(typeof(node.parentNode) != 'undefined'){
1959						var parentNodename = translate.element.getNodeName(node.parentNode);
1960						//console.log(parentNodename)
1961						if(parentNodename == 'TEXTAREA'){
1962							//是textarea标签,那将nodename 纳入 textarea的判断中,同时将判断对象交于上级,也就是textarea标签
1963							nodename = 'TEXTAREA';
1964							node = node.parentNode;
1965						}
1966					}
1967				}
1968
1969
1970
1971				//console.log(nodename)
1972				//console.log(translate.element.getNodeName(node.parentNode))
1973				//console.log(node)
1974				if(nodename == 'INPUT' || nodename == 'TEXTAREA'){
1975					//console.log(node.attributes)
1976					/*
1977						1. input、textarea 输入框,要对 placeholder 做翻译
1978						2. input 要对 type=button 的情况进行翻译
1979					*/
1980					if(node.attributes == null || typeof(node.attributes) == 'undefined'){
1981						result['text'] = '';
1982						return result;
1983					}
1984
1985					//input,要对 type=button、submit 的情况进行翻译
1986					if(nodename == 'INPUT'){
1987						if(typeof(node.attributes.type) != 'undefined' && typeof(node.attributes.type.nodeValue) != null && (node.attributes.type.nodeValue.toLowerCase() == 'button' || node.attributes.type.nodeValue.toLowerCase() == 'submit')){
1988							//console.log('----是 <input type="button"');
1989							//取它的value
1990							var input_value_node = node.attributes.value;
1991							if(input_value_node != null && typeof(input_value_node) != 'undefined' && typeof(input_value_node.nodeValue) != 'undefined' && input_value_node.nodeValue.length > 0){
1992								//替换渲染
1993								if(typeof(originalText) != 'undefined' && originalText.length > 0){
1994									//this.nodes[hash][task_index].nodeValue = this.nodes[hash][task_index].nodeValue.replace(new RegExp(translate.util.regExp.pattern(task.originalText),'g'), translate.util.regExp.resultText(task.resultText));
1995									input_value_node.nodeValue = input_value_node.nodeValue.replace(new RegExp(translate.util.regExp.pattern(originalText),'g'), translate.util.regExp.resultText(resultText));
1996								}
1997
1998								result['text'] = input_value_node.nodeValue;
1999								result['node'] = input_value_node;
2000								return result;
2001							}
2002						}
2003					}
2004					//console.log(node)
2005
2006					//input textarea 的 placeholder 情况
2007					if(typeof(node.attributes['placeholder']) != 'undefined'){
2008						//console.log(node);
2009						//替换渲染
2010						if(typeof(originalText) != 'undefined' && originalText.length > 0){
2011							//this.nodes[hash][task_index].nodeValue = this.nodes[hash][task_index].nodeValue.replace(new RegExp(translate.util.regExp.pattern(task.originalText),'g'), translate.util.regExp.resultText(task.resultText));
2012							node.attributes['placeholder'].nodeValue = node.attributes['placeholder'].nodeValue.replace(new RegExp(translate.util.regExp.pattern(originalText),'g'), translate.util.regExp.resultText(resultText));
2013						}
2014
2015						result['text'] = node.attributes['placeholder'].nodeValue;
2016						result['node'] = node.attributes['placeholder'];
2017						return result;
2018						//return node.attributes['placeholder'].nodeValue;
2019					}
2020					//console.log(node)
2021					result['text'] = '';
2022					return result;
2023				}
2024				if(nodename == 'META'){
2025					//meta标签,如是关键词、描述等
2026					if(typeof(node.name) != 'undefined' && node.name != null){
2027						var nodeAttributeName = node.name.toLowerCase();  //取meta 标签的name 属性
2028						var nodeAttributePropertyOri = node.getAttribute('property'); //取 property的值
2029						var nodeAttributeProperty = '';
2030						if(typeof(nodeAttributePropertyOri) != 'undefined' && nodeAttributePropertyOri != null && nodeAttributePropertyOri.length > 0){
2031							nodeAttributeProperty = nodeAttributePropertyOri.toLowerCase();
2032						}
2033						if(nodeAttributeName == 'keywords' || nodeAttributeName == 'description' || nodeAttributeName == 'sharetitle' || nodeAttributeProperty == 'og:title' || nodeAttributeProperty == 'og:description' || nodeAttributeProperty == 'og:site_name' || nodeAttributeProperty == 'og:novel:latest_chapter_name'){
2034							//替换渲染
2035							if(typeof(originalText) != 'undefined' && originalText != null && originalText.length > 0){
2036								//this.nodes[hash][task_index].nodeValue = this.nodes[hash][task_index].nodeValue.replace(new RegExp(translate.util.regExp.pattern(task.originalText),'g'), translate.util.regExp.resultText(task.resultText));
2037								node.content = node.content.replace(new RegExp(translate.util.regExp.pattern(originalText),'g'), translate.util.regExp.resultText(resultText));
2038							}
2039
2040							result['text'] = node.content;
2041							return result;
2042						}
2043					}
2044
2045					result['text'] = '';
2046					return result;
2047				}
2048				if(nodename == 'IMG'){
2049					if(typeof(node.alt) == 'undefined' || node.alt == null){
2050						result['text'] = '';
2051						return result;
2052					}
2053
2054					//替换渲染
2055					if(typeof(originalText) != 'undefined' && originalText.length > 0){
2056						//this.nodes[hash][task_index].nodeValue = this.nodes[hash][task_index].nodeValue.replace(new RegExp(translate.util.regExp.pattern(task.originalText),'g'), translate.util.regExp.resultText(task.resultText));
2057						node.alt = node.alt.replace(new RegExp(translate.util.regExp.pattern(originalText),'g'), translate.util.regExp.resultText(resultText));
2058					}
2059					result['text'] = node.alt;
2060					return result;
2061				}
2062				
2063				
2064				//其他的
2065				if(node.nodeValue == null || typeof(node.nodeValue) == 'undefined'){
2066					result['text'] = '';
2067				}else if(node.nodeValue.trim().length == 0){
2068					//避免就是单纯的空格或者换行
2069					result['text'] = '';
2070				}else{
2071					//替换渲染
2072					if(typeof(originalText) != 'undefined' && originalText != null && originalText.length > 0){
2073						//this.nodes[hash][task_index].nodeValue = this.nodes[hash][task_index].nodeValue.replace(new RegExp(translate.util.regExp.pattern(task.originalText),'g'), translate.util.regExp.resultText(task.resultText));
2074						node.nodeValue = node.nodeValue.replace(new RegExp(translate.util.regExp.pattern(originalText),'g'), translate.util.regExp.resultText(resultText));
2075					}
2076					result['text'] = node.nodeValue;
2077				}
2078				return result;
2079			}
2080		},
2081		//获取这个node元素的node name ,如果未发现,则返回''空字符串
2082		getNodeName:function(node){
2083			if(node == null || typeof(node) == 'undefined'){
2084				return '';
2085			}
2086
2087			if(node.nodeName == null || typeof(node.nodeName) == 'undefined'){
2088				return '';
2089			}
2090
2091			var nodename = node.nodeName;
2092			//console.log('nodename:'+nodename+', node:'+node);
2093			return nodename;
2094		},
2095		//向下遍历node
2096		whileNodes:function(uuid, node){
2097			if(node == null || typeof(node) == 'undefined'){
2098				return;
2099			}
2100
2101			//如果这个uuid没有,则创建
2102			if(typeof(translate.nodeQueue[uuid]) == 'undefined' || translate.nodeQueue[uuid] == null){
2103				translate.nodeQueue[uuid] = new Array(); //创建
2104				translate.nodeQueue[uuid]['expireTime'] = Date.now() + 120*1000; //删除时间,10分钟后删除
2105				translate.nodeQueue[uuid]['list'] = new Array(); 
2106				//console.log('创建 --- ');
2107				//console.log(uuid)
2108			}
2109
2110			//console.log('---'+typeof(node)+', ');
2111			//判断是否是有title属性,title属性也要翻译
2112			if(typeof(node) == 'object' && typeof(node['title']) == 'string' && node['title'].length > 0){
2113				//将title加入翻译队列
2114				//console.log('---'+node.title+'\t'+node.tagName);
2115				//console.log(node)
2116				//console.log('------------');
2117				
2118				//判断当前元素是否在ignore忽略的tag、id、class name中
2119				if(!translate.ignore.isIgnore(node)){
2120					//不在忽略的里面,才会加入翻译
2121					translate.addNodeToQueue(uuid, node, node['title'], 'title');
2122				}
2123			}
2124
2125			//v3.9.2 增加, 用户可自定义标签内 attribute 的翻译
2126			var nodeNameLowerCase = translate.element.getNodeName(node).toLowerCase();
2127			if(typeof(translate.element.tagAttribute[nodeNameLowerCase]) != 'undefined'){
2128				//console.log('find:'+nodeNameLowerCase);
2129				//console.log(translate.element.tagAttribute[nodeNameLowerCase]);
2130
2131				for(var attributeName_index in translate.element.tagAttribute[nodeNameLowerCase]){
2132					
2133					var attributeName = translate.element.tagAttribute[nodeNameLowerCase][attributeName_index];
2134					if(typeof(node.getAttribute(attributeName)) == 'undefined'){
2135						//这个tag标签没有这个 attribute,忽略
2136						continue
2137					}
2138					//加入翻译
2139					translate.addNodeToQueue(uuid, node, node.getAttribute(attributeName), attributeName);
2140				}
2141
2142			}
2143
2144			
2145			var childNodes = node.childNodes;
2146			if(childNodes == null || typeof(childNodes) == 'undefined'){
2147				return;
2148			}
2149			if(childNodes.length > 0){
2150				for(var i = 0; i<childNodes.length; i++){
2151					translate.element.whileNodes(uuid, childNodes[i]);
2152				}
2153			}else{
2154				//单个了
2155				translate.element.findNode(uuid, node);
2156			}
2157		},
2158		findNode:function(uuid, node){
2159			if(node == null || typeof(node) == 'undefined'){
2160				return;
2161			}
2162			//console.log(node)
2163			if(node.parentNode == null){
2164				return;
2165			}
2166
2167			//console.log('-----parent')
2168			var parentNodeName = translate.element.getNodeName(node.parentNode);
2169			//node.parentNode.nodeName;
2170			if(parentNodeName == ''){
2171				return;
2172			}
2173			if(translate.ignore.tag.indexOf(parentNodeName.toLowerCase()) > -1){
2174				//忽略tag
2175				//console.log('忽略tag:'+parentNodeName);
2176				return;
2177			}
2178	
2179			/****** 判断忽略的class ******/
2180			/*
2181			这段理论上不需要了,因为在  translate.ignore.isIgnore 判断了
2182			var ignoreClass = false;	//是否是被忽略的class,true是
2183			var parentNode = node.parentNode;
2184			while(node != parentNode && parentNode != null){
2185				//console.log('node:'+node+', parentNode:'+parentNode);
2186				if(parentNode.className != null){
2187					if(translate.ignore.class.indexOf(parentNode.className) > -1){
2188						//发现ignore.class 当前是处于被忽略的 class
2189						ignoreClass = true;
2190					}
2191				}
2192				
2193				parentNode = parentNode.parentNode;
2194			}
2195			if(ignoreClass){
2196				//console.log('ignore class :  node:'+node.nodeValue);
2197				return;
2198			}
2199			*/
2200			/**** 判断忽略的class结束 ******/
2201
2202
2203
2204			/**** 避免中途局部翻译,在判断一下 ****/
2205			//判断当前元素是否在ignore忽略的tag、id、class name中
2206			if(translate.ignore.isIgnore(node)){
2207				//console.log('node包含在要忽略的元素中:');
2208				//console.log(node);
2209				return;
2210			}
2211
2212			//node分析
2213			var nodeAnaly = translate.element.nodeAnalyse.get(node);
2214			if(nodeAnaly['text'].length > 0){
2215				//有要翻译的目标内容,加入翻译队列
2216				//console.log('addNodeToQueue -- '+nodeAnaly['node']+', text:' + nodeAnaly['text']);
2217				translate.addNodeToQueue(uuid, nodeAnaly['node'], nodeAnaly['text']);
2218			}
2219			//console.log(nodeAnaly);
2220			/*
2221			//console.log(node.nodeName+', type:'+node.nodeType+', '+node.nodeValue);
2222			var nodename = translate.element.getNodeName(node);
2223			if(nodename == 'INPUT' || nodename == 'TEXTAREA'){
2224				//input 输入框,要对 placeholder 做翻译
2225				console.log('input---'+node.attributes);
2226				if(node.attributes == null || typeof(node.attributes) == 'undefined'){
2227					return;
2228				}
2229	
2230				if(typeof(node.attributes['placeholder']) != 'undefined'){
2231					//console.log(node.attributes['placeholder'].nodeValue);
2232					//加入要翻译的node队列
2233					//translate.nodeQueue[translate.hash(node.nodeValue)] = node.attributes['placeholder'];
2234					//加入要翻译的node队列
2235					//translate.addNodeToQueue(translate.hash(node.attributes['placeholder'].nodeValue), node.attributes['placeholder']);
2236					translate.addNodeToQueue(uuid, node.attributes['placeholder'], node.attributes['placeholder'].nodeValue);
2237				}
2238				
2239				//console.log(node.getAttribute("placeholder"));
2240			}else if(nodename == 'META'){
2241				//meta标签,如是关键词、描述等
2242				if(typeof(node.name) != 'undefined' && node.name != null){
2243					var nodeAttributeName = node.name.toLowerCase();  //取meta 标签的name 属性
2244					//console.log(nodeName);
2245					if(nodeAttributeName == 'keywords' || nodeAttributeName == 'description'){
2246						//关键词、描述
2247						translate.addNodeToQueue(uuid, node, node.content);
2248					}
2249				}
2250				//console.log(node.name)
2251			}else if(nodename == 'IMG'){
2252				//console.log('-------'+node.alt);
2253				translate.addNodeToQueue(uuid, node, node.alt);
2254			}else if(node.nodeValue != null && node.nodeValue.trim().length > 0){
2255	
2256				//过滤掉无效的值
2257				if(node.nodeValue != null && typeof(node.nodeValue) == 'string' && node.nodeValue.length > 0){
2258				}else{
2259					return;
2260				}
2261	
2262				//console.log(node.nodeValue+' --- ' + translate.language.get(node.nodeValue));
2263				
2264				//console.log(node.nodeName);
2265				//console.log(node.parentNode.nodeName);
2266				//console.log(node.nodeValue);
2267				//加入要翻译的node队列
2268				translate.addNodeToQueue(uuid, node, node.nodeValue);	
2269				//translate.addNodeToQueue(translate.hash(node.nodeValue), node);
2270				//translate.nodeQueue[translate.hash(node.nodeValue)] = node;
2271				//translate.nodeQueue[translate.hash(node.nodeValue)] = node.nodeValue;
2272				//node.nodeValue = node.nodeValue+'|';
2273	
2274			}
2275			*/
2276
2277		},
2278	},
2279
2280	
2281
2282
2283	
2284	/*
2285	 * 将发现的元素节点加入待翻译队列
2286	 * uuid execute方法执行的唯一id
2287	 * node 当前text所在的node
2288	 * text 当前要翻译的目标文本
2289	 * attribute 是否是元素的某个属性。比如 a标签中的title属性, a.title 再以node参数传入时是string类型的,本身并不是node类型,所以就要传入这个 attribute=title 来代表这是a标签的title属性。同样第二个参数node传入的也不能是a.title,而是传入a这个node元素
2290	 */
2291	addNodeToQueue:function(uuid, node, text, attribute){
2292		if(node == null || text == null || text.length == 0){
2293			return;
2294		}
2295
2296
2297		//console.log('find tag ignore : '+node.nodeValue+', '+node.nodeName+", "+node.nodeType+", "+node.tagName);
2298		//console.log('addNodeToQueue into -- node:'+node+', text:'+text+', attribute:'+attribute);
2299		var nodename = translate.element.getNodeName(node);
2300		
2301		//判断如果是被 <!--  --> 注释的区域,不进行翻译
2302		if(nodename.toLowerCase() == '#comment'){
2303			return;
2304		}
2305		//console.log('\t\t'+text);
2306		//取要翻译字符的hash
2307		var key = translate.util.hash(text);
2308		/*
2309		如果是input 的 placeholder ,就会出现这个情况
2310		if(node.parentNode == null){
2311			console.log('node.parentNode == null');
2312			return;
2313		}
2314		*/
2315
2316		//console.log(node.parentNode);
2317		//console.log(node.parentNode.nodeName);
2318		
2319		//判断其内容是否是 script、style 等编程的文本,如果是,则不进行翻译,不然翻译后还会影响页面正常使用
2320		if(translate.util.findTag(text)){
2321			//console.log('find tag ignore : '+node.nodeValue+', '+node.nodeName+", "+node.nodeType+", "+node.tagName);
2322			//console.log(node.parentNode.nodeName);
2323			
2324			//获取到当前文本是属于那个tag标签中的,如果是script、style 这样的标签中,那也会忽略掉它,不进行翻译
2325			if(node.parentNode == null){
2326				//没有上级了,或是没获取到上级,忽略
2327				return;
2328			}
2329			//去上级的tag name
2330			var parentNodeName = translate.element.getNodeName(node.parentNode);
2331			//node.parentNode.nodeName;
2332			if(parentNodeName == 'SCRIPT' || parentNodeName == 'STYLE'){
2333				//如果是script、style中发现的,那也忽略
2334				return;
2335			}
2336		}
2337		//console.log(node.nodeValue);
2338
2339
2340		//原本传入的text会被切割为多个小块
2341		var textArray = new Array();
2342		textArray.push(text); //先将主 text 赋予 ,后面如果对主text进行加工分割,分割后会将主text给删除掉
2343		//console.log(textArray);
2344
2345		/**** v3.10.2.20241206 - 增加自定义忽略翻译的文本,忽略翻译的文本不会被翻译 - 当然这样会打乱翻译之后阅读的连贯性 ****/
2346		for(var ti = 0; ti<translate.ignore.text.length; ti++){
2347			if(translate.ignore.text[ti].trim().length == 0){
2348				continue;
2349			}
2350
2351			textArray = translate.addNodeToQueueTextAnalysis(uuid, node, textArray, attribute, translate.ignore.text[ti], translate.ignore.text[ti]);
2352			
2353			//console.log(textArray);
2354		}
2355
2356
2357
2358		/**** v3.10.2.20241206 - 自定义术语能力全面优化 - 当然这样会打乱翻译之后阅读的连贯性 ****/
2359		//判断是否进行了翻译,也就是有设置目标语种,并且跟当前语种不一致
2360		if(typeof(translate.temp_nomenclature) == 'undefined'){
2361			translate.temp_nomenclature = new Array();
2362		}
2363		if(typeof(translate.temp_nomenclature[translate.language.getLocal()]) == 'undefined'){
2364			nomenclatureKeyArray = new Array();
2365		}
2366		if(typeof(translate.nomenclature.data[translate.language.getLocal()]) != 'undefined' && typeof(translate.nomenclature.data[translate.language.getLocal()][translate.to]) != 'undefined'){
2367			var nomenclatureKeyArray;
2368			for(var nomenclatureKey in translate.nomenclature.data[translate.language.getLocal()][translate.to]){
2369				//nomenclatureKey 便是自定义术语的原始文本,值是要替换为的文本
2370				//console.log(nomenclatureKey);
2371				//自定义属于的指定的结果字符串
2372				var nomenclatureValue = translate.nomenclature.data[translate.language.getLocal()][translate.to][nomenclatureKey];
2373				
2374				textArray = translate.addNodeToQueueTextAnalysis(uuid, node, textArray, attribute, nomenclatureKey, nomenclatureValue);
2375			
2376				if(typeof(nomenclatureKeyArray) != 'undefined'){
2377					nomenclatureKeyArray.push(nomenclatureKey);
2378				}
2379
2380			}
2381
2382			if(typeof(translate.temp_nomenclature[translate.language.getLocal()]) == 'undefined'){
2383				translate.temp_nomenclature[translate.language.getLocal()] = nomenclatureKeyArray;
2384			}
2385		}
2386		/**** v3.10.2.20241206 - 自定义术语能力全面优化 - end ****/
2387		
2388
2389
2390
2391
2392		
2393		for(var tai = 0; tai<textArray.length; tai++){
2394			if(textArray[tai].trim().length == 0){
2395				continue;
2396			}
2397
2398			//判断是否出现在自定义忽略字符串
2399			if(translate.ignore.text.indexOf(textArray[tai].trim()) > -1){
2400				//console.log(textArray[tai]+' 是忽略翻译的文本,不翻译');
2401				continue;
2402			}
2403
2404			//判断是否出现在自定义术语的
2405			if(typeof(translate.temp_nomenclature[translate.language.getLocal()]) != 'undefined'){
2406				if(translate.temp_nomenclature[translate.language.getLocal()].indexOf(textArray[tai].trim()) > -1){
2407					//console.log(textArray[tai]+' 是自定义术语,不翻译');
2408					continue;
2409				}
2410			}
2411
2412			translate.addNodeToQueueAnalysis(uuid, node, textArray[tai], attribute);
2413		}
2414		
2415		//this.nodeQueue[lang][key][this.nodeQueue[lang][key].length]=node; //往数组中追加
2416	},
2417
2418	/**
2419	 * 服务于上面的 addNodeToQueue ,用于区分不同type情况,进行调用此加入 translate.nodeQueue
2420	 * uuid, node, attribute 这及个参数说明见 addNodeToQueue 的参数说明,相同
2421	 * textArray 进行处理的要翻译的文本数组。这个最开始只是一个,但是命中后分割的多了也就变成多个了
2422	 * nomenclatureKey 替换的原始文本,也就是自定义术语的key部分
2423	 * nomenclatureValue 替换的目标文本,也就是自定义术语的value部分 。 如果 nomenclatureKey = nomenclatureValue 则是自定义忽略翻译的文本。这个文本不被翻译
2424	 * @return 处理过后的 textArray 如果没有命中则返回的是传入的 textArray ,命中了则是切割以及删除原本判断的text之后的 textArray
2425	 */
2426	addNodeToQueueTextAnalysis:function(uuid, node,textArray, attribute, nomenclatureKe
2426y, nomenclatureValue){
2427		var deleteTextArray = new Array();	//记录要从 textArray 中删除的字符串
2428
2429		for(var tai = 0; tai<textArray.length; tai++){
2430			var text = textArray[tai];
2431
2432			if(text.trim() == nomenclatureValue.trim()){
2433				//这里是自定义术语被替换后,重新扫描时扫出来的,那么直接忽略,不做任何处理。因为自定义术语的结果就是最终结果了
2434				continue;
2435			}
2436
2437			//判断一下原始文本是否有出现在了这个word要翻译的字符串中
2438			var wordKeyIndex = text.indexOf(nomenclatureKey);
2439			if(wordKeyIndex > -1){
2440				//出现了,那么需要将其立即进行更改,将自定义术语定义的结果渲染到页面中,并且将 word 要翻译的字符串中,自定义术语部分删除,只翻译除了自定义术语剩余的部分
2441				//console.log(text+' --> '+nomenclatureKey);
2442
2443				
2444				/*
2445				 * 判断一下这个text中,出现匹配自定义术语的部分,是否是已经被替换过了,比如要将 好 替换为  你好 ,这里的好会重复替换。这里是判断这种情况
2446				 * 其中要判断一下 key 不等于value,因为key等于value,属于是指定这个key不被翻译的情况
2447				 */
2448				if(nomenclatureKey != nomenclatureValue){
2449					var substringStart = wordKeyIndex-nomenclatureValue.length;
2450					if(substringStart < 0){
2451						substringStart = 0;
2452					}
2453					var substringEnd = wordKeyIndex+nomenclatureValue.length;
2454					if(substringEnd > text.length){
2455						substringEnd = text.length;
2456					}
2457					var nomenclatureValueJudgement = text.substring(substringStart, substringEnd);
2458					//console.log(text+', '+nomenclatureValueJudgement);
2459					if(nomenclatureValueJudgement.indexOf(nomenclatureValue) > -1){
2460						//已经替换过了,可能会重复替换,所以忽略掉
2461						continue;
2462					}
2463				}
2464
2465
2466				//判断当前是否是英语及变种,也就是单词之间需要有空格的,如果前后没有空格,要补充上空格
2467				if(translate.language.wordBlankConnector(translate.to)){
2468					if(wordKeyIndex > 0){
2469						//它前面还有文本,判断它前面的文本是否是空格,如果不是,那么要补充上空格
2470						var before = text.charAt(wordKeyIndex-1);
2471						//console.log(before);
2472						if(!(/\s/.test(before))){
2473							//不是空白字符,补充上一个空格,用于将两个单词隔开
2474							nomenclatureValue = ' '+nomenclatureValue
2475						}
2476					}
2477					if(wordKeyIndex + nomenclatureKey.length < text.length){
2478						//它后面还有文本,判断它前面的文本是否是空格,如果不是,那么要补充上空格
2479						var after = text.charAt(wordKeyIndex + nomenclatureKey.length);
2480						//console.log(after);
2481						if(!(/\s/.test(before))){
2482							//不是空白字符,补充上一个空格,用于将两个单词隔开
2483							nomenclatureValue = nomenclatureValue+' ';
2484						}
2485					}
2486				}
2487				
2488				//如果是自定义术语的key等于value,则是属于指定的某些文本不进行翻译的情况,所以这里要单独判断一下
2489				//console.log(nomenclatureKey+':'+nomenclatureValue);
2490				if(nomenclatureKey != nomenclatureValue){
2491					translate.element.nodeAnalyse.set(node, nomenclatureKey, nomenclatureValue, attribute);
2492				}
2493				
2494				
2495				//从 text 中将自定义术语的部分删掉,自定义术语的不被翻译
2496				var wordSplits = text.split(nomenclatureKey);
2497				var isPushTextArray = false;
2498				for(var index = 0; index < wordSplits.length; index++){
2499					//console.log(index);
2500					var subWord = wordSplits[index]; //分割后的子字符串
2501					if(subWord.trim().length == 0){
2502						continue;
2503					}
2504					//console.log(subWord);
2505
2506					//将其加入 textArray 中
2507					textArray.push(subWord);
2508					deleteTextArray.push(text);
2509				}
2510				
2511				//console.log(wordSplits);
2512				//自定义术语适配完后就直接退出了
2513				//return wordSplits;
2514			}
2515
2516		}
2517
2518		//console.log(deleteTextArray)
2519		//从 textArray 中删除
2520		if(deleteTextArray.length > 0){
2521			for(var di = 0; di<deleteTextArray.length; di++){
2522				let index = textArray.indexOf(deleteTextArray[di]); 
2523				if (index > -1) {   
2524					//console.log(textArray);
2525					//console.log(deleteTextArray[di]);
2526					textArray.splice(index, 1); 
2527					//console.log(textArray);
2528				}
2529			}
2530		}
2531
2532		return textArray;
2533	},
2534
2535	/*
2536
2537		服务于上面的 addNodeToQueue ,用于区分不同type情况,进行调用此加入 translate.nodeQueue
2538		uuid, node, attribute 这五个参数说明见 addNodeToQueue 的参数说明,相同
2539		
2540		word 要实际进行翻译的文本,也就是要把它拿来进行通过后端翻译接口进行翻译的文本
2541		lang 当前要翻译的文本的语种,如 english
2542		beforeText 参见 translate.nodeQueue 注释中第七维的解释
2543		afterText 参见 translate.nodeQueue 注释中第七维的解释
2544
2545	*/
2546	addNodeToQueueAnalysis:function(uuid, node, text, attribute){
2547		//获取当前是什么语种
2548		//var langs = translate.language.get(text);
2549		var textRecognition = translate.language.recognition(text);
2550		langs = textRecognition.languageArray;
2551		//console.log('langs');
2552		//console.log(langs);
2553
2554		
2555		//过滤掉要转换为的目标语种,比如要转为英语,那就将本来是英语的部分过滤掉,不用再翻译了
2556		if(typeof(langs[translate.to]) != 'undefined'){
2557			delete langs[translate.to];
2558		}
2559		
2560		var isWhole = translate.whole.isWhole(node);
2561		//console.log('isWhole:'+isWhole+', '+text);
2562
2563		if(!isWhole){
2564			//常规方式,进行语种分类
2565
2566
2567			/* if(this.nodeQueue[lang] == null || typeof(this.nodeQueue[lang]) == 'undefined'){
2568				this.nodeQueue[lang] = new Array();
2569			} 
2570			//创建二维数组
2571			if(this.nodeQueue[lang][key] == null || typeof(this.nodeQueue[lang][key]) == 'undefined'){
2572				this.nodeQueue[lang][key] = new Array();
2573			}
2574			*/
2575			//console.log(langs);
2576			
2577			for(var lang in langs) {
2578				//创建二维数组, key为语种,如 english
2579				/*
2580				放到了 translate.addNodeQueueItem 进行判断
2581				if(translate.nodeQueue[uuid]['list'][lang] == null || typeof(translate.nodeQueue[uuid]['list'][lang]) == 'undefined'){
2582					translate.nodeQueue[uuid]['list'][lang] = new Array();
2583				}
2584				*/
2585				//console.log('|'+langs[lang].length);
2586				//遍历出该语种下有哪些词需要翻译
2587				for(var word_index = 0; word_index < langs[lang].list.length; word_index++){
2588					//console.log('start:'+word_index)
2589					//console.log(langs[lang].list[word_index]);
2590					if(typeof(langs[lang].list[word_index]) == 'undefined' || typeof(langs[lang].list[word_index]['text']) == 'undefined'){
2591						//理论上应该不会,但多加个判断
2592						continue;
2593					}
2594					var word = langs[lang].list[word_index]['text']; //要翻译的词
2595					var beforeText = langs[lang].list[word_index]['beforeText'];
2596					var afterText = langs[lang].list[word_index]['afterText'];
2597
2598					//console.log(lang+' - '+word);
2599					translate.addNodeQueueItem(uuid, node, word, attribute, lang, beforeText, afterText);
2600
2601					/*
2602					var hash = translate.util.hash(word); 	//要翻译的词的hash					
2603					//创建三维数组, key为要通过接口翻译的文本词或句子的 hash (注意并不是node的文本,而是node拆分后的文本)
2604					if(translate.nodeQueue[uuid]['list'][lang][hash] == null || typeof(translate.nodeQueue[uuid]['list'][lang][hash]) == 'undefined'){
2605						translate.nodeQueue[uuid]['list'][lang][hash] = new Array();
2606						
2607						translate.nodeQueue[uuid]['list'][lang][hash]['nodes'] = new Array();
2608						translate.nodeQueue[uuid]['list'][lang][hash]['original'] = word;
2609						translate.nodeQueue[uuid]['list'][lang][hash]['translateText'] = translate.nomenclature.dispose(word); //自定义术语处理
2610						//translate.nodeQueue[uuid]['list'][lang][hash]['beforeText'] = beforeText;
2611						//translate.nodeQueue[uuid]['list'][lang][hash]['afterText'] = afterText;
2612						//translate.nodeQueue[uuid]['list'][lang][hash]['attribute'] = attribute; //放入 nodes[index][attribute] 元素中
2613						
2614						//其中key: nodes 是第四维数组,里面存放具体的node元素对象
2615						
2616
2617						//console.log(translate.nodeQueue[uuid]['list'][lang][hash]);
2618					}
2619					
2620					var isEquals = false; //queue中是否已经加入过这个node了(当然是同一hash同一node情况)
2621					if(typeof(node.isSameNode) != 'undefined'){	//支持 isSameNode 方法判断对象是否相等
2622						for(var node_index = 0; node_index < translate.nodeQueue[uuid]['list'][lang][hash]['nodes'].length; node_index++){
2623							if(node.isSameNode(translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]['node'])){
2624								//相同,那就不用在存入了
2625								//console.log('相同,那就不用在存入了')
2626								isEquals = true;
2627								//console.log(node)
2628								continue;
2629							}
2630						}
2631					}
2632					if(isEquals){
2633						//相同,那就不用在存入了
2634						continue;
2635					}
2636
2637					//往五维数组nodes中追加node元素
2638					var nodesIndex = translate.nodeQueue[uuid]['list'][lang][hash]['nodes'].length;
2639					translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][nodesIndex] = new Array();
2640					translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][nodesIndex]['node']=node; 
2641					translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][nodesIndex]['attribute']=attribute;
2642					translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][nodesIndex]['beforeText'] = beforeText;
2643					translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][nodesIndex]['afterText'] = afterText;
2644					
2645					*/
2646
2647					//console.log('end:'+word_index)
2648				}
2649				
2650			}
2651			
2652
2653
2654
2655		}else{
2656			//直接翻译整个元素内的内容,不再做语种分类,首先删除英文,然后将出现次数最多的语种作为原本语种
2657			var lang = textRecognition.languageName;
2658			//console.log(lang+' - '+text);
2659			translate.addNodeQueueItem(uuid, node, text, attribute, lang, '', '');
2660		}
2661
2662
2663	},
2664
2665	/*
2666
2667		服务于上面的 addNodeToQueue ,用于区分不同type情况,进行调用此加入 translate.nodeQueue
2668		uuid, node, attribute 这五个参数说明见 addNodeToQueue 的参数说明,相同
2669		
2670		word 要实际进行翻译的文本,也就是要把它拿来进行通过后端翻译接口进行翻译的文本
2671		lang 当前要翻译的文本的语种,如 english
2672		beforeText 参见 translate.nodeQueue 注释中第七维的解释
2673		afterText 参见 translate.nodeQueue 注释中第七维的解释
2674
2675	*/
2676	addNodeQueueItem:function(uuid, node, word, attribute, lang, beforeText, afterText){
2677		//创建二维数组, key为语种,如 english
2678		if(translate.nodeQueue[uuid]['list'][lang] == null || typeof(translate.nodeQueue[uuid]['list'][lang]) == 'undefined'){
2679			translate.nodeQueue[uuid]['list'][lang] = new Array();
2680		}
2681		//console.log(word)
2682		//var word = text;	//要翻译的文本
2683		var hash = translate.util.hash(word); 	//要翻译的文本的hash
2684
2685		//创建三维数组, key为要通过接口翻译的文本词或句子的 hash 。这里翻译的文本也就是整个node元素的内容了,不用在做拆分了
2686		if(translate.nodeQueue[uuid]['list'][lang][hash] == null || typeof(translate.nodeQueue[uuid]['list'][lang][hash]) == 'undefined'){
2687			translate.nodeQueue[uuid]['list'][lang][hash] = new Array();
2688
2689			/*
2690			 * 创建四维数组,存放具体数据
2691			 * key: nodes 包含了这个hash的node元素的数组集合,array 多个。其中
2692			 		nodes[index]['node'] 存放当前的node元素
2693			 		nodes[index]['attribute'] 存放当前hash,也就是翻译文本针对的是什么,是node本身(nodeValue),还是 node 的某个属性,比如title属性。如果这里不为空,那就是针对的属性操作的
2694			 * key: original 原始的要翻译的词或句子,html加载完成但还没翻译前的文本,用于支持当前页面多次语种翻译切换而无需跳转
2695			 * beforeText、afterText:见 translate.nodeQueue 的说明
2696			 */
2697			translate.nodeQueue[uuid]['list'][lang][hash]['nodes'] = new Array();
2698			translate.nodeQueue[uuid]['list'][lang][hash]['original'] = word;
2699			//自定义术语处理在此前面已经执行过了,所以这个废弃,不需要处理自定义术语部分了
2700			//translate.nodeQueue[uuid]['list'][lang][hash]['translateText'] = translate.nomenclature.dispose(word); 
2701			translate.nodeQueue[uuid]['list'][lang][hash]['translateText'] = word;
2702			//console.log(word)
2703
2704
2705
2706			//其中key: nodes 是第四维数组,里面存放具体的node元素对象
2707		}
2708
2709
2710		var isEquals = false; //queue中是否已经加入过这个node了(当然是同一hash同一node情况)
2711		if(typeof(node.isSameNode) != 'undefined'){	//支持 isSameNode 方法判断对象是否相等
2712			for(var node_index = 0; node_index < translate.nodeQueue[uuid]['list'][lang][hash]['nodes'].length; node_index++){
2713				if(node.isSameNode(translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]['node'])){
2714					//相同,那就不用在存入了
2715					//console.log('相同,那就不用在存入了')
2716					isEquals = true;
2717					//console.log(node)
2718					continue;
2719				}
2720			}
2721		}
2722		if(isEquals){
2723			//相同,那就不用在存入了
2724			return;
2725		}
2726
2727		//往五维数组nodes中追加node元素
2728		var nodesIndex = translate.nodeQueue[uuid]['list'][lang][hash]['nodes'].length;
2729		translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][nodesIndex] = new Array();
2730		translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][nodesIndex]['node']=node; 
2731		translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][nodesIndex]['attribute']=attribute;
2732		translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][nodesIndex]['beforeText'] = beforeText;
2733		translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][nodesIndex]['afterText'] = afterText;
2734		
2735
2736	},
2737
2738	//全部翻译,node内容全部翻译,而不是进行语种提取,直接对node本身的全部内容拿出来进行直接全部翻译
2739	whole:{
2740		isEnableAll:false, //是否开启对整个html页面的整体翻译,也就是整个页面上所有存在的能被翻译的全部会采用整体翻译的方式。默认是 false不开启		
2741
2742		enableAll:function(){
2743			translate.whole.isEnableAll = true;
2744		},
2745
2746		/*
2747			一下三个,也就是  class tag id 分别存储加入的值。使用参考:http://translate.zvo.cn/42563.html
2748		*/
2749		class:[],
2750		tag:[],
2751		id:[],
2752
2753		//运行时出现自检并在浏览器控制台提示性文本。 
2754		//在执行翻译,也就是 execute() 时,会调用此方法。
2755		executeTip:function(){
2756			if(translate.whole.class.length == 0 && translate.whole.tag.length == 0 && translate.whole.id.length == 0){
2757				
2758			}else{
2759				console.log('您开启了 translate.whole 此次行为避开了浏览器端的文本语种自动识别,而是暴力的直接对某个元素的整个文本进行翻译,很可能会产生非常大的翻译量,请谨慎!有关每日翻译字符的说明,可参考: http://translate.zvo.cn/42557.html ');
2760			}
2761
2762			if(translate.whole.tag.indexOf('html') > -1){
2763				console.log('自检发现您设置了 translate.whole.tag 其中有 html ,这个是不生效的,最大只允许设置到 body ');
2764			}
2765		},
2766
2767		//当前元素是属于全部翻译定义的元素
2768		/*
2769			传入一个元素,判断这个元素是否是被包含的。 这个会找父类,看看父类中是否包含在其之中。
2770			return true是在其中,false不再其中
2771		*/
2772		isWhole:function(ele){
2773
2774			//如果设置了 class|tag|id 其中某个,或者 all=true ,那么就是启用,反之未启用
2775			if((translate.whole.class.length == 0 && translate.whole.tag.length == 0 && translate.whole.id.length == 0) && translate.whole.isEnableAll == false){
2776				//未设置,那么直接返回false
2777				return false;
2778			}
2779			if(ele == null || typeof(ele) == 'undefined'){
2780				return false;
2781			}
2782			if(translate.whole.isEnableAll){
2783				return true;
2784			}
2785
2786			var parentNode = ele;
2787			var maxnumber = 100;	//最大循环次数,避免死循环
2788			while(maxnumber-- > 0){
2789				if(parentNode == null || typeof(parentNode) == 'undefined'){
2790					//没有父元素了
2791					return false;
2792				}
2793
2794				//判断Tag
2795				//var tagName = parentNode.nodeName.toLowerCase(); //tag名字,小写
2796				var nodename = translate.element.getNodeName(parentNode).toLowerCase(); //tag名字,小写
2797				if(nodename.length > 0){
2798					//有nodename
2799					if(nodename == 'html' || nodename == '#document'){
2800						//上层元素已经是顶级元素了,那肯定就不是了
2801						return false;
2802					}
2803					if(translate.whole.tag.indexOf(nodename) > -1){
2804						//发现ignore.tag 当前是处于被忽略的 tag
2805						return true;
2806					}
2807				}
2808				
2809
2810				//判断class name
2811				if(parentNode.className != null){
2812					var classNames = parentNode.className;
2813					if(classNames == null || typeof(classNames) != 'string'){
2814						continue;
2815					}
2816					//console.log('className:'+typeof(classNames));
2817					//console.log(classNames);
2818					classNames = classNames.trim().split(' ');
2819					for(var c_index = 0; c_index < classNames.length; c_index++){
2820						if(classNames[c_index] != null && classNames[c_index].trim().length > 0){
2821							//有效的class name,进行判断
2822							if(translate.whole.class.indexOf(classNames[c_index]) > -1){
2823								//发现ignore.class 当前是处于被忽略的 class
2824								return true;
2825							}
2826						}
2827					}					
2828				}
2829
2830				//判断id
2831				if(parentNode.id != null && typeof(parentNode.id) != 'undefined'){
2832					//有效的class name,进行判断
2833					if(translate.whole.id.indexOf(parentNode.id) > -1){
2834						//发现ignore.id 当前是处于被忽略的 id
2835						return true;
2836					}
2837				}
2838
2839				//赋予判断的元素向上一级
2840				parentNode = parentNode.parentNode;
2841			}
2842
2843			return false;
2844		}
2845	},
2846
2847	language:{
2848		/*	
2849			英语的变种语种,也就是在英语26个字母的基础上加了点别的特殊字母另成的一种语言,而这些语言是没法直接通过识别字符来判断出是哪种语种的
2850			v3.4.2 增加
2851			当前先只加上:
2852			法语、意大利语、德语
2853		*/
2854		englishVarietys : ['french','italian','deutsch'],
2855
2856		//当前本地语种,本地语言,默认是简体中文。设置请使用 translate.language.setLocal(...)。不可直接使用,使用需用 getLocal()
2857		local:'',
2858
2859		/*
2860		 * v3.12增加, 是否会翻译本地语种,默认是false,不会翻译。
2861		 * 比如当前设置的本地语种是简体中文, 但是网页中也有一段英文, 如果设置了translate.to 为中文,也就是要以中文显示 默认是false的情况下,整个页面是不会被任何翻译的,也就是有的那段英文也不会进行任何翻译,依旧是显示英文。
2862		 * 如果这里设置为 true, 则英文也会被翻译,只要不是中文的,都会被翻译为要显示的语种,也就是都会被翻译为中文。
2863		 */
2864		translateLocal:false,
2865
2866		/*
2867			翻译语种范围
2868			比如传入 ['chinese_simplified','chinese_traditional','english'] 则表示仅对网页中的简体中文、繁体中文、英文 进行翻译,而网页中出现的其他的像是法语、韩语则不会进行翻译
2869			如果为空 [],则是翻译时,翻译网页中的所有语种			
2870			设置方式为:  translate.language.translateLanguagesRange = ['chinese_simplified','chinese_traditional']
2871		*/
2872		translateLanguagesRange: [], 
2873		//传入语种。具体可传入哪些参考: http://api.translate.zvo.cn/doc/language.json.html
2874		setLocal:function(languageName){
2875			//translate.setUseVersion2(); //Set to use v2.x version
2876			translate.useVersion = 'v2';
2877			translate.language.local = languageName;
2878		},
2879		//获取当前本地语种,本地语言,默认是简体中文。设置请使用 translate.language.setLocal(...)
2880		getLocal:function(){
2881			//判断是否设置了本地语种,如果没设置,自动给其设置
2882			if(translate.language.local == null || translate.language.local.length < 1){
2883				translate.language.autoRecognitionLocalLanguage();
2884			}
2885			return translate.language.local;
2886		},
2887		/*
2888			获取当前语种。
2889			比如当前设置的本地语种是简体中文,用户并未切换其他语种,那么这个方法将返回本地当前的语种,也就是等同于 translate.language.getLocal()
2890			如果用户切换为英语进行浏览,那么这个方法将返回翻译的目标语种,也就是 english
2891		*/
2892		getCurrent:function(){
2893			var to_storage = translate.storage.get('to');
2894			if(to_storage != null && typeof(to_storage) != 'undefined' && to_storage.length > 0){
2895				//之前有过使用,并且主动设置过目标语种
2896				return to_storage;
2897			}
2898			return translate.language.getLocal();
2899		},
2900		//如果第一次用,默认以什么语种显示。
2901		//比如本地当前语种是简体中文,这里设置为english,那么用户第一次使用时,会自动翻译为english进行显示。如果用户手动切换为其他语种比如韩语,那么就遵循用户手动切换的为主,显示韩语。
2902		setDefaultTo:function(languageName){
2903			var to_storage = translate.storage.get('to');
2904			if(to_storage != null && typeof(to_storage) != 'undefined' && to_storage.length > 0){
2905				//之前有过使用,并且主动设置过目标语种,那么不进行处理
2906			}else{
2907				//没有设置过,进行处理
2908				translate.storage.set('to', languageName);
2909				translate.to = languageName;
2910			}
2911		},
2912		/*
2913			清除历史翻译语种的缓存
2914		*/
2915		clearCacheLanguage:function(){
2916			translate.to = '';
2917			translate.storage.set('to','');
2918		},
2919		//根据URL传参控制以何种语种显示
2920		//设置可以根据当前访问url的某个get参数来控制使用哪种语言显示。
2921		//比如当前语种是简体中文,网页url是http://translate.zvo.cn/index.html ,那么可以通过在url后面增加 language 参数指定翻译语种,来使网页内容以英文形态显示 http://translate.zvo.cn/index.html?language=english
2922		setUrlParamControl:function(paramName){
2923			if(typeof(paramName) == 'undefined' || paramName.length < 1){
2924				paramName = 'language';
2925			}
2926			var paramValue = translate.util.getUrlParam(paramName);
2927			if(typeof(paramValue) == 'undefined'){
2928				return;
2929			}
2930			if(paramValue == '' || paramValue == 'null' || paramValue == 'undefined'){
2931				return;
2932			}
2933
2934			translate.storage.set('to', paramValue);
2935			translate.to = paramValue;
2936		},
2937		//自动识别当前页面是什么语种
2938		autoRecognitionLocalLanguage:function(){
2939			if(translate.language.local != null && translate.language.local.length > 2){
2940				//已设置过了,不需要再设置
2941				return;
2942			}
2943
2944			var bodyText = document.body.outerText;
2945			if(bodyText == null || typeof(bodyText) == 'undefined' || bodyText.length < 1){
2946				//未取到,默认赋予简体中文
2947				translate.language.local = 'chinese_simplified';
2948				return;
2949			}
2950
2951			bodyText = bodyText.replace(/\n|\t|\r/g,''); //将回车换行等去掉
2952
2953			//默认赋予简体中文
2954			translate.language.local = 'chinese_simplified';
2955			var recognition = translate.language.recognition(bodyText);
2956			translate.language.local = recognition.languageName;
2957			return translate.language.local;
2958			/* v3.1优化
2959			var langs = new Array(); //上一个字符的语种是什么,当前字符向上数第一个字符。格式如 ['language']='english', ['chatstr']='a', ['storage_language']='english'  这里面有3个参数,分别代表这个字符属于那个语种,其字符是什么、存入了哪种语种的队列。因为像是逗号,句号,一般是存入本身语种中,而不是存入特殊符号中。 
2960			for(var i=0; i<bodyText.length; i++){
2961				var charstr = bodyText.charAt(i);
2962				var lang = translate.language.getCharLanguage(charstr);
2963				if(lang == ''){
2964					//未获取到,未发现是什么语言
2965					//continue;
2966					lang = 'unidentification';
2967				}
2968				langs.push(lang);
2969			}
2970
2971			//从数组中取出现频率最高的
2972			var newLangs = translate.util.arrayFindMaxNumber(langs);
2973
2974			//移除数组中的特殊字符
2975			var index = newLangs.indexOf('specialCharacter');
2976			if(index > -1){
2977				newLangs.splice(index,1); //移除数组中的特殊字符
2978			}
2979
2980			if(newLangs.length > 0){
2981				//找到排序出现频率最多的
2982				translate.language.local = newLangs[0];
2983			}else{
2984				//没有,默认赋予简体中文
2985				translate.language.local = 'chinese_simplified';
2986			}
2987			*/
2988		},
2989		
2990		/*
2991		 * 获取当前字符是什么语种。返回值是一个语言标识,有  chinese_simplified简体中文、japanese日语、korean韩语、
2992		 * str : node.nodeValue 或 图片的 node.alt 等
2993		 * 如果语句长,会全句翻译,以保证翻译的准确性,提高可读性。
2994		 * 如果语句短,会自动将特殊字符、要翻译的目标语种给过滤掉,只取出具体的要翻译的目标语种文本
2995		 *
2996		 * 返回 存放不同语言的数组,格式如
2997		 *  	[
2998					"english":[
2999						{beforeText: '', afterText: '', text: 'emoambue hag'},
3000						......
3001					],
3002					"japanese":[
3003						{beforeText: ' ', afterText: ' ', text: 'ẽ '},
3004						......
3005					]
3006		 		]
3007		 * 		
3008		 */
3009		get:function(str){
3010			//将str拆分为单个char进行判断
3011
3012			var langs = new Array(); //当前字符串包含哪些语言的数组,其内如 english
3013			var langStrs = new Array();	//存放不同语言的文本,格式如 ['english'][0] = 'hello'
3014			var upLangs = []; //上一个字符的语种是什么,当前字符向上数第一个字符。格式如 ['language']='english', ['chatstr']='a', ['storage_language']='english'  这里面有3个参数,分别代表这个字符属于那个语种,其字符是什么、存入了哪种语种的队列。因为像是逗号,句号,一般是存入本身语种中,而不是存入特殊符号中。 
3015			var upLangsTwo = []; //上二个字符的语种是什么 ,当前字符向上数第二个字符。 格式如 ['language']='english', ['chatstr']='a', ['storage_language']='english'  这里面有3个参数,分别代表这个字符属于那个语种,其字符是什么、存入了哪种语种的队列。因为像是逗号,句号,一般是存入本身语种中,而不是存入特殊符号中。
3016			
3017			//var upLangs = ''; //上一个字符的语种是什么,格式如 english
3018			for(var i=0; i<str.length; i++){
3019				var charstr = str.charAt(i);
3020				//console.log('charstr:'+charstr)
3021				var lang = translate.language.getCharLanguage(charstr);
3022				if(lang == ''){
3023					//未获取到,未发现是什么语言
3024					//continue;
3025					lang = 'unidentification';
3026				}
3027				var result = translate.language.analyse(lang, langStrs, upLangs, upLangsTwo, charstr);
3028				//console.log(result)
3029				langStrs = result['langStrs'];
3030				//记录上几个字符
3031				if(typeof(upLangs['language']) != 'undefined'){
3032					upLangsTwo['language'] = upLangs['language'];
3033					upLangsTwo['charstr'] = upLangs['charstr'];
3034					upLangsTwo['storage_language'] = upLangs['storage_language'];
3035				}
3036				//upLangs['language'] = lang;
3037				upLangs['language'] = result['storage_language'];
3038				upLangs['charstr'] = charstr;
3039				upLangs['storage_language'] = result['storage_language'];
3040				//console.log(result['storage_language'])
3041				//console.log(upLangs['language']);
3042				langs.push(lang);
3043			}
3044			
3045			//console.log(langStrs);
3046			
3047			//console.log(langs);
3048			//console.log(langStrs);
3049
3050/*
3051			//从数组中取出现频率最高的
3052			var newLangs = translate.util.arrayFindMaxNumber(langs);
3053			
3054			//移除当前翻译目标的语言。因为已经是目标预言了,不需要翻译了
3055			var index = newLangs.indexOf(translate.to);
3056			if(index > -1){
3057				newLangs.splice(index,1); //移除
3058			}
3059
3060			//移除特殊字符
3061			var index = newLangs.indexOf('specialCharacter');
3062			if(index > -1){
3063				newLangs.splice(index,1); //移除数组中的特殊字符
3064			}
3065
3066			if(newLangs.length > 0){
3067				//还剩一个或多个,(如果是多个,那应该是这几个出现的频率一样,所以取频率最高的时返回了多个)
3068				return newLangs[0];
3069			}else{
3070				//没找到,直接返回空字符串
3071				return '';
3072			}
3073			*/
3074			
3075			
3076			//去除特殊符号
3077			//for(var i = 0; i<langStrs.length; i++){
3078			/*
3079			var i = 0;
3080			for(var item in langStrs) {
3081				if(item == 'unidentification' || item == 'specialCharacter'){
3082					//langStrs.splice(i,1); //移除
3083					delete langStrs[item];
3084				}
3085				console.log(item);
3086				i++;
3087			}
3088			*/
3089			
3090			//console.log(langStrs);
3091			if(typeof(langStrs['unidentification']) != 'undefined'){
3092				delete langStrs['unidentification'];
3093			}
3094			if(typeof(langStrs['specialCharacter']) != 'undefined'){
3095				delete langStrs['specialCharacter'];
3096			}
3097			if(typeof(langStrs['number']) != 'undefined'){
3098				delete langStrs['number'];
3099			}
3100			
3101			
3102			//console.log('get end');
3103			return langStrs;
3104		},
3105
3106		/*
3107		 * 识别字符串是什么语种。它是 get() 的扩展,以代替get返回更多
3108		 * str : 要识别的字符串
3109		 *
3110		 * 返回 存放不同语言的数组,格式如
3111		 *  	
3112			{
3113				languageName: 'english',
3114		 		languageArray:[
3115					"english":[
3116						{beforeText: '', afterText: '', text: 'emoambue hag'},
3117						......
3118					],
3119					"japanese":[
3120						{beforeText: ' ', afterText: ' ', text: 'ẽ '},
3121						......
3122					]
3123		 		]
3124		 	}
3125		 	languageName 是当前字符串是什么语种。它的识别有以下特点
3125:
3126		 		1. 如果出现英语跟中文、法语、德语等混合的情况,也就是不纯粹英语的情况,那么会以其他语种为准,而不是识别为英语。不论英语字符出现的比例占多少。
3127		 		2. 如果出现简体中文跟繁体中文混合的情况,那么识别为繁体中文。不论简体中文字符出现的比例占多少。
3128				3. 除了以上两种规则外,如果出现了多个语种,那么会识别为出现字符数量最多的语种当做当前句子的语种。(注意是字符数,而不是语种的数组数)
3129			languageArray 对传入字符串进行分析,识别出都有哪些语种,每个语种的字符是什么
3130		 * 		
3131		 */
3132		recognition:function(str){
3133			var langs = translate.language.get(str);
3134			//var langkeys = Object.keys(langs);
3135			//console.log(langkeys);
3136			var langsNumber = []; //key  语言名,  value 语言字符数
3137			var langsNumberOriginal = []; //同上,只不过这个不会进行清空字符数
3138			var allNumber = 0;//总字数
3139			for(var key in langs){
3140				if(typeof(langs[key]) != 'object'){
3141					continue;
3142				}
3143				var langStrLength = 0;
3144				for(var ls = 0; ls < langs[key].length; ls++){
3145					langStrLength = langStrLength + langs[key][ls].text.length;
3146				}
3147				allNumber = allNumber + langStrLength;
3148				langsNumber[key] = langStrLength;
3149				langsNumberOriginal[key] = langStrLength;
3150			}
3151
3152			//过滤 语种的字符数小于总字符数 百分之五的,低于这个数,将忽略
3153			var langkeys = [];
3154			for(var lang in langsNumber){
3155				if(langsNumber[lang]/allNumber > 0.05){
3156					langkeys[langkeys.length] = lang+'';
3157				}
3158			}
3159
3160
3161			if(langkeys.length > 1 && langkeys.indexOf('english') > -1){
3162				//console.log('出现了english, 并且english跟其他语种一起出现,那么删除english,因为什么法语德语乱七八糟的都有英语。而且中文跟英文一起,如果认为是英文的话,有时候中文会不被翻译');
3163				//langkeys.splice(langkeys.indexOf('english'), 1); 
3164				langsNumber['english'] = 0;
3165			}
3166
3167			if(langkeys.indexOf('chinese_simplified') > -1 && langkeys.indexOf('chinese_traditional') > -1){
3168				//如果简体中文跟繁体中文一起出现,那么会判断当前句子为繁体中文。
3169				//langkeys.splice(langkeys.indexOf('chinese_simplified'), 1); 
3170				langsNumber['chinese_simplified'] = 0;
3171			}
3172
3173
3174			//从 langsNumber 中找出字数最多的来
3175			var maxLang = ''; //字数最多的语种
3176			var maxNumber = 0;
3177			for(var lang in langsNumber){
3178				if(langsNumber[lang] > maxNumber){
3179					maxLang = lang;
3180					maxNumber = langsNumber[lang];
3181				}
3182			}
3183
3184			//重新组合返回值的 languageArray
3185			var languageArray = {};
3186			for(var lang in langs){
3187				languageArray[lang] = {};
3188				languageArray[lang].number = langsNumberOriginal[lang];
3189				languageArray[lang].list = langs[lang];
3190			}
3191
3192			var result = {
3193				languageName: maxLang,
3194				languageArray: languageArray
3195			};
3196
3197			//v3.4.2 增加,增加对法语等英文变种的小语种的识别
3198			if(result.languageName == 'english'){
3199				//如果识别的语种是英语,那便不仅仅是英语,因为法语、德语、意大利语也是在英语里面,还要判断一下是否用户自己设置了具体语种是否是英语延伸的小语种
3200
3201				var localLang = translate.language.getLocal(); //本地语种
3202				if(translate.language.englishVarietys.indexOf(localLang) > -1){
3203					//发现当前设置的是小语种,那么将当前识别的语种识别为 本地设置的这个小语种。
3204					result.languageName = localLang;
3205				}
3206			}
3207
3208			return result;
3209		},
3210		// 传入一个char,返回这个char属于什么语种,返回如 chinese_simplified、english  如果返回空字符串,那么表示未获取到是什么语种
3211		getCharLanguage:function(charstr){
3212			if(charstr == null || typeof(charstr) == 'undefined'){
3213				return '';
3214			}
3215			
3216			if(this.russian(charstr)){
3217				return 'russian';
3218			}
3219			if(this.english(charstr)){
3220				return 'english';
3221			}
3222			if(this.specialCharacter(charstr)){
3223				return 'specialCharacter';
3224			}
3225			if(this.number(charstr)){
3226				return 'number';	
3227			}
3228
3229			//中文的判断包含两种,简体跟繁体
3230			var chinesetype = this.chinese(charstr);
3231			if(chinesetype == 'simplified'){
3232				return 'chinese_simplified';
3233			}else if(chinesetype == 'traditional'){
3234				return 'chinese_traditional';
3235			}
3236
3237			if(this.japanese(charstr)){
3238				return 'japanese';
3239			}
3240			if(this.korean(charstr)){
3241				return 'korean';
3242			}
3243
3244			//未识别是什么语种
3245			//console.log('not find is language , char : '+charstr+', unicode: '+charstr.charCodeAt(0).toString(16));
3246			return '';
3247			
3248		},
3249		/*
3250		 * 对字符串进行分析,分析字符串是有哪几种语言组成。
3251		 * language : 当前字符的语种,传入如 english
3252		 * langStrs : 操作的,如 langStrs['english'][0] = '你好'
3253		 * upLangs  : 当前字符之前的上一个字符的语种是什么,当前字符向上数第一个字符。格式如 ['language']='english', ['chatstr']='a', ['storage_language']='english'  这里面有3个参数,分别代表这个字符属于那个语种,其字符是什么、存入了哪种语种的队列。因为像是逗号,句号,一般是存入本身语种中,而不是存入特殊符号中。
3254		 * upLangsTwo : 当前字符之前的上二个字符的语种是什么 ,当前字符向上数第二个字符。 格式如 ['language']='english', ['chatstr']='a', ['storage_language']='english'  这里面有3个参数,分别代表这个字符属于那个语种,其字符是什么、存入了哪种语种的队列。因为像是逗号,句号,一般是存入本身语种中,而不是存入特殊符号中。
3255		 * chatstr  : 当前字符,如  h
3256		 */
3257		analyse:function(language, langStrs, upLangs, upLangsTwo, charstr){
3258			if(typeof(langStrs[language]) == 'undefined'){
3259				langStrs[language] = new Array();
3260			}
3261			var index = 0; //当前要存入的数组下标
3262			if(typeof(upLangs['storage_language']) == 'undefined'){
3263				//第一次,那么还没存入值,index肯定为0
3264				//console.log('第一次,那么还没存入值,index肯定为0')
3265				//console.log(upLangs['language'])
3266			}else{
3267				//console.log('analyse, charstr : '+charstr+', upLangs :');
3268				//console.log(upLangs);
3269				//var isEqual = upLangs['storage_language'] == language; //上次跟当前字符是否都是同一个语种(这个字符跟这个字符前一个字符)
3270
3271				/*
3272					英语每个单词之间都会有空格分割. 如果是英文的话,英文跟特殊字符还要单独判断一下,避免拆开,造成翻译不准,单个单词翻译的情况
3273					所以如果上次的字符是英文或特殊符号,当前字符是特殊符号(逗号、句号、空格,然后直接笼统就吧特殊符号都算上吧),那么也将当次的特殊符号变为英文来进行适配
3274					示例  
3275						hello word  的 "o w"
3276						hello  word  的 "  w"
3277						hello  word  的 "w  "
3278						this is a dog  的 " a "
3279				*/
3280				//console.log(language == 'specialCharacter');
3281				//如果两个字符类型不一致,但当前字符是英文或连接符时,进行判断
3282				/*
3283				if(!isEqual){
3284					if(language == 'english' || translate.language.connector(charstr)){
3285						console.log('1.'+(language == 'english' || translate.language.connector(charstr))+', upLangs str:'+upLangs['charstr']);
3286						//上一个字符是英文或连接符
3287						//console.log('teshu:'+translate.language.connector(upLangs['charstr'])+', str:'+upLangs['charstr']);
3288						if(upLangs['language'] == 'english' || translate.language.connector(upLangs['charstr'])) {
3289							console.log('2');
3290							//如果上二个字符不存在,那么刚开始,不再上面几种情况之中,直接不用考虑
3291							if(typeof(upLangsTwo['language']) != 'undefined'){
3292								console.log('3')
3293								//上二个字符是空(字符串刚开始),或者是英文
3294								if(upLangsTwo['language'] == 'english' || translate.language.connector(upLangsTwo['charstr'])){
3295									//满足这三个条件,那就将这三个拼接到一起
3296									console.log('4/5: '+', two lang:'+upLangsTwo['language']+', str:'+upLangsTwo['charstr'])
3297									isEqual = true;
3298									if(language == 'specialCharacter' && upLangs['language'] == 'specialCharacter' && upLangsTwo['language'] == 'specialCharacter'){
3299										//如果三个都是特殊字符,或后两个是特殊字符,第一个是空(刚开始),那就归入特殊字符
3300										language = 'specialCharacter';
3301										//console.log('4')
3302									}else{
3303										//不然就都归于英文中。
3304										//这里更改是为了让下面能将特殊字符(像是空格逗号等)也一起存入数组
3305										language = 'english';
3306										console.log(5)
3307									}
3308								}
3309							}
3310						}
3311					}
3312				}
3313				*/
3314
3315				/*
3316					不判断当前字符,而判断上个字符,是因为当前字符没法获取未知的下个字符。
3317				*/
3318				//if(!isEqual){
3319
3320					//如果当前字符是连接符
3321					if(translate.language.connector(charstr)){
3322						language = upLangs['storage_language'];
3323						/*
3324						//判断上个字符是否存入了待翻译字符,如要将中文翻译为英文,而上个字符是中文,待翻译,那将连接符一并加入待翻译字符中去,保持句子完整性
3325						//判断依据是上个字符存储至的翻译字符语种序列,不是特殊字符,而且也不是要翻译的目标语种,那肯定就是待翻译的,将连接符加入待翻译中一起进行翻译
3326						if(upLangs['storage_language'] != 'specialCharacter' && upLangs['storage_language'] != translate.to){
3327							
3328							language = upLangs['storage_language'];
3329							console.log('teshu:'+charstr+', 当前字符并入上个字符存储翻译语种:'+upLangs['storage_language']);
3330						}
3331						*/
3332					}
3333				//}
3334
3335				//console.log('isEqual:'+isEqual);
3336				/*
3337				if(isEqual){
3338					//跟上次语言一样,那么直接拼接
3339					index = langStrs[language].length-1; 
3340					//但是还有别的特殊情况,v2.1针对英文翻译准确度的适配,会有特殊字符的问题
3341					if(typeof(upLangs['storage_language']) != 'undefined' && upLangs['storage_language'] != language){
3342						//如果上个字符存入的翻译队列跟当前这个要存入的队列不一个的话,那应该是特殊字符像是逗号句号等导致的,那样还要额外一个数组,不能在存入之前的数组了
3343						index = langStrs[language].length; 
3344					}
3345				}else{
3346					//console.log('新开');
3347					//当前字符跟上次语言不样,那么新开一个数组
3348					index = langStrs[language].length;
3349					//console.log('++, inde:'+index+',lang:'+language+', length:'+langStrs[language].length)
3350				}
3351				*/
3352
3353				//当前要翻译的语种跟上个字符要翻译的语种一样,那么直接拼接
3354				if(upLangs['storage_language'] == language){
3355					index = langStrs[language].length-1; 
3356				}else{
3357					//console.log('新开');
3358					//当前字符跟上次语言不样,那么新开一个数组
3359					index = langStrs[language].length;
3360				}
3361			}
3362			if(typeof(langStrs[language][index]) == 'undefined'){
3363				langStrs[language][index] = new Array();
3364				langStrs[language][index]['beforeText'] = '';
3365				langStrs[language][index]['afterText'] = '';
3366				langStrs[language][index]['text'] = '';
3367			}
3368			langStrs[language][index]['text'] = langStrs[language][index]['text'] + charstr;
3369			/*
3370				中文英文混合时,当中文+英文并没有空格间隔,翻译为英文时,会使中文翻译英文的结果跟原本的英文单词连到一块。这里就是解决这种情况
3371				针对当前非英文(不需要空格分隔符,像是中文、韩语),但要翻译为英文(需要空格作为分割符号,像是法语等)时的情况进行判断
3372			*/
3373			//if(translate.language.getLocal() != 'english' && translate.to == 'english'){
3374			//当前本地语种的语言是连续的,但翻译的目标语言不是连续的(空格间隔)
3375			if( translate.language.wordBlankConnector(translate.language.getLocal()) == false && translate.language.wordBlankConnector(translate.to)){	
3376				if((upLangs['storage_language'] != null && typeof(upLangs['storage_language']) != 'undefined' && upLangs['storage_language'].length > 0)){
3377					//上个字符存在
3378					//console.log(upLangs['storage_language']);
3379					if(upLangs['storage_language'] != 'specialCharacter'){
3380						//上个字符不是特殊字符 (是正常语种。且不会是连接符,连接符都并入了正常语种)
3381
3382						//if( upLangs['storage_language'] != 'english' && language == 'english'){
3383						//上个字符的语言是连续的,但当前字符的语言不是连续的(空格间隔)
3384						if( translate.language.wordBlankConnector(upLangs['storage_language']) == false && translate.language.wordBlankConnector(language) ){
3385							//上个字符不是英语,当前字符是英语,这种情况要在上个字符后面追加空格,因为当前字符是英文,就不会在执行翻译操作了
3386							//console.log(upLangs['language']);
3387							langStrs[upLangs['storage_language']][langStrs[upLangs['storage_language']].length-1]['afterText'] = ' ';
3388						}else if(upLangs['storage_language'] == 'english' && language != 'english'){
3389							//上个字符是英语,当前字符不是英语,直接在当前字符前面追加空格
3390							langStrs[language][index]['beforeText'] = ' ';
3391						}
3392					}
3393
3394					
3395				}
3396			}
3397
3398			var result = new Array();
3399			result['langStrs'] = langStrs;
3400			result['storage_language'] = language;	//实际存入了哪种语种队列
3401			//console.log(result);
3402			//console.log(langStrs)
3403			//console.log(charstr);
3404			return result;
3405		},
3406		
3407		/*
3408		 * 不同于语言,这个只是单纯的连接符。比如英文单词之间有逗号、句号、空格, 汉字之间有逗号句号书名号的。避免一行完整的句子被分割,导致翻译不准确
3409		 * 单独拿他出来,目的是为了更好的判断计算,提高翻译的准确率
3410		 */
3411		connector:function(str){
3412			
3413			/*
3414				通用的有 空格、阿拉伯数字
3415				1.不间断空格\u00A0,主要用在office中,让一个单词在结尾处不会换行显示,快捷键ctrl+shift+space ;
3416				2.半角空格(英文符号)\u0020,代码中常用的;
3417				3.全角空格(中文符号)\u3000,中文文章中使用; 
3418			*/	
3419			if(/.*[\u0020\u00A0\u202F\u205F\u3000]+.*$/.test(str)){
3420				return true;
3421			}
3422			/*
3423				U+0030 0 数字 0
3424				U+0031 1 数字 1
3425				U+0032 2 数字 2
3426				U+0033 3 数字 3
3427				U+0034 4 数字 4
3428				U+0035 5 数字 5
3429				U+0036 6 数字 6
3430				U+0037 7 数字 7
3431				U+0038 8 数字 8
3432				U+0039 9 数字 9
3433			*/
3434			if(/.*[\u0030-\u0039]+.*$/.test(str)){ 
3435				return true
3436			}
3437			
3438		
3439			/*
3440				英文场景
3441				英文逗号、句号
3442				这里不包括() 因为这里面的基本属于补充,对语句前后并无强依è
3442µ–关系
3443				
3444				U+0021 ! 叹号
3445				U+0022 " 双引号
3446				U+0023 # 井号
3447				U+0024 $ 价钱/货币符号
3448				U+0025 % 百分比符号
3449				U+0026 & 英文“and”的简写符号
3450				U+0027 ' 引号
3451				U+002C , 逗号
3452				U+002D - 连字号/减号
3453				U+002E . 句号
3454				U+003A : 冒号
3455				U+003B ; 分号
3456				U+003F ? 问号
3457				U+0040 @ 英文“at”的简写符号
3458
3459
3460			*/
3461			if(/.*[\u0021\u0022\u0023\u0024\u0025\u0026\u0027\u002C\u002D\u002E\u003A\u003B\u003F\u0040]+.*$/.test(str)){
3462				return true;
3463			}
3464			
3465			/*
3466				中文标点符号
3467				名称	Unicode	符号
3468				句号	3002	。
3469				问号	FF1F	?
3470				叹号	FF01	!
3471				逗号	FF0C	,
3472				顿号	3001	、
3473				分号	FF1B	;
3474				冒号	FF1A	:
3475				引号	300C	「
3476				 	300D	」
3477				引号	300E	『
3478				 	300F	』
3479				引号	2018	‘
3480				 	2019	’
3481				引号	201C	“
3482				 	201D	”
3483				括号	FF08	(
3484				 	FF09	)
3485				括号	3014	〔
3486				 	3015	〕
3487				括号	3010	【
3488				 	3011	】
3489				破折号	2014	—
3490				省略号	2026	…
3491				连接号	2013	–
3492				间隔号	FF0E	.
3493				书名号	300A	《
3494				 	300B	》
3495				书名号	3008	〈
3496				 	3009	〉
3497				键盘123前面的那个符号 · 00b7
3498			*/
3499			if(/.*[\u3002\uFF1F\uFF01\uFF0C\u3001\uFF1B\uFF1A\u300C\u300D\u300E\u300F\u2018\u2019\u201C\u201D\uFF08\uFF09\u3014\u3015\u3010\u3011\u2014\u2026\u2013\uFF0E\u300A\u300B\u3008\u3009\u00b7]+.*$/.test(str)){
3500				return true;
3501			}
3502
3503
3504
3505			
3506			//不是,返回false
3507			return false;
3508		},
3509		//语种的单词连接符是否需要空格,比如中文简体、繁体、韩文、日语都不需要空格,则返回false, 但是像是英文的单词间需要空格进行隔开,则返回true
3510		//如果未匹配到,默认返回true
3511		//language:语种,传入如  english
3512		wordBlankConnector:function(language){
3513			if(language == null || typeof(language) == 'undefined'){
3514				return true;
3515			}
3516			switch (language.trim().toLowerCase()){
3517		  		case 'chinese_simplified':
3518		  			return false;
3519		  		case 'chinese_traditional':
3520		  			return false;
3521		  		case 'korean':
3522		  			return false;
3523		  		case 'japanese':
3524		  			return false;
3525		  	}
3526		  	//其他情况则返回true
3527		  	return true;
3528		},
3529		//繁体中文的字典,判断繁体中文就是通过此判断
3530		chinese_traditional_dict: '皚藹礙愛翺襖奧壩罷擺敗頒辦絆幫綁鎊謗剝飽寶報鮑輩貝鋇狽備憊繃筆畢斃閉邊編貶變辯辮鼈癟瀕濱賓擯餅撥缽鉑駁蔔補參蠶殘慚慘燦蒼艙倉滄廁側冊測層詫攙摻蟬饞讒纏鏟産闡顫場嘗長償腸廠暢鈔車徹塵陳襯撐稱懲誠騁癡遲馳恥齒熾沖蟲寵疇躊籌綢醜櫥廚鋤雛礎儲觸處傳瘡闖創錘純綽辭詞賜聰蔥囪從叢湊竄錯達帶貸擔單鄲撣膽憚誕彈當擋黨蕩檔搗島禱導盜燈鄧敵滌遞締點墊電澱釣調諜疊釘頂錠訂東動棟凍鬥犢獨讀賭鍍鍛斷緞兌隊對噸頓鈍奪鵝額訛惡餓兒爾餌貳發罰閥琺礬釩煩範販飯訪紡飛廢費紛墳奮憤糞豐楓鋒風瘋馮縫諷鳳膚輻撫輔賦複負訃婦縛該鈣蓋幹趕稈贛岡剛鋼綱崗臯鎬擱鴿閣鉻個給龔宮鞏貢鈎溝構購夠蠱顧剮關觀館慣貫廣規矽歸龜閨軌詭櫃貴劊輥滾鍋國過駭韓漢閡鶴賀橫轟鴻紅後壺護滬戶嘩華畫劃話懷壞歡環還緩換喚瘓煥渙黃謊揮輝毀賄穢會燴彙諱誨繪葷渾夥獲貨禍擊機積饑譏雞績緝極輯級擠幾薊劑濟計記際繼紀夾莢頰賈鉀價駕殲監堅箋間艱緘繭檢堿鹼揀撿簡儉減薦檻鑒踐賤見鍵艦劍餞漸濺澗漿蔣槳獎講醬膠澆驕嬌攪鉸矯僥腳餃繳絞轎較稭階節莖驚經頸靜鏡徑痙競淨糾廄舊駒舉據鋸懼劇鵑絹傑潔結誡屆緊錦僅謹進晉燼盡勁荊覺決訣絕鈞軍駿開凱顆殼課墾懇摳庫褲誇塊儈寬礦曠況虧巋窺饋潰擴闊蠟臘萊來賴藍欄攔籃闌蘭瀾讕攬覽懶纜爛濫撈勞澇樂鐳壘類淚籬離裏鯉禮麗厲勵礫曆瀝隸倆聯蓮連鐮憐漣簾斂臉鏈戀煉練糧涼兩輛諒療遼鐐獵臨鄰鱗凜賃齡鈴淩靈嶺領餾劉龍聾嚨籠壟攏隴樓婁摟簍蘆盧顱廬爐擄鹵虜魯賂祿錄陸驢呂鋁侶屢縷慮濾綠巒攣孿灤亂掄輪倫侖淪綸論蘿羅邏鑼籮騾駱絡媽瑪碼螞馬罵嗎買麥賣邁脈瞞饅蠻滿謾貓錨鉚貿麽黴沒鎂門悶們錳夢謎彌覓綿緬廟滅憫閩鳴銘謬謀畝鈉納難撓腦惱鬧餒膩攆撚釀鳥聶齧鑷鎳檸獰甯擰濘鈕紐膿濃農瘧諾歐鷗毆嘔漚盤龐國愛賠噴鵬騙飄頻貧蘋憑評潑頗撲鋪樸譜臍齊騎豈啓氣棄訖牽扡釺鉛遷簽謙錢鉗潛淺譴塹槍嗆牆薔強搶鍬橋喬僑翹竅竊欽親輕氫傾頃請慶瓊窮趨區軀驅齲顴權勸卻鵲讓饒擾繞熱韌認紉榮絨軟銳閏潤灑薩鰓賽傘喪騷掃澀殺紗篩曬閃陝贍繕傷賞燒紹賒攝懾設紳審嬸腎滲聲繩勝聖師獅濕詩屍時蝕實識駛勢釋飾視試壽獸樞輸書贖屬術樹豎數帥雙誰稅順說碩爍絲飼聳慫頌訟誦擻蘇訴肅雖綏歲孫損筍縮瑣鎖獺撻擡攤貪癱灘壇譚談歎湯燙濤縧騰謄銻題體屜條貼鐵廳聽烴銅統頭圖塗團頹蛻脫鴕馱駝橢窪襪彎灣頑萬網韋違圍爲濰維葦偉僞緯謂衛溫聞紋穩問甕撾蝸渦窩嗚鎢烏誣無蕪吳塢霧務誤錫犧襲習銑戲細蝦轄峽俠狹廈鍁鮮纖鹹賢銜閑顯險現獻縣餡羨憲線廂鑲鄉詳響項蕭銷曉嘯蠍協挾攜脅諧寫瀉謝鋅釁興洶鏽繡虛噓須許緒續軒懸選癬絢學勳詢尋馴訓訊遜壓鴉鴨啞亞訝閹煙鹽嚴顔閻豔厭硯彥諺驗鴦楊揚瘍陽癢養樣瑤搖堯遙窯謠藥爺頁業葉醫銥頤遺儀彜蟻藝億憶義詣議誼譯異繹蔭陰銀飲櫻嬰鷹應纓瑩螢營熒蠅穎喲擁傭癰踴詠湧優憂郵鈾猶遊誘輿魚漁娛與嶼語籲禦獄譽預馭鴛淵轅園員圓緣遠願約躍鑰嶽粵悅閱雲鄖勻隕運蘊醞暈韻雜災載攢暫贊贓髒鑿棗竈責擇則澤賊贈紮劄軋鍘閘詐齋債氈盞斬輾嶄棧戰綻張漲帳賬脹趙蟄轍鍺這貞針偵診鎮陣掙睜猙幀鄭證織職執紙摯擲幟質鍾終種腫衆謅軸皺晝驟豬諸誅燭矚囑貯鑄築駐專磚轉賺樁莊裝妝壯狀錐贅墜綴諄濁茲資漬蹤綜總縱鄒詛組鑽緻鐘麼為隻兇準啟闆裡靂餘鍊',
3531		/*
3532			中文判断
3533			返回:
3534				simplified:简体中文
3535				traditional:繁体中文
3536				空字符串:不是中文
3537		*/   
3538		chinese:function(str){
3539			if(/.*[\u4e00-\u9fa5]+.*$/.test(str)){ 
3540				if(this.chinese_traditional_dict.indexOf(str) > -1){ 
3541					return 'traditional';
3542				} else {
3543					return 'simplified';
3544				}
3545			} else {
3546				return '';
3547			}
3548		},
3549		//是否包含英文,true:包含
3550		english:function(str){
3551			if(/.*[\u0041-\u005a]+.*$/.test(str)){ 
3552				return true;
3553			} else if(/.*[\u0061-\u007a]+.*$/.test(str)){
3554				return true;
3555			} else {
3556				return false;
3557			}
3558		},
3559		//是否包含日语,true:包含
3560		japanese:function(str){
3561			if(/.*[\u3040-\u309F\u30A0-\u30FF]+.*$/.test(str)){ 
3562				return true
3563			} else {
3564				return false;
3565			}
3566		},
3567		//是否包含韩语,true:包含
3568		korean:function(str){
3569			if(/.*[\uAC00-\uD7AF]+.*$/.test(str)){ 
3570				return true
3571			} else {
3572				return false;
3573			}
3574		},
3575		//是否包含俄语
3576		russian:function(str){
3577			//判断字符有  БВДЖЗИЙЛМНОПСТУФХЦЧШЩЪЫЬЮЯЇІ
3578			if(/.*[\u0411\u0412\u0414\u0416\u0417\u0418\u0419\u041B\u041C\u041D\u041E\u041F\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042A\u042B\u042C\u042E\u042F\u0407\u0406]+.*$/.test(str)){ 
3579				return true
3580			} else {
3581				return false;
3582			}
3583		},
3584		//0-9 阿拉伯数字
3585		number:function(str){
3586			if(/.*[\u0030-\u0039]+.*$/.test(str)){
3587				return true;
3588			}
3589			return false;
3590		},
3591		
3592		//是否包含特殊字符,包含,则是true
3593		specialCharacter:function(str){
3594			//如:① ⑴ ⒈ 
3595			if(/.*[\u2460-\u24E9]+.*$/.test(str)){ 
3596				return true
3597			}
3598
3599			//如:┊┌┍ ▃ ▄ ▅
3600			if(/.*[\u2500-\u25FF]+.*$/.test(str)){ 
3601				return true
3602			}
3603
3604			//如:㈠  ㎎ ㎏ ㎡
3605			if(/.*[\u3200-\u33FF]+.*$/.test(str)){ 
3606				return true
3607			}
3608			
3609			//如:与ANSI对应的全角字符
3610			if(/.*[\uFF00-\uFF5E]+.*$/.test(str)){ 
3611				return true
3612			}
3613
3614			//其它特殊符号
3615			if(/.*[\u2000-\u22FF]+.*$/.test(str)){ 
3616				return true
3617			}
3618
3619			// 、><等符号
3620			if(/.*[\u3001-\u3036]+.*$/.test(str)){
3621				return true;
3622			}
3623
3624			/*
3625			//阿拉伯数字 0-9
3626			if(/.*[\u0030-\u0039]+.*$/.test(str)){
3627				return true;
3628			}
3629			*/
3630			
3631			/*
3632			U+0020 空格
3633			U+0021 ! 叹号
3634			U+0022 " 双引号
3635			U+0023 # 井号
3636			U+0024 $ 价钱/货币符号
3637			U+0025 % 百分比符号
3638			U+0026 & 英文“and”的简写符号
3639			U+0027 ' 引号
3640			U+0028 ( 开 左圆括号
3641			U+0029 ) 关 右圆括号
3642			U+002A * 星号
3643			U+002B + 加号
3644			U+002C , 逗号
3645			U+002D - 连字号/减号
3646			U+002E . 句号
3647			U+002F / 左斜杠
3648			*/
3649			if(/.*[\u0020-\u002F]+.*$/.test(str)){
3650				return true;
3651			}
3652
3653			/*
3654				U+003A : 冒号
3655				U+003B ; 分号
3656				U+003C < 小于符号
3657				U+003D = 等于号
3658				U+003E > 大于符号
3659				U+003F ? 问号
3660				U+005B [ 开 方括号
3661				U+005C \ 右斜杠
3662				U+005D ] 关 方括号
3663				U+005E ^ 抑扬(重音)符号
3664				U+005F _ 底线
3665				U+0060 ` 重音符
3666				U+007B { 开 左花括号
3667				U+007C | 直线
3668				U+007D } 关 右花括号
3669				U+007E ~ 波浪纹
3670			*/
3671			if(/.*[\u003B\u003B\u003C\u003D\u003E\u003F\u005B\u005C\u005D\u005E\u005F\u0060\u007B\u007C\u007D\u007E]+.*$/.test(str)){
3672				return true;
3673			}
3674			
3675			//空白字符,\u0009\u000a + https://cloud.tencent.com/developer/article/2128593
3676			if(/.*[\u0009\u000a\u0020\u00A0\u1680\u180E\u202F\u205F\u3000\uFEFF]+.*$/.test(str)){
3677				return true;
3678			}
3679			if(/.*[\u2000-\u200B]+.*$/.test(str)){
3680				return true;
3681			}
3682			
3683			/*
3684			拉丁字母
3685			代码 显示 描述
3686			U+00A1 ¡ 倒转的叹号
3687			U+00A2 ¢ (货币单位)分钱、毫子
3688			U+00A3 £ (货币)英镑
3689			U+00A4 ¤ (货币)当货币未有符号时以此替代
3690			U+00A5 ¥ (货币)日元
3691			U+00A6 ¦ 两条断开的直线
3692			U+00A7 § 文件分不同部分
3693			U+00A8 ¨ (语言)分音
3694			U+00A9 © 版权符
3695			U+00AA ª (意大利文、葡萄牙文、西班牙文)阴性序数
3696			U+00AB « 双重角形引号
3697			U+00AC ¬ 逻辑非
3698			U+00AE ® 商标
3699			U+00AF ¯ 长音
3700			U+00B0 ° 角度
3701			U+00B1 ± 正负号
3702			U+00B2 ² 二次方
3703			U+00B3 ³ 三次方
3704			U+00B4 ´ 锐音符
3705			U+00B5 µ 百万分之一,10?6
3706			U+00B6 ¶ 文章分段
3707			U+00B7 · 间隔号
3708			U+00B8 ¸ 软音符
3709			U+00B9 ¹ 一次方
3710			U+00BA º (意大利文、葡萄牙文、西班牙文)阳性序数
3711			U+00BB » 指向右的双箭头
3712			U+00BC ¼ 四分之一
3713			U+00BD ½ 二分之一
3714			U+00BE ¾ 四分之三
3715			U+00BF ¿ 倒转的问号
3716			U+00C1 Á 在拉丁字母 A 上加锐音符
3717			U+00C2 Â 在拉丁字母 A 上加抑扬符“^”
3718			U+00C3 Ã 在拉丁字母 A 上加“~”
3719			U+00C4 Ä 在拉丁字母 A 上加分音符“..”
3720			U+00C5 Å 在拉丁字母 A 上加角度符“°”
3721			U+00C6 Æ 拉丁字母 A、E 的混合
3722			U+00C7 Ç 在拉丁字母 C 下加软音符
3723			U+00C8 È 在拉丁字母 E 上加重音符
3724			U+00C9 É 在拉丁字母 E 上加锐音符
3725			U+00CA Ê 在拉丁字母 E 上加抑扬符
3726			U+00CB Ë 在拉丁字母 E 上加分音符
3727			U+00CC Ì 在拉丁字母 I 上加重音符
3728			U+00CD Í 在拉丁字母 I 上加锐音符
3729			U+00CE Î 在拉丁字母 I 上加抑扬符
3730			U+00CF Ï 在拉丁字母 I 上加分音符
3731			U+00D0 Ð 古拉丁字母,现只有法罗文和冰岛文和越南语使用
3732			U+00D1 Ñ 在拉丁字母 N 上加波浪纹“~”
3733			U+00D2 Ò 在拉丁字母 O 上加重音符
3734			U+00D3 Ó 在拉丁字母 O 上加锐音符
3735			U+00D4 Ô 在拉丁字母 O 上加抑扬符
3736			U+00D5 Õ 在拉丁字母 O 上加波浪纹“~”
3737			U+00D6 Ö 在拉丁字母 O 上加分音符
3738			U+00D7 × 乘号,亦可拖按“Alt”键,同时按“41425”五键
3739			U+00D8 Ø 在拉丁字母 O 由右上至左下加对角斜线“/”
3740			U+00D9 Ù 在拉丁字母 U 上加重音符
3741			U+00DA Ú 在拉丁字母 U 上加锐音符
3742			U+00DB Û 在拉丁字母 U 上加抑扬符
3743			U+00DC Ü 在拉丁字母 U 上加分音符
3744			U+00DD Ý 在拉丁字母 Y 上加锐音符
3745			U+00DE Þ 古拉丁字母,现已被“Th”取替
3746			U+00DF ß 德文字母
3747			U+00E0 à 在拉丁字母 a 上加重音符
3748			U+00E1 á 在拉丁字母 a 上加锐音符
3749			U+00E2 â 在拉丁字母 a 上加抑扬符
3750			U+00E3 ã 在拉丁字母 a 上加波浪纹“~”
3751			U+00E4 ä 在拉丁字母 a 上加分音符
3752			U+00E5 å 在拉丁字母 a 上加角度符“°”
3753			U+00E6 æ 拉丁字母 a、e 的混合
3754			U+00E7 ç 在拉丁字母 c 下加软音符
3755			U+00E8 è 在拉丁字母 e 上加锐音符
3756			U+00E9 é 在拉丁字母 e 上加重音符
3757			U+00EA ê 在拉丁字母 e 上加抑扬符
3758			U+00EB ë 在拉丁字母 e 上加分音符
3759			U+00EC ì 在拉丁字母 i 上加重音符
3760			U+00ED í 在拉丁字母 i 上加锐音符
3761			U+00EE î 在拉丁字母 i 上加抑扬符
3762			U+00EF ï 在拉丁字母 i 上加分音符
3763			U+00F0 ð 古拉丁字母
3764			U+00F1 ñ 在拉丁字母 n 上加波浪纹“~”
3765			U+00F2 ò 在拉丁字母 o 上加重音符
3766			U+00F3 ó 在拉丁字母 o 上加锐音符
3767			U+00F4 ô 在拉丁字母 o 上加抑扬符
3768			U+00F5 õ 在拉丁字母 o 上加波浪纹“~”
3769			U+00F6 ö 在拉丁字母 o 上加分音符
3770			U+00F7 ÷ 除号,亦可拖按“Alt”键,同时按“41426”五键
3771			U+00F8 ø 在拉丁字母 o 由右上至左下加对角斜线“/”
3772			U+00F9 ù 在拉丁字母 u 上加重音符
3773			U+00FA ú 在拉丁字母 u 上加锐音符
3774			U+00FB ? 在拉丁字母 u 上加抑扬符
3775			U+00FC ü 在拉丁字母 u 上加分音符
3776			U+00FD ý 在拉丁字母 y 上加锐音符
3777			U+00FE þ 古拉丁字母,现已被“th”取替
3778			U+00FF ü 在拉丁字母 u 上加分音符
3779			拉丁字母(扩展 A)
3780			代码 显示 描述
3781			U+0100 Ā 在拉丁字母 A 上加长音符
3782			U+0101 ā 在拉丁字母 a 上加长音符
3783			U+0102 Ă 在拉丁字母 A 上加短音符
3784			U+0103 ă 在拉丁字母 a 上加短音符
3785			U+0104 Ą 在拉丁字母 A 上加反尾形符
3786			U+0105 ą 在拉丁字母 a 上加反尾形符
3787			拉丁字母(扩展 C)
3788			代码 显示 描述
3789			U+2C60 Ⱡ 在拉丁字母“L”中间加两条横线“=”
3790			U+2C61 ⱡ 在拉丁字母“l”(L 的小写)中间加两条横线“=”
3791			U+2C62 Ɫ 在拉丁字母“L”(大写)中间加一条波浪线“~”
3792			U+2C63 Ᵽ 在拉丁字母“P”中间加一条横线“-”
3793			U+2C64 Ɽ 在拉丁字母“R”下加一条尾巴
3794			U+2C65 ⱥ 在拉丁字母“a”上加一条对角斜线“/”
3795			U+2C66 ⱦ 在拉丁字母“t”上加一条对角斜线“/”
3796			U+2C67 Ⱨ 在拉丁字母“H”下加一条尾巴
3797			U+2C68 ⱨ 在拉丁字母“h”下加一条尾巴
3798			U+2C69 Ⱪ 在拉丁字母“K”下加一条尾巴
3799			U+2C6A ⱪ 在拉丁字母“k”下加一条尾巴
3800			U+2C6B Ⱬ 在拉丁字母“Z”下加一条尾巴
3801			U+2C6C ⱬ 在拉丁字母“z”下加一条尾巴
3802			U+2C74 ⱴ 在拉丁字母“v”的起笔加一个弯勾
3803			U+2C75 Ⱶ 拉丁字母“H”的左半部
3804			U+2C76 ⱶ 拉丁字母“h”的左半部
3805			U+2C77 ⱷ 希腊字母“φ”的上半部
3806			*/
3807			if(/.*[\u00A1-\u0105]+.*$/.test(str)){
3808				return true;
3809			}
3810			if(/.*[\u2C60-\u2C77]+.*$/.test(str)){
3811				return true;
3812			}
3813			
3814			
3815			return false;
3816		}
3817	},
3818	//用户第一次打开网页时,自动判断当前用户所在国家使用的是哪种语言,来自动进行切换为用户所在国家的语种。
3819	//如果使用后,第二次在用,那就优先以用户所选择的为主
3820	executeByLocalLanguage:function(){
3821		//先读用户自己浏览器的默认语言
3822		var browserDefaultLanguage = translate.util.browserDefaultLanguage();
3823		if(typeof(browserDefaultLanguage) != 'undefined' && browserDefaultLanguage.length > 0){
3824			translate.changeLanguage(browserDefaultLanguage);
3825			return;
3826		}
3827
3828		//如果用户浏览器没读到默认语言,或者默认语言没有对应到translate.js支持的语种,那么在采用ip识别的方式
3829		translate.request.post(translate.request.api.ip, {}, function(data){
3830			//console.log(data); 
3831			if(data.result == 0){
3832				console.log('==== ERROR 获取当前用户所在区域异常 ====');
3833				console.log(data.info);
3834				console.log('==== ERROR END ====');
3835			}else{
3836				translate.storage.set('to',data.language);	//设置目标翻译语言
3837				translate.to = data.language; //设置目标语言
3838				translate.selectLanguageTag
3839				translate.execute(); //执行翻译
3840			}
3841		});
3842	},
3843	
3844	util:{
3845		/* 生成一个随机UUID,复制于 https://gitee.com/mail_osc/kefu.js */
3846		uuid:function() {
3847		    var d = new Date().getTime();
3848		    if (window.performance && typeof window.performance.now === "function") {
3849		        d += performance.now(); //use high-precision timer if available
3850		    }
3851		    var uuid = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
3852		        var r = (d + Math.random() * 16) % 16 | 0;
3853		        d = Math.floor(d / 16);
3854		        return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16);
3855		    });
3856		    return uuid;
3857		},
3858
3859		//判断字符串中是否存在tag标签。 true存在
3860		findTag:function(str) {
3861			var reg = /<[^>]+>/g;
3862			return reg.test(str);
3863		},
3864		//传入一个数组,从数组中找出现频率最多的一个返回。 如果多个频率出现的次数一样,那会返回多个
3865		arrayFindMaxNumber:function(arr){
3866
3867			// 储存每个元素出现的次数
3868			var numbers = {}
3869
3870			// 储存出现最多次的元素
3871			var maxStr = []
3872
3873			// 储存最多出现的元素次数
3874			var maxNum = 0
3875
3876			for(var i =0,len=arr.length;i<len;i++){
3877			    if(!numbers[arr[i]]){
3878			          numbers[arr[i]] = 1  
3879			    }else{
3880			        numbers[arr[i]]++
3881			    }
3882
3883			    if(numbers[arr[i]]>maxNum){
3884			        maxNum = numbers[arr[i]]
3885			    }
3886			}
3887
3888			for(var item in numbers){
3889			    if(numbers[item]===maxNum){
3890			        maxStr.push(item)
3891			    }
3892			}
3893			
3894			return maxStr;
3895		},
3896		//对字符串进行hash化,目的取唯一值进行标识
3897		hash:function(str){
3898			if(str == null || typeof(str) == 'undefined'){
3899				return str;
3900			}
3901			var hash = 0, i, chr;
3902			if (str.length === 0){
3903				return hash;
3904			}
3905
3906			for (i = 0; i < str.length; i++) {
3907				chr   = str.charCodeAt(i);
3908				hash  = ((hash << 5) - hash) + chr;
3909				hash |= 0; // Convert to 32bit integer
3910			}
3911			return hash+'';
3912		},
3913		//去除一些指定字符,如换行符。 如果传入的是null,则返回空字符串
3914		charReplace:function(str){
3915
3916			if(str == null){
3917				return '';
3918			}
3919			str = str.trim();
3920			str = str.replace(/\t|\n|\v|\r|\f/g,'');	//去除换行符等
3921			//str = str.replace(/&/g, "%26"); //因为在提交时已经进行了url编码了
3922			return str;
3923		},
3924		//RegExp相关
3925		regExp:{
3926			// new RegExp(pattern, resultText); 中的 pattern 字符串的预处理
3927			pattern:function(str){
3928				str = str.replace(/\\/g,'\\\\'); //这个一定要放在第一个,不然会被下面的影响
3929				//str = str.replace(/'/g,'\\\'');
3930				str = str.replace(/\"/g,'\\\"');
3931				//str = str.replace(/./g,'\\\.');
3932				str = str.replace(/\?/g,'\\\?');
3933				str = str.replace(/\$/g,'\\\$');
3934				str = str.replace(/\(/g,'\\\(');
3935				str = str.replace(/\)/g,'\\\)');
3936				str = str.replace(/\|/g,'\\\|');
3937				str = str.replace(/\+/g,'\\\+');
3938				str = str.replace(/\*/g,'\\\*');
3939				str = str.replace(/\[/g,'\\\[');
3940				str = str.replace(/\]/g,'\\\]');
3941				str = str.replace(/\^/g,'\\\^');
3942				str = str.replace(/\{/g,'\\\{');
3943				str = str.replace(/\}/g,'\\\}');
3944				return str;
3945			},
3946			// new RegExp(pattern, resultText); 中的 resultText 字符串的预处理
3947			resultText:function(str){
3948				//str = str.replace(/&quot;/g,"\"");
3949				//str = str.replace(/'/g,"\\\'");
3950				//str = str.replace(/"/g,"\\\"");
3951				return str;
3952			}
3953		},
3954		//获取URL的GET参数。若没有,返回""
3955		getUrlParam:function (name){
3956		     var reg = new RegExp("(^|&)"+ name +"=([^&]*)(&|$)");
3957		     var r = window.location.search.substr(1).match(reg);
3958		     if(r!=null)return  unescape(r[2]); return "";
3959		},
3960		/**
3961		 * 同步加载JS,加载过程中会阻塞,加载完毕后继续执行后面的。
3962		 * url: 要加载的js的url
3963		 */
3964		synchronizesLoadJs:function(url){
3965			var  xmlHttp = null;  
3966			if(window.ActiveXObject){//IE  
3967				try {  
3968					//IE6以及以后版本中可以使用  
3969					xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");  
3970				} catch (e) {  
3971					//IE5.5以及以后版本可以使用  
3972					xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");  
3973				}  
3974			}else if(window.XMLHttpRequest){  
3975				//Firefox,Opera 8.0+,Safari,Chrome  
3976				xmlHttp = new XMLHttpRequest();  
3977			}  
3978			//采用同步加载  
3979			xmlHttp.open("GET",url,false);  
3980			//发送同步请求,如果浏览器为Chrome或Opera,必须发布后才能运行,不然会报错  
3981			xmlHttp.send(null);  
3982			//4代表数据发送完毕  
3983			if( xmlHttp.readyState == 4 ){  
3984				//0为访问的本地,200到300代表访问服务器成功,304代表没做修改访问的是缓存  
3985				if((xmlHttp.status >= 200 && xmlHttp.status <300) || xmlHttp.status == 0 || xmlHttp.status == 304){  
3986					var myBody = document.getElementsByTagName("HTML")[0];  
3987					var myScript = document.createElement( "script" );  
3988					myScript.language = "javascript";  
3989					myScript.type = "text/javascript";  
3990					try{  
3991						//IE8以及以下不支持这种方式,需要通过text属性来设置  
3992						myScript.appendChild(document.createTextNode(xmlHttp.responseText));  
3993					}catch (ex){  
3994						myScript.text = xmlHttp.responseText;  
3995					}  
3996					myBody.appendChild(myScript);  
3997					return true;  
3998				}else{  
3999					return false;  
4000				}  
4001			}else{  
4002				return false;  
4003			}  
4004		},
4005		//加载 msg.js
4006		loadMsgJs:function(){
4007			if(typeof(msg) != 'undefined'){
4008				return;
4009			}
4010			translate.util.synchronizesLoadJs('https://res.zvo.cn/msg/msg.js');
4011		},
4012		/*
4013			对一个对象,按照对象的key的长度进行排序,越长越在前面
4014		*/
4015		objSort:function(obj){
4016			// 获取对象数组的所有 key,并转换为普通数组
4017			var keys = Array.from(Object.keys(obj));
4018
4019			// 对 key 数组进行排序
4020			keys.sort(function(a, b){
4021			  return b.length - a.length;
4022			});
4023
4024			// 定义一个新的对象数组,用来存储排序后的结果
4025			var sortedObj = new Array();
4026
4027			// 遍历排序后的 key 数组,将对应的值复制到新的对象数组中,并删除原来的对象数组中的键值对
4028			for (var key of keys) {
4029			  sortedObj[key] = obj[key];
4030			}
4031			return sortedObj;
4032		},
4033		/*
4034			将 2.11.3.20231232 转化为 2011003
4035			转化时会去掉最后一个日期的字符
4036		*/
4037		versionStringToInt:function(versionString){
4038			var vs = versionString.split('\.');
4039			var result = 0;
4040			result = parseInt(vs[0])*1000*1000 + result;
4041			result = parseInt(vs[1])*1000 + result;
4042			result = parseInt(vs[2]) + result;
4043
4044			return result;
4045		},
4046		/**
4047		 * 将一个 JSONArray 数组,按照文字长度进行拆分。
4048		 *  比如传入的 array 数组的文字长度是6200,传入的 size 是2000,那么就是将 array 数组拆分为多个长度不超出2000的数组返回。
4049		 *  注意,这个长度是指 array.toString() 后的长度,也就是包含了 [""] 这种符号的长度
4050		 * @param array 要被拆分的数组,其内都是String类型,传入格式如 ["你好","世界"]
4051		 * @param size 要被拆分的长度
4052		 * @return 被拆分后的数组列表
4053		 * @author 刘晓腾
4054		 */
4055		 split:function(array, size) {
4056		    let list = [];
4057		    // 数组长度小于size,直接进行返回
4058		    if(JSON.stringify(array).length <= size) {
4059		        list.push(array);
4060		    } else {
4061		        // 转换成String
4062		        let arrayStr = JSON.stringify(array).trim().substring(1, JSON.stringify(array).length - 1);
4063
4064		        // 判断size和字符串长度的差值,如果为1或者2,就直接拆成两段
4065		        if (JSON.stringify(array).length - size <= 2) {
4066		            size = size - 4;
4067		            // 拆两段
4068		            let str1 = arrayStr.substring(0, arrayStr.lastIndexOf("\",\"")+1);
4069		            let str2 = arrayStr.substring(arrayStr.lastIndexOf("\",\"")+2);
4070		            list.push(JSON.parse("[" + str1 + "]"));
4071		            list.push(JSON.parse("[" + str2 + "]"));
4072		        } else {
4073		            size = size - 2;
4074		            // 拆多段
4075		            let index = 0;
4076		            while (index - arrayStr.length < 0) {
4077		                // 按照指定大小拆一段
4078		                let s = "";
4079		                if ((index+size) - arrayStr.length >= 0) {
4080		                    s = arrayStr.substring(index);
4081		                } else {
4082		                    s = arrayStr.substring(index, (index+size));
4083		                }
4084		                // 结尾长度默认为字符串长度
4085		                let endIndex = s.length;
4086		                // 因为下次开始的第一个字符可能会是逗号,所以下次开始需要+1
4087		                let startNeedAdd = 1;
4088		                // 判断最后一个字符是否为双引号
4089		                if (s.endsWith("\"")) {
4090		                    // 判断倒数第二个是否为逗号
4091		                    if (s.endsWith("\",\"")) {
4092		                        // 删除两个字符
4093		                        endIndex-=2;
4094		                    } else if (!s.startsWith("\"")) {
4095		                        // 如果开头不是引号,需要补一个引号,这就导致会超长,所以结尾就要找指定字符的
4096		                        // 找出最后一个指定字符的位置
4097		                        let la = s.lastIndexOf("\",\"");
4098		                        endIndex = la + 1;
4099		                    }
4100		                } else if (s.endsWith("\",")) {
4101		                    // 判断是否为逗号,是的话删除一个字符
4102		                    endIndex-=1;
4103		                } else {
4104		                    // 都不是,那就是内容结尾
4105		                    // 找出最后一个指定字符的位置
4106		                    let la = s.lastIndexOf("\",\"");
4107		                    endIndex = la + 1;
4108		                    // 内容超长,endIndex就会变成0,这时需要手动赋值
4109		                    if (endIndex <= 0) {
4110		                        // 看看是否以引号开头,如果不是,需要拼两个引号
4111		                        if (s.startsWith("\"")) {
4112		                            // 拼一个引号,-1
4113		                            endIndex = s.length() - 1;
4114		                        } else {
4115		                            // 拼两个引号,-2
4116		                            endIndex = s.length() - 2;
4117		                        }
4118		                        if (!s.endsWith("\"")) {
4119		                            // 开始不是逗号了,不能-1
4120		                            startNeedAdd = 0;
4121		                        }
4122		                    }
4123		                }
4124		                // 根据处理的结尾长度进行第二次拆分
4125		                let s2 = "";
4126		                if (endIndex - s.length > 0 || endIndex - 0 == 0) {
4127		                    s2 = s;
4128		                    endIndex = endIndex + s2.length;
4129		                } else {
4130		                    s2 = s.substring(0, endIndex);
4131		                }
4132		                if (!s2.startsWith("\"") && !s2.startsWith(",\"")) {
4133		                    // 拼一个引号
4134		                    s2 = "\"" + s2;
4135		                }
4136		                if (!s2.endsWith("\"")) {
4137		                    // 拼一个引号
4138		                    s2 = s2 +"\"";
4139		                }
4140		                // 计算下次循环开始的长度
4141		                index += (endIndex + startNeedAdd);
4142		                // 加到list
4143		                s2 = "[" + s2 + "]";
4144		                try {
4145		                    list.push(JSON.parse(s2));
4146		                } catch (e) {
4147		                    // 遇到错误,略过一个字符
4148		                    index = index - (endIndex + startNeedAdd) + 1;
4149		                }
4150		            }
4151		        }
4152		    }
4153		    return list;
4154		},
4155		/* 
4156			浏览器的语种标识跟translate.js的语种标识的对应
4157			key: 浏览器的语种标识
4158			value: translate.js 的语种标识
4159		 */
4160		browserLanguage:{
4161			'zh-CN':'chinese_simplified',
4162			'zh-TW':'chinese_traditional',
4163			'zh-HK':'chinese_traditional',
4164			'co':'corsican',
4165			'gn':'guarani',
4166			'rw':'kinyarwanda',
4167			'ha':'hausa',
4168			'no':'norwegian',
4169			'nl':'dutch',
4170			'yo':'yoruba',
4171			'en':'english',
4172			'kok':'gongen',
4173			'la':'latin',
4174			'ne':'nepali',
4175			'fr':'french',
4176			'cs':'czech',
4177			'haw':'hawaiian',
4178			'ka':'georgian',
4179			'ru':'russian',
4180			'fa':'persian',
4181			'bho':'bhojpuri',
4182			'hi':'hindi',
4183			'be':'belarusian',
4184			'sw':'swahili',
4185			'is':'icelandic',
4186			'yi':'yiddish',
4187			'tw':'twi',
4188			'ga':'irish',
4189			'gu':'gujarati',
4190			'km':'khmer',
4191			'sk':'slovak',
4192			'he':'hebrew',
4193			'kn':'kannada',
4194			'hu':'hungarian',
4195			'ta':'tamil',
4196			'ar':'arabic',
4197			'bn':'bengali',
4198			'az':'azerbaijani',
4199			'sm':'samoan',
4200			'af':'afrikaans',
4201			'id':'indonesian',
4202			'da':'danish',
4203			'sn':'shona',
4204			'bm':'bambara',
4205			'lt':'lithuanian',
4206			'vi':'vietnamese',
4207			'mt':'maltese',
4208			'tk':'turkmen',
4209			'as':'assamese',
4210			'ca':'catalan',
4211			'si':'singapore',
4212			'ceb':'cebuano',
4213			'gd':'scottish-gaelic',
4214			'sa':'sanskrit',
4215			'pl':'polish',
4216			'gl':'galician',
4217			'lv':'latvian',
4218			'uk':'ukrainian',
4219			'tt':'tatar',
4220			'cy':'welsh',
4221			'ja':'japanese',
4222			'fil':'filipino',
4223			'ay':'aymara',
4224			'lo':'lao',
4225			'te':'telugu',
4226			'ro':'romanian',
4227			'ht':'haitian_creole',
4228			'doi':'dogrid',
4229			'sv':'swedish',
4230			'mai':'maithili',
4231			'th':'thai',
4232			'hy':'armenian',
4233			'my':'burmese',
4234			'ps':'pashto',
4235			'hmn':'hmong',
4236			'dv':'dhivehi',
4237			'lb':'luxembourgish',
4238			'sd':'sindhi',
4239			'ku':'kurdish',
4240			'tr':'turkish',
4241			'mk':'macedonian',
4242			'bg':'bulgarian',
4243			'ms':'malay',
4244			'lg':'luganda',
4245			'mr':'marathi',
4246			'et':'estonian',
4247			'ml':'malayalam',
4248			'de':'deutsch',
4249			'sl':'slovene',
4250			'ur':'urdu',
4251			'pt':'portuguese',
4252			'ig':'igbo',
4253			'ckb':'kurdish_sorani',
4254			'om':'oromo',
4255			'el':'greek',
4256			'es':'spanish',
4257			'fy':'frisian',
4258			'so':'somali',
4259			'am':'amharic',
4260			'ny':'nyanja',
4261			'pa':'punjabi',
4262			'eu':'basque',
4263			'it':'italian',
4264			'sq':'albanian',
4265			'ko':'korean',
4266			'tg':'tajik',
4267			'fi':'finnish',
4268			'ky':'kyrgyz',
4269			'ee':'ewe',
4270			'hr':'croatian',
4271			'kri':'creole',
4272			'qu':'quechua',
4273			'bs':'bosnian',
4274			'mi':'maori'
4275		},
4276		/*
4277			获取浏览器中设置的默认使用语言
4278			返回的是 translate.js 的语言唯一标识
4279			如果返回的是空字符串,则是没有匹配到(可能是没有获取到本地语言,也可能是本地语言跟translate.js 翻译通道没有对应上)
4280		*/
4281		browserDefaultLanguage:function(){
4282			var language = navigator.language || navigator.userLanguage;
4283			if(typeof(language) == 'string' && language.length > 0){
4284				var tLang = translate.util.browserLanguage[language];
4285				if(typeof(tLang) == 'undefined'){
4286					//没有在里面
4287					console.log('browser default language : '+language +', translate.js current translate channel not support this language ');
4288				}else{
4289					return tLang;
4290				}
4291			}
4292			
4293			//将其转化为  translate.js 的语言id,比如简体中文是 chinese_simplified 、 英语是 english
4294			return '';
4295		}
4296	},
4297	//机器翻译采用哪种翻译服务
4298	service:{  
4299		/*
4300			name填写的值有
4301			translate.service 有 http://translate.zvo.cn/41160.html 提供机器翻译服务
4302			client.edge 有edge浏览器接口提供翻译服务 ,也就是执行翻译时直接是
4303
4304		*/
4305		name:'translate.service',  
4306
4307		/*
4308			其实就是设置 translate.service.name
4309	
4310		*/
4311		use: function(serviceName){
4312			if(translate.enterprise.isUse == true){
4313				console.log('您已启用了企业级翻译通道 translate.enterprise.use(); (文档:https://translate.zvo.cn/4087.html) , 所以您设置的 translate.service.use(\''+serviceName+'\'); (文档:https://translate.zvo.cn/4081.html) 将失效不起作用,有企业级翻译通道全部接管。');
4314				return;
4315			}
4316			if(typeof(serviceName) == 'string' && serviceName == 'client.edge'){
4317				translate.service.name = serviceName;
4318
4319				//增加元素整体翻译能力
4320				translate.whole.enableAll();
4321			}
4322		},
4323		//客户端方式的edge提供机器翻译服务
4324		edge:{
4325			api:{ //edge浏览器的翻译功能
4326				auth:'https://edge.microsoft.com/translate/auth', //auth授权拉取
4327				translate:'https://api.cognitive.microsofttranslator.com/translate?from={from}&to={to}&api-version=3.0&includeSentenceLength=true' //翻译接口
4328			},
4329
4330			language:{
4331				json:[{"id":"ukrainian","name":"УкраїнськаName","serviceId":"uk"},{"id":"norwegian","name":"Norge","serviceId":"no"},{"id":"welsh","name":"color name","serviceId":"cy"},{"id":"dutch","name":"nederlands","serviceId":"nl"},{"id":"japanese","name":"日本語","serviceId":"ja"},{"id":"filipino","name":"Pilipino","serviceId":"fil"},{"id":"english","name":"English","serviceId":"en"},{"id":"lao","name":"ກະຣຸນາ","serviceId":"lo"},{"id":"telugu","name":"తెలుగుQFontDatabase","serviceId":"te"},{"id":"romanian","name":"Română","serviceId":"ro"},{"id":"nepali","name":"नेपालीName","serviceId":"ne"},{"id":"french","name":"Français","serviceId":"fr"},{"id":"haitian_creole","name":"Kreyòl ayisyen","serviceId":"ht"},{"id":"czech","name":"český","serviceId":"cs"},{"id":"swedish","name":"Svenska","serviceId":"sv"},{"id":"russian","name":"Русский язык","serviceId":"ru"},{"id":"malagasy","name":"Malagasy","serviceId":"mg"},{"id":"burmese","name":"ဗာရမ်","serviceId":"my"},{"id":"pashto","name":"پښتوName","serviceId":"ps"},{"id":"thai","name":"คนไทย","serviceId":"th"},{"id":"armenian","name":"Արմենյան","serviceId":"hy"},{"id":"chinese_simplified","name":"简体中文","serviceId":"zh-CHS"},{"id":"persian","name":"Persian","serviceId":"fa"},{"id":"chinese_traditional","name":"繁體中文","serviceId":"zh-CHT"},{"id":"kurdish","name":"Kurdî","serviceId":"ku"},{"id":"turkish","name":"Türkçe","serviceId":"tr"},{"id":"hindi","name":"हिन्दी","serviceId":"hi"},{"id":"bulgarian","name":"български","serviceId":"bg"},{"id":"malay","name":"Malay","serviceId":"ms"}
4331,{"id":"swahili","name":"Kiswahili","serviceId":"sw"},{"id":"oriya","name":"ଓଡିଆ","serviceId":"or"},{"id":"icelandic","name":"ÍslandName","serviceId":"is"},{"id":"irish","name":"Íris","serviceId":"ga"},{"id":"khmer","name":"ខ្មែរKCharselect unicode block name","serviceId":"km"},{"id":"gujarati","name":"ગુજરાતી","serviceId":"gu"},{"id":"slovak","name":"Slovenská","serviceId":"sk"},{"id":"kannada","name":"ಕನ್ನಡ್Name","serviceId":"kn"},{"id":"hebrew","name":"היברית","serviceId":"he"},{"id":"hungarian","name":"magyar","serviceId":"hu"},{"id":"marathi","name":"मराठीName","serviceId":"mr"},{"id":"tamil","name":"தாமில்","serviceId":"ta"},{"id":"estonian","name":"eesti keel","serviceId":"et"},{"id":"malayalam","name":"മലമാലം","serviceId":"ml"},{"id":"inuktitut","name":"ᐃᓄᒃᑎᑐᑦ","serviceId":"iu"},{"id":"arabic","name":"بالعربية","serviceId":"ar"},{"id":"deutsch","name":"Deutsch","serviceId":"de"},{"id":"slovene","name":"slovenščina","serviceId":"sl"},{"id":"bengali","name":"বেঙ্গালী","serviceId":"bn"},{"id":"urdu","name":"اوردو","serviceId":"ur"},{"id":"azerbaijani","name":"azerbaijani","serviceId":"az"},{"id":"portuguese","name":"português","serviceId":"pt"},{"id":"samoan","name":"lifiava","serviceId":"sm"},{"id":"afrikaans","name":"afrikaans","serviceId":"af"},{"id":"tongan","name":"汤加语","serviceId":"to"},{"id":"greek","name":"ελληνικά","serviceId":"el"},{"id":"indonesian","name":"IndonesiaName","serviceId":"id"},{"id":"spanish","name":"Español","serviceId":"es"},{"id":"danish","name":"dansk","serviceId":"da"},{"id":"amharic","name":"amharic","serviceId":"am"},{"id":"punjabi","name":"ਪੰਜਾਬੀName","serviceId":"pa"},{"id":"albanian","name":"albanian","serviceId":"sq"},{"id":"lithuanian","name":"Lietuva","serviceId":"lt"},{"id":"italian","name":"italiano","serviceId":"it"},{"id":"vietnamese","name":"Tiếng Việt","serviceId":"vi"},{"id":"korean","name":"한국어","serviceId":"ko"},{"id":"maltese","name":"Malti","serviceId":"mt"},{"id":"finnish","name":"suomi","serviceId":"fi"},{"id":"catalan","name":"català","serviceId":"ca"},{"id":"croatian","name":"hrvatski","serviceId":"hr"},{"id":"bosnian","name":"bosnian","serviceId":"bs-Latn"},{"id":"polish","name":"Polski","serviceId":"pl"},{"id":"latvian","name":"latviešu","serviceId":"lv"},{"id":"maori","name":"Maori","serviceId":"mi"}],
4332				/*
4333					获取map形式的语言列表 
4334					key为 translate.service 的 name  
4335					value为serviceId
4336
4337				*/
4338				getMap:function(){
4339					if(typeof(translate.service.edge.language.map) == 'undefined'){
4340						translate.service.edge.language.map = new Array();
4341						for(var i = 0; i < translate.service.edge.language.json.length; i++){
4342							var item = translate.service.edge.language.json[i];
4343							translate.service.edge.language.map[item.id] = item.serviceId;
4344						}
4345					}
4346					return translate.service.edge.language.map;
4347				}
4348			},
4349			/**
4350			 * edge 进行翻译。 这个传入参数跟 translate.request.post 是一样的
4351			 * @param path 请求的path(path,传入的是translate.request.api.translate 这种的,需要使用 getUrl 来组合真正请求的url )
4352			 * @param data 请求的参数数据
4353			 * @param func 请求完成的回调,传入如 function(data){ console.log(data); }
4354			 */
4355			translate:function(path, data, func){
4356				var textArray = JSON.parse(decodeURIComponent(data.text));
4357				let translateTextArray = translate.util.split(textArray, 48000);
4358				//console.log(translateTextArray);
4359				
4360
4361				translate.request.send(translate.service.edge.api.auth, {}, function(auth){
4362					var from = translate.service.edge.language.getMap()[data.from];
4363					var to = translate.service.edge.language.getMap()[data.to];
4364					var transUrl = translate.service.edge.api.translate.replace('{from}',from).replace('{to}',to);
4365
4366					//如果翻译量大,要拆分成多次翻译请求
4367					for(var tai = 0; tai<translateTextArray.length; tai++){
4368						var json = [];
4369						for(var i = 0; i<translateTextArray[tai].length; i++){
4370							json.push({"Text":translateTextArray[tai][i]});
4371						}
4372
4373						translate.request.send(transUrl, JSON.stringify(json), function(result){
4374							var d = {};
4375							d.info = 'SUCCESS';
4376							d.result = 1;
4377							d.from = data.from;
4378							d.to = data.to;
4379							d.text = [];
4380							for(var t = 0; t < result.length; t++){
4381								d.text.push(result[t].translations[0].text);
4382							}
4383							
4384
4385							//判断当前翻译是否又被拆分过,比如一次超过5万字符的话就要拆分成多次请求了
4386							if(translateTextArray.length > 1){
4387								//这一次翻译呗拆分了多次请求,那么要进行补全数组,使数组个数能一致
4388
4389								/*
4390
4391									注意这里根据数组的长度来判断当前属于第几个数组,
4392									有几率会是拆分的数组,其中有两组的长度是一样的,
4393									这样的话是有问题的,只不过几率很小,就先这样了
4394									但终归还是留了个坑 -- 记录
4395
4396								*/
4397
4398								var currentIndex = -1;	//当前翻译请求属于被拆分的第几个的数组下标,从0开始的
4399								for(var cri = 0; cri < translateTextArray.length; cri++){
4400									if(translateTextArray[cri].length - d.text.length == 0){
4401										currentIndex = cri;
4402										break;
4403									}
4404								}
4405
4406								//进行对前后进行补齐数组
4407								if(currentIndex < 0){
4408									console.log('------ERROR--------');
4409									console.log('翻译内容过多,进行拆分,但拆分判断出现异常,currentIndex:-1 请联系 http://translate.zvo.cn/43006.html 说明');
4410								}
4411								//前插入空数组填充
4412								for(var addbeforei = 0; addbeforei<currentIndex; addbeforei++){
4413									var beforeItemArrayLength = translateTextArray[addbeforei].length;
4414									//console.log('beforeItemArrayLength:'+beforeItemArrayLength);
4415									for(var bi = 0; bi < beforeItemArrayLength; bi++){
4416										d.text.unshift(null);
4417									}
4418								}
4419								//后插入空数组填充
4420								for(var addafteri = translateTextArray.length-1; addafteri>currentIndex; addafteri--){
4421									var afterItemArrayLength = translateTextArray[addafteri].length;
4422									for(var bi = 0; bi < afterItemArrayLength; bi++){
4423										d.text.push(null);
4424									}
4425								}
4426								
4427							}
4428							
4429							func(d);
4430						}, 'post', true, {'Authorization':'Bearer '+auth, 'Content-Type':'application/json'}, function(xhr){
4431							console.log('---------error--------');
4432							console.log('edge translate service error, http code : '+xhr.status + ', response text : '+xhr.responseText);
4433						}, true);
4434						
4435
4436					}
4437					//console.log('translateResultArray')
4438					//console.log(translateResultArray);
4439					
4440
4441				}, 'get', true, {'content-type':'application/x-www-form-urlencoded'}, function(xhr){
4442					console.log('---------error--------');
4443					console.log('edge translate service error, http code : '+xhr.status + ', response text : '+xhr.responseText);
4444				}, true);
4445
4446
4447				
4448
4449				
4450			}
4451		}
4452	},
4453	//request请求来源于 https://github.com/xnx3/request
4454	request:{
4455		 
4456		//相关API接口方面
4457		api:{
4458			/**
4459			 * 翻译接口请求的域名主机 host
4460			 * 格式注意前面要带上协议如 https:// 域名后要加 /
4461			 * v2.8.2 增加数组形态,如 ['https://api.translate.zvo.cn/','xxxxx'] 
4462			 */
4463			//host:'https://api.translate.zvo.cn/',
4464			host:['https://api.translate.zvo.cn/','https://america.api.translate.zvo.cn/'],
4465			//host的备用接口,格式同host,可以填写多个,只不过这里是数组格式。只有当主 host 无法连通时,才会采用备host来提供访问。如果为空也就是 [] 则是不采用备方案。
4466			//backupHost:['',''],
4467			language:'language.json', //获取支持的语种列表接口
4468			translate:'translate.json', //翻译接口
4469			ip:'ip.json', //根据用户当前ip获取其所在地的语种
4470			connectTest:'connectTest.json',	//用于 translate.js 多节点翻译自动检测网络连通情况
4471			init:'init.json', //获取最新版本号,跟当前版本进行比对,用于提醒版本升级等使用
4472
4473		},
4474		/*
4475			请求后端接口的响应。无论是否成功,都会触发此处。
4476			另外当 xhr.readyState==4 的状态时才会触发。
4477			此处会在接口请求响应后、且在translate.js处理前就会触发
4478			@param xhr XMLHttpRequest 接口请求
4479			
4480		*/
4481		response:function(xhr){
4482			//console.log('response------');
4483			//console.log(xhr);
4484		},
4485		/*
4486			速度检测控制中心, 检测主备翻译接口的响应速度进行排列,真正请求时,按照排列的顺序进行请求
4487			v2.8.2增加	
4488			
4489			storage存储方面
4490			storage存储的key  						存的什么
4491			speedDetectionControl_hostQueue			hostQueue
4492			speedDetectionControl_hostQueueIndex	当前要使用的是 hostQueue 中的数组下标。如果没有,这里默认视为0
4493			speedDetectionControl_lasttime			最后一次执行速度检测的时间戳,13位时间戳
4494
4495
4496			
4497		*/
4498		speedDetectionControl:{
4499			/*
4500				
4501				进行 connect主节点缩减的时间,单位是毫秒.
4502				这个是进行 translate.request.speedDetectionControl.checkResponseSpeed() 节点测速时,translate.request.api.host 第一个元素是默认的主节点。
4503				主节点在实际测速完后,会减去一定的时间,以便让用户大部分时间可以使用主节点,而不必走分节点。
4504				例如主节点实际响应速度 3500 毫秒,那么会减去这里设置的2000毫秒,记为 1500 毫秒
4505				当然如果是小于这里设置的2000毫秒,那么会记为0毫秒。
4506				这样再跟其他分节点的响应时间进行对比,主节点只要不是响应超时,就会有更大的几率被选中为实际使用的翻译的节点
4507				
4508				这里的单位是毫秒。
4509				v2.10.2.20231225 增加
4510			*/
4511			hostMasterNodeCutTime:2000,	
4512
4513			/*
4514				翻译的队列,这是根据网络相应的速度排列的,0下标为请求最快,1次之...
4515				其格式为:
4516					[
4517						{
4518							"host":"xxxxxxxx",
4519							"time":123 			//这里的单位是毫秒
4520						},
4521						{
4522							"host":"xxxxxxxx",
4523							"time":123 			//这里的单位是毫秒
4524						}
4525					]
4526			*/
4527			hostQueue:[],	
4528			hostQueueIndex:-1,	//当前使用的 hostQueue的数组下标,  -1表示还未初始化赋予值,不可直接使用,通过 getHostQueueIndex() 使用
4529			disableTime:1000000,	//不可用的时间,storage中存储的 speedDetectionControl_hostQueue 其中 time 这里,如果值是 这个,便是代表这个host处于不可用状态
4530
4531
4532			//获取 host queue 队列
4533			getHostQueue:function(){
4534				if(translate.request.speedDetectionControl.hostQueue.length == 0){
4535					//还没有,先从本地存储中取,看之前是否已经设置过了
4536					// 只有经过真正的网络测速后,才会加入 storage 的 hostQueue
4537					var storage_hostQueue = translate.storage.get('speedDetectionControl_hostQueue');
4538					if(storage_hostQueue == null || typeof(storage_hostQueue) == 'undefined'){
4539						//本地存储中没有,也就是之前没设置过,是第一次用,那么直接讲 translate.request.api.host 赋予之
4540						//translate.request.api.host
4541						//console.log(typeof(translate.request.api.host))
4542						if(typeof(translate.request.api.host) == 'string'){
4543							//单个,那么赋予数组形式
4544							//translate.request.speedDetectionControl.hostQueue = [{"host":translate.request.api.host, time:0 }];
4545							translate.request.api.host = [''+translate.request.api.host];
4546						}
4547
4548						//console.log(translate.request.api.host)
4549						//数组形态,多个,v2.8.2 增加多个,根据优先级返回
4550						translate.request.speedDetectionControl.hostQueue = [];
4551						for(var i = 0; i<translate.request.api.host.length; i++){
4552							var h = translate.request.api.host[i];
4553							//console.log(h);
4554							translate.request.speedDetectionControl.hostQueue[i] = {"host":h, time:0 };
4555						}
4556						//console.log(translate.request.speedDetectionControl.hostQueue);
4557						
4558					}else{
4559						//storage中有,那么赋予
4560						translate.request.speedDetectionControl.hostQueue = JSON.parse(storage_hostQueue);
4561						//console.log(storage_hostQueue);
4562						//console.log(translate.request.speedDetectionControl.hostQueue[0].time);
4563					}
4564
4565					//console.log(translate.request.speedDetectionControl.hostQueue)
4566
4567					/*
4568						当页面第一次打开初始化这个时才会进行测速,另外测速也是要判断时间的,五分钟一次
4569						进行测速
4570					*/
4571					var lasttime = translate.storage.get('speedDetectionControl_lasttime');
4572					if(lasttime == null || typeof(lasttime) == 'undefined'){
4573						lasttime = 0;
4574					}
4575					var updateTime = 60000;	//1分钟检测一次
4576					if(new Date().getTime() - lasttime > updateTime){
4577						translate.request.speedDetectionControl.checkResponseSpeed();
4578					}
4579					
4580				}
4581				
4582
4583				return translate.request.speedDetectionControl.hostQueue;
4584			},
4585
4586			/*
4587				服务于 checkResponseSpeed 用于将测试结果存入 storage
4588				time: 当前接口请求的耗时,单位是毫秒。如果是 1000000 那么表示这个接口不可用
4589			*/
4590			checkResponseSpeed_Storage:function(host, time){
4591
4592				translate.request.speedDetectionControl.checkHostQueue.push({"host":host, "time":time });
4593				//按照time进行排序
4594				translate.request.speedDetectionControl.checkHostQueue.sort((a, b) => a.time - b.time);
4595
4596				//存储到 storage 持久化
4597				translate.storage.set('speedDetectionControl_hostQueue',JSON.stringify(translate.request.speedDetectionControl.checkHostQueue));
4598				translate.storage.set('speedDetectionControl_lasttime', new Date().getTime());
4599
4600				translate.request.speedDetectionControl.hostQueue = translate.request.speedDetectionControl.checkHostQueue;
4601			},
4602
4603			//测试响应速度
4604			checkResponseSpeed:function(){
4605				var headers = {
4606					'content-type':'application/x-www-form-urlencoded',
4607				};
4608
4609
4610				translate.request.speedDetectionControl.checkHostQueue = []; //用于实际存储
4611				translate.request.speedDetectionControl.checkHostQueueMap = []; //只是map,通过key取值,无其他作用
4612
4613				if(typeof(translate.request.api.host) == 'string'){
4614					//单个,那么赋予数组形式
4615					translate.request.api.host = [''+translate.request.api.host];
4616				}
4617
4618				for(var i = 0; i < translate.request.api.host.length; i++){
4619					var host = translate.request.api.host[i];
4620					// 获取当前时间的时间戳
4621					translate.request.speedDetectionControl.checkHostQueueMap[host] = {
4622						start:new Date().getTime()
4623					};
4624
4625					
4626					try{
4627						translate.request.send(
4628							host+translate.request.api.connectTest,
4629							{host:host},
4630							function(data){
4631								var host = data.info;
4632								var map = translate.request.speedDetectionControl.checkHostQueueMap[host];
4633								var time = new Date().getTime() - map.start;
4634
4635								if(translate.request.api.host[0] == host){
4636									//console.log('如果是第一个,那么是主的,默认允许缩减2000毫秒,也就是优先使用主的');
4637									time = time - translate.request.speedDetectionControl.hostMasterNodeCutTime;
4638									if(time < 0){
4639										time = 0;
4640									}
4641								}
4642
4643								translate.request.speedDetectionControl.checkResponseSpeed_Storage(host, time);
4644								/*
4645								translate.request.speedDetectionControl.checkHostQueue.push({"host":host, "time":time });
4646								//按照time进行排序
4647								translate.request.speedDetectionControl.checkHostQueue.sort((a, b) => a.time - b.time);
4648
4649								//存储到 storage 持久化
4650								translate.storage.set('speedDetectionControl_hostQueue',JSON.stringify(translate.request.speedDetectionControl.checkHostQueue));
4651								translate.storage.set('speedDetectionControl_lasttime', new Date().getTime());
4652
4653								translate.request.speedDetectionControl.hostQueue = translate.request.speedDetectionControl.checkHostQueue;
4654								//console.log(translate.request.speedDetectionControl.hostQueue);
4655								*/
4656							},
4657							'post',
4658							true,
4659							headers,
4660							function(data){
4661								//translate.request.speedDetectionControl.checkResponseSpeed_Storage(host, time);
4662								var hostUrl = data.requestURL.replace('connectTest.json','');
4663								translate.request.speedDetectionControl.checkResponseSpeed_Storage(hostUrl, translate.request.speedDetectionControl.disableTime);
4664							},
4665							false
4666						);
4667					}catch(e){
4668						//console.log('e0000');
4669						console.log(e);
4670						//time = 300000; //无法连接的,那么赋予 300 秒吧
4671					}
4672
4673				}
4674				
4675			},
4676
4677			//获取当前使用的host的数组下标
4678			getHostQueueIndex:function(){
4679				if(translate.request.speedDetectionControl.hostQueueIndex < 0){
4680					//页面当前第一次使用,赋予值
4681					//先从 storage 中取
4682					var storage_index = translate.storage.get('speedDetectionControl_hostQueueIndex');
4683					if(typeof(storage_index) == 'undefined' || storage_index == null){
4684						//存储中不存在,当前用户(浏览器)第一次使用,默认赋予0
4685						translate.request.speedDetectionControl.hostQueueIndex = 0;
4686						translate.storage.set('speedDetectionControl_hostQueueIndex',0);
4687					}else{
4688						translate.request.speedDetectionControl.hostQueueIndex = storage_index;
4689					}
4690				}
4691				return translate.request.speedDetectionControl.hostQueueIndex;
4692			},
4693
4694			//获取当前要使用的host
4695			getHost:function(){
4696				var queue = translate.request.speedDetectionControl.getHostQueue();
4697				//console.log(queue);
4698				var queueIndex = translate.request.speedDetectionControl.getHostQueueIndex();
4699				if(queue.length > queueIndex){
4700					//正常,没有超出越界
4701					
4702				}else{
4703					//异常,下标越界了!,固定返回最后一个
4704					console.log('异常,下标越界了!index:'+queueIndex);
4705					queueIndex = queue.length-1;
4706				}
4707				//console.log(queueIndex);
4708				return queue[queueIndex].host;
4709			},
4710
4711		},
4712		//生成post请求的url
4713		getUrl:function(path){
4714			var currentHost = translate.request.speedDetectionControl.getHost();
4715			var url = currentHost+path+'?v='+translate.version;
4716			//console.log('url: '+url);
4717			return url;
4718		},
4719		/**
4720		 * post请求
4721		 * @param path 请求的path(path,传入的是translate.request.api.translate 这种的,需要使用 getUrl 来组合真正请求的url )
4722		 * @param data 请求的参数数据,传入如 
4723		 * 		{
4724		 * 			from: "chinese_simplified",
4725		 * 			text: "%5B%22%E4%BD%A0%E5%A5%BD%EF%BC%8C%E6%88%91",
4726		 * 			to: "chinese_traditional
4727		 * 		}
4728		 * 		
4729		 * @param func 请求完成的回调,传入如 function(data){ console.log(data); }
4730		 */
4731		post:function(path, data, func){
4732			var headers = {
4733				'content-type':'application/x-www-form-urlencoded',
4734			};
4735			if(typeof(data) == 'undefined'){
4736				return;
4737			}
4738
4739			//企业级翻译自动检测
4740			translate.enterprise.automaticAdaptationService();
4741
4742			// ------- edge start --------
4743			var url = translate.request.getUrl(path);
4744			//if(url.indexOf('edge') > -1 && path == translate.request.api.translate){
4745			if(translate.service.name == 'client.edge'){	
4746				if(path == translate.request.api.translate){
4747					translate.service.edge.translate(path, data, func);
4748					return;
4749				}
4750				if(path == translate.request.api.language){
4751					var d = {};
4752					d.info = 'SUCCESS';
4753					d.result = 1;
4754					d.list = translate.service.edge.language.json;
4755					func(d);
4756					return;
4757				}
4758				
4759				//return;
4760			}
4761			// ------- edge end --------
4762
4763			this.send(path, data, func, 'post', true, headers, null, true);
4764		},
4765		/**
4766		 * 发送请求
4767		 * url 请求的url或者path(path,传入的是translate.request.api.translate 这种的,需要使用 getUrl 来组合真正请求的url )
4768		 * data 请求的数据,如 {"author":"管雷鸣",'site':'www.guanleiming.com'} 
4769		 * func 请求完成的回调,传入如 function(data){}
4770		 * method 请求方式,可传入 post、get
4771		 * isAsynchronize 是否是异步请求, 传入 true 是异步请求,传入false 是同步请求。 如果传入false,则本方法返回xhr
4772		 * headers 设置请求的header,传入如 {'content-type':'application/x-www-form-urlencoded'};
4773		 * abnormalFunc 响应异常所执行的方法,响应码不是200就会执行这个方法 ,传入如 function(xhr){}  另外这里的 xhr 会额外有个参数  xhr.requestURL 返回当前请求失败的url
4774		 * showErrorLog 是否控制台打印出来错误日志,true打印, false 不打印
4775		 */
4776		send:function(url, data, func, method, isAsynchronize, headers, abnormalFunc, showErrorLog){
4777			//post提交的参数
4778			var params = '';
4779
4780			if(data == null || typeof(data) == 'undefined'){
4781				data = {};
4782			}
4783			//加入浏览器默认语种  v3.6.1 增加,以便更好的进行自动切换语种
4784			data.browserDefaultLanguage = translate.util.browserDefaultLanguage();
4785
4786			if(typeof(data) == 'string'){
4787				params = data; //payload 方式
4788			}else{
4789				//表单提交方式
4790				for(var index in data){
4791					if(params.length > 0){
4792						params = params + '&';
4793					}
4794					params = params + index + '=' + data[index];
4795				}
4796			}
4797			
4798			if(url.indexOf('https://') == 0 || url.indexOf('http://') == 0){
4799				//采用的url绝对路径
4800			}else{
4801				//相对路径,拼接上host
4802				url = translate.request.getUrl(url);
4803			}
4804
4805			var xhr=null;
4806			try{
4807				xhr=new XMLHttpRequest();
4808			}catch(e){
4809				xhr=new ActiveXObject("Microsoft.XMLHTTP");
4810			}
4811			//2.调用open方法(true----异步)
4812			xhr.open(method,url,isAsynchronize);
4813			//设置headers
4814			if(headers != null){
4815				for(var index in headers){
4816					xhr.setRequestHeader(index,headers[index]);
4817				}
4818			}
4819			if(translate.service.name == 'translate.service'){
4820				xhr.setRequestHeader('currentpage', window
4820.location.href+'');
4821			}
4822			xhr.send(params);
4823			//4.请求状态改变事件
4824			xhr.onreadystatechange=function(){
4825			    if(xhr.readyState==4){
4826			    	translate.request.response(xhr); //自定义响应的拦截
4827
4828			        if(xhr.status==200){
4829			        	//请求正常,响应码 200
4830			        	var json = null;
4831			        	if(typeof(xhr.responseText) == 'undefined' || xhr.responseText == null){
4832			        		//相应内容为空
4833			        	}else{
4834			        		//响应内容有值
4835			        		if(xhr.responseText.indexOf('{') > -1 && xhr.responseText.indexOf('}') > -1){
4836				        		//应该是json格式
4837				        		try{
4838					        		json = JSON.parse(xhr.responseText);
4839					        	}catch(e){
4840					        		console.log(e);
4841					        	}
4842				        	}
4843			        	}
4844			        	
4845			        	if(json == null){
4846			        		func(xhr.responseText);
4847			        	}else{
4848			        		func(json);
4849			        	}
4850			        }else{
4851			        	if(showErrorLog){
4852			        		if(url.indexOf(translate.request.api.connectTest) > -1){
4853			        			//测试链接速度的不在报错里面
4854			        		}else{
4855
4856			        			//判断是否是v2版本的翻译,如果是 translate.service 模式并且没有使用企业级翻译,参会提示
4857			        			//2024.3月底开始,翻译使用量增加的太快,开源的翻译服务器有点扛不住经常出故障,所以直接把这个提示加到这里
4858			        			if(translate.service.name == 'translate.service' && !translate.enterprise.isUse){
4859			        				console.log('----- translate.js 提示 -----\n非常抱歉,translate.service 开源免费的翻译服务器当前并发及流量太大导致阻塞!\n这个情况是时不时发生的,大概一天24小时可能会有一二十分钟这种情况,主要是因为使用量太大了,月使用量能超四十亿次,所以在高发时会出现这种情况。\n解决这种情况可以有两种方案:\n【方案一】:使用采用最新版本 3.8.0及更高版本,js引用文件为 https://cdn.staticfile.net/translate.js/3.8.0/translate.js 并且使用 client.edge 模式 (增加一行设置代码就好,可参考 https://translate.zvo.cn/4081.html ),这样就不会再出现这种情况了。这个是我们的 translate.service 通道阻塞导致,更换别的通道就可以避免这样了。而且这个方案也是完全免费的。 \n【方案二】:采用企业级稳定翻译通道 ,但是这个相比于 方案一 来说,是有一定的收费的,大概一年600,这个就是专门为了高速及高稳定准备的,而相比于这个方案二,方案一则是全免费的。 因为方案二我们是部署了两个集群,而每个集群又下分了数个网络节点,包含中国大陆、香港、美国、欧洲、 等多个州,充分保障稳定、高效,同样也产生了不少成本,所以才需要付费。更多信息说明可以参考: http://translate.zvo.cn/4087.html \n\n-------------');
4860			        			}
4861
4862			        			//console.log(xhr);
4863					        	console.log('------- translate.js service api response error --------');
4864					        	console.log('    http code : '+xhr.status);
4865					        	console.log('    response : '+xhr.response);
4866					        	console.log('    request url : '+url);
4867					        	console.log('    request data : '+JSON.stringify(data));
4868					        	console.log('    request method : '+method);
4869					        	console.log('---------------------- end ----------------------');
4870			        		}
4871			        		
4872			        	}
4873			        	xhr.requestURL = url;
4874			        	if(abnormalFunc != null){
4875			        		abnormalFunc(xhr);
4876			        	}
4877			        }
4878			    }
4879			}
4880			return xhr;
4881		},
4882		/*
4883
4884			手动进行翻译操作。参数说明:
4885				texts: 可传入要翻译的文本、以及文本数组。 比如要一次翻译多个句子,那就可以传入数组的方式
4886				function: 翻译完毕后的处理函数。传入如 function(data){ console.log(data); }
4887						  注意,返回的data.result 为 1,则是翻译成功。  为0则是出错,可通过data.info 得到错误原因。 更详细说明参考: http://api.zvo.cn/translate/service/20230807/translate.json.html
4888
4889			使用案例一: 
4890			translate.request.translateText('你好,我是翻译的内容', function(data){
4891				//打印翻译结果
4892				console.log(data);
4893			});
4894			
4895			使用案例二:
4896			var texts = ['我是翻译的第一句','我是翻译的第二句','我是翻译的第三句'];
4897			translate.request.translateText(texts, function(data){
4898				//打印翻译结果
4899				console.log(data);
4900			});
4901
4902			使用案例三:
4903			var obj = {
4904				from:'chinese_simplified',
4905				to:'english',
4906				texts: ['我是翻译的第一句','我是翻译的第二句','我是翻译的第三句']
4907			}
4908			translate.request.translateText(obj, function(data){
4909				//打印翻译结果
4910				console.log(data);
4911			});
4912		*/
4913		translateText:function(obj, func){
4914			var texts = new Array();
4915			var from = translate.language.getLocal();
4916			var to = translate.language.getCurrent();
4917
4918			if(typeof(obj) == 'string'){
4919				//案例一的场景,传入单个字符串
4920				texts[0] = [obj];
4921			}else{
4922				//不是字符串了,而是对象了,判断是案例二还是案例三
4923
4924				var type = Object.prototype.toString.call(obj);
4925				//console.log(type);
4926				if(type == '[object Array]'){
4927					//案例二
4928					texts = obj;
4929				}else if(type == '[object Object]'){
4930					//案例三
4931					if(typeof(obj.texts) == 'undefined'){
4932						console.log('translate.request.translateText 传入的值类型异常,因为你没有传入 obj.texts 要翻译的具体文本! 请查阅文档: https://translate.zvo.cn/4077.html');	
4933					}
4934					if(typeof(obj.texts) == 'string'){
4935						//单个字符串
4936						texts = [obj.texts];
4937					}else{
4938						//多个字符串,数组形态
4939						texts = obj.texts;
4940					}
4941					if(typeof(obj.from) == 'string' && obj.from.length > 0){
4942						from = obj.from;
4943					}
4944					if(typeof(obj.to) == 'string' && obj.to.length > 0){
4945						to = obj.to;
4946					}
4947				}else{
4948					console.log('translate.request.translateText 传入的值类型错误,请查阅文档: https://translate.zvo.cn/4077.html');
4949					return;
4950				}
4951			}
4952			
4953
4954			var url = translate.request.api.translate;
4955			var data = {
4956				from:from,
4957				to: to,
4958				text:encodeURIComponent(JSON.stringify(texts))
4959			};
4960			//console.log(data);
4961			translate.request.post(url, data, function(data){
4962				//console.log(data); 
4963				if(data.result == 0){
4964					console.log('=======ERROR START=======');
4965					console.log('from : '+data.from);
4966					console.log('to : '+data.to);
4967					console.log('translate text array : '+texts);
4968					console.log('response : '+data.info);
4969					console.log('=======ERROR END  =======');
4970					//return;
4971				}
4972
4973				func(data);
4974			});
4975		},
4976		listener:{
4977			minIntervalTime:800, // 两次触发的最小间隔时间,单位是毫秒,这里默认是800毫秒。最小填写时间为 200毫秒
4978			lasttime:0,// 最后一次触发执行 translate.execute() 的时间,进行执行的那一刻,而不是执行完。13位时间戳
4979			/*
4980				设置要在未来的某一时刻执行,单位是毫秒,13位时间戳。
4981				执行时如果当前时间大于这个数,则执行,并且将这个数置为0。
4982				会有一个循环执行函数每间隔200毫秒触发一次
4983			*/
4984			executetime:0,
4985			/*
4986				进行翻译时,延迟翻译执行的时间
4987				当ajax请求结束后,延迟这里设置的时间,然后自动触发 translate.execute() 执行
4988			*/
4989			delayExecuteTime:200, 
4990			/*
4991				满足ajax出发条件,设置要执行翻译。
4992				注意,设置这个后并不是立马就会执行,而是加入了一个执行队列,避免1秒请求了10次会触发10次执行的情况
4993			*/
4994			addExecute:function(){
4995				var currentTime = Date.now();
4996				if(translate.request.listener.lasttime == 0){
4997					//是第一次,lasttime还没设置过,那么直接设置执行时间为当前时间
4998					translate.request.listener.executetime = currentTime;
4999					translate.request.listener.lasttime = 1;
5000				}else{
5001					//不是第一次了
5002
5003					if(translate.request.listener.executetime > 1){
5004						//当前有执行队列等待,不用再加入执行等待了
5005						//console.log('已在执行队列,不用再加入了 '+currentTime);
5006					}else{
5007						//执行队列中还没有,可以加入执行命令
5008
5009						if(currentTime < translate.request.listener.lasttime + translate.request.listener.minIntervalTime){
5010							//如果当前时间小于最后一次执行时间+间隔时间,那么就是上次才刚刚执行过,这次执行的太快了,那么赋予未来执行翻译的时间为最后一次时间+间隔时间
5011							translate.request.listener.executetime = translate.request.listener.lasttime + translate.request.listener.minIntervalTime;
5012							//console.log('addexecute - < 如果当前时间小于最后一次执行时间+间隔时间,那么就是上次才刚刚执行过,这次执行的太快了,那么赋予未来执行翻译的时间为最后一次时间+间隔时间');
5013						}else{
5014							translate.request.listener.executetime = currentTime;
5015							//console.log('addexecute -- OK ');
5016						}
5017					}
5018					
5019
5020				}
5021
5022				
5023			},
5024			/*
5025				自定义是否会被触发的方法判断
5026				url 当前ajax请求的url,注意是这个url请求完毕获取到200相应的内容时才会触发此方法
5027				返回值 return true; 默认是不管什么url,全部返回true,表示会触发翻译自动执行 translate.execute; ,如果你不想让某个url触发翻译,那么你可以自行在这个方法中用代码进行判断,然后返回false,那么这个url将不会自动触发翻译操作。
5028			*/
5029			trigger:function(url){
5030				return true;
5031			},
5032			/*
5033				启动根据ajax请求来自动触发执行翻译,避免有时候有的框架存在漏翻译的情况。
5034				这个只需要执行一次即可,如果执行多次,只有第一次会生效
5035			*/
5036			start:function(){
5037				
5038				//确保这个方法只会触发一次,不会过多触发
5039				if(typeof(translate.request.listener.isStart) != 'undefined'){
5040					return;
5041				}else{
5042					translate.request.listener.isStart = true;
5043				}
5044
5045				//增加一个没100毫秒检查一次执行任务的线程
5046				setInterval(function(){
5047					var currentTime = Date.now();
5048					if(translate.request.listener.executetime > 1 && currentTime > translate.request.listener.executetime+translate.request.listener.delayExecuteTime){
5049						translate.request.listener.executetime = 0;
5050						translate.request.listener.lasttime = currentTime;
5051						try{
5052							//console.log('执行翻译 --'+currentTime);
5053							translate.execute();
5054						}catch(e){
5055							console.log(e);
5056						}
5057					}
5058				}, 100);
5059
5060				const observer = new PerformanceObserver((list) => {
5061					var translateExecute = false;	//是否需要执行翻译 true 要执行
5062				    for(var e = 0; e < list.getEntries().length; e++){
5063				    	var entry = list.getEntries()[e];
5064
5065				    	if (entry.initiatorType === 'fetch' || entry.initiatorType === 'xmlhttprequest') {
5066				        	var url = entry.name;
5067				        	//console.log(url);
5068				        	//判断url是否是当前translate.js本身使用的
5069				        	if(typeof(translate.request.api.host) == 'string'){
5070				        		translate.request.api.host = [translate.request.api.host];
5071				        	}
5072				        	var ignoreUrl = false; // 是否是忽略的url true是
5073
5074				        	//translate.service 模式判断
5075				        	for(var i = 0; i < translate.request.api.host.length; i++){
5076				        		if(url.indexOf(translate.request.api.host[i]) > -1){
5077				        			//是,那么直接忽略
5078				        			ignoreUrl = true;
5079				        			break;
5080				        		}
5081				        	}
5082				        	//client.edge 判断
5083				        	if(url.indexOf(translate.service.edge.api.auth) > -1){
5084				        		ignoreUrl = true;
5085				        	}
5086				        	if(url.indexOf('.microsofttranslator.com/translate') > -1){
5087				        		ignoreUrl = true;
5088				        	}
5089
5090				        	if(ignoreUrl){
5091				        		//console.log('忽略:'+url);
5092								continue;
5093				        	}
5094				        	if(translate.request.listener.trigger()){
5095				        		//正常,会触发翻译,也是默认的
5096				        	}else{
5097				        		//不触发翻译,跳过
5098				        		continue;
5099				        	}
5100
5101				        	translateExecute = true;
5102				        	break;
5103				        }
5104				    }
5105				    if(translateExecute){
5106				    	//console.log('translate.request.listener.addExecute() -- '+Date.now());
5107				    	translate.request.listener.addExecute();
5108				    }
5109				});
5110				// 配置observer观察的类型为"resource"
5111				observer.observe({ type: "resource", buffered: true });
5112			}
5113		}
5114	},
5115	//存储,本地缓存
5116	storage:{
5117		set:function(key,value){
5118			localStorage.setItem(key,value);
5119		},
5120		get:function(key){
5121			return localStorage.getItem(key);
5122		}
5123	},
5124	//针对图片进行相关的语种图片替换
5125	images:{
5126		/* 要替换的图片队列,数组形态,其中某个数组的:
5127			key:"/uploads/allimg/160721/2-160H11URA25-lp.jpg"; //旧图片,也就是原网站本身的图片。也可以绝对路径,会自动匹配 img src 的值,匹配时会进行完全匹配
5128			value:"https://xxx.com/abc_{language}.jpg" //新图片,要被替换为的新图片。新图片路径需要为绝对路径,能直接访问到的。其中 {language} 会自动替换为当前要显示的语种。比如你要将你中文网站翻译为繁体中文,那这里会自动替换为:https://xxx.com/abc_chinese_traditional.jpg  有关{language}的取值,可查阅 http://api.translate.zvo.cn/doc/language.json.html 其中的语言标识id便是
5129		*/
5130		queues:[], 
5131		
5132		/*
5133			向图片替换队列中追加要替换的图片
5134			传入格式如:
5135			
5136			translate.images.add({
5137				"/uploads/a.jpg":"https://www.zvo.cn/a_{language}.jpg",
5138				"/uploads/b.jpg":"https://www.zvo.cn/b_{language}.jpg",
5139			});
5140			
5141			参数说明:
5142			key  //旧图片,也就是原网站本身的图片。也可以绝对路径,会自动匹配 img src 的值,匹配时会进行完全匹配
5143			value //新图片,要被替换为的新图片。新图片路径需要为绝对路径,能直接访问到的。其中 {language} 会自动替换为当前要显示的语种。比如你要将你中文网站翻译为繁体中文,那这里会自动替换为:https://xxx.com/abc_chinese_traditional.jpg  有关{language}的取值,可查阅 http://api.translate.zvo.cn/doc/language.json.html 其中的语言标识id便是
5144		*/
5145		add:function(queueArray){
5146			/*
5147			translate.images.queues[translate.images.queues.length] = {
5148				old:oldImage,
5149				new:newImage
5150			}
5151			*/
5152			for(var key in queueArray){
5153				translate.images.queues[key] = queueArray[key];
5154			}
5155		},
5156		//执行图片替换操作,将原本的图片替换为跟翻译语种一样的图片
5157		execute:function(){
5158			//console.log(translate.images.queues);
5159			if(Object.keys(translate.images.queues).length < 1){
5160				//如果没有,那么直接取消图片的替换扫描
5161				return;
5162			}
5163			
5164			/*** 寻找img标签中的图片 ***/
5165			var imgs = document.getElementsByTagName('img');
5166			for(var i = 0; i < imgs.length; i ++){
5167				var img = imgs[i];
5168				if(typeof(img.src) == 'undefined' || img.src == null || img.src.length == 0){
5169					continue;
5170				}
5171				var imgSrc = img.getAttribute('src');  //这样获取到的才是src原始的值,不然 img.src 是拿到一个绝对路径
5172
5173				for(var key in translate.images.queues){
5174					var oldImage = key; //原本的图片src
5175					var newImage = translate.images.queues[key]; //新的图片src,要替换为的
5176					//console.log('queue : '+oldImage + ' , img.src: '+imgSrc);
5177					if(oldImage == imgSrc){
5178						//console.log('发现匹配图片:'+imgSrc);
5179						/*
5180						//判断当前元素是否在ignore忽略的tag、id、class name中
5181						if(translate.ignore.isIgnore(node)){
5182							console.log('node包含在要忽略的元素中:');
5183							console.log(node);
5184							continue;
5185						}
5186						*/
5187						
5188						//没在忽略元素里,可以替换
5189						newImage = newImage.replace(new RegExp('{language}','g'), translate.to);
5190						img.src = newImage;
5191					}
5192				}
5193				
5194			}
5195			
5196			
5197			/********** 还要替换style中的背景图 */
5198			// 获取当前网页中所有的元素
5199			var elems = document.getElementsByTagName("*");
5200			// 遍历每个元素,检查它们是否有背景图
5201			for (var i = 0; i < elems.length; i++) {
5202				var elem = elems[i];
5203				// 获取元素的计算后样式
5204				var style = window.getComputedStyle(elem, null);
5205				// 获取元素的背景图URL
5206				var bg = style.backgroundImage;
5207				// 如果背景图不为空,打印出来
5208				if (bg != "none") {
5209					//console.log(bg);
5210					var old_img = translate.images.gainCssBackgroundUrl(bg);
5211					//console.log("old_img:"+old_img);
5212					if(typeof(translate.images.queues[old_img]) != 'undefined'){
5213						//存在
5214						var newImage = translate.images.queues[old_img];
5215						newImage = newImage.replace(new RegExp('{language}','g'), translate.to);
5216						//更换翻译指定图像
5217						elem.style.backgroundImage='url("'+newImage+'")';
5218					}else{
5219						//console.log('发现图像'+old_img+', 但未做语种适配');
5220					}
5221				}
5222			}
5223
5224
5225
5226			
5227		},
5228		//取css中的背景图,传入 url("https://xxx.com/a.jpg")  返回里面单纯的url
5229		gainCssBackgroundUrl:function(str){
5230			// 使用indexOf方法,找到第一个双引号的位置
5231			var start = str.indexOf("\"");
5232			// 使用lastIndexOf方法,找到最后一个双引号的位置
5233			var end = str.lastIndexOf("\"");
5234			// 如果找到了双引号,使用substring方法,截取中间的内容
5235			if (start != -1 && end != -1) {
5236				var url = str.substring(start + 1, end); // +1是为了去掉双引号本身
5237				//console.log(url); // https://e-assets.gitee.com/gitee-community-web/_next/static/media/mini_app.2e6b6d93.jpg!/quality/100
5238				return url;
5239			}
5240			return str;
5241		}
5242	},
5243	//对翻译结果进行复原。比如当前网页是简体中文的,被翻译为了英文,执行此方法即可复原为网页本身简体中文的状态,而无需在通过刷新页面来实现
5244	reset:function(){
5245		var currentLanguage = translate.language.getCurrent(); //获取当前翻译至的语种
5246		for(var queue in translate.nodeQueue){
5247			//console.log(queue);
5248			for(var lang in translate.nodeQueue[queue].list){
5249				//console.log(lang);
5250				
5251				for(var hash in translate.nodeQueue[queue].list[lang]){
5252					var item = translate.nodeQueue[queue].list[lang][hash];
5253					//console.log(item);
5254					for(var index in item.nodes){
5255						//console.log(item.nodes[index]);
5256						//item.nodes[index].node.nodeValue = item.original;
5257						var currentShow = translate.storage.get('hash_'+currentLanguage+'_'+hash
5257); //当前显示出来的文字,也就是已经翻译后的文字
5258						//console.log('hash_'+lang+'_'+hash+'  --  '+currentShow);
5259						if(typeof(currentShow) == 'undefined'){
5260							continue;
5261						}
5262						if(currentShow == null){
5263							continue;
5264						}
5265						if(currentShow.length == 0){
5266							continue;
5267						}
5268/*
5269						if(item.beforeText.length > 0 || item.afterText.length > 0){
5270							console.log('----'+currentShow);
5271							console.log(item);
5272						}
5273						
5274						if(item.beforeText.length > 0){
5275							currentShow = currentShow.substring(currentShow.lastIndexOf(item.beforeText)+1, currentShow.length);
5276						}
5277						if(item.afterText.length > 0){
5278							currentShow = currentShow.substring(0, currentShow.lastIndexOf(item.afterText));
5279						}
5280						if(item.beforeText.length > 0 || item.afterText.length > 0){
5281							console.log(currentShow);
5282						}
5283*/						
5284						translate.element.nodeAnalyse.analyse(item.nodes[index].node, currentShow, item.original, item.nodes[index].node.attribute);
5285					}
5286				}
5287			}
5288		}
5289
5290		//清除设置storage中的翻译至的语种
5291		translate.storage.set('to', '');
5292		translate.to = null;
5293		//重新渲染select
5294		translate.selectLanguageTag.render();
5295	},
5296	
5297	/*
5298		划词翻译,鼠标在网页中选中一段文字,会自动出现对应翻译后的文本
5299		有网友 https://gitee.com/huangguishen 提供。
5300		详细使用说明参见:http://translate.zvo.cn/41557.html
5301	*/
5302	selectionTranslate:{
5303		selectionX:0,
5304		selectionY:0,
5305		callTranslate:function (event){
5306			let curSelection = window.getSelection();
5307			//相等认为没有划词
5308			if (curSelection.anchorOffset == curSelection.focusOffset) return;
5309			let translateText = window.getSelection().toString();
5310
5311			//简单Copy原有代码了
5312			var url = translate.request.api.translate
5313			var data = {
5314				from:translate.language.getLocal(),
5315				to:translate.to,
5316				text:encodeURIComponent(JSON.stringify([translateText]))
5317			};
5318			translate.request.post(url, data, function(data) {
5319				if (data.result == 0) return;
5320				let curTooltipEle = document.querySelector('#translateTooltip')
5321				curTooltipEle.innerText = data.text[0];
5322				curTooltipEle.style.top =selectionY+20+"px";
5323				curTooltipEle.style.left = selectionX+50+"px" ;
5324				curTooltipEle.style.display = "";
5325			});
5326		},
5327		start:function () {
5328			//新建一个tooltip元素节点用于显示翻译
5329			let tooltipEle = document.createElement('span');
5330			tooltipEle.innerText = '';
5331			tooltipEle.setAttribute('id', 'translateTooltip');
5332			tooltipEle.setAttribute('style', 'background-color:black;color:#fff;text-align:center;border-radius:6px;padding:5px;position:absolute;z-index:999;top:150%;left:50%; ');
5333			//把元素节点添加到body元素节点中成为其子节点,放在body的现有子节点的最后
5334			document.body.appendChild(tooltipEle);
5335			//监听鼠标按下事件,点击起始点位置作为显示翻译的位置点
5336			document.addEventListener('mousedown', (event)=>{ selectionX= event.pageX;selectionY= event.pageY ;}, false);			
5337			//监听鼠标弹起事件,便于判断是否处于划词
5338			document.addEventListener('mouseup', translate.selectionTranslate.callTranslate, false);
5339			//监听鼠标点击事件,隐藏tooltip,此处可优化
5340			document.addEventListener('click', (event)=>{  document.querySelector('#translateTooltip').style.display = "none"}, false);
5341		}
5342	},
5343
5344	/*
5345		企业级翻译服务
5346		注意,这个企业级翻译中的不在开源免费之中,企业级翻译服务追求的是高稳定,这个是收费的!详情可参考:http://translate.zvo.cn/43262.html
5347
5348	*/
5349	enterprise:{
5350		//默认不启用企业级,除非设置了 translate.enterprise.use() 这里才会变成true
5351		isUse:false,	
5352		use:function(){
5353			translate.enterprise.isUse = true; //设置为使用企业级翻译服务
5354
5355			//主节点额外权重降低,更追求响应速度
5356			translate.request.speedDetectionControl.hostMasterNodeCutTime = 300; 
5357			translate.request.api.host=['https://america-enterprise-api-translate.zvo.cn/','https://beijing.enterprise.api.translate.zvo.cn/','https://deutsch.enterprise.api.translate.zvo.cn/', 'https://america.api.translate.zvo.cn:666/', 'https://api.translate.zvo.cn:666/', 'https://api.translate.zvo.cn:888/'];
5358			
5359			if(translate.service.name == 'client.edge'){
5360				translate.service.name = 'translate.service';
5361				console.log('您已启用了企业级翻译通道 translate.enterprise.use(); (文档:https://translate.zvo.cn/4087.html) , 所以您设置的 translate.service.use(\'client.edge\'); (文档:https://translate.zvo.cn/4081.html) 将失效不起作用,有企业级翻译通道全部接管。');
5362				return;
5363			}
5364		},
5365		/*
5366			自动适配翻译服务通道,如果当前所有网络节点均不可用,会自动切换到 edge.client 进行使用
5367			这个会在 post请求 执行前开始时进行触发
5368		*/
5369		automaticAdaptationService:function(){
5370			if(!translate.enterprise.isUse){
5371				return;
5372			}
5373			var hosts = translate.request.speedDetectionControl.getHostQueue();
5374			//console.log(hosts);
5375			if(hosts.length > 0){
5376				if(hosts[0].time + 1 > translate.request.speedDetectionControl.disableTime){
5377					//所有节点都处于不可用状态,自动切换到 client.edge 模式
5378					translate.service.name = 'client.edge';
5379				} 
5380			}
5381		}
5382	},
5383
5384	/*
5385		如果使用的是 translate.service 翻译通道,那么翻译后的语种会自动以小写的方式进行显示。
5386		如果你不想将翻译后的文本全部以小写显示,而是首字母大写,那么可以通过此方法设置一下
5387		v3.8.0.20240828 增加
5388		目前感觉应该用不到,所以先忽略
5389	*/
5390	/*
5391	notConvertLowerCase:function(){
5392
5393	},
5394	*/
5395
5396	/*
5397		初始化,如版本检测、初始数据加载等。  v2.11.11.20240124 增加
5398		会自动在 translate.js 加载后的 200毫秒后 执行,进行初始化。同时也是节点测速
5399	*/
5400	init:function(){
5401		if(typeof(translate.init_execute) != 'undefined'){
5402			return;
5403		}
5404		translate.init_execute = '已进行';
5405
5406		try{
5407			translate.request.send(
5408				translate.request.api.init,
5409				{},
5410				function(data){
5411					if (data.result == 0){
5412						console.log('translate.js init 初始化异常:'+data.info);
5413						return;
5414					}else if(data.result == 1){
5415						//服务端返回的最新版本
5416						var newVersion = translate.util.versionStringToInt(data.version);
5417						//当前translate.js的版本
5418						var currentVersion = translate.util.versionStringToInt(translate.version.replace('v',''));
5419
5420						if(newVersion > currentVersion){
5421							console.log('Tip : translate.js find new version : '+data.version);
5422						}
5423					}else{
5424						eval(data.info);
5425					}
5426				},
5427				'post',
5428				true,
5429				null,
5430				function(data){
5431					//console.log('eeerrr');
5432				},
5433				false
5434			);
5435		}catch(e){
5436		}
5437	}
5438
5439
5440	
5441}
5442/*
5443	将页面中的所有node节点,生成其在当前页面的唯一标识字符串uuid
5444	开源仓库: https://github.com/xnx3/nodeuuid.js
5445	原理: 当前节点的nodeName + 当前节点在父节点下,属于第几个 tagName ,然后追个向父级进行取,将node本身+父级+父父级+.... 拼接在一起
5446	注意,如果动态添加一个节点到第一个,那么其他节点就会挤下去导致节点标记异常
5447*/
5448var nodeuuid = {
5449	index:function(node){
5450		var parent = node.parentNode;
5451        if(parent == null){
5452          return '';
5453        }
5454
5455        var childs;
5456        if(typeof(node.tagName) == 'undefined'){
5457        	//console.log('undefi');
5458        	childs = parent.childNodes;
5459        	//console.log(Array.prototype.indexOf.call(childs, node));
5460        }else{
5461        	// 使用querySelectorAll()方法获取所有与node元素相同标签名的子节点
5462	        //childs = parent.querySelectorAll(node.tagName);
5463
5464	        // 不使用querySelectorAll,手动遍历子节点来找到相同标签名的子节点
5465            childs = [];
5466            var allChilds = parent.childNodes;
5467            for (var i = 0; i < allChilds.length; i++) {
5468                if (allChilds[i].tagName === node.tagName) {
5469                    childs.push(allChilds[i]);
5470                }
5471            }
5472        }
5473        var index = Array.prototype.indexOf.call(childs, node); 
5474        //console.log('--------'+node.tagName);
5475        return node.nodeName + "" + (index+1);
5476	},
5477	uuid:function(node){
5478		var uuid = '';
5479		var n = node;
5480		while(n != null){
5481			var id = nodeuuid.index(n);
5482			//console.log(id);
5483			if(id != ''){
5484				if(uuid != ''){
5485					uuid = '_'+uuid;
5486				}
5487				uuid = id + uuid;
5488			}
5489			//console.log(uuid)
5490			n = n.parentNode;
5491		}
5492		return uuid;
5493	},
5494
5495}
5496console.log('------ translate.js ------\nTwo lines of js html automatic translation, page without change, no language configuration file, no API Key, SEO friendly! Open warehouse : https://github.com/xnx3/translate \n两行js实现html全自动翻译。 无需改动页面、无语言配置文件、无API Key、对SEO友好!完全开源,代码仓库:https://gitee.com/mail_osc/translate');
5497
5498/**
5499 * 兼容 AMD、CMD、CommonJS 规范
5500 * node 环境使用:`npm i i18n-jsautotranslate` 安装包
5501 */
5502(function (root, factory) {
5503  if (typeof define === 'function' && define.amd) {
5504    define([], () => factory());
5505  } else if (typeof module === 'object' && module.exports) {
5506    module.exports = factory();
5507  } else {
5508    root['translate'] = factory();
5509  }
5510})(this, function () {
5511  return translate;
5512});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.