pdfjs_rails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (125) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +37 -0
  6. data/Rakefile +1 -0
  7. data/lib/generators/pdfjs_rails/install_generator.rb +13 -0
  8. data/lib/pdfjs/build/pdf.js +7071 -0
  9. data/lib/pdfjs/build/pdf.worker.js +38687 -0
  10. data/lib/pdfjs/web/compatibility.js +471 -0
  11. data/lib/pdfjs/web/debugger.js +532 -0
  12. data/lib/pdfjs/web/images/annotation-check.svg +10 -0
  13. data/lib/pdfjs/web/images/annotation-comment.svg +15 -0
  14. data/lib/pdfjs/web/images/annotation-help.svg +25 -0
  15. data/lib/pdfjs/web/images/annotation-insert.svg +9 -0
  16. data/lib/pdfjs/web/images/annotation-key.svg +10 -0
  17. data/lib/pdfjs/web/images/annotation-newparagraph.svg +10 -0
  18. data/lib/pdfjs/web/images/annotation-note.svg +41 -0
  19. data/lib/pdfjs/web/images/annotation-paragraph.svg +15 -0
  20. data/lib/pdfjs/web/images/findbarButton-next-rtl.png +0 -0
  21. data/lib/pdfjs/web/images/findbarButton-next.png +0 -0
  22. data/lib/pdfjs/web/images/findbarButton-previous-rtl.png +0 -0
  23. data/lib/pdfjs/web/images/findbarButton-previous.png +0 -0
  24. data/lib/pdfjs/web/images/loading-icon.gif +0 -0
  25. data/lib/pdfjs/web/images/loading-small.png +0 -0
  26. data/lib/pdfjs/web/images/secondaryToolbarButton-firstPage.png +0 -0
  27. data/lib/pdfjs/web/images/secondaryToolbarButton-lastPage.png +0 -0
  28. data/lib/pdfjs/web/images/secondaryToolbarButton-rotateCcw.png +0 -0
  29. data/lib/pdfjs/web/images/secondaryToolbarButton-rotateCw.png +0 -0
  30. data/lib/pdfjs/web/images/shadow.png +0 -0
  31. data/lib/pdfjs/web/images/texture.png +0 -0
  32. data/lib/pdfjs/web/images/toolbarButton-bookmark.png +0 -0
  33. data/lib/pdfjs/web/images/toolbarButton-download.png +0 -0
  34. data/lib/pdfjs/web/images/toolbarButton-menuArrows.png +0 -0
  35. data/lib/pdfjs/web/images/toolbarButton-openFile.png +0 -0
  36. data/lib/pdfjs/web/images/toolbarButton-pageDown-rtl.png +0 -0
  37. data/lib/pdfjs/web/images/toolbarButton-pageDown.png +0 -0
  38. data/lib/pdfjs/web/images/toolbarButton-pageUp-rtl.png +0 -0
  39. data/lib/pdfjs/web/images/toolbarButton-pageUp.png +0 -0
  40. data/lib/pdfjs/web/images/toolbarButton-presentationMode.png +0 -0
  41. data/lib/pdfjs/web/images/toolbarButton-print.png +0 -0
  42. data/lib/pdfjs/web/images/toolbarButton-search.png +0 -0
  43. data/lib/pdfjs/web/images/toolbarButton-secondaryToolbarToggle.png +0 -0
  44. data/lib/pdfjs/web/images/toolbarButton-sidebarToggle.png +0 -0
  45. data/lib/pdfjs/web/images/toolbarButton-viewOutline.png +0 -0
  46. data/lib/pdfjs/web/images/toolbarButton-viewThumbnail.png +0 -0
  47. data/lib/pdfjs/web/images/toolbarButton-zoomIn.png +0 -0
  48. data/lib/pdfjs/web/images/toolbarButton-zoomOut.png +0 -0
  49. data/lib/pdfjs/web/l10n.js +922 -0
  50. data/lib/pdfjs/web/locale/ar/viewer.properties +112 -0
  51. data/lib/pdfjs/web/locale/ca/viewer.properties +131 -0
  52. data/lib/pdfjs/web/locale/cs/viewer.properties +58 -0
  53. data/lib/pdfjs/web/locale/da/viewer.properties +132 -0
  54. data/lib/pdfjs/web/locale/de/viewer.properties +128 -0
  55. data/lib/pdfjs/web/locale/el/viewer.properties +124 -0
  56. data/lib/pdfjs/web/locale/en-US/viewer.properties +134 -0
  57. data/lib/pdfjs/web/locale/es/viewer.properties +131 -0
  58. data/lib/pdfjs/web/locale/fi/viewer.properties +129 -0
  59. data/lib/pdfjs/web/locale/fr/viewer.properties +130 -0
  60. data/lib/pdfjs/web/locale/he/viewer.properties +59 -0
  61. data/lib/pdfjs/web/locale/it/viewer.properties +44 -0
  62. data/lib/pdfjs/web/locale/ja/viewer.properties +132 -0
  63. data/lib/pdfjs/web/locale/ko/viewer.properties +131 -0
  64. data/lib/pdfjs/web/locale/locale.properties +78 -0
  65. data/lib/pdfjs/web/locale/lt/viewer.properties +129 -0
  66. data/lib/pdfjs/web/locale/nl/viewer.properties +134 -0
  67. data/lib/pdfjs/web/locale/pl/viewer.properties +132 -0
  68. data/lib/pdfjs/web/locale/pt-BR/viewer.properties +44 -0
  69. data/lib/pdfjs/web/locale/ro/viewer.properties +55 -0
  70. data/lib/pdfjs/web/locale/ru/viewer.properties +62 -0
  71. data/lib/pdfjs/web/locale/sr/viewer.properties +55 -0
  72. data/lib/pdfjs/web/locale/sv/viewer.properties +134 -0
  73. data/lib/pdfjs/web/locale/tr/viewer.properties +129 -0
  74. data/lib/pdfjs/web/locale/vi/viewer.properties +131 -0
  75. data/lib/pdfjs/web/locale/zh-CN/viewer.properties +129 -0
  76. data/lib/pdfjs/web/locale/zh-TW/viewer.properties +132 -0
  77. data/lib/pdfjs/web/viewer.css +1544 -0
  78. data/lib/pdfjs/web/viewer.html +250 -0
  79. data/lib/pdfjs/web/viewer.js +4477 -0
  80. data/lib/pdfjs_rails.rb +6 -0
  81. data/lib/pdfjs_rails/railtie.rb +9 -0
  82. data/lib/pdfjs_rails/version.rb +3 -0
  83. data/lib/pdfjs_rails/view_helpers.rb +225 -0
  84. data/pdfjs_rails.gemspec +24 -0
  85. data/vender/assets/images/annotation-check.svg +10 -0
  86. data/vender/assets/images/annotation-comment.svg +15 -0
  87. data/vender/assets/images/annotation-help.svg +25 -0
  88. data/vender/assets/images/annotation-insert.svg +9 -0
  89. data/vender/assets/images/annotation-key.svg +10 -0
  90. data/vender/assets/images/annotation-newparagraph.svg +10 -0
  91. data/vender/assets/images/annotation-note.svg +41 -0
  92. data/vender/assets/images/annotation-paragraph.svg +15 -0
  93. data/vender/assets/images/findbarButton-next-rtl.png +0 -0
  94. data/vender/assets/images/findbarButton-next.png +0 -0
  95. data/vender/assets/images/findbarButton-previous-rtl.png +0 -0
  96. data/vender/assets/images/findbarButton-previous.png +0 -0
  97. data/vender/assets/images/loading-icon.gif +0 -0
  98. data/vender/assets/images/loading-small.png +0 -0
  99. data/vender/assets/images/shadow.png +0 -0
  100. data/vender/assets/images/texture.png +0 -0
  101. data/vender/assets/images/toolbarButton-bookmark.png +0 -0
  102. data/vender/assets/images/toolbarButton-download.png +0 -0
  103. data/vender/assets/images/toolbarButton-menuArrows.png +0 -0
  104. data/vender/assets/images/toolbarButton-openFile.png +0 -0
  105. data/vender/assets/images/toolbarButton-pageDown-rtl.png +0 -0
  106. data/vender/assets/images/toolbarButton-pageDown.png +0 -0
  107. data/vender/assets/images/toolbarButton-pageUp-rtl.png +0 -0
  108. data/vender/assets/images/toolbarButton-pageUp.png +0 -0
  109. data/vender/assets/images/toolbarButton-presentationMode.png +0 -0
  110. data/vender/assets/images/toolbarButton-print.png +0 -0
  111. data/vender/assets/images/toolbarButton-search.png +0 -0
  112. data/vender/assets/images/toolbarButton-sidebarToggle.png +0 -0
  113. data/vender/assets/images/toolbarButton-viewOutline.png +0 -0
  114. data/vender/assets/images/toolbarButton-viewThumbnail.png +0 -0
  115. data/vender/assets/images/toolbarButton-zoomIn.png +0 -0
  116. data/vender/assets/images/toolbarButton-zoomOut.png +0 -0
  117. data/vender/assets/javascripts/application.js +22 -0
  118. data/vender/assets/javascripts/compatibility.js +471 -0
  119. data/vender/assets/javascripts/debugger.js +532 -0
  120. data/vender/assets/javascripts/l10n.js +922 -0
  121. data/vender/assets/javascripts/pdf.js +7063 -0
  122. data/vender/assets/javascripts/pdf.worker.js +38679 -0
  123. data/vender/assets/javascripts/viewer.js +4317 -0
  124. data/vender/assets/stylesheets/viewer.css +1382 -0
  125. metadata +209 -0
