PageSourceSearch

https://wcowin.work/javascripts/translate.js

js wcowin.work collected 2026-09-28 05:46:10 UTC 489,042 bytes, 11,463 lines download raw bytes

1/*
2
3	国际化,网页自动翻译。
4	作者:管雷鸣
5	开原仓库:https://github.com/xnx3/translate
6 */ 
7if(typeof(translate) == 'object' && typeof(translate.version) == 'string'){
8	throw new Error('translate.js 已经加载过一次了,当前是重复加载,避免你的翻译出现异常,已帮你拦截此次加载。本信息只是给你一个提示,你可以检查一下你的项目中是否出现了重复引入 translate.js ,当然,这个异常并不会影响到你的具体使用,它已经自动帮你处理拦截了这个异常,只不过提示出来是让你知道,你的代码里出现了重复引入的情况。');
9}
10var translate = {
11	/**
12	 * 当前的版本
13	 * 由 npm 脚本自动更新,无需手动修改
14	 * 格式:major.minor.patch.date
15	 */
16	// AUTO_VERSION_START
17	version: '3.18.98.20251202',
18	// AUTO_VERSION_END
19	/*
20		当前使用的版本,默认使用v2. 可使用 setUseVersion2(); 
21		来设置使用v2 ,已废弃,主要是区分是否是v1版本来着,v2跟v3版本是同样的使用方式
22	*/
23	useVersion:'v2',
24	/*js translate.setUseVersion2 start*/
25	setUseVersion2:function(){
26		translate.useVersion = 'v2';
27		translate.log('提示:自 v2.10 之后的版本默认就是使用V2版本(当前版本为:'+translate.version+'), translate.setUseVersion2() 可以不用再加这一行了。当然加了也无所谓,只是加了跟不加是完全一样的。');
28	},
29	/*js translate.setUseVersion2 end*/
30	/*
31	 * 翻译的对象,也就是 new google.translate.TranslateElement(...)
32	 * 已废弃,v1使用的
33	 */
34	translate:null,
35	
36	/*js translate.includedLanguages end*/
37	/*
38	 * 支持哪些语言切换,包括: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
39	 * 已废弃,请使用 translate.selectLanguageTag.languages 
40	 */
41	includedLanguages:'zh-CN,zh-TW,en',
42	/*js translate.includedLanguages end*/
43
44	/*js translate.resourcesUrl start*/
45	/*
46	 * 资源文件url的路径
47	 * 已废弃,v1使用的
48 	 */
49	resourcesUrl:'//res.zvo.cn/translate',
50	/*js translate.resourcesUrl end*/
51
52	/*js translate.log start*/
53	log: function(obj){
54		console.log(obj);
55	},
56	/*js translate.log end*/
57
58	/**
59	 * 默认出现的选择语言的 select 选择框,可以通过这个选择切换语言。
60	 */
61	selectLanguageTag:{
62		/*
63			v3.1 增加,将 select切换语言的选择框赋予哪个id,这里是具体的id的名字。
64			如果这个id不存在,会创建这个id的元素
65		*/
66		documentId:'translate',
67		/* 是否显示 select选择语言的选择框,true显示; false不显示。默认为true */
68		show:true,
69		/* 
70			支持哪些语言切换
71			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 
72			v2.x 版本根据后端翻译服务不同,支持的语言也不同。具体支持哪些,可通过 http://api.translate.zvo.cn/doc/language.json.html 获取 (如果您私有部署的,将请求域名换为您自己私有部署的域名)
73		*/
74		languages:'',
75		alreadyRender:false, //当前是否已渲染过了 true为是 v2.2增加
76
77		changeLanguageBeforeLoadOfflineFile: function(path){
78
79		},
80
81		selectOnChange:function(event){
82			var language = event.target.value;
83			translate.changeLanguage(language);
84		},
85		//重新绘制 select 语种下拉选择。比如进行二次开发过translate.js,手动进行了设置 translate.to ,但是手动改动后的,在select语种选择框中并不会自动进行改变,这是就需要手动重新绘制一下 select语种选择的下拉选择框
86		refreshRender:function(){
87			// 获取元素
88			let element = document.getElementById(translate.selectLanguageTag.documentId+"SelectLanguage");
89
90			// 删除元素
91			if (element) {
92				element.parentNode.removeChild(element);
93			}
94
95			//设置为未 render 状态,允许进行 render
96			translate.selectLanguageTag.alreadyRender = false;	
97
98			translate.selectLanguageTag.render();
99		},
100		/*
101			自定义语种 translate.selectLanguageTag.languages 的处理,进行按顺序筛选出来
102	
103			@param languageList 当前支持的所有语种列表,传入格式如:
104			[
105				{id: 'english', name: 'English', serviceId: 'en'}, 
106				{id: 'korean', name: '한국어', serviceId: 'ko'},
107				...
108			]
109
110			返回值是将当前翻译通道所支持的语种进行按顺序筛选完后的结果返回。
111			比如 
112			translate.selectLanguageTag.languages = 'english,chinese_simplified,korean';
113			那么这里返回的便是
114
115			[
116				{id: 'english', name: 'English', serviceId: 'en'}, 
117				{id: 'chinese_simplified', name: '简体中文', serviceId: 'zh-CHS'},
118				{id: 'korean', name: '한국어', serviceId: 'ko'}
119			]
120	
121			如果 translate.selectLanguageTag.languages 未设置,那么这里将返回当前支持的所有语种
122		*/
123		customLanguagesHandle:function(languageList){
124			if(translate.selectLanguageTag.languages.length > 0){
125				//设置了自定义显示的语言,需要重新根据自定义的语言进行过滤,同时顺序也要保持跟它一致
126
127				//都转小写判断
128				var divLanguages = translate.selectLanguageTag.languages.toLowerCase();
129				var divArray = divLanguages.split(',');
130				
131				//将支持的语种 languageList 转化为 map 形态
132				if(typeof(translate.selectLanguageTag.supportLanguageMap) == 'undefined'){
133					translate.selectLanguageTag.supportLanguageMap = new Map();
134					for(var si = 0; si<languageList.length; si++){
135						if(typeof(languageList[si]) != 'undefined' && typeof(languageList[si].id)){
136							translate.selectLanguageTag.supportLanguageMap.set(languageList[si].id, languageList[si]);
137						}
138					}
139					//console.log(translate.selectLanguageTag.supportLanguageMap)
140				}
141
142
143				//重新组合要显示的语种
144				var newLangs = [];
145				for(var i = 0; i<divArray.length; i++){
146					if(divArray[i].length > 0 && translate.selectLanguageTag.supportLanguageMap.get(divArray[i]) != null){
147						newLangs.push(translate.selectLanguageTag.supportLanguageMap.get(divArray[i]));
148					}
149				}
150				return newLangs;
151			}
152
153			return languageList;
154		},
155
156		/*
157			自定义切换语言的样式渲染 v3.2.4 增加
158			
159		*/
160		customUI:function(languageList){
161			//select的onchange事件
162			var onchange = function(event){ translate.selectLanguageTag.selectOnChange(event); }
163			
164			//创建 select 标签
165			var selectLanguage = document.createElement("select"); 
166			selectLanguage.id = translate.selectLanguageTag.documentId+'SelectLanguage';
167			selectLanguage.className = translate.selectLanguageTag.documentId+'SelectLanguage';
168			var to = translate.language.getCurrent();
169
170
171			for(var i = 0; i<languageList.length; i++){
172				var option = document.createElement("option"); 
173			    option.setAttribute("value",languageList[i].id);
174
175			    
176				/*判断默认要选中哪个语言*/
177
178			    if(to != null && typeof(to) != 'undefined' && to.length > 0){
179					//设置了目标语言,那就进行判断显示目标语言
180					if(to == languageList[i].id){
181						option.setAttribute("selected",'selected');
182					}
183			    }else{
184					//没设置目标语言,那默认选中当前本地的语种
185					if(languageList[i].id == translate.language.getLocal()){
186						option.setAttribute("selected",'selected');
187					}
188				}
189				
190			    option.appendChild(document.createTextNode(languageList[i].name)); 
191			    selectLanguage.appendChild(option);
192			}
193			//增加 onchange 事件
194			if(window.addEventListener){ // Mozilla, Netscape, Firefox 
195				selectLanguage.addEventListener('change', onchange,false); 
196			}else{ // IE 
197				selectLanguage.attachEvent('onchange',onchange); 
198			} 
199
200			//将select加入进网页显示
201			document.getElementById(translate.selectLanguageTag.documentId).appendChild(selectLanguage);
202				
203		},
204		render:function(){ //v2增加
205			if(translate.selectLanguageTag.alreadyRender){
206				return;
207			}
208			translate.selectLanguageTag.alreadyRender = true;
209			
210			//判断如果不显示select选择语言,直接就隐藏掉
211			if(!translate.selectLanguageTag.show){
212				return;
213			}
214			
215			//判断translate 的id是否存在,不存在就创建一个
216			if(document.getElementById(translate.selectLanguageTag.documentId) == null){
217				var findBody = document.getElementsByTagName('body');
218				if(findBody.length == 0){
219					translate.log('body tag not find, translate.selectLanguageTag.render() is not show Select Language');
220					return;
221				}
222				var body_trans = findBody[0];
223				var div = document.createElement("div");  //创建一个script标签
224				div.id=translate.selectLanguageTag.documentId;
225				body_trans.appendChild(div);
226			}else{
227				//存在,那么判断一下 select是否存在,要是存在就不重复创建了
228				if(document.getElementById(translate.selectLanguageTag.documentId+'SelectLanguage') != null){
229					//select存在了,就不重复创建了
230					return;
231				}
232			}
233
234			//从服务器加载支持的语言库
235			if(typeof(translate.request.api.language) == 'string' && translate.request.api.language.length > 0){
236				//从接口加载语种
237				translate.request.post(translate.request.api.language, {}, function(responseData, requestData){
238					if(responseData.result == 0){
239						translate.log('load language list error : '+responseData.info);
240						return;
241					}
242					//console.log(data.list);
243					translate.request.api.language = responseData.list; //进行缓存,下一次切换语言渲染的时候直接从缓存取,就不用在通过网络加载了
244					translate.selectLanguageTag.customUI(translate.selectLanguageTag.customLanguagesHandle(responseData.list));
245				}, null);
246			}else if(typeof(translate.request.api.language) == 'object'){
247				//无网络环境下,自定义显示语种
248				translate.selectLanguageTag.customUI(translate.selectLanguageTag.customLanguagesHandle(translate.request.api.language));
249			}
250		}
251	},
252	
253	/*
254	 * 当前本地语言
255	 * 已废弃,v1使用的
256	 */
257	//localLanguage:'zh-CN',
258	/*js translate.localLanguage start*/
259	localLanguage:'zh-CN',
260	/*js translate.localLanguage end*/
261	
262	/*js translate.googleTranslateElementInit start*/
263	/**
264	 * google翻译执行的
265	 * 已废弃,v1使用的
266	 */
267	googleTranslateElementInit:function(){
268		var selectId = '';
269		if(document.getElementById('translate') != null){	// && document.getElementById('translate').innerHTML.indexOf('translateSelectLanguage') > 0
270			//已经创建过了,存在
271			selectId = 'translate';
272		}
273		
274		translate.translate = new google.translate.TranslateElement(
275			{
276				//这参数没用,请忽略
277				pageLanguage: 'zh-CN',
278				//一共80种语言选择,这个是你需要翻译的语言,比如你只需要翻译成越南和英语,这里就只写en,vi
279				//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',
280	            includedLanguages: translate.selectLanguageTag.languages,
281				//选择语言的样式,这个是面板,还有下拉框的样式,具体的记不到了,找不到api~~  
282				layout: 0,
283				//自动显示翻译横幅,就是翻译后顶部出现的那个,有点丑,设置这个属性不起作用的话,请看文章底部的其他方法
284				//autoDisplay: false, 
285				//disableAutoTranslation:false,
286				//还有些其他参数,由于原插件不再维护,找不到详细api了,将就了,实在不行直接上dom操作
287			}, 
288			selectId //触发按钮的id
289		);
290	},
291	/*js translate.googleTranslateElementInit end*/
292	
293	/**
294	 * 初始化,如加载js、css资源
295	 * 已废弃,v1使用的
296	 */
297	/* v2.11.11.20240124 彻底注释掉,有新的init方法替代
298	init:function(){
299		var protocol = window.location.protocol;
300		if(window.location.protocol == 'file:'){
301			//本地的,那就用http
302			protocol = 'http:';
303		}
304		if(this.resourcesUrl.indexOf('://') == -1){
305			//还没设置过,进行设置
306			this.resourcesUrl = protocol + this.resourcesUrl;
307		}
308		
309		//this.resourcesUrl = 'file://G:/git/translate';
310		
311	},
312	*/
313
314
315	/*js translate.execute_v1 start*/
316	/**
317	 * 执行翻译操作
318	 * 已废弃,v1使用的
319	 */
320	execute_v1:function(){
321		translate.log('=====ERROR======');
322		translate.log('The v1 version has been discontinued since 2022. Please use the latest V3 version and refer to: http://translate.zvo.cn/41162.html');
323	},
324	/*js translate.execute_v1 end*/
325
326	/*js translate.setCookie start*/
327	/**
328	 * 设置Cookie,失效时间一年。
329	 * @param name
330	 * @param value
331	 * * 已废弃,v1使用的
332	 */
333	setCookie:function (name,value){
334		var cookieString=name+"="+escape(value); 
335		document.cookie=cookieString; 
336	},
337	/*js translate.setCookie end*/
338
339	/*js translate.getCookie start*/
340	//获取Cookie。若是不存再,返回空字符串
341	//* 已废弃,v1使用的
342	getCookie:function (name){ 
343		var strCookie=document.cookie; 
344		var arrCookie=strCookie.split("; "); 
345		for(var i=0;i<arrCookie.length;i++){ 
346			var arr=arrCookie[i].split("="); 
347			if(arr[0]==name){
348				return unescape(arr[1]);
349			}
350		}
351		return "";
352	},
353	/*js translate.getCookie end*/
354	
355
356	/*js translate.currentLanguage start*/
357	/*
358	 获取当前页面采用的是什么语言
359	 返回值如 en、zh-CN、zh-TW (如果是第一次用,没有设置过,那么返回的是 translate.localLanguage 设置的值)		
360	 已废弃,v1使用的
361	 */
362	currentLanguage:function(){
363		//translate.check();
364		var cookieValue = translate.getCookie('googtrans');
365		if(cookieValue.length > 0){
366			return cookieValue.substr(cookieValue.lastIndexOf('/')+1,cookieValue.length-1);
367		}else{
368			return translate.localLanguage;
369		}
370	},
371	/*js translate.currentLanguage end*/
372	
373	/**
374	 * 切换语言,比如切换为英语、法语
375 	 * @param languageName 要切换的语言语种。传入如 english
376	 * 				会自动根据传入的语言来判断使用哪种版本。比如传入 en、zh-CN 等,则会使用v1.x版本
377	 * 														传入 chinese_simplified 、english 等,则会使用 v2.x版本
378	 */
379	changeLanguage:function(languageName){
380		translate.time.log('触发');
381		//console.log('changeLanguage -> '+languageName);
382		//判断使用的是否是v1.x
383		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,';
384		if(v1.indexOf(','+languageName+',') > -1){
385			//用的是v1.x
386			translate.log('您使用的是v1版本的切换语种方式,v1已在2021年就以废弃,请更换为v2,参考文档: http://translate.zvo.cn/41549.html');
387			translate.check();
388			
389			var googtrans = '/'+translate.localLanguage+'/'+languageName;
390			
391			//先清空泛解析域名的设置
392			var s = document.location.host.split('.');
393			if(s.length > 2){
394				var fanDomain = s[s.length-2]+'.'+s[s.length-1];
395				document.cookie = 'googtrans=;expires='+(new Date(1))+';domain='+fanDomain+';path=/';
396				document.cookie = 'googtrans='+googtrans+';domain='+fanDomain+';path=/';
397			}
398			
399			translate.setCookie('googtrans', ''+googtrans);
400			translate.refreshCurrentPage();
401			return;
402		}
403		
404		translate.lifecycle.changeLanguage.trigger_Trigger({
405			to:languageName
406		});
407
408		
409		//用的是v2.x或更高
410		//translate.setUseVersion2();
411		translate.useVersion = 'v2';
412		var isReload = false; //标记要刷新页面, true刷新, false不刷新
413		//判断是否是第一次翻译,如果是,那就不用刷新页面了。 true则是需要刷新,不是第一次翻译
414		if(translate.node.data == null){
415			translate.node.data = new Map();
416		}
417		if(translate.node.data.size > 0){  //那当前已经被翻译过
418			isReload = true; //标记要刷新页面
419		}
420		
421		translate.to = languageName;
422		translate.storage.set('to',languageName);	//设置目标翻译语言
423		
424		//判断当前页面是否需要进行翻译,如果需要,那还要对整个页面内容文本进行隐藏处理
425		if(translate.visual.webPageLoadTranslateBeforeHiddenText_use){
426			//清除 最开始的全部文本隐藏的first记录
427			if(typeof(translate.visual.hideText.first_translate_request_uuid) != 'undefined'){ 
428				//是第一次翻译请求,记录其uuid
429				translate.visual.hideText.first_translate_request_uuid = undefined;
430			}
431
432			//网页打开时自动隐藏文字,翻译完成后显示译文 http://translate.zvo.cn/549731.html
433			translate.visual.webPageLoadTranslateBeforeHiddenText({
434				inHeadTip: false  //警告要在head中触发的控制台消息提醒,true是如果发现就打印这个提醒。 默认不设置便是true
435			}); 
436		}
437
438
439		//将翻译进行还原
440		translate.reset({
441			selectLanguageRefreshRender:false //是否重新渲染select选择语言到原始未翻译前的状态,默认不设置则是true,进行重新渲染
442		}); 
443
444
445		/*
446			先触发父级,免得当前刷新了,导致父级不执行翻译了
447		*/
448		//检测当前是否处于iframe中,如果当前是在iframe中,有父级页面,也要触发父级进行翻译
449		try{
450			if(window.self !== window.top){
451				if(typeof(window.parent.translate) == 'object' && typeof(window.parent.translate.version) == 'string'){
452					//iframe页面中存在 translate,那么也控制iframe中的进行翻译
453					if(window.parent.translate.to !== languageName){
454						//如果父页面当前的语种不是需要翻译的语种,对其进行翻译
455						window.parent.translate.changeLanguage(languageName);
456					}
457				}
458			}
459		}catch(e){
460			//增加try,避免异常导致无法用
461			translate.log(e);
462		}
463		
464		translate.time.log('父级 iframe 触发changeLanguage完成');
465
466		translate.to = languageName;
467		translate.storage.set('to',languageName);	//设置目标翻译语言
468
469		translate.lifecycle.changeLanguage.resetAfter_Trigger({
470			to: languageName
471		});
472
473		//重新绘制 select 选择语言
474		translate.selectLanguageTag.refreshRender();
475
476		//无刷新切换语言		
477		isReload = false;
478		if(isReload){
479			location.reload(); //刷新页面
480		}else{
481			//不用刷新,直接翻译
482		
483			translate.execute(); //翻译
484		
485			//检测是否有iframe中的子页面,如果有,也对子页面下发翻译命令。这个是针对 LayuiAdmin 框架的场景适配,它的主体区域是在 iframe 中的,不能点击切换语言后,只翻译外面的大框,而iframe中的不翻译
486			const iframes = document.querySelectorAll('iframe');
487			for (let i = 0; i < iframes.length; i++) {
488				const iframe = iframes[i];
489				// 获取 iframe 的 window 对象
490				const iframeWindow = iframe.contentWindow; 
491				try{       
492					if(typeof(iframeWindow.translate) == 'object' && typeof(iframeWindow.translate.version) == 'string'){
493						//iframe页面中存在 translate,那么也控制iframe中的进行翻译
494						if(iframeWindow.translate.to != languageName){
495							iframeWindow.translate.to = languageName;
496							iframeWindow.translate.storage.set('to',languageName);	//设置目标翻译语言
497							//iframeWindow.translate.execute();
498							iframeWindow.translate.changeLanguage(languageName);
499						}
500					}
501				}catch(e){
502					//增加try,避免异常,比如跨域,中断导致无法用
503					if(typeof(iframe.src) === 'string'){
504						translate.log('change sub page iframe exception , iframe src : '+iframe.src);
505					}
506					translate.log(e);
507				}
508			}
509		}
510
511		/*
512		放到了 translate.init 中
513		//当用户代码设置里启用了 translate.listener.start() 然后用户加载页面后并没有翻译(这时listener是不启动的只是把listener.use标记为true),然后手动点击翻译按钮翻译为其他语种(这是不会刷新页面),翻译后也要跟着启动监听
514		if(translate.listener.use == true && translate.listener.isStart == false){
515			if(typeof(translate.listener.start) != 'undefined'){
516				translate.listener.addListener();
517			}
518		}
519		*/
520	},
521	
522	/**
523	 * 自检提示,适用于 v1.x, 在 v2.x中已废弃
524	 * english
525	 * 已废弃,v1使用的
526	 */
527	/*js translate.check start*/
528	check:function(){
529		if(window.location.protocol == 'file:'){
530			translate.log('\r\n---WARNING----\r\ntranslate.js 主动翻译组件自检异常,当前协议是file协议,翻译组件要在正常的线上http、https协议下才能正常使用翻译功能\r\n------------');
531		}
532	},
533	/*js translate.check end*/
534	
535	
536	/**************************** v2.0 */
537	to:'', //翻译为的目标语言,如 english 、chinese_simplified
538	//用户第一次打开网页时,自动判断当前用户所在国家使用的是哪种语言,来自动进行切换为用户所在国家的语种。
539	//如果使用后,第二次在用,那就优先以用户所选择的为主,这个就不管用了
540	//默认是false,不使用,可设置true:使用
541	//使用 setAutoDiscriminateLocalLanguage 进行设置
542	autoDiscriminateLocalLanguage:false,
543	documents:[], //指定要翻译的元素的集合,可设置多个,如设置: document.getElementsByTagName('DIV')
544	
545	//翻译时忽略的一些东西,比如忽略某个tag、某个class等
546	ignore:{
547		tag:['style', 'script', 'link', 'pre', 'code'],
548		class:['ignore','translateSelectLanguage'],
549		id:[],
550		/*
551			传入一个元素,判断这个元素是否是被忽略的元素。 这个会找父类,看看父类中是否包含在忽略的之中。
552			return true是在忽略的之中,false不再忽略的之中
553		*/
554		isIgnore:function(ele){
555			if(ele == null || typeof(ele) == 'undefined'){
556				return false;
557			}
558			if(ele.nodeType === 2){ //是属性,将其转为元素判断,因为当前忽略配置,是针对元素配置的
559				ele = ele.ownerElement;
560			}else if(ele.nodeType === 3){
561				//文本节点,转为元素
562				ele = ele.parentNode;
563			}
564
565			var parentNode = ele;
566			var maxnumber = 100;	//最大循环次数,避免死循环
567			while(maxnumber-- > 0){
568				if(parentNode == null || typeof(parentNode) == 'undefined'){
569					//没有父元素了
570					return false;
571				}
572
573				//判断Tag
574				//var tagName = parentNode.nodeName.toLowerCase(); //tag名字,小写
575				var nodename = translate.element.getNodeName(parentNode).toLowerCase(); //tag名字,小写
576				if(nodename.length > 0){
577					//有nodename
578					if(nodename == 'body' || nodename == 'html' || nodename == '#document'){
579						//上层元素已经是顶级元素了,那肯定就不是了
580						return false;
581					}
582					if(translate.ignore.tag.indexOf(nodename) > -1){
583						//发现ignore.tag 当前是处于被忽略的 tag
584						return true;
585					}
586				}
587				
588
589				//判断class name
590				if(parentNode.className !== null && typeof(parentNode.className) === 'string'){
591					var classNames = parentNode.className;
592					if(classNames == null || typeof(classNames) != 'string'){
593						continue;
594					}
595					//console.log('className:'+typeof(classNames));
596					//console.log(classNames);
597					classNames = classNames.trim().split(' ');
598					for(var c_index = 0; c_index < classNames.length; c_index++){
599						if(classNames[c_index] != null && classNames[c_index].trim().length > 0){
600							//有效的class name,进行判断
601							if(translate.ignore.class.indexOf(classNames[c_index]) > -1){
602								//发现ignore.class 当前是处于被忽略的 class
603								return true;
604							}
605						}
606					}					
607				}
608
609				//判断id
610				if(parentNode.id != null && typeof(parentNode.id) != 'undefined'){
611					//有效的class name,进行判断
612					if(translate.ignore.id.indexOf(parentNode.id) > -1){
613						//发现ignore.id 当前是处于被忽略的 id
614						return true;
615					}
616				}
617
618				//赋予判断的元素向上一级
619				parentNode = parentNode.parentNode;
620			}
621
622			return false;
623		},
624
625		/*
626		 * 忽略不被翻译的文本,这里出现的文本将不会被翻译。
627		 * 这个其实是借用了 自定义术语 的能力,设置了自定义术语的原字符等于翻译后的字符, 于是这个字符就不会被翻译了
628		 * 这里可以是多个,数组,如 ['你好','世界']
629		 */
630		text:[],
631		/*
632			下面的 textRegex 、 setTextRegexs 正则方式设置忽略不翻译text的能力,有 https://github.com/wangliangyu 提交贡献, 弥补 translate.ignore.text 固定设置的不足
633		*/
634		textRegex:[], 
635		/*
636			使用方式如:
637			translate.ignore.setTextRegexs([/请求/g, /[\u4a01-\u4a05]+/g]);
638		*/
639		setTextRegexs: function(arr) {
640			if (!Array.isArray(arr)) throw new Error('参数必须为数组');
641			for (let i = 0; i < arr.length; i++) {
642				if (!(arr[i] instanceof RegExp)) {
643					throw new Error('第' + i + '项不是RegExp对象');
644				}
645			}
646			//this.textRegex = [...this.textRegex, ...arr];
647			//改为兼容 es5 的方式,提供更多兼容
648			this.textRegex = this.textRegex.concat(arr); 
649		},
650	},
651	//刷新页面,你可以自定义刷新页面的方式,比如在 uniapp 打包生成 apk 时,apk中的刷新页面就不是h5的这个刷新,而是app的刷新方式,就需要自己进行重写这个刷新页面的方法了
652	refreshCurrentPage:function(){
653		location.reload();
654	},
655
656	/*
657		判定当前是否使用翻译,也就是使用多语言切换能力
658		注意,它里面会触发 translate.language.getLocal() 进行判定,要保证提前设置了本地语种,或在dom加载完(网页内容已渲染完毕,以便能进行本地语种自动识别)后使用此
659		
660		@param to 要以什么语种显示。 如果不传入,则默认赋予 translate.to
661
662		true:是
663		false:否,不需要进行任何翻译
664	*/
665	isTranslate: function(to){
666		if(typeof(to) === 'undefined'){
667			to = translate.to;
668		}
669		if(to.length === 0){
670			return false;
671		}
672		if(to === translate.language.getLocal()){
673			if(translate.language.translateLocal){
674				return true;
675			}else{
676				return false;
677			}
678		}else{
679			return true;
680		}
681	},
682
683	//自定义翻译术语
684	nomenclature:{
685		/*
686			术语表
687			一维:要转换的语种,如 english
688			二维:翻译至的目标语种,如 english
689			三维:要转换的字符串,如 "你好"
690			结果:自定义的翻译结果,如 “Hallo”
691		*/
692		data:new Array(),
693		
694		/*
695			原始术语表,可编辑的
696			一维:要自定义目标词
697			二维:针对的是哪个语种
698			值:要翻译为什么内容
699
700			其设置如 
701			var data = new Array();
702			data['版本'] = {
703				english : 'banben',
704				korean : 'BanBen'
705			};
706			data['国际化'] = {
707				english : 'guojihua',
708				korean : 'GuoJiHua'
709			};
710			
711			【已过时】
712		*/
713		old_Data:[],
714		/*
715		set:function(data){
716			translate.nomenclature.data = data;
717		},
718		*/
719		set:function(data){
720			alert('请将 translate.nomenclature.set 更换为 append,具体使用可参考: https://github.com/xnx3/translate ');
721		},
722		/*
723			向当前术语库中追加自定义术语。如果追加的数据重复,会自动去重
724			传入参数:
725				from 要转换的语种
726				to 翻译至的目标语种
727				properties 属于配置表,格式如:
728						你好=Hello
729						世界=ShiJie
730
731		*/
732		append:function(from, to, properties){
733			if(typeof(from) == 'undefined' || from == null || from == 'auto'){
734				//如果from未传入,则自动识别当前页面的语种为from
735				//如果自动识别,也要确保是页面加载完后,免得放到了head里,那肯定啥也识别不出来
736				if(document.body == null){
737					translate.log('使用错误!你使用自定义术语 translate.nomenclature.append 时,from 未传值,此时 translate.js 会自动识别当前翻译区域的内容是什么语种,但是你当前吧 translate.nomenclature.append 放在了body之前就加载了,body都还没加载出来,翻译区域当前无内容,所以无法识别当前页面的语种。请将 translate.nomenclature.append 放在翻译内容加载完后再执行 (注意,要将 translate.nomenclature.append 放在 translate.execute() 的前面),建议将 translate.nomenclature.append 放在 </body> 跟 </html> 之间。');
738				}else{
739					if (document.readyState === 'loading') {
740	   					translate.log('使用异常告警:你使用的自定义术语 translate.nomenclature.append 时,from 未传值,此时 translate.js 会自动识别当前翻译区域的内容是什么语种,但页面Dom还未加载完毕时就触发了它,如果翻译区域当前无内容或者内容不是完整的,会造成识别当前页面的语种会有异常不准确,你需要仔细确认这个问题。建议将 translate.nomenclature.append 放在 </body> 跟 </html> 之间。');
741					}
742				}
743				
744				from = translate.language.getLocal();
745			}
746
747			if(typeof(translate.nomenclature.data[from]) == 'undefined'){
748				translate.nomenclature.data[from] = new Array();
749			}
750			if(typeof(translate.nomenclature.data[from][to]) == 'undefined'){
751				translate.nomenclature.data[from][to] = new Array();
752			}
753			
754			//将properties进行分析
755			//按行拆分
756			var line = properties.split('\n');
757			//console.log(line)
758			for(var line_index = 0; line_index < line.length; line_index++){
759				var item = line[line_index].trim();
760				if(item.length < 1){
761					//空行,忽略
762					continue;
763				}
764				var kvs = item.split('=');
765				//console.log(kvs)
766				if(kvs.length != 2){
767					//不是key、value构成的,忽略
768					continue;
769				}
770				var key = kvs[0].trim();
771				var value = kvs[1].trim();
772				//console.log(key)
773				if(key.length == 0 || value.length == 0){
774					//其中某个有空,则忽略
775					continue;
776				}
777
778
779				//加入,如果之前有加入,则会覆盖
780				translate.nomenclature.data[from][to][key] = value;
781				//console.log(local+', '+target+', key:'+key+', value:'+value);
782			}
783
784			//追加完后,对整个对象数组进行排序,key越大越在前面
785			translate.nomenclature.data[from][to] = translate.util.objSort(translate.nomenclature.data[from][to]);
786
787		},
788		//获取当前定义的术语表
789		get:function(){
790			return translate.nomenclature.data;
791		},
792
793		/**
794		 * 对指定文本进行自定义术语替换
795		 * 示例:
796		 *  translate.nomenclature.replace(['你好我好她也好'],'好','hao', null)
797		 * 结果:
798		   {
799		       find:true,
800		       texts: ['你', '我', '她也'],
801		       resultText: "你hao我hao她也hao"
802		    }
803		 * 
804		 * @param text 要进行自定义术语替换的文本
805		 * @param nomenclatureKey 自定义术语的key
806		 * @param nomenclatureValue 自定义术语的value
807		 * @param nodeObject 要进行替换的节点对象,自定义术语命中后,会直接在这个节点上进行替换显示
808		 *                  如果传入 null,则不进行任何替换操作
809		 *                  如果传入具体的值,则是: 
810		 *                      {
811		 *                          node: node节点 ,要改动的文字所在的node节点。 如果改动的文字比如是 div 的title中,那么这里传入的node应该是 title 的node,而不是 div 的node
812		 *                      }
813		 *
814		 * @returns {
815		 *              find:false,    //是否命中了自定义术语,命中了,则是 true,也代表 textArray 已经不是传入的那个了,已经被处理分割过了
816		                texts:['你','好'],    //针对传入的 textArray 参数,进行术语命中完成后,将命中术语的部分剔除掉,进行分割,所返回的新的textArray
817		                resultText: "你hao我hao她也hao"  //对text处理后,替换后的文本
818
819		            }
820		 */
821		replace: function(text, nomenclatureKey, nomenclatureValue, nodeObject){
822			/*
823		    if(text.trim() == nomenclatureValue.trim()){
824		    	
825
826
827		        //这里是自定义术语被替换后,重新扫描时扫出来的,那么直接忽略,不做任何处理。因为自定义术语的结果就是最终结果了
828		        return {
829		            texts:[text],
830		            find:false,
831		            resultText:text
832		        }
833		    }
834		    */
835		    if(nomenclatureKey.length == 0){  //上个版本有这个,应该不会有这个情况,但是还是保留了
836		    	return {
837		            texts:[text],
838		            find:false,
839		            resultText:text
840		        }
841		    }
842
843		    //判断一下原始文本是否有出现在了这个word要翻译的字符串中
844		    var wordKeyIndex = text.indexOf(nomenclatureKey);
845		    if(wordKeyIndex > -1){
846		        //出现了,那么需要将其立即进行更改,将自定义术语定义的结果渲染到页面中,并且将 word 要翻译的字符串中,自定义术语部分删除,只翻译除了自定义术语剩余的部分
847		        
848		        //这里考虑重复替换问题,比如要将 好 替换为  你好 ,如果重复替换,可能会出来 你你你你你好
849		        //另外还要考虑特殊字符问题,如果用 split 会出现异常
850		        //注意,可能会出现多个key的情况
851		        var positions = [];
852		        var pos = wordKeyIndex;
853		        // 当找到 key的文字时继续查找
854		        while (pos !== -1) {
855		            positions.push(pos);
856		            // 从当前位置的下一个字符开始继续查找
857		            pos = text.indexOf(nomenclatureKey, pos + 1);
858		        }
859		        // 遍历所有找到的位置,判断是否是已经自定义术语替换后的,如果全部都是替换后的,那么就不需要继续替换了,直接 return 退出,避免重复替换。
860		        //但是如果只要有一次是没有被替换的,那么都会往下执行,可能会存在重复替换。
861		        //比如  "你好吗我好吗大家好都好呀" 将 "好" 替换为 “好吗”,这里会替换为 “你好吗吗我好吗吗大家好吗都好吗呀” ,因为最后的俩“好”经过识别,是没有被替换过的,所以这句是要被进行替换执行的,这个整句替换是现有的方法,这个后续可以把提花你方法拆分一下,进行针对性的只针对最后的俩“好”进行精准替换,而不对前面的俩“好吗”在进行替换
862		        
863		        var texts = new Array(); //它是text经过pos的分割后的数组,要返回的数组
864		        var resultText = text;
864 //这是有text文本经过将 nomenclatureKey 替换为 nomenclatureValue 之后,得到的新的文本
865		        var lastPos = text.length; //记录上一个pos的位置
866		        for(var i = positions.length-1; i>=0; i--){ //采用--的方式,保证替换后下标依旧能保持一致
867		            var itempos = positions[i];
868
869		            //将pos分割之后的文本,加入到 wordSplits 数组中
870		            texts.unshift(text.substring(itempos + nomenclatureKey.length, lastPos));
871		            //console.log(pos +' --> '+text.substring(pos + nomenclatureKey.length, lastPos));
872
873		            // 将 text 中 的 pos 下标的文本,也就是从 pos 开始,到pos+nomenclatureKey.length 结束的文本,替换为 nomenclatureValue 
874		            resultText = resultText.substring(0, itempos) + nomenclatureValue + resultText.substring(itempos+nomenclatureKey.length);
875
876		            lastPos = itempos;
877		        }
878		        if(lastPos > 0){
879		            //将pos分割之前的文本,加入到 wordSplits 数组中
880		            texts.unshift(text.substring(0, lastPos));
881		        }
882
883		        //筛选 texts ,将空字符串 length == 0 的剔除
884		        if(texts.length > 0){
885		            for(var di = texts.length - 1; di >= 0; di--){
886		                if(texts[di].length == 0){
887		                    texts.splice(di, 1);
888		                }
889		            }
890		        }
891
892
893		        //如果是自定义术语的key等于value,则是属于指定的某些文本不进行翻译的情况,所以这里要单独判断一下,它俩不相等才会去进行替换操作,免得进行性能计算浪费 - 虽然这一步是不会到的,因为在这个方法的入口处就已经经过这个判定了
894		        if(nodeObject != null && typeof(nodeObject.node) !== 'undefined' && nodeObject.node !== null){
895
896		        	// 记录此次node的改变是有 translate.js 导致的,避免被dom改变监听给误以为别的引起的
897		        	if(translate.node.get(nodeObject.node) != null){
898		        		translate.node.get(nodeObject.node).lastTranslateRenderTime = Date.now();
899		        	}else{
900		        		//这个如果有 translate.js 内部自主触发,肯定不会没有值的。但是如果手动再其他程序里触发,那这个是会没有值的
901		        	}
902
903		        	if(nomenclatureKey === nomenclatureValue){
904		        		//自定义忽略翻译的文字 ,key 跟 value 相等,便是忽略翻译的
905		        		translate.element.nodeAnalyse.set(nodeObject.node, nomenclatureKey, nomenclatureValue, nodeObject.attribute);
906		        	}else{
907		        		//自定义术语的
908		        		translate.element.nodeAnalyse.set(nodeObject.node, nomenclatureKey, nomenclatureValue, nodeObject.attribute);
909		        	}
910		        }
911		        
912		        return {
913		            texts:texts,
914		            find:true,
915		            resultText:resultText
916		        }
917		    }else{
918		        return {
919		            texts:[text],
920		            find:false,
921		            resultText:text
922		        }
923		    }
924		},
925		//对传入的str字符进行替换,将其中的自定义术语提前进行替换,然后将替换后的结果返回
926		/*
927		  自定义术语
928		  示例:
929		   translate.nomenclature.dispose(['你好我好她也好'],'好','hao', null)
930		  结果:
931		    {
932		       find:true,
933		       texts: ['你', '我', '她也'],
934		       resultText: "你hao我hao她也hao"
935		    }
936		  
937		  @param {*} texts 要进行自定义术语替换的文本数组,传入比如 ["你好","世界"]
938		  @param {*} nomenclatureKey 
939		  @param {*} nomenclatureValue 
940		  @param {*} nodeObject 要进行替换的节点对象,自定义术语命中后,会直接在这个节点上进行替换显示
941		                   如果传入 null,则不进行任何替换操作
942		                   如果传入具体的值,则是: 
943		                       {
944		                           node: node节点 ,要改动的文字所在的node节点。 如果改动的文字比如是 div 的title中,那么这里传入的node应该是 title 的node,而不是 div 的node
945		                       }
946		 
947
948		  @returns {
949		               find:false,    //是否命中了自定义术语,命中了,则是 true,也代表 textArray 已经不是传入的那个了,已经被处理分割过了
950		               texts:['你','好'],    //针对传入的 textArray 参数,进行术语命中完成后,将命中术语的部分剔除掉,进行分割,所返回的新的textArray . 如果没有命中术语,那么这里是只有一个值,
950那便是返回传入的text
951		               resultText: "你hao我hao她也hao"
952		           }
953		 */
954		dispose: function(textArray, nomenclatureKey, nomenclatureValue, nodeObject){
955		    // 输入验证
956		    if (!Array.isArray(textArray)) {
957		        textArray = [String(textArray)];
958		    }
959
960		    //这里要调用 translate.nomenclature.replace 方法,对 textArray 中的每个文本进行自定义术语替换处理
961		    var result = {
962		    	find:false
963		    };
964		    result.texts = new Array();
965		    result.resultText = new Array();
966		    for(var i = 0; i < textArray.length; i++){
967		        var text = textArray[i];
968		        var res = translate.nomenclature.replace(text, nomenclatureKey, nomenclatureValue, nodeObject);
969		        if(res.find){
970		        	result.find = true;
971		        }
972		        result.texts = result.texts.concat(res.texts);
973		        result.resultText.push(res.resultText);
974		    }
975
976		    //对 result.texts 进行去重处理
977		    if(result.texts.length > 0){
978		        for(var di = result.texts.length - 1; di >= 0; di--){
979		            if(result.texts.indexOf(result.texts[di]) != di){
980		                result.texts.splice(di, 1);
981		            }
982		        }
983		    }
984
985		    return result;
986
987		},
988	},
989
990	//已转为 offline ,这个是对旧版做兼容
991	office:{
992		export:function(){
993			console.log('请使用最新版本的 translate.offline.export , 而不是 translate.office.export');
994		},
995		showPanel:function(){
996			console.log('请使用最新版本的 translate.offline.showPanel , 而不是 translate.office.export');
997		},
998		append:function(to, properties){
999			translate.offline.append(to, properties);
1000		},
1001		fullExtract:{
1002			isUse:false
1003		}
1004	},
1005	offline:{
1006		/*
1007			网页上翻译之后,自动导出当前页面的术语库
1008			
1009			需要先指定本地语种,会自动将本地语种进行配置术语库
1010			
1011		*/
1012		export:function(){
1013			if(translate.language.getLocal() == translate.language.getCurrent()){
1014				alert('本地语种跟要翻译的语种一致,无需导出');
1015				return;
1016			}
1017
1018			var text = '';
1019			for(var uuid in translate.nodeQueue){
1020				if (!translate.nodeQueue.hasOwnProperty(uuid)) {
1021		    		continue;
1022		    	}
1023
1024				var queueValue = translate.nodeQueue[uuid];
1025				for(var lang in translate.nodeQueue[uuid].list){
1026					if (!translate.nodeQueue[uuid].list.hasOwnProperty(lang)) {
1027			    		continue;
1028			    	}
1029					//console.log('------'+lang)
1030					if(typeof(lang) != 'string' || lang.length < 1){
1031						continue;
1032					}
1033					//if(translate.language.getLocal() == lang){
1034						//console.log(translate.nodeQueue[uuid].list[lang]);
1035						for(var hash in translate.nodeQueue[uuid].list[lang]){
1036							if (!translate.nodeQueue[uuid].list[lang].hasOwnProperty(hash)) {
1037					    		continue;
1038					    	}
1039					    	
1040					    	var result = translate.storage.get('hash_'+translate.language.getCurrent()+'_'+hash);
1041							//如果翻译结果不存在,可能是同语种本身就没有翻译,忽略就好了 (因为有个本地语种也强制翻译的能力,所以同语种也放行,在这里进行一次结果判断,免得遗漏同语种也翻译的情况)
1042							if(typeof(result) === 'undefined' || result === null || result.length === 0){
1043								continue;
1044							}
1045							
1046							//将配置中出现的换行替换为 \n 这个符号
1047							var lineText = translate.nodeQueue[uuid].list[lang][hash].original + '='+result;
1048							text = text + '\n' + (lineText.replace(/\n/g, '{\\\\n}'));
1049						}
1050					//}
1051				}
1052				
1053			}
1054
1055			if(text.length > 0){
1056				//有内容
1057				text = 'translate.offline.append(\''+translate.language.getCurrent()+'\',`'+text+'\n`);';
1058				//console.log(text);
1059				translate.util.loadMsgJs();
1060				msg.popups({
1061				    text:'<textarea id="msgPopupsTextarea" style="width:100%; height:100%; color: black; padding: 8px;">loaing...</textarea>',
1062				    width:'750px',
1063				    height:'600px',
1064				    padding:'1px',
1065				});	
1066				document.getElementById('msgPopupsTextarea').value = text;
1067			}else{
1068				msg.alert('无有效内容!');
1069			}
1070
1071
1072		},
1073		//显示导出面板
1074		showPanel:function(){
1075			translate.recycle = function(){}; //重写垃圾回收,弃用
1076
1077			let panel = document.createElement('div');
1078			panel.setAttribute('id', 'translate_export');
1079			panel.setAttribute('class','ignore');
1080
1081			//导出按钮
1082			let button = document.createElement('button');
1083			button.onclick = function() {
1084			  translate.offline.export();
1085			};
1086			button.innerHTML = '导出配置信息';
1087			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;');
1088			panel.appendChild(button);
1089
1090			//说明文字
1091			let textdiv = document.createElement('div');
1092			textdiv.innerHTML = '1. 首先将当前语种切换为你要翻译的语种<br/>2. 点击导出按钮,将翻译的配置信息导出<br/>3. 将导出的配置信息粘贴到代码中,即可完成<br/><a href="http://translate.zvo.cn/4076.html" target="_black" style="color: aliceblue; text-decoration: underline;">点此进行查阅详细使用说明</a>';
1093			textdiv.setAttribute('style','font-size: 14px; padding: 12px;');
1094
1095			panel.appendChild(textdiv);			
1096			
1097			panel.setAttribute('style', 'background-color: black; color: #fff; width: 320px; height: 206px; position: fixed; bottom: 50px; right: 50px;');
1098			//把元素节点添加到body元素节点中成为其子节点,放在body的现有子节点的最后
1099			document.body.appendChild(panel);
1100
1101			translate.util.loadMsgJs();
1102		},
1103		/*
1104			追加离线翻译数据。如果追加的数据重复,会自动去重
1105			传入参数:
1106				from 要转换的语种
1107				to 翻译至的目标语种
1108				properties 属于配置表,格式如:
1109						你好=Hello
1110						世界=ShiJie
1111			这个传入参数跟 translate.nomenclature.append 的传入参数格式是一致的			
1112		*/
1113		append:function(to, properties){
1114			//console.log(properties)
1115			//将properties进行分析
1116			//按行拆分
1117			var line = properties.split('\n');
1118			//console.log(line)
1119
1120			//计算前10行,判定当前配置文件的行开头缩进方式
1121			var lmap = new Map();
1122			for(var line_index = 0; line_index < line.length && line_index < 10; line_index++){
1123				const match = line[line_index].match(/^[ \t]+/);
1124  				var suojin = match ? match[0] : '0'; //0便是没有空白符缩进
1125  				var sum = 1; //累加次数
1126				if(typeof(lmap.get(suojin)) !== 'undefined'){
1127					sum = sum+lmap.get(suojin);
1128				}
1129				lmap.set(suojin, sum);
1130			}
1131
1132			// 核心逻辑:遍历Map找到次数最多的key
1133			let maxCount = 0; // 记录最大次数(初始为0,次数至少为1,不影响)
1134			let maxKey = null; // 记录次数最多的key
1135
1136			// 方式1:for...of遍历Map.entries()(推荐,直观)
1137			for (const [key, count] of lmap.entries()) {
1138			    if (count > maxCount) {
1139			        maxCount = count; // 更新最大次数
1140			        maxKey = key;     // 更新对应key
1141			    }
1142			}
1143			//console.log(lmap);
1144			//console.log(maxKey+' -> '+maxCount);
1145			lmap = null;
1146
1147			for(var line_index = 0; line_index < line.length; line_index++){
1148				var item = line[line_index];
1149				//有缩进,那就需要把行开始的缩进去掉
1150				if(maxKey !== '0'){ 
1151					if(line[line_index].startsWith(maxKey)){
1152						var item = line[line_index].slice(maxKey.length);
1153					}else{
1154						//异常提示告警
1155						if(line[line_index].trim().length > 0){
1156							translate.log('WAINING : translate.offline.append 异常,发现某行的配置项缩进异常,这行的缩进应该跟其他行的缩进保持一致!异常的这行配置项为:\n'+item);
1157						}
1158					}
1159				}
1160				if(item.length < 1){
1161					//空行,忽略
1162					continue;
1163				}
1164				item = item.replace(/\{\\n\}/g, '\n');
1165				var kvs = item.split('=');
1166				//console.log(kvs)
1167				if(kvs.length != 2){
1168					//不是key、value构成的,忽略
1169					continue;
1170				}
1171				var key = kvs[0];
1172				var value = kvs[1];
1173				//console.log(key)
1174				if(key.length == 0 || value.length == 0){
1175					//其中某个有空,则忽略
1176					continue;
1177				}
1178				//console.log('set---'+key);
1179				//加入 storate
1180				translate.storage.set('hash_'+to+'_'+translate.util.hash(key), value);
1181			}
1182		},
1183
1184		
1185		//全部提取能力(整站的离线翻译数据提取)
1186		fullExtract:{
1187			/*js translate.offline.fullExtract.set start*/
1188			/*
1189				将翻译的结果加入
1190				hash: 翻译前的文本的hash
1191				originalText: 翻以前的文本,原始文本
1192				toLanguage: 翻译为什么语言
1193				translateText: 翻译结果的文本
1194			*/
1195			set: async function(hash, originalText, toLanguage, translateText){
1196				if(typeof(translate.storage.IndexedDB) == 'undefined'){
1197					translate.log('ERROR: translate.storage.IndexedDB not find');
1198					return;
1199				}
1200				var obj = await translate.storage.IndexedDB.get('hash_'+hash);
1201				if(typeof(obj) == 'undefined' && obj == null){
1202					obj = {
1203						originalText:originalText
1204					};
1205				}
1206				obj[toLanguage] = translateText;
1207				await translate.storage.IndexedDB.set('hash_'+hash, obj);
1208			},
1209			/*js translate.offline.fullExtract.set end*/
1210
1211			/*js translate.offline.fullExtract.export start*/
1212			/*
1213				将存储的数据导出为 txt 文件下载下来
1214			*/
1215			export: async function(to){
1216				if(typeof(translate.storage.IndexedDB) == 'undefined'){
1217					translate.log('ERROR: translate.storage.IndexedDB not find');
1218					return;
1219				}
1220				if(typeof(to) != 'string'){
1221					translate.log('error : to param not find, example: "english"');
1222					return;
1223				}
1224				var text = 'translate.offline.append(\''+to+'\',`';
1225				
1226				var data = await translate.storage.IndexedDB.list('hash_*');
1227				for(var i in data){
1228					if (!data.hasOwnProperty(i)) {
1229			    		continue;
1230			    	}
1231					//var originalText = data[i].value.originalText.replace(/\n/g, "\\n").replace(/\t/g, "\\t");
1232					//text = text + '\n' + originalText + '='+data[i].value.english.replace(/\n/g, "\\n").replace(/\t/g, "\\t");
1233
1234			    	//如果翻译结果不存在,可能是同语种本身就没有翻译,忽略就好了 (因为有个本地语种也强制翻译的能力,所以同语种也放行,在这里进行一次结果判断,免得遗漏同语种也翻译的情况)
1235					if(typeof(data[i].value) === 'undefined' || typeof(data[i].value[to]) === 'undefined' || data[i].value[to] === null || data[i].value[to].trim().length === 0){
1236						continue;
1237					}
1238
1239					var lineText = data[i].value.originalText+'='+data[i].value[to];
1240					text = text + '\n' + (lineText.replace(/\n/g, '{\\\\n}'));
1241				}
1242				text = text + '\n`);'
1243
1244				const blob = new Blob([text], { type: "text/plain" });
1245				const url = URL.createObjectURL(blob);
1246				const link = document.createElement("a");
1247				link.href = url;
1248				link.download = to+".txt";
1249				link.click();
1250				URL.revokeObjectURL(url);
1251			},
1252			/*js translate.offline.fullExtract.export end*/
1253
1254			/*
1255				是否启用全部提取的能力
1256				true: 启用,  默认是false不启用。
1257				如果设置为true,则每次通过调用翻译接口进行翻译后,都会将翻译的原文、译文、翻译为什么语种,都会单独记录一次,存入浏览器的 IndexedDB 的 translate.js 数据库
1258					然后可以浏览所有页面后,把所有翻译一对一的对应翻译结果直接全部导出,用于做离线翻译配置使用。
1259			*/
1260			isUse:false,
1261		}
1262	},
1263	setAutoDiscriminateLocalLanguage:function(){
1264		translate.autoDiscriminateLocalLanguage = true;
1265	},
1266	/*
1267		待翻译的页面的node队列
1268		一维:key:uuid,也就是execute每次执行都会创建一个翻译队列,这个是翻译队列的唯一标识。   
1269			 value:
1270				k/v 
1271		二维:对象形态,具体有:
1272			 key:expireTime 当前一维数组key的过期时间,到达过期时间会自动删除掉这个一维数组。如果<0则代表永不删除,常驻内存
1273			 value:list 从DOM中自动识别出的语言文本及节点数据,按照语种进行了划分,每个语种便是其中的一项。
1274		三维:针对二维的value,  key:english、chinese_simplified等语种,这里的key便是对value的判断,取value中的要翻译的词是什么语种,对其进行了语种分类    value: k/v
1275		四维:针对三维的value,  key:要翻译的词(经过语种分割的)的hash,   value: n
1275ode数组
1276		五维:针对四维的value,  这是个对象, 其中
1277				original: 是三维的key的hash的原始文字, node 元素中的原始文字(可能是node元素整个内容,也可能是被分割出的某一块内容,比如中英文混合时单独提取中文)
1278				cacheHash: 如果翻译时匹配到了自定义术语库中的词,那么翻译完后存入到缓存中时,其缓存的翻译前字符串已经不是original,而是匹配完术语库后的文本的hash了。所以这里额外多增加了这个属性。如果匹配了术语库,那这里就是要进行缓存的翻译前文本的hash,如果未使用术语库,这里就跟其key-hash 相同。
1279				translateText: 针对 original 的经过加工过的文字,比如经过自定义术语、以及其他处理操作后的,待进行文本翻译的文字。
1280				nodes: 有哪些node元素中包含了这个词,都会在这里记录
1281		六维:针对五维的 nodes,将各个具体的 node 以及 其操作的 attribute 以数组形式列出
1282		七维:针对六维列出的nodes数组,其中包含:
1283				node: 具体操作的node元素
1284				attribute: 也就是翻译文本针对的是什么,是node本身(nodeValue),还是 node 的某个属性,比如title属性,这则是设置为 "title"。如果这里不为空,那就是针对的属性操作的。 如果这里为空或者undefined ,那就是针对node本身,也就是 nodeValue 的字符串操作的
1285				beforeText: node元素中进行翻译结果赋予时,额外在翻译结果的前面加上的字符串。其应用场景为,如果中英文混合场景下,避免中文跟英文挨着导致翻译为英语后,连到一块了。默认是空字符串 ''
1286				afterText:  node元素中进行翻译结果赋予时,额外在翻译结果的后面加上的字符串。其应用场景为,如果中英文混合场景下,避免中文跟英文挨着导致翻译为英语后,连到一块了。默认是空字符串 ''
1287
1288		生命周期: 当execute()执行时创建,  当execute结束(其中的所有request接收到响应并渲染完毕)时销毁(当前暂时不销毁,以方便调试)
1289	*/
1290	nodeQueue:{},
1291	//指定要翻译的元素的集合,可传入一个元素或多个元素
1292	//如设置一个元素,可传入如: document.getElementById('test')
1293	//如设置多个元素,可传入如: document.getElementsByTagName('DIV')
1294	setDocuments:function(documents){
1295		if (documents == null || typeof(documents) == 'undefined') {
1296			return;
1297		}
1298		
1299		if(typeof(documents.length) == 'undefined'){
1300			//不是数组,是单个元素
1301			translate.documents[0] = documents;
1302		}else{
1303			//是数组,直接赋予
1304			translate.documents = documents;
1305		}
1306		//清空翻译队列,下次翻译时重新检索
1307		translate.nodeQueue = {};
1308		//console.log('set documents , clear translate.nodeQueue');
1309	},
1310	//获取当前指定翻译的元素(数组形式 [document,document,...])
1311	//如果用户未使用setDocuments 指定的,那么返回整个网页
1312	//它返回的永远是个数组形式
1313	getDocuments:function(){
1314		if(translate.documents != null && typeof(translate.documents) != 'undefined' && translate.documents.length > 0){
1315			// setDocuments 指定的
1316			return translate.documents;
1317		}else{
1318			//未使用 setDocuments指定,那就是整个网页了
1319			//return document.all; //翻译所有的  这是 v3.5.0之前的
1320			//v3.5.0 之后采用 拿 html的最上层的demo,而不是 document.all 拿到可能几千个dom
1321			var doms = new Array();
1322			doms[0] = document.documentElement;
1323			return doms;
1324		}
1325	},
1326	
1327	listener:{
1328		//当前页面打开后,是否已经执行完execute() 方法进行翻译了,只要执行完一次,这里便是true。 (多种语言的API请求完毕并已渲染html)
1329		//isExecuteFinish:false,
1330		//是否已经使用了 translate.listener.start() 了,如果使用了,那这里为true,多次调用 translate.listener.start() 只有第一次有效
1331		isStart:false,
1332		//用户的代码里是否启用了 translate.listener.start() ,true:启用
1333		//当用户加载页面后,但是未启用翻译时,为了降低性能,监听是不会启动的,但是用户手动点击翻译后,也要把监听启动起来,所以就加了这个参数,来表示当前是否在代码里启用了监听,以便当触发翻译时,监听也跟着触发
1334		use:false, 
1335
1336		//针对 input 的 value 监听情况, 它无法用dom监控,针对像是 vant 框架,要用 input 的 value 进行作为内容æ
1336˜¾ç¤ºçš„,就要采用这种方式来监听变动并翻译了
1337        input:{
1338			/*
1339				原生value属性描述符
1340                如果为null,则是还没对input的value进行监听。
1341                如果已进行监听,会把原本的 value 改变的 set ... 赋予这里。
1342			*/
1343			originalValueDescriptor : null,
1344
1345			/*
1346				启动对 input value 变动的监听及翻译
1347			*/
1348			start:function(){
1349				if(translate.listener.input.originalValueDescriptor !== null){
1350					console.log('已启动过了,无需在启动');
1351					return;
1352				}
1353
1354				// 1. 保存原生value属性描述符
1355				translate.listener.input.originalValueDescriptor = Object.getOwnPropertyDescriptor(
1356					HTMLInputElement.prototype,
1357					'value'
1358				);
1359
1360				// 2. 重写HTMLInputElement原型的value setter(影响所有input)
1361				Object.defineProperty(HTMLInputElement.prototype, 'value', {
1362					...translate.listener.input.originalValueDescriptor,
1363					set(newValue) {
1364						const oldValue = this.value; // this指向当前被修改的input
1365
1366						// 执行原生赋值
1367						translate.listener.input.originalValueDescriptor.set.call(this, newValue);
1368
1369						// 值变化时触发逻辑
1370						if (newValue !== oldValue) {
1371							//console.log(`JS修改了input值:`);
1372							//console.log(`  旧值=${oldValue} → 新值=${newValue}`);
1373							//console.log(this)
1374
1375							//如果有 translate.node 历史,要根据历史判定一下,如果当前不是translate.js 导致的改变,那就是其他js触发的,那么将其删掉,这样才能触发它重新翻译
1376							if(translate.node.find(this)){
1377								var nodeData = translate.node.get(this);
1378								
1379								if(typeof(nodeData.lastTranslateRenderTime) === 'number' && Date.now() - nodeData.lastTranslateRenderTime < 100){
1380									//小于100毫秒,这是 translate.js 引起的改动,不需要任何处理
1381								}else{
1382									//不是 translate.js 引起的,那么需要进行翻译
1383									//删掉当前的记录,以便能正常扫描加入翻译
1384									translate.node.delete(this);
1385								}
1386
1387							}
1388							translate.execute([this]);
1389						}
1390					}
1391				});
1392
1393			},
1394
1395			/*
1396				当启动对input value监听时,如果切换回源语种了且本地语种并不强制翻译,那么就不需要再翻译了,还原回来,避免性能浪费。 
1397				也就是相当于对 translate.listener.input.start() 触发后的还原
1398			*/
1399			reset: function(){
1400				if(translate.listener.input.originalValueDescriptor === null){
1401					return;
1402				}
1403
1404				// 1. 还原HTMLInputElement原型的原生value属性描述符
1405			    Object.defineProperty(
1406			        HTMLInputElement.prototype,
1407			        'value',
1408			        translate.listener.input.originalValueDescriptor
1409			    );
1410
1411			    // 2. 重置标记为未监听状态,允许后续重新启动监听
1412			    translate.listener.input.originalValueDescriptor = null;
1413			}
1414
1415          
1416        },
1417
1418
1419		//translate.listener.start();	//开启html页面变化的监控,对变化部分会进行自动翻译。注意,这里变化区域,是指使用 translate.setDocuments(...) 设置的区域。如果未设置,那么为监控整个网页的变化
1420		start:function(){
1421			if(typeof(translate.temp_listenerStartInterval) != 'undefined'){
1422				//已经触发过一次了,不需要再触发了
1423				return;
1424			}
1425			translate.listener.use = true;
1426
1427			/*
1428			放到了 translate.init 中
1429			translate.temp_listenerStartInterval = setInterval(function(){
1430				if(document.readyState == 'complete'){
1431					//dom加载完成,进行启动
1432
1433					// 先判断定时器是否已被清除(防止重复执行)
1434    				if (!translate.temp_listenerStartInterval){
1435    					return;
1436    				}
1437
1438					clearInterval(translate.temp_listenerStartInterval);//停止
1439					
1440					//如果不需要翻译的情况,是不需要进行监听的
1441					if(translate.language.getCurrent() == translate.language.getLocal()){
1442						if(translate.language.translateLocal){
1443							//本地语种也要强制翻译跟本地语种不一致的语种
1444						}else{
1445							//console.log('本地语种跟目标语种一致,不进行翻译操作,无需监听。');
1446							return;
1447						}
1448					}
1449
1450					//console.log('进行监听。。');
1451					translate.listener.addListener();
1452				}
1453				
1454	        }, 300);
1455	        */
1456	        
1457		},
1458		/*
1459			对 dom 动态监听进行还原操作,还原到未监听时的状态,进行还原
1460		*/
1461		reset: function(){
1462
1463			//清除 translate.listener 
1464			if(typeof(translate.listener.observer) != 'undefined' && translate.listener.observer != null){
1465				translate.listener.observer.disconnect();
1466			}
1467
1468			//设置为未启动	
1469			if(translate.listener.isStart){
1470				translate.listener.isStart = false; 
1471			}
1472
1473			//还原 input value 监听
1474			translate.listener.input.reset();
1475		},
1476		
1477		/*
1478			用于监听发生改变的这个 node 是否有正常需要翻译的内容、以及是否是非translate.js触发的需要被翻译。
1479			注意,传入进行判断的node中的文本必须是 node.nodeValue ,也就是这个必须是 node.nodeType == 2(某个元素的属性,比如 input 的 placeholder) 或 3(文本节点), 这样他们才会有正常的 node.nodeValue,而且文本也存在于 node.nodeValue 中
1480			比如 div title="你好" ,要对 title 的 你好 这个值进行判定,传入的node必须是 title 的 node,而非 div 的 no
1480de
1481			它主要是为了给 translate.listener.addListener 中的动态监听node改变所服务的
1482
1483			@param node 要判断的这个是否需要触发翻译的node
1484			@return boolean true:需要触发 translate.execute(node) 进行翻译
1485		*/
1486		nodeValueChangeNeedTranslate: function(node){
1487			//是否是要加入翻译扫描触发执行,是则是true
1488			var addTranslateExecute = true;
1489
1490			/*
1491				不会进入翻译的情况 - 
1492					1. 认为是有 translate.js 本身翻译导致的改变,不进行翻译
1493						取 translate.node.data 中的数据,当改变的node节点在其中找到了对应的数据后,进行判定
1494							1. 是整体翻译,且当前node改变后的内容,跟上次翻译后的结果一样,那说明当前node改变事件
1495							2. 不是整体翻译,可能是触发自定义术语、或直接没启用整体翻译能力,那就要根据最后翻译时间这个来判定了。如果这个node元素,已经被翻译过了,最后一次翻译渲染时间,距离当前时间不超过500毫秒
1496					2. 其他的情况如果后续发现有遗漏,再加入,当前没有这种考虑
1497				*/	
1498			if(translate.node.get(node) != null){
1499				//根据现实结果来判断是否是有translate.js 本身翻译导致的dom改变
1500				if(typeof(translate.node.get(node).translateResults) !== 'undefined' && typeof(translate.node.get(node).translateResults[node.nodeValue]) === 'number'){
1501					//是translate.js翻译导致的dom文字改变
1502					addTranslateExecute = false;
1503				}
1504				
1505				if(addTranslateExecute === true){
1506					if(typeof(translate.node.get(node).whole) !== 'undefined' && translate.node.get(node).whole === true){
1507						//整体翻译
1508						if(typeof(translate.node.get(node).resultText) !== 'undefined' && translate.node.get(node).resultText === node.nodeValue){
1509							//当前改变后的内容,跟上次翻译后的结果一样,那说明当前node改变事件,是有translate.js 本身翻译导致的,不进行翻译
1510							addTranslateExecute = false;
1511						}
1512					}else{
1513						//不是整体翻译,可能是触发自定义术语、或直接没启用整体翻译能力
1514
1515						//这就要根据最后翻译时间这个来判定了 -- 这个计划要剔除,因为本身在 translate.node.get(node).translateResults 已经判定了,这个属于重复判定。 这个先留一段时间
1516						if(typeof(translate.node.get(node).lastTranslateRenderTime) === 'number' && translate.node.get(node).lastTranslateRenderTime + 30 > Date.now()){
1517							//如果这个node元素,已经被翻译过了,最后一次翻译渲染时间,距离当前时间不超过500毫秒,那认为这个元素动态改变,是有translate.js 本身引起的,将不做任何动作	
1518							addTranslateExecute = false;
1519						}
1520					}
1521				}
1522			}
1523
1524			//如果新的里面没有非空白字符的值,那也不再触发翻译
1525			if(addTranslateExecute === true){
1526				if(node.nodeValue.trim().length === 0){
1527					addTranslateExecute = false;
1528				}
1529			}
1530
1531			return addTranslateExecute;
1532		},
1533
1534
1535		//增加监听,开始监听。这个不要直接调用,需要使用上面的 start() 开启
1536		addListener:function(){
1537			if(translate.listener.isStart == true){
1538				//console.log('translate.listener.start() 已经启动了,无需再重复启动监听,增加浏览器负担');
1539				return;
1540			}
1541			translate.listener.isStart = true; //记录已执行过启动方法了
1542			//console.log('translate.listener.addListener() ...');
1543
1544			// 观察器的配置(需要观察什么变动)
1545			translate.listener.config = { attributes: true, childList: true, subtree: true, characterData: true, attributeOldValue:true, characterDataOldValue:true };
1546			// 当观察到变动时执行的回调函数
1547			translate.listener.callback = function(mutationsList, observer) {
1548				var documents = []; //有变动的元素
1549				//console.log('--------- lisetner 变动');
1550				//console.log(mutationsList);
1551			    // Use traditional 'for loops' for IE 11
1552			    for(let mutation of mutationsList) {
1553			    	let addNodes = [];
1554					if (mutation.type === 'childList') {
1555						if(mutation.addedNodes.length > 0){
1556							//多了组件
1557							for(var ani = 0; ani < mutation.addedNodes.length; ani++){
1558								var addNodeName = translate.element.getNodeName(mutation.addedNodes[ani]).toLowerCase();
1559								if(addNodeName.length >
1559 0 && translate.ignore.tag.indexOf(addNodeName) == -1){
1560									addNodes.push(mutation.addedNodes[ani]);
1561								}
1562							}
1563							//addNodes = mutation.addedNodes;
1564							//documents.push.apply(documents, mutation.addedNodes);
1565						}
1566						if(mutation.removedNodes.length > 0){
1567							//console.log('remove:');
1568							//console.log(mutation.removedNodes);
1569							for(var ri = 0; ri < mutation.removedNodes.length; ri++){
1570								//console.log('listener --  mutation.type === childList -- delete node ----: ');
1571								//console.log(mutation.removedNodes[ri]);
1572								translate.node.delete(mutation.removedNodes[ri]); //删除掉被dom给移除的节点,比如执行了 InnerHTML 操作的元素会自动删除
1573							}
1574						}
1575					}else if (mutation.type === 'attributes') {
1576						if(mutation.attributeName === 'class' || mutation.attributeName === 'style'){
1577							//如果是class/ style 这种常见的,不做任何改变,直接跳出
1578							continue;
1579						}
1580						
1581						/*
1582							这里要判断一些允许翻译的属性
1583							input 的 placeholder 属性 ,直接判断 placeholder 就行了,也就 input、textarea 有这个属性
1584							img 的 alt 属性
1585							所有标签的 title 属性
1586						*/
1587						 
1588						if(mutation.attributeName === 'placeholder' || mutation.attributeName === 'alt' || mutation.attributeName === 'title'){
1589							//允许翻译
1590						}else{
1591							//判断是否是 translate.element.tagAttribute 自定义翻译属性的
1592							var divTagAttribute = translate.element.tagAttribute[mutation.target.nodeName.toLowerCase()];
1593							//console.log('divTagAttribute:'+divTagAttribute);
1594							if(typeof(divTagAttribute) !== 'undefined' && divTagAttribute.attribute.indexOf(mutation.attributeName) > -1 && divTagAttribute.condition(mutation.target)){
1595								//是自定义翻译这个属性的,以及判定是否达到翻译条件
1596								//条件满足,允许翻译
1597							}else{
1598								//条件不满足,不在翻译的属性范围
1599								continue;
1600							}
1601						}
1602
1603						//这里出现的 mutation.target 是定位到了元素上面,而不是变化的这个 attributes 属性上,需要用 mutation.attributeName 获取到这个属性的node
1604						var node = mutation.target.getAttributeNode(mutation.attributeName);
1605						
1606						//是否是要加入翻译扫描触发执行,是则是true
1607						var addTranslateExecute = translate.listener.nodeValueChangeNeedTranslate(node);
1608						if(addTranslateExecute){ //不是 translate.js 翻译引起的改变,那么
1609							//console.log('listener attributes change ' + mutation.target.nodeName+'['+ mutation.attributeName + '] '+mutation.oldValue+' --> '+node.nodeValue);
1610							translate.node.delete(node); 
1611							addNodes = [node]; //将这个属性转为的node加入待翻译
1612						}
1613					}else if(mutation.type === 'characterData'){
1614						//内容改变
1615						
1616						//是否是要加入翻译扫描触发执行,是则是true
1617						var addTranslateExecute = translate.listener.nodeValueChangeNeedTranslate(mutation.target);
1618
1619						if(addTranslateExecute){ //不是 translate.js 翻译引起的改变,那么
1620							translate.node.delete(mutation.target); 
1621							addNodes = [mutation.target]; //将重新触发 translate.execute();
1622							//console.log('listener - mutation.type === \'characterData\' , node: ');
1623							//console.log(mutation.target)
1624						}
1625						
1626						//documents.push.apply(documents, [mutation.target]);
1627					}
1628
1629					//去重并加入 documents
1630					for(let item of addNodes){
1631						//console.log(item);
1632
1633						//判断是否已经加入过了,如果已经加入过了,就不重复加了
1634						var isFind = false;
1635						for(var di = 0; di < documents.length; di++){
1636							if(documents[di].isSameNode(item)){
1637								isFind = true;
1638								break;
1639							}
1640						}
1641						if(isFind){
1642							break;
1643						}
1644						documents.push.apply(documents, [item]);
1645					}
1646	          	}
1647			    
1648			    if(documents.length > 0){
1649					//有变动,需要看看是否需要翻译,延迟10毫秒执行
1650					translate.time.log('监听到元素发生变化,'+documents.length+'个元素');
1651					translate.execute(documents);					
1652				}
1653			};
1654			// 创建一个观察器实例并传入回调函数
1655			translate.listener.observer = new MutationObserver(translate.listener.callback);
1656			// 以上述配置开始观察目标节点
1657			var docs = translate.getDocuments();
1658			for(var docs_index = 0; docs_index < docs.length; docs_index++){
1659				var doc = docs[docs_index];
1660				if(doc != null){
1661					translate.listener.observer.observe(doc, translate.listener.config);
1662				}
1663			}
1664
1665			
1666			//如果要对 input 的value进行翻译,那么还要监听 input 的 value 的值
1667			if(typeof(translate.element.tagAttribute['input']) === 'object' && translate.element.tagAttribute['input'].attribute.indexOf('value') > -1){
1668				translate.listener.input.start();
1669			}
1670
1671
1672		},
1673		/*
1674			每当执行完一次渲染任务(翻译)时会触发此。注意页面一次翻译会触发多个渲染任务。普通情况下,一次页面的翻译可能会触发两三次渲染任务。
1675			另外如果页面中有ajax交互方面的信息,时,每次ajax信息刷新后,也会进行翻译,也是一次渲染任务。
1676			这个是为了方便扩展使用。比如在layui中扩展,监控 select 的渲染
1677		*/
1678		renderTaskFinish:function(renderTask){
1679			//console.log(renderTask);
1680		},
1681
1682		/*
1683            翻译执行过程中,相关的监控
1684        */
1685        execute:{
1686
1687            /*
1688                每当触发执行 translate.execute() 时,当缓存中未发现,需要请求翻译API进行翻译时,在发送API请求前,触发此
1689
1690                @param uuid:translate.nodeQueue[uuid] 这里的
1691                @param from 来源语种,翻译前的语种
1692				@param to 翻译为的语种
1693            */
1694            renderStartByApi : [],
1695            renderStartByApiRun:function(uuid, from, to){
1696                //console.log(translate.nodeQueue[uuid]);
1697                for(var i = 0;
1697 i < translate.listener.execute.renderStartByApi.length; i++){
1698                    try{
1699                        translate.listener.execute.renderStartByApi[i](uuid, from, to);
1700                    }catch(e){
1701                        translate.log(e);
1702                    }
1703                }
1704            },
1705
1706            /*
1707                每当 translate.execute() 执行完毕(前提是采用API翻译的,API将翻译结果返回,并且界面上的翻译结果也已经渲染完毕)后,触发此方法。
1708
1709                @param uuid:translate.nodeQueue[uuid] 这里的
1710                @param from 来源语种,翻译前的语种
1711				@param to 翻译为的语种
1712            */
1713            renderFinishByApi : [],
1714            renderFinishByApiRun:function(uuid, from, to){
1715                //console.log(translate.nodeQueue[uuid]);
1716                for(var i = 0; i < translate.listener.execute.renderFinishByApi.length; i++){
1717                    try{
1718                    	translate.listener.execute.renderFinishByApi[i](uuid, from, to);
1719                    }catch(e){
1720                        translate.log(e);
1721                    }
1722                }
1723            }
1724        }
1725
1726	},
1727	//对翻译结果进行替换渲染的任务,将待翻译内容替换为翻译内容的过程
1728	renderTask:class{
1729		constructor(){
1730			/*
1731			 * 任务列表
1732			 * map
1733			 * key: node
1734			 * value: [task,task,...]  是多个task的数组集合,存放多个 task,每个task是一个替换。这里的数组是同一个nodeValue的多个task替换
1735			 * 				每个 task:  
1736			 					task['originalText'] 
1737			 					task['resultText'] 存放要替换的字符串
1738			 					task['attribute'] 存放要替换的属性,比如 a标签的title属性。 如果是直接替换node.nodeValue ,那这个没有
1739			 */
1740			this.taskQueue = new Map();
1741			
1742			/*
1743			 * 要进行翻译的node元素,
1744			 * 一维数组 key:node.nodeValue 的 hash , value:node的元素数组
1745			 * 二维数组,也就是value中包含的node集合 [node,node,...]
1746	 		 */
1747			this.nodes = [];
1748		}
1749		
1750		/**
1751		 * 向替换队列中增加替换任务
1752		 * node:要替换的字符属于那个node元素
1753		 * originalText:待翻译的字符
1754		 * resultText:翻译后的结果字符
1755		 * attribute: 要替换的是哪个属性,比如 a标签的title属性,这里便是传入title。如果不是替换属性,这里不用传入,或者传入null
1756		 */
1757		add(node, originalText, resultText, attribute){
1758			//console.log('renderTask.add : originalText:'+originalText+', resultText:'+resultText+', attribute:'+attribute+', node:');
1759			//console.log(node);
1760			var nodeAnaly = translate.element.nodeAnalyse.get(node, attribute); //node解析
1761			//var hash = translate.util.hash(translate.element.getTextByNode(node)); 	//node中内容的hash
1762			var hash = translate.util.hash(nodeAnaly['text']);
1763			//console.log('--------------'+hash);
1764			//console.log(nodeAnaly);
1765
1766			/****** 加入翻译的元素队列  */
1767			if(typeof(this.nodes[hash]) == 'undefined'){
1768				this.nodes[hash] = new Array();
1769			}
1770			this.nodes[hash].push(node);
1771			//console.log(node)
1772			
1773			/****** 加入翻译的任务队列  */
1774			//var tasks = this.taskQueue[hash];
1775			var tasks = this.taskQueue.get(node);
1776			if(tasks == null || typeof(tasks) == 'undefined'){
1777				//console.log(node.nodeValue);
1778				tasks = new Array(); //任务列表,存放多个 task,每个task是一个替换。这里的数组是同一个nodeValue的多个task替换
1779			}
1780			var task = new Array();
1781			
1782			//v2.3.3 增加 -- 开始
1783			//这里要进行处理,因为有时候翻译前,它前或者后是有空格的,但是翻译后会把前或者后的空格给自动弄没了,如果是这种情况,要手动补上
1784			if (originalText.substr(0, 1) == ' ') {
1785				//console.log('第一个字符是空格');
1786				if(resultText.substr(0, 1) != ' '){
1787					//翻译结果的第一个字符不是空格,那么补上
1788					resultText = ' ' + resultText;
1789				}
1790			}
1791			if (originalText.substr(originalText.length - 1, 1) === ' ') {
1792				//console.log('最后一个字符是空格');
1793				if(resultText.substr(0, 1) != ' '){
1794					//翻译结果的最后一个字符不是空格,那么补上
1795					resultText = resultText + ' ';
1796				}
1797			}
1798			//v2.3.3 增加 -- 结束
1799
1800			task['originalText'] = originalText;
1801			task['resultText'] = resultText;
1802			task['attribute'] = attribute;
1803
1804			//console.log(task);
1805			tasks.push(task);
1806			//this.taskQueue[hash] = tasks;
1807			this.taskQueue.set(node, tasks);
1808			/****** 加入翻译的任务队列 end  */
1809		}
1810		//进行替换渲染任务,对页面进行渲染替换翻译
1811		execute(){
1812			//先对tasks任务队列的替换词进行排序,将同一个node的替换词有大到小排列,避免先替换了小的,大的替换时找不到
1813			//for(var hash in this.taskQueue){
1814			for (let node of this.taskQueue.keys()) {
1815				var tasks = this.taskQueue.get(node);
1816				if (tasks == null) {
1817		    		continue;
1818		    	}
1819				if(typeof(tasks) == 'function'){
1820					//进行异常的预处理调出
1821					continue;
1822				}
1823
1824				//进行排序,将原字符串长的放前面,避免造成有部分不翻译的情况(bug是先翻译了短的,导致长的被打断而无法进行适配)
1825				tasks.sort((a, b) => b.originalText.length - a.originalText.length);
1826				
1827				//this.taskQueue[hash] = tasks;
1828				this.taskQueue.set(node, tasks);
1829			}
1830			
1831			//console.log('===========task=========');
1832			//console.log(this.taskQueue);
1833			//console.log(this.nodes);
1834			//console.log('===========task======end===');
1835
1836			
1837			//对nodeQueue进行翻译
1838			for(var hash in this.nodes){
1839				if (!this.nodes.hasOwnProperty(hash)) {
1840		    		continue;
1841		    	}
1842		    	
1843				//var tasks = this.taskQueue[hash]; //取出当前node元素对应的替换任务
1844				//var tagName = this.nodes[hash][0].nodeName; //以下节点的tag name
1845				//console.log(tasks);
1846				for(var node_index = 0; node_index < this.nodes[hash].length; node_index++){
1847					//当前翻译的node
1848					var node = this.nodes[hash][node_index];
1849
1850					//取出当前node元素对应的替换任务
1851					var tasks = this.taskQueue.get(node);
1852					//console.log(tasks);
1853					if (tasks == null) {
1854						translate.log('WARNING : renderTask.execute 中,this.taskQueue.get(node) == null ,理论上要进行替换任务,就应该会有内容的,数据在理论上出现异常');
1855			    		continue;
1856			    	}
1857
1858					//对这个node元素进行替换翻译字符
1859					for(var task_index=0; task_index<tasks.length; task_index++){
1860						var task = tasks[task_index];
1861						if(typeof(tasks) == 'function'){
1862							//进行异常的预处理调出
1863							continue;
1864						}
1865						
1866
1867						// translate.node 记录
1868						
1869						var translateNode = null; //当前操作的,要记录入 translate.node 中的,进行翻译的node
1870						var translateNode_attribute = ''; //当前操作的是node中的哪个attribute,如果没有是node本身则是空字符串
1871						if(typeof(task['attribute']) === 'string' && task['attribute'].length > 0){
1872							//当前渲染任务是针对的元素的某个属性,这是要取出这个元素的具体属性,作为一个目的 node 来进行加入 translate.node 
1873							//是操作的元素的某个属性,这时要判断 是否是 input、textarea 的value属性
1874							if(task['attribute'] === 'value'){
1875								var nodeNameLowerCase = translate.element.getNodeName(this.nodes[hash][node_index]).toLowerCase();
1876								if((nodeNameLowerCase === 'input' || nodeNameLowerCase === 'textarea')){
1877									translateNode = this.nodes[hash][node_index];
1878									translateNode_attribute = 'value';
1879								}
1880							}
1881							if(translateNode === null){
1882								translateNode = this.nodes[hash][node_index].getAttributeNode(task['attribute']);
1883								translateNode_attribute = task['attribute'];
1884							}
1885						}else{
1886							//操作的就是node本身
1887							translateNode = this.nodes[hash][node_index];
1888						}
1889						//console.log(translateNode)
1890						//var nodeAttribute = translate.node.getAttribute(task['attribute']);
1891						if(typeof(translate.node.data.get(translateNode)) === 'undefined' || translate.node.data.get(translateNode) === null){
1892							translate.log('执行异常,渲染时,node 未在 translate.node 中找到, 这个很有可能是点击过快,上一个翻译任务还在网络请求中,又点击了别的地方导致内容又被改变。当前异常已被容错。 node:');
1893							translate.log(translateNode);
1894
1895							var getNodeText = translate.element.nodeAnalyse.get(node, task['attribute']);
1896							translate.node.set(translateNode, {
1897								attribute: translateNode_attribute,
1898								originalText: getNodeText.text,
1899								whole: true,
1900								translateTexts: {}
1901							});
1902							translate.node.setModified(translateNode, 'create:translate.renderTask.execute');
1903						}
1904
1905						// 记录当前有 translate.js 所触发翻译之后渲染到dom界面显示的时间,13位时间戳
1906						translate.node.get(translateNode).lastTranslateRenderTime = Date.now();
1907
1908
1909						//渲染页面进行翻译显示
1910						//console.log(task.originalText+' ('+task['attribute']+') --> ' + task.resultText+', node:');
1911						//console.log(node);
1912						var analyseSet = translate.element.nodeAnalyse.set(node, task.originalText, task.resultText, task['attribute']);
1913						//console.log(analyseSet);
1914
1915						if(translate.node.data.get(translateNode) != null){
1916							//将具体通过文本翻译接口进行翻译的文本记录到 translate.node.data
1917							translate.node.get(translateNode).translateTexts[task.originalText] = task.resultText;
1918							//将翻译完成后要显示出的文本进行记录
1919							translate.node.get(translateNode).resultText = analyseSet.resultText;
1920
1921							//将其加入 translate.history.translateTexts 
1922							translate.history.translateText.add(translate.node.get(translateNode).originalText ,analyseSet.resultText);
1923						}
1924						
1925						
1926						/*
1927						//var tagName = translate.element.getTagNameByNode(this.nodes[hash][task_index]);//节点的tag name
1928						//console.log(tagName)
1929						//console.log(this.nodes[hash][task_index])
1930						//var tagName = this.nodes[hash][task_index].nodeName; //节点的tag name
1931						var nodename = translate.element.getNodeName(this.nodes[hash][task_index]);
1932						
1933						//console.log(this.nodes[hash][task_index]+', '+task.originalText+', '+task.resultText+', tagName:'+tagName);
1934						if(nodename == 'META'){
1935							if(typeof(this.nodes[hash][task_index].name) != 'undefined' && this.nodes[hash][task_index].name != null){
1936								//var nodeName = this.nodes[hash][task_index].name.toLowerCase();  //取meta 标签的name 属性
1937								
1938								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));
1939							}
1940						}else if(nodename == 'IMG'){
1941							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));
1942						}else{
1943							//普通的
1944							//console.log('task.originalText : '+task.originalText);
1945							//console.log(translate.util.regExp.pattern(task.originalText))
1946							//console.log('task.resultText : '+task.resultText);
1947							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));
1948						}
1949						*/
1950					}
1951				}
1952			}
1953			
1954			//console.log('---listen');
1955
1956			//监听 - 增加到翻译历史里面 nodeHistory
1957			if(typeof(this.taskQueue) != 'undefined' && Object.keys(this.taskQueue).length > 0){
1958				//50毫秒后执行,以便页面渲染完毕
1959				var renderTask = this;
1960				setTimeout(function() {
1961					/** 执行完成后,触发用户自定义的翻译完成执行函数 **/
1962					translate.listener.renderTaskFinish(renderTask);
1963				}, 5);
1964				
1965			}else{
1966				//console.log(this.taskQueue);
1967				//console.log('---this.taskQueue is null');
1968			}
1969		}
1970	},
1971
1972
1973	/*
1974		当前状态,执行状态
1975		0 空闲(或者执行翻译完毕)
1976		2 translate.execute 触发,立即变为3,然后再执行 translate.execute 的一些初始化自检啥的
1977		10 扫描要翻译的node,并读取浏览器缓存的翻译内容进行渲染显示
1978		20 浏览器缓存渲染完毕,ajax通过文本翻译接口开始请求,在发起ajax请求前,状态变为20,然后再发起ajax请求
1979		至于翻译完毕后进行渲染,这个就不单独记录了,因为如果页面存在不同的语种,不同的语种是按照不同的请求来的,是多个异步同时进行的过程
1980	*/
1981	state:0,
1982
1983
1984	/*
1985		等待翻译队列  v3.12.6 增加
1986		当前是否有需要等待翻译的任务,这个目的是为了保证同一时间 translate.execute() 只有一次在执行,免得被新手前端给造成死循环,导致edge翻译给你屏蔽,用户网页还卡死
1987		当执行 translate.execute() 时,会先判断状态 translate.state 是否是0空闲的状态,如果空闲,才会执行,如果不是空闲,则不会执行,而是进入到这里进行等待,等待执行完毕后 translate.state 变成0空闲之后,再来执行这里的
1988		
1989	*/
1990	waitingExecute:{
1991		use:true, //默认是使用,自有部署场景不担心并发的场景,可以禁用,以提高用户使用体验。
1992
1993		/*
1994			一维数组形态,存放执行的翻译任务
1995			二维对象形态,存放执行传入的 docs
1996		*/
1997		queue:[],
1998		/*
1999			增加一个翻译任务到翻译队列中
2000			docs 同 translate.execute(docs) 的传入参数
2001		 */ 
2002		add:function(docs){
2003			//向数组末尾追加
2004			translate.waitingExecute.queue.push(docs);
2005			//开启一个定时器进行触发
2006			let intervalId = setInterval(function() {
2007				if(translate.state == 0){
2008					//清除定时器,结束循环
2009					clearInterval(intervalId);
2010					var docs = translate.waitingExecute.get();
2011					translate.execute(docs);
2012					//console.log('stop waitingExecute setInterval');
2013				}
2014			}, 500);
2015		},
2016		/*
2017			从 quque 中取第一个元素,同时将其从queue中删除掉它。
2018			如果取的时候 quque已经没有任何元素了,会返回 null, 但是理论上不会出现null
2019		 */
2020		get:function(){
2021			//使用 shift 方法删除数组的第一个元素,并将第一个元素的值返回
2022			if(translate.waitingExecute.queue.length > 0){
2023				return translate.waitingExecute.queue.shift();
2024			}else{
2025				translate.log('警告, translate.waitingExecute.get 出现异常,quque已空,但还往外取。');
2026				return null;
2027			}
2028		},
2029		/*
2030			当前 translate.translateRequest[uuid] 的是否已经全部执行完毕
2031			这里单纯只是对 translate.translateRequest[uuid] 的进行判断
2032			这里要在 translate.json 接口触发完并渲染完毕后触发,当然接口失败时也要触发。
2033
2034			正常情况下,是根据本地语言不同,进行分别请求翻译的,比如本地中包含中文、英文、俄语三种语种,要翻译为韩语,那么
2035				* 中文->韩语会请求一次api
2036				* 英文->韩语会请求一次APi
2037				* 俄语->韩语会请求一次APi
2038			也就会触发三次
2039
2040			@param uuid translate.translateRequest[uuid]中的uuid,也是 translate.nodeQueue 中的uuid
2041			@param from 来源语种,翻译前的语种
2042			@param to 翻译为的语种
2043			@param result 本次网络请求的结果, 1成功, 0失败。  网络不通,翻译结果返回result非1都是记入0
2044			@param info 如果result为0,这里是失败信息
2045		*/
2046		isAllExecuteFinish:function(uuid, from, to, result, info){
2047
2048			translate.listener.execute.renderFinishByApiRun(uuid, from, to);
2049
2050			//通过 uuid、from 取得本次翻译相关的 texts、nodes , 触发 translateNetworkAfter_Trigger 钩子
2051			//获取请求日志
2052			var requestData = translate.request.data[uuid].list[from][to];
2053			translate.lifecycle.execute.translateNetworkAfter_Trigger({
2054				uuid: uuid,
2055				from: from,
2056				to: to,
2057				texts: requestData.texts,
2058				nodes: requestData.nodes,
2059				result: result,
2060				info: info
2061			});
2062
2063			//console.log('uuid:'+uuid+', from:'+from+', to:'+to);
2064			for(var lang in translate.translateRequest[uuid]){
2065				if (!translate.translateRequest[uuid].hasOwnProperty(lang)) {
2066		    		continue;
2067		    	}
2068				if(translate.translateRequest[uuid][lang].executeFinish == 0){
2069					//这个还没执行完,那么直接退出,不在向后执行了
2070					//console.log('uuid:'+uuid+'  lang:'+lang+'  executeFinish:0  time:'+translate.translateRequest[uuid][lang][i][addtime]);
2071					
2072					//这里要考虑进行时间判断
2073
2074					return;
2075				}
2076			}
2077
2078			//生命周期触发事件
2079			translate.lifecycle.execute.renderFinish_Trigger(uuid, to);
2080
2081			//都执行完了,那么设置完毕
2082			translate.state = 0;
2083			translate.executeNumber++;
2084		}
2085
2086	},
2087	
2088	//execute() 方法已经被执行过多少次了, 只有 translate.execute() 完全执行完,也就是界面渲染完毕后,它才会+1
2089	executeNumber:0,
2090	//translate.execute() 方法已经被触发过多少次了, 只要 translate.execute() 被触发,它就会在触发时立即 +1 (translate.execute() 默认是同一时刻只能执行一次,这个触发是在这个同一时刻执行一次的判定之前进行++ 的,如果这个同一时刻执行一次不通过,还有其他在执行,进入排队执行时,这里也会++ ,当从排队的中顺序排到进行执行时,又会执行++ ) 。 当页面打开第一次触发执行translate.execute(),这里便是 1
2091	executeTriggerNumber:0, 
2092	
2093	lifecycle:{
2094
2095		/*
2096		 * 切换语言 
2097		 */
2098		changeLanguage:{
2099
2100			/*
2101				当触发 translate.changeLanguage(...) 时,会立即先触发此,再去执行 translate.changeLanguage(...) 的处理
2102			*/
2103			trigger:[],
2104			trigger_Trigger:function(data){
2105				for(var i = 0; i < translate.lifecycle.changeLanguage.trigger.length; i++){
2106	                try{
2107	                    translate.lifecycle.changeLanguage.trigger[i](data);
2108	                }catch(e){
2109	                    translate.log(e);
2110	                }
2111	            }
2112
2113	            //兼容旧版本的
2114	            for(var i = 0; i < translate.lifecycle.changeLanguage.old_trigger_array.length; i++){
2115	                try{
2116	                    translate.lifecycle.changeLanguage.old_trigger_array[i](data.to);
2117	                }catch(e){
2118	                    translate.log(e);
2119	                }
2120	            }
2121			},
2122			/*
2123				下面这两个是兼容以前版本的
2124			*/
2125			//通过 push 加入的,只会加入到 old_trigger_array 中,传入参数是 to,也就是 string格式
2126			old_trigger_array:[],
2127			push: function(func){
2128				translate.log('提示, translate.lifecycle.changeLanguage.push 方式已过时,但依旧生效,可正常使用。 最新的方式,文档参考 http://translate.zvo.cn/540189.html ');
2129				translate.lifecycle.changeLanguage.trigger.push(func);
2130				translate.lifecycle.changeLanguage.old_trigger_array.push(func);
2131			},
2132
2133
2134
2135			/*
2136				在触发 translate.reset() 之后、 执行切换语言动作之前,进行触发
2137				{
2138					to: 			//翻译为的语种
2139				}
2140			*/
2141			resetAfter:[],
2142			resetAfter_Trigger:function(data){
2143				var isNextExecute = true; //是否继续向下执行,true则是继续执行,false则是不继续执行。 
2144            	for(var i = 0; i < translate.lifecycle.changeLanguage.resetAfter.length; i++){
2145            		try{
2146                        translate.lifecycle.changeLanguage.resetAfter[i](data);
2147                    }catch(e){
2148                        translate.log(e);
2149                    }
2150                }
2151            },
2152
2153		},
2154
2155		/*
2156			translate.execute() 执行相关
2157		*/
2158		execute:{
2159			/*
2160                每当触发执行 translate.execute() 时,会直接触发此。  
2161                这个触发是指在所有判断之前,也就是只要 触发了 translate.execute() 会立即触发此,然后在进行执行其他的。
2162                {
2163					to: ,			//翻译为的语种
2164					docs: 			//当前触发 translate.execute() 要进行翻译的元素。
2165										比如单纯触发执行 translate.execute() 、translate.request.listener.start()  那么这里 docs 则是 通过 translate.setDocuments(...) 所设置的元素。 如果没有使用 translate.setDocuments(...) 设置过,那就是翻译整个html页面。
2166										如果是 translate.listener.start(); 监控页面发生变化的元素进行翻译,则这里的docs 则是发生变化的元素
2167					executeTriggerNumber:  整数型,当前触发 translate.execute() 执行,属于打开页面后第几次执行 translate.execute() , 它不会经过任何初始化判断,只要触发了 translate.execute() 就会立即+1,即使初始化判断当前不é
2167œ€è¦ç¿»è¯‘、或者当前正在翻译需要排队等待,它依旧也会+1
2168					
2169				}
2170               	
2171               	注意,它有返回参数,boolean 类型:
2172               		true 则是继续执行 translate.execute() 
2173               		false 则是不继续执行,直接终止本次的 translate.execute() 也就是后面的 translate.lifecycle.execute.start 都不会执行到,不会触发。
2174					如果钩子没有任何返回值,则默认是 true
2175
2176               		如果本钩子有多个实现,其中某个实现返回 false,它不会阻止其他钩子的执行,其他的钩子实现也都会触发执行。 只不过里面只要其中有一个是返回 false,那么 translate.execute() 都会终止。
2177            */
2178			trigger: [],
2179			trigger_Trigger:function(data){
2180				var isNextExecute = true; //是否继续向下执行,true则是继续执行,false则是不继续执行。 
2181            	for(var i = 0; i < translate.lifecycle.execute.trigger.length; i++){
2182            		try{
2183                        var isNext = translate.lifecycle.execute.trigger[i](data);
2184                        if(typeof(isNext) === 'boolean' && isNext === false){
2185                        	isNextExecute = false;
2186                        }
2187                    }catch(e){
2188                        translate.log(e);
2189                    }
2190                }
2191                return isNextExecute;
2192            },
2193
2194			/*
2195                每当触发执行 translate.execute() 时,会先进行当前是否可以正常进行翻译的判定,比如 当前语种是否就已经是翻译之后的语种了是否没必要翻译了等。(这些初始判定可以理解成它的耗时小于1毫秒,几乎没有耗时)
2196                经过初始的判断后,发现允许被翻译,那么在向后执行之前,先触发此。  
2197                也就是在进行翻译之前,触发此。 
2198
2199				{
2200					uuid: ,			//translate.nodeQueue[uuid] 这里的
2201					to: ,			//翻译为的语种
2202				}
2203               
2204            */
2205            start : [],
2206            //start_Trigger:function(uuid, to){
2207            start_Trigger:function(data){
2208            	for(var i = 0; i < translate.lifecycle.execute.start.length; i++){
2209            		if(translate.lifecycle.execute.start[i].length === 2){
2210            			//原本的,旧版 20250925 之前的,是string方式传入 uuid, to 这2个参数
2211            			try{
2212	                        translate.lifecycle.execute.start[i](data.uuid, data.to);
2213	                    }catch(e){
2214	                        translate.log(e);
2215	                    }
2216            		}else{
2217            			try{
2218	                        translate.lifecycle.execute.start[i](data);
2219	                    }catch(e){
2220	                        translate.log(e);
2221	                    }
2222            		}
2223                    
2224                }
2225            },
2226
2227
2228			//待整理
2229            start_old : [],
2230            startRun:function(uuid, from, to){
2231                //console.log(translate.nodeQueue[uuid]);
2232                for(var i = 0; i < translate.listener.execute.renderStartByApi.length; i++){
2233                    try{
2234                        translate.listener.execute.renderStartByApi[i](uuid, from, to);
2235                    }catch(e){
2236                        translate.log(e);
2237                    }
2238                }
2239            },
2240
2241            /*
2242                当扫描整个节点完成,进行翻译(1. 命中本地缓存、 2.进行网络翻译请求)之前,触发
2243                待整理
2244			 */
2245            scanNodesFinsh: [],
2246
2247            
2248            /*
2249                每当触发执行 translate.execute() 时,当缓存中未发现,需要请求翻译API进行翻译时,在发送API请求前,触发此
2250				
2251				{
2252					uuid: ,			//translate.nodeQueue[uuid] 这里的
2253					lang: 			//来源语种,翻译前的语种
2254					to: ,			//翻译为的语种
2255					texts: ,		//要翻译的文本,它是一个数组形态,是要进行通过API翻译接口进行翻译的文本,格式如 ['你好','世界']
2256					nodes: 			//要翻译的文本的node集合,也就是有哪些node中的文本参与了 通过API接口进行翻译文本,这里是这些node。 格式如 [
2256node1, node2, ...]
2257				}
2258
2259            */
2260            translateNetworkBefore:[],
2261            //translateNetworkBefore_Trigger:function(uuid, from, to, texts){
2262            translateNetworkBefore_Trigger:function(data){
2263            	/*
2264            	if(typeof(data) == 'string'){
2265            		data = {
2266            			uuid: data,
2267            		};
2268            	}
2269            	if(typeof(from) == 'string'){
2270            		data.from = from;
2271            	}
2272            	if(typeof(to) == 'string'){
2273            		data.to = to;
2274            	}
2275            	if(typeof(texts) == 'string'){
2276            		data.texts = texts;
2277            	}
2278            	*/
2279
2280            	for(var i = 0; i < translate.lifecycle.execute.translateNetworkBefore.length; i++){
2281            		//console.log('translate.lifecycle.execute.translateNetworkBefore[i] 传入参数的数量:'+translate.lifecycle.execute.translateNetworkBefore[i].length);
2282            		if(translate.lifecycle.execute.translateNetworkBefore[i].length === 4){
2283            			//原本的,旧版 20250915 之前的,是string方式传入 uuid, from, to, texts 这四个参数
2284            			try{
2285	                        translate.lifecycle.execute.translateNetworkBefore[i](data.uuid, data.from, data.to, data.texts);
2286	                    }catch(e){
2287	                        translate.log(e);
2288	                    }
2289            		}else{
2290            			//2025.9.15 之后的新的
2291            			try{
2292	                        translate.lifecycle.execute.translateNetworkBefore[i](data);
2293	                    }catch(e){
2294	                        translate.log(e);
2295	                    }
2296            		}
2297                    
2298                }
2299            },
2300
2301            /*
2302				当 translate.execute() 触发网络翻译请求完毕,并将翻译结果渲染到页面完毕后(不管网络翻译请求成功还是失败、还是翻译请求本身返回翻译失败),都触发此。
2303				
2304				{
2305					uuid: ,			//translate.nodeQueue[uuid] 这里的
2306					lang: 			//来源语种,翻译前的语种
2307					to: ,			//翻译为的语种
2308					texts: ,		//要翻译的文本,它是一个数组形态,是要进行通过API翻译接口进行翻译的文本,格式如 ['你好','世界']
2309					nodes: ,		//要翻译的文本的node集合,也就是有哪些node中的文本参与了 通过API接口进行翻译文本,这里是这些node。 格式如 [node1, node2, ...]
2310					result: 		//执行结果 1成功, 0失败
2311				}
2312            */
2313            translateNetworkAfter:[], 
2314            translateNetworkAfter_Trigger:function(data){
2315                for(var i = 0; i < translate.lifecycle.execute.translateNetworkAfter.length; i++){
2316                    try{
2317                        translate.lifecycle.execute.translateNetworkAfter[i](data);
2318                    }catch(e){
2319                        translate.log(e);
2320                    }
2321                }
2322            },
2323           
2324
2325
2326            /*
2327				translate.execute() 的翻译渲染完毕触发
2328				这个完毕是指它当触发 translate.execute() 进行翻译后,无论是全部命中了本地缓存,还是有部分要通过翻译接口发起多个网络请求,当拿到结果(缓存中的翻译结果或多个不同的有xx语种翻译的网络请求全部完成,这个完成是包含所有成功跟失败的响应),并完成将翻译结果渲染到页面中进行显示后,触发此
2329				它跟 translateNetworkFinish 的区别是, translateNetworkFinish 仅仅针对有网络请求的才会触发,而 renderFinish 是如果全部命中了浏览器本地缓存,无需发起任何网络翻译请求这种情况时,也会触发。
2330            	@param uuid translate.nodeQueue 的uuid
2331				@param to 当前是执行的翻译为什么语种
2332            */
2333            renderFinish:[function(uuid, to){ //这里默认带着一个触发翻译为英文后,自动对英文进行元素视觉处理,追加空格的
2334            	if(typeof(translate.visual) != 'undefined'){
2335            		translate.visual.adjustTranslationSpacesByNodequeueUuid(uuid);
2336            	}
2337            }],
2338            renderFinish_Trigger:function(uuid, to){
2339            	for(var i = 0; i < translate.lifecycle.execute.renderFinish.length; i++){
2340                    try{
2341                        translate.lifecycle.execute.renderFinish[i](uuid, to);
2342                    }catch(e){
2343                        translate.log(e);
2344                    }
2345                }
2346            },
2347
2348            /*
2349                每当 translate.execute() 执行结束、中止、自检不通过跳出 ... 等,都会触发这个。  
2350                注意,不管在 translate.execute() 是否自检通过、不管是否进行了翻译、不管文本翻译API接口是否拿到翻译结果,只要 translate.execute 执行完毕或触发了什么自检不通过不再往下执行,都会触发这个。  
2351                这个仅仅只是用于 translate.execute() 从上而下执行完跳出时,进行触发的。
2352				
2353               	{
2354					uuid: translate.nodeQueue[uuid] 这里的。 如果当前没有进行正常翻译,比如自检失败不在执行跳出了,那这个将会返回空字符串 ''
2355					to:   翻译为什么语种,如果当前没有进行正常翻译,比如自检失败不在执行跳出了,那这个将会返回空字符串 ''
2356					state : 状态,用于判断是什么情况执行完的,整数型,取值有:
2357						1 当前翻译未完结,新翻译任务已加入等待翻译队列,待上个翻译任务结束后便会执行当前翻译任务
2358						3 没有指定翻译目标语言,不翻译
2359						5 本地语种跟要翻译的目标语种一样,且没有启用本地语种也强制翻译,那么当前不需要执行翻译,退出
2360						16 已经匹配完自定义术语跟离线翻译,但是用户设置了不掉翻译接口进行翻译,不在向后执行通过文本翻译接口进行翻译
2361						18 已经匹配完自定义术语跟离线翻译,此时所有要翻译的文本都已经匹配完了,没有在需要通过文本翻译接口进行翻译的了
2362						21 进行通过文本翻译API进行调用接口翻译时,某个语种的数据校验失败导致退出。 这个情况理论上应该不会出现,预留这个情况,后续将会剔除这个状态
2363						25 已通过文本翻译接口发起所有翻译请求,translate.execute 执行完毕。 (只是发起网络请求,不代表翻译完成,因为这里还没有等着拿到网络请求的响应结果,还处于网络请求的过程中)
2364					triggerNumber:	translate.execute() 方法已经被触发过多少次了, 只要 translate.execute() 被触发,它就会在触发时立即 +1 (translate.execute() 默认是同一时刻只能执行一次,这个触发是在这个同一时刻执行一次的判定之前进行++ 的,如果这个同一时刻执行一次不通过,还有其他在执行,进入排队执行时,这里也会++ ,当从排队的中顺序排到进行执行时,又会执行++ ) 。 当页面打开第一次触发执行translate.execute(),这里便是 1
2365               	}
2366            */
2367            finally : [],
2368            finally_Trigger:function(data){
2369            	//console.log(data)
2370            	for(var i = 0; i < translate.lifecycle.execute.finally.length; i++){
2371                    try{
2372                        translate.lifecycle.execute.finally[i](data);
2373                    }catch(e){
2374                        translate.log(e);
2375                    }
2376                }
2377            },
2378		}
2379	},
2380
2381	/*translate.execute() start */
2382	/*
2383		执行翻译操作。翻译的是 nodeQueue 中的
2384		docs 如果传入,那么翻译的只是传入的这个docs的。传入如 [document.getElementById('xxx'),document.getElementById('xxx'),...]
2385			 如果不传入或者传入null,则是翻译整个网页所有能翻译的元素	
2386	 */ 
2387	execute:function(docs){
2388		translate.executeTriggerNumber = translate.executeTriggerNumber + 1;
2389		var triggerNumber = translate.executeTriggerNumber; //为了整个 translate.execute 的数据一致性,下面都是使用这个变量
2390
2391		//每次执行execute,都会生成一个唯一uuid,也可以叫做队列的唯一标识,每一次执行execute都会创建一个独立的翻译执行队列
2392		var uuid = translate.util.uuid();
2393		translate.time.log('创建uuid:'+uuid);
2394
2395		//如果页面打开第一次使用,先判断缓存中有没有上次使用的语种,从缓存中取出
2396		if(translate.to == null || translate.to == ''){
2397			var to_storage = translate.storage.get('to');
2398			if(to_storage != null && typeof(to_storage) != 'undefined' && to_storage.length > 0){
2399				translate.to = to_storage;
2400			}
2401		}
2402
2403		/*
2404			进行翻译指定的node操作。优先级为:
2405			1. 这个方法已经指定的翻译 nodes
2406			2. setDocuments 指定的 
2407			3. 整个网页 
2408			其实2、3都是通过 getDocuments() 取,在getDocuments() 就对2、3进行了判断
2409		*/
2410		var all;
2411		if(typeof(docs) != 'undefined' && docs != null){
2412			if(typeof(docs.length) == 'undefined'){
2413				//不是数组,是单个元素
2414				all = new Array();
2415				all[0] = docs;
2416			}else{
2417				//是数组,直接赋予
2418				all = docs;
2419			}
2420		}else{
2421			//2、3
2422			all = translate.getDocuments();
2423		}
2424
2425
2426		//钩子
2427		var triggerIsNextExecute = translate.lifecycle.execute.trigger_Trigger({
2428		    to:translate.to,
2429		    docs: all,
2430		    executeTriggerNumber: triggerNumber,
2431		    uuid: uuid
2432		});
2433		if(!triggerIsNextExecute){
2434			//终止执行
2435			
2436			//钩子
2437			translate.lifecycle.execute.finally_Trigger({
2438			    uuid:uuid,
2439			    to:translate.to,
2440			    state: 2,
2441			    triggerNumber: triggerNumber
2442			});
2443
2444			return;
2445		}
2446		
2447
2448
2449		if(translate.waitingExecute.use){
2450			if(translate.state != 0){
2451				var sliceDocString = '';
2452
2453				if(typeof(docs) != 'undefined' && docs != null){
2454					var sliceDoc = docs.slice(0, 2);
2455				
2456					for(var di = 0; di < sliceDoc.length; di++){
2457						if(sliceDocString.length > 0){
2458							sliceDocString = sliceDocString + ', ';
2459						}
2460						if(sliceDoc[di].nodeType === 1){
2461							//元素
2462							sliceDocString = sliceDocString + ""+sliceDoc[di].tagName;
2463							if(typeof(sliceDoc[di].id) == 'string' && sliceDoc[di].id.length > 0){
2464								sliceDocString = sliceDocString + " id="+sliceDoc[di].id;
2465							}
2466							if(sliceDoc[di].getAttribute('class') != null && typeof(sliceDoc[di].getAttribute('class')) == 'string' && sliceDoc[di].getAttribute('class').length > 0){
2467								sliceDocString = sliceDocString + " class="+sliceDoc[di].getAttribute('class');
2468							}
2469						}else if(sliceDoc[di].nodeType === 2 || sliceDoc[di].nodeType === 3){
2470							//2属性 或 3文本节点
2471							sliceDocString = sliceDocString + sliceDoc[di].nodeValue.replaceAll(/\r?\n/g, '[换行符]');
2472						}
2473					}
2474					sliceDocString = ' ('+docs.length+')['+sliceDocString+(docs.length > 2 ? ', ...':'')+']';
2475				}
2476				
2477
2478				translate.log('当前翻译未完结,新翻译任务已加入等待翻译队列,待上个翻译任务结束后便会执行当前翻译任务'+sliceDocString);
2479				translate.waitingExecute.add(docs);
2480
2481				//钩子
2482				translate.lifecycle.execute.finally_Trigger({
2483				    uuid:uuid,
2484				    to:translate.to,
2485				    state: 4,
2486				    triggerNumber: triggerNumber
2487				});
2488
2489				return;
2490			}
2491		}
2492		
2493		
2494		translate.state = 2;
2495		translate.time.log('触发');
2496
2497		//init.json
2498		translate.request.initRequest();
2499
2500		//console.log('translate.state = 1');
2501		if(typeof(docs) != 'undefined'){
2502			//execute传入参数,只有v2版本才支持
2503			translate.useVersion = 'v2';
2504		}
2505		
2506		if(translate.useVersion == 'v1'){
2507		//if(this.to == null || this.to == ''){
2508			//采用1.x版本的翻译,使用google翻译
2509			//translate.execute_v1();
2510			//return;
2511			//v2.5.1增加
2512			translate.log('提示:https://github.com/xnx3/translate 在 v2.5 版本之后,由于谷歌翻译调整,免费翻译通道不再支持,所以v1版本的翻译接口不再被支持,v1全线下架。考虑到v1已不能使用,当前已自动切换到v2版本。如果您使用中发现什么异常,请针对v2版本进行适配。');
2513			translate.useVersion = 'v2';
2514		}
2515		
2516		
2517
2518		/****** 采用 2.x 版本的翻译,使用自有翻译算法 */
2519		
2520
2521		
2522		//console.log('=====')
2523		//console.log(translate.nodeQueue);
2524		
2525		/* v2.4.3 将初始化放到了 translate.element.whileNodes 中,如果uuid对应的没有,则自动创建
2526
2527		translate.nodeQueue[uuid] = new Array(); //创建
2528		translate.nodeQueue[uuid]['expireTime'] = Date.now() + 120*1000; //删除时间,10分钟后删除
2529		translate.nodeQueue[uuid]['list'] = new Array(); 
2530		*/
2531		//console.log(translate.nodeQueue);
2532		//console.log('=====end')
2533		
2534		
2535		
2536		translate.time.log('渲染出选择语言的select窗口-开始');
2537		//渲染select选择语言
2538		try{
2539			translate.selectLanguageTag.render();	
2540		}catch(e){
2541			translate.log(e);
2542		}
2543		
2544		translate.time.log('渲染出选择语言的select窗口-已完成');
2545
2546		//判断是否还未指定翻译的目标语言
2547		if(translate.to == null || typeof(translate.to) == 'undefined' || translate.to.length == 0){
2548			//未指定,判断如果指定了自动获取用户本国语种了,那么进行获取
2549			if(translate.autoDiscriminateLocalLanguage){
2550				translate.executeByLocalLanguage();
2551			}else{
2552				//没有指定翻译目标语言、又没自动获取用户本国语种,则不翻译
2553				translate.state = 0;
2554
2555				//钩子
2556				translate.lifecycle.execute.finally_Trigger({
2557				    uuid:uuid,
2558				    to:translate.to,
2559				    state: 6,
2560				    triggerNumber: triggerNumber
2561				});
2562
2563				return;
2564			}
2565		}
2566		
2567		//判断本地语种跟要翻译的目标语种是否一样,如果是一样,那就不需要进行任何翻译
2568		if(translate.to == translate.language.getLocal()){
2569			if(translate.language.translateLocal){
2570				//这是自定义设置的允许翻译本地语种中,跟本地语种不一致的语言进行翻译
2571
2572			}else{
2573				translate.state = 0;
2574
2575				//钩子
2576				translate.lifecycle.execute.finally_Trigger({
2577				    uuid:uuid,
2578				    to:translate.to,
2579				    state: 8,
2580				    triggerNumber: triggerNumber
2581				});
2582
2583				return;
2584			}
2585		}
2586		
2587		//初始化 translate.element.tagAttribute ,主要针对 v3.17.10 版本的适配调整,对 translate.element.tagAttribute  的设置做了改变,做旧版本的适配
2588		try{
2589			for(var te_tag in translate.element.tagAttribute){
2590				if (!translate.element.tagAttribute.hasOwnProperty(te_tag)) {
2591		    		continue;
2592		    	}
2593		    	if(translate.element.tagAttribute[te_tag] instanceof Array){
2594		    		//是 v3.17.10 之前版本的设置方式,要进行对旧版本的适配
2595		    		var tArray = translate.element.tagAttribute[te_tag];
2596		    		translate.element.tagAttribute[te_tag] = {
2597		    			attribute: tArray,
2598		    			condition: function(element){
2599							return true;
2600						}
2601		    		}
2602		    	}
2603			}  
2604		}catch(e){
2605			translate.log(e);
2606		}
2607
2608
2609		/********** 翻译进行 */
2610		
2611		translate.time.log('生命周期-触发翻译进行之前,用户自定义的钩子-开始');
2612
2613		//生命周期-触发翻译进行之前,用户自定义的钩子
2614		translate.lifecycle.execute.start_Trigger({
2615			uuid:uuid,
2616			to: translate.to
2617		});
2618		translate.time.log('生命周期-触发翻译进行之前,用户自定义的钩子-完成');
2619		
2620		translate.time.log('进行图片翻译-开始');
2621		//先进行图片的翻译替换,毕竟图片还有加载的过程
2622		translate.images.execute();
2623		translate.time.log('进行图片翻译-完成');
2624
2625		
2626		//console.log('----要翻译的目标元素-----');
2627		//console.log(all)
2628		
2629		if(all.length > 1500){
2630			translate.log('------tip------');
2631			translate.log('警告 translate.execute( docs ) 传入的docs.length 过大,超过1500,这不正常,当前 docs.length : '+all.length+' , 它依旧会正常进行执行,但会有性能损耗。 这个情况很可能是你用的某些框架,没有等dom渲染完就执行了translate.execute() ,同时启用了dom变动监听,导致监听到页面加载大量的DOM渲染; 另外也有可能你本身页面就是列表页或者什么原因就是有大量的动态元素获取后渲染,如果是这种情况,这是正常的。');
2632		}
2633
2634
2635		translate.time.log('开始扫描要翻译区域的元素');
2636		//检索目标内的node元素
2637		for(var i = 0; i< all.length & i < 15000; i++){
2638			var node = all[i];
2639			translate.element.whileNodes(uuid, node);	
2640		}
2641		translate.time.log('扫描要翻译区域的元素完成');
2642
2643		/***** translate.language.translateLanguagesRange 开始 *****/
2644		if(translate.language.translateLanguagesRange.length > 0){
2645			//如果大于0,则是有设置,那么只翻译有设置的语种,不在设置中的语种不会参与翻译
2646			for(var lang in translate.nodeQueue[uuid].list){
2647				if (!translate.nodeQueue[uuid].list.hasOwnProperty(lang)) {
2648		    		continue;
2649		    	}
2650				if(translate.language.translateLanguagesRange.indexOf(lang) < 0){
2651					//删除这个语种
2652					delete translate.nodeQueue[uuid].list[lang];
2653				}
2654			}
2655		}
2656		
2657		/***** translate.language.translateLanguagesRange 结束 *****/
2658		
2659		//修复如果translate放在了页面最顶部,此时执行肯定扫描不到任何东西的,避免这种情况出现报错
2660		if(typeof(translate.nodeQueue[uuid]) == 'undefined'){
2661			translate.nodeQueue[uuid] = new Array();
2662			translate.nodeQueue[uuid].list = [];
2663			translate.log('--- translate.js warn tip 警告!! ---');
2664			translate.log('您使用translate.js时可能放的位置不对,不要吧 translate.js 放在网页最顶部,这样当 translate.js 进行执行,也就是 translate.execute() 执行时,因为网页是从上往下加载,它放在网页最顶部,那么它执行时网页后面的内容都还没加载出来,这个是不会获取到网页任何内容的,也就是它是不起任何作用的');
2665		}
2666		for(var lang in translate.nodeQueue[uuid].list){
2667			if (!translate.nodeQueue[uuid].list.hasOwnProperty(lang)) {
2668	    		continue;
2669	    	}
2670			//console.log('lang:'+lang)
2671			for(var hash in translate.nodeQueue[uuid].list[lang]){
2672				if (!translate.nodeQueue[uuid].list[lang].hasOwnProperty(hash)) {
2673		    		continue;
2674		    	}
2675				//console.log(hash)
2676				if(typeof(translate.nodeQueue[uuid].list[lang][hash]) == 'function'){
2677					//v2.10增加,避免hash冒出个 Contains 出来导致for中的.length 出错
2678					continue;
2679				}
2680				if(typeof(translate.nodeQueue[uuid].list[lang][hash].nodes) == 'undefined' || typeof(translate.nodeQueue[uuid].list[lang][hash].nodes.length) == 'undefined'){
2681					//v3.16.2 增加,针对深圳北理莫斯科学校龙老师提出的这里 .length 遇到了 undefined 的情况
2682					continue;
2683				}
2684
2685
2686				/* 20250912 删除,因为在扫描阶段就已经判定了
2687				for(var nodeindex = translate.nodeQueue[uuid].list[lang][hash].nodes.length-1; nodeindex > -1; nodeindex--){
2688					//console.log(translate.nodeQueue[uuid].list[lang][hash].nodes[nodeindex]);
2689					var analyse = translate.element.nodeAnalyse.get(translate.nodeQueue[uuid].list[lang][hash].nodes[nodeindex].node);
2690					//analyse.text  analyse.node
2691					var nodeid = nodeuuid.uuid(analyse.node);
2692					//translate.nodeQueue[uuid].list[lang][hash].nodes.splice(nodeindex, 1);
2693					//console.log(nodeid+'\t'+analyse.text);
2694					//这个放到了node扫描里去进行判定了,后续要考虑删除
2695					if(translate.node.get(analyse.node) != null){
2696						//存在,判断其内容是否发生了改变
2697						//console.log('比较---------');
2698						//console.log(translate.node[nodeid].translateText);
2699						//console.log(analyse.text);
2700						var nodeAttribute = translate.node.getAttribute(translate.nodeQueue[uuid].list[lang][hash].nodes[nodeindex].attribute);
2701						//console.log(translate.node.get(analyse.node)[nodeAttribute.key]);
2702						if(translate.node.get(analyse.node)[nodeAttribute.key].resultText == analyse.text){
2703							//内容未发生改变,那么不需要再翻译了,从translate.nodeQueue中删除这个node
2704							translate.nodeQueue[uuid].list[lang][hash].nodes.splice(nodeindex, 1);
2705							//console.log('发现相等的node,删除 '+analyse.text+'\t'+hash);
2706						}else{
2707							//console.log("发现变化的node =======nodeid:"+nodeid);
2708							//console.log(translate.node[nodeid].translateText == analyse.text);
2709							//console.log(translate.node[nodeid].node);
2710							//console.log(translate.node[nodeid].translateText);
2711							//console.log(analyse.text);
2712							
2713						}
2714					}else{
2715						//console.log('未在 nodeHistory 中发现,新的node  nodeid:'+nodeid);
2716						//console.log(analyse.node)
2717					}
2718
2719					//以上考虑删除
2720
2721				}
2722				*/
2723
2724				if(translate.nodeQueue[uuid].list[lang][hash].nodes.length == 0){
2725					//如果node数组中已经没有了,那么直接把这个hash去掉
2726					delete translate.nodeQueue[uuid].list[lang][hash];
2727				}
2728			}
2729			if(Object.keys(translate.nodeQueue[uuid].list[lang]).length == 0){
2730				//如果这个语言中没有要翻译的node了,那么删除这个语言
2731				delete translate.nodeQueue[uuid].list[lang];
2732			}
2733		}
2734
2735		translate.time.log('对扫描到的元素进行预处理完毕');
2736		//console.log('new queuq');
2737		//console.log(translate.nodeQueue[uuid])
2738		//translate.node.data[nodeid]
2739
2740		
2741		//console.log('-----待翻译:----');
2742		//console.log(translate.nodeQueue);
2743		
2744		//translateTextArray[lang][0]
2745		var translateTextArray = {};	//要翻译的文本的数组,格式如 ["你好","欢迎"]
2746		var translateHashArray = {};	//要翻译的文本的hash,跟上面的index是一致的,只不过上面是存要翻译的文本,这个存hash值
2747		/*
2748			要翻译的文本所在的 node ,这些要翻译的文本是在哪些node中。
2749			它是二维的。
2750			一维:
2751				key: language
2752				value: map
2753					key: node
2754					value: 1		//value无任何意义,只是凑上去的 , 这样key会自动排重
2755
2756		*/
2757		var translateTextNodeMap = new Map(); 
2758		
2759
2760		/*
2761				要进行第二次扫描的node - 2023.8.22 解决缓存会打散扫描到的翻译文本,导致翻译结束后找寻不到而导致不翻译的问题
2762				一维 key: lang
2763				二维 key: hash
2764				三维 key: 
2765						node: 当前的node元素
2766
2767				四维	-delete ...	 array: 当前缓存中进行翻译的文本数组:
2768							cacheOriginal: 已缓存被替换前的文本
2769							cacheTranslateText: 已缓存被替换后的翻译文本		
2770					
2771		*/
2772		var twoScanNodes = {};
2773		var cacheScanNodes = []; //同上面的 twoScanNodes,只不过 twoScanNodes 是按照lang存的,而这个不再有lang区分
2774		for(var lang in translate.nodeQueue[uuid]['list']){ //二维数组中,取语言
2775			if (!translate.nodeQueue[uuid]['list'].hasOwnProperty(lang)) {
2776	    		continue;
2777	    	}
2778			//console.log('lang:'+lang); //lang为english这种语言标识
2779			if(lang == null || typeof(lang) == 'undefined' || lang.length == 0 || lang == 'undefined'){
2780				//console.log('lang is null : '+lang);
2781				continue;
2782			}
2783
2784			translateTextArray[lang] = [];
2785			translateTextNodeMap.set(lang, new Map());
2786			translateHashArray[lang] = [];
2787			
2788			let task = new translate.renderTask();
2789			//console.log(translate.nodeQueue);
2790			
2791			twoScanNodes[lang] = [];
2792			//二维数组,取hash、value
2793			for(var hash in translate.nodeQueue[uuid]['list'][lang]){
2794				if (!translate.nodeQueue[uuid]['list'][lang].hasOwnProperty(hash)) {
2795		    		continue;
2796		    	}
2797				if(typeof(translate.nodeQueue[uuid]['list'][lang][hash]) == 'function'){
2798					//跳出,增加容错。  正常情况下应该不会这样
2799					continue;
2800				}
2801
2802				//原始的node中的词
2803				var originalWord = translate.nodeQueue[uuid]['list'][lang][hash]['original'];	
2804				//要翻译的词
2805				var translateText = translate.nodeQueue[uuid]['list'][lang][hash]['translateText'];
2806				//console.log(originalWord);
2807
2808				//根据hash,判断本地是否有缓存了
2809				var cacheHash = originalWord == translateText ? hash:translate.util.hash(translateText); //如果匹配到了自定义术语库,那翻译前的hash是被改变了
2810				translate.nodeQueue[uuid]['list'][lang][hash]['cacheHash'] = cacheHash; //缓存的hash。 缓存时,其hash跟翻译的语言是完全对应的,缓存的hash就是翻译的语言转换来的
2811				var cache = translate.storage.get('hash_'+translate.to+'_'+cacheHash);
2812
2813				//缓存是否有拿到具体缓存内容
2814				if(cache != null && cache.length > 0){
2815					for(var node_index = 0; node_index < translate.nodeQueue[uuid]['list'][lang][hash]['nodes'].length; node_index++){
2816						//console.log(translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]);
2817
2818
2819						
2820						//翻译结果的文本,包含了before  、 after 了
2821						var translateResultText = translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]['beforeText']+cache+translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]['afterText'];
2822						task.add(translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]['node'], originalWord, translateResultText, translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]['attribute']);
2823						//this.nodeQueue[lang][hash]['nodes'][node_index].nodeValue = this.nodeQueue[lang][hash]['nodes'][node_index].nodeValue.replace(new RegExp(originalWord,'g'), cache);
2824						//console.log(translateResultText);
2825
2826						//重新扫描这个node,避免这种情况:
2827						/*
2828							localstorage缓存中有几个词的缓存了,但是从缓存中使用时,把原本识别的要翻译的数据给打散了,导致翻译结果没法赋予,导致用户展示时有些句子没成功翻译的问题 -- 2023.8.22
2829							比如有这个 node,其内容为:
2830								你是谁?你好世界
2831							扫描完后,触发了自定义术语将文本分割成多个、或者未启用整体翻译,出现分割后的文本数组为
2832								['你是谁','你','世界']
2833							这时, '你' 这个字发现有本地缓存,被触发立即替换为 you ,替换完成后,会导致将 '你是谁' 也被替换了,node 当前的文本变成了
2834								you 是谁?you 好世界
2835							
2836						*/
2837						//console.log('继续扫描 + 1 - '+twoScanNodes.length);
2838						var twoScanIndex = -1; //当前元素是否在 twoScan 中已经加入了,如果已经加入了,那么这里赋予当前所在的下标
2839						for(var i = 0; i<twoScanNodes[lang].length; i++){
2840							if(translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]['node'].isSameNode(twoScanNodes[lang][i]['node'])){
2841							//if(translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]['node'].isSameNode(cacheScanNodes[i]['node'])){
2842								//如果已经加入过了,那么跳过
2843								twoScanIndex = i;
2844								break;
2845							}
2846						}
2847						var twoScanIndex_cache = -1; //当前元素是否在 twoScan 中已经加入了,如果已经加入了,那么这里赋予当前所在的下标
2848						for(var i = 0; i<cacheScanNodes.length; i++){
2849							//if(translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]['node'].isSameNode(twoScanNodes[lang][i]['node'])){
2850							if(translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]['node'].isSameNode(cacheScanNodes[i]['node'])){
2851								//如果已经加入过了,那么跳过
2852								twoScanIndex_cache = i;
2853								break;
2854							}
2855						}
2856
2857						if(twoScanIndex == -1){
2858							//console.log(translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]['node']);
2859							twoScanIndex = twoScanNodes[lang].length;
2860							twoScanNodes[lang][twoScanIndex] = {};
2861							twoScanNodes[lang][twoScanIndex]['node'] = translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]['node'];
2862							twoScanNodes[lang][twoScanIndex]['array'] = [];
2863						}
2864
2865						if(twoScanIndex_cache == -1){
2866							twoScanIndex_cache = cacheScanNodes.length;
2867							cacheScanNodes[twoScanIndex_cache] = {};
2868							cacheScanNodes[twoScanIndex_cache]['node'] = translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]['node'];
2869							cacheScanNodes[twoScanIndex_cache]['array'] = [];
2870						}
2871
2872						//未加入过,那么加入
2873						var arrayIndex = twoScanNodes[lang][twoScanIndex]['array'].length;
2874						twoScanNodes[lang][twoScanIndex]['array'][arrayIndex] = translateResultText;
2875						
2876						var arrayIndex_cache = cacheScanNodes[twoScanIndex_cache]['array'].length;
2877						cacheScanNodes[twoScanIndex_cache]['array'][arrayIndex_cache] = translateResultText;
2878						
2879					}
2880					
2881
2882
2883						
2884					continue;	//跳出,不用在传入下面的翻译接口了
2885				}
2886				
2887				/*
2888				//取出数组
2889				var queueNodes = this.nodeQueue[lang][hash];
2890				if(queueNodes.length > 0){
2891					//因为在这个数组中的值都是一样的,那么只需要取出第一个就行了
2892					var valueStr = queueNodes[0].nodeValue;
2893					valueStr = this.util.charReplace(valueStr);
2894
2895					translateTextArray[lang].push(valueStr);
2896					translateHashArray[lang].push(hash);
2897				}
2898				*/
2899				
2900				//加入待翻译数组
2901				translateTextArray[lang].push(translateText);
2902				for(var ni = 0; ni<translate.nodeQueue[uuid]['list'][lang][hash].nodes.length; ni++){
2903					translateTextNodeMap.get(lang).set(translate.nodeQueue[uuid]['list'][lang][hash].nodes[ni].node, 1)
2904				}
2905				translateHashArray[lang].push(hash); //这里存入的依旧还是用原始hash,未使用自定义术语库前的hash,目的是不破å
2905 nodeQueue 的 key
2906			}
2907
2908			task.execute(); //执行渲染任务
2909		}
2910
2911		translate.time.log('对扫描到的元素进行浏览器本地缓存命中-完毕');
2912		//console.log(twoScanNodes);
2913		//console.log('cacheScanNodes:');
2914		//console.log(cacheScanNodes);
2915		//console.log(translateTextArray);
2916		//return;
2917
2918		if(typeof(translate.request.api.translate) != 'string' || translate.request.api.translate == null || translate.request.api.translate.length < 1){
2919			//用户已经设置了不掉翻译接口进行翻译
2920			translate.state = 0;
2921			
2922			//生命周期触发事件
2923			translate.lifecycle.execute.renderFinish_Trigger(uuid, translate.to);
2924			translate.executeNumber++;
2925
2926			//钩子
2927			translate.lifecycle.execute.finally_Trigger({
2928			    uuid:uuid,
2929			    to:translate.to,
2930			    state: 16,
2931			    triggerNumber: triggerNumber
2932			});
2933
2934			return;
2935		}
2936
2937
2938
2939		/******* 进行第二次扫描、追加入翻译队列。目的是防止缓存打散扫描的待翻译文本 ********/
2940		/*
2941		for(var lang in twoScanNodes){
2942			if (!twoScanNodes.hasOwnProperty(lang)) {
2943	    		continue;
2944	    	}
2945
2946			//记录第一次扫描的数据,以便跟第二次扫描后的进行对比
2947			var firstScan = Object.keys(translate.nodeQueue[uuid]['list'][lang]);
2948			var firstScan_lang_langth = firstScan.length; //第一次扫描后的数组长度
2949
2950			//console.log(twoScanNodes[lang]);
2951			for(var i = 0; i<twoScanNodes[lang].length; i++){
2952				
2953				//找到这个node元素命中缓存后的翻译记录
2954				for(var ci = 0; ci<cacheScanNodes.length; ci++){
2955					if(twoScanNodes[lang][i].node.isSameNode(cacheScanNodes[ci]['node'])){
2956						//如果发现,那么赋予
2957						twoScanNodes[lang][i].array = cacheScanNodes[ci].array;
2958						break;
2959					}
2960				}
2961
2962				twoScanNodes[lang][i].array.sort(function(a, b) { return b.length - a.length; });
2963				//console.log(twoScanNodes[lang][i].array);
2964
2965				var nodeAnaly = translate.element.nodeAnalyse.get(twoScanNodes[lang][i].node);
2966				//console.log(nodeAnaly);
2967				var text = nodeAnaly.text;
2968				//console.log(text.indexOf(twoScanNodes[lang][i].array[0]));
2969
2970				for(var ai = 0; ai < twoScanNodes[lang][i].array.length; ai++){
2971					if(twoScanNodes[lang][i].array[ai] < 1){
2972						continue;
2973					}
2974					text = text.replace(new RegExp(translate.util.regExp.pattern(twoScanNodes[lang][i].array[ai]),'g'), translate.util.regExp.resultText('\n'));
2975				}
2976				
2977				//console.log(text);
2978				var textArray = text.split('\n');
2979				//console.log(textArray);
2980				for(var tai = 0; tai < textArray.length; tai++){
2981					if(textArray[tai] < 1){
2982						continue;
2983					}
2984					//console.log(textArray[tai]);
2985					//将新增的追加到 translate.nodeQueue 中
2986					translate.addNodeToQueue(uuid, nodeAnaly['node'], textArray[tai]);
2987				}
2988			}
2989
2990
2991			//取第二次扫描追加后的数据
2992			var twoScan = Object.keys(translate.nodeQueue[uuid]['list'][lang]);
2993			var twoScan_lang_langth = twoScan.length; //第二次扫描后的数组长度
2994			//console.log(firstScan_lang_langth+ '=='+twoScan_lang_langth);
2995			if(firstScan_lang_langth - twoScan_lang_langth == 0){
2996				//一致,没有新增,那么直接跳出,忽略
2997				continue;
2998			}
2999
3000			//console.log(translate.nodeQueue[uuid]['list'][lang]);
3001			//console.log(firstScan);
3002			for(var ti=0; ti<twoScan.length; ti++){
3003				var twoHash = twoScan[ti];
3004				//console.log(twoHash + '-- '+firstScan.indexOf(twoHash));
3005				if(firstScan.indexOf(twoHash) == -1){
3006					//需要追加了
3007					var item = translate.nodeQueue[uuid]['list'][lang][twoHash];
3008
3009					var cacheHash = item.original == item.translateText ? twoHash:translate.util.hash(item.translateText); //如果匹配到了自定义术语库,那翻译前的hash是被改变了
3010					translate.nodeQueue[uuid]['list'][lang][twoHash]['cacheHash'] = cacheHash; //缓存的hash。 缓存时,其hash跟翻译的语言是完全对应的,缓存的hash就是翻译的语言转换来的
3011					
3012					translateTextArray[lang].push(item.translateText);
3013					translateHashArray[lang].push(twoHash);
3014				}
3015			}
3016			
3017		}
3018		translate.time.log('对未命中本地缓存的元素进行第二轮扫描-完毕');
3019		*/
3020		/******* 进行第二次扫描、追加入翻译队列  -- 结束 ********/
3021
3022		
3023		//window.translateHashArray = translateHashArray;
3024		
3025		//统计出要翻译哪些语种 ,这里面的语种会调用接口进行翻译。其内格式如 english
3026		var fanyiLangs = []; 
3027		//console.log(translateTextArray)
3028		for(var lang in translate.nodeQueue[uuid]['list']){ //二维数组中取语言
3029			if (!translate.nodeQueue[uuid]['list'].hasOwnProperty(lang)) {
3030	    		continue;
3031	    	}
3032	    	
3033			if(typeof(translateTextArray[lang]) == 'undefined'){
3034				continue;
3035			}
3036			if(translateTextArray[lang].length < 1){
3037				continue;
3038			}
3039
3040			//如果当前语种就是需要显示的语种(也就是如果要切换的语种),那么也不会进行翻译,直接忽略
3041			if(lang == translate.to){
3042				continue;
3043			}
3044			fanyiLangs.push(lang);
3045		}
3046		
3047
3048		/******* 用以记录当前是否进行完第一次翻译了 *******/
3049		/*
3050		if(!translate.listener.isExecuteFinish){
3051			translate.temp_executeFinishNumber = 0;	//下面请求接口渲染,翻译执行完成的次数	
3052			//判断是否是执行完一次了
3053	        translate.temp_executeFinishInterval = setInterval(function(){
3054				if(translate.temp_executeFinishNumber == fanyiLangs.length){
3055					translate.listener.isExecuteFinish = true; //记录当前已执行完第一次了
3056					clearInterval(translate.temp_executeFinishInterval);//停止
3057					console.log('translate.execute() Finish!');
3058					//console.log(uuid);
3059					
3060				}
3061	        }, 50);
3062		}
3063		*/
3064
3065		//console.log(translate.nodeQueue[uuid]['list'])
3066		if(fanyiLangs.length == 0){
3067			//没有需要翻译的,直接退出
3068
3069			//生命周期触发事件
3070			translate.lifecycle.execute.renderFinish_Trigger(uuid, translate.to);
3071
3072			translate.state = 0;
3073			translate.executeNumber++;
3074
3075			//钩子
3076			translate.lifecycle.execute.finally_Trigger({
3077			    uuid:uuid,
3078			    to:translate.to,
3079			    state: 18,
3080			    triggerNumber: triggerNumber
3081			});
3082
3083			return;
3084		}
3085		
3086		
3087		//状态
3088		translate.state = 20;
3089
3090		translate.time.log('调用翻译接口进行翻译 - 开始');
3091
3092		/* 
3093			将翻译请求的信息记录到 translate.js 本身中
3094			uuid 每次 translate.execute() 触发生成的uuid
3095				time: 触发后加入到 data 中的时间,13位时间戳
3096				list: 记录当前uuid下发起的网络请求
3097					from: 从什么语种进行的翻译,如: chinese_simplified 
3098						to: 翻译为什么语种,如 : english
3099							nodes: 当前网络请求有哪些node节点,值为 [node1, node2, ...]
3100							texts: 当前网络请求有哪些文本进行翻译,值为 [text1, text2, ...]
3101
3102		*/
3103		translate.request.data[uuid] = {
3104			time:Date.now(),
3105			list:{}
3106		};
3107
3108
3109		//进行掉接口翻译
3110		for(var lang_index in fanyiLangs){ //一维数组,取语言
3111			if (!fanyiLangs.hasOwnProperty(lang_index)) {
3112	    		continue;
3113	    	}
3114			var lang = fanyiLangs[lang_index];
3115			if(typeof(lang) != 'string'){
3116				continue;
3117			}
3118			
3119			if(typeof(translateTextArray[lang]) == 'undefined' || translateTextArray[lang].length < 1){
3120				translate.log('异常,理论上不应该存在, lang:'+lang+', translateTextArray:');
3121				translate.log(translateTextArray);
3122				translate.log('你无需担心,这个只是个提示,它并不影响你翻译的正常进行,只是个异常提示而已,它会自动容错处理的,不会影响翻译的使用。');
3123
3124				translate.state = 0;
3125				translate.executeNumber++;
3126
3127				//钩子
3128				translate.lifecycle.execute.finally_Trigger({
3129				    uuid:uuid,
3130				    to:translate.to,
3131				    state: 21,
3132				    triggerNumber: triggerNumber
3133				});
3134
3135				return;
3136			}
3137
3138			//自定义术语
3139			/*var nomenclatureCache = translate.nomenclature.dispose(cache);
3140			for(var ttr_index = 0; ttr_index<translateTextArray[lang].length; ttr_index++){
3141				console.log(translateTextArray[lang][ttr_index])
3142			}*/
3143
3144			//将需要请求翻译接口的加入到 translate.translateRequest 中
3145			if(typeof(translate.translateRequest[uuid]) == 'undefined' || translate.translateRequest[uuid] == null){
3146				translate.translateRequest[uuid] = {};
3147			}
3148			translate.translateRequest[uuid][lang] = {};
3149			translate.translateRequest[uuid][lang].executeFinish = 0;
3149 //是否执行完毕,0是执行中, 1是执行完毕(不管是失败还是成功) 而且执行完毕是指ajax请求获得响应,并且dom渲染完成之后才算完毕。当然如果ajax接口失败那也是直接算完毕
3150			translate.translateRequest[uuid][lang].addtime = Math.floor(Date.now() / 1000);
3151
3152
3153			//listener
3154			translate.listener.execute.renderStartByApiRun(uuid, lang, translate.to); 
3155			
3156			//console.log(translateTextArray[lang]);
3157			var translateTextNodes = [];
3158			for (let key of translateTextNodeMap.get(lang).keys()) {
3159   				translateTextNodes.push(key);
3160			}
3161			//console.log(translateTextNodes)
3162			translate.lifecycle.execute.translateNetworkBefore_Trigger({
3163				uuid: uuid,
3164				lang: lang,
3165				to: translate.to,
3166				texts: translateTextArray[lang],
3167				nodes: translateTextNodes
3168			}); 
3169
3170			//记入请求日志
3171			if(typeof(translate.request.data[uuid].list[lang]) === 'undefined'){
3172				translate.request.data[uuid].list[lang] = {};
3173			}
3174			translate.request.data[uuid].list[lang][translate.to] = {
3175				texts: translateTextArray[lang],
3176				nodes: translateTextNodes,
3177			};
3178
3179			
3180			/*** 翻译开始 ***/
3181			var url = translate.request.api.translate;
3182			var data = {
3183				from:lang,
3184				to:translate.to,
3185				//lowercase:translate.whole.isEnableAll? '0':'1', //首字母大写
3186				//text:JSON.stringify(translateTextArray[lang])
3187				text:encodeURIComponent(JSON.stringify(translateTextArray[lang]))
3188			};
3189			translate.request.post(url, data, function(responseData, requestData){
3190				//console.log(data); 
3191				//console.log(translateTextArray[data.from]);
3192
3193				//针对 giteeai 增加了账户余额、账户是否激活的拍的判定,所以增加了 401 这个参数,凡是账户异常的,参数值是 401~499 之间。所以只要不是1都是失败
3194				if(responseData.result != 1){
3195					if(typeof(translate.translateRequest[uuid]) == 'object' && typeof(translate.translateRequest[uuid][requestData.from]) == 'object'){
3196						translate.translateRequest[uuid][requestData.from]['result'] = 2;
3197						translate.translateRequest[uuid][requestData.from].executeFinish = 1; //1是执行完毕
3198						translate.translateRequest[uuid][requestData.from].stoptime = Math.floor(Date.now() / 1000);
3199					}else{
3200						translate.log('WARINNG!!! translate.translateRequest[uuid][requestData.from] is not object');
3201					}
3202
3203					//为了兼容 v3.14以前的translate.service 版本,做了判断
3204					var from = '';
3205					if(typeof(requestData.from) != 'undefined' && requestData.from != null){
3206						from = requestData.from;
3207					}
3208					var to = '';
3209					if(typeof(requestData.to) != 'undefined' && requestData.to != null){
3210						to = requestData.to;
3211					}else{
3212						to = translate.to;
3213					}
3214					translate.waitingExecute.isAllExecuteFinish(uuid, from, to, 0, responseData.info);
3215					
3216
3217					translate.log('=======ERROR START=======');
3218					translate.log(translateTextArray[requestData.from]);
3219					//console.log(encodeURIComponent(JSON.stringify(translateTextArray[data.from])));
3220					translate.log('response : '+responseData.info);
3221					translate.log('=======ERROR END  =======');
3222					//translate.temp_executeFinishNumber++; //记录执行完的次数
3223					return;
3224				}
3225				
3226				if(typeof(translate.nodeQueue[uuid]) == 'undefined'){
3227					translate.log('提示:你很可能多次引入了 translate.js 所以造成了翻译本身的数据错乱,这只是个提示,它还是会给你正常翻译的,但是你最好不要重复引入太多次 translate.js ,正常情况下只需要引入一次 translate.js 就可以了。太多的话很可能会导致你页面卡顿');
3228					return;
3229				}
3230
3231				//console.log('-----待翻译3:----');
3232				//console.log(translate.nodeQueue);
3233				
3234				//console.log('response:'+uuid);
3235				let task = new translate.renderTask();
3236				//遍历 translateHashArray
3237				for(var i=0; i<translateHashArray[responseData.from].length; i++){
3238					//翻译前的语种,如 english
3239					var lang = responseData.from;	
3240					//翻译后的内容
3241					var text = responseData.text[i];	
3242					//如果text为null,那么这个可能是一次翻译字数太多,为了保持数组长度,拼上的null
3243					if(text == null){
3244						continue;
3245					}
3246
3247					// v3.0.3 添加,避免像是 JavaScript 被错误翻译为 “JavaScript的” ,然后出现了多个句子中都出现了Javascript时,会出现翻译后文本重复的问题
3248					// 这里就是验证一下,翻译后的文本,是否会完全包含翻以前的文本,如果包含了,那么强制将翻译后的文本赋予翻译前的原始文本(也就是不被翻译)
3249					if(text.toLowerCase().indexOf(translateTextArray[responseData.from][i].toLowerCase()) > -1){
3250						//发现了,那么强制赋予翻以前内容
3251						text = translateTextArray[responseData.from][i];
3252					}
3253
3254
3255					//翻译前的hash对应下标
3256					var hash = translateHashArray[responseData.from][i];	
3257					var cacheHash = translate.nodeQueue[uuid]['list'][lang][hash]['cacheHash'];
3258
3259
3260					
3261					//取原始的词,还未经过翻译的,需要进行翻译的词
3262					var originalWord = '';
3263					try{
3264						originalWord = translate.nodeQueue[uuid]['list'][lang][hash]['original'];
3265						//console.log('bef:'+translate.nodeQueue[uuid]['list'][lang][hash]['beforeText']);
3266					}catch(e){
3267						translate.log('uuid:'+uuid+', originalWord:'+originalWord+', lang:'+lang+', hash:'+hash+', text:'+text+', queue:'+translate.nodeQueue[uuid]);
3268						translate.log(e);
3269						continue;
3270					}
3271					
3272					//for(var index = 0; index < translate.nodeQueue[lang][hash].length; index++){
3273					for(var node_index = 0; node_index < translate.nodeQueue[uuid]['list'][lang][hash]['nodes'].length; node_index++){
3274						//translate.nodeQueue[lang][hash]['nodes'][node_index].nodeValue = translate.nodeQueue[lang][hash]['nodes'][node_index].nodeValue.replace(new RegExp(originalWord,'g'), text);
3275						//加入任务
3276						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']);
3277					}
3278					//}
3279					/*
3280					for(var index = 0; index < translate.nodeQueue[data.from][hash].length; index++){
3281						translate.nodeQueue[data.from][hash][index].nodeValue = text;
3282					}
3283					*/
3284					
3285					//将翻译结果以 key:hash  value翻译结果的形式缓存
3286					translate.storage.set('hash_'+responseData.to+'_'+cacheHash,text);
3287					//如果离线翻译启用了全部提取,那么还要存入离ç
3287º¿ç¿»è¯‘指定存储
3288					if(translate.offline.fullExtract.isUse){
3289						translate.offline.fullExtract.set(hash, originalWord, responseData.to, text);
3290					}
3291				}
3292				task.execute(); //执行渲染任务
3293				//translate.temp_executeFinishNumber++; //记录执行完的次数
3294
3295				translate.translateRequest[uuid][lang].result = 1;
3296				translate.translateRequest[uuid][lang].executeFinish = 1; //1是执行完毕
3297				translate.translateRequest[uuid][lang].stoptime = Math.floor(Date.now() / 1000);
3298				setTimeout(function(){
3299					translate.waitingExecute.isAllExecuteFinish(uuid, responseData.from, responseData.to, 1, '');
3300				},5);
3301			}, function(xhr){
3302				translate.translateRequest[uuid][xhr.data.from].executeFinish = 1; //1是执行完毕
3303				translate.translateRequest[uuid][xhr.data.from].stoptime = Math.floor(Date.now() / 1000);
3304				translate.translateRequest[uuid][xhr.data.from].result = 3;
3305				var info = '';
3306				if(typeof(xhr.status) != 'undefined'){
3307					if(xhr.status < 1){
3308						info = 'Network connection failed. url: '+xhr.requestURL;
3309					}else{
3310						info = 'HTTP response code : '+xhr.status+', url: '+xhr.requestURL;
3311					}
3312				}else{
3313					info = 'Network connection failed. url: '+xhr.requestURL;
3314				}
3315				translate.waitingExecute.isAllExecuteFinish(uuid, xhr.data.from, translate.to, 0, info);
3316			});
3317			/*** 翻译end ***/
3318		}
3319
3320		//钩子
3321		translate.lifecycle.execute.finally_Trigger({
3322		    uuid:uuid,
3323		    to:translate.to,
3324		    state: 25,
3325		    triggerNumber: triggerNumber
3326		});
3327	},
3328	/*translate.execute() end */
3329
3330	/**
3331	 * 翻译请求记录
3332	 * 一维:key:uuid,也就是execute每次执行都会创建一个翻译队列,这个是翻译队列的唯一标识。  这个uuid跟 nodeQueue 的uuid是一样的
3333	 * 		value:对象
3334	 * 二维: 对象,包含:
3335	 * 		from 存放的是要翻译的源语种,比如要讲简体中文翻译为英文,这里存放的就是 chinese_simplified
3336	 * 		state 是否执行完毕,0是执行中, 1是执行完毕(不管是失败还是成功) 而且执行完毕是指ajax请求获得响应,并且dom渲染完成之后才算完毕。当然如果ajax接口失败那也是直接算完毕
3337	 * 		addtime 这条数据加入到本数组的时间,也就是进行ajax请求开始那一刻的时间,10位时间戳
3338	 * 		stoptime 执行完毕的时间,也就是state转为2那一刻的时间
3339	 * 		result 执行结果, 0 是还没执行完,等待执行完, > 0 是执行完了有结果了,  
3340	 * 												  1 是执行成功
3341	 * 												  2 是接口有响应,也是200响应,但是接口响应的结果返回了错误,也就是返回了 {result:0, info:'...'}
3342	 * 												  3 是接口不是200响应码
3343	 * 
3344	 */
3345	translateRequest:{
3346		/* 
3347		uuid:[
3348			'chinese_simplified':{
3349				executeFinish:0,
3350				addtime:150001111,
3351				stoptime:150001111,
3352				result:0
3353			},
3354			...
3355		] 
3356		*/
3357	},
3358
3359	//20250908 废弃,要删除这个
3360	nodeHistory:null,
3361
3362	//当前页面中,翻译后操作的 ,dom 中有效的node节点
3363	node:{
3364		/*
3365			将已扫描的节点进行记录,这里是只要进行扫描到了,也就是在加入 translate.nodeQueue 时就也要加入到这里。
3366			这是一个map,为了兼容es5,这里设置为null,在 translate.execute 中在进行初始化
3367
3368			key: node ,进行翻译的文本的node, 如果是 div 的 title属性进行的翻译,那这个node是定位在 title 上的node,而不是 div 这个依附的元素
3369					注意,如果是对 input、textarea 的value进行翻译,而 value是通过js赋予的,那么这个value属性的值并不是一个单独的node,所以是为空的,此时要记录的node便是  input、textarea 这个node本身。
3370			value: 这是一个对像
3371				其中,key的取值有这几种:
3372				translate_default_value: 如果当前翻译的是元素本身的值或node节点本身的值(nodeValue),那么这里的key就是固定的 translate_default_value
3373				attribute_属性名: 如果当前翻译的是元素的attribute 的某个属性,那么这里就是 attribute_属性名, 比如 a 标签的 title ,那这里便是 attribute_title
3374				modified: 被改动的动作,比如创建、值发生改动,都会记录到这里,它没有什么实际作用,仅仅只是为了方便开发调试使用。 
3375						这是一个数组格式,其值如:
3376						[
3377							'create:translate.faultTolerance.documentCreateTextNode.enable',
3378							'update:translate.execute'
3379						]
3380						创建便是 create 开头,数据修改(追加属性、属性值修改、删除属性等)便是 update 开头,后面跟着的是操作它是在哪个方法里
3381						越早操作,下标越小,也就是 [0] 是最开始创建的,然后每次修改都会push进一个数据进去
3382
3383				lastTranslateRenderTime: 记录当前有 translate.js 所触发翻译之后渲染到dom界面显示的时间,13位时间戳。
3384										 每当触发渲染时这里都会重新赋予一次最新的时间,这里也就是最后一次渲染的时间。 如果还没渲染那这里便是 undefined 或者 null,总之 typeof 不是 number
3385										 另外这个时间是渲染的前一刻赋予的,赋予后立即进行的DOM渲染
3386				
3387				translateResults: array string 文本数组,这里是被 translate.element.nodeAnalyse.set 进行翻译渲染之后,每次针对node进行一次渲染,它都会讲渲染的文本(注意是翻译之后的文本,而不是原文)设置进来,不管是
3387node本身还是属性还是什么,都会直接讲其具体结果拿过来。
3388						注意,翻译完毕进行渲染时,是先将要显示的文本(翻译后的文本)拿来赋予到这里,然后在执行 dom渲染(触发listener)  
3389						当listener动态监听时,也是根据这个来判定当前是否是有 translate.js 本身导致的node发生了改变
3390						{
3391							你好,世界:1
3392							你是谁:1
3393						}
3394						它使用是 typeof(translate.node.get(node).translateResults['你好世界']) === 'number' 这样使用,至于后面的value为1那纯属是凑的,没任何意义						 
3395				
3396				
3397				attribute 这个翻译的node对象是否是翻译的其中的某个attribute属性,如果是,那么这里便是长度大于0, 如果是元素或节点本身(nodeValue),那么这里就是空字符串,注意,是空字符串 ''
3398						另外这个字段,当前应该仅仅只是针对 input、textarea 的 value 属性有用,也就是它的值要么是空字符串,要么是 'value', 因为像是 input value 的属性是不属于dom的,必须 input.value 这样才能点出来
3399
3400				resultText: string 翻译完成后,当前node节点的内容文本,注意,是node节点整体所有的内容文本(是已经翻译渲染过的)
3401									注意,翻译失败或者本身是特殊字符比如数字,不需要被翻译,是没有这个属性的
3402				originalText: string 翻译前显示的文本,是node节点所有的内容文本,原始的文本,(当前这里仅仅只对元素整体翻译时才会记录这个 - v3.18.14.20250903 增加)	
3403				
3404				translateTexts: array string 文本数组,这里是被文本翻译接口所翻译的文本。 
3405						比如其中某项为 '你好':'hello' ,其中key是翻译前的, value是翻译后的结果, 如果 value 为 null,则代表还未进行翻译拿到翻译结果
3406				
3407				whole: boolean 当前是否是整体进行翻译的,比如当前即使是设置的整体翻译,但是这个node命中了自定义术语,被术语分割了,那当前翻译也不是整体翻译的。 
3408						这个属性在扫描完节点,进行请求翻译接口或命中本地缓存之前,就要被设置。  
3409						true:是节点内容整体翻译
3410					
3411		*/
3412		data: null,
3413		/*
3414			从 translate.node.data 中,根据key,进行获取 translate.node.data.get(node)
3415		 */ 
3416		get:function(node){
3417			return translate.node.data.get(node);
3418		},
3419		//判断某个node (key) 是否在 translate.node.data 中是否存在,如果存在返回true
3420		find:function(node){
3421			return translate.node.get(node) != null;
3422		},
3423		set:function(node, value){
3424			translate.node.data.set(node,value);
3425		},
3426		/*
3427			向 translate.node 的元素中,追加属性 modified 的数组内容
3428		*/
3429		setModified:function(node, text){
3430			if(typeof(translate.node.data.get(node)) === 'undefined' || translate.node.data.get(node) === null){
3431				translate.log('translate.node.setModified exception: node not find in translate.node,  node:');
3432				translate.log(node);
3433				return;
3434			}
3435
3436			if(typeof(translate.node.data.get(node).modified) === 'undefined'){
3437				translate.node.data.get(node).modified = [];
3438			}
3439			translate.node.data.get(node).modified.push(text);
3440		},
3441		//从 translate.node.data 中 删除 key 是 node 的
3442		delete: function(node){
3443			//console.log('delete node -- > '+node.nodeValue);
3444			translate.node.data.delete(node);
3445		},
3446		/*
3447			获取 translate.node.get(node)[attribute] 这里的 attribute
3448			
3449			attribute 传入的可以是 undefined、null、'' 、 以及具体的字符串
3450
3451			返回的是一个对象:
3452			{
3453				key: translate.node 中 translate.node.get(node)[attribute] 所使用的 attribute 的字符串,如 attribute_title 、translate_default_value
3454				attribute: 这里是attribute具体的内容,比如 key 是 attribute_title 那么这里就是 title , key 是 translate_default_value 这里就是 '' 空字符串
3455			}
3456		 
3457		getAttribute:function(attribute){
3458			var history_attribute;
3459			if(typeof(attribute) != 'undefined' && attribute.length > 0){
3460				//是对 attribute 进行的操作
3461				history_attribute = 'attribute_'+attribute
3462			}else{
3463				//是对节点本身进行的操作,操作的是 nodeValue
3464				attribute = '';
3465				history_attribute = 'translate_default_value';
3466			}
3467			return {
3468				key:history_attribute,
3469				attribute:attribute
3470			}
3471		},
3472		*/
3473		/*
3474			刷新 translate.node.data 中的数据,剔除过时的(node已经不存在于dom的)
3475		*/
3476		refresh: function(){
3477
3478			// 收集要删除的无效节点
3479			const deleteKeys = new Array();
3480
3481			for (let key of translate.node.data.keys()) {
3482				// 检查节点是否还在DOM中
3483			    let isValidNode = false;
3484			    
3485			    if (key.nodeType === Node.ELEMENT_NODE) {
3486			        // 元素节点
3487			        isValidNode = key.isConnected;
3488			    } else if (key.nodeType === Node.ATTRIBUTE_NODE) {
3489			        // 属性节点(如placeholder)
3490			        isValidNode = key.ownerElement && key.ownerElement.isConnected;
3491			    } else if (key.nodeType === Node.TEXT_NODE) {
3492			        // 文本节点
3493			        isValidNode = key.isConnected;
3494			    }
3495			    
3496			    if (!isValidNode) {
3497			        //console.log('节点已经不存在,剔除节点');
3498			        deleteKeys.push(key);
3499			    }
3500			    
3501			    // 处理有效节点...
3502				//if(!key.isConnected){  text node 没有 isConnected
3503				//	console.log(key.nodeValue+' 这个translate.node 中的 node不存在,忽略');
3504				//	continue;
3505				//}
3506			}
3507
3508			// 统一删除无效节点
3509			for (var i = 0; i < deleteKeys.length; i++) {
3510			    translate.node.delete(deleteKeys[i]);
3511			}
3512
3513		}
3514	},
3515
3516	element:{
3517
3518		/*
3519			注意,里面全部的必须小写。
3520			第一个是tag,第二个是tag的属性。比如要翻译 input 的 value 属性,那么如下:
3521				translate.element.tagAttribute['input']=['value'];
3522			比如要翻译 input 的 value 、 data-value 这两个属性,那么如下:
3523				translate.element.tagAttribute['input']=['value','data-value'];
3524			有几个要翻译的属性,就写上几个。
3525			同样,有几个要额外翻译的tag,就加上几行。  
3526			详细文档参考:  http://translate.zvo.cn/231504.html
3527
3528	
3529			//针对宁德时代提出的需求,需要对 标签本身进行一个判定,是否符合条件符合条件才会翻译,不符合条件则不要进行翻译
3530			//比如标签带有 disabled 的才会被翻译,所以要增加一个自定义入参的 function ,返回 true、false
3531			translate.element.tagAttribute['input']={
3532				//要被翻译的tag的属性,这里是要翻译 input 的 value 、 data-value 这两个属性。 
3533				//数组格式,可以一个或多个属性
3534				attribute:['value','data-value'],  
3535				//条件,传入一个function,返回一个布尔值。
3536				//只有当返回的布尔值是true时,才会对上面设置的 attribute 进行翻译,否则并不会对当前设定标签的 attribute 进行任何翻译操作。
3537				condition:function(element){ 	   
3538					//	element 便是当前的元素,
3539					//	比如这里是 translate.element.tagAttribute['input']  那这个 element 参数便是扫描到的具体的 input 元素
3540					//	可以针对 element 这个当前元素本身来进行判定,来决定是否进行翻译。
3541					//	返回值是布尔值 true、false
3542					//	    return true; //要对 attribute中设置的 ['value','data-value'] 这两个input 的属性的值进行翻译。 
3543					//	                     如果不设置或传入 condition ,比如单纯这样设置: 
3544					//	                     translate.element.tagAttribute['input']={ 
3545					//	                         attribute:['value','data-value'] 
3546					//	                     } 
3547					//	                     那么这里默认就是 return true;
3548					//	    return false; //不对 attribute中设置的 ['value','data-value'] 这两个input 的属性的值进行任何操作
3549					return true;
3550				}
3551			};
3552
3553		*/
3554		tagAttribute : {},
3555
3556		//对翻译前后的node元素的分析(翻以前)及渲染(翻译后)
3557		nodeAnalyse:{
3558			/*
3559				获取node中的要进行翻译的文本内容、以及要操作的实际node对象(这个node对象很可能是传入的node中的某个子node)
3560				node 
3561				attribute 要获取的是某个属性的值,还是node本身的值。比如 a标签的title属性的值,则传入 title。  如果是直接获取node.nodeValue ,那这个没有
3562
3563				返回结果是一个数组。其中:
3564					['text']:要进行翻译的text内容文本
3565					['node']:要进行翻译的目标node
3566
3567			*/
3568			get:function(node, attribute){
3569				return translate.element.nodeAnalyse.analyse(node,'','', attribute);
3570			},
3571			/*
3572				同上,只不过这个是扫描 element/node 下的所有可翻译的子节点(下层节点),返回数组形态。
3573				这里面的数组,已经经过判断, text 必然是有不为空的值的。
3574				所以它的返回值,有可能是一个空的数组
3575
3576				[
3577					{
3578						node: 当前扫描出的node (传入的node、或下层node)
3579						attribute: 是否是下层属性,比如 alt、placeholder , 如果是传入的node本身,不是任何下层属性,则这里是空白字符串 ''
3580						text: 可进行翻译的文本,也就是当前数组中 node 的值的文本
3581					},
3582					...
3583				]
3584			*/
3585			gets:function(node){
3586				var resultArray = [];
3587
3588				var nodename = translate.element.getNodeName(node).toUpperCase();
3589				switch (nodename) {
3590				  case 'META': 	//meta标签,如是关键词、描述等
3591				    var nodeAttributeName = node.name.toLowerCase();  //取meta 标签的name 属性
3592					var nodeAttributePropertyOri = node.getAttribute('property'); //取 property的值
3593					var nodeAttributeProperty = '';
3594					if(typeof(nodeAttributePropertyOri) === 'string' && nodeAttributePropertyOri.length > 0){
3595						nodeAttributeProperty = nodeAttributePropertyOri.toLowerCase();
3596					}
3597
3598					if(nodeAttributeName == 'keywords' || nodeAttributeName == 'description' || nodeAttributeName == 'sharetitle' || nodeAttributeProperty == 'og:title' || nodeAttributeProperty == 'og:description' || nodeAttributeProperty == 'og:site_name' || nodeAttributeProperty == 'og:novel:latest_chapter_name'){
3599						if(typeof(node.content) === 'string' && node.content.trim().length > 0){
3600							resultArray.push({
3601								text: node.content,
3602								attribute: 'content',
3603								node: node.getAttributeNode('content')
3604							});
3605						}
3606					}
3607				    break;
3608				  case 'IMG':
3609				    if(typeof(node.alt) === 'string' && node.alt.trim().length > 0){
3610						resultArray.push({
3611							text: node.alt,
3612							attribute: 'alt',
3613							node: node.getAttributeNode('alt')
3614						});
3615					}
3616				    break;
3617				  case 'INPUT':  
3618				  	/*
3619						input,要对以下情况进行翻译
3620							placeholder
3621							type=button、submit 的情况下的 value 
3622				  	*/
3623
3624				  	//针对 type=button、submit 的情况下的 value 
3625				  	if(typeof(node.attributes.type) !== 'undefined' && node.attributes.type !== null &&  typeof(node.attributes.type.nodeValue) === 'string' && (node.attributes.type.nodeValue.toLowerCase() == 'button' || node.attributes.type.nodeValue.toLowerCase() == 'submit')){
3626						//取它的value
3627						var input_value_node = node.attributes.value;
3628						if(typeof(input_value_node) !== 'undefined' && input_value_node !== null && typeof(input_value_node.nodeValue) === 'string' && input_value_node.nodeValue.trim().length > 0){
3629							resultArray.push({
3630								text: input_value_node.nodeValue,
3631								attribute: 'value',
3632								node: input_value_node
3633							});
3634						}
3635					}
3636
3637					//针对 placeholder
3638					if(typeof(node.attributes['placeholder']) !== 'undefined' && typeof(node.attributes['placeholder'].nodeValue) === 'string' && node.attributes['placeholder'].nodeValue.trim().length > 0){
3639						resultArray.push({
3640							text: node.attributes['placeholder'].nodeValue,
3641							attribute: 'placeholder',
3642							node: node.attributes['placeholder']
3643						});
3644					}
3645					break;
3646				  case 'TEXTAREA':	
3647				  	//针对 placeholder
3648					if(typeof(node.attributes['placeholder']) !== 'undefined' && typeof(node.attributes['placeholder'].nodeValue) === 'string' && node.attributes['placeholder'].nodeValue.trim().length > 0){
3649						resultArray.push({
3650							text: node.attributes['placeholder'].nodeValue,
3651							attribute: 'placeholder',
3652							node: node.attributes['placeholder']
3653						});
3654					}
3655					break;
3656				}
3657
3658				//判断是否是 translate.element.tagAttribute 自定义翻译属性的
3659				var divTagAttribute = translate.element.tagAttribute[nodename.toLowerCase()];
3660				if(typeof(divTagAttribute) !== 'undefined'){
3661					//有这个标签的自定义翻译某个属性
3662					for(var ai = 0; ai<node.attributes.length; ai++){
3663						var arrtibuteNodeName = translate.element.getNodeName(node.attributes[ai]).toLowerCase();
3664						if(divTagAttribute.attribute.indexOf(arrtibuteNodeName) > -1 && divTagAttribute.condition(node)){
3665							//包含这个属性,且自定义判断条件满足,允许翻译
3666							//判定一下是否已经加入过了,如果没有加入过,才会加入。这里主要是针对input 标签进行判断,比如 input type="submit" 的,value值如果也被用户自定义翻译,那上面的value就已经加上了,不需要在加了
3667							var isAlreadyAdd = false; //true已经加入过了
3668							for(var ri = 0; ri < resultArray.length; ri++){
3669								if(resultArray[ri].node === node.attributes[ai]){
3670									//相同,则不在加入了
3671									isAlreadyAdd = true;
3672								}
3673							}
3674							if(!isAlreadyAdd){
3675								resultArray.push({
3676									text: node.attributes[ai].nodeValue,
3677									attribute: arrtibuteNodeName,
3678									node: node.attributes[ai]
3679								});
3680							}
3681						}
3682					}
3683				}else{
3684					//条件不满足,不在翻译的属性范围
3685				}
3686
3687
3688				//所有元素都要判定的属性 - title 属性
3689				if(typeof(node['title']) === 'string' && node['title'].trim().length > 0){
3690					var titleNode = node.getAttributeNode('title');
3691					resultArray.push({
3692						text: titleNode.nodeValue,
3693						attribute: 'title',
3694						node: titleNode
3695					});
3696				}
3697
3698
3699				//最后判定 node 本身
3700				if(typeof(node.nodeValue) === 'string' && node.nodeValue.trim().length > 0){
3701			  		//返回传入的node本身
3702				    resultArray.push({
3703						text: node.nodeValue,
3704						attribute: '',
3705						node: node
3706					});
3707			  	}
3708
3709				
3710				return resultArray;
3711			},
3712			/*
3713				进行翻译之后的渲染显示
3714				注意,它会对node本身进行扫描的,需要进行通过文本翻译接口进行翻译的文本进行识别,比如 这个 node 其内容为:
3715					你是谁?你好世界
3716				扫描完后,触发了自定义术语将文本分割成多个、或者未启用整体翻译,出现分割后的文本数组为
3717					['你是谁','你','世界']
3718				那如果命中缓存 'ä½ ' 后,进行替换时,就不能将 '你是谁' 给替换了,不然会造成字符串无需拆分,直接纯单词翻译,没有什么语义了。另外这样也会导致漏翻译的情况。  经过这次调整,将 translate.execute() 二次扫æ
3718ç›´æŽ¥ç»™ä¼˜åŒ–掉了,提高了语义通顺、自定义术语的精准
3719				这个取值,是从 translate.node.get(node).translateTexts 中取这个要进行文本翻译的数组的。 
3720				当然,如果 translate.node.get(node).whole 为 true,本身就是整体翻译,那就没这些破事,直接替换就好了
3721
3722
3723				参数:
3724					node 当前翻译的node元素
3725						注意,如果是对 input、textarea 的value进行翻译,而 value是通过js赋予的,那么这个value属性的值并不是一个单独的node,所以是为空的,此时要记录的node便是  input、textarea 这个node本身。
3726					originalText 翻译之前的内容文本
3727					resultText 翻译之后的内容文本
3728					attribute 存放要替换的属性,比如 a标签的title属性。 如果是直接替换node.nodeValue ,那这个没有
3729				返回结果是一个数组,其中:
3730					resultText: 翻译完成之后的text内容文本,注意,如果返回的是空字符串,那么则是翻译结果进行替换时,并没有成功替换,应该是翻译的过程中,这个node的值被其他js又赋予其他内容了。
3731					node: 进行翻译的目标node	
3732
3733				注意,使用本set方法,不要用 返回的 text参数,要用 	resultText 这个参数,这个才是翻译之后的文本	
3734			*/
3735			set:function(node, originalText, resultText, attribute){
3736				return translate.element.nodeAnalyse.analyse(node,originalText,resultText, attribute);
3737			},
3738			/*	
3739				
3740				注意,这个不使用,只是服务于上面的get、set使用。具体使用用上面的get、set
3741
3742				1. 只传入 node:
3743					获取node中的要进行翻译的文本内容、以及要操作的实际node对象(这个node对象很可能是传入的node中的某个子node)
3744					返回结果是一个数组。其中:
3745						['text']:要进行翻译的text内容文本
3746						['node']:要进行翻译的目标node
3747									注意,如果是对 input、textarea 的value进行翻译,而 value是通过js赋予的,那么这个value属性的值并不是一个单独的node,所以是为空的,此时要记录的node便是  input、textarea 这个node本身。
3748				2. 传入 node、originalText、 resultText
3749					则是进行翻译之后的渲染显示
3750
3751				attribute : 进行替换渲染时使用,存放要替换的属性,比如 a标签的title属性。 如果是直接替换node.nodeValue ,那这个没有
3752				
3753				返回结果是一个数组,其中:
3754					resultText: 翻译完成之后的text内容文本。 当使用 translate.element.nodeAnalyse.set 时才会有这个参数返回。 注意,如果返回的是空字符串,那么则是翻译结果进行替换时,并没有成功替换,应该是翻译的过程中,这个node的值被其他js又赋予其他内容了。
3755					text : 要进行翻译的text内容文本,当使用 translate.element.nodeAnalyse.get 时才会有这个参数的返回
3756					node: 要进行翻译的目标node
3757							注意,如果是对 input、textarea 的value进行翻译,而 value是通过js赋予的,那么这个value属性的值并不是一个单独的node,所以是为空的,此时要记录的node便是  input、textarea 这个node本身。
3758			*/
3759			analyse:function(node, originalText, resultText, attribute){
3760				var result = new Array(); //返回的结果
3761				result['node'] = node;
3762				result['text'] = '';
3763
3764				var nodename = translate.element.getNodeName(node);
3765				//console.log('nodeAnalyse.analyse: NodeName:'+nodename+', originalText:'+originalText+', resultText:'+resultText+', attribute:'+attribute+', node:');
3766				//console.log(node)
3767				
3768				if(attribute != null && typeof(attribute) == 'string' && attribute.length > 0){
3769					//这个node有属性,替换的是node的属性,而不是nodeValue
3770
3771					var nodeAttributeValue; //这个 attribute 属性的值
3772					if((nodename === 'INPUT' || nodename === 'TEXTAREA') && attribute.toLowerCase() == 'value'){
3773						//如果是input\textarea 的value属性,那么要直接获取,而非通过 attribute ,不然用户自己输入的通过 attribute 是获取不到的 -- catl 赵阳 提出
3774						
3775						nodeAttributeValue = node.value;
3776					}else{
3777						nodeAttributeValue = node[attribute];
3778					}
3779					result['text'] = nodeAttributeValue;
3780					
3781
3782					//替换渲染
3783					if(typeof(originalText) != 'undefined' && originalText.length > 0){
3784						if(typeof(nodeAttributeValue) != 'undefined'){
3785							//这种是主流框架,像是vue、element、react 都是用这种 DOM Property 的方式,更快
3786							var resultShowText = translate.util.textReplace(nodeAttributeValue, originalText, resultText, translate.to);
3787							translate.element.nodeAnalyse.analyseReplaceBefore_DateToTranslateNode(node, attribute, resultShowText);
3788
3789							if((nodename === 'INPUT' || nodename === 'TEXTAREA') && attribute.toLowerCase() == 'value'){
3790								//input 的value 对于用户输入的必须用 .value 操作
3791								node.value = resultShowText;
3792							}else{
3793								node[attribute] = resultShowText;  //2025.4.26 变更为此方式
3794							}
3795							if(resultShowText.indexOf(resultText) > -1){
3796								result['resultText'] = resultShowText;
3797							}else{
3798								result['resultText'] = '';
3799							}
3800						}
3801
3802						/* 20250911 删除
3803						//这种 Html Attribute 方式 是 v3.12 版本之前一直使用的方式,速度上要慢于 上面的,为了向前兼容不至于升级出问题,后面可能会优化掉
3804						if(node.nodeType === 1){ //是 element 节点
3805							var htmlAttributeValue = node.getAttribute(attribute);
3806							if(htmlAttributeValue != null && typeof(htmlAttributeValue) != 'undefined'){
3807								var resultShowText = translate.util.textReplace(htmlAttributeValue, originalText, resultText, translate.to);
3808								//这个才是在v3.9.2 后要用的,上面的留着只是为了适配以前的
3809								node.setAttribute(attribute, resultShowText); 
3810								if(resultShowText.indexOf(resultText) > -1){
3811									result['resultText'] = resultShowText;
3812								}else{
3813									result['resultText'] = '';
3814								}
3815							}
3816						}
3817						*/
3818					}
3819					return result;
3820				}
3821
3822				
3823
3824				//正常的node ,typeof 都是 object
3825
3826				/* 这里是通用方法,不应该有限制
3827				//console.log(typeof(node)+node);
3828				if(nodename == '#text'){
3829					//如果是普通文本,判断一下上层是否是包含在textarea标签中
3830					if(typeof(node.parentNode) != 'undefined'){
3831						var parentNodename = translate.element.getNodeName(node.parentNode);
3832						//console.log(parentNodename)
3833						if(parentNodename == 'TEXTAREA'){
3834							//是textarea标签,那将nodename 纳入 textarea的判断中,同时将判断对象交于上级,也就是textarea标签
3835							nodename = 'TEXTAREA';
3836							node = node.parentNode;
3837						}
3838					}
3839				}
3840				*/
3841
3842
3843				//console.log(nodename)
3844				//console.log(translate.element.getNodeName(node.parentNode))
3845				//console.log(node)
3846				if(nodename == 'INPUT' || nodename == 'TEXTAREA'){
3847					//console.log(node.attributes)
3848					/*
3849						1. input、textarea 输入框,要对 placeholder 做翻译
3850						2. input 要对 type=button 的情况进行翻译
3851					*/
3852					if(node.attributes == null || typeof(node.attributes) == 'undefined'){
3853						result['text'] = '';
3854						return result;
3855					}
3856
3857					//input,要对 type=button、submit 的情况进行翻译
3858					if(nodename == 'INPUT'){
3859						if(typeof(node.attributes.type) != 'undefined' && typeof(node.attributes.type.nodeValue) != null && (node.attributes.type.nodeValue.toLowerCase() == 'button' || node.attributes.type.nodeValue.toLowerCase() == 'submit')){
3860							//console.log('----是 <input type="button"');
3861							//取它的value
3862							var input_value_node = node.attributes.value;
3863							if(input_value_node != null && typeof(input_value_node) != 'undefined' && typeof(input_value_node.nodeValue) != 'undefined' && input_value_node.nodeValue.length > 0){
3864								//替换渲染
3865								if(typeof(originalText) != 'undefined' && originalText.length > 0){
3866									var resultShowText = translate.util.textReplace(input_value_node.nodeValue, originalText, resultText, translate.to);
3867									translate.element.nodeAnalyse.analyseReplaceBefore_DateToTranslateNode(node, attribute, resultShowText);
3868
3869									input_value_node.nodeValue = resultShowText;  //2025.4.26 变更为此方式
3870									if(resultShowText.indexOf(resultText) > -1){
3871										result['resultText'] = resultShowText;
3872									}else{
3873										result['resultText'] = '';
3874									}
3875								}
3876
3877								result['text'] = input_value_node.nodeValue;
3878								result['node'] = input_value_node;
3879								return result;
3880							}
3881						}
3882					}
3883					//console.log(node)
3884
3885					//input textarea 的 placeholder 情况
3886					if(typeof(node.attributes['placeholder']) != 'undefined'){
3887						//console.log(node);
3888						//替换渲染
3889						if(typeof(originalText) != 'undefined' && originalText.length > 0){
3890							var resultShowText = translate.util.textReplace(node.attributes['placeholder'].nodeValue, originalText, resultText, translate.to);
3891							translate.element.nodeAnalyse.analyseReplaceBefore_DateToTranslateNode(node, attribute, resultShowText);
3892
3893							node.attributes['placeholder'].nodeValue = resultShowText;  //2025.4.26 变更为此方式
3894							if(resultShowText.indexOf(resultText) > -1){
3895								result['resultText'] = resultShowText;
3896							}else{
3897								result['resultText'] = '';
3898							}
3899						}
3900
3901						result['text'] = node.attributes['placeholder'].nodeValue;
3902						result['node'] = node.attributes['placeholder'];
3903						return result;
3904						//return node.attributes['placeholder'].nodeValue;
3905					}
3906					//console.log(node)
3907					result['text'] = '';
3908					return result;
3909				}
3910				if(nodename == 'META'){
3911					//meta标签,如是关键词、描述等
3912					if(typeof(node.name) != 'undefined' && node.name != null){
3913						var nodeAttributeName = node.name.toLowerCase();  //取meta 标签的name 属性
3914						var nodeAttributePropertyOri = node.getAttribute('property'); //取 property的值
3915						var nodeAttributeProperty = '';
3916						if(typeof(nodeAttributePropertyOri) != 'undefined' && nodeAttributePropertyOri != null && nodeAttributePropertyOri.length > 0){
3917							nodeAttributeProperty = nodeAttributePropertyOri.toLowerCase();
3918						}
3919						if(nodeAttributeName == 'keywords' || nodeAttributeName == 'description' || nodeAttributeName == 'sharetitle' || nodeAttributeProperty == 'og:title' || nodeAttributeProperty == 'og:description' || nodeAttributeProperty == 'og:site_name' || nodeAttributeProperty == 'og:novel:latest_chapter_name'){
3920							//替换渲染
3921							if(typeof(originalText) != 'undefined' && originalText != null && originalText.length > 0){
3922								var resultShowText = translate.util.textReplace(node.content, originalText, resultText, translate.to);
3923								translate.element.nodeAnalyse.analyseReplaceBefore_DateToTranslateNode(node, attribute, resultShowText);
3924
3925								node.content = resultShowText;  //2025.4.26 变更为此方式
3926								if(resultShowText.indexOf(resultText) > -1){
3927									result['resultText'] = resultShowText;
3928								}else{
3929									result['resultText'] = '';
3930								}
3931							}
3932
3933							result['text'] = node.content;
3934							return result;
3935						}
3936					}
3937
3938					result['text'] = '';
3939					return result;
3940				}
3941
3942				if(nodename == 'IMG'){
3943
3944					if(typeof(node.alt) == 'undefined' || node.alt == null){
3945						result['text'] = '';
3946						return result;
3947					}
3948
3949					//替换渲染
3950					if(typeof(originalText) != 'undefined' && originalText.length > 0){
3951						var resultShowText = translate.util.textReplace(node.alt, originalText, resultText, translate.to);
3952						translate.element.nodeAnalyse.analyseReplaceBefore_DateToTranslateNode(node, attribute, resultShowText);
3953
3954						node.alt = resultShowText;  //2025.4.26 变更为此方式
3955						if(resultShowText.indexOf(resultText) > -1){
3956							result['resultText'] = resultShowText;
3957						}else{
3958							result['resultText'] = '';
3959						}
3960					}
3961					result['text'] = node.alt;
3962					return result;
3963				}
3964				
3965				
3966				//其他的
3967				if(node.nodeValue == null || typeof(node.nodeValue) == 'undefined'){
3968					result['text'] = '';
3969				}else if(node.nodeValue.trim().length == 0){
3970					//避免就是单纯的空格或者换行
3971					result['text'] = '';
3972				}else{
3973					//替换渲染
3974					if(typeof(originalText) != 'undefined' && originalText != null && originalText.length > 0){
3975						//console.log(originalText+'|');
3976						var resultShowText = translate.util.textReplace(node.nodeValue, originalText, resultText, translate.to);
3977						translate.element.nodeAnalyse.analyseReplaceBefore_DateToTranslateNode(node, attribute, resultShowText);
3978
3979						//console.log(resultShowText+'|');
3980						node.nodeValue = resultShowText;  //2025.4.26 变更为此方式
3981						if(resultShowText.indexOf(resultText) > -1){
3982							result['resultText'] = resultShowText;
3983						}else{
3984							result['resultText'] = '';
3985						}
3986					}
3987					result['text'] = node.nodeValue;
3988				}
3989				return result;
3990			},
3991			/*
3992				在 analyse set 设置到dom之前,先将数据同步到 translate.node 中进行记录
3993				
3994				node: translate.element.nodeAnalyse.analyse中传入的node
3995				attribute: translate.element.nodeAnalyse.analyse中传入的attribute
3996				resultShowText: translate.element.nodeAnalyse.analyse 进行设置翻译后的文本渲染时,提前计算好这个node显示的所有文本,然后在赋予 dom,这里是计算好的node要整体显示的文本
3997			*/	
3998			analyseReplaceBefore_DateToTranslateNode:function(node, attribute, resultShowText){
3999				//console.log('analyseReplaceBefore_DateToTranslateNode: attribute:'+attribute+', resultShowText:'+resultShowText+', node:');
4000				//console.log(node);
4001
4002				var translateNode = null; //当前操作的,要记录入 translate.node 中的,进行翻译的node
4003				var translateNode_attribute = ''; //当前操作的是node中的哪个attribute,如果没有是node本身则是空字符串
4004
4005				if(typeof(attribute) === 'string' && attribute.length > 0){
4006					//是操作的元素的某个属性,这时要判断 是否是 input、textarea 的value属性
4007					if(attribute !== null && attribute === 'value'){
4008						var nodeNameLowerCase = translate.element.getNodeName(node).toLowerCase();
4009						if((nodeNameLowerCase === 'input' || nodeNameLowerCase === 'textarea')){
4010							translateNode = node;
4011							translateNode_attribute = 'value';
4012						}
4013					}
4014					if(translateNode === null){
4015						translateNode = node.getAttributeNode(attribute);
4016						translateNode_attribute = attribute;
4017					}
4018				}else{
4019					//操作的就是node本身
4020					translateNode = node;
4021				}
4022
4023				if(translate.node.find(translateNode)){
4024					if(typeof(translate.node.get(translateNode).translateResults) === 'undefined'){
4025						translate.node.get(translateNode).translateResults = {};
4026					}
4027					translate.node.get(translateNode).translateResults[resultShowText] = 1;
4028					translate.node.get(translateNode).resultText = resultShowText;
4029				}else{
4030					//翻译过程中,会有时间差,比如通过文本翻译api请求,这时node元素本身被其他js改变了,导致翻译完成后,原本的node不存在了
4031					//console.log('[debug] 数据异常,analyse - set 中发现 translate.node 中的 node 不存在,理论上应该只要被扫描了,被翻译了,到这里就一定会存在的,不存在怎么会扫描到交给去翻译呢');
4032				}
4033			},
4034		},
4035		//获取这个node元素的node name ,如果未发现,则返回''空字符串
4036		getNodeName:function(node){
4037			if(node == null || typeof(node) == 'undefined'){
4038				return '';
4039			}
4040
4041			if(node.nodeName == null || typeof(node.nodeName) == 'undefined'){
4042				return '';
4043			}
4044
4045			var nodename = node.nodeName;
4046			if(typeof(node.nodeName) == 'string'){
4047				return node.nodeName;
4048			}else{
4049				if(typeof(node.tagName) == 'string' && node.tagName.length > 0){
4050					return node.tagName;
4051				}else{
4052					translate.log('warn : get nodeName is null, this node ignore translate. node : ');
4053					translate.log(node);
4054					return '';
4055				}
4056			}
4057		},
4058		/*
4059			向下遍历node
4060			其中如果使用了自定义 textarea 、input 的 value 属性,则认为是 node 本身进行挂钩,而非其 value 值(value值并不是个node)
4061		*/
4062		whileNodes:function(uuid, node){
4063			if(node == null || typeof(node) == 'undefined'){
4064				return;
4065			}
4066
4067			//如果这个uuid没有,则创建
4068			if(typeof(translate.nodeQueue[uuid]) == 'undefined' || translate.nodeQueue[uuid] == null){
4069				translate.nodeQueue[uuid] = new Array(); //创建
4070				translate.nodeQueue[uuid]['expireTime'] = Date.now() + 120*1000; //删除时间,10分钟后删除
4071				translate.nodeQueue[uuid]['list'] = new Array(); 
4072				//console.log('创建 --- ');
4073				//console.log(uuid)
4074			}
4075
4076			//console.log('---'+typeof(node)+', ');
4077			//判断是否是有title属性,title属性也要翻译
4078			if(typeof(node) == 'object' && typeof(node['title']) == 'string' && node['title'].trim().length > 0){
4079				//将title加入翻译队列
4080				//console.log('---'+node.title+'\t'+node.tagName);
4081				//console.log(node)
4082				//console.log('------------');
4083				
4084				//判断当前元素是否在ignore忽略的tag、id、class name中
4085				if(!translate.ignore.isIgnore(node)){
4086					//不在忽略的里面,才会加入翻译
4087					//translate.addNodeToQueue(uuid, node, node['title'], 'title');
4088					translate.addNodeToQueue(uuid, node.getAttributeNode('title'), node['title'], '');
4089				}
4090			}
4091
4092			//v3.9.2 增加, 用户可自定义标签内 attribute 的翻译
4093			var nodeNameLowerCase = translate.element.getNodeName(node).toLowerCase();
4094			if(typeof(translate.element.tagAttribute[nodeNameLowerCase]) != 'undefined'){
4095				//console.log('find:'+nodeNameLowerCase);
4096				//console.log(translate.element.tagAttribute[nodeNameLowerCase]);
4097				//console.log(translate.element.tagAttribute[nodeNameLowerCase].attribute);
4098
4099				for(var attributeName_index in translate.element.tagAttribute[nodeNameLowerCase].attribute){
4100					if (!translate.element.tagAttribute[nodeNameLowerCase].attribute.hasOwnProperty(attributeName_index)) {
4101			    		continue;
4102			    	}
4103			    	if(typeof(translate.element.tagAttribute[nodeNameLowerCase].condition) !='undefined' && !translate.element.tagAttribute[nodeNameLowerCase].condition(node)){
4104			    		continue;
4105			    	}
4106					
4107					var attributeName = translate.element.tagAttribute[nodeNameLowerCase].attribute[attributeName_index];
4108					//console.log(attributeName);
4109					//console.log(node.getAttribute(attributeName));
4110
4111					//是否是 input、 textarea 的 value ,如果是 则是 true
4112					var isInputValue = false;
4113					if((nodeNameLowerCase === 'input' || nodeNameLowerCase === 'textarea') && attributeName.toLowerCase() == 'value'){
4114						//如果是input 的value属性,那么要直接获取,而非通过 attribute ,不然用户自己输入的通过 attribute 是获取不到的 - catl 赵阳 提出
4115						attributeValue = node.value;
4116						DOMPropOrHTMLAttr = 'DOMProperty';
4117						isInputValue = true;
4118					}else{
4119						/*
4120						 * 默认是 HtmlAtrribute 也就是 HTML特性。取值有两个:
4121						 * HTMLAtrribute : HTML特性
4122						 * DOMProperty : DOM属性
4123						 */
4124						var DOMPropOrHTMLAttr = 'HTMLAtrribute'; 
4125						var attributeValue = node.getAttribute(attributeName);
4126						if(typeof(attributeValue) == 'undefined' || attributeValue == null){
4127							//vue、element、react 中的一些动态赋值,比如 element 中的 el-select 选中后赋予显示出来的文本,getAttribute 就取不到,因为是改动的 DOM属性,所以要用这种方式才能取出来
4128							attributeValue = node[attributeName];
4129							DOMPropOrHTMLAttr = 'DOMProperty';
4130						}
4131						if(typeof(attributeValue) == 'undefined' || attributeValue == null){
4132							//这个tag标签没有这个属性,忽略
4133							continue;
4134						}
4135					}
4136					
4137
4138					//if(typeof(node.getAttribute(attributeName)) == 'undefined' && typeof(node[attributeName]) == 'undefined'){
4139					//	//这个tag标签没有这个 attribute,忽略
4140					//	continue
4141					//}
4142					//判断当前元素是否在ignore忽略的tag、id、class name中   v3.15.7 增加	
4143					if(!translate.ignore.isIgnore(node)){
4144						//加入翻译
4145						translate.addNodeToQueue(uuid, isInputValue? node:node.getAttributeNode(attributeName), attributeValue, isInputValue? 'value':'');
4146					}
4147				}
4148			}
4149
4150			
4151			var childNodes = node.childNodes;
4152			if(childNodes == null || typeof(childNodes) == 'undefined'){
4153				return;
4154			}
4155			if(childNodes.length > 0){
4156				for(var i = 0; i<childNodes.length; i++){
4157					translate.element.whileNodes(uuid, childNodes[i]);
4158				}
4159			}else{
4160				//单个了
4161				translate.element.findNode(uuid, node);
4162			}
4163		},
4164		findNode:function(uuid, node){
4165			if(node == null || typeof(node) == 'undefined'){
4166				return;
4167			}
4168			if(node.nodeType === 2){  //是属性node,比如 div 的 title 属性的 node
4169				if(node.ownerElement == null){
4170					return;
4171				}
4172			}else{		//是元素了
4173				if(node.parentNode == null){
4174					return;
4175				}	
4176			}
4177			
4178			/****** 判断忽略的class ******/
4179			/*
4180			这段理论上不需要了,因为在  translate.ignore.isIgnore 判断了
4181			var ignoreClass = false;	//是否是被忽略的class,true是
4182			var parentNode = node.parentNode;
4183			while(node != parentNode && parentNode != null){
4184				//console.log('node:'+node+', parentNode:'+parentNode);
4185				if(parentNode.className != null){
4186					if(translate.ignore.class.indexOf(parentNode.className) > -1){
4187						//发现ignore.class 当前是处于被忽略的 class
4188						ignoreClass = true;
4189					}
4190				}
4191				
4192				parentNode = parentNode.parentNode;
4193			}
4194			if(ignoreClass){
4195				//console.log('ignore class :  node:'+node.nodeValue);
4196				return;
4197			}
4198			*/
4199			/**** 判断忽略的class结束 ******/
4200
4201
4202
4203			/**** 避免中途局部翻译,在判断一下 ****/
4204			//判断当前元素是否在ignore忽略的tag、id、class name中
4205			if(translate.ignore.isIgnore(node)){
4206				//console.log('node包含在要忽略的元素中:');
4207				//console.log(node);
4208				return;
4209			}
4210
4211			//node分析,分析这个node的所有可翻译属性(包含自定义翻译属性 translate.element.tagAttribute )
4212			var nodeAnalyChild = translate.element.nodeAnalyse.gets(node);
4213			//console.log(nodeAnalyChild);
4214			for(var nci = 0; nci < nodeAnalyChild.length; nci++){
4215				translate.addNodeToQueue(uuid, nodeAnalyChild[nci].node, nodeAnalyChild[nci].text, '');
4216			}
4217			/*
4218			var nodeAnaly = translate.element.nodeAnalyse.get(node);
4219			if(nodeAnaly['text'].length > 0){
4220				//有要翻译的目标内容,加入翻译队列
4221				console.log(nodeAnaly)
4222				console.log('addNodeToQueue -- '+nodeAnaly['node']+', text:' + nodeAnaly['text']);
4223				translate.addNodeToQueue(uuid, nodeAnaly['node'], nodeAnaly['text'], '');
4224			}
4225			*/
4226			
4227			//console.log(nodeAnaly);
4228			/*
4229			//console.log(node.nodeName+', type:'+node.nodeType+', '+node.nodeValue);
4230			var nodename = translate.element.getNodeName(node);
4231			if(nodename == 'INPUT' || nodename == 'TEXTAREA'){
4232				//input 输入框,要对 placeholder 做翻译
4233				console.log('input---'+node.attributes);
4234				if(node.attributes == null || typeof(node.attributes) == 'undefined'){
4235					return;
4236				}
4237	
4238				if(typeof(node.attributes['placeholder']) != 'undefined'){
4239					//console.log(node.attributes['placeholder'].nodeValue);
4240					//加入要翻译的node队列
4241					//translate.nodeQueue[translate.hash(node.nodeValue)] = node.attributes['placeholder'];
4242					//加入要翻译的node队列
4243					//translate.addNodeToQueue(translate.hash(node.attributes['placeholder'].nodeValue), node.attributes['placeholder']);
4244					translate.addNodeToQueue(uuid, node.attributes['placeholder'], node.attributes['placeholder'].nodeValue);
4245				}
4246				
4247				//console.log(node.getAttribute("placeholder"));
4248			}else if(nodename == 'META'){
4249				//meta标签,如是关键词、描述等
4250				if(typeof(node.name) != 'undefined' && node.name != null){
4251					var nodeAttributeName = node.name.toLowerCase();  //取meta 标签的name 属性
4252					//console.log(nodeName);
4253					if(nodeAttributeName == 'keywords' || nodeAttributeName == 'description'){
4254						//关键词、描述
4255						translate.addNodeToQueue(uuid, node, node.content);
4256					}
4257				}
4258				//console.log(node.name)
4259			}else if(nodename == 'IMG'){
4260				//console.log('-------'+node.alt);
4261				translate.addNodeToQueue(uuid, node, node.alt);
4262			}else if(node.nodeValue != null && node.nodeValue.trim().length > 0){
4263	
4264				//过滤掉无效的值
4265				if(node.nodeValue != null && typeof(node.nodeValue) == 'string' && node.nodeValue.length > 0){
4266				}else{
4267					return;
4268				}
4269	
4270				//console.log(node.nodeValue+' --- ' + translate.language.get(node.nodeValue));
4271				
4272				//console.log(node.nodeName);
4273				//console.log(node.parentNode.nodeName);
4274				//console.log(node.nodeValue);
4275				//加入要翻译的node队列
4276				translate.addNodeToQueue(uuid, node, node.nodeValue);	
4277				//translate.addNodeToQueue(translate.hash(node.nodeValue), node);
4278				//translate.nodeQueue[translate.hash(node.nodeValue)] = node;
4279				//translate.nodeQueue[translate.hash(node.nodeValue)] = node.nodeValue;
4280				//node.nodeValue = node.nodeValue+'|';
4281	
4282			}
4283			*/
4284
4285		},
4286		/*
4287			将node转为element输出。
4288				如果node是文本元素,则转化为这个文本元素所在的element元素
4289				如果node是属性,则转化为这个属性所在的element元素
4290				如果node本身就是元素标签,那就还是这样返回。
4291			
4292			
4293			nodes: node数组,传入如 [node1,node2, ...] 它里面可能包含 node.nodeType 1\2\3 等值
4294
4295			返回这些node转化为所在元素后的数组,返回如 [element1, element2, ...]
4296			注意的是
4297				1. 输出的一定是 element 元素,也就是 node.nodeType 一定等于1
4298				2. 输出的元素数组不一定等于传入的nodes数组,也就是他们的数量跟下标并不是对应相等的
4299			
4300		*/
4301		nodeToElement: function(nodes){
4302			var elements = new Array(); //要改动的元素
4303
4304			//遍历所有node组合到 nodes. 这个不单纯只是遍历组合,它会判断如果是文本节点,则取它的父级元素。它组合的结果是元素的集合
4305		    for(var r = 0; r<nodes.length; r++){
4306		    	var node = nodes[r];
4307	    		if(typeof(node) == 'undefined' || typeof(node.parentNode) == 'undefined'){
4308	    			continue;
4309	    		}
4310	    		if(node.nodeType === 2){
4311	    			//是属性节点,可能是input、textarea 的 placeholder ,获取它的父元素
4312	    			var nodeParentElement = node.ownerElement;
4313			        if(nodeParentElement == null){
4314			        	continue;
4315			        }
4316			        elements.push(nodeParentElement);
4317	    		}else if(node.nodeType === 3){
4318	    			//是文本节点
4319	    			var nodeParentElement = node.parentNode;
4320	    			if(nodeParentElement == null){
4321			        	continue;
4322			        }
4323			        elements.push(nodeParentElement);
4324	    		}else if(node.nodeType === 1){
4325	    			//元素节点了,直接加入
4326	    			elements.push(node);
4327	    		}else{
4328	    			//1\2\3 都不是,这不应该是 translate.js 中应该出现的
4329	    			translate.log('translate.element.nodeToElement 中,发现传入的node.nodeType 类型有异常,理论上不应该存在, node.nodeType:'+node.nodeType);
4330	    			translate.log(node);
4331	    		}
4332	    	}	
4333
4334	    	return elements;
4335		}
4336	},
4337
4338	
4339
4340
4341	
4342	/*
4343	 * 将发现的元素节点加入待翻译队列
4344	 * uuid execute方法执行的唯一id
4345	 * node 当前text所在的node
4346	 * text 当前要翻译的目标文本
4347	 * attribute 是否是元素的某个属性。比如 a标签中的title属性, a.title 再以node参数传入时是string类型的,本身并不是node类型,所以就要传入这个 attribute=title 来代表这是a标签的title属性。同样第二个参数node传入的也不能是a.title,而是传入a这个node元素
4348	 			如果不穿或者传入 '' 空字符串,则代表不是 attribute 属性,而是nodeValue 本身
4349	 			注意, textarea、input 标签的 value 属性的特殊性,如果 node 是textarea、input ,那么value时这个 attribute 要传递 'value' 进来的
4350	 */
4351	addNodeToQueue:function(uuid, node, text, attribute){
4352		//console.log('addNodeToQueue - params: uuid:'+uuid+', text:'+text+', attribute:'+attribute+', node:');
4353		//console.log(node);
4354		
4355		if(node == null || text == null || text.length == 0){
4356			return;
4357		}
4358
4359
4360		//console.log('find tag ignore : '+node.nodeValue+', '+node.nodeName+", "+node.nodeType+", "+node.tagName);
4361		//console.log('addNodeToQueue into -- node:'+node+', text:'+text+', attribute:'+attribute);
4362		var nodename = translate.element.getNodeName(node).toLowerCase();
4363		
4364		//判断如果是被 <!--  --> 注释的区域,不进行翻译
4365		if(nodename == '#comment'){
4366			return;
4367		}
4368		//console.log('\t\t'+text);
4369		//取要翻译字符的hash
4370		var key = translate.util.hash(text);
4371		/*
4372		如果是input 的 placeholder ,就会出现这个情况
4373		if(node.parentNode == null){
4374			console.log('node.parentNode == null');
4375			return;
4376		}
4377		*/
4378
4379		//console.log(node.parentNode);
4380		//console.log(node.parentNode.nodeName);
4381		
4382		//判断其内容是否是 script、style 等编程的文本,如果是,则不进行翻译,不然翻译后还会影响页面正常使用
4383		if(translate.util.findTag(text)){
4384			//console.log('find tag ignore : '+node.nodeValue+', '+node.nodeName+", "+node.nodeType+", "+node.tagName);
4385			//console.log(node.parentNode.nodeName);
4386			
4387			//获取到当前文本是属于那个tag标签中的,如果是script、style 这样的标签中,那也会忽略掉它,不进行翻译
4388			if(node.parentNode == null){
4389				//没有上级了,或是没获取到上级,忽略
4390				return;
4391			}
4392			//去上级的tag name
4393			var parentNodeName = translate.element.getNodeName(node.parentNode);
4394			//node.parentNode.nodeName;
4395			if(parentNodeName == 'SCRIPT' || parentNodeName == 'STYLE'){
4396				//如果是script、style中发现的,那也忽略
4397				return;
4398			}
4399		}
4400		//console.log(node.nodeValue);
4401
4402
4403		/***** 记录这个node 到 translate.node.data,这也是node进入 translate.node.data 记录的第一入口 *****/
4404		var translateNode; //当前操作的,要记录入 translate.node 中的,进行翻译的node
4405		var translateNode_attribute = ''; //当前操作的是node中的哪个attribute,如果没有是node本身则是空字符串
4406		if(typeof(attribute) === 'string' && attribute.length > 0){
4407			//是操作的元素的某个属性,这时要判断 是否是 input、textarea 的value属性
4408			if((nodename === 'input' || nodename === 'textarea') && attribute !== null && attribute === 'value'){
4409				translateNode = node;
4410				translateNode_attribute = 'value';
4411			}else{
4412				translateNode = node.getAttributeNode(attribute);
4413				translateNode_attribute = attribute;
4414			}
4415		}else{
4416			//操作的就是node本身
4417			translateNode = node;
4418		}
4419		if(translate.node.get(translateNode) == null){
4420			translate.node.set(translateNode, {});
4421		}
4422
4423		//var nodeAttribute = translate.node.getAttribute(attribute);
4424		//console.log(text+'-----:');
4425		//console.log(translate.node.get(translateNode));
4426		//if(typeof(translate.node.get(translateNode)[nodeAttribute.key]) == 'undefined'){
4427		//	translate.node.get(node)[nodeAttribute.key] = {};
4428		//}
4429		translate.node.get(translateNode).attribute = translateNode_attribute;
4430		if(typeof(translate.node.get(translateNode).originalText) === 'string'){
4431			//这个节点有过记录原始显示的文本了,那么不再对其进行后续的扫描,除非它有被触发过动态监听元素改变, --- 至于它有被触发过动态监听元素改变--后续想怎么判定
4432			//console.log(translate.node.get(node)[nodeAttribute.key].originalText+'\t又过了,不在翻译');
4433			return;
4434		}else{
4435			//没有过,是第一次,那么赋予值
4436			translate.node.get(translateNode).originalText = text;
4437		}
4438		//console.log(translateNode);
4439		//console.log(translate.node.get(translateNode));
4440		/*
4441		if(typeof(translate.node.get(node).translateTexts) != 'undefined'){ 
4442			//这个node之前已经被扫描过了,那么判断一下上次扫描的文本跟当前获取到的文本是否一致,如果一致,那就没必要进行翻译了
4443			//这个一致,是跟通过文本翻译接口的,翻译前或者翻译后的文本,任何一个相等,就都不需要被翻译
4444			for(var originalText in translate.node.get(node).translateTexts){
4445				if (!translate.node.get(node).translateTexts.hasOwnProperty(originalText)) {
4446		    		continue;
4447		    	}
4448			    if(originalText === text || (translate.node.get(node).translateTexts[originalText] != null && translate.node.get(node).translateTexts[originalText] === text)){
4449			    	console.log('这个node之前已经被翻译过了,有翻译结果,那么判断一下翻译结果跟当前获取到的文本是否一致,如果一致,那就没必要进行翻译了, text:'+text);
4450					return;
4451			    }
4452			}
4453		}
4454		*/
4455		/*
4456		// 将传入的 hitNomenclatureArray 的所有键值对添加到 translate.node.get(node).hitNomenclatureArray 中
4457		hitNomenclatureArray.forEach((value, key) => {
4458		  translate.node.get(node).hitNomenclatureArray.set(key, value);
4459		});
4460		*/
4461		/*
4462		if(typeof(translate.node.get(node).originalText) == 'string' && translate.node.get(node).originalText === text){ 
4463			console.log('这个node之前已经被搜索节点并分析过了, text:'+text);
4464			return;
4465		}
4466		*/
4467		if(typeof(translate.node.get(translateNode).translateTexts) === 'undefined'){
4468			translate.node.get(translateNode).translateTexts = {};
4469		}
4470		/***** 自检完毕,准备进行翻译了 *****/
4471
4472
4473		//原本传入的text会被切割为多个小块
4474		var textArray = new Array();
4475		textArray.push(text); //先将主 text 赋予 ,后面如果对主text进行加工分割,分割后会将主text给删除掉
4476		//console.log(textArray);
4477
4478		// 处理 ignore.regex
4479		var temporaryIgnoreTexts = [];  //仅仅针对当前text文本,通过 translate.ignore.textRegex 所产生的临时不翻译的文本,它并不能作用于其他节点的文本
4480		for (var ri = 0; ri < translate.ignore.textRegex.length; ri++) {
4481			var regex = translate.ignore.textRegex[ri];
4482			for (var tai = 0; tai < textArray.length; tai++) {
4483				var text = textArray[tai];
4484				//temporaryIgnoreTexts = text.match(regex) || []
4485				var matches = text.match(regex) || [];
4486				temporaryIgnoreTexts = temporaryIgnoreTexts.concat(matches);
4487				//translate.ignore.text = translate.ignore.text.concat(ignoreTexts)
4488			}
4489		}
4490		
4491		//将当前节点文本的 不翻译文本规则,重新组合到 temporaryIgnoreTextsByRegex
4492		if(temporaryIgnoreTexts.length == 0){
4493			temporaryIgnoreTexts = translate.ignore.text;
4494		}else{
4495			//将其加入 translate.history.translateTexts 中
4496			temporaryIgnoreTexts.concat(translate.ignore.text);
4497			for(var ti = 0; ti<temporaryIgnoreTexts.length; ti ++){
4498				translate.history.translateText.add(temporaryIgnoreTexts[ti], temporaryIgnoreTexts[ti]);
4499			}
4500		}
4501		
4502		/**** v3.10.2.20241206 - 增加自定义忽略翻译的文本,忽略翻译的文本不会被翻译 - 当然这样会打乱翻译之后阅读的连贯性 ****/
4503		for(var ti = 0; ti<temporaryIgnoreTexts.length; ti++){
4504			if(temporaryIgnoreTexts[ti].trim().length == 0){
4505				continue;
4506			}
4507
4508			//textArray = translate.addNodeToQueueTextAnalysis(uuid, node, textArray, attribute, temporaryIgnoreTexts[ti], temporaryIgnoreTexts[ti]);
4509			
4510			//console.log(textArray);
4511			textArray = translate.nomenclature.dispose(textArray, temporaryIgnoreTexts[ti], temporaryIgnoreTexts[ti], {
4512				node:translateNode,
4513				attribute:translateNode_attribute
4514			}).texts;
4515			//console.log(textArray);
4516		}
4517
4518
4519		/**** v3.10.2.20241206 - 自定义术语能力全面优化 - 当然这样会打乱翻译之后阅读的连贯性 ****/
4520		//判断是否进行了翻译,也就是有设置目标语种,并且跟当前语种不一致
4521		if(typeof(translate.temp_nomenclature) == 'undefined'){
4522			translate.temp_nomenclature = new Array();
4523		}
4524		if(typeof(translate.temp_nomenclature[translate.language.getLocal()]) == 'undefined'){
4525			nomenclatureKeyArray = new Array();
4526		}
4527		if(typeof(translate.nomenclature.data[translate.language.getLocal()]) != 'undefined' && typeof(translate.nomenclature.data[translate.language.getLocal()][translate.to]) != 'undefined'){
4528			var nomenclatureKeyArray;
4529			for(var nomenclatureKey in translate.nomenclature.data[translate.language.getLocal()][translate.to]){
4530				if (!translate.nomenclature.data[translate.language.getLocal()][translate.to].hasOwnProperty(nomenclatureKey)) {
4531		    		continue;
4532		    	}
4533				//nomenclatureKey 便是自定义术语的原始文本,值是要替换为的文本
4534				//console.log(nomenclatureKey);
4535				//自定义属于的指定的结果字符串
4536				var nomenclatureValue = translate.nomenclature.data[translate.language.getLocal()][translate.to][nomenclatureKey];
4537
4538				//console.log('----translate.nomenclature.dispose---');
4539				//console.log(textArray);
4540				var nomenclatureDispose = translate.nomenclature.dispose(textArray, nomenclatureKey, nomenclatureValue, {
4541					node:translateNode,
4542					attribute:translateNode_attribute
4543				});
4544				
4545				textArray = nomenclatureDispose.texts;
4546				if(nomenclatureDispose.find){
4547					//console.log('发现自定义术语,并已进行替换处理:');
4548					//console.log(nomenclatureDispose);
4549				}
4550				
4551				if(typeof(nomenclatureKeyArray) != 'undefined'){
4552					nomenclatureKeyArray.push(nomenclatureKey);
4553				}
4554			}
4555
4556			if(typeof(translate.temp_nomenclature[translate.language.getLocal()]) == 'undefined'){
4557				translate.temp_nomenclature[translate.language.getLocal()] = nomenclatureKeyArray;
4558			}
4559		}
4560		/**** v3.10.2.20241206 - 自定义术语能力全面优化 - end ****/
4561		
4562
4563
4564		//记录 nodeHistory - 判断text是否已经被拆分了
4565		if(textArray.length > 0 && textArray[0] != text){  //主要是后面的是否相等,前面的>0只是避免代码报错
4566			translate.node.get(translateNode).whole = false; //已经被拆分了,不是整体翻译了
4567			//这时,也默认给其赋值操作,将自定义术语匹配后的结果进行赋予
4568		}else{
4569			translate.node.get(translateNode).whole = true; //未拆分,是整体翻译
4570		}
4571		//成功加入到 nodeQueue 的对象。 如果长度为0,那就是还没有加入到 translate.nodeQueue 中,可能全被自定义术语命中了
4572		var addQueueObjectArray = [];
4573
4574		//console.log(textArray);
4575		for(var tai = 0; tai<textArray.length; tai++){
4576			if(textArray[tai].trim().length == 0){
4577				continue;
4578			}
4579
4580			/* 自定义术语 - 忽略翻译文本  , 在本方法的上面已经完成了识别,这里就不再需要了
4581			//判断是否出现在自定义忽略字符串
4582			if(translate.ignore.text.indexOf(textArray[tai].trim()) > -1){
4583				//console.log(textArray[tai]+' 是忽略翻译的文本,不翻译');
4584				continue;
4585			}
4586			*/
4587
4588			/* 自定义术语,在本方法的上面已经完成了识别,这里就不再需要了
4589			//判断是否出现在自定义术语的
4590			if(typeof(translate.temp_nomenclature[translate.language.getLocal()]) != 'undefined'){
4591				if(translate.temp_nomenclature[translate.language.getLocal()].indexOf(textArray[tai].trim()) > -1){
4592					//console.log(textArray[tai]+' 是自定义术语,不翻译');
4593					continue;
4594				}
4595			}
4596			*/
4597
4598			var newAddQueueArray = translate.addNodeToQueueAnalysis(uuid, node, textArray[tai], attribute);
4599			//console.log(newAddQueueArray)
4600			Array.prototype.push.apply(addQueueObjectArray, newAddQueueArray);
4601		}
4602		
4603		//console.log('成功加入进nodequeue的数量:'+addQueueObjectArray.length);
4604		//console.log(addQueueObjectArray);
4605
4606		if(addQueueObjectArray.length == 0){
4607			//没有加入到 nodeQueue 中,那么也就是在自定义术语这一层,就已经完成了渲染,此时要触发相关钩子
4608			// translate.node 记录
4609			
4610			// 记录当前有 translate.js 所触发翻译之后渲染到dom界面显示的时间,13位时间戳
4611			translate.node.get(translateNode).lastTranslateRenderTime = Date.now();
4612			//将具体通过文本翻译接口进行翻译的文本记录到 translate.node.data
4613			translate.node.get(translateNode).translateTexts = {}; //这里全部命中了,所以根本没有走翻译接口的文本
4614			//将翻译完成后要显示出的文本进行记录
4615			translate.node.get(translateNode).resultText = translate.element.nodeAnalyse.get(node, attribute).text; //直接获取当前node显示出来的文本作为最后的结果的文本
4616
4617			//将其加入 translate.history.translateTexts 中
4618			translate.history.translateText.add(translate.node.get(translateNode).originalText, translate.node.get(translateNode).resultText);
4619		}
4620		
4621	},
4622
4623	
4624
4625	/*
4626
4627		服务于上面的 addNodeToQueue ,用于区分不同type情况,进行调用此加入 translate.nodeQueue
4628		uuid, node, attribute 这五个参数说明见 addNodeToQueue 的参数说明,相同
4629		
4630		word 要实际进行翻译的文本,也就是要把它拿来进行通过后端翻译接口进行翻译的文本
4631		lang 当前要翻译的文本的语种,如 english
4632		beforeText 参见 translate.nodeQueue 注释中第七维的解释
4633		afterText 参见 translate.nodeQueue 注释中第七维的解释
4634		
4635
4636		返回:
4637			加入 nodeQueue 后的对象。 
4638			这里跟addNodeQueueItem方法返回一样,只不过 addNodeQueueItem 方法返回的是一个,而这里是多个,数组的形式。
4639			如果一个也没有加入到 nodeQueue,那么这里返回的数组长度便是0
4640	*/
4641	addNodeToQueueAnalysis:function(uuid, node, text, attribute){
4642		//获取当前是什么语种
4643		//console.log('uuid:'+uuid+', text:'+text+', attribute:'+attribute+'node:');
4644		//console.log(node);
4645		//var langs = translate.language.get(text);
4646		var textRecognition = translate.language.recognition(text);
4647		var langs = textRecognition.languageArray;
4648		//console.log('langs');
4649		//console.log(langs);
4650
4651		
4652		//过滤掉要转换为的目标语种,比如要转为英语,那就将本来是英语的部分过滤掉,不用再翻译了
4653		if(typeof(langs[translate.to]) != 'undefined'){
4654			delete langs[translate.to];
4655		}
4656		
4657		var isWhole = translate.whole.isWhole(node);
4658		//console.log('isWhole:'+isWhole+', '+text);
4659
4660		//记录成功加入 nodeQueue 的,如果加入了多个,那就是多个数组,如果长度为0,那就是啥也没加入了
4661		var addNodeQueueArray = [];
4662
4663		if(!isWhole){
4664			//常规方式,进行语种分类
4665
4666
4667			/* if(this.nodeQueue[lang] == null || typeof(this.nodeQueue[lang]) == 'undefined'){
4668				this.nodeQueue[lang] = new Array();
4669			} 
4670			//创建二维数组
4671			if(this.nodeQueue[lang][key] == null || typeof(this.nodeQueue[lang][key]) == 'undefined'){
4672				this.nodeQueue[lang][key] = new Array();
4673			}
4674			*/
4675			//console.log(langs);
4676			
4677			for(var lang in langs) {
4678				if (!langs.hasOwnProperty(lang)) {
4679		    		continue;
4680		    	}
4681				//创建二维数组, key为语种,如 english
4682				/*
4683				放到了 translate.addNodeQueueItem 进行判断
4684				if(translate.nodeQueue[uuid]['list'][lang] == null || typeof(translate.nodeQueue[uuid]['list'][lang]) == 'undefined'){
4685					translate.nodeQueue[uuid]['list'][lang] = new Array();
4686				}
4687				*/
4688				//console.log('|'+langs[lang].length);
4689				//遍历出该语种下有哪些词需要翻译
4690				for(var word_index = 0; word_index < langs[lang].list.length; word_index++){
4691					//console.log('start:'+word_index)
4692					//console.log(langs[lang].list[word_index]);
4693					if(typeof(langs[lang].list[word_index]) == 'undefined' || typeof(langs[lang].list[word_index]['text']) == 'undefined'){
4694						//理论上应该不会,但多加个判断
4695						continue;
4696					}
4697					var word = langs[lang].list[word_index]['text']; //要翻译的词
4698					var beforeText = langs[lang].list[word_index]['beforeText'];
4699					var afterText = langs[lang].list[word_index]['afterText'];
4700
4701					//console.log(lang+' - '+word+', attribute:'+attribute);
4702					var addQueue = translate.addNodeQueueItem(uuid, node, word, attribute, lang, beforeText, afterText);
4703					if(addQueue != null){
4704						addNodeQueueArray.push(addQueue);
4705					}
4706
4707					/*
4708					var hash = translate.util.hash(word); 	//要翻译的词的hash					
4709					//创建三维数组, key为要通过接口翻译的文本词或句子的 hash (注意并不是node的文本,而是node拆分后的文本)
4710					if(translate.nodeQueue[uuid]['list'][lang][hash] == null || typeof(translate.nodeQueue[uuid]['list'][lang][hash]) == 'undefined'){
4711						translate.nodeQueue[uuid]['list'][lang][hash] = new Array();
4712						
4713						translate.nodeQueue[uuid]['list'][lang][hash]['nodes'] = new Array();
4714						translate.nodeQueue[uuid]['list'][lang][hash]['original'] = word;
4715						translate.nodeQueue[uuid]['list'][lang][hash]['translateText'] = translate.nomenclature.dispose(word); //自定义术语处理
4716						//translate.nodeQueue[uuid]['list'][lang][hash]['beforeText'] = beforeText;
4717						//translate.nodeQueue[uuid]['list'][lang][hash]['afterText'] = afterText;
4718						//translate.nodeQueue[uuid]['list'][lang][hash]['attribute'] = attribute; //放入 nodes[index][attribute] 元素中
4719						
4720						//其中key: nodes 是第四维数组,里面存放具体的node元素对象
4721						
4722
4723						//console.log(translate.nodeQueue[uuid]['list'][lang][hash]);
4724					}
4725					
4726					var isEquals = false; //queue中是否已经加入过这个node了(当然是同一hash同一node情况)
4727					if(typeof(node.isSameNode) != 'undefined'){	//支持 isSameNode 方法判断对象是否相等
4728						for(var node_index = 0; node_index < translate.nodeQueue[uuid]['list'][lang][hash]['nodes'].length; node_index++){
4729							if(node.isSameNode(translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]['node'])){
4730								//相同,那就不用在存入了
4731								//console.log('相同,那就不用在存入了')
4732								isEquals = true;
4733								//console.log(node)
4734								continue;
4735							}
4736						}
4737					}
4738					if(isEquals){
4739						//相同,那就不用在存入了
4740						continue;
4741					}
4742
4743					//往五维数组nodes中追加node元素
4744					var nodesIndex = translate.nodeQueue[uuid]['list'][lang][hash]['nodes'].length;
4745					translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][nodesIndex] = new Array();
4746					translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][nodesIndex]['node']=node; 
4747					translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][nodesIndex]['attribute']=attribute;
4748					translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][nodesIndex]['beforeText'] = beforeText;
4749					translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][nodesIndex]['afterText'] = afterText;
4750					
4751					*/
4752
4753					//console.log('end:'+word_index)
4754				}
4755				
4756			}
4757			
4758
4759
4760
4761		}else{
4762			//直接翻译整个元素内的内容,不再做语种分类
4763			var lang = translate.language.recognition_languageName_force(textRecognition);
4764			//console.log(lang+' - '+text);
4765			var addQueue = translate.addNodeQueueItem(uuid, node, text, attribute, lang, '', '');
4766			if(addQueue != null){
4767				addNodeQueueArray.push(addQueue);
4768			}
4769		}
4770
4771		//console.log('-----'+addNodeQueueArray.length);
4772		return addNodeQueueArray;
4773	},
4774
4775	/*
4776
4777		服务于上面的 addNodeToQueue ,用于区分不同type情况,进行调用此加入 translate.nodeQueue
4778		uuid, node, attribute 这五个参数说明见 addNodeToQueue 的参数说明,相同
4779		
4780		word 要实际进行翻译的文本,也就是要把它拿来进行通过后端翻译接口进行翻译的文本
4781		lang 当前要翻译的文本的语种,如 english
4782		beforeText 参见 translate.nodeQueue 注释中第七维的解释
4783		afterText 参见 translate.nodeQueue 注释中第七维的解释
4784
4785		返回值判断 不为 null,则是成功加入了 nodeQueue ,返回加入后的  translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][nodesIndex] ,包含这些:
4786			afterText: 
4787			attribute: 
4788			beforeText: 
4789			node: 
4790
4791	*/
4792	addNodeQueueItem:function(uuid, node, word, attribute, lang, beforeText, afterText){
4793		//console.log('uuid:'+uuid+', word:'+word+', attribute:'+attribute+', lang:'+lang+', beforeText:'+beforeText+', afterText:'+afterText+', node:');
4794		//console.log(node);
4795
4796		//创建二维数组, key为语种,如 english
4797		if(translate.nodeQueue[uuid]['list'][lang] == null || typeof(translate.nodeQueue[uuid]['list'][lang]) == 'undefined'){
4798			translate.nodeQueue[uuid]['list'][lang] = new Array();
4799		}
4800		//console.log(word)
4801		//var word = text;	//要翻译的文本
4802		var hash = translate.util.hash(word); 	//要翻译的文本的hash
4803
4804		//创建三维数组, key为要通过接口翻译的文本词或句子的 hash 。这里翻译的文本也就是整个node元素的内容了,不用在做拆分了
4805		if(translate.nodeQueue[uuid]['list'][lang][hash] == null || typeof(translate.nodeQueue[uuid]['list'][lang][hash]) == 'undefined'){
4806			translate.nodeQueue[uuid]['list'][lang][hash] = new Array();
4807
4808			/*
4809			 * 创建四维数组,存放具体数据
4810			 * key: nodes 包含了这个hash的node元素的数组集合,array 多个。其中
4811			 		nodes[index]['node'] 存放当前的node元素
4812			 		nodes[index]['attribute'] 存放当前hash,也就是翻译文本针对的是什么,是node本身(nodeValue),还是 node 的某个属性,比如title属性。如果这里不为空,那就是针对的属性操作的
4813			 * key: original 原始的要翻译的词或句子,html加载完成但还没翻译前的文本,用于支持当前页面多次语种翻译切换而无需跳转
4814			 * beforeText、afterText:见 translate.nodeQueue 的说明
4815			 */
4816			translate.nodeQueue[uuid]['list'][lang][hash]['nodes'] = new Array();
4817			translate.nodeQueue[uuid]['list'][lang][hash]['original'] = word;
4818			//自定义术语处理在此前面已经执行过了,所以这个废弃,不需要处理自定义术语部分了
4819			//translate.nodeQueue[uuid]['list'][lang][hash]['translateText'] = translate.nomenclature.dispose(word); 
4820			translate.nodeQueue[uuid]['list'][lang][hash]['translateText'] = word;
4821			//console.log(word)
4822
4823
4824
4825			//其中key: nodes 是第四维数组,里面存放具体的node元素对象
4826		}
4827
4828
4829		var isEquals = false; //queue中是否已经加入过这个node了(当然是同一hash同一node且同一 attribute的 情况)
4830		if(typeof(node.isSameNode) != 'undefined'){	//支持 isSameNode 方法判断对象是否相等
4831			for(var node_index = 0; node_index < translate.nodeQueue[uuid]['list'][lang][hash]['nodes'].length; node_index++){
4832				if(node.isSameNode(translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index]['node'])){
4833					//在判断 attribute 是否相同
4834					//console.log('attribute:'+attribute+", 对比的 :"+translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index].attribute)
4835					if(attribute === translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][node_index].attribute){
4836						//相同,那就不用在存入了
4837						isEquals = true;
4838					}
4839					//console.log(node)
4840					continue;
4841				}
4842			}
4843		}
4844		if(isEquals){
4845			//相同,那就不用在存入了
4846			return null;
4847		}
4848
4849		//往五维数组nodes中追加node元素
4850		var nodesIndex = translate.nodeQueue[uuid]['list'][lang][hash]['nodes'].length;
4851		translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][nodesIndex] = new Array();
4852		translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][nodesIndex]['node']=node; 
4853		translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][nodesIndex]['attribute']=attribute;
4854		translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][nodesIndex]['beforeText'] = beforeText;
4855		translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][nodesIndex]['afterText'] = afterText;
4856		
4857		return translate.nodeQueue[uuid]['list'][lang][hash]['nodes'][nodesIndex];
4858		/*
4859		//记录这个node
4860		if(translate.node.get(node) == null){
4861			translate.node.set(node, {});
4862		}
4863		
4864		if(typeof(translate.node.get(node).hitNomenclatureArray) == 'undefined'){
4865			translate.node.get(node).hitNomenclatureArray = new Map();
4866		}
4867		translate.node.get(node).translateTexts[word] = null; //设置要进行通过文本翻译接口翻译的文字
4868		*/
4869		
4870	},
4871
4872	//全部翻译,node内容全部翻译,而不是进行语种提取,直接对node本身的全部内容拿出来进行直接全部翻译
4873	whole:{
4874		isEnableAll:false, //是否开启对整个html页面的整体翻译,也就是整个页面上所有存在的能被翻译的全部会采用整体翻译的方式。默认是 false不开启		
4875
4876		enableAll:function(){
4877			translate.whole.isEnableAll = true;
4878		},
4879
4880		/*
4881			一下三个,也就是  class tag id 分别存储加入的值。使用参考:http://translate.zvo.cn/42563.html
4882		*/
4883		class:[],
4884		tag:[],
4885		id:[],
4886
4887		//运行时出现自检并在浏览器控制台提示性文本。 
4888		//在执行翻译,也就是 execute() 时,会调用此方法。
4889		executeTip:function(){
4890			if(translate.whole.class.length == 0 && translate.whole.tag.length == 0 && translate.whole.id.length == 0){
4891				
4892			}else{
4893				translate.log('您开启了 translate.whole 此次行为避开了浏览器端的文本语种自动识别,而是暴力的直接对某个元素的整个文本进行翻译,很可能会产生非常大的翻译量,请谨慎!有关每日翻译字符的说明,可参考: http://translate.zvo.cn/42557.html ');
4894			}
4895
4896			if(translate.whole.tag.indexOf('html') > -1){
4897				translate.log('自检发现您设置了 translate.whole.tag 其中有 html ,这个是不生效的,最大只允许设置到 body ');
4898			}
4899		},
4900
4901		//当前元素是属于全部翻译定义的元素
4902		/*
4903			传入一个元素,判断这个元素是否是被包含的。 这个会找父类,看看父类中是否包含在其之中。
4904			return true是在其中,false不再其中
4905		*/
4906		isWhole:function(ele){
4907
4908			if(translate.whole.isEnableAll){
4909				return true;
4910			}
4911
4912			//如果设置了 class|tag|id 其中某个,或者 all=true ,那么就是启用,反之未启用
4913			if((translate.whole.class.length == 0 && translate.whole.tag.length == 0 && translate.whole.id.length == 0) && translate.whole.isEnableAll == false){
4914				//未设置,那么直接返回false
4915				return false;
4916			}
4917			if(ele == null || typeof(ele) == 'undefined'){
4918				return false;
4919			}
4920			
4921
4922			var parentNode = ele;
4923			var maxnumber = 100;	//最大循环次数,避免死循环
4924			while(maxnumber-- > 0){
4925				if(parentNode == null || typeof(parentNode) == 'undefined'){
4926					//没有父元素了
4927					return false;
4928				}
4929
4930				//判断Tag
4931				//var tagName = parentNode.nodeName.toLowerCase(); //tag名字,小写
4932				var nodename = translate.element.getNodeName(parentNode).toLowerCase(); //tag名字,小写
4933				if(nodename.length > 0){
4934					//有nodename
4935					if(nodename == 'html' || nodename == '#document'){
4936						//上层元素已经是顶级元素了,那肯定就不是了
4937						return false;
4938					}
4939					if(translate.whole.tag.indexOf(nodename) > -1){
4940						//发现ignore.tag 当前是处于被忽略的 tag
4941						return true;
4942					}
4943				}
4944				
4945
4946				//判断class name
4947				if(parentNode.className !== null && typeof(parentNode.className) === 'string'){
4948					var classNames = parentNode.className;
4949					if(classNames == null || typeof(classNames) != 'string'){
4950						continue;
4951					}
4952					//console.log('className:'+typeof(classNames));
4953					//console.log(classNames);
4954					classNames = classNames.trim().split(' ');
4955					for(var c_index = 0; c_index < classNames.length; c_index++){
4956						if(classNames[c_index] != null && classNames[c_index].trim().length > 0){
4957							//有效的class name,进行判断
4958							if(translate.whole.class.indexOf(classNames[c_index]) > -1){
4959								//发现ignore.class 当前是处于被忽略的 class
4960								return true;
4961							}
4962						}
4963					}					
4964				}
4965
4966				//判断id
4967				if(parentNode.id != null && typeof(parentNode.id) != 'undefined'){
4968					//有效的class name,进行判断
4969					if(translate.whole.id.indexOf(parentNode.id) > -1){
4970						//发现ignore.id 当前是处于被忽略的 id
4971						return true;
4972					}
4973				}
4974
4975				//赋予判断的元素向上一级
4976				parentNode = parentNode.parentElement;
4977			}
4978
4979			return false;
4980		}
4981	},
4982
4983	language:{
4984
4985		
4986
4987		/*	
4988			英语的变种语种,也就是在英语26个字母的基础上加了点别的特殊字母另成的一种语言,而这些语言是没法直接通过识别字符来判断出是哪种语种的
4989			
4990			法语、意大利语、德语、葡萄牙语
4991
4992			要废弃,用下面的 systems 、 name
4993		*/
4994		englishVarietys : ['french','italian','deutsch', 'portuguese'],
4995
4996		/*
4997			语言的书写体系,分成哪几个语言体系。
4998				这里区分,主要是单纯从文字组成长进行区分的。
4999
5000			其中
5001			key : 语言体系的名字
5002			value: 语言体系的详细信息
5003				direction: 书写方向 (当前只是记录,无其他意义)
5004				remark: 说明备注 (当前只是记录,无其他意义)
5005				languages: 这个语言体系下,有哪些具体语种, translate.js 的语言标识
5006
5007		 */ 
5008		systems : {
5009			
5010			// 拉丁字母体系
5011			latin:{
5012				direction: "left-to-right", // 书写方向
5013				coreFeatures: "基础字母26个(A-Z),部分语言添加变音符号(如é、ñ、ü),从左到右书写",
5014				languages: [ //包含的语种
5015			      "english", "latin", "french", "spanish", "deutsch", "portuguese",
5016			      "italian", "越南语", "马来语", "印尼语",
5017			      "土耳其语", "波兰语", "荷兰语", "瑞典语", "非洲诸语(多数)",
5018			      "美洲诸语(多数)", "菲律宾语", "哈萨克语(现代拉丁化)"
5019			    ]
5020			},
5021			
5022			// 汉字体系(表意文字)
5023			chinese:{
5024				direction: "left-to-right",
5025				coreFeatures: "表意文字,单字独立,可组合成词,笔画复杂,现代多横向书写",
5026				languages: [ 
5027			      "chinese_simplified", "chinese_traditional", "japanese", "korean"
5028			    ]
5029			},
5030
5031			//阿拉伯字母体系
5032			arabic:{
5033				direction: "right-to-left",
5034				coreFeatures: "表意文字,单字独立,可组合成词,笔画复杂,现代多横向书写",
5035				languages: [ 
5036			      "阿拉伯语", "波斯语", "乌尔都语", "旁遮普语(巴基斯坦)","豪萨语(西非)", "普什图语",
5037			    ]
5038			},
5039
5040			//西里尔字母体系
5041			cyrillic:{
5042				direction: "left-to-right",
5043				coreFeatures: "源于希腊字母,字母形态独特(如п、в、м),部分字母与拉丁字母形似但发音不同",
5044				languages: [ 
5045			      "俄语", "乌克兰语", "白俄罗斯语", "保加利亚语", "塞尔维亚语(官方)"
5046			    ]
5047			},
5048
5049			//泰语体系
5050			thai:{
5051				direction: "left-to-right",
5052				coreFeatures: "元音附标文字,字母弯曲优美,含音调符号(影响词义)",
5053				languages: [ 
5054			      "thai"
5055			    ]
5056			},
5057
5058			//其他后续补充吧
5059			
5060		},
5061
5062		/*
5063			生成 translate.language.english 这种语种对象,通过 translate.language.systems
5064		*/
5065		generateLanguageNameObject:function(){
5066			var languages = new Map();
5067			for(var key in translate.language.systems){
5068				if (!translate.language.systems.hasOwnProperty(key)) {
5069		    		continue;
5070		    	}
5071				for(var li = 0; li < translate.language.systems[key].languages.length; li++){
5072					//console.log(translate.language.systems[key].languages[li])
5073					languages.set(translate.language.systems[key].languages[li], {
5074						system: key
5075					});
5076				}
5077			}
5078			return languages;
5079		},
5080
5081		/*
5082			语言表示:属性相关,他会在translate.js 加载完后自动初始化,从 translate.language.systems 中遍历出来,赋予 translate.language.name
5083			它里面的值为: 
5084				translate.language.map.get('english') = {
5085					system:'latin'	//所属系统语族 , 也就是 translate.language.systems[key] 的 key
5086				}
5087				
5088			它会在 translate.execute() 是进行初始化,通过触发 translate.language.generateLanguageNameObject 赋予值
5089		*/
5090		map_data: null, //这是一个map 
5091		map: function(){
5092			if(translate.language.map_data == null){
5093				translate.language.map_data = translate.language.generateLanguageNameObject();
5094			}
5095			return translate.language.map_data;
5096		},
5097		
5098
5099		//当前本地语种,本地语言,默认是简体中文。设置请使用 translate.language.setLocal(...)。不可直接使用,使用需用 getLocal()
5100		local:'',
5101
5102		/*
5103		 * v3.12增加, 是否会翻译本地语种,默认是false,不会翻译。
5104		 * 比如当前设置的本地语种是简体中文, 但是网页中也有一段英文, 如果设置了translate.to 为中文,也就是要以中文显示 默认是false的情况下,整个页面是不会被任何翻译的,也就是有的那段英文也不会进行任何翻译,依旧是显示英文。
5105		 * 如果这里设置为 true, 则英文也会被翻译,只要不是中文的,都会被翻译为要显示的语种,也就是都会被翻译为中文。
5106		 */
5107		translateLocal:false,
5108
5109		/*
5110			翻译语种范围
5111			比如传入 ['chinese_simplified','chinese_traditional','english'] 则表示仅对网页中的简体中文、繁体中文、英文 进行翻译,而网页中出现的其他的像是法语、韩语则不会进行翻译
5112			如果为空 [],则是翻译时,翻译网页中的所有语种			
5113			设置方式为:  translate.language.translateLanguagesRange = ['chinese_simplified','chinese_traditional']
5114		*/
5115		translateLanguagesRange: [], 
5116		//传入语种。具体可传入哪些参考: http://api.translate.zvo.cn/doc/language.json.html
5117		setLocal:function(languageName){
5118			//translate.setUseVersion2(); //Set to use v2.x version
5119			translate.useVersion = 'v2';
5120			translate.language.local = languageName;
5121		},
5122		//获取当前本地语种,本地语言,默认是简体中文。设置请使用 translate.language.setLocal(...)
5123		getLocal:function(){
5124			//判断是否设置了本地语种,如果没设置,自动给其设置
5125			if(translate.language.local == null || translate.language.local.length < 1){
5126				translate.language.autoRecognitionLocalLanguage();
5127			}
5128			return translate.language.local;
5129		},
5130		/*
5131			获取当前语种。
5132			比如当前设置的本地语种是简体中文,用户并未切换其他语种,那么这个方法将返回本地当前的语种,也就是等同于 translate.language.getLocal()
5133			如果用户切换为英语进行浏览,那么这个方法将返回翻译的目标语种,也就是 english
5134		*/
5135		getCurrent:function(){
5136			var to_storage = translate.storage.get('to');
5137			if(to_storage != null && typeof(to_storage) != 'undefined' && to_storage.length > 0){
5138				//之前有过使用,并且主动设置过目标语种
5139				return to_storage;
5140			}
5141			return translate.language.getLocal();
5142		},
5143		//如果第一次用,默认以什么语种显示。
5144		//比如本地当前语种是简体中文,这里设置为english,那么用户第一次使用时,会自动翻译为english进行显示。如果用户手动切换为其他语种比如韩语,那么就遵循用户手动切换的为主,显示韩语。
5145		setDefaultTo:function(languageName){
5146			var to_storage = translate.storage.get('to');
5147			if(to_storage != null && typeof(to_storage) != 'undefined' && to_storage.length > 0){
5148				//之前有过使用,并且主动设置过目标语种,那么不进行处理
5149			}else{
5150				//没有设置过,进行处理
5151				translate.storage.set('to', languageName);
5152				translate.to = languageName;
5153			}
5154		},
5155		/*
5156			清除历史翻译语种的缓存
5157		*/
5158		clearCacheLanguage:function(){
5159			if(typeof(translate.language.setUrlParamControl_use) != 'undefined'){
5160				if(translate.language.setUrlParamControl_use){
5161					translate.log('使用提示:')
5162					translate.log('translate.language.setUrlParamControl(...) 的作用是 可以通过URL传一个语种,来指定当前页面以什么语种显示。 参考文档: http://translate.zvo.cn/4075.html');
5163					translate.log('translate.language.clearCacheLanguage() 是清除历史翻译语种缓存,也就是清除之前指定翻译为什么语种。 参考文档:http://translate.zvo.cn/4080.html')
5164					translate.log('如果你执行了 translate.language.setUrlParamControl(...) 那么是要根据url传参来切换语种的,但是后面又出现了 translate.language.clearCacheLanguage() 它会阻止 translate.language.setUrlParamControl(...) 它的设置,即使有url传递翻译为什么语言,也会因为 translate.language.clearCacheLanguage() 给清除掉,使URL传参的语种不起任何作用。')
5165				}
5166			}
5167			translate.to = '';
5168			translate.storage.set('to','');
5169		},
5170		//根据URL传参控制以何种语种显示
5171		//设置可以根据当前访问url的某个get参数来控制使用哪种语言显示。
5172		//比如当前语种是简体中文,网页url是http://translate.zvo.cn/index.html ,那么可以通过在url后面增加 language 参数指定翻译语种,来使网页内容以英文形态显示 http://translate.zvo.cn/index.html?language=english
5173		setUrlParamControl:function(paramName){
5174			translate.language.setUrlParamControl_use = true; //标记已执行了 translate.language.setUrlParamControl  ,仅仅只是标记,无其他作用
5175			if(typeof(paramName) == 'undefined' || paramName.length < 1){
5176				paramName = 'language';
5177			}
5178			var paramValue = translate.util.getUrlParam(paramName);
5179			if(typeof(paramValue) == 'undefined'){
5180				return;
5181			}
5182			if(paramValue == '' || paramValue == 'null' || paramValue == 'undefined'){
5183				return;
5184			}
5185
5186			translate.storage.set('to', paramValue);
5187			translate.to = paramValue;
5188		},
5189		/* 
5190			获取翻译区域的原始文本,翻译前的文本。 这里会把空白符等过滤掉,只返回纯显示的文本
5191			也就是获取 translate.setDocument(...) 定义的翻译区域中,翻译前,要参与翻译的文本。 
5192			其中像是 translate.ignore.tag 这种忽略翻译的标签ï¼
5192Œè¿™é‡Œä¹Ÿä¸ä¼šèŽ·å–çš„ï¼Œè¿™é‡Œåªæ˜¯èŽ·å–å®žé™…è¦å‚ä¸Žç¿»è¯‘çš„æ–‡æœ¬ã€‚
5193		 */
5194		getTranslateAreaText:function(){
5195			//v3.16.1 优化,获取本地语种,针对开源中国只对 readme 部分进行翻译的场景,将针对设置的 translate.setDocument() 区域的元素的显示文本进行判定语种
5196			var translateAreaText = ''; //翻译区域内当前的文本
5197			
5198			/** 构建虚拟容器,将要翻译的区域放入虚拟容器,以便后续处理 **/
5199			var virtualContainer = document.createElement('div'); // 创建虚拟容器,处理、判断也都是针对这个虚拟容器
5200			if(translate.documents != null && typeof(translate.documents) != 'undefined' && translate.documents.length > 0){
5201				// setDocuments 指定的
5202				for(var docs_index = 0; docs_index < translate.documents.length; docs_index++){
5203					var doc = translate.documents[docs_index];
5204					if(typeof(doc) != 'undefined' && doc != null && typeof(doc.innerText) != 'undefined' && doc.innerText != null && doc.innerText.length > 0){
5205						virtualContainer.appendChild(doc.cloneNode(true));
5206					}
5207				}
5208			}else{
5209				//未使用 setDocuments指定,那就是整个网页了
5210				//return document.all; //翻译所有的  这是 v3.5.0之前的
5211				//v3.5.0 之后采用 拿 html的最上层的demo,而不是 document.all 拿到可能几千个dom
5212				if(typeof(document.head) != 'undefined'){
5213					virtualContainer.appendChild(document.head.cloneNode(true));
5214				}
5215				if(typeof(document.body) != 'undefined'){
5216					virtualContainer.appendChild(document.body.cloneNode(true));
5217				}
5218			}
5219			//console.log(virtualContainer);
5220
5221
5222			/** 对虚拟容器中的元素进行处理,移除忽略的 tag (这里暂时就只是移除忽略的tag, 其他忽略的后续再加) **/
5223			// 遍历标签列表
5224			//console.log('---- remove element');
5225		    for (var i = 0; i < translate.ignore.tag.length; i++) {
5226		        var tagName = translate.ignore.tag[i];
5227		        var elements = virtualContainer.querySelectorAll(tagName);
5228		        // 将 NodeList 转换为数组
5229		        var elementArray = Array.prototype.slice.call(elements);
5230		        // 遍历并移除每个匹配的元素
5231		        for (var j = 0; j < elementArray.length; j++) {
5232		            var element = elementArray[j];
5233		            if (element.parentNode) {
5234		                //console.log(element);
5235		                element.parentNode.removeChild(element);
5236		            }
5237		        }
5238		    }
5239			//console.log('---- remove element end');
5240
5241
5242			/*** 取过滤完后的文本字符 ***/
5243			translateAreaText = virtualContainer.innerText;
5244			if(translateAreaText == null || typeof(translateAreaText) == 'undefined' || translateAreaText.length < 1){
5245				//未取到,默认赋予简体中文
5246				translate.language.local = 'chinese_simplified';
5247				return;
5248			}
5249			// 移除所有空白字符(包括空格、制表符、换行符等)
5250			translateAreaText = translateAreaText.replace(/\s/g, '');
5251
5252			//console.log('translateAreaText:\n'+translateAreaText);
5253			return translateAreaText;
5254		},
5255		//自动识别当前页面是什么语种
5256		autoRecognitionLocalLanguage:function(){
5257			if(translate.language.local != null && translate.language.local.length > 2){
5258				//已设置过了,不需要再设置
5259				return translate.language.local;
5260			}
5261
5262			var translateAreaText = translate.language.getTranslateAreaText();
5263
5264			//默认赋予简体中文
5265			translate.language.local = 'chinese_simplified';
5266			var recognition = translate.language.recognition(translateAreaText);
5267			//console.log(recognition);
5268			translate.language.local = recognition.languageName;
5269			return translate.language.local;
5270			/* v3.1优化
5271			var langs = new Array(); //上一个字符的语种是什么,当前字符向上数第一个字符。格式如 ['language']='english', ['chatstr']='a', ['storage_language']='english'  这里面有3个参数,分别代表这个字符属于那个语种,其字符是什么、存入了哪种语种的队列。因为像是逗号,句号,一般是存入本身语种中,而不是存入特殊符号中。 
5272			for(var i=0; i<bodyText.length; i++){
5273				var charstr = bodyText.charAt(i);
5274				var lang = translate.language.getCharLanguage(charstr);
5275				if(lang == ''){
5276					//未获取到,未发现是什么语言
5277					//continue;
5278					lang = 'unidentification';
5279				}
5280				langs.push(lang);
5281			}
5282
5283			//从数组中取出现频率最高的
5284			var newLangs = translate.util.arrayFindMaxNumber(langs);
5285
5286			//移除数组中的特殊字符
5287			var index = newLangs.indexOf('specialCharacter');
5288			if(index > -1){
5289				newLangs.splice(index,1); //移除数组中的特殊字符
5290			}
5291
5292			if(newLangs.length > 0){
5293				//找到排序出现频率最多的
5294				translate.language.local = newLangs[0];
5295			}else{
5296				//没有,默认赋予简体中文
5297				translate.language.local = 'chinese_simplified';
5298			}
5299			*/
5300		},
5301		
5302		/*
5303		 * 获取当前字符是什么语种。返回值是一个语言标识,有  chinese_simplified简体中文、japanese日语、korean韩语、
5304		 * str : node.nodeValue 或 图片的 node.alt 等
5305		 * 如果语句长,会全句翻译,以保证翻译的准确性,提高可读性。
5306		 * 如果语句短,会自动将特殊字符、要翻译的目标语种给过滤掉,只取出具体的要翻译的目标语种文本
5307		 *
5308		 * 返回 存放不同语言的数组,格式如
5309		 *  	[
5310					"english":[
5311						{beforeText: '', afterText: '', text: 'emoambue hag'},
5312						......
5313					],
5314					"japanese":[
5315						{beforeText: ' ', afterText: ' ', text: 'ẽ '},
5316						......
5317					]
5318		 		]
5319		 * 		
5320		 */
5321		get:function(str){
5322			//将str拆分为单个char进行判断
5323
5324			var langs = new Array(); //当前字符串包含哪些语言的数组,其内如 english
5325			var langStrs = new Array();	//存放不同语言的文本,格式如 ['english'][0] = 'hello'
5326			var upLangs = []; //上一个字符的语种是什么,当前字符向上数第一个字符。格式如 ['language']='english', ['chatstr']='a', ['storage_language']='english'  这里面有3个参数,分别代表这个字符属于那个语种,其字符是什么、存入了哪种语种的队列。因为像是逗号,句号,一般是存入本身语种中,而不是存入特殊符号中。 
5327			var upLangsTwo = []; //上二个字符的语种是什么 ,当前字符向上数第二个字符。 格式如 ['language']='english', ['chatstr']='a', ['storage_language']='english'  这里面有3个参数,分别代表这个字符属于那个语种,其字符是什么、存入了哪种语种的队列。因为像是逗号,句号,一般是存入本身语种中,而不是存入特殊符号中。
5328			
5329			//var upLangs = ''; //上一个字符的语种是什么,格式如 english
5330			for(var i=0; i<str.length; i++){
5331				var charstr = str.charAt(i);
5332				//console.log('charstr:'+charstr)
5333				var lang = translate.language.getCharLanguage(charstr);
5334				if(lang == ''){
5335					//未获取到,未发现是什么语言
5336					//continue;
5337					lang = 'unidentification';
5338				}
5339				var result = translate.language.analyse(lang, langStrs, upLangs, upLangsTwo, charstr);
5340				//console.log(result)
5341				langStrs = result['langStrs'];
5342				//记录上几个字符
5343				if(typeof(upLangs['language']) != 'undefined'){
5344					upLangsTwo['language'] = upLangs['language'];
5345					upLangsTwo['charstr'] = upLangs['charstr'];
5346					upLangsTwo['storage_language'] = upLangs['storage_language'];
5347				}
5348				//upLangs['language'] = lang;
5349				upLangs['language'] = result['storage_language'];
5350				upLangs['charstr'] = charstr;
5351				upLangs['storage_language'] = result['storage_language'];
5352				//console.log(result['storage_language'])
5353				//console.log(upLangs['language']);
5354				langs.push(lang);
5355			}
5356			
5357			//console.log(langStrs);
5358			
5359			//console.log(langs);
5360			//console.log(langStrs);
5361
5362/*
5363			//从数组中取出现频率最高的
5364			var newLangs = translate.util.arrayFindMaxNumber(langs);
5365			
5366			//移除当前翻译目标的语言。因为已经是目标预言了,不需要翻译了
5367			var index = newLangs.indexOf(translate.to);
5368			if(index > -1){
5369				newLangs.splice(index,1); //移除
5370			}
5371
5372			//移除特殊字符
5373			var index = newLangs.indexOf('specialCharacter');
5374			if(index > -1){
5375				newLangs.splice(index,1); //移除数组中的特殊字符
5376			}
5377
5378			if(newLangs.length > 0){
5379				//还剩一个或多个,(如果是多个,那应该是这几个出现的频率一样,所以取频率最高的时返回了多个)
5380				return newLangs[0];
5381			}else{
5382				//没找到,直接返回空字符串
5383				return '';
5384			}
5385			*/
5386			
5387			
5388			//去除特殊符号
5389			//for(var i = 0; i<langStrs.length; i++){
5390			/*
5391			var i = 0;
5392			for(var item in langStrs) {
5393				if(item == 'unidentification' || item == 'specialCharacter'){
5394					//langStrs.splice(i,1); //移除
5395					delete langStrs[item];
5396				}
5397				console.log(item);
5398				i++;
5399			}
5400			*/
5401			
5402			//console.log(langStrs);
5403			if(typeof(langStrs['unidentification']) != 'undefined'){
5404				delete langStrs['unidentification'];
5405			}
5406			if(typeof(langStrs['specialCharacter']) != 'undefined'){
5407				delete langStrs['specialCharacter'];
5408			}
5409			if(typeof(langStrs['number']) != 'undefined'){
5410				delete langStrs['number'];
5411			}
5412			
5413			
5414			//console.log('get end');
5415			return langStrs;
5416		},
5417		/*	
5418			语种识别策略
5419
5420			str 要识别的字符串 
5421			data 对于str字符串识别的结果,格式如:
5422				{
5423					languageName: 'english',
5424			 		languageArray:[
5425						english:[
5426							list[
5427								{beforeText: ' ', afterText: ' ', text: 'hello word'},
5428								{beforeText: ' ', afterText: ' ', text: 'who?'},
5429							],
5430							number:12
5431						],
5432						japanese:[
5433							......
5434						]
5435			 		]
5436			 	}
5437			 	有关这里面具体参数的说明,参考 translate.language.recognition 的说明
5438			languagesSize key:语言名, value:语言字符数
5439			allSize 当前所有发现的语种,加起来的总字符数,也就是 languagesSize 遍历所有的value相加的数
5440
5441			最后,要 return data;
5442		*/
5443		recognitionAlgorithm:function(str, data, languagesSize, allSize){
5444			
5445			/*
5446				如果英语跟罗曼语族(法语意大利语等多个语言)一起出现,且当前 data.languageName 认定是英语(也就是英文字符占比最大),那么要判定一下:
5447					如果 罗曼语族的字符数/英文的字符数 > 0.008 , 那么认为当前是罗曼语族的中的某个语种, 在对其判定出具体是罗曼语族中的哪个语种赋予最终结果。
5448			*/
5449			if(typeof(languagesSize['english']) != 'undefined' && typeof(languagesSize['romance']) != 'undefined' && data.languageName == 'english'){
5450				if(languagesSize['romance']/languagesSize['english'] > 0.008){
5451					//排定是罗曼语族了,那么判断一下到底是 法语、西班牙语、葡萄牙语、意大利语 中的哪一种呢
5452
5453					//先判定是否有设置本地语种是罗曼语族中其中的某一个
5454					if(typeof(translate.language.local) != 'undefined' && translate.language.local.length > 1){
5455						if(translate.language.englishVarietys.indexOf(translate.language.local) > -1){
5456							//发现当前设置的是小语种,那么将当前识别的语种识别为 本地设置的这个小语种。
5457							data.languageName = translate.language.local;
5458						}
5459					}
5460
5461					if(data.languageName == 'english'){
5462						//还是英语,那就是没有经过上面本地语种的判定,那进行罗曼语的具体语种识别
5463
5464						var romanceSentenceLanguage = translate.language.romanceSentenceAnaly(str);
5465						if(romanceSentenceLanguage.length == 0){
5466							translate.log('语种识别异常,应该是 法语、西班牙语、葡萄牙语、意大利语 中的一种才是,除非是除了这四种语种之外的别的 罗曼语族 中的语种,当前已将 '+ str +'识别为英语。 你可以联系我们求助 https://translate.zvo.cn/4030.html');
5467						}else{
5468							data.languageName = romanceSentenceLanguage;
5469						}
5470					}
5471				}
5472			}
5473
5474			
5475			/*
5476				日语判定
5477				如果发现日语存在,且当前 data.languageName 认定不是日语,那么要判定一下:
5478					如果 日语的字符数/所有字符数 的字符数 > 0.08 , 那么认为当前是日语的
5479			*/
5480			if( typeof(languagesSize['japanese']) != 'undefined' && data.languageName != 'japanese'){
5481				if(languagesSize['japanese']/allSize > 0.08){
5482					data.languageName = 'japanese'
5483				}
5484			}
5485
5486			/*
5487				如果发现英语、简体中文或繁体中文 一起存在,且当前 data.languageName 认定是英语时,那么要判定一下:
5488					如果 (简体中文+繁体中文)的字符数/英语 > 0.05 , 那么认为当前是简体中文(不认为是繁体中文,因为下面还有 简体中文跟繁体中文的判定)
5489			*/
5490			if( (typeof(languagesSize['chinese_simplified']) != 'undefined' || typeof(languagesSize['chinese_traditional']) != 'undefined' ) && typeof(languagesSize['english']) != 'undefined' && data.languageName == 'english'){
5491				var size = 0;
5492				if(typeof(languagesSize['chinese_simplified']) != 'undefined'){
5493					size = size + languagesSize['chinese_simplified'];
5494				}
5495				if(typeof(languagesSize['chinese_traditional']) != 'undefined'){
5496					size = size + languagesSize['chinese_traditional'];
5497				}
5498				if(size/languagesSize['english'] > 0.05){
5499					data.languageName = 'chinese_simplified'
5500				}
5501			}
5502
5503
5504			/*
5505				如果简体中文跟繁体中文一起出现,且当前 data.languageName 认定是简体中文(也就是简体中文字符占比最大),那么要判定一下繁体中文:
5506					如果 繁体中文的字符数/简体中文的字符数 > 0.08 , 那么认为当前是繁体中文的
5507			*/
5508			if(typeof(languagesSize['chinese_simplified']) != 'undefined' && typeof(languagesSize['chinese_traditional']) != 'undefined' && data.languageName == 'chinese_simplified'){
5509				if(languagesSize['chinese_traditional']/languagesSize['chinese_simplified'] > 0.03){
5510					data.languageName = 'chinese_traditional'
5511				}
5512			}
5513			/* if(langkeys.indexOf('chinese_simplified') > -1 && langkeys.indexOf('chinese_traditional') > -1){
5514				langsNumber['chinese_simplified'] = 0;
5515			} */
5516
5517
5518
5519			return data;
5520		},
5521		/*
5522			强制识别,这里目前咱是配合  translate.language.translateLocal 使用,当它为true时才会进行强制识别,不管比例,只要出现字符,就强制识别。 
5523			这个也是只能有大模型翻译、自动识别语种的翻译才使用它。
5524
5525			比如: translate.language.recognition('你 @¿Lo que introduzco ahora es contenido español, ¿ se puede traducir en chino? Este texto está en inglés. Si desea ')
5526			会被识别为西班牙语,因为按照上面的逻辑,简体中文占得比重太小了,而西班牙语占比重大,所以是西班牙语。
5527			但是如果当前要翻译为的语种是西班牙语,那么它根据比例识别出也是西班牙语,这句话是不会被翻译的,但是用户实际看上去,却是有显示 '你' 这个中文文字,是不合适的,所以不管是出现了多小的比重,都不能在含有 中文、日语的字符,不然不符合用户以西班牙语阅读的习惯,即使显示单个中文单词,那也属于刺眼的
5528		
5529			返回:
5530			当前 recognition 结果识别的语种,比如 english ,translate.js 的语言标识
5531		*/
5532		recognition_languageName_force:function(recognition_result){
5533
5534			//未启用,那用 recognition 的结果
5535			if(!translate.language.translateLocal){
5536				return recognition_result.languageName;
5537			}
5538			if(translate.language.getLocal() == translate.language.getCurrent() && translate.language.translateLocal === false){
5539				//如果本地语种跟当前语种一致,且不进行强制翻译,那么肯定就不进行翻译的,直接原样返回
5540				return recognition_result.languageName;
5541			}else{
5542				//其他的情况就是要翻译了
5543
5544				//当前语种
5545				var currentLanguage = translate.language.getCurrent(); 
5546				
5547				if(typeof(translate.language.map().get(currentLanguage)) != 'undefined' && typeof(translate.language.map().get(translate.language.getLocal())) != 'undefined'){
5548					//本地语种跟当前语种都是有语族的
5549					
5550					/*
5551						语族 ,当前文本中的文字包含多少语种
5552						key 是语族的名字,如 latin (如果 translate.language.map().get 中没有取到语族,那么这里就直接去掉)
5553						value 是具体的语种名字,如 english 。 这里比如字符串中有英语也有西班牙语,那这里只会记录其中一个,因为主要记录的是key语族的名字
5554					*/
5555					var languageSystem = {}; 	
5556					//遍历当前有的语种
5557					for (var language in recognition_result.languageArray) {
5558						// 必须加 hasOwnProperty 检查,避免遍历原型链上的属性
5559						if (!recognition_result.languageArray.hasOwnProperty(language)) {
5560					  		continue;
5561						}
5562
5563						if(typeof(translate.language.map().get(language)) != 'undefined'){
5564					  		languageSystem[translate.language.map().get(language).system] = language;
5565						}
5566					}
5567
5568					if(translate.language.map().get(currentLanguage).system == 'latin'){
5569						//要以拉丁语族显示,那如果其中字符有 chinese 语族的,那么要把这个语族的全部翻译
5570						
5571						delete languageSystem['latin'];
5572						var yuzuArray = Object.keys(languageSystem);
5573						if(yuzuArray.length > 0){
5574							//发现还有其他语族的,那么以其他语族为主,目的是能一起翻译,那么直接返回第一个语族名即可
5575							return languageSystem[yuzuArray[0]];
5576						}
5577					}
5578				}
5579			}
5580
5581			//其他那就都是用 recognition 的结果
5582			return recognition_result.languageName;
5583		},
5584
5585		/*
5586		 * 识别字符串是什么语种。它是 get() 的扩展,以代替get返回更多
5587		 * str : 要识别的字符串
5588		 *
5589		 * 返回 存放不同语言的数组,格式如
5590		 *  	
5591			{
5592				languageName: 'english',
5593		 		languageArray:[
5594					english:[
5595						list[
5596							{beforeText: ' ', afterText: ' ', text: 'hello word'},
5597							{beforeText: ' ', afterText: ' ', text: 'who?'},
5598						],
5599						number:12
5600					],
5601					japanese:[
5602						......
5603					]
5604		 		]
5605		 	}
5606		 	languageName 是当前字符串最终判定结果是什么语种。它的识别有以下特点:
5607		 		1. 如果出现英语跟中文、罗曼语族、德语等混合的情况,也就是不纯粹英语的情况,那么会以其他语种为准,而不是识别为英语。不论英语字符出现的比例占多少。
5608		 		2. 如果出现简体中文跟繁体中文混合的情况,那么识别为繁体中文。不论简体中文字符出现的比例占多少。
5609		 		3. 如果出现简体中文、繁体中文、日语混合的情况,那么识别为日语。不论简体中文、繁体中文出现的比例占多少。 2025.4.19 增加
5610				4. 除了以上两种规则外,如果出现了多个语种,那么会识别为出现字符数量最多的语种当做当前句子的语种。(注意是字符数,而不是语种的数组数)
5611			languageArray 对传入字符串进行分析,识别出都有哪些语种,每个语种的字符是什么
5612		 * 		
5613		 */
5614		recognition:function(str){
5615			var langs = translate.language.get(str);
5616			//var langkeys = Object.keys(langs);
5617			//console.log(langkeys);
5618			var langsNumber = []; //key  语言名,  value 语言字符数
5619			var langsNumberOriginal = []; //同上,只不过这个不会进行清空字符数
5620			var allNumber = 0;//总字数
5621
5622			/** 进行字数统计相关 - start **/
5623			for(var key in langs){
5624				if (!langs.hasOwnProperty(key)) {
5625		    		continue;
5626		    	}
5627				if(typeof(langs[key]) != 'object'){
5628					continue;
5629				}
5630				var langStrLength = 0;
5631				for(var ls = 0; ls < langs[key].length; ls++){
5632					langStrLength = langStrLength + langs[key][ls].text.length;
5633				}
5634				allNumber = allNumber + langStrLength;
5635				langsNumber[key] = langStrLength;
5636				langsNumberOriginal[key] = langStrLength;
5637			}
5638			/** 进行字数统计相关 - end **/
5639
5640
5641
5642			//从 langsNumber 中找出字数最多的来
5643			var maxLang = ''; //字数最多的语种
5644			var maxNumber = 0;
5645			for(var lang in langsNumber){
5646				if (!langsNumber.hasOwnProperty(lang)) {
5647		    		continue;
5648		    	}
5649				if(langsNumber[lang] > maxNumber){
5650					maxLang = lang;
5651					maxNumber = langsNumber[lang];
5652				}
5653			}
5654
5655			//重新组合返回值的 languageArray
5656			var languageArray = {};
5657			for(var lang in langs){
5658				if (!langs.hasOwnProperty(lang)) {
5659		    		continue;
5660		    	}
5661				languageArray[lang] = {};
5662				languageArray[lang].number = langsNumberOriginal[lang];
5663				languageArray[lang].list = langs[lang];
5664			}
5665
5666			var result = {
5667				languageName: maxLang,
5668				languageArray: languageArray
5669			};
5670			
5671			//最后进行一层简单的算法处理
5672			return translate.language.recognitionAlgorithm(str, result, langsNumber, allNumber);
5673		},
5674		/*
5675			传入一个char,返回这个char属于什么语种,返回如   如果返回空字符串,那么表示未获取到是什么语种
5676			chinese_simplified 简体中文
5677			chinese_traditional 繁体中文
5678			russian 俄罗斯语
5679			english 英语
5680			romance 罗曼语族,它是 法语、西班牙语、意大利语、葡萄牙語 的集合,并不是单个语言
5681			specialCharacter 特殊字符,符号
5682			number 阿拉伯数字
5683			japanese 日语
5684			korean 韩语
5685			greek 希腊语
5686			thai 泰语
5687			arabic 阿拉伯语
5688			romanian 罗马尼亚语
5689			hebrew 希伯来语
5690
5691		*/
5692		getCharLanguage:function(charstr){
5693			if(charstr == null || typeof(charstr) == 'undefined'){
5694				return '';
5695			}
5696			
5697			if(this.russian(charstr)){
5698				return 'russian';
5699			}
5700			if(this.english(charstr)){
5701				return 'english';
5702			}
5703			if(this.romance(charstr)){
5704				return 'romance';
5705			}
5706			if(this.specialCharacter(charstr)){
5707				return 'specialCharacter';
5708			}
5709			if(this.number(charstr)){
5710				return 'number';	
5711			}
5712
5713			//中文的判断包含两种,简体跟繁体
5714			var chinesetype = this.chinese(charstr);
5715			if(chinesetype == 'simplified'){
5716				return 'chinese_simplified';
5717			}else if(chinesetype == 'traditional'){
5718				return 'chinese_traditional';
5719			}
5720
5721			if(this.japanese(charstr)){
5722				return 'japanese';
5723			}
5724			if(this.korean(charstr)){
5725				return 'korean';
5726			}
5727			if(this.greek(charstr)){
5728				return 'greek';
5729			}
5730			if(this.thai(charstr)){
5731				return 'thai';
5732			}
5733			if(this.arabic(charstr)){
5734				return 'arabic';
5735			}
5736			if(this.romanian(charstr)){
5737				return 'romanian';
5738			}
5739			if(this.hebrew(charstr)){
5740				return 'hebrew';
5741			}
5742			//未识别是什么语种
5743			//console.log('not find is language , char : '+charstr+', unicode: '+charstr.charCodeAt(0).toString(16));
5744			return '';
5745			
5746		},
5747		/*
5748		 * 对字符串进行分析,分析字符串是有哪几种语言组成。
5749		 * language : 当前字符的语种,传入如 english
5750		 * langStrs : 操作的,如 langStrs['english'][0] = '你好'
5751		 * upLangs  : 当前字符之前的上一个字符的语种是什么,当前字符向上数第一个字符。格式如 ['language']='english', ['chatstr']='a', ['storage_language']='english'  这里面有3个参数,分别代表这个字符属于那个语种,其字符是什么、存入了哪种语种的队列。因为像是逗号,句号,一般是存入本身语种中,而不是存入特殊符号中。
5752		 * upLangsTwo : 当前字符之前的上二个字符的语种是什么 ,当前字符向上数第二个字符。 格式如 ['language']='english', ['chatstr']='a', ['storage_language']='english'  这里面有3个参数,分别代表这个字符属于那个语种,其字符是什么、存入了哪种语种的队列。因为像是逗号,句号,一般是存入本身语种中,而不是存入特殊符号中。
5753		 * chatstr  : 当前字符,如  h
5754		 */
5755		analyse:function(language, langStrs, upLangs, upLangsTwo, charstr){
5756			if(typeof(langStrs[language]) == 'undefined'){
5757				langStrs[language] = new Array();
5758			}
5759			var index = 0; //当前要存入的数组下标
5760			if(typeof(upLangs['storage_language']) == 'undefined'){
5761				//第一次,那么还没存入值,index肯定为0
5762				//console.log('第一次,那么还没存入值,index肯定为0')
5763				//console.log(upLangs['language'])
5764			}else{
5765				//console.log('analyse, charstr : '+charstr+', upLangs :');
5766				//console.log(upLangs);
5767				//var isEqual = upLangs['storage_language'] == language; //上次跟当前字符是否都是同一个语种(这个字符跟这个字符前一个字符)
5768
5769				/*
5770					英语每个单词之间都会有空格分割. 如果是英文的话,英文跟特殊字符还要单独判断一下,避免拆开,造成翻译不准,单个单词翻译的情况
5771					所以如果上次的字符是英文或特殊符号,当前字符是特殊符号(逗号、句号、空格,然后直接笼统就吧特殊符号都算上吧),那么也将当次的特殊符号变为英文来进行适配
5772					示例  
5773						hello word  的 "o w"
5774						hello  word  的 "  w"
5775						hello  word  的 "w  "
5776						this is a dog  的 " a "
5777				*/
5778				//console.log(language == 'specialCharacter');
5779				//如果两个字符类型不一致,但当前字符是英文或连接符时,进行判断
5780				/*
5781				if(!isEqual){
5782					if(language == 'english' || translate.language.connector(charstr)){
5783						console.log('1.'+(language == 'english' || translate.language.connector(charstr))+', upLangs str:'+upLangs['charstr']);
5784						//上一个字符是英文或连接符
5785						//console.log('teshu:'+translate.language.connector(upLangs['charstr'])+', str:'+upLangs['charstr']);
5786						if(upLangs['language'] == 'english' || translate.language.connector(upLangs['charstr'])) {
5787							console.log('2');
5788							//如果上二个字符不存在,那么刚开始,不再上面几种情况之中,直接不用考虑
5789							if(typeof(upLangsTwo['language']) != 'undefined'){
5790								console.log('3')
5791								//上二个字符是空(字符串刚开始),或者是英文
5792								if(upLangsTwo['language'] == 'english' || translate.language.connector(upLangsTwo['charstr'])){
5793									//满足这三个条件,那就将这三个拼接到一起
5794									console.log('4/5: '+', two lang:'+upLangsTwo['language']+', str:'+upLangsTwo['charstr'])
5795									isEqual = true;
5796									if(language == 'specialCharacter' && upLangs['language'] == 'specialCharacter' && upLangsTwo['language'] == 'specialCharacter'){
5797										//如果三个都是特殊字符,或后两个是特殊字符,第一个是空(刚开始),那就归入特殊字符
5798										language = 'specialCharacter';
5799										//console.log('4')
5800									}else{
5801										//不然就都归于英文中。
5802										//这里更改是为了让下面能将特殊字符(像是空格逗号等)也一起存入数组
5803										language = 'english';
5804										console.log(5)
5805									}
5806								}
5807							}
5808						}
5809					}
5810				}
5811				*/
5812
5813				/*
5814					不判断当前字符,而判断上个字符,是因为当前字符没法获取未知的下个字符。
5815				*/
5816				//if(!isEqual){
5817
5818					//如果当前字符是连接符
5819					if(translate.language.connector(charstr)){
5820						language = upLangs['storage_language'];
5821						/*
5822						//判断上个字符是否存入了待翻译字符,如要将中文翻译为英文,而上个字符是中文,待翻译,那将连接符一并加入待翻译字符中去,保持句子完整性
5823						//判断依据是上个字符存储至的翻译字符语种序列,不是特殊字符,而且也不是要翻译的目标语种,那肯定就是待翻译的,将连接符加入待翻译中一起进行翻译
5824						if(upLangs['storage_language'] != 'specialCharacter' && upLangs['storage_language'] != translate.to){
5825							
5826							language = upLangs['storage_language'];
5827							console.log('teshu:'+charstr+', 当前字符并入上个字符存储翻译语种:'+upLangs['storage_language']);
5828						}
5829						*/
5830					}
5831				//}
5832
5833				//console.log('isEqual:'+isEqual);
5834				/*
5835				if(isEqual){
5836					//跟上次语言一样,那么直接拼接
5837					index = langStrs[language].length-1; 
5838					//但是还有别的特殊情况,v2.1针对英文翻译准确度的适配,会有特殊字符的问题
5839					if(typeof(upLangs['storage_language']) != 'undefined' && upLangs['storage_language'] != language){
5840						//如果上个字符存入的翻译队列跟当前这个要存入的队列不一个的话,那应该是特殊字符像是逗号句号等导致的,那样还要额外一个数组,不能在存入之前的数组了
5841						index = langStrs[language].length; 
5842					}
5843				}else{
5844					//console.log('新开');
5845					//当前字符跟上次语言不样,那么新开一个数组
5846					index = langStrs[language].length;
5847					//console.log('++, inde:'+index+',lang:'+language+', length:'+langStrs[language].length)
5848				}
5849				*/
5850
5851				//当前要翻译的语种跟上个字符要翻译的语种一样,那么直接拼接
5852				if(upLangs['storage_language'] == language){
5853					index = langStrs[language].length-1; 
5854				}else{
5855					//console.log('新开');
5856					//当前字符跟上次语言不样,那么新开一个数组
5857					index = langStrs[language].length;
5858				}
5859			}
5860			if(typeof(langStrs[language][index]) == 'undefined'){
5861				langStrs[language][index] = new Array();
5862				langStrs[language][index]['beforeText'] = '';
5863				langStrs[language][index]['afterText'] = '';
5864				langStrs[language][index]['text'] = '';
5865			}
5866			langStrs[language][index]['text'] = langStrs[language][index]['text'] + charstr;
5867			/*
5868				中文英文混合时,当中文+英文并没有空格间隔,翻译为英文时,会使中文翻译英文的结果跟原本的英文单词连到一块。这里就是解决这种情况
5869				针对当前非英文(不需要空格分隔符,像是中文、韩语),但要翻译为英文(需要空格作为分割符号,像是法语等)时的情况进行判断
5870			*/
5871			//if(translate.language.getLocal() != 'english' && translate.to == 'english'){
5872			//当前本地语种的语言是连续的,但翻译的目标语言不是连续的(空格间隔)
5873			if( translate.language.wordBlankConnector(translate.language.getLocal()) == false && translate.language.wordBlankConnector(translate.to)){	
5874				if((upLangs['storage_language'] != null && typeof(upLangs['storage_language']) != 'undefined' && upLangs['storage_language'].length > 0)){
5875					//上个字符存在
5876					//console.log(upLangs['storage_language']);
5877					if(upLangs['storage_language'] != 'specialCharacter'){
5878						//上个字符不是特殊字符 (是正常语种。且不会是连接符,连接符都并入了正常语种)
5879
5880						//if( upLangs['storage_language'] != 'english' && language == 'english'){
5881						//上个字符的语言是连续的,但当前字符的语言不是连续的(空格间隔)
5882						if( translate.language.wordBlankConnector(upLangs['storage_language']) == false && translate.language.wordBlankConnector(language) ){
5883							//上个字符不是英语,当前字符是英语,这种情况要在上个字符后面追加空格,因为当前字符是英文,就不会在执行翻译操作了
5884							//console.log(upLangs['language']);
5885							langStrs[upLangs['storage_language']][langStrs[upLangs['storage_language']].length-1]['afterText'] = ' ';
5886						}else if(upLangs['storage_language'] == 'english' && language != 'english'){
5887							//上个字符是英语,当前字符不是英语,直接在当前字符前面追加空格
5888							langStrs[language][index]['beforeText'] = ' ';
5889						}
5890					}
5891
5892					
5893				}
5894			}
5895
5896			var result = new Array();
5897			result['langStrs'] = langStrs;
5898			result['storage_language'] = language;	//实际存入了哪种语种队列
5899			//console.log(result);
5900			//console.log(langStrs)
5901			//console.log(charstr);
5902			return result;
5903		},
5904		
5905		/*
5906		 * 不同于语言,这个只是单纯的连接符。比如英文单词之间有逗号、句号、空格, 汉字之间有逗号句号书名号的。避免一行完整的句子被分割,导致翻译不准确
5907		 * 单独拿他出来,目的是为了更好的判断计算,提高翻译的准确率
5908		 */
5909		connector:function(str){
5910			
5911			/*
5912				通用的有 空格、阿拉伯数字
5913				1.不间断空格\u00A0,主要用在office中,让一个单词在结尾处不会换行显示,快捷键ctrl+shift+space ;
5914				2.半角空格(英文符号)\u0020,代码中常用的;
5915				3.全角空格(中文符号)\u3000,中文文章中使用; 
5916			*/	
5917			if(/.*[\u0020\u00A0\u202F\u205F\u3000]+.*$/.test(str)){
5918				return true;
5919			}
5920			/*
5921				U+0030 0 数字 0
5922				U+0031 1 数字 1
5923				U+0032 2 数字 2
5924				U+0033 3 数字 3
5925				U+0034 4 数字 4
5926				U+0035 5 数字 5
5927				U+0036 6 数字 6
5928				U+0037 7 数字 7
5929				U+0038 8 数字 8
5930				U+0039 9 数字 9
5931			*/
5932			if(/.*[\u0030-\u0039]+.*$/.test(str)){ 
5933				return true
5934			}
5935			
5936		
5937			/*
5938				英文场景
5939				英文逗号、句号
5940				这里不包括() 因为这里面的基本属于补充,对语句前后并无强依è
5940µ–关系
5941				
5942				U+0021 ! 叹号
5943				U+0022 " 双引号
5944				U+0023 # 井号
5945				U+0024 $ 价钱/货币符号
5946				U+0025 % 百分比符号
5947				U+0026 & 英文“and”的简写符号
5948				U+0027 ' 引号
5949				U+002C , 逗号
5950				U+002D - 连字号/减号
5951				U+002E . 句号
5952				U+003A : 冒号
5953				U+003B ; 分号
5954				U+003F ? 问号
5955				U+0040 @ 英文“at”的简写符号
5956
5957
5958			*/
5959			if(/.*[\u0021\u0022\u0023\u0024\u0025\u0026\u0027\u002C\u002D\u002E\u003A\u003B\u003F\u0040]+.*$/.test(str)){
5960				return true;
5961			}
5962			
5963			/*
5964				中文标点符号
5965				名称	Unicode	符号
5966				句号	3002	。
5967				问号	FF1F	?
5968				叹号	FF01	!
5969				逗号	FF0C	,
5970				顿号	3001	、
5971				分号	FF1B	;
5972				冒号	FF1A	:
5973				引号	300C	「
5974				 	300D	」
5975				引号	300E	『
5976				 	300F	』
5977				引号	2018	‘
5978				 	2019	’
5979				引号	201C	“
5980				 	201D	”
5981				括号	FF08	(
5982				 	FF09	)
5983				括号	3014	〔
5984				 	3015	〕
5985				括号	3010	【
5986				 	3011	】
5987				破折号	2014	—
5988				省略号	2026	…
5989				连接号	2013	–
5990				间隔号	FF0E	.
5991				书名号	300A	《
5992				 	300B	》
5993				书名号	3008	〈
5994				 	3009	〉
5995				键盘123前面的那个符号 · 00b7
5996			*/
5997			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)){
5998				return true;
5999			}
6000
6001
6002
6003			
6004			//不是,返回false
6005			return false;
6006		},
6007		//语种的单词连接符是否需要空格,比如中文简体、繁体、韩文、日语都不需要空格,则返回false, 但是像是英文的单词间需要空格进行隔开,则返回true
6008		//另外这也是区分是否使用标点符号 ,。还是 ,. 的
6009		//如果未匹配到,默认返回true
6010		//language:语种,传入如  english
6011		wordBlankConnector:function(language){
6012			if(language == null || typeof(language) == 'undefined'){
6013				return true;
6014			}
6015			switch (language.trim().toLowerCase()){
6016		  		case 'chinese_simplified':
6017		  			return false;
6018		  		case 'chinese_traditional':
6019		  			return false;
6020		  		case 'korean':
6021		  			return false;
6022		  		case 'japanese':
6023		  			return false;
6024		  	}
6025		  	//其他情况则返回true
6026		  	return true;
6027		},
6028		//繁体中文的字典,判断繁体中文就是通过此判断
6029		chinese_traditional_dict: '皚藹礙愛翺襖奧壩罷擺敗頒辦絆幫綁鎊謗剝飽寶報鮑輩貝鋇狽備憊繃筆畢斃閉邊編貶變辯辮鼈癟瀕濱賓擯餅撥缽鉑駁蔔補參蠶殘慚慘燦蒼艙倉滄廁側冊測層詫攙摻蟬饞讒纏鏟産闡顫場嘗長償腸廠暢鈔車徹塵陳襯撐稱懲誠騁癡遲馳恥齒熾沖蟲寵疇躊籌綢醜櫥廚鋤雛礎儲觸處傳瘡闖創錘純綽辭詞賜聰蔥囪從叢湊竄錯達帶貸擔單鄲撣膽憚誕彈當擋黨蕩檔搗島禱導盜燈鄧敵滌遞締點墊電澱釣調諜疊釘頂錠訂東動棟凍鬥犢獨讀賭鍍鍛斷緞兌隊對噸頓鈍奪鵝額訛惡餓兒爾餌貳發罰閥琺礬釩煩範販飯訪紡飛廢費紛墳奮憤糞豐楓鋒風瘋馮縫諷鳳膚輻撫輔賦複負訃婦縛該鈣蓋幹趕稈贛岡剛鋼綱崗臯鎬擱鴿閣鉻個給龔宮鞏貢鈎溝構購夠蠱顧剮關觀館慣貫廣規矽歸龜閨軌詭櫃貴劊輥滾鍋國過駭韓漢閡鶴賀橫轟鴻紅後壺護滬戶嘩華畫劃話懷壞歡環還緩換喚瘓煥渙黃謊揮輝毀賄穢會燴彙諱誨繪葷渾夥獲貨禍擊機積饑譏雞績緝極輯級擠幾薊劑濟計記際繼紀夾莢頰賈鉀價駕殲監堅箋間艱緘繭檢堿鹼揀撿簡儉減薦檻鑒踐賤見鍵艦劍餞漸濺澗漿蔣槳獎講醬膠澆驕嬌攪鉸矯僥腳餃繳絞轎較稭階節莖驚經頸靜鏡徑痙競淨糾廄舊駒舉據鋸懼劇鵑絹傑潔結誡屆緊錦僅謹進晉燼盡勁荊覺決訣絕鈞軍駿開凱顆殼課墾懇摳庫褲誇塊儈寬礦曠況虧巋窺饋潰擴闊蠟臘萊來賴藍欄攔籃闌蘭瀾讕攬覽懶纜爛濫撈勞澇樂鐳壘類淚籬離裏鯉禮麗厲勵礫曆瀝隸倆聯蓮連鐮憐漣簾斂臉鏈戀煉練糧涼兩輛諒療遼鐐獵臨鄰鱗凜賃齡鈴淩靈嶺領餾劉龍聾嚨籠壟攏隴樓婁摟簍蘆盧顱廬爐擄鹵虜魯賂祿錄陸驢呂鋁侶屢縷慮濾綠巒攣孿灤亂掄輪倫侖淪綸論蘿羅邏鑼籮騾駱絡媽瑪碼螞馬罵嗎買麥賣邁脈瞞饅蠻滿謾貓錨鉚貿麽黴沒鎂門悶們錳夢謎彌覓綿緬廟滅憫閩鳴銘謬謀畝鈉納難撓腦惱鬧餒膩攆撚釀鳥聶齧鑷鎳檸獰甯擰濘鈕紐膿濃農瘧諾歐鷗毆嘔漚盤龐國愛賠噴鵬騙飄頻貧蘋憑評潑頗撲鋪樸譜臍齊騎豈啓氣棄訖牽扡釺鉛遷簽謙錢鉗潛淺譴塹槍嗆牆薔強搶鍬橋喬僑翹竅竊欽親輕氫傾頃請慶瓊窮趨區軀驅齲顴權勸卻鵲讓饒擾繞熱韌認紉榮絨軟銳閏潤灑薩鰓賽傘喪騷掃澀殺紗篩曬閃陝贍繕傷賞燒紹賒攝懾設紳審嬸腎滲聲繩勝聖師獅濕詩屍時蝕實識駛勢釋飾視試壽獸樞輸書贖屬術樹豎數帥雙誰稅順說碩爍絲飼聳慫頌訟誦擻蘇訴肅雖綏歲孫損筍縮瑣鎖獺撻擡攤貪癱灘壇譚談歎湯燙濤縧騰謄銻題體屜條貼鐵廳聽烴銅統頭圖塗團頹蛻脫鴕馱駝橢窪襪彎灣頑萬網韋違圍爲濰維葦偉僞緯謂衛溫聞紋穩問甕撾蝸渦窩嗚鎢烏誣無蕪吳塢霧務誤錫犧襲習銑戲細蝦轄峽俠狹廈鍁鮮纖鹹賢銜閑顯險現獻縣餡羨憲線廂鑲鄉詳響項蕭銷曉嘯蠍協挾攜脅諧寫瀉謝鋅釁興洶鏽繡虛噓須許緒續軒懸選癬絢學勳詢尋馴訓訊遜壓鴉鴨啞亞訝閹煙鹽嚴顔閻豔厭硯彥諺驗鴦楊揚瘍陽癢養樣瑤搖堯遙窯謠藥爺頁業葉醫銥頤遺儀彜蟻藝億憶義詣議誼譯異繹蔭陰銀飲櫻嬰鷹應纓瑩螢營熒蠅穎喲擁傭癰踴詠湧優憂郵鈾猶遊誘輿魚漁娛與嶼語籲禦獄譽預馭鴛淵轅園員圓緣遠願約躍鑰嶽粵悅閱雲鄖勻隕運蘊醞暈韻雜災載攢暫贊贓髒鑿棗竈責擇則澤賊贈紮劄軋鍘閘詐齋債氈盞斬輾嶄棧戰綻張漲帳賬脹趙蟄轍鍺這貞針偵診鎮陣掙睜猙幀鄭證織職執紙摯擲幟質鍾終種腫衆謅軸皺晝驟豬諸誅燭矚囑貯鑄築駐專磚轉賺樁莊裝妝壯狀錐贅墜綴諄濁茲資漬蹤綜總縱鄒詛組鑽緻鐘麼為隻兇準啟闆裡靂餘鍊',
6030		/*
6031			中文判断
6032			返回:
6033				simplified:简体中文
6034				traditional:繁体中文
6035				空字符串:不是中文
6036		*/   
6037		chinese:function(str){
6038			if(/.*[\u4e00-\u9fa5]+.*$/.test(str)){ 
6039				if(this.chinese_traditional_dict.indexOf(str) > -1){ 
6040					return 'traditional';
6041				} else {
6042					return 'simplified';
6043				}
6044			} else {
6045				return '';
6046			}
6047		},
6048		//是否包含日语,true:包含
6049		japanese:function(str){
6050			if(/.*[\u3040-\u309F\u30A0-\u30FF]+.*$/.test(str)){ 
6051				return true
6052			} else {
6053				return false;
6054			}
6055		},
6056		//是否包含韩语,true:包含
6057		korean:function(str){
6058			if(/.*[\uAC00-\uD7AF]+.*$/.test(str)){ 
6059				return true
6060			} else {
6061				return false;
6062			}
6063		},
6064		//是否包含俄语
6065		russian:function(str){
6066			// 正则表达式匹配俄语大小写字母(包含 Ё/ё,排除其他语言特有的西里尔字符)
6067			//АаБбВвГгДдЕеЁёЖжЗзИиЙйКкЛлМмНнОоПпРрСсТтУуФфХхЦцЧчШшЩщЪъЫыЬьЮюЯя
6068			//if(/^[А-Яа-яЁё]$/.test(str)){ 
6069			if(/^[\u0410-\u044F\u0401\u0451]$/.test(str)){ 	
6070				return true
6071			} else {
6072				return false;
6073			}
6074		},
6075		//是否包含泰语
6076		thai:function(str){
6077			if(/^[\u0E01-\u0E59]$/.test(str)){ 
6078				return true
6079			} else {
6080				return false;
6081			}
6082		},
6083		//是否包含阿拉伯语
6084		arabic:function(str){
6085			/*
6086				阿拉伯语基本区块(U+0600–U+06FF)
6087				阿拉伯语补充区块(U+0750–U+077F)
6088			*/
6089			return /^[\u0600-\u06FF\u0750-\u077F]$/.test(str);
6090		},
6091		//是否包含 罗马尼亚语
6092		romanian:function(str) {
6093			/*
6094				U+00C0–U+00FF:Latin-1 Supplement (包含带变音符号的字母,如 Ă/ă 的部分形式)
6095				U+0100–U+017F:Latin Extended-A (包含罗马尼亚语特有字母 Ă/ă、Â/â、Î/î 等);
6096				U+0218–U+021B:Latin Extended-B (包含 Ș/ș 和 Ț/ț,这是罗马尼亚语标志性字母)
6097			*/
6098		    return /^[\u00C0-\u00FF\u0100-\u017F\u0218-\u021B]$/.test(str);
6099		},
6100		//是否包含希腊语
6101		greek:function(str){
6102			const greekRegex = /^[\u0391-\u03A9\u03B1-\u03C9]$/;
6103			//判断字符有  БВДЖЗИЙЛМНОПСТУФХЦЧШЩЪЫЬЮЯЇІ
6104			if(/^[\u0391-\u03A9\u03B1-\u03C9]$/.test(str)){ 
6105				return true
6106			} else {
6107				return false;
6108			}
6109		},
6110		//希伯来语
6111		hebrew:function(str){
6112			return /^[\u0590-\u05FF]$/.test(str);
6113		},
6114		//0-9 阿拉伯数字
6115		number:function(str){
6116			if(/.*[\u0030-\u0039]+.*$/.test(str)){
6117				return true;
6118			}
6119			return false;
6120		},
6121		//是否包含英文,true:包含
6122		english:function(str){
6123			if(/.*[\u0041-\u005a]+.*$/.test(str)){ 
6124				return true;
6125			} else if(/.*[\u0061-\u007a]+.*$/.test(str)){
6126				return true;
6127			} else {
6128				return false;
6129			}
6130		},
6131		//是否包含 罗曼语族 的特殊字符,因为 法语、西班牙语、意大利语、葡萄牙語  都属于这个语族,单纯判断特殊字符已经不能判断出到底属于哪个语种了
6132		romance_dict:['é','è','ê','à','ç','œ','ñ','á','ó','ò','ì','ã','õ'],
6133		romance:function(str){
6134			if(this.romance_dict.indexOf(str) > -1){ 
6135				return true;
6136			} else {
6137				return false;
6138			}
6139		},
6140		//对 罗曼语族 的句子进行分析,看它是属于 法语、西班牙语、意大利语、葡萄牙語 的哪个。注意这个是传入的整体的句子,不是传入的单个字符
6141		//返回识别的语种:  french、spanish、italian、portuguese  如果都没有识别出来,则返回空字符串
6142		romanceSentenceAnaly:function(text) {
6143		    // 定义各语言的典型字母/符号权重 (可调整)
6144		    const langFeatures = {
6145		        'french': { score:0 , chars: ['é','è','ê','à','ç','œ'] },
6146		        'spanish': { score:0 , chars: ['ñ','á','ó'], pairs: ['ll'] },
6147		        'italian': { score:0 , chars: ['ò','ì'], pairs: ['cc', 'ss'] },
6148		        'portuguese': { score:0 , chars: ['ã', 'õ'] }
6149		    };
6150
6151		    // 逐字扫描 + 相邻配对检测
6152		    for (let i=0; i<text.length; i++) {
6153		        const char = text[i].toLowerCase(); 
6154		        
6155		        // 单字匹配
6156		        Object.keys(langFeatures).forEach(lang => {
6157		            if (langFeatures[lang].chars.includes(char)) {
6158		                langFeatures[lang].score +=1;
6159		            }
6160		        });
6161
6162		        // 双字配对检测 (如 ll)
6163		        if(i < text.length -1) {
6164		            const pair = text.slice(i,i+2).toLowerCase();
6165		            Object.keys(langFeatures).forEach(lang => {
6166		            	const pairs = langFeatures[lang].pairs;
6167				        if (pairs && pairs.includes(pair)) {
6168				            langFeatures[lang].score += 2; // pair权重大于单字
6169				        }
6170		            });
6171		         }
6172		    }
6173
6174		    // 结果判定 (取最高分)
6175		    let maxLang = '';
6176		    let maxScore = -1;
6177		    
6178		    Object.keys(langFeatures).forEach(lang =>{
6179		       if(langFeatures[lang].score > maxScore){
6180		           maxScore = langFeatures[lang].score;
6181		           maxLang = lang;
6182		       } 
6183		    });
6184
6185		    return maxLang || ''; 
6186		},
6187		/**romanceSentenceAnaly end**/
6188
6189		//是否包含特殊字符,包含,则是true
6190		specialCharacter:function(str){
6191			//如:① ⑴ ⒈ 
6192			if(/.*[\u2460-\u24E9]+.*$/.test(str)){ 
6193				return true
6194			}
6195
6196			//如:┊┌┍ ▃ ▄ ▅
6197			if(/.*[\u2500-\u25FF]+.*$/.test(str)){ 
6198				return true
6199			}
6200
6201			//如:㈠  ㎎ ㎏ ㎡
6202			if(/.*[\u3200-\u33FF]+.*$/.test(str)){ 
6203				return true
6204			}
6205			
6206			//如:与ANSI对应的全角字符
6207			if(/.*[\uFF00-\uFF5E]+.*$/.test(str)){ 
6208				return true
6209			}
6210
6211			//其它特殊符号
6212			if(/.*[\u2000-\u22FF]+.*$/.test(str)){ 
6213				return true
6214			}
6215
6216			// 、><等符号
6217			if(/.*[\u3001-\u3036]+.*$/.test(str)){
6218				return true;
6219			}
6220
6221			
6222			
6223			/*
6224			U+0020 空格
6225			U+0021 ! 叹号
6226			U+0022 " 双引号
6227			U+0023 # 井号
6228			U+0024 $ 价钱/货币符号
6229			U+0025 % 百分比符号
6230			U+0026 & 英文“and”的简写符号
6231			U+0027 ' 引号
6232			U+0028 ( 开 左圆括号
6233			U+0029 ) 关 右圆括号
6234			U+002A * 星号
6235			U+002B + 加号
6236			U+002C , 逗号
6237			U+002D - 连字号/减号
6238			U+002E . 句号
6239			U+002F / 左斜杠
6240			*/
6241			if(/.*[\u0020-\u002F]+.*$/.test(str)){
6242				return true;
6243			}
6244
6245			/*
6246				U+003A : 冒号
6247				U+003B ; 分号
6248				U+003C < 小于符号
6249				U+003D = 等于号
6250				U+003E > 大于符号
6251				U+003F ? 问号
6252				U+005B [ 开 方括号
6253				U+005C \ 右斜杠
6254				U+005D ] 关 方括号
6255				U+005E ^ 抑扬(重音)符号
6256				U+005F _ 底线
6257				U+0060 ` 重音符
6258				U+007B { 开 左花括号
6259				U+007C | 直线
6260				U+007D } 关 右花括号
6261				U+007E ~ 波浪纹
6262			*/
6263			if(/.*[\u003B\u003B\u003C\u003D\u003E\u003F\u005B\u005C\u005D\u005E\u005F\u0060\u007B\u007C\u007D\u007E]+.*$/.test(str)){
6264				return true;
6265			}
6266			
6267			//空白字符,\u0009\u000a + https://cloud.tencent.com/developer/article/2128593
6268			if(/.*[\u0009\u000a\u0020\u00A0\u1680\u180E\u202F\u205F\u3000\uFEFF]+.*$/.test(str)){
6269				return true;
6270			}
6271			if(/.*[\u2000-\u200B]+.*$/.test(str)){
6272				return true;
6273			}
6274			
6275			/*
6276				这些字符主要是 罕见的拉丁字母变体 ,通常用于:
6277				某些非洲语言或方言;
6278				古文字、语音学符号;
6279				特殊排版或装饰性字体。
6280			*/
6281			if(/.*[\u2C60-\u2C77]+.*$/.test(str)){
6282				return true;
6283			}
6284			
6285			
6286			return false;
6287		},
6288		/*
6289            文本翻译的替换。
6290
6291            @Deprecated 2025.4.26 最新的在  translate.util.textReplace 
6292
6293            text: 原始文本,翻译的某句或者某个词就在这个文本之中
6294            translateOriginal: 翻译的某个词或句,在翻译之前的文本
6295            translateResult: 翻译的某个词或句,在翻译之后的文本,翻译结果
6296            language: 显示的语种,这里是对应的 translateResult 这个文本的语种。 也就是最终替换之后要显示给用户的语种。比如将中文翻译为英文,这里也就是英文。 这里会根据显示的语种不同,来自主决定是否前后加空格进行分割。 另外这里传入的语种也是 translate.js 的语种标识
6297        	
6298        	(注意,如果 translateResult 中发现 translateOriginal 的存在,将不进行任何处理,因为没必要了,还会造成死循环。直接将 text 返回)
6299			
6300			使用此方法:
6301			var text = '你世好word世界';
6302			var translateOriginal = '世';
6303			var translateResult = '世杰'; //翻译结果
6304			translate.language.textTranslateReplace(text, translateOriginal, translateResult, 'english');
6305			
6306        */
6307        textTranslateReplace:function(text, translateOriginal, translateResult, language){
6308        	return translate.util.textReplace(text, translateOriginal, translateResult, language);
6309        }
6310	},
6311	//用户第一次打开网页时,自动判断当前用户所在国家使用的是哪种语言,来自动进行切换为用户所在国家的语种。
6312	//如果使用后,第二次在用,那就优先以用户所选择的为主
6313	executeByLocalLanguage:function(){
6314		//先读用户自己浏览器的默认语言
6315		var browserDefaultLanguage = translate.util.browserDefaultLanguage();
6316		if(typeof(browserDefaultLanguage) != 'undefined' && browserDefaultLanguage.length > 0){
6317			translate.changeLanguage(browserDefaultLanguage);
6318			return;
6319		}
6320
6321		if(typeof(translate.request.api.ip) != 'string' || translate.request.api.ip == null || translate.request.api.ip.length < 1){
6322			return;
6323		}
6324
6325		//如果用户浏览器没读到默认语言,或者默认语言没有对应到translate.js支持的语种,那么在采用ip识别的方式
6326		translate.request.post(translate.request.api.ip, {}, function(responseData, requestData){
6327			//console.log(responseData); 
6328			if(responseData.result != 1){
6329				translate.log('==== ERROR 获取当前用户所在区域异常 ====');
6330				translate.log(data.info);
6331				translate.log('==== ERROR END ====');
6332			}else{
6333				translate.storage.set('to',responseData.language);	//设置目标翻译语言
6334				translate.to = responseData.language; //设置目标语言
6335				//translate.selectLanguageTag
6336				translate.execute(); //执行翻译
6337			}
6338		}, null);
6339	},
6340	
6341	util:{
6342		/*
6343            文本替换,将替换完毕的结果返回
6344            自定义术语等都是通过这个来进行替换
6345            2025.4.26 从 language 中 拿到这里
6346            
6347            text: 原始文本,翻译的某句或者某个词就在这个文本之中
6348            translateOriginal: 翻译的某个词或句,在翻译之前的文本
6349            translateResult: 翻译的某个词或句,在翻译之后的文本,翻译结果
6350            language: 显示的语种,这里是对应的 translateResult 这个文本的语种。 也就是最终替换之后要显示给用户的语种。比如将中文翻译为英文,这里也就是英文ã€
6350‚ 这里会根据显示的语种不同,来自主决定是否前后加空格进行分割。 另外这里传入的语种也是 translate.js 的语种标识
6351        	participles: 分词,数组形态。默认不传则是没有其他分词需要保留的。 传入比如  ['你好','你是谁'] 
6352        		比如 translateOriginal 传入 '你' 时, text 中的 '你好','你是谁' 是不能被拆出'你'这个字进行替换的,不然就破坏了分词了
6353
6354        	(注意,如果 translateResult 中发现 translateOriginal 的存在,将不进行任何处理,因为没必要了,还会造成死循环。直接将 text 返回)
6355			
6356			使用此方法:
6357			var text = '你世好word世界';
6358			var translateOriginal = '世';
6359			var translateResult = '世杰'; //翻译结果
6360			translate.util.textReplace(text, translateOriginal, translateResult, 'english'); //没有分词,正常替换
6361			translate.util.textReplace(text, translateOriginal, translateResult, 'english',['世界','好word']); //有分词,要保留分词结构,不能被拆分替换,不能拆分分词的语义
6362
6363        */
6364        textReplace:function(text, translateOriginal, translateResult, language, participles){
6365        	//console.log('----text:'+text.replace(/\t/g, '\\t').replace(/\r/g, '\\r').replace(/\n/g, '\\n').replace(/ /g, '[空白符]')+', translateOriginal:'+translateOriginal+', translateResult:'+translateResult);
6366        	
6367        	//如果要替换的源文本直接就是整个文本,那也就不用在做什么判断了,直接将 翻译的结果文本返回就好了
6368        	if(text == translateOriginal){
6369        		return translateResult;
6370        	}
6371        	
6372        	//文字之间需要空格,也就是半角标点符号,像是英语,法语, 则是true
6373        	var requireSpace = translate.language.wordBlankConnector(language);
6374
6375        	//如果要替换的文本只是原文本中的一部分,那么就需要进行处理了
6376        	var findText = translateOriginal;
6377
6378        	/**** 判断 findText 的开始字符跟结束字符是否包含着 特殊符号 ,:。 因为在 translate.util.textReplace 替换时,会根据当前语种,自动将前后有句号等符号时进行中英的符号转换,此时如果 findText 传入的带有句号的,比如 “你好,” 而实际上text的内容是已经被替换过,就会导致 “你好,” 找不到,而 “你好,” 能找到 ****/
6379			var punctuationMarks_fullWidth = [',',':','。']; //标点符号-全角,用于中文等语种
6380			var punctuationMarks_halfWidth = [',',':','.']; //标点符号-半角,用于英文等语种
6381			
6382			//取第一个字符
6383			var findTextFirstChar = findText.charAt(0);
6384			//取最后一个字符
6385			var findTextLastChar = findText.charAt(findText.length-1);
6386
6387			/*
6388			 * translateOriginal 生成的用于替换的变种,可能是多个,比如  “你好,世界。” 中的  ",世界" 在翻译为英文情况时,会出现这几种变种:
6389			 * , 世界。
6390			 * , 世界. 
6391			 * ,世界。
6392			 * ,世界. 
6393			 * 根据不同的中英文,标点符号后面是否跟空格也不同
6394			 */
6395			var originalArray = [];
6396			originalArray.push(translateOriginal); //首先把当前的加入进去
6397
6398			//翻译替换为半角标点符号,如英语
6399			if(requireSpace){
6400
6401				//第一个发现全角字符, 转为半角处理
6402				if(punctuationMarks_fullWidth.indexOf(findTextFirstChar) > -1){
6403					var processFirstCharText = punctuationMarks_halfWidth[punctuationMarks_fullWidth.indexOf(findTextFirstChar)]+' '+findText.substring(1, findText.length);
6404					originalArray.push(processFirstCharText);
6405					
6406					//第一个处理后,寻找最后一个全角字符转为半角处理
6407					if(punctuationMarks_fullWidth.indexOf(findTextLastChar) > -1){
6408						originalArray.push(processFirstCharText.substring(0, processFirstCharText.length-1)+punctuationMarks_halfWidth[punctuationMarks_fullWidth.indexOf(findTextLastChar)]);
6409					}
6410				}
6411
6412				//将最后一个全角字符转为半角处理
6413				if(punctuationMarks_fullWidth.indexOf(findTextLastChar) > -1){
6414					originalArray.push(findText.substring(0, findText.length-1)+punctuationMarks_halfWidth[punctuationMarks_fullWidth.indexOf(findTextLastChar)]);
6415				}
6416			}else{
6417				//翻译替换为全角标点符号,如中文
6418
6419				//第一个发现半字符, 转为全角处理。这里不用跟上面似的追加去除空格的,因为 textReplace_service 只为了阅读方便追加空格,并没有做去空格处理。 
6420				if(punctuationMarks_halfWidth.indexOf(findTextFirstChar) > -1){
6421					var processLastCharText = punctuationMarks_fullWidth[punctuationMarks_halfWidth.indexOf(findTextFirstChar)]+' '+findText.substring(1, findText.length);
6422					originalArray.push(processLastCharText);
6423
6424					//第一个处理后,寻找最后一个半角字符转为全角处理
6425					if(punctuationMarks_halfWidth.indexOf(findTextLastChar) > -1){
6426						originalArray.push(processLastCharText.substring(0, processLastCharText.length-1)+punctuationMarks_fullWidth[punctuationMarks_halfWidth.indexOf(findTextLastChar)]);
6427					}
6428				}
6429
6430				//将最后一个全角字符转为半角处理
6431				if(punctuationMarks_halfWidth.indexOf(findTextLastChar) > -1){
6432					originalArray.push(findText.substring(0, findText.length-1)+punctuationMarks_fullWidth[punctuationMarks_halfWidth.indexOf(findTextLastChar)]);
6433				}
6434			}
6435			//console.log(originalArray);
6436
6437			for(var i = 0; i < originalArray.length; i++){
6438				if(text.indexOf(originalArray[i]) > -1){
6439					text = translate.util.textReplace_service(text, originalArray[i], translateResult, language, participles);
6440				}
6441			}
6442			
6443			return text;
6444        },
6445        /*
6446            它服务于上面的 textReplace,不需要直接使用这个
6447
6448        */
6449        textReplace_service:function(text, translateOriginal, translateResult, language, participles){
6450        	//console.log('----text:'+text+', translateOriginal:'+translateOriginal+', translateResult:'+translateResult);
6451        	//如果要替换的源文本直接就是整个文本,那也就不用在做什么判断了,直接将 翻译的结果文本返回就好了
6452        	if(text == translateOriginal){
6453        		return translateResult;
6454        	}
6455        	
6456        	/*
6457
6458        	//当前替换后,替换结果结束位置的下标。 
6459        	//一开始还没进行替换,那么这个下标就是 0
6460        	//比如 你好吗  中的 好 替换为 "好的" 那最后结果为 "你好的吗" ,这里是 “的” 的下标 2
6461        	let currentReplaceEndIndex = 0;
6462
6463        	//while最大循环次数30次,免得出现未知异常导致死循环
6464        	let maxWhileNumber = 30;
6465			*/
6466			
6467			var indexArray = translate.util.findParticiple(text, translateOriginal, participles);
6468			for(var i = indexArray.length-1; i > -1 ; i--){
6469        		//console.log('text:'+text+'\tcurrentReplaceEndIndex:'+currentReplaceEndIndex);
6470
6471        		//通过 translate.util.replaceFromIndex 进行替换时的 index 开始位置 。 比如如果下面识别前面的一个字符,要变为, 那也就是要继续向前一位,这里就要 -1
6472        		let replaceIndex = indexArray[i]; 
6473        		//要替换的结果文本(这个文本可能前面有加空格或者后面有加空格的)
6474           		let replaceResultText = ''+translateResult; 
6475           		//替换的文本 ,这里有可能会追加上某些标点符号,所以单独也列出来,而不是使用方法中传入的 translateOriginal
6476           		let replaceOriginalText = '' + translateOriginal; 
6477
6478           		//根据不同的语种,如果有的语种需要加空格来进行区分单词,那么也要进行空格的判定
6479           		if(translate.language.wordBlankConnector(language)){
6480	                //let originalIndex = text.indexOf(translateOriginal, currentReplaceEndIndex); //翻译之前,翻译的单词在字符串中的起始坐标(0开始)
6481	                let originalIndex = indexArray[i];
6482	                //console.log("originalIndex: "+originalIndex);
6483
6484	                //要先判断后面,不然先判断前面,加了后它的长度就又变了
6485
6486	                //判断它后面是否还有文本
6487	                var afterCharIndex = indexArray[i]+translateOriginal.length; //translateOriginal之后的第一个文本的index下标
6488	                if(afterCharIndex < text.length){
6489	                	let char = text.charAt(afterCharIndex);
6490	                    //console.log(translateOriginal+' after char : '+char+", index:"+afterCharIndex);
6491	                    if(/。/.test(char)){
6492	                    	replaceResultText = replaceResultText + '. ';
6493	                    	replaceOriginalText = translateOriginal + '。';
6494	                    }else if(/,/.test(char)){
6495	                    	replaceResultText = replaceResultText + ', ';
6496	                    	replaceOriginalText = translateOriginal + ',';
6497	                    }else if(/:/.test(char)){
6498	                    	replaceResultText = replaceResultText + ': ';
6499	                    	replaceOriginalText = translateOriginal + ':';	
6500	                    }else if([' ', '\n','\t',']','|', '_','-','/'].indexOf(char) !== -1){
6501							// 如果后面的字符是 这些字符,那么不用添加空格隔开
6502						}else{
6503							//补充上一个空格,用于将两个单词隔开。  不过 ,如果当前 replaceResultText 的最后一个字符也是空格,那就不需要再加空格了。 这里就只判断空格就好了,至于其他的换行等基本不会出现这个情况,所以不考虑
6504							if(replaceResultText.length > 0 && replaceResultText.charAt(replaceResultText.length-1) == ' '){
6505								//replaceResultText 本身有值,且最后一个字符就是空格,就不需要再追加空格进行隔开了
6506							}else{
6507								replaceResultText = replaceResultText + ' ';
6508							}
6509	                    }
6510	                }
6511
6512	                //判断它前面是否还有文本
6513	                if(originalIndex > 0){
6514	                    let char = text.charAt(originalIndex-1);
6515	                    //console.log(char);
6516	                    
6517  						if(/。/.test(char)){
6518  							replaceIndex--;
6519	                    	replaceResultText = '. '+replaceResultText;
6520	                    	replaceOriginalText = '。'+replaceOriginalText;
6521	                    }else if(/,/.test(char)){
6522	                    	replaceIndex--;
6523	                    	replaceResultText = ', '+replaceResultText;
6524	                    	replaceOriginalText = ','+replaceOriginalText;
6525	                    }else if(/:/.test(char)){
6526	                    	replaceIndex--;
6527	                    	replaceResultText = ': '+replaceResultText;
6528	                    	replaceOriginalText = ':'+replaceOriginalText;	
6529	                    }else if([' ', '\n','\t','[', '|', '_','-','/'].indexOf(char) !== -1){
6530							// 如果前面的字符是 这些字符,那么不用添加空格隔开
6531							//console.log('不需要空格隔开的');
6532						}else{
6533	                        //补充上一个空格,用于将两个单词隔开。  不过 ,如果当前 replaceResultText 的第一个字符也是空格,那就不需要再加空格了。  这里就只判断空格就好了,至于其他的换行等基本不会出现这个情况,所以不考虑
6534							if(replaceResultText.length > 0 && replaceResultText.charAt(0) == ' '){
6535								//replaceResultText 本身有值,且最后一个字符就是空格,就不需要再追加空格进行隔开了
6536							}else{
6537								replaceResultText = ' '+replaceResultText;
6538							}
6539							//console.log('before add space : '+replaceResultText);
6540	                    }
6541	                }
6542	            }else{
6543	            	//如果是其他语种比如英语法语翻译为中文、日文,那么标点符号也要判断的,这个因为目前这个场景还没咋遇到,就不判断了,遇到了在加。
6544
6545	            }
6546	            //console.log('replaceOriginalText: '+ replaceOriginalText+" --> replaceResultText: "+replaceResultText);
6547	            
6548	            let replaceResult  = translate.util.replaceFromIndex(text, replaceIndex, replaceOriginalText, replaceResultText);
6549
6550	            if(replaceResult.replaceEndIndex < 1){
6551	            	translate.log('translate.util.findParticiple 中已经发现了,但是实际没有替换,出现异常了!理论上这是不应该出现的。 text:'+text+' , index:'+indexArray[i]+',  translateOriginal:'+translateOriginal);
6552	            }else{
6553	            	text = replaceResult.text;
6554	            }
6555        	}
6556
6557            return text;
6558        },
6559        /*
6560			从一个字符串中, 寻找某个分词。这个分词不能破坏其他分词。
6561			text: 原始文本,翻译的某句或者某个词就在这个文本之中
6562			findParticiple: 寻找的分词文本
6563			participles: 分词,数组形态。默认不传则是没有其他分词需要保留的。 传入比如  ['你好','你是谁'] 
6564        		比如 translateOriginal 传入 '你' 时, text 中的 '你好','你是谁' 是不能被拆出'你'这个字进行替换的,不然就破坏了分词了
6565			
6566			
6567			return 返回寻找到的分词文本在 text 中的下标数组(下标是从0开始)
6568					比如: [0, 3, 6] 便是在 text 中的0下标出现了这个 findParticiple 寻找的分词文本
6569				   如果没有发现,则返回 [] 空数组
6570				   注意,它里面的元素都是按照顺序由小往大,顺序排的
6571
6572        */
6573        findParticiple:function(text, findText, translateTexts){
6574        	var resultArray = [];
6575
6576			//兼容 translateTexts 不传入的情况
6577			if(typeof(translateTexts) == 'undefined' || translateTexts == null){
6578				translateTexts = [];
6579			}
6580
6581			/*****1. 先过滤,过滤掉 translateTexts 中 不包含 translateOriginal、 以及文本长度小于等于 translateOriginal  这个分词的情况*****/
6582			var newTranslateTexts = []; 
6583			for(var i = 0; i < translateTexts.length; i++){
6584				if(translateTexts[i].indexOf(findText) != -1 && translateTexts[i].length > findText.length){
6585					newTranslateTexts.push(translateTexts[i]);
6586				}
6587			}
6588			//console.log('包含'+findText+'的分词:');
6589			//console.log(newTranslateTexts)
6590
6591			//当前替换后,替换结果结束位置的下标。 
6592			//一开始还没进行替换,那么这个下标就是 0
6593			//比如 你好吗  中的 好 替换为 "好的" 那最后结果为 "你好的吗" ,这里是 “的” 的下标 2
6594			var currentReplaceEndIndex = 0;
6595
6596			//while最大循环次数30次,免得出现未知异常导致死循环
6597			var maxWhileNumber = 30;
6598
6599			// 识别,indexOf 逐个识别 '你' ,识别到之后,再跟 其他比如 '你是谁'  进行判断,比如  '你是谁'  就要讲 indexOf 的下标+2 来截取这text中的三个字符,去跟 '你是谁' 判定,以判定是否是一个正常的不能拆分的分词
6600			while(text.indexOf(findText, currentReplaceEndIndex) > -1 && maxWhileNumber-- > 0){
6601				var index = text.indexOf(findText, currentReplaceEndIndex);
6602
6603				var findParticiple = false; //发现是其他分词了是true,没发现可以替换则是false
6604
6605				//进行其他分词发现策略
6606				if(newTranslateTexts.length > 0){
6607					//发现的这个词可能是其他分词中的一部分,这个要判断当前index是否是其他分词的一部分。 这里要进行遍历 newTranslateTexts 逐个取出进行对比
6608
6609					for(var j = 0; j < newTranslateTexts.length; j++){
6610						//判断 newTranslateTexts[j] 这个分词中包含的 findText 这个文本,这个文本是在 newTranslateTexts[j] 的下标的多少
6611						var indexInNewTranslateTexts = newTranslateTexts[j].indexOf(findText);
6612						// 因为 newTranslateTexts 是通过筛选包含 findText 而出的,所以它肯定是包含的,有下标的
6613						// 这里要从 text 中,根据 indexInNewTranslateTexts 及 原本 findText 的 index,在这个index的前或者后,追加几个文本,这追加的文本长度,也就是根据 indexInNewTranslateTexts 以及 newTranslateTexts[j] 的长度
6614
6615						//这里准备要根据 newTranslateTexts[j] 、 indexInNewTranslateTexts ,来定义从 text 中去取 对应 newTranslateTexts[j] 的长度,以判断当前index是否是取的 newTranslateTexts[j] 这个分词的 
6616						var length = newTranslateTexts[j].length;
6617						var split_text = text.substring(index-indexInNewTranslateTexts, index-indexInNewTranslateTexts + length);
6618						if(split_text == newTranslateTexts[j]){
6619							//说明当前index是取的 newTranslateTexts[j] 这个分词的,那这个就不能替换,要忽略
6620							//console.log('当前是其他分词,不能直接替换 : '+newTranslateTexts[j]);
6621							findParticiple = true;
6622							break;
6623						}
6624					}
6625				}
6626
6627				if(!findParticiple){
6628					resultArray.push(index);
6629				}
6630				currentReplaceEndIndex = index+findText.length;
6631			}
6632
6633
6634
6635			  /*
6636			    这里需要将下面的 9、7 这个下标找出来,然后进行替换。  注意要先从后进行替换,避免从前替换,之后的下标长度出现变化
6637			  */
6638
6639			  //text = translate.util.replaceFromIndex(text, 9, translateOriginal, translateResult).text;
6640			  //text = translate.util.replaceFromIndex(text, 7, translateOriginal, translateResult).text;
6641
6642
6643			  //return text;
6644
6645			return resultArray;
6646        },
6647        /*
6648			js 的 replace 能力,这个是可以指定从第几个字符开始进行replace
6649			1. 这里可以 replaceText 本身包含着 originalText
6650			2. originalText 可以出现多次
6651
6652			@param
6653				text 要进行替换的原始文本
6654				index 要从 text 的哪个下标开始。 (第一个字符下标是0)
6655				originalText 要替换的文本,被替换的文本
6656				replaceText 替换为的文本,将 originalText 替换为什么
6657				replaceFromIndex('你好吗?你也好?', 0, '你', '你是谁');
6658
6659			@return 对象
6660				text 替换的结果
6661				replaceEndIndex 当前替换后,替换结果结束位置的下标。 
6662        				如果没进行替换,那么这个下标就是 0
6663        				比如 你好吗  中的 好 替换为 "好的" 那最后结果为 "你好的吗" ,这里是 “的” 的下标 2
6664		*/
6665        replaceFromIndex:function(text, index, originalText, replaceText){
6666		    const before = text.slice(0, index);
6667		    const after = text.slice(index);
6668		    const originalTextIndex = after.indexOf(originalText);
6669		    if(originalTextIndex > -1){
6670		    	const replacedAfter = after.replace(originalText, replaceText);
6671		    	return {
6672		    		text: before + replacedAfter, 
6673		    		replaceEndIndex: index + originalTextIndex + replaceText.length
6674		    	}
6675		    }else{
6676		    	//没有发现可替换的字符,那么就原样返回
6677		    	//console.log('after:'+after);
6678		    	//console.log(text+originalText);
6679		    	return {
6680		    		text: before, 
6681		    		replaceEndIndex: 0
6682		    	};
6683		    }
6684        },
6685
6686		/* 生成一个随机UUID,复制于 https://gitee.com/mail_osc/kefu.js */
6687		uuid:function() {
6688		    var d = new Date().getTime();
6689		    if (window.performance && typeof window.performance.now === "function") {
6690		        d += performance.now(); //use high-precision timer if available
6691		    }
6692		    var uuid = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
6693		        var r = (d + Math.random() * 16) % 16 | 0;
6694		        d = Math.floor(d / 16);
6695		        return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16);
6696		    });
6697		    return uuid;
6698		},
6699
6700		//判断字符串中是否存在tag标签。 true存在
6701		findTag:function(str) {
6702			var reg = /<[^>]+>/g;
6703			return reg.test(str);
6704		},
6705		//传入一个数组,从数组中找出现频率最多的一个返回。 如果多个频率出现的次数一样,那会返回多个
6706		arrayFindMaxNumber:function(arr){
6707
6708			// 储存每个元素出现的次数
6709			var numbers = {}
6710
6711			// 储存出现最多次的元素
6712			var maxStr = []
6713
6714			// 储存最多出现的元素次数
6715			var maxNum = 0
6716
6717			for(var i =0,len=arr.length;i<len;i++){
6718			    if(!numbers[arr[i]]){
6719			          numbers[arr[i]] = 1  
6720			    }else{
6721			        numbers[arr[i]]++
6722			    }
6723
6724			    if(numbers[arr[i]]>maxNum){
6725			        maxNum = numbers[arr[i]]
6726			    }
6727			}
6728
6729			for(var item in numbers){
6730				if (!numbers.hasOwnProperty(item)) {
6731		    		continue;
6732		    	}
6733			    if(numbers[item]===maxNum){
6734			        maxStr.push(item)
6735			    }
6736			}
6737			
6738			return maxStr;
6739		},
6740		//对字符串进行hash化,目的取唯一值进行标识
6741		hash:function(str){
6742			if(str == null || typeof(str) == 'undefined'){
6743				return str;
6744			}
6745			var hash = 0, i, chr;
6746			if (str.length === 0){
6747				return hash;
6748			}
6749
6750			for (i = 0; i < str.length; i++) {
6751				chr   = str.charCodeAt(i);
6752				hash  = ((hash << 5) - hash) + chr;
6753				hash |= 0; // Convert to 32bit integer
6754			}
6755			return hash+'';
6756		},
6757		//去除一些指定字符,如换行符。 如果传入的是null,则返回空字符串
6758		charReplace:function(str){
6759
6760			if(str == null){
6761				return '';
6762			}
6763			str = str.trim();
6764			str = str.replace(/\t|\n|\v|\r|\f/g,'');	//去除换行符等
6765			//str = str.replace(/&/g, "%26"); //因为在提交时已经进行了url编码了
6766			return str;
6767		},
6768		//RegExp相关
6769		regExp:{
6770			// new RegExp(pattern, resultText); 中的 pattern 字符串的预处理
6771			pattern:function(str){
6772				str = str.replace(/\\/g,'\\\\'); //这个一定要放在第一个,不然会被下面的影响
6773				//str = str.replace(/'/g,'\\\'');
6774				str = str.replace(/\"/g,'\\\"');
6775				//str = str.replace(/./g,'\\\.');
6776				str = str.replace(/\?/g,'\\\?');
6777				str = str.replace(/\$/g,'\\\$');
6778				str = str.replace(/\(/g,'\\\(');
6779				str = str.replace(/\)/g,'\\\)');
6780				str = str.replace(/\|/g,'\\\|');
6781				str = str.replace(/\+/g,'\\\+');
6782				str = str.replace(/\*/g,'\\\*');
6783				str = str.replace(/\[/g,'\\\[');
6784				str = str.replace(/\]/g,'\\\]');
6785				str = str.replace(/\^/g,'\\\^');
6786				str = str.replace(/\{/g,'\\\{');
6787				str = str.replace(/\}/g,'\\\}');
6788				return str;
6789			},
6790			// new RegExp(pattern, resultText); 中的 resultText 字符串的预处理
6791			resultText:function(str){
6792				//str = str.replace(/&quot;/g,"\"");
6793				//str = str.replace(/'/g,"\\\'");
6794				//str = str.replace(/"/g,"\\\"");
6795				return str;
6796			}
6797		},
6798		//获取URL的GET参数。若没有,返回""
6799		getUrlParam:function (name){
6800		     var reg = new RegExp("(^|&)"+ name +"=([^&]*)(&|$)");
6801		     var r = window.location.search.substr(1).match(reg);
6802		     if(r!=null)return  unescape(r[2]); return "";
6803		},
6804		/**
6805		 * 同步加载JS,加载过程中会阻塞,加载完毕后继续执行后面的。
6806		 * url: 要加载的js的url
6807		 */
6808		synchronizesLoadJs:function(url){
6809			var  xmlHttp = null;  
6810			if(window.ActiveXObject){//IE  
6811				try {  
6812					//IE6以及以后版本中可以使用  
6813					xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");  
6814				} catch (e) {  
6815					//IE5.5以及以后版本可以使用  
6816					xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");  
6817				}  
6818			}else if(window.XMLHttpRequest){  
6819				//Firefox,Opera 8.0+,Safari,Chrome  
6820				xmlHttp = new XMLHttpRequest();  
6821			}  
6822			//采用同步加载  
6823			xmlHttp.open("GET",url,false);  
6824			//发送同步请求,如果浏览器为Chrome或Opera,必须发布后才能运行,不然会报错  
6825			xmlHttp.send(null);  
6826			//4代表数据发送完毕  
6827			if( xmlHttp.readyState == 4 ){  
6828				//0为访问的本地,200到300代表访问服务器成功,304代表没做修改访问的是缓存  
6829				if((xmlHttp.status >= 200 && xmlHttp.status <300) || xmlHttp.status == 0 || xmlHttp.status == 304){  
6830					var myBody = document.getElementsByTagName("HTML")[0];  
6831					var myScript = document.createElement( "script" );  
6832					myScript.language = "javascript";  
6833					myScript.type = "text/javascript";  
6834					try{  
6835						//IE8以及以下不支持这种方式,需要通过text属性来设置  
6836						myScript.appendChild(document.createTextNode(xmlHttp.responseText));  
6837					}catch (ex){  
6838						myScript.text = xmlHttp.responseText;  
6839					}  
6840					myBody.appendChild(myScript);  
6841					return true;  
6842				}else{  
6843					return false;  
6844				}  
6845			}else{  
6846				return false;  
6847			}  
6848		},
6849
6850		/*js translate.util.loadMsgJs start*/
6851		//加载 msg.js
6852		loadMsgJs:function(){
6853			if(typeof(msg) != 'undefined'){
6854				return;
6855			}
6856			translate.util.synchronizesLoadJs('https://res.zvo.cn/msg/msg.js');
6857		},
6858		/*js translate.util.loadMsgJs end*/
6859		/*
6860			对一个对象,按照对象的key的长度进行排序,越长越在前面
6861		*/
6862		objSort:function(obj){
6863			// 获取对象数组的所有 key,并转换为普通数组
6864			var keys = Array.from(Object.keys(obj));
6865			//var keys = [].slice.call(Object.keys(obj)); //适配es5
6866
6867			// 对 key 数组进行排序
6868			keys.sort(function(a, b){
6869			  return b.length - a.length;
6870			});
6871
6872			// 定义一个新的对象数组,用来存储排序后的结果
6873			var sortedObj = new Array();
6874
6875			// 遍历排序后的 key 数组,将对应的值复制到新的对象数组中,并删除原来的对象数组中的键值对
6876			for (var key of keys) {
6877			  sortedObj[key] = obj[key];
6878			}
6879			return sortedObj;
6880		},
6881		/*
6882			将 2.11.3.20231232 转化为 2011003
6883			转化时会去掉最后一个日期的字符
6884		*/
6885		versionStringToInt:function(versionString){
6886			var vs = versionString.split('\.');
6887			var result = 0;
6888			result = parseInt(vs[0])*1000*1000 + result;
6889			result = parseInt(vs[1])*1000 + result;
6890			result = parseInt(vs[2]) + result;
6891
6892			return result;
6893		},
6894		/**
6895		 * 将一个 JSONArray 数组,按照文字长度进行拆分。
6896		 *  比如传入的 array 数组的文字长度是6200,传入的 stringLength 是2000,那么就是将 array 数组拆分为多个长度不超出2000的数组返回。
6897		 * 		如果传入了 maxSize = 5 那么会对拆分后的数组的长度进行判断,如果数组内元素超过5,那么还要进行缩短,拆分后的数组不允许超过这个数
6898		 * 		也就是拆分后的数组有两重限制,一是限制转化为文本形式的长度、再就是拆分后本身数组的大小。
6899		 * 		
6900		 *  注意,这个长度是指 array.toString() 后的长度,也就是包含了 [""] 这种符号的长度
6901		 * @param array 要被拆分的数组,其内都是String类型,传入格式如 ["你好","世界"]
6902		 * @param stringLength 要被拆分的数组转化为字符串之后的长度
6903		 * @param maxSize 被拆分的数组最大包含多少个,数组大小最大允许多大,要小于等于这个数。 如果设置为0则是不启用这个,不对拆分后的数组进行判断。
6904		 * @return 被拆分后的数组列表
6905		 * @author 刘晓腾
6906		 */
6907		 split:function(array, size, maxSize) {
6908		 	let orgsize = size;
6909		    let list = [];
6910		    // 数组长度小于size,直接进行返回
6911		    if(JSON.stringify(array).length <= size) {
6912		        list.push(array);
6913		    } else {
6914		        // 转换成String
6915		        let arrayStr = JSON.stringify(array).trim().substring(1, JSON.stringify(array).length - 1);
6916
6917		        // 判断size和字符串长度的差值,如果为1或者2,就直接拆成两段
6918		        if (JSON.stringify(array).length - size <= 2) {
6919		            size = size - 4;
6920		            // 拆两段
6921		            let str1 = arrayStr.substring(0, arrayStr.lastIndexOf("\",\"")+1);
6922		            let str2 = arrayStr.substring(arrayStr.lastIndexOf("\",\"")+2);
6923		            list.push(JSON.parse("[" + str1 + "]"));
6924		            list.push(JSON.parse("[" + str2 + "]"));
6925		        } else {
6926		            size = size - 2;
6927		            // 拆多段
6928		            let index = 0;
6929		            while (index - arrayStr.length < 0) {
6930		                // 按照指定大小拆一段
6931		                let s = "";
6932		                if ((index+size) - arrayStr.length >= 0) {
6933		                    s = arrayStr.substring(index);
6934		                } else {
6935		                    s = arrayStr.substring(index, (index+size));
6936		                }
6937		                // 结尾长度默认为字符串长度
6938		                let endIndex = s.length;
6939		                // 因为下次开始的第一个字符可能会是逗号,所以下次开始需要+1
6940		                let startNeedAdd = 1;
6941		                // 判断最后一个字符是否为双引号
6942		                if (s.endsWith("\"")) {
6943		                    // 判断倒数第二个是否为逗号
6944		                    if (s.endsWith("\",\"")) {
6945		                        // 删除两个字符
6946		                        endIndex-=2;
6947		                    } else if (!s.startsWith("\"")) {
6948		                        // 如果开头不是引号,需要补一个引号,这就导致会超长,所以结尾就要找指定字符的
6949		                        // 找出最后一个指定字符的位置
6950		                        let la = s.lastIndexOf("\",\"");
6951		                        endIndex = la + 1;
6952		                    }
6953		                } else if (s.endsWith("\",")) {
6954		                    // 判断是否为逗号,是的话删除一个字符
6955		                    endIndex-=1;
6956		                } else {
6957		                    // 都不是,那就是内容结尾
6958		                    // 找出最后一个指定字符的位置
6959		                    let la = s.lastIndexOf("\",\"");
6960		                    endIndex = la + 1;
6961		                    // 内容超长,endIndex就会变成0,这时需要手动赋值
6962		                    if (endIndex <= 0) {
6963		                        // 看看是否以引号开头,如果不是,需要拼两个引号
6964		                        if (s.startsWith("\"")) {
6965		                            // 拼一个引号,-1
6966		                            endIndex = s.length - 1;
6967		                        } else {
6968		                            // 拼两个引号,-2
6969		                            endIndex = s.length - 2;
6970		                        }
6971		                        if (!s.endsWith("\"")) {
6972		                            // 开始不是逗号了,不能-1
6973		                            startNeedAdd = 0;
6974		                        }
6975		                    }
6976		                }
6977		                // 根据处理的结尾长度进行第二次拆分
6978		                let s2 = "";
6979		                if (endIndex - s.length > 0 || endIndex - 0 == 0) {
6980		                    s2 = s;
6981		                    endIndex = endIndex + s2.length;
6982		                } else {
6983		                    s2 = s.substring(0, endIndex);
6984		                }
6985		                if (!s2.startsWith("\"") && !s2.startsWith(",\"")) {
6986		                    // 拼一个引号
6987		                    s2 = "\"" + s2;
6988		                }
6989		                if (!s2.endsWith("\"")) {
6990		                    // 拼一个引号
6991		                    s2 = s2 +"\"";
6992		                }
6993		                // 计算下次循环开始的长度
6994		                index += (endIndex + startNeedAdd);
6995		                // 加到list
6996		                s2 = "[" + s2 + "]";
6997		                try {
6998		                    list.push(JSON.parse(s2));
6999		                } catch (e) {
7000		                    // 遇到错误,略过一个字符
7001		                    index = index - (endIndex + startNeedAdd) + 1;
7002		                }
7003		            }
7004		        }
7005		    }
7006			// 设置了maxSize,进行处理
7007			if (maxSize && maxSize > 0) {
7008				list = translate.util._splitMaxSize(list, orgsize, maxSize);
7009			}
7010		    return list;
7011		},
7012		/**
7013		 * 针对split函数中maxSize的处理
7014		 * 	private
7015		 * @param array 已拆分的二维数组
7016		 * @param size 拆分的长度
7017		 * @param maxSize 元素数量
7018		 * @author 刘晓腾
7019		 */
7020		_splitMaxSize:function(array, size, maxSize) {
7021			// console.log("------ splitMaxSize run ------")
7022
7023			// 返回的数据
7024			let list = [];
7025			// 暂存的数组,用来存储每次遍历时超出的数据
7026			let tmp = [];
7027
7028		 	// 遍历二维数组
7029			array.forEach(function(arr, index) {
7030				// 累加数组
7031				arr = tmp.concat(arr);
7032				// 计算元素数量
7033				let length = arr.length;
7034				// 数组中元素数量大于maxSize,对多余的元素进行移除
7035				if (length > maxSize) {
7036					// 第一个数组,包含前N个元素
7037					let firstArray = arr.slice(0, maxSize);
7038					// 第二个数组,包含剩下的元素
7039					let secondArray = arr.slice(maxSize);
7040
7041					// 处理长度
7042					let len = 1;
7043					while (JSON.stringify(firstArray).length > size) {
7044						// 长度超过限制,进行处理
7045						firstArray = arr.slice(0, maxSize - len);
7046						secondArray = arr.slice(maxSize - len);
7047						len++;
7048						if (len >= arr.length+1) {
7049							break;
7050						}
7051					}
7052
7053					// 第一个数组记录
7054					list.push(firstArray);
7055					// 第二个数组暂存
7056					tmp.length = 0;
7057					tmp = secondArray;
7058				} else {
7059					// 没超,只处理长度
7060					// 处理长度
7061					let firstArray = arr;
7062					let secondArray = [];
7063					let len = 1;
7064					while (JSON.stringify(firstArray).length > size) {
7065						// 长度超过限制,进行处理
7066						firstArray = arr.slice(0, maxSize - len);
7067						secondArray = arr.slice(maxSize - len);
7068						len++;
7069						if (len >= arr.length+1) {
7070							break;
7071						}
7072					}
7073
7074					// 第一个数组记录
7075					list.push(firstArray);
7076					// 第二个数组暂存
7077					tmp.length = 0;
7078					tmp = secondArray;
7079				}
7080
7081			});
7082
7083			// 临时数组中还有元素,也要进行处理
7084			if (tmp.length > 0) {
7085				let tmpl = [];
7086				tmpl.push(tmp);
7087				// 递归处理
7088				let l = translate.util._splitMaxSize(tmpl, size, maxSize);
7089				list = list.concat(l);
7090			}
7091
7092			return list;
7093		},
7094		/* 
7095			浏览器的语种标识跟translate.js的语种标识的对应
7096			key: 浏览器的语种标识
7097			value: translate.js 的语种标识
7098		 */
7099		browserLanguage:{
7100			'zh':'chinese_simplified',
7101			'zh-CN':'chinese_simplified',
7102			'zh-TW':'chinese_traditional',
7103			'zh-HK':'chinese_traditional',
7104			'co':'corsican',
7105			'gn':'guarani',
7106			'rw':'kinyarwanda',
7107			'ha':'hausa',
7108			'no':'norwegian',
7109			'nl':'dutch',
7110			'yo':'yoruba',
7111			'en':'english',
7112			'en-US':'english',
7113			'kok':'gongen',
7114			'la':'latin',
7115			'ne':'nepali',
7116			'fr':'french',
7117			'cs':'czech',
7118			'haw':'hawaiian',
7119			'ka':'georgian',
7120			'ru':'russian',
7121			'fa':'persian',
7122			'bho':'bhojpuri',
7123			'hi':'hindi',
7124			'be':'belarusian',
7125			'sw':'swahili',
7126			'is':'icelandic',
7127			'yi':'yiddish',
7128			'tw':'twi',
7129			'ga':'irish',
7130			'gu':'gujarati',
7131			'km':'khmer',
7132			'sk':'slovak',
7133			'he':'hebrew',
7134			'kn':'kannada',
7135			'hu':'hungarian',
7136			'ta':'tamil',
7137			'ar':'arabic',
7138			'bn':'bengali',
7139			'az':'azerbaijani',
7140			'sm':'samoan',
7141			'af':'afrikaans',
7142			'id':'indonesian',
7143			'da':'danish',
7144			'sn':'shona',
7145			'bm':'bambara',
7146			'lt':'lithuanian',
7147			'vi':'vietnamese',
7148			'mt':'maltese',
7149			'tk':'turkmen',
7150			'as':'assamese',
7151			'ca':'catalan',
7152			'si':'singapore',
7153			'ceb':'cebuano',
7154			'gd':'scottish-gaelic',
7155			'sa':'sanskrit',
7156			'pl':'polish',
7157			'gl':'galician',
7158			'lv':'latvian',
7159			'uk':'ukrainian',
7160			'tt':'tatar',
7161			'cy':'welsh',
7162			'ja':'japanese',
7163			'fil':'filipino',
7164			'ay':'aymara',
7165			'lo':'lao',
7166			'te':'telugu',
7167			'ro':'romanian',
7168			'ht':'haitian_creole',
7169			'doi':'dogrid',
7170			'sv':'swedish',
7171			'mai':'maithili',
7172			'th':'thai',
7173			'hy':'armenian',
7174			'my':'burmese',
7175			'ps':'pashto',
7176			'hmn':'hmong',
7177			'dv':'dhivehi',
7178			'lb':'luxembourgish',
7179			'sd':'sindhi',
7180			'ku':'kurdish',
7181			'tr':'turkish',
7182			'mk':'macedonian',
7183			'bg':'bulgarian',
7184			'ms':'malay',
7185			'lg':'luganda',
7186			'mr':'marathi',
7187			'et':'estonian',
7188			'ml':'malayalam',
7189			'de':'deutsch',
7190			'sl':'slovene',
7191			'ur':'urdu',
7192			'pt':'portuguese',
7193			'ig':'igbo',
7194			'ckb':'kurdish_sorani',
7195			'om':'oromo',
7196			'el':'greek',
7197			'es':'spanish',
7198			'fy':'frisian',
7199			'so':'somali',
7200			'am':'amharic',
7201			'ny':'nyanja',
7202			'pa':'punjabi',
7203			'eu':'basque',
7204			'it':'italian',
7205			'sq':'albanian',
7206			'ko':'korean',
7207			'tg':'tajik',
7208			'fi':'finnish',
7209			'ky':'kyrgyz',
7210			'ee':'ewe',
7211			'hr':'croatian',
7212			'kri':'creole',
7213			'qu':'quechua',
7214			'bs':'bosnian',
7215			'mi':'maori'
7216		},
7217		/*
7218			获取浏览器中设置的默认使用语言
7219			返回的是 translate.js 的语言唯一标识
7220			如果返回的是空字符串,则是没有匹配到(可能是没有获取到本地语言,也可能是本地语言跟translate.js 翻译通道没有对应上)
7221		*/
7222		browserDefaultLanguage:function(){
7223			var language = navigator.language || navigator.userLanguage;
7224			if(typeof(language) == 'string' && language.length > 0){
7225				var tLang = translate.util.browserLanguage[language];
7226				if(typeof(tLang) == 'undefined'){
7227					//没有在里面
7228					translate.log('browser default language : '+language +', translate.js current translate channel not support this language ');
7229				}else{
7230					return tLang;
7231				}
7232			}
7233			
7234			//将其转化为  translate.js 的语言id,比如简体中文是 chinese_simplified 、 英语是 english
7235			return '';
7236		},
7237		/*
7238			对输入的文本 text 进行判断,判断它里面是否有url存在。如果有url存在,对其进行截取,将url跟非url进行截取处理。
7239			比如传入 “这个示例:https://www.ungm.org/Public/Notice/261001,其他得示例是 http://api.translate.zvo.cn 我呢”
7240			那么返回的截取结果为:
7241			{
7242				"https://www.ungm.org/Public/Notice/261001":"1",
7243				"http://api.translate.zvo.cn":"1",
7244				",其他得示例是 ":"0",
7245				"这个示例:":"0"
7246				" 我呢":"0"
7247			}
7248			其中的key 为截取的文本,value 的值是1或0, 1代表当前key的文本是网址,0则不是网址  
7249		*/
7250		urlSplitByText:function(text){
7251			// 匹配 http/https 的 URL 正则表达式(包含常见 URL 符号,排除中文等非 ASCII 字符)
7252			const urlRegex = /(https?:\/\/[\w\-._~:\/?#[\]@!$&'()*+;=%]+(?=[\s\u4e00-\u9fa5,。;,!?]|$))/gi;
7253
7254			// 使用正则表达式分割文本,保留URL
7255			const parts = text.split(urlRegex);
7256
7257			// 结果对象
7258			let result = {};
7259
7260			// 添加非URL部分,并标记为 0
7261			for (let i = 0; i < parts.length; i++) {
7262				if (i % 2 === 0) {
7263					// 非URL部分
7264					if (parts[i] !== "") {
7265						result[parts[i]] = "0";
7266					}
7267				} else {
7268					// URL部分
7269					result[parts[i]] = "1";
7270				}
7271			}
7272
7273			return result;
7274		},
7275
7276		/*js translate.util.getElementPosition start*/
7277		/*
7278			计算一个元素在浏览器中的坐标系,其绝对定位、以及实际显示出来所占用的区域,宽、高
7279		*/
7280		getElementPosition:function (node) {
7281			// 获取元素的边界矩形信息(相对于视口)
7282			const rect = node.getBoundingClientRect();
7283
7284			// 获取当前页面的滚动位置(兼容不同浏览器)
7285			const scrollX = window.scrollX || document.documentElement.scrollLeft;
7286			const scrollY = window.scrollY || document.documentElement.scrollTop;
7287
7288			// 计算元素在文档中的起始坐标
7289			const startX = rect.left + scrollX;
7290			const startY = rect.top + scrollY;
7291
7292			// 计算元素的宽度和高度
7293			const width = rect.right - rect.left;
7294			const height = rect.bottom - rect.top;
7295
7296			// 计算元素在文档中的结束坐标
7297			const endX = startX + width;
7298			const endY = startY + height;
7299
7300			// 返回包含所有信息的对象(使用ES5兼容语法)
7301		    return {
7302		        startX: startX,
7303		        startY: startY,
7304		        endX: endX,
7305		        endY: endY,
7306		        width: width,
7307		        height: height
7308		    };
7309		},
7310		/*js translate.util.getElementPosition end*/
7311
7312		/*js translate.util.compareStringsIgnoringNumbers start*/
7313		/*	
7314			比较两个字符串,是否除了数字之外,其他的完全一致。
7315			实测 i5 双核 2.4G ,计算1亿次 - 7s
7316
7317			["abc123def", "abc456def", true],
7318	        ["hello7world", "hello8world", true],
7319	        ["test123", "test", true],
7320	        ["123test", "test", true],
7321	        ["abc", "def", false],
7322	        ["a1b2c3", "a4b5c", false],
7323	        ["", "", true],
7324	        ["123", "456", true],
7325	        ["你好123世界", "3你好1世界", true],
7326	        ["你好123世界", "你好世界4", true],
7327		*/
7328		compareStringsIgnoringNumbers: function(a, b) {
7329		    let i = 0, j = 0;
7330		    const lenA = a.length, lenB = b.length;
7331		    
7332		    while (i < lenA || j < lenB) {
7333		        // 跳过a中的数字 (0-9的ASCII码是48-57)
7334		        while (i < lenA && a.charCodeAt(i) >= 48 && a.charCodeAt(i) <= 57) {
7335		            i++;
7336		        }
7337		        
7338		        // 跳过b中的数字
7339		        while (j < lenB && b.charCodeAt(j) >= 48 && b.charCodeAt(j) <= 57) {
7340		            j++;
7341		        }
7342		        
7343		        // 检查是否有一个字符串还有非数字字符而另一个已经结束
7344		        if ((i < lenA) !== (j < lenB)) {
7345		            return false;
7346		        }
7347		        
7348		        // 如果都结束了,返回true
7349		        if (i >= lenA && j >= lenB) {
7350		            return true;
7351		        }
7352		        
7353		        // 比较当前非数字字符
7354		        if (a[i] !== b[j]) {
7355		            return false;
7356		        }
7357		        
7358		        i++;
7359		        j++;
7360		    }
7361		    
7362		    return true;
7363		}
7364		/*js translate.util.compareStringsIgnoringNumbers end*/
7365
7366	},
7367	//机器翻译采用哪种翻译服务
7368	service:{  
7369		/*
7370			name填写的值,参考 translate.service.use 的注释
7371		*/
7372		name:'translate.service',  
7373
7374		/*js translate.service.use start*/
7375		/*
7376			其实就是设置 translate.service.name
7377			可以设置为:
7378
7379			translate.service 自行部署的translate.service 翻译API服务,部署参考: https://translate.zvo.cn/391129.html
7380			client.edge 使用无服务器的翻译,有edge浏览器接口提供翻译服务
7381			siliconflow 使用指点云提供的服务器、硅基流动提供的AI算力进行大模型翻译
7382			giteeAI 使用 giteeAI , 亚洲、美洲、欧洲 网络节点覆盖
7383	
7384		*/
7385		use: function(serviceName){
7386			if(typeof(translate.enterprise) != 'undefined' && translate.enterprise.isUse == true){
7387				translate.log('您已启用了企业级翻译通道 translate.enterprise.use(); (文档:https://translate.zvo.cn/4087.html) , 所以您设置的 translate.service.use(\''+serviceName+'\'); (文档:https://translate.zvo.cn/4081.html) 将失效不起作用,有企业级翻译通道全部接管。');
7388				return;
7389			}
7390			//console.log('--'+serviceName);
7391			if(typeof(serviceName) == 'string'){
7392				translate.service.name = serviceName;
7393				if(serviceName != 'translate.service'){
7394					//增加元素整体翻译能力
7395					translate.whole.enableAll();
7396
7397					if(serviceName.toLowerCase() == 'giteeai'){
7398						//设定翻译接口为GiteeAI的
7399						translate.request.api.host=['https://giteeai.zvo.cn/','https://deutsch.enterprise.api.translate.zvo.cn:1000/','https://api.translate.zvo.cn:1000/', 'https://america.api.translate.zvo.cn:1000/'];
7400						return;
7401					}
7402					if(serviceName.toLowerCase() == 'siliconflow'){
7403						//设定翻译接口为硅基流动的
7404						translate.request.api.host=['https://siliconflow.zvo.cn/','https://america.api.translate.zvo.cn:1414/','https://deutsch.enterprise.api.translate.zvo.cn:1414/'];
7405						return;
7406					}
7407				}
7408			}
7409		},
7410		/*js translate.service.use end*/
7411
7412		/*js translate.service.edge start*/
7413		//客户端方式的edge提供机器翻译服务
7414		edge:{
7415			api:{ //edge浏览器的翻译功能
7416				translate:'https://edge.microsoft.com/translate/translatetext?from={from}&to={to}&isEnterpriseClient=false' //翻译接口
7417			},
7418			language:{
7419
7420				json:[{"id":"ukrainian","name":"Україна","serviceId":"uk"},{"id":"norwegian","name":"Norge","serviceId":"no"},{"id":"welsh","name":"Iaith Weleg","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":"తెలుగుName","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"}
7420,{"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":"ភាសា​ខ្មែរ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"}],
7421				/*
7422					获取map形式的语言列表 
7423					key为 translate.service 的 name  
7424					value为serviceId
7425
7426				*/
7427				getMap:function(){
7428					if(typeof(translate.service.edge.language.map) == 'undefined'){
7429						translate.service.edge.language.map = new Array();
7430						for(var i = 0; i < translate.service.edge.language.json.length; i++){
7431							var item = translate.service.edge.language.json[i];
7432							translate.service.edge.language.map[item.id] = item.serviceId;
7433						}
7434					}
7435					return translate.service.edge.language.map;
7436				}
7437			},
7438			/**
7439			 * edge 进行翻译。 这个传入参数跟 translate.request.post 是一样的
7440			 * @param path 请求的path(path,传入的是translate.request.api.translate 这种的,需要使用 getUrl 来组合真正请求的url )
7441			 * @param data 请求的参数数据
7442			 * @param func 请求完成的回调,传入如 function(data){ console.log(data); }
7443			 */
7444			translate:function(path, data, func, abnormalFunc){
7445				var textArray = JSON.parse(decodeURIComponent(data.text));
7446				let translateTextArray = translate.util.split(textArray, 40000, 900);
7447
7448
7449				var appendXhrData = {
7450					"from":data.from+'',
7451					"to":data.to,
7452					"text":data.text
7453				};
7454				var from = data.from;
7455				if(from != 'auto'){
7456					if(from == 'romance'){
7457						//这里额外加了一个罗曼语族(romance)会自动认为是法语(fr)
7458						from = 'fr';
7459					}else{
7460						from = translate.service.edge.language.getMap()[data.from];
7461					}
7462				}
7463				
7464				var to = translate.service.edge.language.getMap()[data.to];
7465				var transUrl = translate.service.edge.api.translate.replace('{from}',from).replace('{to}',to);
7466
7467				//如果翻译量大,要拆分成多次翻译请求
7468				for(var tai = 0; tai<translateTextArray.length; tai++){
7469					/*
7470					var json = [];
7471					for(var i = 0; i<translateTextArray[tai].length; i++){
7472						json.push({"Text":translateTextArray[tai][i]});
7473					}
7474					*/
7475
7476					translate.request.send(transUrl, JSON.stringify(translateTextArray[tai]), appendXhrData, function(result){
7477						var d = {};
7478						d.info = 'SUCCESS';
7479						d.result = 1;
7480						d.from = data.from;
7481						d.to = data.to;
7482						d.text = [];
7483						for(var t = 0; t < result.length; t++){
7484							d.text.push(result[t].translations[0].text);
7485						}
7486						
7487
7488						//判断当前翻译是否又被拆分过,比如一次超过5万字符的话就要拆分成多次请求了
7489						if(translateTextArray.length > 1){
7490							//这一次翻译呗拆分了多次请求,那么要进行补全数组,使数组个数能一致
7491
7492							/*
7493
7494								注意这里根据数组的长度来判断当前属于第几个数组,
7495								有几率会是拆分的数组,其中有两组的长度是一样的,
7496								这样的话是有问题的,只不过几率很小,就先这样了
7497								但终归还是留了个坑 -- 记录
7498
7499							*/
7500
7501							var currentIndex = -1;	//当前翻译请求属于被拆分的第几个的数组下标,从0开始的
7502							for(var cri = 0; cri < translateTextArray.length; cri++){
7503								if(translateTextArray[cri].length - d.text.length == 0){
7504									currentIndex = cri;
7505									break;
7506								}
7507							}
7508
7509							//进行对前后进行补齐数组
7510							if(currentIndex < 0){
7511								translate.log('------ERROR--------');
7512								translate.log('翻译内容过多,进行拆分,但拆分判断出现异常,currentIndex:-1 请联系 http://translate.zvo.cn/43006.html 说明');
7513							}
7514							//前插入空数组填充
7515							for(var addbeforei = 0; addbeforei<currentIndex; addbeforei++){
7516								var beforeItemArrayLength = translateTextArray[addbeforei].length;
7517								//console.log('beforeItemArrayLength:'+beforeItemArrayLength);
7518								for(var bi = 0; bi < beforeItemArrayLength; bi++){
7519									d.text.unshift(null);
7520								}
7521							}
7522							//后插入空数组填充
7523							for(var addafteri = translateTextArray.length-1; addafteri>currentIndex; addafteri--){
7524								var afterItemArrayLength = translateTextArray[addafteri].length;
7525								for(var bi = 0; bi < afterItemArrayLength; bi++){
7526									d.text.push(null);
7527								}
7528							}
7529							
7530						}
7531						
7532						func(d);
7533					}, 'post', true, {
7534						'Content-Type':'application/json'
7535					}, abnormalFunc, true);
7536					
7537
7538				}
7539
7540
7541				
7542				
7543
7544				
7545			}
7546		}
7547		/*js translate.service.edge end*/
7548	},
7549	//request请求来源于 https://github.com/xnx3/request
7550	request:{
7551		/* 
7552			将通过翻译接口进行翻译请求(/translate.json)的信息记录到 translate.js 本身中
7553			key uuid 每次 translate.execute() 触发生成的uuid
7554			value 	
7555				time: 触发后加入到 data 中的时间,13位时间戳
7556				list: 对象集合,translate.execute() 的触发会发起多次翻译请求,根据识别的语种不同,发起多次网络请求,这里记录的是多次网络请求
7557					[
7558						'english':{						//当前请求是将什么语种进行翻译,也就是 translate.json 请求中的 from 参数
7559							to: chinese_simplified,		//当前请求要翻译为什么语种,也就是 translate.json 请求中的 to 参数
7560							texts:['你好', '世界', ...],	//当前请求要进行翻译的具体文本
7561							nodes:[node1, node2, ...]	//当前请求要翻译的文本所在的node集合,也就是有哪些node中的文本参与了 通过API接口进行翻译文本
7562						},
7563						'korean':{
7564							...
7565						},
7566						...
7567					]
7568
7569			后面要将 translate.translateRequest  合并到这里面		
7570		*/
7571		data:{},
7572		//相关API接口方面
7573		api:{
7574			/**
7575			 * 翻译接口请求的域名主机 host
7576			 * 格式注意前面要带上协议如 https:// 域名后要加 /
7577			 * v2.8.2 增加数组形态,如 ['https://api.translate.zvo.cn/','xxxxx'] 
7578			 */
7579			//host:'https://api.translate.zvo.cn/',
7580			host:['https://api.translate.zvo.cn/','https://america.api.translate.zvo.cn/'],
7581			//host的备用接口,格式同host,可以填写多个,只不过这里是数组格式。只有当主 host 无法连通时,才会采用备host来提供访问。如果为空也就是 [] 则是不采用备方案。
7582			//backupHost:['',''],
7583			language:'language.json', //获取支持的语种列表接口
7584			translate:'translate.json', //翻译接口
7585			ip:'ip.json', //根据用户当前ip获取其所在地的语种
7586			connectTest:'connectTest.json',	//用于 translate.js 多节点翻译自动检测网络连通情况
7587			init:'init.json', //获取最新版本号,跟当前版本进行比对,用于提醒版本升级等使用
7588
7589		},
7590		/*
7591			v3.18.35.20250920 增加
7592		
7593			hosts: 主机域名数组,数组形式,可传入多个主机,传入格式如 ['https://api.translate.zvo.cn/','https://api2.translate.zvo.cn/'] 一定注意最后还有个 /
7594					其中数组的第一个将被优先使用,第一个是主的,可靠性要更高的
7595		*/
7596		setHost:function(hosts){
7597			translate.service.use('translate.service');
7598
7599			if (typeof translate.request.api.host == 'string') {
7600				//单个,那么赋予数组形式
7601				//translate.request.speedDetectionControl.hostQueue = [{"host":translate.request.api.host, time:0 }];
7602				translate.request.api.host = [hosts];
7603			}else{
7604				translate.request.api.host = hosts;
7605			}
7606
7607			translate.request.speedDetectionControl.state = 0; //设置为未进行测速
7608
7609			translate.storage.set('speedDetectionControl_hostQueue', '');
7610			translate.request.speedDetectionControl.hostQueue = [];
7611			translate.request.speedDetectionControl.checkHostQueue = new Array()
7612			//translate.request.speedDetectionControl.checkResponseSpeed_Storage(host, 0)
7613
7614			//进行对host测速
7615			translate.request.speedDetectionControl.checkResponseSpeed();
7616
7617			// init.json 的请求
7618			translate.temp_request_init = undefined;
7619			setTimeout(function(){
7620				translate.request.initRequest();
7621			}, 3000);
7622
7623		},
7624
7625		/*
7626			发起 init.json 的请求
7627			这个应该在translate.execute 未执行完之前就要触发,最好在 setHost() 时、或者刚加载后越早触发越好
7628			它触发多次时,只有第一次才会正常执行。
7629		*/
7630		initRequest:function(){
7631			//初始化请求
7632			if(typeof(translate.request.api.init) == 'string' && translate.request.api.init != null && translate.request.api.init.length > 0){
7633				if(typeof(translate.temp_request_init) == 'undefined'){
7634					translate.temp_request_init = 1;
7635				}else{
7636					//第二次以及之后执行,都直接给返回不允许在执行了
7637					return;
7638				}
7639
7640				try{
7641					translate.request.send(
7642						translate.request.api.init,
7643						{},
7644						{},
7645						function(data){
7646							if (data.result == 0){
7647								translate.log('translate.js init 初始化异常:'+data.info);
7648								return;
7649							}else if(data.result == 1){
7650								//服务端返回的最新版本
7651								var newVersion = translate.util.versionStringToInt(data.version);
7652								//当前translate.js的版本
7653								var currentVersion = translate.util.versionStringToInt(translate.version.replace('v',''));
7654
7655								if(newVersion > currentVersion){
7656									translate.log('Tip : translate.js find new version : '+data.version);
7657								}
7658							}
7659						},
7660						'post',
7661						true,
7662						null,
7663						function(data){
7664							//console.log('eeerrr');
7665						},
7666						false
7667					);
7668				}catch(e){
7669				}
7670			}
7671		},
7672
7673		/*
7674			追加参数,  v3.15.9.20250527 增加
7675			所有通过 translate.request.send 进行网络请求的,都会追加上这个参数
7676			默认是空,没有任何追加参数。
7677
7678			设置方式: https://translate.zvo.cn/471711.html
7679			translate.request.appendParams = {
7680				key1:'key1',
7681				key2:'key2'
7682			}
7683		*/
7684		appendParams:{
7685
7686		},
7687		/*
7688			追加header头的参数,  v3.15.13 增加
7689			所有通过 translate.request.send 进行网络请求的,都会追加上这个参数
7690			默认是空,没有任何追加参数。
7691
7692			设置方式: https://translate.zvo.cn/471711.html
7693			translate.request.appendHeaders = {
7694				key1:'key1',
7695				Aauthorization:'Bearer xxxxxxxxxx'
7696			}
7697		*/
7698		appendHeaders:{
7699
7700		},
7701		/*
7702			请求后端接口的响应。无论是否成功,都会触发此处。
7703			另外当 xhr.readyState==4 的状态时才会触发。
7704			此处会在接口请求响应后、且在translate.js处理前就会触发
7705			@param xhr XMLHttpRequest 接口请求
7706			
7707		*/
7708		response:function(xhr){
7709			//console.log('response------');
7710			//console.log(xhr);
7711		},
7712
7713
7714		/*
7715			速度检测控制中心, 检测主备翻译接口的响应速度进行排列,真正请求时,按照排列的顺序进行请求
7716			v2.8.2增加	
7717			
7718			storage存储方面
7719			storage存储的key  						存的什么
7720			speedDetectionControl_hostQueue			hostQueue
7721			speedDetectionControl_hostQueueIndex	当前要使用的是 hostQueue 中的数组下标。如果没有,这里默认视为0
7722			speedDetectionControl_lasttime			最后一次执行速度检测的时间戳,13位时间戳
7723
7724
7725			
7726		*/
7727		speedDetectionControl:{
7728			/*
7729				当前测速的状态,
7730				0 尚未进行
7731				1 进行中
7732				2 已测速完毕
7733
7734				这个也是用于判断是否为0,来避免多次发起测速情况
7735			 */ 
7736			state: 0, 
7737
7738			/*
7739				
7740				进行 connect主节点缩减的时间,单位是毫秒.
7741				这个是进行 translate.request.speedDetectionControl.checkResponseSpeed() 节点测速时,translate.request.api.host 第一个元素是默认的主节点。
7742				主节点在实际测速完后,会减去一定的时间,以便让用户大部分时间可以使用主节点,而不必走分节点。
7743				例如主节点实际响应速度 3500 毫秒,那么会减去这里设置的2000毫秒,记为 1500 毫秒
7744				当然如果是小于这里设置的2000毫秒,那么会记为0毫秒。
7745				这样再跟其他分节点的响应时间进行对比,主节点只要不是响应超时,就会有更大的几率被选中为实际使用的翻译的节点
7746				
7747				这里的单位是毫秒。
7748				v2.10.2.20231225 增加
7749			*/
7750			hostMasterNodeCutTime:2000,	
7751
7752			/*
7753				翻译的队列,这是根据网络相应的速度排列的,0下标为请求最快,1次之...
7754				其格式为:
7755					[
7756						{
7757							"host":"xxxxxxxx",
7758							"time":123 			//这里的单位是毫秒
7759						},
7760						{
7761							"host":"xxxxxxxx",
7762							"time":123 			//这里的单位是毫秒
7763						}
7764					]
7765			*/
7766			hostQueue:[],	
7767			hostQueueIndex:-1,	//当前使用的 hostQueue的数组下标,  -1表示还未初始化赋予值,不可直接使用,通过 getHostQueueIndex() 使用
7768			disableTime:1000000,	//不可用的时间,storage中存储的 speedDetectionControl_hostQueue 其中 time 这里,如果值是 这个,便是代表这个host处于不可用状态
7769
7770			/*
7771				设置当前使用的翻译通道 host
7772				适用于 进行中时,中途切临时换翻译通道。
7773			*/
7774			setCurrentHost:function(host){
7775				translate.storage.set('speedDetectionControl_hostQueue','');  
7776				translate.request.api.host=host;
7777				translate.request.speedDetectionControl.checkHostQueue = new Array();
7778				translate.request.speedDetectionControl.checkResponseSpeed_Storage(host, 0);
7779			},
7780
7781			//获取 host queue 队列
7782			getHostQueue:function(){
7783				if(translate.request.speedDetectionControl.hostQueue.length == 0){
7784					//还没有,先从本地存储中取,看之前是否已经设置过了
7785					// 只有经过真正的网络测速后,才会加入 storage 的 hostQueue
7786					var storage_hostQueue = translate.storage.get('speedDetectionControl_hostQueue');
7787					if(storage_hostQueue == null || typeof(storage_hostQueue) == 'undefined' || storage_hostQueue == ''){
7788						//本地存储中没有,也就是之前没设置过,是第一次用,那么直接讲 translate.request.api.host 赋予之
7789						//translate.request.api.host
7790				
7791						if(typeof(translate.request.api.host) == 'string'){
7792							//单个,那么赋予数组形式
7793							//translate.request.speedDetectionControl.hostQueue = [{"host":translate.request.api.host, time:0 }];
7794							translate.request.api.host = [''+translate.request.api.host];
7795						}
7796
7797						//数组形态,多个,v2.8.2 增加多个,根据优先级返回
7798						translate.request.speedDetectionControl.hostQueue = [];
7799						for(var i = 0; i<translate.request.api.host.length; i++){
7800							var h = translate.request.api.host[i];
7801							//console.log(h);
7802							translate.request.speedDetectionControl.hostQueue[i] = {"host":h, time:0 };
7803						}
7804						//console.log(translate.request.speedDetectionControl.hostQueue);
7805						
7806					}else{
7807						//storage中有,那么赋予
7808						translate.request.speedDetectionControl.hostQueue = JSON.parse(storage_hostQueue);
7809						//console.log(storage_hostQueue);
7810						//console.log(translate.request.speedDetectionControl.hostQueue[0].time);
7811					}
7812
7813					//console.log(translate.request.speedDetectionControl.hostQueue)
7814
7815					/*
7816						当页面第一次打开初始化这个时才会进行测速,另外测速也是要判断时间的,五分钟一次
7817						进行测速
7818					*/
7819					var lasttime = translate.storage.get('speedDetectionControl_lasttime');
7820					if(lasttime == null || typeof(lasttime) == 'undefined'){
7821						lasttime = 0;
7822					}
7823					var updateTime = 60000;	//1分钟检测一次
7824					if(new Date().getTime() - lasttime > updateTime){
7825						translate.request.speedDetectionControl.checkResponseSpeed();
7826					}
7827					
7828				}
7829				
7830
7831				return translate.request.speedDetectionControl.hostQueue;
7832			},
7833
7834			/*
7835				服务于 checkResponseSpeed 用于将测试结果存入 storage
7836				time: 当前接口请求的耗时,单位是毫秒。如果是 1000000 那么表示这个接口不可用
7837			*/
7838			checkResponseSpeed_Storage:function(host, time){
7839
7840				translate.request.speedDetectionControl.checkHostQueue.push({"host":host, "time":time });
7841				//按照time进行排序
7842				translate.request.speedDetectionControl.checkHostQueue.sort((a, b) => a.time - b.time);
7843
7844				//存储到 storage 持久化
7845				translate.storage.set('speedDetectionControl_hostQueue',JSON.stringify(translate.request.speedDetectionControl.checkHostQueue));
7846				translate.storage.set('speedDetectionControl_lasttime', new Date().getTime());
7847
7848				translate.request.speedDetectionControl.hostQueue = translate.request.speedDetectionControl.checkHostQueue;
7849			},
7850
7851			/*
7852				执行测试响应速度动作
7853			*/
7854			checkResponseSpeed:function(){
7855				translate.request.speedDetectionControl.state = 1; //设置为进行测速中
7856
7857				var headers = {
7858					'content-type':'application/x-www-form-urlencoded',
7859				};
7860
7861				if(typeof(translate.request.api.connectTest) != 'string' || translate.request.api.connectTest == null || translate.request.api.connectTest.length < 1){
7862					return;
7863				}
7864				
7865
7866				translate.request.speedDetectionControl.checkHostQueue = []; //用于实际存储
7867				translate.request.speedDetectionControl.checkHostQueueMap = []; //只是map,通过key取值,无其他作用
7868
7869				if(typeof(translate.request.api.host) == 'string'){
7870					//单个,那么赋予数组形式
7871					translate.request.api.host = [''+translate.request.api.host];
7872				}
7873
7874				for(var i = 0; i < translate.request.api.host.length; i++){
7875					var host = translate.request.api.host[i];
7876					// 获取当前时间的时间戳
7877					translate.request.speedDetectionControl.checkHostQueueMap[host] = {
7878						start:new Date().getTime()
7879					};
7880
7881					
7882					try{
7883						translate.request.send(
7884							host+translate.request.api.connectTest,
7885							{host:host},
7886							{host:host},
7887							function(data){
7888								//只要其中某个取得响应,都代表测速完成
7889								translate.request.speedDetectionControl.state = 2;
7890
7891								var host = data.info;
7892								var map = translate.request.speedDetectionControl.checkHostQueueMap[host];
7893								var time = new Date().getTime() - map.start;
7894
7895								if(translate.request.api.host[0] == host){
7896									//console.log('如果是第一个,那么是主的,默认允许缩减2000毫秒,也就是优先使用主的');
7897									time = time - translate.request.speedDetectionControl.hostMasterNodeCutTime;
7898									if(time < 0){
7899										time = 0;
7900									}
7901								}
7902
7903								translate.request.speedDetectionControl.checkResponseSpeed_Storage(host, time);
7904								/*
7905								translate.request.speedDetectionControl.checkHostQueue.push({"host":host, "time":time });
7906								//按照time进行排序
7907								translate.request.speedDetectionControl.checkHostQueue.sort((a, b) => a.time - b.time);
7908
7909								//存储到 storage 持久化
7910								translate.storage.set('speedDetectionControl_hostQueue',JSON.stringify(translate.request.speedDetectionControl.checkHostQueue));
7911								translate.storage.set('speedDetectionControl_lasttime', new Date().getTime());
7912
7913								translate.request.speedDetectionControl.hostQueue = translate.request.speedDetectionControl.checkHostQueue;
7914								//console.log(translate.request.speedDetectionControl.hostQueue);
7915								*/
7916							},
7917							'post',
7918							true,
7919							headers,
7920							function(data){
7921								//只要其中某个取得响应,都代表测速完成
7922								translate.request.speedDetectionControl.state = 2;
7923
7924								//translate.request.speedDetectionControl.checkResponseSpeed_Storage(host, time);
7925								var hostUrl = data.requestURL.replace(translate.request.api.connectTest,'');
7926								translate.request.speedDetectionControl.checkResponseSpeed_Storage(hostUrl, translate.request.speedDetectionControl.disableTime);
7927							},
7928							false
7929						);
7930					}catch(e){
7931						//console.log('e0000');
7932						translate.log(e);
7933						//time = 300000; //无法连接的,那么赋予 300 秒吧
7934					}
7935
7936				}
7937				
7938			},
7939
7940			//获取当前使用的host的数组下标
7941			getHostQueueIndex:function(){
7942				if(translate.request.speedDetectionControl.hostQueueIndex < 0){
7943					//页面当前第一次使用,赋予值
7944					//先从 storage 中取
7945					var storage_index = translate.storage.get('speedDetectionControl_hostQueueIndex');
7946					if(typeof(storage_index) == 'undefined' || storage_index == null){
7947						//存储中不存在,当前用户(浏览器)第一次使用,默认赋予0
7948						translate.request.speedDetectionControl.hostQueueIndex = 0;
7949						translate.storage.set('speedDetectionControl_hostQueueIndex',0);
7950					}else{
7951						translate.request.speedDetectionControl.hostQueueIndex = storage_index;
7952					}
7953				}
7954				return translate.request.speedDetectionControl.hostQueueIndex;
7955			},
7956
7957			//获取当前要使用的host
7958			getHost:function(){
7959				var queue = translate.request.speedDetectionControl.getHostQueue();
7960				//console.log(queue);
7961				var queueIndex = translate.request.speedDetectionControl.getHostQueueIndex();
7962				if(queue.length > queueIndex){
7963					//正常,没有超出越界
7964					
7965				}else{
7966					//异常,下标越界了!,固定返回最后一个
7967					translate.log('异常,下标越界了!index:'+queueIndex);
7968					queueIndex = queue.length-1;
7969				}
7970				//console.log(queueIndex);
7971				return queue[queueIndex].host;
7972			},
7973
7974		},
7975		//生成post请求的url
7976		getUrl:function(path){
7977			var currentHost = translate.request.speedDetectionControl.getHost();
7978			var url = currentHost+path+'?v='+translate.version;
7979			//console.log('url: '+url);
7980			return url;
7981		},
7982		/**
7983		 * post请求
7984		 * @param path 请求的path(path,传入的是translate.request.api.translate 这种的,需要使用 getUrl 来组合真正请求的url )
7985		 * @param data 请求的参数数据,传入如 
7986		 * 		{
7987		 * 			from: "chinese_simplified",
7988		 * 			text: "%5B%22%E4%BD%A0%E5%A5%BD%EF%BC%8C%E6%88%91",
7989		 * 			to: "chinese_traditional
7990		 * 		}
7991		 * 		
7992		 * @param func 请求完成的回调,也就是只要响应码是 200 ,则会触发这个方法。 传入如 function(responseData, requestData){ console.log(responseData); }
7993		 * 				其中的参数:
7994		 * 					responseData 响应的数据
7995		 * 					requestData post请求所携带的数据
7996		 * 				注意,是响应数据是第一个参数,请求数据是第二个参数。 以向前兼容
7997		 * @param abnormalFunc 响应异常所执行的方法,响应码不是200就会执行这个方法 ,传入如 function(xhr){}  另外这里的 xhr 会额外有个参数  xhr.requestURL 返回当前请求失败的url
7998		 */
7999		post:function(path, data, func, abnormalFunc){
8000			var headers = {
8001				'content-type':'application/x-www-form-urlencoded',
8002			};
8003			if(typeof(data) == 'undefined'){
8004				return;
8005			}
8006
8007			//企业级翻译自动检测
8008			if(typeof(translate.enterprise) != 'undefined'){
8009				translate.enterprise.automaticAdaptationService();
8010			}
8011			
8012			// ------- edge start --------
8013			var url = translate.request.getUrl(path);
8014			//if(url.indexOf('edge') > -1 && path == translate.request.api.translate){
8015			if(translate.service.name == 'client.edge'){	
8016				if(path == translate.request.api.translate){
8017					translate.service.edge.translate(path, data, func, abnormalFunc);
8018					return;
8019				}
8020				if(path == translate.request.api.language){
8021					var d = {};
8022					d.info = 'SUCCESS';
8023					d.result = 1;
8024					d.list = translate.service.edge.language.json;
8025					func(d);
8026					return;
8027				}
8028				
8029				//return;
8030			}
8031			// ------- edge end --------
8032
8033			this.send(path, data, data, func, 'post', true, headers, abnormalFunc, true);
8034		},
8035		/**
8036		 * 发送请求
8037		 * url 请求的url或者path(path,传入的是translate.request.api.translate 这种的,需要使用 getUrl 来组合真正请求的url )
8038		 * data 请求的数据,如 {"author":"管雷鸣",'site':'www.guanleiming.com'} 
8039		 * appendXhrData 附加到 xhr.data 中的对象数据,传入比如  {"from":"english","to":"japanese"} ,他会直接赋予 xhr.data
8040		 * func 请求完成的回调,也就是只要响应码是 200 ,则会触发这个方法。 传入如 function(requestData, responseData){ console.log(responseData); }
8041		 * 				其中的参数:
8042		 * 					requestData post请求所携带的数据
8043		 * 					responseData 响应的数据
8044		 * method 请求方式,可传入 post、get
8045		 * isAsynchronize 是否是异步请求, 传入 true 是异步请求,传入false 是同步请求。 如果传入false,则本方法返回xhr
8046		 * headers 设置请求的header,传入如 {'content-type':'application/x-www-form-urlencoded'};
8047		 * abnormalFunc 响应异常所执行的方法,响应码不是200就会执行这个方法 ,传入如 function(xhr){}  另外这里的 xhr 会额外有个参数  xhr.requestURL 返回当前请求失败的url
8048		 * showErrorLog 是否控制台打印出来错误日志,true打印, false 不打印
8049		 */
8050		send:function(url, data, appendXhrData, func, method, isAsynchronize, headers, abnormalFunc, showErrorLog){
8051			//post提交的参数
8052			var params = '';
8053
8054			if(data == null || typeof(data) == 'undefined'){
8055				data = {};
8056			}
8057			
8058			if(typeof(data) == 'string'){
8059				params = data; //payload 方式 , edge 的方式
8060			}else{
8061				//表单提交方式
8062				
8063				//加入浏览器默认语种  v3.6.1 增加,以便更好的进行自动切换语种
8064				data.browserDefaultLanguage = translate.util.browserDefaultLanguage();
8065				
8066				//追加附加参数
8067				for(var apindex in translate.request.appendParams){
8068					if (!translate.request.appendParams.hasOwnProperty(apindex)) {
8069			    		continue;
8070			    	}
8071					data[apindex] = translate.request.appendParams[apindex];
8072				}
8073
8074				if(typeof(translate.enterprise) != 'undefined'){
8075					//加入key
8076					if(typeof(translate.enterprise.key) != 'undefined' && typeof(translate.enterprise.key) == 'string' && translate.enterprise.key.length > 0){
8077						data.key = translate.enterprise.key;
8078					}
8079				}
8080				
8081				//组合参数
8082				for(var index in data){
8083					if (!data.hasOwnProperty(index)) {
8084			    		continue;
8085			    	}
8086					if(params.length > 0){
8087						params = params + '&';
8088					}
8089					params = params + index + '=' + data[index];
8090				}
8091			}
8092			if(url.indexOf('https://') == 0 || url.indexOf('http://') == 0){
8093				//采用的url绝对路径
8094			}else{
8095				//相对路径,拼接上host
8096				url = translate.request.getUrl(url);
8097			}
8098
8099			var xhr=null;
8100			try{
8101				xhr=new XMLHttpRequest();
8102			}catch(e){
8103				xhr=new ActiveXObject("Microsoft.XMLHTTP");
8104			}
8105			xhr.data=appendXhrData;
8106			//2.调用open方法(true----异步)
8107			xhr.open(method,url,isAsynchronize);
8108			//设置headers
8109			if(headers != null){
8110				for(var index in headers){
8111					if (!headers.hasOwnProperty(index)) {
8112			    		continue;
8113			    	}
8114					xhr.setRequestHeader(index,headers[index]);
8115				}
8116			}
8117
8118			//追加附加参数
8119			for(var ahindex in translate.request.appendHeaders){
8120				if (!translate.request.appendHeaders.hasOwnProperty(ahindex)) {
8121		    		continue;
8122		    	}
8123				xhr.setRequestHeader(ahindex,translate.request.appendHeaders[ahindex]);
8124			}
8125
8126			if(translate.service.name != 'client.edge'){
8127				xhr.setRequestHeader('currentpage', window.location.href+'');
8128			}
8129			xhr.send(params);
8130			//4.请求状态改变事件
8131			xhr.onreadystatechange=function(){
8132			    if(xhr.readyState==4){
8133			    	translate.request.response(xhr); //自定义响应的拦截
8134
8135			        if(xhr.status==200){
8136			        	//请求正常,响应码 200
8137			        	var json = null;
8138			        	if(typeof(xhr.responseText) == 'undefined' || xhr.responseText == null){
8139			        		//相应内容为空
8140			        	}else{
8141			        		//响应内容有值
8142			        		if(xhr.responseText.indexOf('{') > -1 && xhr.responseText.indexOf('}') > -1){
8143				        		//应该是json格式
8144				        		try{
8145					        		json = JSON.parse(xhr.responseText);
8146					        	}catch(e){
8147					        		translate.log(e);
8148					        	}
8149				        	}
8150			        	}
8151			        	
8152			        	if(json === null){
8153			        		func(xhr.responseText);
8154			        	}else{
8155			        		func(json, xhr.data);
8156			        	}
8157			        }else{
8158			        	if(showErrorLog){
8159			        		if(url.indexOf(translate.request.api.connectTest) > -1){
8160			        			//测试链接速度的不在报错里面
8161			        		}else{
8162
8163			        			//判断是否是v2版本的翻译,如果是 translate.service 模式并且没有使用企业级翻译,参会提示
8164			        			//2024.3月底开始,翻译使用量增加的太快,开源的翻译服务器有点扛不住经常出故障,所以直接把这个提示加到这里
8165			        			if(translate.service.name == 'translate.service'){
8166			        				translate.log('----- translate.js 提示 -----\n翻译服务响应异常,解决这种情况可以有两种方案:\n【方案一】:使用采用最新版本 3.16.0及更高版本,js引用文件为 https://cdn.staticfile.net/translate.js/3.16.0/translate.js 并且使用 client.edge 模式 (增加一行设置代码就好,可参考 https://translate.zvo.cn/4081.html ),这样就不会再出现这种情况了,而且这个方案也是完全免费的。 \n【方案二】:采用企业级稳定翻译通道 ,但是这个相比于 方案一 来说,是有一定的收费的,大概一年600,这个就是专门为了高速及é«
8166˜ç¨³å®šå‡†å¤‡çš„,而相比于这个方案二,方案一则是全免费的。 因为方案二我们是部署了两个集群,而每个集群又下分了数个网络节点,包含中国大陆、香港、美国、欧洲、 等多个州,充分保障稳定、高效,同样也产生了不少成本,所以才需要付费。更多信息说明可以参考: http://translate.zvo.cn/4087.html \n【方案三】:私有部署你自己的翻译通道,并且启用内存级翻译缓存,毫秒级响应,但是需要依赖一台1æ ¸2G服务器,是最推荐的方式。具体参考:https://translate.zvo.cn/391129.html\n-------------');
8167			        			}
8168
8169			        			//console.log(xhr);
8170					        	translate.log('------- translate.js service api response error --------');
8171					        	translate.log('    http code : '+xhr.status);
8172					        	translate.log('    response : '+xhr.response);
8173					        	translate.log('    request url : '+url);
8174					        	translate.log('    request data : '+JSON.stringify(data));
8175					        	translate.log('    request method : '+method);
8176					        	translate.log('---------------------- end ----------------------');
8177			        		}
8178			        		
8179			        	}
8180			        	xhr.requestURL = url;
8181			        	if(abnormalFunc != null){
8182			        		abnormalFunc(xhr);
8183			        	}
8184			        }
8185			    }
8186			}
8187			return xhr;
8188		},
8189		/*
8190
8191			手动进行翻译操作。参数说明:
8192				texts: 可传入要翻译的文本、以及文本数组。 比如要一次翻译多个句子,那就可以传入数组的方式
8193				function: 翻译完毕后的处理函数。传入如 function(data){ console.log(data); }
8194						  注意,返回的data.result 为 1,则是翻译成功。  为0则是出错,可通过data.info 得到错误原因。 更详细说明参考: http://api.zvo.cn/translate/service/20230807/translate.json.html
8195
8196				abnormalFunc: 翻译失败后的处理函数。传入如 function(xhr){ console.log(xhr); }
8197						  注意,这里的 xhr 是 XMLHttpRequest 对象,可以通过 xhr.status 获取响应状态码,通过 xhr.responseText 获取响应内容。
8198
8199			使用案例一: 
8200			translate.request.translateText('你好,我是翻译的内容', function(data){
8201				//打印翻译结果
8202				console.log(data);
8203			}, function(xhr){
8204				//打印翻译失败后的信息
8205				console.log(xhr);
8206			});
8207			
8208			使用案例二:
8209			var texts = ['我是翻译的第一句','我是翻译的第二句','我是翻译的第三句'];
8210			translate.request.translateText(texts, function(data){
8211				//打印翻译结果
8212				console.log(data);
8213			}, function(xhr){
8214				//打印翻译失败后的信息
8215				console.log(xhr);
8216			});
8217
8218			使用案例三:
8219			var obj = {
8220				from:'chinese_simplified',
8221				to:'english',
8222				texts: ['我是翻译的第一句','我是翻译的第二句','我是翻译的第三句']
8223			}
8224			translate.request.translateText(obj, function(data){
8225				//打印翻译结果
8226				console.log(data);
8227			}, function(xhr){
8228				//打印翻译失败后的信息
8229				console.log(xhr);
8230			});
8231		*/
8232		translateText:function(obj, func, abnormalFunc){
8233			var texts = new Array();
8234			var from = translate.language.getLocal();
8235			var to = translate.language.getCurrent();
8236
8237			if(typeof(obj) == 'string'){
8238				//案例一的场景,传入单个字符串
8239				texts[0] = obj;
8240			}else{
8241				//不是字符串了,而是对象了,判断是案例二还是案例三
8242
8243				var type = Object.prototype.toString.call(obj);
8244				//console.log(type);
8245				if(type == '[object Array]'){
8246					//案例二
8247					texts = obj;
8248				}else if(type == '[object Object]'){
8249					//案例三
8250					if(typeof(obj.texts) == 'undefined'){
8251						translate.log('translate.request.translateText 传入的值类型异常,因为你没有传入 obj.texts 要翻译的具体文本! 请查阅文档: https://translate.zvo.cn/4077.html');	
8252					}
8253					if(typeof(obj.texts) == 'string'){
8254						//单个字符串
8255						texts = [obj.texts];
8256					}else{
8257						//多个字符串,数组形态
8258						texts = obj.texts;
8259					}
8260					if(typeof(obj.from) == 'string' && obj.from.length > 0){
8261						from = obj.from;
8262					}
8263					if(typeof(obj.to) == 'string' && obj.to.length > 0){
8264						to = obj.to;
8265					}
8266				}else{
8267					translate.log('translate.request.translateText 传入的值类型错误,请查阅文档: https://translate.zvo.cn/4077.html');
8268					return;
8269				}
8270			}
8271			//console.log(obj);
8272			//返回的翻译结果,下标跟 obj.texts 一一对应的
8273			var translateResultArray = new Array();
8274
8275			// 筛选需要翻译的文本及其原始索引
8276  			var apiTranslateText = [];
8277			var apiTranslateArray = {};
8278			for(var i = 0; i < texts.length; i++){
8279				//判断是否在浏览器缓存中出现了
8280				var hash = translate.util.hash(texts[i]);
8281				var cache = translate.storage.get('hash_'+to+'_'+hash);
8282				//console.log(hash+'\t'+texts[i]+'\t'+cache);
8283				if(cache != null && cache.length > 0){
8284					//缓存中发现了这个得结果,那这个就不需要再进行翻译了
8285					translateResultArray[i] = cache;
8286				}else{
8287					translateResultArray[i] = '';
8288					apiTranslateText.push(texts[i]);
8289					apiTranslateArray[hash] = i;
8290				}
8291			}
8292			if (apiTranslateText.length == 0) {
8293				//没有需要进行通过网络API翻译的任务了,全部命中缓存,那么直接返回
8294				var data = {
8295					from:from,
8296					to: to,
8297					text:translateResultArray,
8298					result:1
8299				};
8300				//console.log(data);
8301			    func(data);
8302			    return;
8303			}
8304
8305
8306
8307			//还有需要进行通过API接口进行翻译的文本,需要调用翻译接口
8308			if(typeof(translate.request.api.translate) != 'string' || translate.request.api.translate == null || translate.request.api.translate.length < 1){
8309				//用户已经设置了不掉翻译接口进行翻译
8310				return;
8311			}
8312
8313			var url = translate.request.api.translate;
8314			var data = {
8315				from:from,
8316				to: to,
8317				text:encodeURIComponent(JSON.stringify(apiTranslateText))
8318			};
8319			//console.log(apiTranslateText);
8320			translate.request.post(url, data, function(responseData, requestData){
8321				//console.log(responseData); 
8322				//console.log(data); 
8323				if(responseData.result != 1){
8324					translate.log('=======ERROR START=======');
8325					translate.log('from : '+requestData.from);
8326					translate.log('to : '+requestData.to);
8327					translate.log('translate text array : '+texts);
8328					translate.log('response error info: '+responseData.info);
8329					translate.log('=======ERROR END  =======');
8330					//return;
8331				}
8332
8333				for(var i = 0; i < responseData.text.length; i++){
8334					//将翻译结果以 key:hash  value翻译结果的形式缓存
8335					var hash = translate.util.hash(apiTranslateText[i]);
8336					translate.storage.set('hash_'+to+'_'+hash, responseData.text[i]);
8337					//如果离线翻译启用了全部提取,那么还要存入离线翻译指定存储
8338					if(translate.offline.fullExtract.isUse){
8339						translate.offline.fullExtract.set(hash, apiTranslateText[i], data.to, responseData.text[i]);
8340					}
8341
8342					//进行组合数据到 translateResultArray
8343					translateResultArray[apiTranslateArray[hash]] = responseData.text[i];
8344				}
8345				responseData.text = translateResultArray;			
8346
8347				func(responseData);
8348			}, (function(xhr){
8349				if(abnormalFunc && typeof(abnormalFunc) == 'function'){
8350					abnormalFunc(xhr);
8351				}
8352			}));
8353		},
8354		listener:{
8355			//是否已经启动过 translate.request.listener.addListener() 开始监听了,开始了则是true,默认没开始则是false
8356			isStart:false,
8357			//用户的代码里是否启用了 translate.request.listener.start() ,true:启用
8358			use:false, 
8359			minIntervalTime:800, // 两次触发的最小间隔时间,单位是毫秒,这里默认是800毫秒。最小填写时间为 200毫秒
8360			lasttime:0,// 最后一次触发执行 translate.execute() 的时间,进行执行的那一刻,而不是执行完。13位时间戳
8361			/*
8362				设置要在未来的某一时刻执行,单位是毫秒,13位时间戳。
8363				执行时如果当前时间大于这个数,则执行,并且将这个数置为0。
8364				会有一个循环执行函数每间隔200毫秒触发一次
8365			*/
8366			executetime:0,
8367			/*
8368				进行翻译时,延迟翻译执行的时间
8369				当ajax请求结束后,延迟这里设置的时间,然后自动触发 translate.execute() 执行
8370			*/
8371			delayExecuteTime:200, 
8372			/*
8373				满足ajax出发条件,设置要执行翻译。
8374				注意,设置这个后并不是立马就会执行,而是加入了一个执行队列,避免1秒请求了10次会触发10次执行的情况
8375			*/
8376			addExecute:function(){
8377				var currentTime = Date.now();
8378				if(translate.request.listener.lasttime == 0){
8379					//是第一次,lasttime还没设置过,那么直接设置执行时间为当前时间
8380					translate.request.listener.executetime = currentTime;
8381					translate.request.listener.lasttime = 1;
8382				}else{
8383					//不是第一次了
8384
8385					if(translate.request.listener.executetime > 1){
8386						//当前有执行队列等待,不用再加入执行等待了
8387						//console.log('已在执行队列,不用再加入了 '+currentTime);
8388					}else{
8389						//执行队列中还没有,可以加入执行命令
8390
8391						if(currentTime < translate.request.listener.lasttime + translate.request.listener.minIntervalTime){
8392							//如果当前时间小于最后一次执行时间+间隔时间,那么就是上次才刚刚执行过,这次执è
8392¡Œçš„太快了,那么赋予未来执行翻译的时间为最后一次时间+间隔时间
8393							translate.request.listener.executetime = translate.request.listener.lasttime + translate.request.listener.minIntervalTime;
8394							//console.log('addexecute - < 如果当前时间小于最后一次执行时间+间隔时间,那么就是上次才刚刚执行过,这次执行的太快了,那么赋予未来执行翻译的时间为最后一次时间+间隔时间');
8395						}else{
8396							translate.request.listener.executetime = currentTime;
8397							//console.log('addexecute -- OK ');
8398						}
8399					}
8400					
8401
8402				}
8403
8404				
8405			},
8406			/*
8407				自定义是否会被触发的方法判断
8408				url 当前ajax请求的url,注意是这个url请求完毕获取到200相应的内容时才会触发此方法
8409				返回值 return true; 默认是不管什么url,全部返回true,表示会触发翻译自动执行 translate.execute; ,如果你不想让某个url触发翻译,那么你可以自行在这个方法中用代码进行判断,然后返回false,那么这个url将不会自动触发翻译操作。
8410			*/
8411			trigger:function(url){
8412				return true;
8413			},
8414
8415			/*js translate.request.listener.start start*/
8416			/*
8417				启动根据ajax请求来自动触发执行翻译,避免有时候有的框架存在漏翻译的情况。
8418				这个只需要执行一次即可,如果执行多次,只有第一次会生效
8419			*/
8420			start:function(){
8421				translate.request.listener.use = true;
8422			},
8423			/*js translate.request.listener.start end*/
8424
8425			// 当 translate.execute() 触发时,也就是触发了生命周期的 start 时,才会启动这里。这里要在翻译进行后才能触发,不然提前出发会导致跟用户设置的启动时间不相符造成异常
8426			addListener:function(){
8427				if(translate.request.listener.use == false){
8428					//根本就没设置启用,直接推出
8429					return;
8430				}
8431
8432				//确保这个方法只会触发一次,不会过多触发
8433				if(typeof(translate.request.listener.isStart) != 'undefined' && translate.request.listener.isStart == true){
8434					return;
8435				}else{
8436					translate.request.listener.isStart = true;
8437				}
8438
8439				//增加一个没100毫秒检查一次执行任务的线程
8440				setInterval(function(){
8441					var currentTime = Date.now();
8442					//console.log(translate.request.listener.executetime)
8443					if(translate.request.listener.executetime > 1 && currentTime > translate.request.listener.executetime+translate.request.listener.delayExecuteTime){
8444						translate.request.listener.executetime = 0;
8445						translate.request.listener.lasttime = currentTime;
8446						if(translate.executeTriggerNumber > 0){ //已经执行过了 translate.execute() ,那么才会触发
8447							try{
8448								//console.log('translate.request.listener.start ... 执行翻译 --'+currentTime);
8449								translate.execute();
8450							}catch(e){
8451								translate.log(e);
8452							}
8453						}
8454					}
8455				}, 100);
8456
8457				if(typeof(PerformanceObserver) == 'undefined'){
8458					translate.log('因浏览器版本较低, translate.request.listener.start() 中 PerformanceObserver 对象不存在,浏览器不支持,所以 translate.request.listener.start() 未生效。');
8459					return;
8460				}
8461
8462				const observer = new PerformanceObserver((list) => {
8463					var translateExecute = false;	//是否需要执行翻译 true 要执行
8464				    for(var e = 0; e < list.getEntries().length; e++){
8465				    	var entry = list.getEntries()[e];
8466				    	//console.log(entry)
8467
8468				    	if (entry.initiatorType === 'fetch' || entry.initiatorType === 'xmlhttprequest') {
8469				        	var url = entry.name;
8470				        	//console.log(url);
8471				        	//判断url是否是当前translate.js本身使用的
8472				        	if(typeof(translate.request.api.host) == 'string'){
8473				        		translate.request.api.host = [translate.request.api.host];
8474				        	}
8475				        	var ignoreUrl = false; // 是否是忽略的url true是
8476
8477				        	//translate.service 模式判断
8478				        	for(var i = 0; i < translate.request.api.host.length; i++){
8479				        		if(url.indexOf(translate.request.api.host[i]) > -1){
8480				        			//是,那么直接忽略
8481				        			ignoreUrl = true;
8482				        			break;
8483				        		}
8484				        	}
8485				        	//client.edge 判断   translate.service.edge可能会被精简translate.js定制时给直接干掉,所以提前加个判断
8486				        	if(typeof(translate.service.edge) != 'undefined'){
8487				        		if(url.indexOf('edge.microsoft.com/translate/translatetext') > -1){
8488					        		ignoreUrl = true;
8489					        	}
8490				        	}
8491				        	
8492				        	if(ignoreUrl){
8493				        		//console.log('忽略:'+url);
8494								continue;
8495				        	}
8496				        	if(translate.request.listener.trigger()){
8497				        		//正常,会触发翻译,也是默认的
8498				        	}else{
8499				        		//不触发翻译,跳过
8500				        		continue;
8501				        	}
8502
8503				        	translateExecute = true;
8504				        	break;
8505				        }
8506				    }
8507				    if(translateExecute){
8508				    	//console.log('translate.request.listener.addExecute() -- '+Date.now());
8509				    	translate.request.listener.addExecute();
8510				    }
8511				});
8512
8513				//v3.15.14.20250617 增加
8514				// 优先使用 entryTypes  兼容 ES5 的写法
8515
8516				var supportedTypes = PerformanceObserver.supportedEntryTypes;
8517				if (supportedTypes) {
8518				    var hasResource = false;
8519				    for (var i = 0; i < supportedTypes.length; i++) {
8520				        if (supportedTypes[i] === "resource") {
8521				            hasResource = true;
8522				            break;
8523				        }
8524				    }
8525				    if (hasResource) {
8526				        try {
8527				            observer.observe({ entryTypes: ["resource"] });
8528				            return;
8529				        } catch (e) {
8530				            translate.log("PerformanceObserver entryTypes 失败,尝试 type 参数");
8531				        }
8532				    }
8533				}
8534
8535
8536				// 回退到 type 参数
8537				try {
8538					observer.observe({ type: "resource", buffered: true });
8539					translate.log("使用 PerformanceObserver type");
8540				} catch (e) {
8541					translate.log("当前浏览器不支持 PerformanceObserver 的任何参数, translate.request.listener.start() 未启动");
8542				}
8543
8544			}
8545			
8546		}
8547	},
8548	//存储,本地缓存
8549	storage:{
8550		/*js translate.storage.IndexedDB start*/
8551		//对浏览器的 IndexedDB 操作
8552		IndexedDB:{
8553			db: null,
8554			// 初始化数据库
8555			initDB: function () {
8556				const self = this;
8557				return new Promise((resolve, reject) => {
8558					const DB_NAME = 'translate.js';
8559					const STORE_NAME = 'kvStore';
8560					const DB_VERSION = 1;
8561
8562					const request = indexedDB.open(DB_NAME, DB_VERSION);
8563
8564					request.onupgradeneeded = function(event) {
8565						const upgradedDb = event.target.result;
8566						if (!upgradedDb.objectStoreNames.contains(STORE_NAME)) {
8567							upgradedDb.createObjectStore(STORE_NAME, { keyPath: 'key' });
8568						}
8569					};
8570
8571					request.onsuccess = function(event) {
8572						self.db = event.target.result;
8573						resolve();
8574					};
8575
8576					request.onerror = function(event) {
8577						reject('IndexedDB 打开失败');
8578					};
8579				});
8580			},
8581			/*
8582				存储键值对
8583				使用方式:
8584					await translate.storage.indexedDB.set("user_001", { name: "Alice" });
8585			*/
8586			set: async function (key, value) {
8587				if (!this.db) await this.initDB();
8588
8589				return new Promise((resolve, reject) => {
8590					const tx = this.db.transaction('kvStore', 'readwrite');
8591					const store = tx.objectStore('kvStore');
8592					const item = { key, value };
8593					const request = store.put(item);
8594
8595					request.onsuccess = () => resolve();
8596					request.onerror = () => reject('写入失败');
8597				});
8598			},
8599			/*
8600				获取键对应的值
8601				使用方式:
8602					var user = await translate.storage.indexedDB.get("user_001");
8603			*/
8604			get: async function (key) {
8605				if (!this.db) await this.initDB();
8606
8607				return new Promise((resolve, reject) => {
8608					const tx = this.db.transaction('kvStore', 'readonly');
8609					const store = tx.objectStore('kvStore');
8610					const request = store.get(key);
8611
8612					request.onsuccess = () => {
8613						const result = request.result;
8614						resolve(result ? result.value : undefined);
8615					};
8616
8617					request.onerror = () => reject('读取失败');
8618				});
8619			},
8620			/*
8621				列出针对key进行模糊匹配的所有键值对
8622				使用方式:
8623					const users = await translate.storage.IndexedDB.list("*us*r*");
8624					其中传入的key可以模糊搜索,其中的 * 标识另个或多个
8625			*/
8626			list: async function (key = '') {
8627				if (!this.db) await this.initDB();
8628
8629				return new Promise((resolve, reject) => {
8630					const tx = this.db.transaction('kvStore', 'readonly');
8631					const store = tx.objectStore('kvStore');
8632					const request = store.openCursor();
8633					const results = [];
8634
8635					// 将通配符 pattern 转换为正则表达式
8636					const regexStr = '^' + key.replace(/\*/g, '.*') + '$';
8637					const regex = new RegExp(regexStr);
8638
8639					request.onsuccess = (event) => {
8640						const cursor = event.target.result;
8641						if (cursor) {
8642							if (regex.test(cursor.key)) {
8643								results.push({ key: cursor.key, value: cursor.value.value });
8644							}
8645							cursor.continue();
8646						} else {
8647							resolve(results);
8648						}
8649					};
8650
8651					request.onerror = () => reject('游标读取失败');
8652				});
8653			}
8654		},
8655		/*js translate.storage.IndexedDB end*/
8656
8657		set:function(key,value){
8658			localStorage.setItem(key,value);
8659		},
8660		get:function(key){
8661			return localStorage.getItem(key);
8662		}
8663
8664	},
8665	//针对图片进行相关的语种图片替换
8666	images:{
8667		/* 要替换的图片队列,数组形态,其中某个数组的:
8668			key:"/uploads/allimg/160721/2-160H11URA25-lp.jpg"; //旧图片,也就是原网站本身的图片。也可以绝对路径,会自动匹配 img src 的值,匹配时会进行完全匹配
8669			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便是
8670		*/
8671		queues:[], 
8672		
8673		/*
8674			向图片替换队列中追加要替换的图片
8675			传入格式如:
8676			
8677			translate.images.add({
8678				"/uploads/a.jpg":"https://www.zvo.cn/a_{language}.jpg",
8679				"/uploads/b.jpg":"https://www.zvo.cn/b_{language}.jpg",
8680			});
8681			
8682			参数说明:
8683			key  //旧图片,也就是原网站本身的图片。也可以绝对路径,会自动匹配 img src 的值,匹配时会进行完全匹配
8684			value //新图片,要被替换为的新图片。新图片路径需要为绝对路径,能直接访问到的。其中 {language} 会自动替换为当前要显示的语种。比如你要将你中文网站翻译为繁体中文,那这里会自动替换为:https://xxx.com/abc_chinese_traditional.jpg  有关{language}的取值,可查阅 http://api.translate.zvo.cn/doc/language.json.html 其中的语言标识id便是
8685		*/
8686		add:function(queueArray){
8687			/*
8688			translate.images.queues[translate.images.queues.length] = {
8689				old:oldImage,
8690				new:newImage
8691			}
8692			*/
8693			for(var key in queueArray){
8694				if (!queueArray.hasOwnProperty(key)) {
8695		    		continue;
8696		    	}
8697				translate.images.queues[key] = queueArray[key];
8698			}
8699		},
8700		//执行图片替换操作,将原本的图片替换为跟翻译语种一样的图片
8701		execute:function(){
8702			//console.log(translate.images.queues);
8703			if(Object.keys(translate.images.queues).length < 1){
8704				//如果没有,那么直接取消图片的替换扫描
8705				return;
8706			}
8707			
8708			/*** 寻找img标签中的图片 ***/
8709			var imgs = document.getElementsByTagName('img');
8710			for(var i = 0; i < imgs.length; i ++){
8711				var img = imgs[i];
8712				if(typeof(img.src) == 'undefined' || img.src == null || img.src.length == 0){
8713					continue;
8714				}
8715				var imgSrc = img.getAttribute('src');  //这样获取到的才是src原始的值,不然 img.src 是拿到一个绝对路径
8716
8717				for(var key in translate.images.queues){
8718					var oldImage = key; //原本的图片src
8719					var newImage = translate.images.queues[key]; //新的图片src,要替换为的
8720					//console.log('queue : '+oldImage + ' , img.src: '+imgSrc);
8721					if(oldImage == imgSrc){
8722						//console.log('发现匹配图片:'+imgSrc);
8723						/*
8724						//判断当前元素是否在ignore忽略的tag、id、class name中
8725						if(translate.ignore.isIgnore(node)){
8726							console.log('node包含在要忽略的元素中:');
8727							console.log(node);
8728							continue;
8729						}
8730						*/
8731						
8732						//没在忽略元素里,可以替换
8733						newImage = newImage.replace(new RegExp('{language}','g'), translate.to);
8734						img.src = newImage;
8735					}
8736				}
8737				
8738			}
8739			
8740			
8741			/********** 还要替换style中的背景图 */
8742			// 获取当前网页中所有的元素
8743			var elems = document.getElementsByTagName("*");
8744			// 遍历每个元素,检查它们是否有背景图
8745			for (var i = 0; i < elems.length; i++) {
8746				var elem = elems[i];
8747				// 获取元素的计算后样式
8748				var style = window.getComputedStyle(elem, null);
8749				// 获取元素的背景图URL
8750				var bg = style.backgroundImage;
8751				// 如果背景图不为空,打印出来
8752				if (bg != "none") {
8753					//console.log(bg);
8754					var old_img = translate.images.gainCssBackgroundUrl(bg);
8755					//console.log("old_img:"+old_img);
8756					if(typeof(translate.images.queues[old_img]) != 'undefined'){
8757						//存在
8758						var newImage = translate.images.queues[old_img];
8759						newImage = newImage.replace(new RegExp('{language}','g'), translate.to);
8760						//更换翻译指定图像
8761						elem.style.backgroundImage='url("'+newImage+'")';
8762					}else{
8763						//console.log('发现图像'+old_img+', 但未做语种适配');
8764					}
8765				}
8766			}
8767
8768
8769
8770			
8771		},
8772		//取css中的背景图,传入 url("https://xxx.com/a.jpg")  返回里面单纯的url
8773		gainCssBackgroundUrl:function(str){
8774			// 使用indexOf方法,找到第一个双引号的位置
8775			var start = str.indexOf("\"");
8776			// 使用lastIndexOf方法,找到最后一个双引号的位置
8777			var end = str.lastIndexOf("\"");
8778			// 如果找到了双引号,使用substring方法,截取中间的内容
8779			if (start != -1 && end != -1) {
8780				var url = str.substring(start + 1, end); // +1是为了去掉双引号本身
8781				//console.log(url); // https://e-assets.gitee.com/gitee-community-web/_next/static/media/mini_app.2e6b6d93.jpg!/quality/100
8782				return url;
8783			}
8784			return str;
8785		}
8786	},
8787	/*js translate.reset start*/
8788	/*
8789		对翻译结果进行复原。比如当前网页是简体中文的,被翻译为了英文,执行此方法即可复原为网页本身简体中文的状态,而无需在通过刷新页面来实现
8790		config 可不传,则是直接恢复到默认未翻译前的状态。
8791			{
8792				selectLanguageRefreshRender:true, //是否重新渲染select选择语言到原始未翻译前的状态,默认不设置则是true,进行重新渲染
8793				notTranslateTip:true 			  //如果当前未执行过翻译,然后触发的 translate.reset() ,是否在控制台打印友好提示,提示未执行翻译,还原指令忽略, true则是正常打印这个提示, false则是不打印这个提示
8794			}
8795	*/
8796	reset:function(config){
8797		if(typeof(config) == 'undefined'){
8798			config = {};
8799		}
8800		if(typeof(config.selectLanguageRefreshRender) == 'undefined'){
8801			config.selectLanguageRefreshRender = true;
8802		}
8803		if(typeof(config.notTranslateTip) == 'undefined'){
8804			config.notTranslateTip = true;
8805		}
8806
8807		
8808		/*
8809		for(var lang in translate.nodeQueue[lastUuid].list){
8810			if (!translate.nodeQueue[lastUuid].list.hasOwnProperty(lang)) {
8811	    		continue;
8812	    	}
8813			//console.log(lang);
8814			
8815			for(var hash in translate.nodeQueue[lastUuid].list[lang]){
8816				if (!translate.nodeQueue[lastUuid].list[lang].hasOwnProperty(hash)) {
8817		    		continue;
8818		    	}
8819				var item = translate.nodeQueue[lastUuid].list[lang][hash];
8820				//console.log(item);
8821				for(var index in item.nodes){
8822					if (!item.nodes.hasOwnProperty(index)) {
8823			    		continue;
8824			    	}
8825					//console.log(item.nodes[index]);
8826					//item.nodes[index].node.nodeValue = item.original;
8827					var currentShow = translate.storage.get('hash_'+currentLanguage+'_'+hash
8827); //当前显示出来的文字,也就是已经翻译后的文字
8828					//console.log('hash_'+lang+'_'+hash+'  --  '+currentShow);
8829					if(typeof(currentShow) == 'undefined'){
8830						continue;
8831					}
8832					if(currentShow == null){
8833						continue;
8834					}
8835					if(currentShow.length == 0){
8836						continue;
8837					}
8838					// v3.16.5 针对gitee 的 readme 接入优化
8839					if(typeof(item.nodes[index].node) == 'undefined'){
8840						continue;
8841					}
8842					
8843					var attribute = typeof(item.nodes[index].node.attribute) == 'undefined' ? null:item.nodes[index].node.attribute;
8844					var analyse = translate.element.nodeAnalyse.analyse(item.nodes[index].node, '', '', attribute);
8845					translate.element.nodeAnalyse.analyse(item.nodes[index].node, analyse.text, item.original, attribute);
8846				}
8847			}
8848		}
8849
8850		*/
8851
8852		//清除 translate.listener 
8853		translate.listener.reset();
8854
8855		//translate.temp_listenerStartInterval = undefined; //设置为尚未启动
8856		translate.init_first_trigger_execute = undefined; //translate.init 的 execute钩子,设置为未初始化状态
8857		
8858
8859		/** 使用基于 translate.node 的还原 **/
8860		for (let key of translate.node.data.keys()) {
8861			if (!translate.node.get(key) == null) {
8862	    		continue;
8863	    	}
8864			//for(var attr in translate.node.get(key)){
8865				//if (!translate.node.get(key).hasOwnProperty(attr)) {
8866		    	//	continue;
8867		    	//}
8868
8869				//var analyse = translate.element.nodeAnalyse.get(key,translate.node.get(key).attribute);
8870	    		if(typeof(translate.node.get(key).originalText) !== 'string'){
8871					continue;
8872				}
8873				//translate.element.nodeAnalyse.analyse(key, analyse.text, translate.node.get(key).originalText, translate.node.get(key).attribute);
8874				
8875				//标注此次改动是有 translate.js 导致的 -- 这里就不用标记了,因为先已经移除了 translate.listener.observer 监听,所以不会再监听到还原的操作了
8876				
8877				//是否是 input、 textarea 的 value ,如果是 则是 true
8878				var isInputValue = false;
8879				if(typeof(translate.node.get(key).attribute) === 'string' && translate.node.get(key).attribute === 'value'){
8880					//可能是input\textarea 的value
8881					var nodename = translate.element.getNodeName(key).toLowerCase();
8882					if(nodename === 'input' || nodename === 'textarea'){
8883						key.value = translate.node.get(key).originalText;
8884						isInputValue = true;
8885					}
8886				}	
8887				if(!isInputValue){
8888					key.nodeValue = translate.node.get(key).originalText;
8889				}
8890			//}
8891		}
8892
8893
8894		//清除 node 中的记录
8895		if(translate.node.data != null){
8896			translate.node.data.clear();
8897		}
8898		
8899		//清除 translate.nodeQueue 的记录
8900		translate.nodeQueue = {};
8901		
8902		//清除 time 的记录
8903		if(typeof(translate.time.execute.data) != 'undefined'){
8904			translate.time.execute.data = {};
8905		}
8906		
8907		//清除设置storage中的翻译至的语种
8908		translate.storage.set('to', '');
8909		translate.to = null;
8910
8911		//清除文本翻译记录
8912		if(translate.history.translateText.originalMap !== null){
8913			translate.history.translateText.originalMap.clear();
8914		}
8915		if(translate.history.translateText.resultMap !== null){
8916			translate.history.translateText.resultMap.clear();
8917		}
8918		
8919
8920		//重新绘制 select 选择语言
8921		if(config.selectLanguageRefreshRender){
8922			translate.selectLanguageTag.refreshRender();
8923		}
8924		
8925
8926		//清除正在进行的 translate.execute() 的执行状态记录
8927		translate.state = 0;
8928
8929	},
8930	/*js translate.reset end*/
8931	
8932	/*js translate.selectionTranslate start*/
8933	/*
8934		划词翻译,鼠标在网页中选中一段文字,会自动出现对应翻译后的文本
8935		有网友 https://gitee.com/huangguishen 提供。
8936		详细使用说明参见:http://translate.zvo.cn/41557.html
8937	*/
8938	selectionTranslate:{
8939		selectionX:0,
8940		selectionY:0,
8941		callTranslate:function (event){
8942			let curSelection = window.getSelection();
8943			//相等认为没有划词
8944			if (curSelection.anchorOffset == curSelection.focusOffset) return;
8945			let translateText = window.getSelection().toString();
8946
8947			//还有需要进行通过API接口进行翻译的文本,需要调用翻译接口
8948			if(typeof(translate.request.api.translate) != 'string' || translate.request.api.translate == null || translate.request.api.translate.length < 1){
8949				//用户已经设置了不掉翻译接口进行翻译
8950				translate.log('已设置了不使用 translate 翻译接口,翻译请求被阻止');
8951				return;
8952			}
8953
8954			//简单Copy原有代码了
8955			var url = translate.request.api.translate
8956			var data = {
8957				from:translate.language.getLocal(),
8958				to:translate.to,
8959				text:encodeURIComponent(JSON.stringify([translateText]))
8960			};
8961			translate.request.post(url, data, function(responseData, requestData) {
8962				if (responseData.result != 1){
8963					translate.log('translate.selectionTranslate network response error : '+responseData.info);
8964					return;
8965				};
8966				let curTooltipEle = document.querySelector('#translateTooltip')
8967				curTooltipEle.innerText = responseData.text[0];
8968				curTooltipEle.style.top =selectionY+20+"px";
8969				curTooltipEle.style.left = selectionX+50+"px" ;
8970				curTooltipEle.style.display = "";
8971			}, null);
8972		},
8973		start:function () {
8974			//新建一个tooltip元素节点用于显示翻译
8975			let tooltipEle = document.createElement('span');
8976			tooltipEle.innerText = '';
8977			tooltipEle.setAttribute('id', 'translateTooltip');
8978			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%; ');
8979			//把元素节点添加到body元素节点中成为其子节点ï¼
8979Œæ”¾åœ¨body的现有子节点的最后
8980			document.body.appendChild(tooltipEle);
8981			//监听鼠标按下事件,点击起始点位置作为显示翻译的位置点
8982			document.addEventListener('mousedown', (event)=>{ selectionX= event.pageX;selectionY= event.pageY ;}, false);			
8983			//监听鼠标弹起事件,便于判断是否处于划词
8984			document.addEventListener('mouseup', translate.selectionTranslate.callTranslate, false);
8985			//监听鼠标点击事件,隐藏tooltip,此处可优化
8986			document.addEventListener('click', (event)=>{  document.querySelector('#translateTooltip').style.display = "none"}, false);
8987		}
8988	},
8989	/*js translate.selectionTranslate end*/
8990
8991	/*js translate.enterprise start*/	
8992	/*
8993		企业级翻译服务
8994		注意,这个企业级翻译中的不在开源免费之中,企业级翻译服务追求的是高稳定,这个是收费的!详情可参考:http://translate.zvo.cn/43262.html
8995
8996	*/
8997	enterprise:{
8998		//默认不启用企业级,除非设置了 translate.enterprise.use() 这里才会变成true
8999		isUse:false,	
9000		use:function(){
9001			translate.enterprise.isUse = true; //设置为使用企业级翻译服务
9002
9003			//主节点额外权重降低,更追求响应速度
9004			translate.request.speedDetectionControl.hostMasterNodeCutTime = 300; 
9005			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/'];
9006			
9007			if(translate.service.name == 'client.edge'){
9008				translate.service.name = 'translate.service';
9009				translate.log('您已启用了企业级翻译通道 translate.enterprise.use(); (文档:https://translate.zvo.cn/4087.html) , 所以您设置的 translate.service.use(\'client.edge\'); (文档:https://translate.zvo.cn/4081.html) 将失效不起作用,有企业级翻译通道全部接管。');
9010				return;
9011			}
9012		},
9013		/*
9014			自动适配翻译服务通道,如果当前所有网络节点均不可用,会自动切换到 edge.client 进行使用
9015			这个会在 post请求 执行前开始时进行触发
9016		*/
9017		automaticAdaptationService:function(){
9018			if(!translate.enterprise.isUse){
9019				return;
9020			}
9021			var hosts = translate.request.speedDetectionControl.getHostQueue();
9022			//console.log(hosts);
9023			if(hosts.length > 0){
9024				if(hosts[0].time + 1 > translate.request.speedDetectionControl.disableTime){
9025					//所有节点都处于不可用状态,自动切换到 client.edge 模式
9026					translate.service.name = 'client.edge';
9027				} 
9028			}
9029		},
9030		/* 企业级翻译通道的key, v3.12.3.20250107 增加,针对打包成APP的场景 */
9031		key:'', 
9032	},
9033	/*js translate.enterprise end*/
9034
9035	/*
9036		如果使用的是 translate.service 翻译通道,那么翻译后的语种会自动以小写的方式进行显示。
9037		如果你不想将翻译后的文本全部以小写显示,而是首字母大写,那么可以通过此方法设置一下
9038		v3.8.0.20240828 增加
9039		目前感觉应该用不到,所以先忽略
9040	*/
9041	/*
9042	notConvertLowerCase:function(){
9043
9044	},
9045	*/
9046
9047
9048	/*js translate.progress start*/
9049	/*
9050		翻译执行的进展相关
9051		比如,浏览器本地缓存没有,需要走API接口的文本所在的元素区域,出现 记载中的动画蒙版,给用户以友好的使用提示
9052	*/
9053	progress:{
9054		style: `
9055			/* CSS部分 */
9056			/* 灰色水平加载动画 */
9057			.translate_api_in_progress {
9058			  position: relative;
9059			  overflow: hidden; /* 隐藏超出部分的动画 */
9060			}
9061
9062			/* 蒙版层 */
9063			.translate_api_in_progress::after {
9064			  content: '';
9065			  position: absolute;
9066			  top: 0;
9067			  left: 0%;
9068			  width: 100%;
9069			  height: 100%;
9070			  background: rgba(255, 255, 255, 1); /* 半透明白色遮罩 */
9071			  z-index: 2;
9072			}
9073
9074			/* 水平加载条动画 */
9075			.translate_api_in_progress::before {
9076			  content: '';
9077			  position: absolute;
9078			  top: 50%;
9079			  left: 0%;
9080			  width: 100%;
9081			  height:100%; /* 细线高度 */
9082			  background: linear-gradient(
9083			    90deg,
9084			    transparent 0%,
9085			    #e8e8e8 25%,  /* 浅灰色 */
9086			    #d0d0d0 50%,  /* 中灰色 */
9087			    #e8e8e8 75%,  /* 浅灰色 */
9088			    transparent 100%
9089			  );
9090			  background-size: 200% 100%;
9091			  animation: translate_api_in_progress_horizontal-loader 3.5s linear infinite;
9092			  z-index: 3;
9093			  transform: translateY(-50%);
9094			}
9095
9096			@keyframes translate_api_in_progress_horizontal-loader {
9097			  0% {
9098			    background-position: 200% 0;
9099			  }
9100			  100% {
9101			    background-position: -200% 0;
9102			  }
9103			}
9104		`,
9105
9106		/*
9107			通过文本翻译API进行的
9108		 */
9109		api:{
9110			isTip:true,//是否显示ui的提示,true显示,false不显示
9111			setUITip:function(tip){
9112				translate.progress.api.isTip = tip;
9113			},
9114			//移除子元素(无限级别)中的所有 class name 的loading 遮罩
9115			//level 层级,数字,比如第一次调用,传入1, 第一次里面产生的第二次调用,这里就是2
9116			removeChildClass:function(node, level){
9117
9118				//判断是否有子元素,判断其两级子元素,是否有加了loading遮罩了
9119		        var childNodes = node.childNodes;
9120				if(childNodes == null || typeof(childNodes) == 'undefined'){
9121					
9122				}else if(childNodes.length > 0){
9123					for(var i = 0; i<childNodes.length; i++){
9124						translate.progress.api.removeChildClass(childNodes[i], level+1);
9125					}
9126				}
9127
9128				if(level == 1){
9129					//第一次调用,是不删除本身的class name
9130					return;
9131				}
9132				if(typeof(node) == 'undefined'){
9133					return;
9134				}
9135				if(typeof(node.className) != 'string'){
9136					return;
9137				}
9138				if(node.className.indexOf('translate_api_in_progress') < -1){
9139					return;
9140				}
9141				node.className = node.className.replace(/translate_api_in_progress/g, '');
9142			},
9143			
9144			/*
9145				config: 可设置的一些参数
9146					{
9147						maskLayerMinWidth:10.0 	//当翻译时,需要请求网络,此时翻译的文本上会出现遮罩层显示一个进行中的动画,这个动画出现在的元素,最小宽度是多少。如果不设置,默认是10,也就是10像素,也就是当元素大于等于10像素时,才会在上面显示这个进行中的动画。而小于10像素宽度的元素,则是空白一片什么也不显示。 它支持设置float类型的值
9148					}
9149
9150				
9151			*/
9152			startUITip:function(config){
9153				if(typeof(config) === 'undefined'){
9154					config = {};
9155				}
9156				if(typeof(config.maskLayerMinWidth) !== 'number'){
9157					config.maskLayerMinWidth = 10;
9158				}
9159				
9160				//创建隐藏文字的 style
9161				var translatejsTextElementHidden = document.getElementById('translatejs-text-element-hidden');
9162				if(typeof(translatejsTextElementHidden) == 'undefined' || translatejsTextElementHidden == null){
9163					const style = document.createElement('style');
9164			        // 设置 style 元素的文本内容为要添加的 CSS 规则
9165			       	style.textContent = ' .translatejs-text-element-hidden, .translatejs-text-element-hidden[type="text"]::placeholder{color: transparent !important; -webkit-text-fill-color: transparent !important; text-shadow: none !important;} ';
9166			        style.id = 'translatejs-text-element-hidden';
9167			        // 将 style 元素插入到 head 元素中
9168			        document.head.appendChild(style);
9169				}
9170
9171				// 创建一个 遮罩层加载中动画的 style 元素
9172				var translatejsMaskLayerAnimation = document.getElementById('translatejs-mask-layer-animation');
9173				if(typeof(translatejsMaskLayerAnimation) == 'undefined' || translatejsMaskLayerAnimation == null){
9174					const style = document.createElement('style');
9175			        // 设置 style 元素的文本内容为要添加的 CSS 规则
9176			       	style.textContent = translate.progress.style;
9177			       	style.id = 'translatejs-mask-layer-animation';
9178			        // 将 style 元素插入到 head 元素中
9179			        document.head.appendChild(style);
9180				}
9181		        
9182
9183				if(translate.progress.api.isTip){
9184					//translate.listener.execute.renderStartByApi.push(function(uuid, from, to){
9185					translate.lifecycle.execute.translateNetworkBefore.push(function(data){
9186						var startTime = new Date().getTime();
9187
9188						//取出当前变动的node,对应的元素
9189						var elements = translate.element.nodeToElement(data.nodes);
9190						//console.log(elements)
9191				    	
9192						//隐藏所有node的文本
9193					    for(var r = 0; r<elements.length; r++){
9194							elements[r].className = elements[r].className+' translatejs-text-element-hidden';
9195						}
9196
9197						var rects = translate.visual.getRects(elements);
9198					    //console.log(rects)
9199					    var rectsOneArray = translate.visual.rectsToOneArray(rects);
9200
9201					    //排序
9202					    var sortRects = translate.visual.coordinateSort(rectsOneArray);
9203						//console.log(sortRects);
9204
9205					    //过滤,比如过滤掉宽度非常小的,不然显示出来会很丑
9206						// 1. 收集需要删除的下标
9207						const indicesToRemove = [];
9208						for (let i = 0; i < sortRects.length; i++) {
9209						  if (sortRects[i].width < config.maskLayerMinWidth) {
9210						    indicesToRemove.push(i);
9211						  }
9212						}
9213						// 2. 移除宽度极小的rects元素
9214						for(var di = indicesToRemove.length-1; di > -1; di--){
9215							//console.log(sortRects[indicesToRemove[di]]);
9216							sortRects.splice(indicesToRemove[di], 1);
9217						}
9218
9219					    //去除空间重叠
9220					    var spaceEORects = translate.visual.rectsSpaceEliminateOverlap(sortRects);
9221					    //console.log('计算耗时:'+(new Date().getTime() - startTime));
9222
9223						//var rectLineSplit = translate.visual.filterRectsByLineInterval(spaceEORects.rects, 1);
9224						var rectLineSplit = spaceEORects.rects;
9225						//var rectLineSplit = sortRects;
9226						for(var r = 0; r<rectLineSplit.length; r++){
9227							//判断这个元素的父级是否已经添加了,可能存在检测到多个本地语种,然后中文转英语后,又出现了日语转英语。 这里避免第二次日语转英语时,跟第一次中文转英语重复,导致出现样式过渡动画的重叠
9228							var parentNode = rectLineSplit[r].node.parentNode;
9229							if(typeof(parentNode) !== 'undefined' && typeof(parentNode.className) === 'string' && parentNode.className.indexOf('translate_api_in_progress') > -1){
9230								//上级已经有了,那么就不需要再加动画了
9231							}else{
9232								//上级没有加,那么这个才能考虑加
9233								if(typeof(rectLineSplit[r].node.className) === 'string' && rectLineSplit[r].node.className.indexOf('translate_api_in_progress') > -1){
9234						    		//已经存在了,就不继续加了
9235						    	}else{
9236						    		rectLineSplit[r].node.className = rectLineSplit[r].node.className+' translate_api_in_progress';	
9237						    	}
9238							}
9239						}
9240						//console.log('计算+渲染耗时:'+(new Date().getTime() - startTime));
9241					});
9242					
9243					translate.lifecycle.execute.translateNetworkAfter.push(function(data){
9244						//取出当前变动的node,对应的元素
9245						var elements = translate.element.nodeToElement(data.nodes);
9246				    	
9247				    	for(var r = 0; r<elements.length; r++){
9248				    		if(typeof(elements[r].className) === 'string'){
9249				    			if(elements[r].className.indexOf('translatejs-text-element-hidden') > -1){
9250				    				elements[r].className = elements[r].className.replace(/translatejs-text-element-hidden/g, '');
9251				    			}
9252				    			if(elements[r].className.indexOf('translate_api_in_progress') > -1){
9253									elements[r].className = elements[r].className.replace(/translate_api_in_progress/g, '');	
9254								}
9255				    		}
9256				    	}
9257						
9258						
9259					});
9260
9261				}
9262			}
9263		}
9264	},
9265	/*js translate.progress end*/
9266
9267	/*js dispose start*/
9268	/*
9269		对js对象内的值进行翻译,可以是JS定义的 对象、数组、甚至是单个具体的值
9270	*/
9271	js:{
9272
9273		/*
9274			jsString 传入的js对象的字符串格式
9275			targetLanguage 翻译为的目标语言
9276			successFunction 执行成功后触发,传入格式  function(obj){ console.log(obj); }  其中 obj 是翻译之后的结果
9277			failureFunction 执行失败后触发,传入格式 function(failureInfo){ console.log(failureInfo); } 其中 failureInfo 是失败原因
9278
9279			示例:
9280
9281			var str = `
9282				{
9283				  "hello":"你好",
9284				  "word":"单词",
9285				  "你是谁": [
9286				      "世界",
9287				      "大海"
9288				    ]
9289				}
9290			`
9291			translate.js.transString(str,'english',function(obj){ console.log(obj); }, function(failureInfo){ console.log(failureInfo); });
9292
9293		*/
9294		transString: function (jsString, targetLanguage, successFunction, failureFunction) {
9295			let jsObject;
9296			try{
9297				jsObject = JSON.parse(jsString);
9298			}catch(e){
9299				try{
9300					jsObject =  eval('(' + jsString + ')');
9301				}catch(e){
9302					translate.log(e)
9303					failureFunction(e);	
9304					return;
9305				}
9306			}
9307			translate.js.transObject(jsObject, targetLanguage, successFunction, failureFunction);
9308		},
9309
9310		/*
9311			jsObject 传入的js对象,支持对象、数组等
9312			targetLanguage 翻译为的目标语言
9313			successFunction 执行成功后触发,传入格式  function(obj){ console.log(obj); }  其中 obj 是翻译之后的结果
9314			failureFunction 执行失败后触发,传入格式 function(failureInfo){ console.log(failureInfo); } 其中 failureInfo 是失败原因
9315
9316			示例:
9317
9318			var obj = {
9319				"hello":"你好",
9320				"word":"单词",
9321				"世界":["世界","大海"]
9322			};
9323			translate.js.transObject(obj,'english',function(obj){ console.log(obj); }, function(failureInfo){ console.log(failureInfo); });
9324
9325		*/
9326		transObject: function (jsObject, targetLanguage, successFunction, failureFunction) {
9327			let tj_find = translate.js.find(jsObject);
9328			let kvs = tj_find.stringResult;
9329			
9330			/**** 第二步,将文本值进行翻译 ***/
9331			//先将其 kvs 的key 取出来
9332			var texts = new Array();
9333			for (const key in kvs) {
9334				texts.push(key);
9335			}
9336
9337			/*
9338				它主要用于拆分场景,如果不需要拆分,它用不到
9339				下标对应,上面 texts 的原始下标跟拆分后的下标对应
9340				key 新数组的下标
9341				value 对象,包含:
9342					  index: 旧数组的下标
9343					  original: 翻译的原始文本
9344					  isSplit: 是否是被拆分的, true是,false不是
9345				
9346			*/
9347			var originalArrayIndexMap = new Array();
9348
9349			var obj = {
9350				from:'auto',
9351				to: targetLanguage,
9352				texts: texts
9353			}
9354
9355			if(translate.ignore.text.length > 0){ //有设置忽略翻译的文本
9356				var newTexts = new Array(); //新组合的
9357
9358				//组合  split 切割
9359				var splitStrng = '';
9360				for(var ig = 0; ig < translate.ignore.text.length; ig++){
9361					if(translate.ignore.text[ig].trim().length == 0){
9362						continue;
9363					}
9364					var ignoretext = translate.ignore.text[ig];
9365					if(ignoretext.indexOf('.') > -1){
9366						ignoretext = ignoretext.replaceAll(/\./g, "\\.");
9367					}
9368					if(ignoretext.indexOf('$') > -1){
9369						ignoretext = ignoretext.replaceAll(/\$/g, "\\$");
9370					}
9371
9372					if(splitStrng != ''){
9373						splitStrng = splitStrng + '|';
9374					}
9375					splitStrng = splitStrng + ignoretext;
9376				}
9377				var regex = new RegExp(splitStrng, 'g'); // 创建正则表达式对象,添加'g'修饰符表示全局匹配
9378				//console.log(regex);
9379
9380				for(var tai = 0; tai<texts.length; tai++){
9381					
9382					var isFind = false; //是否发现匹配进行拆分了,true是
9383						
9384					//发现了忽略翻译的文本,将其单独抽取出来,不进行翻译
9385					//进行拆分
9386					var splits = texts[tai].split(regex);
9387					if(splits.length > 1){
9388						isFind = true; //拆分了
9389						for(var s = 0; s < splits.length; s++){
9390							if(splits[s].length > 0){
9391								newTexts.push(splits[s]);
9392								originalArrayIndexMap[newTexts.length-1] = {
9393									index:tai,
9394									original:splits[s],
9395									isSplit:true
9396								};
9397							}
9398						}
9399					}else{
9400						//没有拆分,那就原样加入
9401						newTexts.push(texts[tai]);
9402						originalArrayIndexMap[newTexts.length-1] = {
9403							index:tai,
9404							original:texts[tai],
9405							isSplit:false
9406						};
9407					}
9408					
9409				}
9410				obj.texts = newTexts;
9411			}
9412			//console.log(originalArrayIndexMap);
9413			
9414			translate.request.translateText(obj, function (data) {
9415				//打印翻译结果
9416				//console.log(data);
9417				if(typeof(data.result) == 'undefined' || data.result == 0){
9418					failureFunction('network connect failure');
9419					return;
9420				}
9421				if(data.result == 0){
9422					failureFunction(data.info);
9423					return;
9424				}
9425
9426				/**** 第三步,将翻译结果赋予 jsObject ***/
9427				var translatedTexts; //跟最初拆分前的 texts 下标一一对应
9428
9429				//判断是否有过分割
9430				if(translate.ignore.text.length > 0){ //有过分割,进行合并
9431					translatedTexts = new Array();
9432
9433					for(var i = 0; i < data.text.length; i++){
9434						var originalTextIndex = originalArrayIndexMap[i].index; //最初分割前的原数组下标
9435						if(translatedTexts.length < originalTextIndex+1){
9436							translatedTexts.push(texts[originalTextIndex]);
9437						}
9438						translatedTexts[originalTextIndex] = translate.util.textReplace(translatedTexts[originalTextIndex], originalArrayIndexMap[i].original, data.text[i], data.to);
9439					}
9440				}else{
9441					translatedTexts = data.text; // 直接获取翻译结果数组赋予
9442				}
9443
9444				if (translatedTexts && translatedTexts.length === texts.length) {
9445					texts.forEach((originalText, index) => {
9446						const translatedText = translatedTexts[index]; // 根据索引获取翻译结果
9447						const paths = kvs[originalText]; // 获取该文本的路径数组
9448						if (paths && paths.length > 0) {
9449							paths.forEach(path => {
9450								translate.js.setValueByPath(jsObject, path, translatedText); // 更新 jsObject
9451							});
9452						}
9453					});
9454				} else {
9455					console.error("翻译结果长度不匹配或为空");
9456				}
9457
9458				if(Object.keys(tj_find.functionResult).length > 0){
9459					for(var sf in tj_find.functionResult){
9460						if (!tj_find.functionResult.hasOwnProperty(sf)) {
9461							continue;
9462						}
9463						translate.js.setValueByPath(jsObject, sf, tj_find.functionResult[sf]); // 更新 jsObject
9464					}
9465				}
9466				successFunction(jsObject);
9467				//console.log("翻译后的 jsObject:", jsObject);
9468			});
9469		},
9470		setValueByPath: function(obj, path, value){
9471			const parts = path.replace(/\[(\d+)\]/g, '.$1').split('.');
9472			let current = obj;
9473			for (let i = 0; i < parts.length - 1; i++) {
9474				current = current[parts[i]];
9475			}
9476			current[parts[parts.length - 1]] = value;
9477		},
9478		/*
9479			对js对象进行翻译
9480			obj: 可以是JS定义的 对象、数组、甚至是单个具体的值
9481
9482			var obj = {
9483				"hello":"你好",
9484				"word":"单词",
9485				"世界":["世界","大海"]
9486			};
9487			translate.js.find(obj);
9488
9489			返回值:
9490			{
9491				stringResult:
9492				functionResult:
9493			}
9494
9495		*/
9496		find: function (obj, parentKey = '') {
9497			let kvs = {}; //stringResult
9498			let frs = {}; //functionResult
9499
9500			if (typeof obj === 'object' && obj !== null) {
9501				if (Array.isArray(obj)) {
9502					obj.forEach((item, index) => {
9503						const currentKey = parentKey ? `${parentKey}[${index}]` : `[${index}]`;
9504						const tj_find = translate.js.find(item, currentKey);
9505						const subKvs = tj_find.stringResult;
9506						for (const [text, paths] of Object.entries(subKvs)) {
9507							if (!kvs[text]) {
9508								kvs[text] = [];
9509							}
9510							kvs[text] = kvs[text].concat(paths);
9511						}
9512
9513						const subFrs = tj_find.functionResult;
9514						for(var sf in subFrs){
9515							if (!subFrs.hasOwnProperty(sf)) {
9516								continue;
9517							}
9518							frs[sf] = subFrs[sf];
9519						}
9520					});
9521				} else {
9522
9523					for (const key in obj) {
9524						const currentKey = parentKey ? `${parentKey}.${key}` : key;
9525						if (typeof obj[key] === 'object' && obj[key] !== null) {
9526							const tj_find = translate.js.find(obj[key], currentKey);
9527							const subKvs = tj_find.stringResult;
9528							for (const [text, paths] of Object.entries(subKvs)) {
9529								if (!kvs[text]) {
9530									kvs[text] = [];
9531								}
9532								kvs[text] = kvs[text].concat(paths);
9533							}
9534							const subFrs = tj_find.functionResult;
9535							for(var sf in subFrs){
9536								if (!subFrs.hasOwnProperty(sf)) {
9537									continue;
9538								}
9539								frs[sf] = subFrs[sf];
9540							}
9541
9542						} else if (typeof obj[key] === 'string') {
9543							if (typeof kvs[obj[key]] === 'undefined') {
9544								kvs[obj[key]] = [];
9545							}
9546							kvs[obj[key]].push(currentKey);
9547						}else if(typeof(obj[key]) == 'function'){
9548							//value是一个方法,那么也将他返回
9549							frs[currentKey]=obj[key];
9550						}
9551					}
9552				}
9553			} else if (typeof obj === 'string') {
9554				if (typeof kvs[obj] === 'undefined') {
9555					kvs[obj] = [];
9556				}
9557				kvs[obj].push(parentKey);
9558			}
9559			return {
9560				stringResult:kvs,
9561				functionResult:frs
9562			};
9563		},
9564		/*
9565			将 translate.js.transString 执行结果的 obj 对象 转化为 字符串输出
9566			这个可以直接输出到 textarea 中显示
9567
9568			obj: js对象
9569			formatSupplementaryCharLength: 对这个js对象进行格式化自动补充字符的长度,比如 2、 4
9570
9571			2025.10.10 优化传入参数
9572			obj:{
9573				jsObject: 原本的obj参数, 也就是js对象
9574				formatSupplementaryCharLength: 对这个js对象进行格式化自动补充字符的长度,比如 2、 4 ,默认不设置则是4
9575				functionBodyHandle: 针对值是function函数类型时,可以自定义对函数体的源码进行处理,它是传入 function 类型的,比如:
9576						functionBodyHandle: function(functionBody){
9577							functionBody = functionBody+'123';
9578							return functionBody;
9579						}
9580						传入值是函数体的string类型的源码内容
9581						返回值是修改过后最新的函数体的string类型的源码内容
9582						这是 2025.10.10 新增参数,应对layui的 i18n 全自动翻译函数体中的字符串文本
9583			}
9584		*/
9585		objToString:function(obj, formatSupplementaryCharLength){
9586			if(typeof(obj) === 'object'){
9587				if(typeof(obj.jsObject) === 'object'){
9588					//是 2025.10.10 以后的新版本
9589				}else{
9590					//是 2025.10.10 以前的旧版本
9591					var newObj = {
9592						jsObject: obj
9593					}
9594					obj = newObj;
9595				}
9596			}else{
9597				obj = {};
9598			}
9599			if(typeof(formatSupplementaryCharLength) === 'number'){
9600				obj.formatSupplementaryCharLength = formatSupplementaryCharLength;
9601			}
9602			//未设置,就赋予默认值4
9603			if(typeof(obj.formatSupplementaryCharLength) !== 'number'){
9604				obj.formatSupplementaryCharLength = 4; 
9605			}
9606
9607			// 自定义replacer函数,将函数转换为字符串
9608			let jsonStr = JSON.stringify(obj.jsObject, (key, value) => {
9609			  if (typeof value === 'function') {
9610			    // 将函数转换为其源代码字符串
9611			    var funcString = value.toString();
9612			    if(typeof(funcString) === 'string' && funcString.length > 0){
9613			    	funcString = funcString.replace(/\n/g, '___TRANSLATEJS_LINE_BREAK___');	
9614			    }
9615			    return funcString;
9616			  }else{
9617			  	return value;
9618			  }
9619
9620	          return result;
9621			}, obj.formatSupplementaryCharLength);
9622
9623
9624			//对 function 的函数体进行处理
9625			// 将转义的\n替换为实际的换行符 -- 20251009 优化,去掉换行符替换,layui 工具中发现这样会将原本文本中的换行符替换掉,而是调整为仅仅针对function方法进行针对替换
9626			// 逐行判断,判断其中哪一行的value是function,要将function的字符串格式变为function函数格式
9627			if(jsonStr.indexOf('___TRANSLATEJS_LINE_BREAK___') > -1){
9628				const lines = jsonStr.split('\n');
9629			    for(var li = 0; li<lines.length; li++){
9630			    	// 检查当前行是否包含特定标记
9631			        if (lines[li].includes('___TRANSLATEJS_LINE_BREAK___')) {
9632			        	lines[li] = lines[li].replace(/___TRANSLATEJS_LINE_BREAK___/g, '\n'); //将其替换为原本的换行符
9633
9634			            // 查找值部分(假设格式是 "key": "function...")
9635			            const valueMatch = lines[li].match(/"[^"]+":\s*"([^"]+)"/);
9636			            if (valueMatch && valueMatch[1]) {
9637			                // 替换换行标记为实际换行
9638			                let functionStr = valueMatch[1].replace(/___TRANSLATEJS_LINE_BREAK___/g, '\n');
9639			                
9640			                // 将函数字符串转换为实际函数
9641			                try {
9642			                    // 使用Function构造函数创建函数更安全一ä
9642º›
9643			                    const functionParts = functionStr.match(/function\s*([^\(]*)\(([^)]*)\)\s*\{([\s\S]*)\}/);
9644			                    
9645			                    if (functionParts) {
9646			                        var [, name, params, body] = functionParts;
9647			                        if(typeof(obj.functionBodyHandle) === 'function'){
9648			                        	body = obj.functionBodyHandle(body);
9649			                        }
9650			                        // 替换原行中的字符串为函数表达式
9651			                        lines[li] = lines[li].replace(`"${valueMatch[1]}"`, `function${name}(${params}){${body}}`);
9652			                    }
9653			                } catch (e) {
9654			                    console.error('转换函数时出错:', e);
9655			                }
9656			            }
9657			        }
9658			    }
9659			 	jsonStr = lines.join('\n');   
9660			}
9661		    
9662			return jsonStr;
9663		}
9664	},
9665	/*js dispose end*/
9666
9667	/*js translate.network start*/
9668	/*
9669		网络请求数据拦截并翻译
9670		当用户触发ajax请求时,它可以针对ajax请求中的某个参数,进行获取,并进行翻译,将翻译后的文本赋予这个参数,然后再放开请求。
9671		
9672		使用场景如:
9673			搜索场景,原本是中文的页面,翻译为英文后,给美国人使用,美国人使用时,进行搜索,输入的是英文,然后点击搜索按钮,发起搜索。
9674			然后此会拦截网络请求,将请求中用户输入的搜索文本的内容提取出来,识别它输入的是中文还是英文,如果不是本地的语种中文,那就将其翻译为中文,然后再赋予此请求的这个参数中,然后再放开这次请求。
9675			这样请求真正到达服务端接口时,服务端接受到的搜索的文本内容实际就是翻译后的中文文本,而不是用户输入的英文文本。
9676		
9677		何时自动进行翻译:
9678			1. 当前用户没有进行切换语言
9679			2. 切换语言了,但是输入的文本的语言是不需要进行翻译的, 输入的文本本身就是本地的语言
9680			这两种情况那就不需要拦截翻译
9681				
9682
9683	*/	
9684	network: {
9685	    // 原始方法保存
9686	    originalOpen: XMLHttpRequest.prototype.open,
9687	    originalSend: XMLHttpRequest.prototype.send,
9688	    setRequestHeaderOriginal: XMLHttpRequest.prototype.setRequestHeader,
9689
9690	    // 规则配置
9691	    rules: [
9692	        {
9693	            url: /https:\/\/www\.guanleiming\.com\/a\/b\/.html/,
9694	            methods: ['GET', 'POST'],
9695	            params: ['a','b1']
9696	        }
9697	    ],
9698	    //根据 当前请求的url 跟 method 来判断当前请求是否符合规则, 
9699	    //如果符合,则返回符合的 rule 规则,也就是 translate.network.rules 中配置的某个。
9700	    //如果没有找到符合的,则返回 null
9701	    getRuleMatch:function(url, method){
9702			for (let i = 0; i < translate.network.rules.length; i++) {
9703			    const rule = translate.network.rules[i];
9704			    
9705			    // 检查 URL 是否匹配
9706			    if(typeof(rule.url) == 'undefined' && rule.url == ''){
9707			    	translate.log('WARINNG : translate.network.rule find url is null:');
9708			    	translate.log(rule);
9709			    	continue;
9710			    }
9711			    //console.log(rule);
9712			    const isUrlMatch = rule.url.test(url);
9713			    if(!isUrlMatch){
9714			    	continue;
9715			    }
9716			    
9717			    // 检查方法是否匹配(忽略大小写)
9718			    const isMethodMatch = rule.methods.includes(method.toUpperCase());
9719			    if(!isMethodMatch){
9720			    	continue;
9721			    }
9722
9723			    return rule;
9724			}
9725
9726			return null;
9727	    },
9728	    use:function(){
9729	    	// 应用Hook
9730			XMLHttpRequest.prototype.open = function(...args) {
9731			    return translate.network.hookOpen.apply(this, args);
9732			};
9733
9734			XMLHttpRequest.prototype.send = function(...args) {
9735			    return translate.network.hookSend.apply(this, args);
9736			};
9737
9738			// 劫持 setRequestHeader 方法
9739		    XMLHttpRequest.prototype.setRequestHeader = function(...args) {
9740		        return translate.network.setRequestHeader.apply(this, args);
9741		    };
9742
9743		    translate.network.fetch.use();
9744	    },
9745	    // 私有工具方法
9746	    _translateText(text) {
9747	    	if(translate.language.getLocal() == translate.language.getCurrent() || (typeof(text) == 'string' && text.length > 0 && translate.language.recognition(text).languageName == translate.language.getLocal())){
9748	    		/*
9749					1. 没有进行切换语言
9750					2. 切换语言了,但是输入的文本的语言是不需要进行翻译的, 输入的文本本身就是本地的语言
9751
9752					这两种情况那就不需要拦截翻译
9753				*/
9754	    		
9755	    		return new Promise((resolve, reject) => {
9756		            const obj = {
9757		                from: 'auto',
9758		                to: translate.language.getLocal(),
9759		                text: [text]
9760		            };
9761		            
9762		            resolve(obj);
9763		        });
9764	    	}else{
9765	    		//有进行切换了,那进行翻译,将其他语种翻译为当前的本地语种
9766	    		return new Promise((resolve, reject) => {
9767		            const obj = {
9768		                from: 'auto',
9769		                to: translate.language.getLocal(),
9770		                texts: [text]
9771		            };
9772		            
9773		            //console.log('翻译请求:', obj);
9774		            translate.request.translateText(obj, function(data) {
9775		                if (data.result === 1) {
9776		                    resolve(data);
9777		                } else {
9778		                    reject(data);
9779		                }
9780		            });
9781		        });
9782	    	}
9783	        
9784	    },
9785		//劫持 setRequestHeader
9786		setRequestHeader: function(header, value) {
9787		    if (this._requestContext) {
9788		        this._requestContext.headers = this._requestContext.headers || {};
9789		        this._requestContext.headers[header] = value;
9790		    }
9791
9792		    return translate.network.setRequestHeaderOriginal.call(this, header, value);
9793		},
9794	    // 请求处理工具
9795	    RequestHandler: {
9796	        async handleGet(url, rule) {
9797	        	//console.log(url);
9798	        	//console.log(rule);
9799	        	if(typeof(rule.params) == 'undefined' && typeof(rule.params.length) == 'undefined' && rule.params.length < 1){
9800	        		translate.log('WARINNG: rule not find params , rule : ');
9801	        		translate.log(rule);
9802	        		rule.params = [];
9803	        	}
9804	        	
9805
9806	            try {
9807	                const urlObj = new URL(url, window.location.origin);
9808	                const params = urlObj.searchParams;
9809	                //console.log(rule.params);
9810
9811	                //for (const paramName in rule.params) {
9812	                for(var p = 0; p < rule.params.length; p++){
9813	                	var paramName = rule.params[p];
9814	                		//console.log(paramName);
9815	                    if (params.has(paramName)) {
9816	                        const original = params.get(paramName);
9817	                        const translateResultData = await translate.network._translateText(original);
9818	                        
9819	                        if(typeof(translateResultData) == 'undefined'){
9820	                    				translate.log('WARINNG: translateResultData is undefined');
9821	                    		}else if(typeof(translateResultData.result) == 'undefined'){
9822	                    				translate.log('WARINNG: translateResultData.result is undefined');
9823	                    		}else if(translateResultData.result != 1){
9824	                    				translate.log('WARINNG: translateResultData.result failure : '+translateResultData.info);
9825	                    		}else{
9826	                    				params.set(paramName, decodeURIComponent(translateResultData.text[0]));
9827	                    		}
9828
9829	                    }
9830	                }
9831	                
9832	                return urlObj.toString();
9833	            } catch (e) {
9834	                console.warn('GET处理失败:', e);
9835	                return url;
9836	            }
9837	        },
9838
9839	        async handleForm(body, rule) {
9840	            try {
9841	                const params = new URLSearchParams(body);
9842	                const modified = {...params};
9843	                
9844	                for (const paramName of rule.params) {
9845	                    if (params.has(paramName)) {
9846	                        const original = params.get(paramName);
9847	                        const translated = await translate.network._translateText(original);
9848	                        modified[paramName] = translated;
9849	                    }
9850	                }
9851	                
9852	                return new URLSearchParams(modified).toString();
9853	            } catch (e) {
9854	                console.warn('表单处理失败:', e);
9855	                return body;
9856	            }
9857	        },
9858
9859	        async handleJson(body, rule) {
9860	            try {
9861	                const json = JSON.parse(body);
9862	                const modified = {...json};
9863	                
9864	                for (const paramName of rule.params) {
9865	                    if (modified.hasOwnProperty(paramName)) {
9866	                        const original = modified[paramName];
9867	                        modified[paramName] = await translate.network._translateText(original);
9868	                    }
9869	                }
9870	                
9871	                return JSON.stringify(modified);
9872	            } catch (e) {
9873	                console.warn('JSON处理失败:', e);
9874	                return body;
9875	            }
9876	        }
9877	    },
9878
9879	    // 请求上下文管理
9880	    _requestContext: null,
9881
9882	    
9883
9884	    // Hook open 方法
9885	    hookOpen(method, url, async, user, password) {
9886	    	let matchedRule = null;
9887	        this._requestContext = {
9888	            method: method.toUpperCase(),
9889	            originalUrl: url,
9890	            async: async,
9891	            user: user,
9892	            password: password,
9893	            matchedRule: translate.network.getRuleMatch(url, method)
9894	        };
9895
9896	        return translate.network.originalOpen.call(this, method, url, async, user, password);
9897	    },
9898
9899	    // Hook send 方法
9900	    hookSend(body) {
9901	        const ctx = this._requestContext;
9902	        if (!ctx || !ctx.matchedRule) {
9903	            return translate.network.originalSend.call(this, body);
9904	        }
9905
9906	        const processRequest = async () => {
9907	            let modifiedBody = body;
9908	            const method = ctx.method;
9909
9910	            try {
9911	                // 处理GET请求
9912	                //if (method === 'GET') {
9913	                    const newUrl = await translate.network.RequestHandler.handleGet(ctx.originalUrl, ctx.matchedRule);
9914	                    translate.network.originalOpen.call(this, method, newUrl, ctx.async, ctx.user, ctx.password);
9915	                //}
9916
9917	                // 恢复请求头
9918	                if (ctx.headers) {
9919	                    for (const header in ctx.headers) {
9920	                        translate.network.setRequestHeaderOriginal.call(this, header, ctx.headers[header]);
9921	                    }
9922	                }    
9923	                
9924	                // 处理POST请求
9925	                if (method === 'POST') {
9926                    	if(typeof(body) != 'undefined' && body != null && body.length < 2000){
9927                			var isJsonBody = false; //是否是json格式的数据,是否json已经处理了, true 是
9928                			if(body.trim().indexOf('[') == 0 || body.trim().indexOf('{') == 0){
9929                				//可能是json
9930                				try{
9931            						modifiedBody = await translate.network.RequestHandler.handleJson(body, ctx.matchedRule);
9932            						isJsonBody = true;
9933                				}catch(je){ 
9934            						isJsonBody = false;
9935                				}
9936                			}
9937                			if(!isJsonBody){
9938                				try{
9939                					modifiedBody = await translate.network.RequestHandler.handleForm(body, ctx.matchedRule);
9940                				}catch(je){ 
9941                				}
9942                			}
9943                		}
9944	                }
9945	            } catch (e) {
9946	                console.warn('请求处理异常:', e);
9947	            }
9948
9949	            translate.network.originalSend.call(this, modifiedBody);
9950	        };
9951
9952	        // 异步处理
9953	        if (ctx.async !== false) {
9954	            processRequest.call(this);
9955	        } else {
9956	            console.warn('同步请求不支持翻译拦截');
9957	            translate.network.originalSend.call(this, body);
9958	        }
9959	    },
9960	    //fetch请求
9961	    fetch:{
9962			originalFetch: window.fetch,
9963
9964			// 保存原始 fetch 方法
9965			use: function () {
9966				const self = this;
9967				window.fetch = function (...args) {
9968					return self.hookFetch.apply(self, args);
9969				};
9970			},
9971
9972			// 拦截 fetch 请求
9973			hookFetch: async function (input, init) {
9974				const request = new Request(input, init);
9975				const url = request.url;
9976				const method = request.method;
9977
9978				// 获取匹配规则
9979				const rule = translate.network.getRuleMatch(url, method);
9980				if (!rule) {
9981					return this.originalFetch.call(window, request);
9982				}
9983
9984				// 初始化请求上下文
9985				const ctx = {
9986					method,
9987					url,
9988					headers: {},
9989					rule,
9990					isModified: false
9991				};
9992
9993				// 保存请求头
9994				request.headers.forEach((value, key) => {
9995					ctx.headers[key] = value;
9996				});
9997
9998				this._requestContext = ctx;
9999
10000				try {
10001					const newUrl = await translate.network.RequestHandler.handleGet(url, rule);
10002					// 处理 GET 请求
10003					if (method === 'GET') {
10004						
10005						const newRequest = new Request(newUrl, {
10006							method,
10007							headers: new Headers(ctx.headers),
10008							mode: request.mode,
10009							credentials: request.credentials,
10010							cache: request.cache,
10011							redirect: request.redirect,
10012							referrer: request.referrer,
10013							referrerPolicy: request.referrerPolicy
10014						});
10015						return this.originalFetch.call(window, newRequest);
10016					}
10017
10018					// 处理 POST 请求
10019					if (method === 'POST') {
10020						let body = null;
10021						if (request.body) {
10022							body = await request.clone().text();
10023						}
10024
10025						const contentType = request.headers.get('Content-Type');
10026						let modifiedBody = body;
10027
10028						if(typeof(body) != 'undefined' && body != null && body.length < 2000){
10029                			var isJsonBody = false; //是否是json格式的数据,是否json已经处理了, true 是
10030                			if(body.trim().indexOf('[') == 0 || body.trim().indexOf('{') == 0){
10031                				//可能是json
10032                				try{
10033                					modifiedBody = await translate.network.RequestHandler.handleJson(body, rule);
10034                					isJsonBody = true;
10035                				}catch(je){ 
10036                					isJsonBody = false;
10037                				}
10038                			}
10039                			if(!isJsonBody){
10040                				try{
10041                					modifiedBody = await translate.network.RequestHandler.handleForm(body, rule);
10042                				}catch(je){ 
10043                				}
10044                			}
10045                		}
10046
10047						const newRequest = new Request(newUrl, {
10048							method,
10049							headers: new Headers(ctx.headers),
10050							body: modifiedBody,
10051							mode: request.mode,
10052							credentials: request.credentials,
10053							cache: request.cache,
10054							redirect: request.redirect,
10055							referrer: request.referrer,
10056							referrerPolicy: request.referrerPolicy
10057						});
10058
10059						return this.originalFetch.call(window, newRequest);
10060					}
10061
10062					// 其他方法直接返回原始请求
10063					return this.originalFetch.call(window, request);
10064				} catch (e) {
10065					console.warn('fetch 请求处理异常:', e);
10066					return this.originalFetch.call(window, request);
10067				}
10068			},
10069			// 请求上下文管理
10070			_requestContext: null
10071
10072	    }
10073	},
10074
10075	/*js translate.network end*/
10076
10077
10078	/*js translate.visual start*/
10079	/*
10080		人眼所看到的纯视觉层的处理
10081	*/
10082	visual: {
10083		/**
10084		 * 获取一组节点的视觉矩形信息
10085		 * @param nodes - 节点数组,格式如 :
10086		 * 			[node1,node2,node3]
10087		 * @returns 返回的是二维数组,其中第一维度跟输入的 nodes 下标一一对应。
10088		 * 				其中第二维度,是应对换行的情况。比如  node1 没有换行,那第二维度就只有一个
10089		 * 												node2 有换行,有三行,那么第二维度就有三个,每行一个。 这个也是每行都有一个 开始坐标(x,y)、结束坐标(x,y)
10090		 * 	
10091		 */
10092		getRects:function(nodes){
10093			/*
10094
10095		  return nodes.map(node => {
10096		    if (!node) return null;
10097		    
10098		    let rect;
10099		    if (node.nodeType === Node.TEXT_NODE) {
10100		      const range = document.createRange();
10101		      range.selectNodeContents(node);
10102		      const rects = range.getClientRects();
10103		      //console.log(rect);
10104		      rect = rects.length > 0 ? rects[0] : null;
10105		    } else if (node.nodeType === Node.ELEMENT_NODE) {
10106		      rect = node.getBoundingClientRect();
10107		    }
10108		    
10109		    return rect ? {
10110		      node,
10111		      left: rect.left,
10112		      top: rect.top,
10113		      right: rect.right,
10114		      bottom: rect.bottom,
10115		      width: rect.width,
10116		      height: rect.height
10117		    } : null;
10118		  });
10119		  */
10120
10121			return nodes.map(node => {
10122                if (!node) return []; // 节点不存在时返回空数组
10123                
10124                let rects = [];
10125                if (node.nodeType === Node.TEXT_NODE) {
10126                    // 处理文本节点:获取所有行的矩形
10127                    const range = document.createRange();
10128                    range.selectNodeContents(node);
10129                    const clientRects = range.getClientRects();
10130                    // 转换为数组并处理每个行矩形
10131                    rects = Array.from(clientRects).map(rect => ({
10132                        node,
10133                        left: rect.left,
10134                        top: rect.top,
10135                        right: rect.right,
10136                        bottom: rect.bottom,
10137                        width: rect.width,
10138                        height: rect.height,
10139                        lineIndex: Array.from(clientRects).indexOf(rect) // 增加行索引,方便区分第几行
10140                    }));
10141                } else if (node.nodeType === Node.ELEMENT_NODE) {
10142                    // 处理元素节点:获取元素整体矩形(保持原有逻辑)
10143                    const rect = node.getBoundingClientRect();
10144                    rects = rect ? [{
10145                        node,
10146                        left: rect.left,
10147                        top: rect.top,
10148                        right: rect.right,
10149                        bottom: rect.bottom,
10150                        width: rect.width,
10151                        height: rect.height
10152                    }] : [];
10153                }
10154                
10155                return rects;
10156            });
10157		},
10158		/**
10159		 * 将 translate.visual.getRects 获取到的二维坐标数据转为一维坐标
10160		 */
10161		rectsToOneArray:function(rects){
10162			// 将 reacts 二维数组转化为 一维数组,以便对一维数组进行排序
10163			var oneArrayRects = new Array();
10164			for(var r = 0; r < rects.length; r++){
10165				for(var twoR = 0; twoR < rects[r].length; twoR++){
10166					oneArrayRects.push(rects[r][twoR]);
10167				}
10168			}
10169			return oneArrayRects;
10170		},
10171		/**
10172		 * 按行间隔筛选rects数组中的节点
10173		 * @param rects 一维的矩形信息数组(包含node和坐标信息),也就是 translate.visual.rectsToOneArray(translate.visual.getRects(nodes)); 取得的信息。它并不需要提前排序
10174		 * @param line - 间隔行数,1表示每行都取,2表示隔一行取一个,3表示隔2行取一个,以此类推
10175		 * @returns 筛选后的矩形信息数组,并按照 top 的值有小往大排序
10176		 */
10177		filterRectsByLineInterval:function(rects, line) {
10178	        // 验证输入
10179		    if (!Array.isArray(rects) || typeof line !== 'number' || line < 1) {
10180		        console.error('输入参数无效,请确保rects是数组且line是大于0的数字');
10181		        return [];
10182		    }
10183		    
10184		    // 1. 先处理所有矩形,计算每行的基准线(使用top作为主要依据)
10185		    // 为每个矩形添加行标识临时属性
10186		    const processedRects = rects.map(rect => {
10187		        if (!rect || rect.top === undefined) {
10188		            return null; // 过滤无效矩形
10189		        }
10190		        return {
10191		            ...rect,
10192		            // 计算行基准(使用top的整数部分,处理可能的浮点精度问题)
10193		            rowBase: Math.round(rect.top)
10194		        };
10195		    }).filter(Boolean); // 移除null值
10196		    
10197		    // 2. 按行基准分组(完全相同的rowBase属于同一行)
10198		    const rowMap = new Map();
10199		    processedRects.forEach(rect => {
10200		        const key = rect.rowBase;
10201		        if (!rowMap.has(key)) {
10202		            rowMap.set(key, []);
10203		        }
10204		        rowMap.get(key).push(rect);
10205		    });
10206		    
10207		    // 3. 将Map转换为数组并按行基准排序(确保从上到下的顺序)
10208		    const lineGroups = Array.from(rowMap.entries())
10209		        .sort((a, b) => a[0] - b[0]) // 按行基准升序排序
10210		        .map(entry => entry[1]); // 提取每组的矩形数组
10211		    
10212		    // 4. 按间隔行数筛选行组,并只保留每行的第一个元素
10213		    const filtered = [];
10214		    lineGroups.forEach((group, index) => {
10215		        if (index % line === 0 && group.length > 0) {
10216		            // 保留每行的第一个元素
10217		            filtered.push(group[0]);
10218		        }
10219		    });
10220		    
10221		    // 调试:打印所有行组的基准值和数量,方便验证
10222		    /*
10223		    console.log('行分组基准与数量:', lineGroups.map((g, i) => ({
10224		        rowBase: g[0].rowBase,
10225		        top: g[0].top,
10226		        count: g.length,
10227		        isSelected: i % line === 0 // 是否被选中
10228		    })));
10229		    */
10230		    
10231		    return filtered;
10232		},
10233		/*
10234			对传入的 rects 进行重叠识别排除,将重叠的、且面积小的删掉。
10235			说明:
10236				 * - 认为“重叠”必须在水平和垂直两个方向均严格交叉,交叉的位置比如水平或垂直产生了2个像素或超过2个像素的重叠,也就是面积上实际上已经重叠了。
10237				 *   所以如果两个矩形仅在边界上相接(例如 a.bottom === b.top 或 a.right === b.left)则不视为重叠,甚至稍微重叠不超过2像素也不视为重叠, 不会删除任何一方。
10238				 * - 决定保留哪一个:保留面积更大的矩形;若面积相等,则保留在排序中先出现的那个(确定性)。
10239				 * - 性能优化:先按 left 升序排序,比较时只与那些 left < current.right 的后续矩形比较(剪枝)。
10240				 * - 不做原地 splice(避免 O(n^2) 的移动开销),而是用布尔标记 removed[],最后重建结果数组。
10241
10242			@param rects 一维的矩形信息数组(包含node和坐标信息),比如 translate.visual.coordinateSort(rects); 排序后取得的信息。
10243						输入: rects: [{left, top, right, bottom}, ...]
10244			@return 返回排除重叠的坐标数组。
10245					{ 
10246						rects: 保留的不互相覆盖的矩形数组, 
10247						removes: rects中被移除的矩形数组 
10248					}
10249
10250		*/
10251		rectsSpaceEliminateOverlap: function (inputRects) {
10252			if (!Array.isArray(inputRects) || inputRects.length === 0){
10253				return { rects: [], removes: [] };
10254			}
10255
10256			const pixelThreshold = 2;
10257			const EPS = 1e-6;
10258
10259			const areaOf = r =>
10260			Math.max(0, r.right - r.left) * Math.max(0, r.bottom - r.top);
10261
10262			const intersectionWH = (a, b) => ({
10263				w: Math.min(a.right, b.right) - Math.max(a.left, b.left),
10264				h: Math.min(a.bottom, b.bottom) - Math.max(a.top, b.top)
10265			});
10266
10267			const rectsWithIndex = inputRects.map((r, idx) => {
10268				let { left, top, right, bottom } = r;
10269				if (right < left){
10270					[left, right] = [right, left];
10271				}
10272				if (bottom < top){
10273					[top, bottom] = [bottom, top]
10274				};
10275				return { r: { left, top, right, bottom }, idx };
10276			});
10277
10278			rectsWithIndex.sort((A, B) => {
10279				if (Math.abs(A.r.left - B.r.left) > EPS){
10280					return A.r.left - B.r.left;
10281				}
10282				if (Math.abs(A.r.top - B.r.top) > EPS){
10283					return A.r.top - B.r.top;
10284				}
10285				if (Math.abs(A.r.right - B.r.right) > EPS){
10286					return A.r.right - B.r.right;
10287				}
10288				return A.r.bottom - B.r.bottom;
10289			});
10290
10291			const n = rectsWithIndex.length;
10292			const removed = new Array(n).fill(false);
10293			const areas = rectsWithIndex.map(x => areaOf(x.r));
10294
10295			for (let i = 0; i < n; i++) {
10296				if (removed[i]) {
10297					continue;
10298				}
10299				const Ai = rectsWithIndex[i].r;
10300				const Ai_area = areas[i];
10301
10302				for (let j = i + 1; j < n; j++) {
10303					if (removed[j]) {
10304						continue;
10305					}
10306					const Bj = rectsWithIndex[j].r;
10307
10308					// ---- 优先检测包含(几何方式,带阈值)
10309					const A_contains_B =
10310						Ai.left <= Bj.left + pixelThreshold &&
10311						Ai.top <= Bj.top + pixelThreshold &&
10312						Ai.right >= Bj.right - pixelThreshold &&
10313						Ai.bottom >= Bj.bottom - pixelThreshold;
10314
10315					const B_contains_A =
10316						Bj.left <= Ai.left + pixelThreshold &&
10317						Bj.top <= Ai.top + pixelThreshold &&
10318						Bj.right >= Ai.right - pixelThreshold &&
10319						Bj.bottom >= Ai.bottom - pixelThreshold;
10320
10321					if (A_contains_B || B_contains_A) {
10322						if (A_contains_B && !B_contains_A) {
10323							removed[j] = true;
10324							continue;
10325						}
10326						if (B_contains_A && !A_contains_B) {
10327							removed[i] = true;
10328							break;
10329						}
10330						// 双包含(几乎重合)按面积或顺序
10331						const Bj_area = areas[j];
10332						if (Ai_area >= Bj_area){
10333							removed[j] = true;
10334						}else {
10335							removed[i] = true;
10336							break;
10337						}
10338					}
10339
10340					// ---- 剪枝 ----
10341					if (Bj.left >= Ai.right - EPS){
10342						break;
10343					}
10344
10345					// ---- 检查普通重叠 ----
10346					const { w, h } = intersectionWH(Ai, Bj);
10347					if (w <= pixelThreshold || h <= pixelThreshold){
10348						continue
10349					};
10350
10351					const Bj_area = areas[j];
10352					if (Ai_area > Bj_area){
10353						 removed[j] = true;
10354					}else if (Bj_area > Ai_area) {
10355						removed[i] = true;
10356						break;
10357					} else {
10358						removed[j] = true;
10359					}
10360				}
10361			}
10362
10363			const keeps = [], removes = [];
10364			const sortedToOrig = rectsWithIndex.map(x => x.idx);
10365			const origToSorted = new Map();
10366			for (let p = 0; p < n; p++){
10367				origToSorted.set(sortedToOrig[p], p);
10368			}
10369			for (let origIdx = 0; origIdx < inputRects.length; origIdx++) {
10370				const pos = origToSorted.get(origIdx);
10371				if (pos === undefined || !removed[pos]) {
10372					keeps.push(inputRects[origIdx]);
10373				}else{
10374					removes.push(inputRects[origIdx]);
10375				}
10376			}
10377
10378			return { rects: keeps, removes };
10379		},
10380
10381		/*
10382			对一组坐标进行排序
10383			按开始坐标从左到右、从上到下排序
10384			@param rects translate.visual.getRects获取到的坐标数据
10385		*/
10386		coordinateSort:function(rects){
10387			// 按从左到右、从上到下排序
10388		  const sortedRects = rects
10389		    .filter(rect => rect !== null)
10390		    .sort((a, b) => {
10391		      if (Math.abs(a.top - b.top) < 5) { // 同一行
10392		        return a.left - b.left;
10393		      }
10394		      return a.top - b.top;
10395		    });
10396		  return sortedRects;
10397		},
10398		
10399		/**
10400		 * 查找左右紧邻的矩形对
10401		 * @param rects translate.visual.getRects 获取到的坐标数据,转化为 一维数组 后传入
10402		 * @returns {Array<{before: Object, after: Object}>} - 左右紧邻的矩形对数组
10403		 */
10404		afterAdjacent:function(rects){
10405		  //进行从左到右-从上到下进行排序
10406		  var sortedRects = translate.visual.coordinateSort(rects);
10407
10408		  const adjacentPairs = [];
10409		  //按行分组的矩形
10410		  const lineGroups = translate.visual.groupRectsByLine(sortedRects);
10411		  
10412		  // 检查每行中的所有紧邻元素对
10413		  lineGroups.forEach(line => {
10414		    for (let i = 0; i < line.length; i++) {
10415		      for (let j = i + 1; j < line.length; j++) {
10416		        const prev = line[i];
10417		        const next = line[j];
10418		        
10419		        // 如果后续元素与当前元素不紧邻,则后续其他元素也不可能紧邻
10420		        if (!translate.visual.areHorizontallyAdjacent(prev, next)) {
10421		          break;
10422		        }
10423		        
10424		        adjacentPairs.push({ before: prev, after: next });
10425		      }
10426		    }
10427		  });
10428		  
10429		  return adjacentPairs;
10430		},
10431		/**
10432		 * 按行分组矩形
10433		 * @param rects - 排序后的矩形数组 @param rects translate.visual.coordinateSort 获取到的坐标数据
10434		 * @returns {Object[][]} - 按行分组的矩形
10435		 */
10436		groupRectsByLine:function(rects){
10437			const lineGroups = [];
10438		  let currentLine = [];
10439		  
10440		  rects.forEach(rect => {
10441		    if (currentLine.length === 0) {
10442		      currentLine.push(rect);
10443		    } else {
10444		      const lastRect = currentLine[currentLine.length - 1];
10445		      // 如果在同一行,则添加到当前行
10446		      if (Math.abs(rect.top - lastRect.top) < 5) {
10447		        currentLine.push(rect);
10448		      } else {
10449		        // 否则开始新的一行
10450		        lineGroups.push(currentLine);
10451		        currentLine = [rect];
10452		      }
10453		    }
10454		  });
10455		  
10456		  // 添加最后一行
10457		  if (currentLine.length > 0) {
10458		    lineGroups.push(currentLine);
10459		  }
10460		  
10461		  return lineGroups;
10462		},
10463		/**
10464		 * 判断两个矩形是否水平紧邻
10465		 * @param {Object} rect1 - 第一个矩形
10466		 * @param {Object} rect2 - 第二个矩形
10467		 * @returns {boolean} - 是否水平紧邻
10468		 */
10469		areHorizontallyAdjacent:function(rect1, rect2){
10470			// 检查垂直方向是否有重叠(在同一行)
10471		  const verticalOverlap = Math.min(rect1.bottom, rect2.bottom) - Math.max(rect1.top, rect2.top);
10472		  
10473		  // 检查水平间距是否在阈值范围内
10474		  const horizontalGap = rect2.left - rect1.right;
10475		  
10476		  return verticalOverlap > 0 && Math.abs(horizontalGap) < 1; // 允许1px误差
10477		},
10478		/**
10479		 * 找到需要在节点文本末尾添加空格的节点
10480		 * @param {Array<{before: Object, after: Object}>} adjacentPairs - 左右紧邻的矩形对数组
10481		 * @returns {Node[]} - 需要添加空格的节点数组
10482		 */
10483		afterAddSpace:function(adjacentPairs){
10484
10485		  const nodesToAddSpace = [];
10486		  
10487		  adjacentPairs.forEach(pair => {
10488		    const { before, after } = pair;
10489		    const beforeNode = before.node;
10490		    const afterNode = after.node;
10491		    
10492		    // 获取计算样式
10493		    const beforeStyle = window.getComputedStyle(
10494		      beforeNode.nodeType === Node.TEXT_NODE ? beforeNode.parentElement : beforeNode
10495		    );
10496		    
10497		    const afterStyle = window.getComputedStyle(
10498		      afterNode.nodeType === Node.TEXT_NODE ? afterNode.parentElement : afterNode
10499		    );
10500		    
10501		    // 检查间距是否由CSS属性引起
10502		    const hasRightSpacing = parseFloat(beforeStyle.marginRight) > 0 || 
10503		                           parseFloat(beforeStyle.paddingRight) > 0;
10504		    
10505		    const hasLeftSpacing = parseFloat(afterStyle.marginLeft) > 0 || 
10506		                          parseFloat(afterStyle.paddingLeft) > 0;
10507		    
10508		    // 如果没有明确的间距,且后一个节点的开始非空白符,则需要添加空格
10509		    if (!hasRightSpacing && !hasLeftSpacing) {
10510		    	//判断 before 节点的最后一个字符是否是空白符
10511		    	if(typeof(beforeNode.textContent) == 'string' && typeof(afterNode.textContent) == 'string'){
10512		    		if(/\s$/.test(beforeNode.textContent)){
10513		    			//before 最后一个字符是空格,则不需要追加空格符了
10514		    		}else if(/^\s/.test(afterNode.textContent)){
10515		    			//after 节点的开始第一个字符是空白符,那么也不需要追加空格符了
10516		    		}else{
10517		    			//这里就需要对 beforeNode 追加空格了
10518		    			nodesToAddSpace.push(beforeNode);
10519		    		}
10520		    	}
10521		    }
10522		  });
10523		  
10524		  return nodesToAddSpace;
10525		},
10526		/**
10527		 * 主函数:处理翻译后的空格调整
10528		 * @param {Node[]} nodes - 节点数组
10529		 */
10530		adjustTranslationSpaces:function(nodes){
10531
10532			//先判断当前要显示的语种,是否需要用空格进行间隔单词,如果本身不需要空格间隔,像是中文,那就根本不需要去计算视觉距离
10533			if(!translate.language.wordBlankConnector(translate.to)){
10534				return;
10535			}
10536
10537			//var startTime = Date.now();
10538			// 1. 获取节点视觉矩形
10539			const rects = translate.visual.getRects(nodes);
10540
10541			// 将 reacts 二维数组转化为 一维数组,以便对一维数组进行排序
10542			var oneArrayRects = translate.visual.rectsToOneArray(rects);
10543
10544			//console.log('rects:');
10545			//console.log(rects);
10546			//console.log('将 reacts 二维数组转化为一维数组 oneArrayRects:');
10547			//console.log(oneArrayRects);
10548
10549			// 2. 查找左右紧邻的矩形对
10550			const adjacentPairs = translate.visual.afterAdjacent(oneArrayRects);
10551			//console.log('adjacentPairs:');
10552			//console.log(adjacentPairs);
10553
10554			// 3. 确定需要添加空格的节点
10555			const nodesToAddSpace = translate.visual.afterAddSpace(adjacentPairs);
10556			//console.log('nodesToAddSpace:');
10557			//console.log(nodesToAddSpace);
10558
10559			// 4. 添加非断行空格
10560			nodesToAddSpace.forEach(node => {
10561			// 确保只修改文本内容,不影响HTML结构
10562			if (node.nodeType === Node.TEXT_NODE) {
10563				
10564				//判断它的最后一个字符是否是空格,如果不是空格,才有必要加空格符
10565				if(node.textContent.length === 0 || node.textContent.substring(node.textContent.length -1, node.textContent.length) !== '\u00A0'){
10566
10567					//找到它对应的 translate.node.data 的数据,先将其进行改动 - 目的是 listener 监听改动知道这是translate.js自己改的 - 以及 让 translate.node 的数据对应起来
10568					if(translate.node.get(node) !== null){
10569						if(typeof(translate.node.get(node).resultText) !== 'string'){
10570							//没有resultText这个属性,如果翻译失败或者本身是特殊字符比如数字,不需要被翻译,是没有这个属性的,那这里默认赋予 originalText 给他,以做记录,免得被listener监听
10571							translate.node.get(node).resultText = translate.node.get(node).originalText;
10572						}
10573						translate.node.get(node).resultText = translate.node.get(node).resultText + '\u00A0';
10574
10575						if(typeof(translate.node.get(node).translateResults) === 'undefined'){
10576							translate.node.get(node).translateResults = {};
10577						}
10578						translate.node.get(node).translateResults[translate.node.get(node).resultText] = 1;
10579
10580						translate.node.get(node).lastTranslateRenderTime = Date.now();
10581					}
10582
10583					//console.log(node.textContent+'-->'+node.textContent.substring(node.textContent.length -1, node.textContent.length));
10584					node.textContent = node.textContent + '\u00A0';
10585				}
10586				
10587				//console.log(translate.node.get(node))
10588			} else if (node.nodeType === Node.ELEMENT_NODE) {
10589				// 如果是元素节点,修改其最后一个子节点(假设是文本节点)
10590				const lastChild = node.lastChild;
10591				if (lastChild && lastChild.nodeType === Node.TEXT_NODE) {
10592					//判断它的最后一个字符是否是空格,如果不是空格,才有必要加空格符
10593					if(lastChild.textContent.length === 0 || lastChild.textContent.substring(lastChild.textContent.length -1, lastChild.textContent.length) !== '\u00A0'){
10594						//找到它对应的 translate.node.data 的数据,先将其进行改动 - 目的是 listener 监听改动知道这是translate.js自己改的 - 以及 让 translate.node 的数据对应起来
10595						if(translate.node.get(lastChild) !== null){
10596							if(typeof(translate.node.get(lastChild).resultText) === 'string'){
10597								translate.node.get(lastChild).resultText = translate.node.get(lastChild).resultText + '\u00A0';
10598								translate.node.get(lastChild).translateResults[translate.node.get(node).resultText] = 1;
10599								translate.node.get(lastChild).lastTranslateRenderTime = Date.now();
10600							}
10601						}
10602						lastChild.textContent = lastChild.textContent + '\u00A0';
10603					}
10604				}
10605			}
10606			});
10607			//var endTime = Date.now();
10608			//console.log('visual recognition time: '+(endTime-startTime)+'ms');
10609		},
10610		/*
10611			通过 translate.nodeQueue[uuid] 中的uuid,来传入这个 translate.nodeQueue[uuid] 中所包含涉及到的所有node (除特殊字符外 ,也就是 translate.nodeQueue[uuid].list 下 特殊字符那一类是不会使用的)
10612		*/
10613		adjustTranslationSpacesByNodequeueUuid:function(uuid){
10614			var nodes = [];
10615			for(var from in translate.nodeQueue[uuid].list){
10616				if (!translate.nodeQueue[uuid].list.hasOwnProperty(from)) {
10617					continue;
10618				}
10619				//空的,也就是有数字标点符号等这一类,也要加入,因为也要算入视觉间隔中去,比如 我有9个,其中的9如果不算的话,翻译后 my have9ge 就没有间隔了
10620				//if(from.length < 1){
10621				//	continue;
10622				//}
10623				if(typeof(translate.nodeQueue[uuid].list[from]) === 'undefined'){
10624					continue;
10625				}
10626				for(var hash in translate.nodeQueue[uuid].list[from]){
10627			    	if (!translate.nodeQueue[uuid].list[from].hasOwnProperty(hash)) {
10628			    		continue;
10629			    	}
10630			    	for(var nodeindex in translate.nodeQueue[uuid].list[from][hash].nodes){
10631			    		if (!translate.nodeQueue[uuid].list[from][hash].nodes.hasOwnProperty(nodeindex)) {
10632				    		continue;
10633				    	}
10634			    		var node = translate.nodeQueue[uuid].list[from][hash].nodes[nodeindex].node;
10635			    		nodes.push(node);
10636			    	}
10637			    }	
10638			}
10639			translate.visual.adjustTranslationSpaces(nodes);
10640		},
10641		/*
10642			通过 translate.nodeQueue 中最后一次执行的 uuid,来获取这个 translate.nodeQueue[uuid] 中所包含涉及到的所有node (除特殊字符外 ,也就是 translate.nodeQueue[uuid].list 下 特殊字符那一类是不会使用的)
10643		*/
10644		adjustTranslationSpacesByLastNodequeueUuid:function(uuid){
10645			var uuid = '';
10646			for(var uuid_index in translate.nodeQueue){
10647				uuid = uuid_index;
10648				break;
10649			}
10650			if(typeof(uuid) == 'string' && uuid.length > 1){
10651				translate.visual.adjustTranslationSpacesByNodequeueUuid(uuid);
10652			}
10653		},
10654
10655		/**
10656		 * 隐藏当前网页的所有文本
10657		 *
10658		 */
10659		hideText:{
10660			style:`
10661				/* 文本隐藏核心样式 - 仅隐藏文本内容 */
10662
10663		        html.translatejs-text-hidden p, html.translatejs-text-hidden div, html.translatejs-text-hidden small, 
10664		        html.translatejs-text-hidden h1, html.translatejs-text-hidden h2, html.translatejs-text-hidden h3,
10665		        html.translatejs-text-hidden h4, html.translatejs-text-hidden h5, html.translatejs-text-hidden h6,
10666		        html.translatejs-text-hidden span, html.translatejs-text-hidden a, html.translatejs-text-hidden b,
10667		        html.translatejs-text-hidden strong, html.translatejs-text-hidden i, html.translatejs-text-hidden em,
10668		        html.translatejs-text-hidden mark,
10669		        html.translatejs-text-hidden blockquote, html.translatejs-text-hidden ul, html.translatejs-text-hidden ol,
10670		        html.translatejs-text-hidden li, html.translatejs-text-hidden table, html.translatejs-text-hidden th,
10671		        html.translatejs-text-hidden td, html.translatejs-text-hidden label, html.translatejs-text-hidden button,
10672		        html.translatejs-text-hidden input, html.translatejs-text-hidden select, html.translatejs-text-hidden textarea {
10673		            color: transparent !important;
10674		            text-shadow: none !important;
10675					transition: none !important;
10676		        }
10677
10678		        /* 隐藏占位符文字 */
10679		        html.translatejs-text-hidden ::placeholder {
10680		            color: transparent !important;
10681		        }
10682
10683		        /* 确保媒体元素不受影响 */
10684		        img, video, iframe, canvas, svg,
10685		        object, embed, picture, source {
10686		            color: initial !important;
10687		        }
10688
10689		        /* 忽略隐藏的元素保持可见 */
10690		        .ignore-hidden {
10691		            color: inherit !important;
10692		        }
10693			`,
10694
10695			/**
10696			 * 当点击切换语言按钮后,会刷新当前页面,然后再进行翻译。 
10697			 * 这时会出现刷新当前页面后,会先显示原本的文本,然后再翻译为切换为的语种,体验效果有点欠缺。  
10698			 * 这个得作用就是增强用户视觉的体验效果,在页面初始化加载时,如果判定需要翻译,那么会隐藏所有网页中的文本 。
10699			 * 这个需要在body标签之前执行,需要在head标签中执行此。也就是加载 translate.js 以及触发此都要放到head标签中
10700			 * 
10701			 * id 唯一标识,可能会隐藏多次,或者同一时间出发多次不同的元素隐藏,每次隐藏跟显示都是根据这个id唯一标识来的, 字符串类型。 如果没有,默认就是 translatejs-text-hidden
10702			 */
10703			hide:function(id){
10704				const style = document.createElement('style');
10705
10706				if(typeof(id) == 'undefined' || id == null || id.length == 0){
10707					id = 'translatejs-text-hidden';
10708					style.textContent = translate.visual.hideText.style;
10709				}else{
10710					//有值
10711					id = 'translatejs-text-hidden-'+id;
10712					style.textContent = translate.visual.hideText.style.replace(/translatejs-text-hidden/g, id).replace(/\/\*(.*)\*\//g, ' ').replace(/\n/g, ' ');
10713				}
10714				style.id = id;
10715				document.head.appendChild(style);
10716			    document.documentElement.classList.add(id);
10717			},
10718			/**
10719			 * 撤销隐藏状态,将原本的文本正常显示出来 
10720			 * 
10721			 * id 同 hide 的
10722			 */
10723			show:function(id){
10724				if(typeof(id) == 'undefined' || id == null || id.length == 0){
10725					id = 'translatejs-text-hidden';
10726				}else{
10727					//有值
10728					id = 'translatejs-text-hidden-'+id;
10729				}
10730
10731				//删除html 的 class name
10732				document.documentElement.classList.remove(id);
10733				//删除 style
10734				var style_translatejs_text_hidden = document.getElementById(id);
10735				if(typeof(style_translatejs_text_hidden) != null && style_translatejs_text_hidden != null){
10736					style_translatejs_text_hidden.remove();
10737				}
10738			}
10739		},
10740
10741		/*
10742			这个主要是配合下面的,如果下面的 webPageLoadTranslateBeforeHiddenText 触发ï¼
10742Œåˆ™è‡ªåŠ¨è®¾ç½®æ­¤å¤„ä¸ºtrue,为启用切换语种或刷新页面后先隐藏原本的文本
10743			它只是提供判断使用,不可直接设置操作
10744		*/
10745		webPageLoadTranslateBeforeHiddenText_use: false,
10746
10747		/**
10748			 网页加载,且要进行翻译时,翻译之前,隐藏当前网页的文本。
10749			 当点击切换语言按钮后,会刷新当前页面,然后再进行翻译。 
10750			 这时会出现刷新当前页面后,会先显示原本的文本,然后再翻译为切换为的语种,体验效果有点欠缺。  
10751			 
10752			 这个得作用就是增强用户视觉的体验效果,在页面初始化加载时,如果判定需要翻译,那么会隐藏所有网页中的文本 。
10753			 他会先隐藏网页所有文本,然后再第一次 translate.execute 执行时,在扫描完节点,
10754			 	1. 将扫描到的几种语种的文本全部发送网络请求之后,(也就是已经触发了发送网络请求的文本node已经处于隐藏状态), 才会去掉整个网页文本的隐藏。
10755				2. 在第一次 translate.execute 执行渲染完毕后,去掉整个网页文本的隐藏。
10756				3. 在 dom
10757
10758			 这个需要在body标签之前执行,需要在head标签中执行此。也就是加载 translate.js 以及触发此都要放到head标签中
10759
10760			 config 参数,配置项,默认不传
10761			 	{
10762					inHeadTip:true, 	//警告要在head中触发的控制台消息提醒,true是如果发现就打印这个提醒。 默认不设置便是true
10763			 	}
10764		 */
10765		webPageLoadTranslateBeforeHiddenText:function(config){
10766			if(typeof(config) == 'undefined'){
10767				config = {};
10768			}
10769			if(typeof(config.inHeadTip) == 'undefined'){
10770				config.inHeadTip = true;
10771			}
10772			
10773			//标记,当前启用整体隐藏文本的能力
10774			translate.visual.webPageLoadTranslateBeforeHiddenText_use = true;
10775
10776			if(typeof(document.body) == 'undefined' || document.body == null){
10777				//正常,body还没加载
10778			}else{
10779				if(config.inHeadTip){
10780					translate.log('警告: translate.visual.webPageLoadTranslateBeforeHiddenText() 要在 head 标签中触发才能达到最好的效果!');
10781				}
10782			}
10783			if(translate.language.local == ''){
10784				translate.log('提醒:在使用 translate.visual.webPageLoadTranslateBeforeHiddenText() 之前,请先手动设置你的本地语种,参考: http://translate.zvo.cn/4066.html  如果你不设置本地语种,则不管你是否有切换语言,网页打开后都会先短暂的不显示文字');
10785			}
10786
10787			if(translate.language.local == '' || translate.language.translateLocal == true || translate.language.local != translate.language.getCurrent()){
10788				//如果当前触发翻译,才会出现这个隐藏文本,因为取消隐藏必须要 translate.execute() 触发后才会取消隐藏
10789
10790				translate.visual.hideText.hide();
10791
10792				/*
10793				// 创建定时器,每10ms执行一次,以保持最顶层 html 标签上的class 不被项目或框架本身自动给覆盖掉
10794				//针对 60HZ刷新率,避免人眼视觉上出现屏闪,所以使用 10ms
10795				translate.visual.hideText.htmlAppendClassIntervalId = setInterval(function(){
10796					document.documentElement.classList.add('translatejs-text-hidden');
10797				}, 10);
10798				*/
10799
10800				//设置发起网络请求前,记录发起了几次翻译请求,避免发起了多次,但是第一次执行完了就显示文本了,但是后几次还在翻译中,还是会出现显示原文的情况
10801				//translate.lifecycle.execute.translateNetworkBefore.push(function(uuid, from, to, texts){
10802				translate.lifecycle.execute.translateNetworkBefore.push(function(data){
10803					if(typeof(translate.visual.hideText.first_translate_request_uuid) == 'undefined'){ 
10804						//是第一次翻译请求,记录其uuid
10805						translate.visual.hideText.first_translate_request_uuid = data.uuid;
10806					}
10807
10808					//只有第一次通过网络翻译接口请求才会记录uuid
10809					if(translate.visual.hideText.first_translate_request_uuid == data.uuid){
10810						if(typeof(translate.visual.hideText.first_translate_request_number) == 'undefined'){
10811							translate.visual.hideText.first_translate_request_number = 0;
10812						}
10813						translate.visual.hideText.first_translate_request_number++;
10814						//console.log('translate.visual.hideText.first_translate_request_number++   from:'+from+', ++ after number: '+translate.visual.hideText.first_translate_request_number);
10815					}
10816				});
10817
10818				//设置翻译完成后,移除隐藏文本的css 的class name
10819				translate.lifecycle.execute.renderFinish.push(function(uuid, to){
10820					//console.log('renderFinish : '+uuid);
10821					if(typeof(translate.visual.hideText.first_translate_request_uuid) == 'undefined'){
10822						//为空,那么可能是已经触发过浏览器缓存了,所有翻译的文本在浏览器缓存中都有,就不必再发起网络请求了
10823
10824
10825					}else{
10826						//是发起过网络请求的,要计算请求数,所有的语种都翻译完后才能显示文本
10827						if(translate.visual.hideText.first_translate_request_uuid != uuid){
10828							//不是同一个uuid的,那也就是并不是第一次翻译了,而这个 webPageLoadTranslateBeforeHiddenText 针对的是页面加载后第一次翻译的避免原文一闪的情况 
10829							return;
10830						}
10831					}
10832					/*
10833					销毁定时器 - 不要删,预留
10834					if(typeof(translate.visual.hideText.htmlAppendClassIntervalId) != 'undefined'){
10835						clearInterval(translate.visual.hideText.htmlAppendClassIntervalId);
10836						console.log("translate.visual.hideText.htmlAppendClassIntervalId 已销毁 : "+translate.visual.hideText.htmlAppendClassIntervalId);
10837					}
10838					*/
10839					
10840					translate.visual.hideText.show();
10841				});
10842			}
10843
10844			//translate.execute 触发执行结束触发
10845			translate.lifecycle.execute.finally.push(function(data){
10846			    if(data.triggerNumber < 3){
10847			    	//只有在第一次、第二次 触发后才会隐藏文本,这里避免只第一次,是万一第一次出现异常,网页在空白不显示内容了,多触发几次也不会影响多少性能。而且这个是对网页整体进行显示的,只有页面初始化打开的时候才会用到这个相关的隐藏跟显示, 正常网络请求使用的就不是这个了
10848					translate.visual.hideText.show();
10849					//console.log('隐藏 translate.visual.hideText.show();');
10850			    }
10851			});
10852
10853
10854		}
10855
10856		
10857
10858
10859
10860	},
10861	/*js translate.visual end*/
10862
10863	/*
10864		历史, 20250924 增加
10865
10866	*/
10867	history:{
10868		/*
10869			翻译文本相关,map的初始化在 translate.init() 中进行
10870			只有当正常翻译且翻译完成(成功)的,才会记录到这里
10871			比如 自定义忽略翻译文字  ‘你好’ ,元素的内容为 ‘你好世界’,它会将   你好、你好世界  这两个都加入进去
10872		*/
10873		translateText: {
10874			/*
10875				以翻译结果为 key 的 map
10876				value: 
10877					original 翻译的原文
10878			*/
10879			resultMap:null,
10880			/*
10881				以翻译原文为 key 的 map
10882				value: 
10883					result 翻译的结果
10884			*/
10885			originalMap:null,
10886
10887			/*
10888				加入一条翻译记录
10889			*/
10890			add: function(original, result){
10891				//console.log(original +' - '+result);
10892				translate.history.translateText.resultMap.set(result, original);
10893				translate.history.translateText.originalMap.set(original, result);
10894			}
10895		},
10896
10897	},
10898	
10899	/*
10900		记录打印翻译执行的耗时情况
10901	*/
10902	time:{
10903		// 执行 translate.execute() 的时间相关
10904		execute:{
10905			//true启用, false不启用,默认是不启用状态,不要直接调用,而是使用 translate.time.execute.start();
10906			isUse: false,
10907
10908			/*
10909				key: uuid ,也就是 每次 translate.execute() 都会创建一个uuid
10910				value: 执行 translate.execute() 的耗时,分为几部分:
10911					all: 总耗时,单位是毫秒,从触发 translate.execute() 到所有的接口请求渲染完毕的耗时
10912
10913
10914			*/
10915			data: {},
10916
10917			/*
10918				启动耗时打印
10919			*/
10920			start:function(){
10921				if(translate.time.execute.isUse){
10922					//已经启动过了,不需要再启动了
10923					translate.log('translate.time.execute.start() 已经启动过了,不需要再启动了');
10924				}
10925
10926				translate.time.execute.data.isUse = true;
10927
10928				//翻译开始
10929				translate.lifecycle.execute.start.push(function(uuid, to){
10930					if(typeof(translate.time.execute.data[uuid]) == 'undefined'){
10931						translate.time.execute.data[uuid] = {};
10932					}
10933					translate.time.execute.data[uuid].startTime = new Date().getTime();
10934				});
10935
10936				//发起网络请求前
10937				translate.lifecycle.execute.translateNetworkBefore.push(function(data){
10938				    translate.time.execute.data[data.uuid].translateNetworkBeforeTime = new Date().getTime();
10939				});
10940				
10941				/*
10942				//发起网络请求后
10943				translate.lifecycle.execute.translateNetworkAfter.push(function(data){
10944				    translate.time.execute.data[data.uuid].translateNetworkBeforeTime = new Date().getTime();
10945				});
10946				*/
10947
10948				//翻译完成(渲染全部语种都完成)
10949				translate.lifecycle.execute.renderFinish.push(function(uuid, to){
10950					translate.time.execute.data[uuid].finishTime = new Date().getTime();
10951					translate.time.execute.data[uuid].allTime = translate.time.execute.data[uuid].finishTime - translate.time.execute.data[uuid].startTime;
10952					
10953
10954					/*** 取当前 translate.execute() 翻译,进行翻译的文本跟翻译的原语种 - start ***/
10955					var translateTexts = new Array(); //翻译的文本的数组,翻译的原文本
10956					var translateLanguages = new Array(); //翻译的语种数组
10957
10958					var queueValue = translate.nodeQueue[uuid];
10959					//console.log(queueValue);
10960					for(var lang in translate.nodeQueue[uuid].list){
10961						if (!translate.nodeQueue[uuid].list.hasOwnProperty(lang)) {
10962				    		continue;
10963				    	}
10964						if(typeof(lang) != 'string' || lang.length < 1){
10965							continue;
10966						}
10967
10968						translateLanguages.push(lang);
10969						
10970						for(var hash in translate.nodeQueue[uuid].list[lang]){
10971							if (!translate.nodeQueue[uuid].list[lang].hasOwnProperty(hash)) {
10972					    		continue;
10973					    	}
10974					    	translateTexts.push(translate.nodeQueue[uuid].list[lang][hash].original);
10975						}
10976						
10977					}
10978					
10979					//console.log(translateTexts)
10980					//console.log(translateLanguages)
10981					/*** 取当前 translate.execute() 翻译,进行翻译的文本跟翻译的原语种 - end ***/
10982
10983					translate.time.execute.data[uuid].translateLanguages = translateLanguages;
10984					translate.time.execute.data[uuid].translateTexts = translateTexts;
10985
10986
10987					translate.log('[time][translate.execute()] '+translate.time.execute.data[uuid].allTime+'ms '+(typeof(translate.time.execute.data[uuid].translateNetworkBeforeTime) != 'undefined'? '(search&cache '+(translate.time.execute.data[uuid].translateNetworkBeforeTime - translate.time.execute.data[uuid].startTime)+'ms)':'')+ (translateTexts.length > 0 ?  (' , ['+translateLanguages+'] : ('+translateTexts.length+')['+translateTexts.slice(0, 3)+(translateTexts.length > 3 ? ', ...':'')+']'):''));
10988				});
10989			},
10990			
10991		},
10992
10993		use:false, //true启用, false不启用,默认是不启用状态
10994
10995
10996		printTime: 0, //打印耗时大于这个的,默认是0,也就是全部打印。单位是毫秒。 比如设置为 100 ,则只打印耗时大于等于100毫秒的动作
10997
10998		/**
10999		 * 增加一条日志记录
11000		 * functionName 触发调用此处log的方法名,传入如 translate.execute
11001		 * remark 备注文字,说明
11002		 */ 
11003		log:function(remark){
11004			if(translate.time.use == false){
11005				return;
11006			}
11007
11008			var timestamp = new Date().getTime(); // 例如:1725053445123
11009
11010			var usetime = 0; //跟上一次记录的间隔耗时,单位是毫秒
11011			if(typeof(translate.time.temp_lasttime) != 'undefined'){
11012				usetime = timestamp-translate.time.temp_lasttime;
11013			}
11014			translate.time.temp_lasttime = timestamp;
11015			
11016			if(usetime < translate.time.printTime){
11017				//不需要打印
11018				return; 
11019			}
11020
11021			var functionName = '';
11022			try {
11023				// 创建一个Error对象来获取调用栈
11024				var error = new Error();
11025				// 解析调用栈,获取调用者信息
11026				// 不同环境下调用栈的索引可能不同,这里做了兼容处理
11027				var stackLines = error.stack.split('\n');
11028				//console.log(stackLines);
11029				var callerLine = stackLines[2] || stackLines[3]; // 兼容不同环境
11030
11031				// 从调用栈中提取方法名
11032				var functionMatch = callerLine.match(/at (\S+)/);
11033
11034				if (functionMatch && functionMatch[1]) {
11035				  functionName = functionMatch[1];
11036				}
11037			} catch (e) {
11038				// 如果获取调用栈失败,使用原始log方法
11039				translate.log(e);
11040			}
11041
11042			functionName = functionName.replace('Object.','translate.');
11043			translate.log(functionName+'() '+usetime+' -> '+remark);
11044		}
11045
11046	},
11047
11048	/*
11049		容错
11050	*/
11051	faultTolerance: {
11052
11053		// 优化文本节点创建的拦截逻辑
11054		// 在对 continew-admin-ui 框架进行适配时,发现有tip鼠标提示场景,而且是出现在table中的,一下就会出来十个,它的渲染跟 translate.listener.start(); 监听有几率会出现一直循环的情况,也就是 translate.listener.start(); 将文本翻译了,然后vue自动给渲染还原,然后 translate.listener.start(); 继续给翻译,造成性能损耗。这里就是处理这种情况的
11055		documentCreateTextNode: {
11056			/*
11057				原本的 document.createTextNode
11058				如果不为null,则是已开启,也就是已经触发了 translate.faultTolerance.documentCreateTextNode.enable();
11059				如果为null,则是未开启,有两种可能
11060						1. 未触发 translate.faultTolerance.documentCreateTextNode.enable();
11061						2. 触发了 translate.faultTolerance.documentCreateTextNode.disable();
11062			*/
11063			originalCreateTextNode: null,
11064
11065			/*
11066				发生改动操作的文本节点
11067				key: node
11068				value: 
11069			*/
11070			node:null,
11071
11072			/*
11073				启用此容错的能力
11074				如果触发此启用,那么会根据用户切换语言及设置,自动进行判定是否介入
11075			*/
11076			use:function(){
11077				if(translate.faultTolerance.documentCreateTextNode.node == null){
11078					translate.faultTolerance.documentCreateTextNode.node = new Map();
11079				}
11080
11081				//当用户点击切换语言时触发
11082				translate.lifecycle.changeLanguage.push(function(to){
11083					if(translate.isTranslate(to)){
11084						//需要触发翻译
11085						translate.faultTolerance.documentCreateTextNode.enable();
11086						//console.log('translate.faultTolerance.documentCreateTextNode enable');
11087					}else{
11088						//不在翻译,禁用,释放
11089						translate.faultTolerance.documentCreateTextNode.disable();
11090						//console.log('translate.faultTolerance.documentCreateTextNode disable');
11091					}
11092				});
11093
11094				//当第一次打开页面执行翻译时,触发
11095				translate.lifecycle.execute.start.push(function(data){
11096				    if(translate.executeNumber === 0){
11097				        //console.log('这是打开页面后,第一次触发 translate.execute() ,因为translate.executeNumber 记录的是translate.execute() 执行完的次数。');
11098				    	if(translate.isTranslate(data.to)){
11099				    		//console.log('data to -->'+data.to);
11100				    		//需要触发翻译
11101							translate.faultTolerance.documentCreateTextNode.enable();
11102				    	}
11103				    }
11104				});
11105			},
11106
11107			/*
11108				启用
11109				可多次调用,如果多次调用,第一次启用,之后的都会不做任何处理
11110			*/
11111			enable: function(){
11112
11113				//如果已开启,那就不需要再重复启用了
11114				if(translate.faultTolerance.documentCreateTextNode.originalCreateTextNode != null){
11115					return;
11116				}
11117
11118
11119				translate.faultTolerance.documentCreateTextNode.originalCreateTextNode = document.createTextNode;
11120				document.createTextNode = function(text) {
11121					var isTrans = false; //是否进行了翻译处理,true是
11122					var originalText = null; //原本要创建node的文本,如果 isTrans为true,这里才会赋予
11123
11124					if(translate.executeTriggerNumber > 0){
11125						//已经触发过翻译执行了,那么才会启用这个能力
11126
11127						if(typeof(text) === 'string' && text.length > 0){
11128							var textTranslateResult = translate.history.translateText.originalMap.get(text);
11129							if(typeof(textTranslateResult) === 'string' && textTranslateResult.length > 0){
11130								// 直接更新text
11131								originalText = text;
11132								text = textTranslateResult;
11133								//console.log('创建文本节点: '+textTranslateResult);
11134								isTrans = true;
11135							}
11136						}
11137					}
11138
11139					// 创建文本节点 - 使用[text]数组代替arguments,使代码更明确和现代
11140					const textNode = translate.faultTolerance.documentCreateTextNode.originalCreateTextNode.call(this, text);
11141					if(isTrans){
11142						//console.log(textNode);
11143						translate.faultTolerance.documentCreateTextNode.node.set(textNode, {
11144							originalText: originalText,
11145							resultText: text
11146						});
11147						//将其记录到 translate.node.data
11148						translate.node.set(textNode,{
11149							attribute:"",
11150							originalText: originalText,
11151							resultText: text,
11152							translateTexts: {}, //这里因为直接从缓存中取的,没有走网络接口,所以这里直接空
11153							whole: true,
11154							translateResults: {
11155								[originalText]:1
11156							},
11157							lastTranslateRenderTime: Date.now()
11158						});
11159						
11160						
11161					}
11162					
11163					return textNode;
11164				};
11165			},
11166			/*
11167				禁用。不再做任何处理,释放性能
11168			*/
11169			disable: function(){
11170				if(translate.faultTolerance.documentCreateTextNode.originalCreateTextNode != null){
11171					document.createTextNode = translate.faultTolerance.documentCreateTextNode.originalCreateTextNode;
11172					translate.faultTolerance.documentCreateTextNode.originalCreateTextNode = null;
11173				}
11174			}
11175		}
11176	},
11177
11178	/*
11179		快速接入,在head中引入使用,它集成了 translate.execute() 进去
11180		
11181		需要提前做的:
11182			//需要提前设置本地语种(当前网页的语种)
11183			translate.language.setLocal('chinese_simplified'); 
11184		
11185		建议做的:
11186			//设置机器翻译服务通道,相关说明参考 http://translate.zvo.cn/545867.html
11187		    translate.service.use('client.edge'); 
11188
11189	*/
11190	quickUse:function(){
11191		//网页打开时自动隐藏文字,翻译完成后显示译文 http://translate.zvo.cn/549731.html
11192		translate.visual.webPageLoadTranslateBeforeHiddenText(); 
11193
11194		//启用翻译中的遮罩层 http://translate.zvo.cn/407105.html
11195		translate.progress.api.startUITip(); 
11196
11197	    //开启页面元素动态监控,js改变的内容也会被翻译,参考文档: http://translate.zvo.cn/4067.html
11198	    translate.listener.start(); 
11199
11200	    //网页ajax请求触发自动翻译 http://translate.zvo.cn/4086.html
11201	    translate.request.listener.start();
11202	    
11203	    //url参数后可以加get方式传递 language 参数的方式控制当前网页以什么语种显示 http://translate.zvo.cn/4075.html
11204	    translate.language.setUrlParamControl(); 
11205
11206	    //本地语种也进行强制翻译 http://translate.zvo.cn/289574.html
11207	    translate.language.translateLocal = true;
11208
11209	    //元素的内容整体翻译能力配置 ,提高翻译的语义 https://translate.zvo.cn/4078.html
11210	    translate.whole.enableAll();
11211
11212	    //dom加载完毕后立即触发翻译
11213	    document.addEventListener('DOMContentLoaded', function() {
11214	    	translate.execute();//完成翻译初始化,进行翻译
11215
11216	    	setTimeout(function(){
11217	    		translate.execute();//完成翻译初始化,进行翻译
11218	    	}, 500);
11219	    	setTimeout(function(){
11220	    		translate.execute();//完成翻译初始化,进行翻译
11221	    	}, 2000);
11222		});
11223
11224	},
11225
11226	/*js translate.recycle start*/
11227	/*
11228		自动回收 translate.js 本身记录的相关信息,避免某些页面有循环触发,停留页面过长导致数据持续堆积
11229
11230		translate.nodeQueue
11231		translate.node.data
11232		
11233	*/
11234	recycle: function(){
11235		var currentTime = new Date().getTime(); //当前时间
11236		//var before_second = 120; 	//要删除往前多少秒的数据
11237
11238		
11239		/*** translate.nodeQueue ****/
11240		var nodeQueueDeleteArray = []; //要删除的nodeQueue,其中存储的是 uuid
11241		for(var uuid in translate.nodeQueue){
11242			if (!translate.nodeQueue.hasOwnProperty(uuid)) {
11243	    		continue;
11244	    	}
11245			var expireTime = translate.nodeQueue[uuid].expireTime;
11246			if(expireTime+120 < currentTime){
11247				nodeQueueDeleteArray.push(uuid);
11248			}	
11249		}
11250		for(var qi = 0; qi < nodeQueueDeleteArray.length; qi++){
11251			delete translate.nodeQueue[nodeQueueDeleteArray[qi]];
11252			//console.log('delete nodeQueue -> '+nodeQueueDeleteArray[qi]);
11253		}
11254
11255
11256		/*** translate.node.data ****/
11257		translate.node.refresh();
11258
11259		/*** translate.time.execute.data ****/
11260		var timeExecuteDeleteArray = []; //要删除的,其中存储的是 uuid
11261		for(var uuid in translate.time.execute.data){
11262			if (!translate.time.execute.data.hasOwnProperty(uuid)) {
11263	    		continue;
11264	    	}
11265	    	if(typeof(translate.time.execute.data[uuid].finishTime) == 'undefined'){
11266	    		//还没执行完
11267	    		continue;
11268	    	}
11269			var finishTime = translate.time.execute.data[uuid].finishTime;
11270			if(finishTime+120 < currentTime){
11271				timeExecuteDeleteArray.push(uuid);
11272			}	
11273		}
11274		for(var ti = 0; ti < timeExecuteDeleteArray.length; ti++){
11275			delete translate.time.execute.data[timeExecuteDeleteArray[ti]];
11276			//console.log('delete translate.time.execute.data -> '+timeExecuteDeleteArray[ti]);
11277		}
11278		
11279
11280
11281	},
11282	/*js translate.recycle end*/
11283
11284	/*js translate.init start*/
11285	/*
11286		初始化,如版本检测、初始数据加载、map声明、监听启动 等
11287		会自动在 translate.js 加载完自动触发执行
11288	*/
11289	init:function(){
11290		
11291		// 确保初始化只进行一次
11292		if(typeof(translate.init_execute) != 'undefined'){
11293			return;
11294		}
11295		translate.init_execute = '已进行';
11296
11297		//初始化 translate.node.data
11298		if(translate.node.data == null){
11299			translate.node.data = new Map();
11300		}
11301		//初始化 历史
11302		if(translate.history.translateText.resultMap == null){
11303			translate.history.translateText.resultMap = new Map();
11304		}
11305		if(translate.history.translateText.originalMap == null){
11306			translate.history.translateText.originalMap = new Map();
11307		}
11308		//语系相关
11309		if(translate.language.name == null){
11310			translate.language.generateLanguageNameObject();
11311		}
11312
11313		//监听,当第一次触发 translate.execute() 时,执行
11314		translate.lifecycle.execute.start.push(function(uuid, to){
11315			//只在第一次触发时,才会做一些初始化
11316			if(typeof(translate.init_first_trigger_execute) != 'undefined'){
11317				return;
11318			}
11319			translate.init_first_trigger_execute = 1;
11320
11321			//将自定义术语加入 translate.history.translateTexts 中
11322			//console.log(translate.nomenclature.data);
11323			for(var currentLanguage in translate.nomenclature.data){
11324				if (!translate.nomenclature.data.hasOwnProperty(currentLanguage)) {
11325		    		continue;
11326		    	}
11327		    	for(var targetLanguage in translate.nomenclature.data[currentLanguage]){
11328					if (!translate.nomenclature.data[currentLanguage].hasOwnProperty(targetLanguage)) {
11329			    		continue;
11330			    	}
11331			    	for(var originalText in translate.nomenclature.data[currentLanguage][targetLanguage]){
11332						if (!translate.nomenclature.data[currentLanguage][targetLanguage].hasOwnProperty(originalText)) {
11333				    		continue;
11334				    	}
11335				    	translate.history.translateText.add(originalText, translate.nomenclature.data[currentLanguage][targetLanguage][originalText]);
11336				    }
11337			    }
11338		    }
11339		   	//将忽略翻译的文本(固定的,非正则)加入 translate.history.translateTexts 中
11340		    for(var ignore_i = 0; ignore_i < translate.ignore.text.length; ignore_i++){
11341		    	translate.history.translateText.add(translate.ignore.text[ignore_i], translate.ignore.text[ignore_i]);
11342		    }
11343			
11344			
11345
11346			//进行判断,DOM是否加载完成了,如果未加载完成就触发了 translate.execute 执行,那么弹出警告提示
11347			if(document.readyState == 'loading'){
11348				translate.log('WARNING : The dom triggered translate.exece() before it was fully loaded, which does not comply with usage standards. The execution of translate.exece() should be done after the DOM is loaded');
11349				translate.log('警告:DOM 在完全加载之前触发了 translate.execute() ,这不符合使用规范,容易出现异常。你应该检查一下你的代码,translate.execute() 的执行应该在DOM加载完成后');
11350			}
11351			
11352
11353			//translate.listener.start() 的触发。
11354			if(translate.listener.use == true && translate.listener.isStart == false){
11355				if(typeof(translate.listener.start) != 'undefined'){
11356					translate.listener.addListener();
11357				}
11358			}
11359
11360			//translate.request.lilstener.start() 触发
11361			if(translate.request.listener.use == true && translate.request.listener.isStart == false){
11362				translate.request.listener.addListener();
11363			}
11364		});
11365
11366		//产生的数据回收,避免一直扩大占用内存
11367		if(typeof(translate.recycle) != 'undefined'){
11368			// 创建定时器,每1分钟执行一次 translate.recycle 进行清理数据存储
11369			setInterval(translate.recycle, 60 * 1000);
11370		}
11371		
11372	},
11373	/*js translate.init end*/
11374
11375}
11376/*
11377	将页面中的所有node节点,生成其在当前页面的唯一标识字符串uuid
11378	开源仓库: https://github.com/xnx3/nodeuuid.js
11379	原理: 当前节点的nodeName + 当前节点在父节点下,属于第几个 tagName ,然后追个向父级进行取,将node本身+父级+父父级+.... 拼接在一起
11380	注意,如果动态添加一个节点到第一个,那么其他节点就会挤下去导致节点标记异常
11381*/
11382var nodeuuid = {
11383	index:function(node){
11384		var parent = node.parentElement;
11385        if(parent == null){
11386          return '';
11387        }
11388
11389        var childs;
11390        if(typeof(node.tagName) == 'undefined'){
11391        	//console.log('undefi');
11392        	childs = parent.childNodes;
11393        	//console.log(Array.prototype.indexOf.call(childs, node));
11394        }else{
11395        	// 使用querySelectorAll()方法获取所有与node元素相同标签名的子节点
11396	        //childs = parent.querySelectorAll(node.tagName);
11397
11398	        // 不使用querySelectorAll,手动遍历子节点来找到相同标签名的子节点
11399            childs = [];
11400            var allChilds = parent.childNodes;
11401            for (var i = 0; i < allChilds.length; i++) {
11402                if (allChilds[i].tagName === node.tagName) {
11403                    childs.push(allChilds[i]);
11404                }
11405            }
11406        }
11407        var index = Array.prototype.indexOf.call(childs, node); 
11408        //console.log('--------'+node.tagName);
11409        return node.nodeName + "" + (index+1);
11410	},
11411	uuid:function(node){
11412		var uuid = '';
11413		var n = node;
11414		while(n != null){
11415			var id = nodeuuid.index(n);
11416			//console.log(id);
11417			if(id != ''){
11418				if(uuid != ''){
11419					uuid = '_'+uuid;
11420				}
11421				uuid = id + uuid;
11422			}
11423			//console.log(uuid)
11424			n = n.parentElement;
11425		}
11426		return uuid;
11427	}
11428}
11429
11430
11431/*js copyright-notice start*/
11432//延迟触发,方便拦截自定义
11433setTimeout(function(){
11434	translate.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');
11435}, 3000);
11436/*js copyright-notice end*/
11437
11438//初始化
11439try{
11440	translate.init();
11441}catch(e){  }
11442
11443/*js amd-cmd-commonjs start*/
11444/*兼容 AMD、CMD、CommonJS 规范 - start*/
11445/**
11446 * 兼容 AMD、CMD、CommonJS 规范
11447 * node 环境使用:`npm i i18n-jsautotranslate` 安装包
11448 */
11449;(function (root, factory) {
11450  if (typeof define === 'function' && define.amd) {
11451    define([], () => factory());
11452  } else if (typeof module === 'object' && module.exports) {
11453    module.exports = factory();
11454  } else {
11455  	if(typeof(root) != 'undefined'){
11456		root['translate'] = factory();
11457	}
11458  }
11459})(this, function () {
11460  return translate;
11461});
11462/*兼容 AMD、CMD、CommonJS 规范 - end*/
11463/*js amd-cmd-commonjs end*/

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.