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.