@@ -0,0 +1,10 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg
3
+ xmlns="http://www.w3.org/2000/svg"
4
+ width="40"
5
+ height="40">
6
+ <path
7
+ d="M 1.5006714,23.536225 6.8925879,18.994244 14.585721,26.037937 34.019683,4.5410479 38.499329,9.2235032 14.585721,35.458952 z"
8
+ id="path4"
9
+ style="fill:#ffff00;fill-opacity:1;stroke:#000000;stroke-width:1.25402856;stroke-opacity:1" />
10
+ </svg>
@@ -0,0 +1,15 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg
3
+ xmlns="http://www.w3.org/2000/svg"
4
+ height="40"
5
+ width="40">
6
+ <rect
7
+ style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
8
+ width="33.76017"
9
+ height="33.76017"
10
+ x="3.119915"
11
+ y="3.119915" />
12
+ <path
13
+ d="m 20.677967,8.54499 c -7.342801,0 -13.295293,4.954293 -13.295293,11.065751 0,2.088793 0.3647173,3.484376 1.575539,5.150563 L 6.0267418,31.45501 13.560595,29.011117 c 2.221262,1.387962 4.125932,1.665377 7.117372,1.665377 7.3428,0 13.295291,-4.954295 13.295291,-11.065753 0,-6.111458 -5.952491,-11.065751 -13.295291,-11.065751 z"
14
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.93031836;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"/>
15
+ </svg>
@@ -0,0 +1,25 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg
3
+ xmlns="http://www.w3.org/2000/svg"
4
+ width="40"
5
+ height="40">
6
+ <g
7
+ transform="translate(0,-60)"
8
+ id="layer1">
9
+ <rect
10
+ width="36.460953"
11
+ height="34.805603"
12
+ x="1.7695236"
13
+ y="62.597198"
14
+ style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.30826771;stroke-opacity:1" />
15
+ <g
16
+ transform="matrix(0.88763677,0,0,0.88763677,2.2472646,8.9890584)">
17
+ <path
18
+ d="M 20,64.526342 C 11.454135,64.526342 4.5263421,71.454135 4.5263421,80 4.5263421,88.545865 11.454135,95.473658 20,95.473658 28.545865,95.473658 35.473658,88.545865 35.473658,80 35.473658,71.454135 28.545865,64.526342 20,64.526342 z m -0.408738,9.488564 c 3.527079,0 6.393832,2.84061 6.393832,6.335441 0,3.494831 -2.866753,6.335441 -6.393832,6.335441 -3.527079,0 -6.393832,-2.84061 -6.393832,-6.335441 0,-3.494831 2.866753,-6.335441 6.393832,-6.335441 z"
19
+ style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.02768445;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
20
+ <path
21
+ d="m 7.2335209,71.819938 4.9702591,4.161823 c -1.679956,2.581606 -1.443939,6.069592 0.159325,8.677725 l -5.1263071,3.424463 c 0.67516,1.231452 3.0166401,3.547686 4.2331971,4.194757 l 3.907728,-4.567277 c 2.541952,1.45975 5.730694,1.392161 8.438683,-0.12614 l 3.469517,6.108336 c 1.129779,-0.44367 4.742234,-3.449633 5.416358,-5.003859 l -5.46204,-4.415541 c 1.44319,-2.424098 1.651175,-5.267515 0.557303,-7.748623 l 5.903195,-3.833951 C 33.14257,71.704996 30.616217,69.018606 29.02952,67.99296 l -4.118813,4.981678 C 22.411934,71.205099 18.900853,70.937534 16.041319,72.32916 l -3.595408,-5.322091 c -1.345962,0.579488 -4.1293881,2.921233 -5.2123901,4.812869 z m 8.1010311,3.426672 c 2.75284,-2.446266 6.769149,-2.144694 9.048998,0.420874 2.279848,2.56557 2.113919,6.596919 -0.638924,9.043185 -2.752841,2.446267 -6.775754,2.13726 -9.055604,-0.428308 -2.279851,-2.565568 -2.107313,-6.589485 0.64553,-9.035751 z"
22
+ style="fill:#000000;fill-opacity:1;stroke:none" />
23
+ </g>
24
+ </g>
25
+ </svg>
@@ -0,0 +1,9 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg
3
+ xmlns="http://www.w3.org/2000/svg"
4
+ width="64"
5
+ height="64">
6
+ <path
7
+ d="M 32.003143,1.4044602 57.432701,62.632577 6.5672991,62.627924 z"
8
+ style="fill:#ffff00;fill-opacity:0.94117647;fill-rule:nonzero;stroke:#000000;stroke-width:1.00493038;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
9
+ </svg>
@@ -0,0 +1,10 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg
3
+ xmlns="http://www.w3.org/2000/svg"
4
+ width="64"
5
+ height="64">
6
+ <path
7
+ d="M 25.470843,9.4933766 C 25.30219,12.141818 30.139101,14.445969 34.704831,13.529144 40.62635,12.541995 41.398833,7.3856498 35.97505,5.777863 31.400921,4.1549155 25.157674,6.5445892 25.470843,9.4933766 z M 4.5246282,17.652051 C 4.068249,11.832873 9.2742983,5.9270407 18.437379,3.0977088 29.751911,-0.87185184 45.495663,1.4008022 53.603953,7.1104009 c 9.275765,6.1889221 7.158128,16.2079421 -3.171076,21.5939521 -1.784316,1.635815 -6.380222,1.21421 -7.068351,3.186186 -1.04003,0.972427 -1.288046,2.050158 -1.232864,3.168203 1.015111,2.000108 -3.831548,1.633216 -3.270553,3.759574 0.589477,5.264544 -0.179276,10.53738 -0.362842,15.806257 -0.492006,2.184998 1.163456,4.574232 -0.734888,6.610642 -2.482919,2.325184 -7.30604,2.189143 -9.193497,-0.274767 -2.733688,-1.740626 -8.254447,-3.615254 -6.104247,-6.339626 3.468112,-1.708686 -2.116197,-3.449897 0.431242,-5.080274 5.058402,-1.39256 -2.393215,-2.304318 -0.146889,-4.334645 3.069198,-0.977415 2.056986,-2.518352 -0.219121,-3.540397 1.876567,-1.807151 1.484149,-4.868919 -2.565455,-5.942205 0.150866,-1.805474 2.905737,-4.136876 -1.679967,-5.20493 C 10.260902,27.882167 4.6872697,22.95045 4.5245945,17.652051 z"
8
+ id="path604"
9
+ style="fill:#ffff00;fill-opacity:1;stroke:#000000;stroke-width:1.72665179;stroke-opacity:1" />
10
+ </svg>
@@ -0,0 +1,10 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg
3
+ xmlns="http://www.w3.org/2000/svg"
4
+ width="64"
5
+ height="64">
6
+ <path
7
+ d="M 32.003143,10.913072 57.432701,53.086929 6.567299,53.083723 z"
8
+ id="path2985"
9
+ style="fill:#ffff00;fill-opacity:0.94117647;fill-rule:nonzero;stroke:#000000;stroke-width:0.83403099;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
10
+ </svg>
@@ -0,0 +1,41 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg
3
+ xmlns="http://www.w3.org/2000/svg"
4
+ width="40"
5
+ height="40">
6
+ <rect
7
+ width="36.075428"
8
+ height="31.096582"
9
+ x="1.962286"
10
+ y="4.4517088"
11
+ id="rect4"
12
+ style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.23004246;stroke-opacity:1" />
13
+ <rect
14
+ width="27.96859"
15
+ height="1.5012145"
16
+ x="6.0157046"
17
+ y="10.285"
18
+ id="rect6"
19
+ style="fill:#000000;fill-opacity:1;stroke:none" />
20
+ <rect
21
+ width="27.96859"
22
+ height="0.85783684"
23
+ x="6.0157056"
24
+ y="23.21689"
25
+ id="rect8"
26
+ style="fill:#000000;fill-opacity:1;stroke:none" />
27
+ <rect
28
+ width="27.96859"
29
+ height="0.85783684"
30
+ x="5.8130345"
31
+ y="28.964394"
32
+ id="rect10"
33
+ style="fill:#000000;fill-opacity:1;stroke:none" />
34
+ <rect
35
+ width="27.96859"
36
+ height="0.85783684"
37
+ x="6.0157046"
38
+ y="17.426493"
39
+ id="rect12"
40
+ style="fill:#000000;fill-opacity:1;stroke:none" />
41
+ </svg>
@@ -0,0 +1,15 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg
3
+ xmlns="http://www.w3.org/2000/svg"
4
+ width="40"
5
+ height="40">
6
+ <rect
7
+ width="33.76017"
8
+ height="33.76017"
9
+ x="3.119915"
10
+ y="3.119915"
11
+ style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
12
+ <path
13
+ d="m 17.692678,34.50206 0,-16.182224 c -1.930515,-0.103225 -3.455824,-0.730383 -4.57593,-1.881473 -1.12011,-1.151067 -1.680164,-2.619596 -1.680164,-4.405591 0,-1.992435 0.621995,-3.5796849 1.865988,-4.7617553 1.243989,-1.1820288 3.06352,-1.7730536 5.458598,-1.7730764 l 9.802246,0 0,2.6789711 -2.229895,0 0,26.3251486 -2.632515,0 0,-26.3251486 -3.45324,0 0,26.3251486 z"
14
+ style="font-size:29.42051125px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.07795751;stroke-opacity:1;font-family:Arial;-inkscape-font-specification:Arial" />
15
+ </svg>
Binary file
Binary file
@@ -0,0 +1,922 @@
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
+ /*
22
+ Additional modifications for PDF.js project:
23
+ - Disables language initialization on page loading;
24
+ - Adds fallback argument to the getL10nData;
25
+ - Removes consoleLog and simplifies consoleWarn;
26
+ - Removes window._ assignment.
27
+ */
28
+ /*jshint browser: true, devel: true, es5: true, globalstrict: true */
29
+ 'use strict';
30
+
31
+ document.webL10n = (function(window, document, undefined) {
32
+ var gL10nData = {};
33
+ var gTextData = '';
34
+ var gTextProp = 'textContent';
35
+ var gLanguage = '';
36
+ var gMacros = {};
37
+ var gReadyState = 'loading';
38
+
39
+ // read-only setting -- we recommend to load l10n resources synchronously
40
+ var gAsyncResourceLoading = true;
41
+
42
+ // debug helpers
43
+ function consoleWarn(message) {
44
+ console.log('[l10n] ' + message);
45
+ };
46
+
47
+ /**
48
+ * DOM helpers for the so-called "HTML API".
49
+ *
50
+ * These functions are written for modern browsers. For old versions of IE,
51
+ * they're overridden in the 'startup' section at the end of this file.
52
+ */
53
+
54
+ function getL10nResourceLinks() {
55
+ return document.querySelectorAll('link[type="application/l10n"]');
56
+ }
57
+
58
+ function getTranslatableChildren(element) {
59
+ return element ? element.querySelectorAll('*[data-l10n-id]') : [];
60
+ }
61
+
62
+ function getL10nAttributes(element) {
63
+ if (!element)
64
+ return {};
65
+
66
+ var l10nId = element.getAttribute('data-l10n-id');
67
+ var l10nArgs = element.getAttribute('data-l10n-args');
68
+ var args = {};
69
+ if (l10nArgs) {
70
+ try {
71
+ args = JSON.parse(l10nArgs);
72
+ } catch (e) {
73
+ consoleWarn('could not parse arguments for #' + l10nId);
74
+ }
75
+ }
76
+ return { id: l10nId, args: args };
77
+ }
78
+
79
+ function fireL10nReadyEvent(lang) {
80
+ var evtObject = document.createEvent('Event');
81
+ evtObject.initEvent('localized', false, false);
82
+ evtObject.language = lang;
83
+ window.dispatchEvent(evtObject);
84
+ }
85
+
86
+
87
+ /**
88
+ * l10n resource parser:
89
+ * - reads (async XHR) the l10n resource matching `lang';
90
+ * - imports linked resources (synchronously) when specified;
91
+ * - parses the text data (fills `gL10nData' and `gTextData');
92
+ * - triggers success/failure callbacks when done.
93
+ *
94
+ * @param {string} href
95
+ * URL of the l10n resource to parse.
96
+ *
97
+ * @param {string} lang
98
+ * locale (language) to parse.
99
+ *
100
+ * @param {Function} successCallback
101
+ * triggered when the l10n resource has been successully parsed.
102
+ *
103
+ * @param {Function} failureCallback
104
+ * triggered when the an error has occured.
105
+ *
106
+ * @return {void}
107
+ * uses the following global variables: gL10nData, gTextData, gTextProp.
108
+ */
109
+
110
+ function parseResource(href, lang, successCallback, failureCallback) {
111
+ var baseURL = href.replace(/\/[^\/]*$/, '/');
112
+
113
+ // handle escaped characters (backslashes) in a string
114
+ function evalString(text) {
115
+ if (text.lastIndexOf('\\') < 0)
116
+ return text;
117
+ return text.replace(/\\\\/g, '\\')
118
+ .replace(/\\n/g, '\n')
119
+ .replace(/\\r/g, '\r')
120
+ .replace(/\\t/g, '\t')
121
+ .replace(/\\b/g, '\b')
122
+ .replace(/\\f/g, '\f')
123
+ .replace(/\\{/g, '{')
124
+ .replace(/\\}/g, '}')
125
+ .replace(/\\"/g, '"')
126
+ .replace(/\\'/g, "'");
127
+ }
128
+
129
+ // parse *.properties text data into an l10n dictionary
130
+ function parseProperties(text) {
131
+ var dictionary = [];
132
+
133
+ // token expressions
134
+ var reBlank = /^\s*|\s*$/;
135
+ var reComment = /^\s*#|^\s*$/;
136
+ var reSection = /^\s*\[(.*)\]\s*$/;
137
+ var reImport = /^\s*@import\s+url\((.*)\)\s*$/i;
138
+ var reSplit = /^([^=\s]*)\s*=\s*(.+)$/; // TODO: escape EOLs with '\'
139
+
140
+ // parse the *.properties file into an associative array
141
+ function parseRawLines(rawText, extendedSyntax) {
142
+ var entries = rawText.replace(reBlank, '').split(/[\r\n]+/);
143
+ var currentLang = '*';
144
+ var genericLang = lang.replace(/-[a-z]+$/i, '');
145
+ var skipLang = false;
146
+ var match = '';
147
+
148
+ for (var i = 0; i < entries.length; i++) {
149
+ var line = entries[i];
150
+
151
+ // comment or blank line?
152
+ if (reComment.test(line))
153
+ continue;
154
+
155
+ // the extended syntax supports [lang] sections and @import rules
156
+ if (extendedSyntax) {
157
+ if (reSection.test(line)) { // section start?
158
+ match = reSection.exec(line);
159
+ currentLang = match[1];
160
+ skipLang = (currentLang !== '*') &&
161
+ (currentLang !== lang) && (currentLang !== genericLang);
162
+ continue;
163
+ } else if (skipLang) {
164
+ continue;
165
+ }
166
+ if (reImport.test(line)) { // @import rule?
167
+ match = reImport.exec(line);
168
+ loadImport(baseURL + match[1]); // load the resource synchronously
169
+ }
170
+ }
171
+
172
+ // key-value pair
173
+ var tmp = line.match(reSplit);
174
+ if (tmp && tmp.length == 3)
175
+ dictionary[tmp[1]] = evalString(tmp[2]);
176
+ }
177
+ }
178
+
179
+ // import another *.properties file
180
+ function loadImport(url) {
181
+ loadResource(url, function(content) {
182
+ parseRawLines(content, false); // don't allow recursive imports
183
+ }, false, false); // load synchronously
184
+ }
185
+
186
+ // fill the dictionary
187
+ parseRawLines(text, true);
188
+ return dictionary;
189
+ }
190
+
191
+ // load the specified resource file
192
+ function loadResource(url, onSuccess, onFailure, asynchronous) {
193
+ var xhr = new XMLHttpRequest();
194
+ xhr.open('GET', url, asynchronous);
195
+ if (xhr.overrideMimeType) {
196
+ xhr.overrideMimeType('text/plain; charset=utf-8');
197
+ }
198
+ xhr.onreadystatechange = function() {
199
+ if (xhr.readyState == 4) {
200
+ if (xhr.status == 200 || xhr.status === 0) {
201
+ if (onSuccess)
202
+ onSuccess(xhr.responseText);
203
+ } else {
204
+ if (onFailure)
205
+ onFailure();
206
+ }
207
+ }
208
+ };
209
+ xhr.send(null);
210
+ }
211
+
212
+ // load and parse l10n data (warning: global variables are used here)
213
+ loadResource(href, function(response) {
214
+ gTextData += response; // mostly for debug
215
+
216
+ // parse *.properties text data into an l10n dictionary
217
+ var data = parseProperties(response);
218
+
219
+ // find attribute descriptions, if any
220
+ for (var key in data) {
221
+ var id, prop, index = key.lastIndexOf('.');
222
+ if (index > 0) { // an attribute has been specified
223
+ id = key.substring(0, index);
224
+ prop = key.substr(index + 1);
225
+ } else { // no attribute: assuming text content by default
226
+ id = key;
227
+ prop = gTextProp;
228
+ }
229
+ if (!gL10nData[id]) {
230
+ gL10nData[id] = {};
231
+ }
232
+ gL10nData[id][prop] = data[key];
233
+ }
234
+
235
+ // trigger callback
236
+ if (successCallback)
237
+ successCallback();
238
+ }, failureCallback, gAsyncResourceLoading);
239
+ };
240
+
241
+ // load and parse all resources for the specified locale
242
+ function loadLocale(lang, callback) {
243
+ clear();
244
+ gLanguage = lang;
245
+
246
+ // check all <link type="application/l10n" href="..." /> nodes
247
+ // and load the resource files
248
+ var langLinks = getL10nResourceLinks();
249
+ var langCount = langLinks.length;
250
+ if (langCount == 0) {
251
+ consoleWarn('no resource to load, early way out');
252
+ fireL10nReadyEvent(lang);
253
+ gReadyState = 'complete';
254
+ return;
255
+ }
256
+
257
+ // start the callback when all resources are loaded
258
+ var onResourceLoaded = null;
259
+ var gResourceCount = 0;
260
+ onResourceLoaded = function() {
261
+ gResourceCount++;
262
+ if (gResourceCount >= langCount) {
263
+ if (callback) // execute the [optional] callback
264
+ callback();
265
+ fireL10nReadyEvent(lang);
266
+ gReadyState = 'complete';
267
+ }
268
+ };
269
+
270
+ // load all resource files
271
+ function l10nResourceLink(link) {
272
+ var href = link.href;
273
+ var type = link.type;
274
+ this.load = function(lang, callback) {
275
+ var applied = lang;
276
+ parseResource(href, lang, callback, function() {
277
+ consoleWarn(href + ' not found.');
278
+ applied = '';
279
+ });
280
+ return applied; // return lang if found, an empty string if not found
281
+ };
282
+ }
283
+
284
+ for (var i = 0; i < langCount; i++) {
285
+ var resource = new l10nResourceLink(langLinks[i]);
286
+ var rv = resource.load(lang, onResourceLoaded);
287
+ if (rv != lang) { // lang not found, used default resource instead
288
+ consoleWarn('"' + lang + '" resource not found');
289
+ gLanguage = '';
290
+ }
291
+ }
292
+ }
293
+
294
+ // clear all l10n data
295
+ function clear() {
296
+ gL10nData = {};
297
+ gTextData = '';
298
+ gLanguage = '';
299
+ // TODO: clear all non predefined macros.
300
+ // There's no such macro /yet/ but we're planning to have some...
301
+ }
302
+
303
+
304
+ /**
305
+ * Get rules for plural forms (shared with JetPack), see:
306
+ * http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
307
+ * https://github.com/mozilla/addon-sdk/blob/master/python-lib/plural-rules-generator.p
308
+ *
309
+ * @param {string} lang
310
+ * locale (language) used.
311
+ *
312
+ * @return {Function}
313
+ * returns a function that gives the plural form name for a given integer:
314
+ * var fun = getPluralRules('en');
315
+ * fun(1) -> 'one'
316
+ * fun(0) -> 'other'
317
+ * fun(1000) -> 'other'.
318
+ */
319
+
320
+ function getPluralRules(lang) {
321
+ var locales2rules = {
322
+ 'af': 3,
323
+ 'ak': 4,
324
+ 'am': 4,
325
+ 'ar': 1,
326
+ 'asa': 3,
327
+ 'az': 0,
328
+ 'be': 11,
329
+ 'bem': 3,
330
+ 'bez': 3,
331
+ 'bg': 3,
332
+ 'bh': 4,
333
+ 'bm': 0,
334
+ 'bn': 3,
335
+ 'bo': 0,
336
+ 'br': 20,
337
+ 'brx': 3,
338
+ 'bs': 11,
339
+ 'ca': 3,
340
+ 'cgg': 3,
341
+ 'chr': 3,
342
+ 'cs': 12,
343
+ 'cy': 17,
344
+ 'da': 3,
345
+ 'de': 3,
346
+ 'dv': 3,
347
+ 'dz': 0,
348
+ 'ee': 3,
349
+ 'el': 3,
350
+ 'en': 3,
351
+ 'eo': 3,
352
+ 'es': 3,
353
+ 'et': 3,
354
+ 'eu': 3,
355
+ 'fa': 0,
356
+ 'ff': 5,
357
+ 'fi': 3,
358
+ 'fil': 4,
359
+ 'fo': 3,
360
+ 'fr': 5,
361
+ 'fur': 3,
362
+ 'fy': 3,
363
+ 'ga': 8,
364
+ 'gd': 24,
365
+ 'gl': 3,
366
+ 'gsw': 3,
367
+ 'gu': 3,
368
+ 'guw': 4,
369
+ 'gv': 23,
370
+ 'ha': 3,
371
+ 'haw': 3,
372
+ 'he': 2,
373
+ 'hi': 4,
374
+ 'hr': 11,
375
+ 'hu': 0,
376
+ 'id': 0,
377
+ 'ig': 0,
378
+ 'ii': 0,
379
+ 'is': 3,
380
+ 'it': 3,
381
+ 'iu': 7,
382
+ 'ja': 0,
383
+ 'jmc': 3,
384
+ 'jv': 0,
385
+ 'ka': 0,
386
+ 'kab': 5,
387
+ 'kaj': 3,
388
+ 'kcg': 3,
389
+ 'kde': 0,
390
+ 'kea': 0,
391
+ 'kk': 3,
392
+ 'kl': 3,
393
+ 'km': 0,
394
+ 'kn': 0,
395
+ 'ko': 0,
396
+ 'ksb': 3,
397
+ 'ksh': 21,
398
+ 'ku': 3,
399
+ 'kw': 7,
400
+ 'lag': 18,
401
+ 'lb': 3,
402
+ 'lg': 3,
403
+ 'ln': 4,
404
+ 'lo': 0,
405
+ 'lt': 10,
406
+ 'lv': 6,
407
+ 'mas': 3,
408
+ 'mg': 4,
409
+ 'mk': 16,
410
+ 'ml': 3,
411
+ 'mn': 3,
412
+ 'mo': 9,
413
+ 'mr': 3,
414
+ 'ms': 0,
415
+ 'mt': 15,
416
+ 'my': 0,
417
+ 'nah': 3,
418
+ 'naq': 7,
419
+ 'nb': 3,
420
+ 'nd': 3,
421
+ 'ne': 3,
422
+ 'nl': 3,
423
+ 'nn': 3,
424
+ 'no': 3,
425
+ 'nr': 3,
426
+ 'nso': 4,
427
+ 'ny': 3,
428
+ 'nyn': 3,
429
+ 'om': 3,
430
+ 'or': 3,
431
+ 'pa': 3,
432
+ 'pap': 3,
433
+ 'pl': 13,
434
+ 'ps': 3,
435
+ 'pt': 3,
436
+ 'rm': 3,
437
+ 'ro': 9,
438
+ 'rof': 3,
439
+ 'ru': 11,
440
+ 'rwk': 3,
441
+ 'sah': 0,
442
+ 'saq': 3,
443
+ 'se': 7,
444
+ 'seh': 3,
445
+ 'ses': 0,
446
+ 'sg': 0,
447
+ 'sh': 11,
448
+ 'shi': 19,
449
+ 'sk': 12,
450
+ 'sl': 14,
451
+ 'sma': 7,
452
+ 'smi': 7,
453
+ 'smj': 7,
454
+ 'smn': 7,
455
+ 'sms': 7,
456
+ 'sn': 3,
457
+ 'so': 3,
458
+ 'sq': 3,
459
+ 'sr': 11,
460
+ 'ss': 3,
461
+ 'ssy': 3,
462
+ 'st': 3,
463
+ 'sv': 3,
464
+ 'sw': 3,
465
+ 'syr': 3,
466
+ 'ta': 3,
467
+ 'te': 3,
468
+ 'teo': 3,
469
+ 'th': 0,
470
+ 'ti': 4,
471
+ 'tig': 3,
472
+ 'tk': 3,
473
+ 'tl': 4,
474
+ 'tn': 3,
475
+ 'to': 0,
476
+ 'tr': 0,
477
+ 'ts': 3,
478
+ 'tzm': 22,
479
+ 'uk': 11,
480
+ 'ur': 3,
481
+ 've': 3,
482
+ 'vi': 0,
483
+ 'vun': 3,
484
+ 'wa': 4,
485
+ 'wae': 3,
486
+ 'wo': 0,
487
+ 'xh': 3,
488
+ 'xog': 3,
489
+ 'yo': 0,
490
+ 'zh': 0,
491
+ 'zu': 3
492
+ };
493
+
494
+ // utility functions for plural rules methods
495
+ function isIn(n, list) {
496
+ return list.indexOf(n) !== -1;
497
+ }
498
+ function isBetween(n, start, end) {
499
+ return start <= n && n <= end;
500
+ }
501
+
502
+ // list of all plural rules methods:
503
+ // map an integer to the plural form name to use
504
+ var pluralRules = {
505
+ '0': function(n) {
506
+ return 'other';
507
+ },
508
+ '1': function(n) {
509
+ if ((isBetween((n % 100), 3, 10)))
510
+ return 'few';
511
+ if (n === 0)
512
+ return 'zero';
513
+ if ((isBetween((n % 100), 11, 99)))
514
+ return 'many';
515
+ if (n == 2)
516
+ return 'two';
517
+ if (n == 1)
518
+ return 'one';
519
+ return 'other';
520
+ },
521
+ '2': function(n) {
522
+ if (n !== 0 && (n % 10) === 0)
523
+ return 'many';
524
+ if (n == 2)
525
+ return 'two';
526
+ if (n == 1)
527
+ return 'one';
528
+ return 'other';
529
+ },
530
+ '3': function(n) {
531
+ if (n == 1)
532
+ return 'one';
533
+ return 'other';
534
+ },
535
+ '4': function(n) {
536
+ if ((isBetween(n, 0, 1)))
537
+ return 'one';
538
+ return 'other';
539
+ },
540
+ '5': function(n) {
541
+ if ((isBetween(n, 0, 2)) && n != 2)
542
+ return 'one';
543
+ return 'other';
544
+ },
545
+ '6': function(n) {
546
+ if (n === 0)
547
+ return 'zero';
548
+ if ((n % 10) == 1 && (n % 100) != 11)
549
+ return 'one';
550
+ return 'other';
551
+ },
552
+ '7': function(n) {
553
+ if (n == 2)
554
+ return 'two';
555
+ if (n == 1)
556
+ return 'one';
557
+ return 'other';
558
+ },
559
+ '8': function(n) {
560
+ if ((isBetween(n, 3, 6)))
561
+ return 'few';
562
+ if ((isBetween(n, 7, 10)))
563
+ return 'many';
564
+ if (n == 2)
565
+ return 'two';
566
+ if (n == 1)
567
+ return 'one';
568
+ return 'other';
569
+ },
570
+ '9': function(n) {
571
+ if (n === 0 || n != 1 && (isBetween((n % 100), 1, 19)))
572
+ return 'few';
573
+ if (n == 1)
574
+ return 'one';
575
+ return 'other';
576
+ },
577
+ '10': function(n) {
578
+ if ((isBetween((n % 10), 2, 9)) && !(isBetween((n % 100), 11, 19)))
579
+ return 'few';
580
+ if ((n % 10) == 1 && !(isBetween((n % 100), 11, 19)))
581
+ return 'one';
582
+ return 'other';
583
+ },
584
+ '11': function(n) {
585
+ if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14)))
586
+ return 'few';
587
+ if ((n % 10) === 0 ||
588
+ (isBetween((n % 10), 5, 9)) ||
589
+ (isBetween((n % 100), 11, 14)))
590
+ return 'many';
591
+ if ((n % 10) == 1 && (n % 100) != 11)
592
+ return 'one';
593
+ return 'other';
594
+ },
595
+ '12': function(n) {
596
+ if ((isBetween(n, 2, 4)))
597
+ return 'few';
598
+ if (n == 1)
599
+ return 'one';
600
+ return 'other';
601
+ },
602
+ '13': function(n) {
603
+ if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14)))
604
+ return 'few';
605
+ if (n != 1 && (isBetween((n % 10), 0, 1)) ||
606
+ (isBetween((n % 10), 5, 9)) ||
607
+ (isBetween((n % 100), 12, 14)))
608
+ return 'many';
609
+ if (n == 1)
610
+ return 'one';
611
+ return 'other';
612
+ },
613
+ '14': function(n) {
614
+ if ((isBetween((n % 100), 3, 4)))
615
+ return 'few';
616
+ if ((n % 100) == 2)
617
+ return 'two';
618
+ if ((n % 100) == 1)
619
+ return 'one';
620
+ return 'other';
621
+ },
622
+ '15': function(n) {
623
+ if (n === 0 || (isBetween((n % 100), 2, 10)))
624
+ return 'few';
625
+ if ((isBetween((n % 100), 11, 19)))
626
+ return 'many';
627
+ if (n == 1)
628
+ return 'one';
629
+ return 'other';
630
+ },
631
+ '16': function(n) {
632
+ if ((n % 10) == 1 && n != 11)
633
+ return 'one';
634
+ return 'other';
635
+ },
636
+ '17': function(n) {
637
+ if (n == 3)
638
+ return 'few';
639
+ if (n === 0)
640
+ return 'zero';
641
+ if (n == 6)
642
+ return 'many';
643
+ if (n == 2)
644
+ return 'two';
645
+ if (n == 1)
646
+ return 'one';
647
+ return 'other';
648
+ },
649
+ '18': function(n) {
650
+ if (n === 0)
651
+ return 'zero';
652
+ if ((isBetween(n, 0, 2)) && n !== 0 && n != 2)
653
+ return 'one';
654
+ return 'other';
655
+ },
656
+ '19': function(n) {
657
+ if ((isBetween(n, 2, 10)))
658
+ return 'few';
659
+ if ((isBetween(n, 0, 1)))
660
+ return 'one';
661
+ return 'other';
662
+ },
663
+ '20': function(n) {
664
+ if ((isBetween((n % 10), 3, 4) || ((n % 10) == 9)) && !(
665
+ isBetween((n % 100), 10, 19) ||
666
+ isBetween((n % 100), 70, 79) ||
667
+ isBetween((n % 100), 90, 99)
668
+ ))
669
+ return 'few';
670
+ if ((n % 1000000) === 0 && n !== 0)
671
+ return 'many';
672
+ if ((n % 10) == 2 && !isIn((n % 100), [12, 72, 92]))
673
+ return 'two';
674
+ if ((n % 10) == 1 && !isIn((n % 100), [11, 71, 91]))
675
+ return 'one';
676
+ return 'other';
677
+ },
678
+ '21': function(n) {
679
+ if (n === 0)
680
+ return 'zero';
681
+ if (n == 1)
682
+ return 'one';
683
+ return 'other';
684
+ },
685
+ '22': function(n) {
686
+ if ((isBetween(n, 0, 1)) || (isBetween(n, 11, 99)))
687
+ return 'one';
688
+ return 'other';
689
+ },
690
+ '23': function(n) {
691
+ if ((isBetween((n % 10), 1, 2)) || (n % 20) === 0)
692
+ return 'one';
693
+ return 'other';
694
+ },
695
+ '24': function(n) {
696
+ if ((isBetween(n, 3, 10) || isBetween(n, 13, 19)))
697
+ return 'few';
698
+ if (isIn(n, [2, 12]))
699
+ return 'two';
700
+ if (isIn(n, [1, 11]))
701
+ return 'one';
702
+ return 'other';
703
+ }
704
+ };
705
+
706
+ // return a function that gives the plural form name for a given integer
707
+ var index = locales2rules[lang.replace(/-.*$/, '')];
708
+ if (!(index in pluralRules)) {
709
+ consoleWarn('plural form unknown for [' + lang + ']');
710
+ return function() { return 'other'; };
711
+ }
712
+ return pluralRules[index];
713
+ }
714
+
715
+ // pre-defined 'plural' macro
716
+ gMacros.plural = function(str, param, key, prop) {
717
+ var n = parseFloat(param);
718
+ if (isNaN(n))
719
+ return str;
720
+
721
+ // TODO: support other properties (l20n still doesn't...)
722
+ if (prop != gTextProp)
723
+ return str;
724
+
725
+ // initialize _pluralRules
726
+ if (!gMacros._pluralRules)
727
+ gMacros._pluralRules = getPluralRules(gLanguage);
728
+ var index = '[' + gMacros._pluralRules(n) + ']';
729
+
730
+ // try to find a [zero|one|two] key if it's defined
731
+ if (n === 0 && (key + '[zero]') in gL10nData) {
732
+ str = gL10nData[key + '[zero]'][prop];
733
+ } else if (n == 1 && (key + '[one]') in gL10nData) {
734
+ str = gL10nData[key + '[one]'][prop];
735
+ } else if (n == 2 && (key + '[two]') in gL10nData) {
736
+ str = gL10nData[key + '[two]'][prop];
737
+ } else if ((key + index) in gL10nData) {
738
+ str = gL10nData[key + index][prop];
739
+ }
740
+
741
+ return str;
742
+ };
743
+
744
+
745
+ /**
746
+ * l10n dictionary functions
747
+ */
748
+
749
+ // fetch an l10n object, warn if not found, apply `args' if possible
750
+ function getL10nData(key, args, fallback) {
751
+ var data = gL10nData[key];
752
+ if (!data) {
753
+ consoleWarn('#' + key + ' missing for [' + gLanguage + ']');
754
+ if (!fallback) {
755
+ return null;
756
+ }
757
+ data = fallback;
758
+ }
759
+
760
+ /** This is where l10n expressions should be processed.
761
+ * The plan is to support C-style expressions from the l20n project;
762
+ * until then, only two kinds of simple expressions are supported:
763
+ * {[ index ]} and {{ arguments }}.
764
+ */
765
+ var rv = {};
766
+ for (var prop in data) {
767
+ var str = data[prop];
768
+ str = substIndexes(str, args, key, prop);
769
+ str = substArguments(str, args);
770
+ rv[prop] = str;
771
+ }
772
+ return rv;
773
+ }
774
+
775
+ // replace {[macros]} with their values
776
+ function substIndexes(str, args, key, prop) {
777
+ var reIndex = /\{\[\s*([a-zA-Z]+)\(([a-zA-Z]+)\)\s*\]\}/;
778
+ var reMatch = reIndex.exec(str);
779
+ if (!reMatch || !reMatch.length)
780
+ return str;
781
+
782
+ // an index/macro has been found
783
+ // Note: at the moment, only one parameter is supported
784
+ var macroName = reMatch[1];
785
+ var paramName = reMatch[2];
786
+ var param;
787
+ if (args && paramName in args) {
788
+ param = args[paramName];
789
+ } else if (paramName in gL10nData) {
790
+ param = gL10nData[paramName];
791
+ }
792
+
793
+ // there's no macro parser yet: it has to be defined in gMacros
794
+ if (macroName in gMacros) {
795
+ var macro = gMacros[macroName];
796
+ str = macro(str, param, key, prop);
797
+ }
798
+ return str;
799
+ }
800
+
801
+ // replace {{arguments}} with their values
802
+ function substArguments(str, args) {
803
+ var reArgs = /\{\{\s*([a-zA-Z\.]+)\s*\}\}/;
804
+ var match = reArgs.exec(str);
805
+ while (match) {
806
+ if (!match || match.length < 2)
807
+ return str; // argument key not found
808
+
809
+ var arg = match[1];
810
+ var sub = '';
811
+ if (arg in args) {
812
+ sub = args[arg];
813
+ } else if (arg in gL10nData) {
814
+ sub = gL10nData[arg][gTextProp];
815
+ } else {
816
+ consoleWarn('could not find argument {{' + arg + '}}');
817
+ return str;
818
+ }
819
+
820
+ str = str.substring(0, match.index) + sub +
821
+ str.substr(match.index + match[0].length);
822
+ match = reArgs.exec(str);
823
+ }
824
+ return str;
825
+ }
826
+
827
+ // translate an HTML element
828
+ function translateElement(element) {
829
+ var l10n = getL10nAttributes(element);
830
+ if (!l10n.id)
831
+ return;
832
+
833
+ // get the related l10n object
834
+ var data = getL10nData(l10n.id, l10n.args);
835
+ if (!data) {
836
+ consoleWarn('#' + l10n.id + ' missing for [' + gLanguage + ']');
837
+ return;
838
+ }
839
+
840
+ // translate element (TODO: security checks?)
841
+ // for the node content, replace the content of the first child textNode
842
+ // and clear other child textNodes
843
+ if (data[gTextProp]) { // XXX
844
+ if (element.children.length === 0) {
845
+ element[gTextProp] = data[gTextProp];
846
+ } else {
847
+ var children = element.childNodes,
848
+ found = false;
849
+ for (var i = 0, l = children.length; i < l; i++) {
850
+ if (children[i].nodeType === 3 &&
851
+ /\S/.test(children[i].textContent)) { // XXX
852
+ // using nodeValue seems cross-browser
853
+ if (found) {
854
+ children[i].nodeValue = '';
855
+ } else {
856
+ children[i].nodeValue = data[gTextProp];
857
+ found = true;
858
+ }
859
+ }
860
+ }
861
+ if (!found) {
862
+ consoleWarn('unexpected error, could not translate element content');
863
+ }
864
+ }
865
+ delete data[gTextProp];
866
+ }
867
+
868
+ for (var k in data) {
869
+ element[k] = data[k];
870
+ }
871
+ }
872
+
873
+ // translate an HTML subtree
874
+ function translateFragment(element) {
875
+ element = element || document.documentElement;
876
+
877
+ // check all translatable children (= w/ a `data-l10n-id' attribute)
878
+ var children = getTranslatableChildren(element);
879
+ var elementCount = children.length;
880
+ for (var i = 0; i < elementCount; i++) {
881
+ translateElement(children[i]);
882
+ }
883
+
884
+ // translate element itself if necessary
885
+ translateElement(element);
886
+ }
887
+
888
+ // cross-browser API (sorry, oldIE doesn't support getters & setters)
889
+ return {
890
+ // get a localized string
891
+ get: function(key, args, fallback) {
892
+ var data = getL10nData(key, args, {textContent: fallback});
893
+ if (data) { // XXX double-check this
894
+ return 'textContent' in data ? data.textContent : '';
895
+ }
896
+ return '{{' + key + '}}';
897
+ },
898
+
899
+ // debug
900
+ getData: function() { return gL10nData; },
901
+ getText: function() { return gTextData; },
902
+
903
+ // get|set the document language
904
+ getLanguage: function() { return gLanguage; },
905
+ setLanguage: function(lang) { loadLocale(lang, translateFragment); },
906
+
907
+ // get the direction (ltr|rtl) of the current language
908
+ getDirection: function() {
909
+ // http://www.w3.org/International/questions/qa-scripts
910
+ // Arabic, Hebrew, Farsi, Pashto, Urdu
911
+ var rtlList = ['ar', 'he', 'fa', 'ps', 'ur'];
912
+ return (rtlList.indexOf(gLanguage) >= 0) ? 'rtl' : 'ltr';
913
+ },
914
+
915
+ // translate an element or document fragment
916
+ translate: translateFragment,
917
+
918
+ // this can be used to prevent race conditions
919
+ getReadyState: function() { return gReadyState; }
920
+ };
921
+
922
+ }) (window, document);