pdfjs_viewer-rails 0.0.1

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