PageSourceSearch

https://nevgen.org/js/jquery-lang.js

js nevgen.org collected 2026-10-02 10:30:53 UTC 18,370 bytes, 647 lines download raw bytes

1/*
2 The MIT License (MIT)
3
4 Copyright (c) 2014 Irrelon Software Limited
5 http://www.irrelon.com
6
7 Permission is hereby granted, free of charge, to any person obtaining a copy
8 of this software and associated documentation files (the "Software"), to deal
9 in the Software without restriction, including without limitation the rights
10 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 copies of the Software, and to permit persons to whom the Software is
12 furnished to do so, subject to the following conditions:
13
14 The above copyright notice, url and this permission notice shall be included in
15 all copies or substantial portions of the Software.
16
17 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 THE SOFTWARE.
24
25 Source: https://github.com/irrelon/jquery-lang-js
26
27 Changelog: See readme.md
28 */
29var Lang = (function () {
30	var Lang = function (defaultLang, currentLang, allowCookieOverride) {
31		var self = this,
32			cookieLang;
33		
34		// Enable firing events
35		this._fireEvents = true;
36		
37		// Allow storage of dynamic language pack data
38		this._dynamic = {};
39		
40		// Store existing mutation methods so we can auto-run
41		// translations when new data is added to the page
42		this._mutationCopies = {
43			append: $.fn.append,
44			appendTo: $.fn.appendTo,
45			prepend: $.fn.prepend,
46			before: $.fn.before,
47			after: $.fn.after,
48			html: $.fn.html
49		};
50		
51		// Now override the existing mutation methods with our own
52		$.fn.append = function () { return self._mutation(this, 'append', arguments) };
53		$.fn.appendTo = function () { return self._mutation(this, 'appendTo', arguments) };
54		$.fn.prepend = function () { return self._mutation(this, 'prepend', arguments) };
55		$.fn.before = function () { return self._mutation(this, 'before', arguments) };
56		$.fn.after = function () { return self._mutation(this, 'after', arguments) };
57		$.fn.html = function () { return self._mutation(this, 'html', arguments) };
58		
59		// Set default and current language to the default one
60		// to start with
61		this.defaultLang = defaultLang || 'en';
62		this.currentLang = defaultLang || 'en';
63		
64		// Check for cookie support when no current language is specified
65		if ((allowCookieOverride || !currentLang) && Cookies) {
66			// Check for an existing language cookie
67			cookieLang = Cookies.get('langCookie');
68			
69			if (cookieLang) {
70				// We have a cookie language, set the current language
71				currentLang = cookieLang;
72			}
73		}
74
75		$(function () {
76			// Setup data on the language items
77			self._start();
78	
79			// Check if the current language is not the same as our default
80			if (currentLang && currentLang !== self.defaultLang) {
81				// Switch to the current language
82				self.change(currentLang);
83			}
84		});
85	};
86
87	/**
88	 * Object that holds the language packs.
89	 * @type {{}}
90	 */
91	Lang.prototype.pack = {};
92	
93
94
95	/**
96	 * Array of translatable attributes to check for on elements.
97	 * @type {string[]}
98	 */
99	Lang.prototype.attrList = [
100		'title',
101		'alt',
102		'placeholder',
103		'href'
104	];
105
106
107	/**
108	 * Defines a language pack that can be dynamically loaded and the
109	 * path to use when doing so.
110	 * @param {String} lang The language two-letter iso-code.
111	 * @param {String} path The path to the language pack js file.
112	 */
113	Lang.prototype.dynamic = function (lang, path) {
114		if (lang !== undefined && path !== undefined) {
115			this._dynamic[lang] = path;
116		}
117	};
118
119	/**
120	 * Loads a new language pack for the given language.
121	 * @param {string} lang The language to load the pack for.
122	 * @param {Function=} callback Optional callback when the file has loaded.
123	 */
124	Lang.prototype.loadPack = function (lang, callback) {
125		var self = this;
126		
127		if (lang && self._dynamic[lang]) {
128			$.ajax({
129				dataType: "json",
130				url: self._dynamic[lang],
131				success: function (data) {
132					self.pack[lang] = data;
133					
134					// Process the regex list
135					if (self.pack[lang].regex) {
136						var packRegex = self.pack[lang].regex,
137							regex,
138							i;
139						
140						for (i = 0; i < packRegex.length; i++) {
141							regex = packRegex[i];
142							if (regex.length === 2) {
143								// String, value
144								regex[0] = new RegExp(regex[0]);
145							} else if (regex.length === 3) {
146								// String, modifiers, value
147								regex[0] = new RegExp(regex[0], regex[1]);
148								
149								// Remove modifier
150								regex.splice(1, 1);
151							}
152						}
153					}
154					
155					//console.log('Loaded language pack: ' + self._dynamic[lang]);
156					if (callback) { callback(false, lang, self._dynamic[lang]); }
157				},
158				error: function () {
159					if (callback) { callback(true, lang, self._dynamic[lang]); }
160					throw('Error loading language pack' + self._dynamic[lang]);
161				}
162			});
163		} else {
164			throw('Cannot load language pack, no file path specified!');
165		}
166	};
167
168	/**
169	 * Scans the DOM for elements with [lang] selector and saves translate data
170	 * for them for later use.
171	 * @private
172	 */
173	Lang.prototype._start = function (selector) {
174		// Get the page HTML
175		var arr = selector !== undefined ? $(selector).find('[lang]') : $(':not(html)[lang]'),
176			arrCount = arr.length,
177			elem;
178
179		while (arrCount--) {
180			elem = $(arr[arrCount]);
181			this._processElement(elem);
182		}
183	};
184	
185	Lang.prototype._processElement = function (elem) {
186		// Only store data if the element is set to our default language
187		if (elem.attr('lang') === this.defaultLang) {
188			// Store translatable attributes
189			this._storeAttribs(elem);
190	
191			// Store translatable content
192			this._storeContent(elem);
193		}
194	};
195
196	/**
197	 * Stores the translatable attribute values in their default language.
198	 * @param {object} elem The jQuery selected element.
199	 * @private
200	 */
201	Lang.prototype._storeAttribs = function (elem) {
202		var attrIndex,
203			attr,
204			attrObj;
205
206		for (attrIndex = 0; attrIndex < this.attrList.length; attrIndex++) {
207			attr = this.attrList[attrIndex];
208			if (elem.attr(attr)) {
209				// Grab the existing attribute store or create a new object
210				attrObj = elem.data('lang-attr') || {};
211
212				// Add the attribute and value to the store
213				attrObj[attr] = elem.attr(attr);
214
215				// Save the attribute data to the store
216				elem.data('lang-attr', attrObj);
217			}
218		}
219	};
220
221	/**
222	 * Reads the existing content from the element and stores it for
223	 * later use in translation.
224	 * @param elem
225	 * @private
226	 */
227	Lang.prototype._storeContent = function (elem) {
228		// Check if the element is an input element
229		if (elem.is('input')) {
230			switch (elem.attr('type')) {
231				case 'button':
232				case 'submit':
233				case 'hidden':
234				case 'reset':
235					elem.data('lang-val', elem.val());
236					break;
237			}
238		} else if (elem.is('img')) {
239			elem.data('lang-src', elem.attr('src'));
240		} else {
241			// Get the text nodes immediately inside this element
242			var nodes = this._getTextNodes(elem);
243			if (nodes) {
244				elem.data('lang-text', nodes);
245			}
246		}
247	};
248
249	/**
250	 * Retrieves the text nodes from an element and returns them in array wrap into
251	 * object with two properties: 
252	 * 	- node - which corresponds to text node,
253	 * 	- langDefaultText - which remember current data of text node
254	 * @param elem
255	 * @returns {Array|*}
256	 * @private
257	 */
258	Lang.prototype._getTextNodes = function (elem) {
259		var nodes = elem.contents(), nodeObjArray = [], nodeObj = {},
260			nodeArr, that = this, map = Array.prototype.map;
261
262        $.each(nodes, function (index, node) {
263            if ( node.nodeType !== 3 ) {
264                return;
265            }
266
267			nodeObj = {
268				node : node,
269				langDefaultText : node.data
270			};
271
272			nodeObjArray.push(nodeObj);
273		});
274		
275		return nodeObjArray;
276	};
277
278	/**
279	 * Sets text nodes of an element translated based on the passed language.
280	 * @param elem
281	 * @param {Array|*} nodes array of objecs with text node and defaultText returned from _getTextNodes
282	 * @param lang
283	 * @private
284	 */
285	Lang.prototype._setTextNodes = function (elem, nodes, lang) {
286		var index,
287			textNode,
288			defaultText,
289			translation,
290			langNotDefault = lang !== this.defaultLang;
291		
292		for (index = 0; index < nodes.length; index++) {
293			textNode = nodes[index];
294			
295			if (langNotDefault) {
296				defaultText = $.trim(textNode.langDefaultText);
297				
298				if (defaultText) {
299					// Translate the langDefaultText
300					translation = this.translate(defaultText, lang);
301					
302					if (translation) {
303						try {
304							// Replace the text with the translated version
305							textNode.node.data = textNode.node.data.split($.trim(textNode.node.data)).join(translation);
306						} catch (e) {
307							
308						}
309					} else {
310						if (console && console.log) {
311							console.log('Translation for "' + defaultText + '" not found!');
312						}
313					}
314				}
315			} else {
316				// Replace with original text
317				try {
318					textNode.node.data = textNode.langDefaultText;
319				} catch (e) {
320					
321				}
322			}
323		}
324	};
325
326	/**
327	 * Translates and sets the attributes of an element to the passed language.
328	 * @param elem
329	 * @param lang
330	 * @private
331	 */
332	Lang.prototype._translateAttribs = function (elem, lang) {
333		var attr,
334			attrObj = elem.data('lang-attr') || {},
335			translation;
336
337		for (attr in attrObj) {
338			if (attrObj.hasOwnProperty(attr)) {
339				// Check the element still has the attribute
340				if (elem.attr(attr)) {
341					if (lang !== this.defaultLang) {
342						// Get the translated value
343						translation = this.translate(attrObj[attr], lang);
344
345						// Check we actually HAVE a translation
346						if (translation) {
347							// Change the attribute to the translated value
348							elem.attr(attr, translation);
349						}
350					} else {
351						// Set default language value
352						elem.attr(attr, attrObj[attr]);
353					}
354				}
355			}
356		}
357	};
358
359	/**
360	 * Translates and sets the contents of an element to the passed language.
361	 * @param elem
362	 * @param lang
363	 * @private
364	 */
365	Lang.prototype._translateContent = function (elem, lang) {
366		var langNotDefault = lang !== this.defaultLang,
367			translation,
368			nodes;
369
370		// Check if the element is an input element
371		if (elem.is('input')) {
372			switch (elem.attr('type')) {
373				case 'button':
374				case 'submit':
375				case 'hidden':
376				case 'reset':
377					if (langNotDefault) {
378						// Get the translated value
379						translation = this.translate(elem.data('lang-val'), lang);
380
381						// Check we actually HAVE a translation
382						if (translation) {
383							// Set translated value
384							elem.val(translation);
385						}
386					} else {
387						// Set default language value
388						elem.val(elem.data('lang-val'));
389					}
390					break;
391			}
392		} else if (elem.is('img')) {
393			if (langNotDefault) {
394				// Get the translated value
395				translation = this.translate(elem.data('lang-src'), lang);
396
397				// Check we actually HAVE a translation
398				if (translation) {
399					// Set translated value
400					elem.attr('src', translation);
401				}
402			} else {
403				// Set default language value
404				elem.attr('src', elem.data('lang-src'));
405			}
406		} else {
407			// Set text node translated text
408			nodes = elem.data('lang-text');
409			if (nodes) {
410				this._setTextNodes(elem, nodes, lang);
411			}
412		}
413	};
414
415	/**
416	 * Call this to change the current language on the page.
417	 * @param {String} lang The new two-letter language code to change to.
418	 * @param {String=} selector Optional selector to find language-based
419	 * elements for updating.
420	 * @param {Function=} callback Optional callback function that will be
421	 * called once the language change has been successfully processed. This
422	 * is especially useful if you are using dynamic language pack loading
423	 * since you will get a callback once it has been loaded and changed.
424	 * Your callback will be passed three arguments, a boolean to denote if
425	 * there was an error (true if error), the second will be the language
426	 * you passed in the change call (the lang argument) and the third will
427	 * be the selector used in the change update.
428	 */
429	Lang.prototype.change = function (lang, selector, callback) {
430		var self = this;
431		
432		if (lang === this.defaultLang || this.pack[lang] || this._dynamic[lang]) {
433			// Check if the language pack is currently loaded
434			if (lang !== this.defaultLang) {
435				if (!this.pack[lang] && this._dynamic[lang]) {
436					// The language pack needs loading first
437					//console.log('Loading dynamic language pack: ' + this._dynamic[lang] + '...');
438					this.loadPack(lang, function (err, loadingLang, fromUrl) {
439						if (!err) {
440							// Process the change language request
441							self.change.call(self, lang, selector, callback);
442						} else {
443							// Call the callback with the error
444							if (callback) { callback('Language pack could not load from: ' + fromUrl, lang, selector); }
445						}
446					});
447					
448					return;
449				} else if (!this.pack[lang] && !this._dynamic[lang]) {
450					// Pack not loaded and no dynamic entry
451					if (callback) { callback('Language pack not defined for: ' + lang, lang, selector); }
452					throw('Could not change language to ' + lang + ' because no language pack for this language exists!');
453				}
454			}
455			
456			var fireAfterUpdate = false,
457				currLang = this.currentLang;
458			
459			if (this.currentLang != lang) {
460				this.beforeUpdate(currLang, lang);
461				fireAfterUpdate = true;
462			}
463			
464			this.currentLang = lang;
465			
466			// Get the page HTML
467			var arr = selector !== undefined ? $(selector).find('[lang]') : $(':not(html)[lang]'),
468				arrCount = arr.length,
469				elem;
470	
471			while (arrCount--) {
472				elem = $(arr[arrCount]);
473	
474				if (elem.attr('lang') !== lang) {
475					this._translateElement(elem, lang);
476				}
477			}
478			
479			if (fireAfterUpdate) {
480				this.afterUpdate(currLang, lang);
481			}
482			
483			// Check for cookie support
484			if (Cookies) {
485				// Set a cookie to remember this language setting with 1 year expiry
486				Cookies.set('langCookie', lang, {
487					expires: 365,
488					path: '/'
489				});
490			}
491			
492			if (callback) { callback(false, lang, selector); }
493		} else {
494			if (callback) { callback('No language pack defined for: ' + lang, lang, selector); }
495			throw('Attempt to change language to "' + lang + '" but no language pack for that language is loaded!');
496		}
497	};
498	
499	Lang.prototype._translateElement = function (elem, lang) {
500		// Translate attributes
501		this._translateAttribs(elem, lang);
502
503		// Translate content
504		if (elem.attr('data-lang-content') != 'false') {
505			this._translateContent(elem, lang);
506		}
507
508		// Update the element's current language
509		elem.attr('lang', lang);
510	};
511
512	/**
513	 * Translates text from the default language into the passed language.
514	 * @param {String} text The text to translate.
515	 * @param {String} lang The two-letter language code to translate to.
516	 * @returns {*}
517	 */
518	Lang.prototype.translate = function (text, lang) {
519		lang = lang || this.currentLang;
520		
521		if (this.pack[lang]) {
522			var translation = '';
523	
524			if (lang != this.defaultLang) {
525				// Check for a direct token translation
526				translation = this.pack[lang].token[text];
527	
528				if (!translation) {
529					// No token translation was found, test for regex match
530					translation = this._regexMatch(text, lang);
531				}
532				
533				if (!translation) {
534					if (console && console.log) {
535						console.log('Translation for "' + text + '" not found in language pack: ' + lang);
536					}
537				}
538	
539				return translation || text;
540			} else {
541				return text;
542			}
543		} else {
544			return text;
545		}
546	};
547
548	/**
549	 * Checks the regex items for a match against the passed text and
550	 * if a match is made, translates to the given replacement.
551	 * @param {String} text The text to test regex matches against.
552	 * @param {String} lang The two-letter language code to translate to.
553	 * @returns {string}
554	 * @private
555	 */
556	Lang.prototype._regexMatch = function (text, lang) {
557		// Loop the regex array and test them against the text
558		var arr,
559			arrCount,
560			arrIndex,
561			item,
562			regex,
563			expressionResult;
564		
565		arr = this.pack[lang].regex;
566		
567		if (arr) {
568			arrCount = arr.length;
569			
570			for (arrIndex = 0; arrIndex < arrCount; arrIndex++) {
571				item = arr[arrIndex];
572				regex = item[0];
573	
574				// Test regex
575				expressionResult = regex.exec(text);
576	
577				if (expressionResult && expressionResult[0]) {
578					return text.split(expressionResult[0]).join(item[1]);
579				}
580			}
581		}
582
583		return '';
584	};
585
586	Lang.prototype.beforeUpdate = function (currentLang, newLang) {
587		if (this._fireEvents) {
588			$(this).triggerHandler('beforeUpdate', [currentLang, newLang, this.pack[currentLang], this.pack[newLang]]);
589		}
590	};
591	
592	Lang.prototype.afterUpdate = function (currentLang, newLang) {
593		if (this._fireEvents) {
594			$(this).triggerHandler('afterUpdate', [currentLang, newLang, this.pack[currentLang], this.pack[newLang]]);
595		}
596	};
597	
598	Lang.prototype.refresh = function () {
599		// Process refresh on the page
600		this._fireEvents = false;
601		this.change(this.currentLang);
602		this._fireEvents = true;
603	};
604	
605	////////////////////////////////////////////////////
606	// Mutation overrides
607	////////////////////////////////////////////////////
608	Lang.prototype._mutation = function (context, method, args) {
609		var result = this._mutationCopies[method].apply(context, args),
610			currLang = this.currentLang,
611			rootElem = $(context);
612		
613		if (rootElem.attr('lang')) {
614			// Switch off events for the moment
615			this._fireEvents = false;
616			
617			// Check if the root element is currently set to another language from current
618			//if (rootElem.attr('lang') !== this.currentLang) {
619				this._translateElement(rootElem, this.defaultLang);
620				this.change(this.defaultLang, rootElem);
621				
622				// Calling change above sets the global currentLang but this is supposed to be
623				// an isolated change so reset the global value back to what it was before
624				this.currentLang = currLang;
625				
626				// Record data on the default language from the root element
627				this._processElement(rootElem);
628				
629				// Translate the root element
630				this._translateElement(rootElem, this.currentLang);
631			//}
632		}
633		
634		// Record data on the default language from the root's children
635		this._start(rootElem);
636		
637		// Process translation on any child elements of this element
638		this.change(this.currentLang, rootElem);
639		
640		// Switch events back on
641		this._fireEvents = true;
642		
643		return result;
644	};
645
646	return Lang;
647})();

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.