vendor: 23,087 bytes, lines 1-829
1/** 2 * Copyright (c) 2011-2013 Fabien Cazenave, Mozilla. 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a copy 5 * of this software and associated documentation files (the "Software"), to 6 * deal in the Software without restriction, including without limitation the 7 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 * sell copies of the Software, and to permit persons to whom the Software is 9 * furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice shall be included in 12 * all copies or substantial portions of the Software. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 * IN THE SOFTWARE. 21 */ 22/* 23 Additional modifications for PDF.js project: 24 - Disables language initialization on page loading; 25 - Removes consoleWarn and consoleLog and use console.log/warn directly. 26 - Removes window._ assignment. 27 - Remove compatibility code for OldIE. 28*/ 29 30/*jshint browser: true, devel: true, es5: true, globalstrict: true */ 31'use strict'; 32 33document.webL10n = (function(window, document, undefined) { 34 var gL10nData = {}; 35 var gTextData = ''; 36 var gTextProp = 'textContent'; 37 var gLanguage = ''; 38 var gMacros = {}; 39 var gReadyState = 'loading'; 40 41 42 /** 43 * Synchronously loading l10n resources significantly minimizes flickering 44 * from displaying the app with non-localized strings and then updating the 45 * strings. Although this will block all script execution on this page, we 46 * expect that the l10n resources are available locally on flash-storage. 47 * 48 * As synchronous XHR is generally considered as a bad idea, we're still 49 * loading l10n resources asynchronously -- but we keep this in a setting, 50 * just in case... and applications using this library should hide their 51 * content until the `localized' event happens. 52 */ 53 54 var gAsyncResourceLoading = true; // read-only 55 56 57 /** 58 * DOM helpers for the so-called "HTML API". 59 * 60 * These functions are written for modern browsers. For old versions of IE, 61 * they're overridden in the 'startup' section at the end of this file. 62 */ 63 64 function getL10nResourceLinks() { 65 return document.querySelectorAll('link[type="application/l10n"]'); 66 } 67 68 function getL10nDictionary() { 69 var script = document.querySelector('script[type="application/l10n"]'); 70 // TODO: support multiple and external JSON dictionaries 71 return script ? JSON.parse(script.innerHTML) : null; 72 } 73 74 function getTranslatableChildren(element) { 75 return element ? element.querySelectorAll('*[data-l10n-id]') : []; 76 } 77 78 function getL10nAttributes(element) { 79 if (!element) 80 return {}; 81 82 var l10nId = element.getAttribute('data-l10n-id'); 83 var l10nArgs = element.getAttribute('data-l10n-args'); 84 var args = {}; 85 if (l10nArgs) { 86 try { 87 args = JSON.parse(l10nArgs); 88 } catch (e) { 89 console.warn('could not parse arguments for #' + l10nId); 90 } 91 } 92 return { id: l10nId, args: args }; 93 } 94 95 function fireL10nReadyEvent(lang) { 96 var evtObject = document.createEvent('Event'); 97 evtObject.initEvent('localized', true, false); 98 evtObject.language = lang; 99 document.dispatchEvent(evtObject); 100 } 101 102 function xhrLoadText(url, onSuccess, onFailure) { 103 onSuccess = onSuccess || function _onSuccess(data) {}; 104 onFailure = onFailure || function _onFailure() { 105 console.warn(url + ' not found.'); 106 }; 107 108 var xhr = new XMLHttpRequest(); 109 xhr.open('GET', url, gAsyncResourceLoading); 110 if (xhr.overrideMimeType) { 111 xhr.overrideMimeType('text/plain; charset=utf-8'); 112 } 113 xhr.onreadystatechange = function() { 114 if (xhr.readyState == 4) { 115 if (xhr.status == 200 || xhr.status === 0) { 116 onSuccess(xhr.responseText); 117 } else { 118 onFailure(); 119 } 120 } 121 }; 122 xhr.onerror = onFailure; 123 xhr.ontimeout = onFailure; 124 125 // in Firefox OS with the app:// protocol, trying to XHR a non-existing 126 // URL will raise an exception here -- hence this ugly try...catch. 127 try { 128 xhr.send(null); 129 } catch (e) { 130 onFailure(); 131 } 132 } 133 134 135 /** 136 * l10n resource parser: 137 * - reads (async XHR) the l10n resource matching `lang'; 138 * - imports linked resources (synchronously) when specified; 139 * - parses the text data (fills `gL10nData' and `gTextData'); 140 * - triggers success/failure callbacks when done. 141 * 142 * @param {string} href 143 * URL of the l10n resource to parse. 144 * 145 * @param {string} lang 146 * locale (language) to parse. Must be a lowercase string. 147 * 148 * @param {Function} successCallback 149 * triggered when the l10n resource has been successully parsed. 150 * 151 * @param {Function} failureCallback 152 * triggered when the an error has occured. 153 * 154 * @return {void} 155 * uses the following global variables: gL10nData, gTextData, gTextProp. 156 */ 157 158 function parseResource(href, lang, successCallback, failureCallback) { 159 var baseURL = href.replace(/[^\/]*$/, '') || './'; 160 161 // handle escaped characters (backslashes) in a string 162 function evalString(text) { 163 if (text.lastIndexOf('\\') < 0) 164 return text; 165 return text.replace(/\\\\/g, '\\') 166 .replace(/\\n/g, '\n') 167 .replace(/\\r/g, '\r') 168 .replace(/\\t/g, '\t') 169 .replace(/\\b/g, '\b') 170 .replace(/\\f/g, '\f') 171 .replace(/\\{/g, '{') 172 .replace(/\\}/g, '}') 173 .replace(/\\"/g, '"') 174 .replace(/\\'/g, "'"); 175 } 176 177 // parse *.properties text data into an l10n dictionary 178 // If gAsyncResourceLoading is false, then the callback will be called 179 // synchronously. Otherwise it is called asynchronously. 180 function parseProperties(text, parsedPropertiesCallback) { 181 var dictionary = {}; 182 183 // token expressions 184 var reBlank = /^\s*|\s*$/; 185 var reComment = /^\s*#|^\s*$/; 186 var reSection = /^\s*\[(.*)\]\s*$/; 187 var reImport = /^\s*@import\s+url\((.*)\)\s*$/i; 188 var reSplit = /^([^=\s]*)\s*=\s*(.+)$/; // TODO: escape EOLs with '\' 189 190 // parse the *.properties file into an associative array 191 function parseRawLines(rawText, extendedSyntax, parsedRawLinesCallback) { 192 var entries = rawText.replace(reBlank, '').split(/[\r\n]+/); 193 var currentLang = '*'; 194 var genericLang = lang.split('-', 1)[0]; 195 var skipLang = false; 196 var match = ''; 197 198 function nextEntry() { 199 // Use infinite loop instead of recursion to avoid reaching the 200 // maximum recursion limit for content with many lines. 201 while (true) { 202 if (!entries.length) { 203 parsedRawLinesCallback(); 204 return; 205 } 206 var line = entries.shift(); 207 208 // comment or blank line? 209 if (reComment.test(line)) 210 continue; 211 212 // the extended syntax supports [lang] sections and @import rules 213 if (extendedSyntax) { 214 match = reSection.exec(line); 215 if (match) { // section start? 216 // RFC 4646, section 4.4, "All comparisons MUST be performed 217 // in a case-insensitive manner." 218 219 currentLang = match[1].toLowerCase(); 220 skipLang = (currentLang !== '*') && 221 (currentLang !== lang) && (currentLang !== genericLang); 222 continue; 223 } else if (skipLang) { 224 continue; 225 } 226 match = reImport.exec(line); 227 if (match) { // @import rule? 228 loadImport(baseURL + match[1], nextEntry); 229 return; 230 } 231 } 232 233 // key-value pair 234 var tmp = line.match(reSplit); 235 if (tmp && tmp.length == 3) { 236 dictionary[tmp[1]] = evalString(tmp[2]); 237 } 238 } 239 } 240 nextEntry(); 241 } 242 243 // import another *.properties file 244 function loadImport(url, callback) { 245 xhrLoadText(url, function(content) { 246 parseRawLines(content, false, callback); // don't allow recursive imports 247 }, null); 248 } 249 250 // fill the dictionary 251 parseRawLines(text, true, function() { 252 parsedPropertiesCallback(dictionary); 253 }); 254 } 255 256 // load and parse l10n data (warning: global variables are used here) 257 xhrLoadText(href, function(response) { 258 gTextData += response; // mostly for debug 259 260 // parse *.properties text data into an l10n dictionary 261 parseProperties(response, function(data) { 262 263 // find attribute descriptions, if any 264 for (var key in data) { 265 var id, prop, index = key.lastIndexOf('.'); 266 if (index > 0) { // an attribute has been specified 267 id = key.substring(0, index); 268 prop = key.substr(index + 1); 269 } else { // no attribute: assuming text content by default 270 id = key; 271 prop = gTextProp; 272 } 273 if (!gL10nData[id]) { 274 gL10nData[id] = {}; 275 } 276 gL10nData[id][prop] = data[key]; 277 } 278 279 // trigger callback 280 if (successCallback) { 281 successCallback(); 282 } 283 }); 284 }, failureCallback); 285 } 286 287 // load and parse all resources for the specified locale 288 function loadLocale(lang, callback) { 289 // RFC 4646, section 2.1 states that language tags have to be treated as 290 // case-insensitive. Convert to lowercase for case-insensitive comparisons. 291 if (lang) { 292 lang = lang.toLowerCase(); 293 } 294 295 callback = callback || function _callback() {}; 296 297 clear(); 298 gLanguage = lang; 299 300 // check all <link type="application/l10n" href="..." /> nodes 301 // and load the resource files 302 var langLinks = getL10nResourceLinks(); 303 var langCount = langLinks.length; 304 if (langCount === 0) { 305 // we might have a pre-compiled dictionary instead 306 var dict = getL10nDictionary(); 307 if (dict && dict.locales && dict.default_locale) { 308 console.log('using the embedded JSON directory, early way out'); 309 gL10nData = dict.locales[lang]; 310 if (!gL10nData) { 311 var defaultLocale = dict.default_locale.toLowerCase(); 312 for (var anyCaseLang in dict.locales) { 313 anyCaseLang = anyCaseLang.toLowerCase(); 314 if (anyCaseLang === lang) { 315 gL10nData = dict.locales[lang]; 316 break; 317 } else if (anyCaseLang === defaultLocale) { 318 gL10nData = dict.locales[defaultLocale]; 319 } 320 } 321 } 322 callback(); 323 } else { 324 console.log('no resource to load, early way out'); 325 } 326 // early way out 327 fireL10nReadyEvent(lang); 328 gReadyState = 'complete'; 329 return; 330 } 331 332 // start the callback when all resources are loaded 333 var onResourceLoaded = null; 334 var gResourceCount = 0; 335 onResourceLoaded = function() { 336 gResourceCount++; 337 if (gResourceCount >= langCount) { 338 callback(); 339 fireL10nReadyEvent(lang); 340 gReadyState = 'complete'; 341 } 342 }; 343 344 // load all resource files 345 function L10nResourceLink(link) { 346 var href = link.href; 347 // Note: If |gAsyncResourceLoading| is false, then the following callbacks 348 // are synchronously called. 349 this.load = function(lang, callback) { 350 parseResource(href, lang, callback, function() { 351 console.warn(href + ' not found.'); 352 // lang not found, used default resource instead 353 console.warn('"' + lang + '" resource not found'); 354 gLanguage = ''; 355 // Resource not loaded, but we still need to call the callback. 356 callback(); 357 }); 358 }; 359 } 360 361 for (var i = 0; i < langCount; i++) { 362 var resource = new L10nResourceLink(langLinks[i]); 363 resource.load(lang, onResourceLoaded); 364 } 365 } 366 367 // clear all l10n data 368 function clear() { 369 gL10nData = {}; 370 gTextData = ''; 371 gLanguage = ''; 372 // TODO: clear all non predefined macros. 373 // There's no such macro /yet/ but we're planning to have some... 374 } 375 376 377 /** 378 * Get rules for plural forms (shared with JetPack), see: 379 * http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html 380 * https://github.com/mozilla/addon-sdk/blob/master/python-lib/plural-rules-generator.p 381 * 382 * @param {string} lang 383 * locale (language) used. 384 * 385 * @return {Function} 386 * returns a function that gives the plural form name for a given integer: 387 * var fun = getPluralRules('en'); 388 * fun(1) -> 'one' 389 * fun(0) -> 'other' 390 * fun(1000) -> 'other'. 391 */ 392 393 function getPluralRules(lang) { 394 var locales2rules = { 395 'af': 3, 396 'ak': 4, 397 'am': 4, 398 'ar': 1, 399 'asa': 3, 400 'az': 0, 401 'be': 11, 402 'bem': 3, 403 'bez': 3, 404 'bg': 3, 405 'bh': 4, 406 'bm': 0, 407 'bn': 3, 408 'bo': 0, 409 'br': 20, 410 'brx': 3, 411 'bs': 11, 412 'ca': 3, 413 'cgg': 3, 414 'chr': 3, 415 'cs': 12, 416 'cy': 17, 417 'da': 3, 418 'de': 3, 419 'dv': 3, 420 'dz': 0, 421 'ee': 3, 422 'el': 3, 423 'en': 3, 424 'eo': 3, 425 'es': 3, 426 'et': 3, 427 'eu': 3, 428 'fa': 0, 429 'ff': 5, 430 'fi': 3, 431 'fil': 4, 432 'fo': 3, 433 'fr': 5, 434 'fur': 3, 435 'fy': 3, 436 'ga': 8, 437 'gd': 24, 438 'gl': 3, 439 'gsw': 3, 440 'gu': 3, 441 'guw': 4, 442 'gv': 23, 443 'ha': 3, 444 'haw': 3, 445 'he': 2, 446 'hi': 4, 447 'hr': 11, 448 'hu': 0, 449 'id': 0, 450 'ig': 0, 451 'ii': 0, 452 'is': 3, 453 'it': 3, 454 'iu': 7, 455 'ja': 0, 456 'jmc': 3, 457 'jv': 0, 458 'ka': 0, 459 'kab': 5, 460 'kaj': 3, 461 'kcg': 3, 462 'kde': 0, 463 'kea': 0, 464 'kk': 3, 465 'kl': 3, 466 'km': 0, 467 'kn': 0, 468 'ko': 0, 469 'ksb': 3, 470 'ksh': 21, 471 'ku': 3, 472 'kw': 7, 473 'lag': 18, 474 'lb': 3, 475 'lg': 3, 476 'ln': 4, 477 'lo': 0, 478 'lt': 10, 479 'lv': 6, 480 'mas': 3, 481 'mg': 4, 482 'mk': 16, 483 'ml': 3, 484 'mn': 3, 485 'mo': 9, 486 'mr': 3, 487 'ms': 0, 488 'mt': 15, 489 'my': 0, 490 'nah': 3, 491 'naq': 7, 492 'nb': 3, 493 'nd': 3, 494 'ne': 3, 495 'nl': 3, 496 'nn': 3, 497 'no': 3, 498 'nr': 3, 499 'nso': 4, 500 'ny': 3, 501 'nyn': 3, 502 'om': 3, 503 'or': 3, 504 'pa': 3, 505 'pap': 3, 506 'pl': 13, 507 'ps': 3, 508 'pt': 3, 509 'rm': 3, 510 'ro': 9, 511 'rof': 3, 512 'ru': 11, 513 'rwk': 3, 514 'sah': 0, 515 'saq': 3, 516 'se': 7, 517 'seh': 3, 518 'ses': 0, 519 'sg': 0, 520 'sh': 11, 521 'shi': 19, 522 'sk': 12, 523 'sl': 14, 524 'sma': 7, 525 'smi': 7, 526 'smj': 7, 527 'smn': 7, 528 'sms': 7, 529 'sn': 3, 530 'so': 3, 531 'sq': 3, 532 'sr': 11, 533 'ss': 3, 534 'ssy': 3, 535 'st': 3, 536 'sv': 3, 537 'sw': 3, 538 'syr': 3, 539 'ta': 3, 540 'te': 3, 541 'teo': 3, 542 'th': 0, 543 'ti': 4, 544 'tig': 3, 545 'tk': 3, 546 'tl': 4, 547 'tn': 3, 548 'to': 0, 549 'tr': 0, 550 'ts': 3, 551 'tzm': 22, 552 'uk': 11, 553 'ur': 3, 554 've': 3, 555 'vi': 0, 556 'vun': 3, 557 'wa': 4, 558 'wae': 3, 559 'wo': 0, 560 'xh': 3, 561 'xog': 3, 562 'yo': 0, 563 'zh': 0, 564 'zu': 3 565 }; 566 567 // utility functions for plural rules methods 568 function isIn(n, list) { 569 return list.indexOf(n) !== -1; 570 } 571 function isBetween(n, start, end) { 572 return start <= n && n <= end; 573 } 574 575 // list of all plural rules methods: 576 // map an integer to the plural form name to use 577 var pluralRules = { 578 '0': function(n) { 579 return 'other'; 580 }, 581 '1': function(n) { 582 if ((isBetween((n % 100), 3, 10))) 583 return 'few'; 584 if (n === 0) 585 return 'zero'; 586 if ((isBetween((n % 100), 11, 99))) 587 return 'many'; 588 if (n == 2) 589 return 'two'; 590 if (n == 1) 591 return 'one'; 592 return 'other'; 593 }, 594 '2': function(n) { 595 if (n !== 0 && (n % 10) === 0) 596 return 'many'; 597 if (n == 2) 598 return 'two'; 599 if (n == 1) 600 return 'one'; 601 return 'other'; 602 }, 603 '3': function(n) { 604 if (n == 1) 605 return 'one'; 606 return 'other'; 607 }, 608 '4': function(n) { 609 if ((isBetween(n, 0, 1))) 610 return 'one'; 611 return 'other'; 612 }, 613 '5': function(n) { 614 if ((isBetween(n, 0, 2)) && n != 2) 615 return 'one'; 616 return 'other'; 617 }, 618 '6': function(n) { 619 if (n === 0) 620 return 'zero'; 621 if ((n % 10) == 1 && (n % 100) != 11) 622 return 'one'; 623 return 'other'; 624 }, 625 '7': function(n) { 626 if (n == 2) 627 return 'two'; 628 if (n == 1) 629 return 'one'; 630 return 'other'; 631 }, 632 '8': function(n) { 633 if ((isBetween(n, 3, 6))) 634 return 'few'; 635 if ((isBetween(n, 7, 10))) 636 return 'many'; 637 if (n == 2) 638 return 'two'; 639 if (n == 1) 640 return 'one'; 641 return 'other'; 642 }, 643 '9': function(n) { 644 if (n === 0 || n != 1 && (isBetween((n % 100), 1, 19))) 645 return 'few'; 646 if (n == 1) 647 return 'one'; 648 return 'other'; 649 }, 650 '10': function(n) { 651 if ((isBetween((n % 10), 2, 9)) && !(isBetween((n % 100), 11, 19))) 652 return 'few'; 653 if ((n % 10) == 1 && !(isBetween((n % 100), 11, 19))) 654 return 'one'; 655 return 'other'; 656 }, 657 '11': function(n) { 658 if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14))) 659 return 'few'; 660 if ((n % 10) === 0 || 661 (isBetween((n % 10), 5, 9)) || 662 (isBetween((n % 100), 11, 14))) 663 return 'many'; 664 if ((n % 10) == 1 && (n % 100) != 11) 665 return 'one'; 666 return 'other'; 667 }, 668 '12': function(n) { 669 if ((isBetween(n, 2, 4))) 670 return 'few'; 671 if (n == 1) 672 return 'one'; 673 return 'other'; 674 }, 675 '13': function(n) { 676 if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14))) 677 return 'few'; 678 if (n != 1 && (isBetween((n % 10), 0, 1)) || 679 (isBetween((n % 10), 5, 9)) || 680 (isBetween((n % 100), 12, 14))) 681 return 'many'; 682 if (n == 1) 683 return 'one'; 684 return 'other'; 685 }, 686 '14': function(n) { 687 if ((isBetween((n % 100), 3, 4))) 688 return 'few'; 689 if ((n % 100) == 2) 690 return 'two'; 691 if ((n % 100) == 1) 692 return 'one'; 693 return 'other'; 694 }, 695 '15': function(n) { 696 if (n === 0 || (isBetween((n % 100), 2, 10))) 697 return 'few'; 698 if ((isBetween((n % 100), 11, 19))) 699 return 'many'; 700 if (n == 1) 701 return 'one'; 702 return 'other'; 703 }, 704 '16': function(n) { 705 if ((n % 10) == 1 && n != 11) 706 return 'one'; 707 return 'other'; 708 }, 709 '17': function(n) { 710 if (n == 3) 711 return 'few'; 712 if (n === 0) 713 return 'zero'; 714 if (n == 6) 715 return 'many'; 716 if (n == 2) 717 return 'two'; 718 if (n == 1) 719 return 'one'; 720 return 'other'; 721 }, 722 '18': function(n) { 723 if (n === 0) 724 return 'zero'; 725 if ((isBetween(n, 0, 2)) && n !== 0 && n != 2) 726 return 'one'; 727 return 'other'; 728 }, 729 '19': function(n) { 730 if ((isBetween(n, 2, 10))) 731 return 'few'; 732 if ((isBetween(n, 0, 1))) 733 return 'one'; 734 return 'other'; 735 }, 736 '20': function(n) { 737 if ((isBetween((n % 10), 3, 4) || ((n % 10) == 9)) && !( 738 isBetween((n % 100), 10, 19) || 739 isBetween((n % 100), 70, 79) || 740 isBetween((n % 100), 90, 99) 741 )) 742 return 'few'; 743 if ((n % 1000000) === 0 && n !== 0) 744 return 'many'; 745 if ((n % 10) == 2 && !isIn((n % 100), [12, 72, 92])) 746 return 'two'; 747 if ((n % 10) == 1 && !isIn((n % 100), [11, 71, 91])) 748 return 'one'; 749 return 'other'; 750 }, 751 '21': function(n) { 752 if (n === 0) 753 return 'zero'; 754 if (n == 1) 755 return 'one'; 756 return 'other'; 757 }, 758 '22': function(n) { 759 if ((isBetween(n, 0, 1)) || (isBetween(n, 11, 99))) 760 return 'one'; 761 return 'other'; 762 }, 763 '23': function(n) { 764 if ((isBetween((n % 10), 1, 2)) || (n % 20) === 0) 765 return 'one'; 766 return 'other'; 767 }, 768 '24': function(n) { 769 if ((isBetween(n, 3, 10) || isBetween(n, 13, 19))) 770 return 'few'; 771 if (isIn(n, [2, 12])) 772 return 'two'; 773 if (isIn(n, [1, 11])) 774 return 'one'; 775 return 'other'; 776 } 777 }; 778 779 // return a function that gives the plural form name for a given integer 780 var index = locales2rules[lang.replace(/-.*$/, '')]; 781 if (!(index in pluralRules)) { 782 console.warn('plural form unknown for [' + lang + ']'); 783 return function() { return 'other'; }; 784 } 785 return pluralRules[index]; 786 } 787 788 // pre-defined 'plural' macro 789 gMacros.plural = function(str, param, key, prop) { 790 var n = parseFloat(param); 791 if (isNaN(n)) 792 return str; 793 794 // TODO: support other properties (l20n still doesn't...) 795 if (prop != gTextProp) 796 return str; 797 798 // initialize _pluralRules 799 if (!gMacros._pluralRules) { 800 gMacros._pluralRules = getPluralRules(gLanguage); 801 } 802 var index = '[' + gMacros._pluralRules(n) + ']'; 803 804 // try to find a [zero|one|two] key if it's defined 805 if (n === 0 && (key + '[zero]') in gL10nData) { 806 str = gL10nData[key + '[zero]'][prop]; 807 } else if (n == 1 && (key + '[one]') in gL10nData) { 808 str = gL10nData[key + '[one]'][prop]; 809 } else if (n == 2 && (key + '[two]') in gL10nData) { 810 str = gL10nData[key + '[two]'][prop]; 811 } else if ((key + index) in gL10nData) { 812 str = gL10nData[key + index][prop]; 813 } else if ((key + '[other]') in gL10nData) { 814 str = gL10nData[key + '[other]'][prop]; 815 } 816 817 return str; 818 }; 819 820 821 /** 822 * l10n dictionary functions 823 */ 824 825 // fetch an l10n object, warn if not found, apply `args' if possible 826 function getL10nData(key, args, fallback) { 827 var data = gL10nData[key]; 828 if (!data) { 829
vendor: 6,166 bytes, lines 829-1033
829console.warn('#' + key + ' is undefined.'); 830 if (!fallback) { 831 return null; 832 } 833 data = fallback; 834 } 835 836 /** This is where l10n expressions should be processed. 837 * The plan is to support C-style expressions from the l20n project; 838 * until then, only two kinds of simple expressions are supported: 839 * {[ index ]} and {{ arguments }}. 840 */ 841 var rv = {}; 842 for (var prop in data) { 843 var str = data[prop]; 844 str = substIndexes(str, args, key, prop); 845 str = substArguments(str, args, key); 846 rv[prop] = str; 847 } 848 return rv; 849 } 850 851 // replace {[macros]} with their values 852 function substIndexes(str, args, key, prop) { 853 var reIndex = /\{\[\s*([a-zA-Z]+)\(([a-zA-Z]+)\)\s*\]\}/; 854 var reMatch = reIndex.exec(str); 855 if (!reMatch || !reMatch.length) 856 return str; 857 858 // an index/macro has been found 859 // Note: at the moment, only one parameter is supported 860 var macroName = reMatch[1]; 861 var paramName = reMatch[2]; 862 var param; 863 if (args && paramName in args) { 864 param = args[paramName]; 865 } else if (paramName in gL10nData) { 866 param = gL10nData[paramName]; 867 } 868 869 // there's no macro parser yet: it has to be defined in gMacros 870 if (macroName in gMacros) { 871 var macro = gMacros[macroName]; 872 str = macro(str, param, key, prop); 873 } 874 return str; 875 } 876 877 // replace {{arguments}} with their values 878 function substArguments(str, args, key) { 879 var reArgs = /\{\{\s*(.+?)\s*\}\}/g; 880 return str.replace(reArgs, function(matched_text, arg) { 881 if (args && arg in args) { 882 return args[arg]; 883 } 884 if (arg in gL10nData) { 885 return gL10nData[arg]; 886 } 887 console.log('argument {{' + arg + '}} for #' + key + ' is undefined.'); 888 return matched_text; 889 }); 890 } 891 892 // translate an HTML element 893 function translateElement(element) { 894 var l10n = getL10nAttributes(element); 895 if (!l10n.id) 896 return; 897 898 // get the related l10n object 899 var data = getL10nData(l10n.id, l10n.args); 900 if (!data) { 901 console.warn('#' + l10n.id + ' is undefined.'); 902 return; 903 } 904 905 // translate element (TODO: security checks?) 906 if (data[gTextProp]) { // XXX 907 if (getChildElementCount(element) === 0) { 908 element[gTextProp] = data[gTextProp]; 909 } else { 910 // this element has element children: replace the content of the first 911 // (non-empty) child textNode and clear other child textNodes 912 var children = element.childNodes; 913 var found = false; 914 for (var i = 0, l = children.length; i < l; i++) { 915 if (children[i].nodeType === 3 && /\S/.test(children[i].nodeValue)) { 916 if (found) { 917 children[i].nodeValue = ''; 918 } else { 919 children[i].nodeValue = data[gTextProp]; 920 found = true; 921 } 922 } 923 } 924 // if no (non-empty) textNode is found, insert a textNode before the 925 // first element child. 926 if (!found) { 927 var textNode = document.createTextNode(data[gTextProp]); 928 element.insertBefore(textNode, element.firstChild); 929 } 930 } 931 delete data[gTextProp]; 932 } 933 934 for (var k in data) { 935 element[k] = data[k]; 936 } 937 } 938 939 // webkit browsers don't currently support 'children' on SVG elements... 940 function getChildElementCount(element) { 941 if (element.children) { 942 return element.children.length; 943 } 944 if (typeof element.childElementCount !== 'undefined') { 945 return element.childElementCount; 946 } 947 var count = 0; 948 for (var i = 0; i < element.childNodes.length; i++) { 949 count += element.nodeType === 1 ? 1 : 0; 950 } 951 return count; 952 } 953 954 // translate an HTML subtree 955 function translateFragment(element) { 956 element = element || document.documentElement; 957 958 // check all translatable children (= w/ a `data-l10n-id' attribute) 959 var children = getTranslatableChildren(element); 960 var elementCount = children.length; 961 for (var i = 0; i < elementCount; i++) { 962 translateElement(children[i]); 963 } 964 965 // translate element itself if necessary 966 translateElement(element); 967 } 968 969 return { 970 // get a localized string 971 get: function(key, args, fallbackString) { 972 var index = key.lastIndexOf('.'); 973 var prop = gTextProp; 974 if (index > 0) { // An attribute has been specified 975 prop = key.substr(index + 1); 976 key = key.substring(0, index); 977 } 978 var fallback; 979 if (fallbackString) { 980 fallback = {}; 981 fallback[prop] = fallbackString; 982 } 983 var data = getL10nData(key, args, fallback); 984 if (data && prop in data) { 985 return data[prop]; 986 } 987 return '{{' + key + '}}'; 988 }, 989 990 // debug 991 getData: function() { return gL10nData; }, 992 getText: function() { return gTextData; }, 993 994 // get|set the document language 995 getLanguage: function() { return gLanguage; }, 996 setLanguage: function(lang, callback) { 997 loadLocale(lang, function() { 998 if (callback) 999 callback(); 1000 translateFragment(); 1001 }); 1002 }, 1003 1004 // get the direction (ltr|rtl) of the current language 1005 getDirection: function() { 1006 // http://www.w3.org/International/questions/qa-scripts 1007 // Arabic, Hebrew, Farsi, Pashto, Urdu 1008 var rtlList = ['ar', 'he', 'fa', 'ps', 'ur']; 1009 var shortCode = gLanguage.split('-', 1)[0]; 1010 return (rtlList.indexOf(shortCode) >= 0) ? 'rtl' : 'ltr'; 1011 }, 1012 1013 // translate an element or document fragment 1014 translate: translateFragment, 1015 1016 // this can be used to prevent race conditions 1017 getReadyState: function() { return gReadyState; }, 1018 ready: function(callback) { 1019 if (!callback) { 1020 return; 1021 } else if (gReadyState == 'complete' || gReadyState == 'interactive') { 1022 window.setTimeout(function() { 1023 callback(); 1024 }); 1025 } else if (document.addEventListener) { 1026 document.addEventListener('localized', function once() { 1027 document.removeEventListener('localized', once); 1028 callback(); 1029 }); 1030 } 1031 } 1032 }; 1033}) (window, document);
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.