Skip to content
This repository
Browse code

added webL10n files

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

0 notes on commit 43f647d

Please sign in to comment.
Something went wrong with that request. Please try again.