socialcast-i18n-js 4.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/.gitignore +13 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +2 -0
  4. data/README.md +324 -0
  5. data/README.rdoc +320 -0
  6. data/Rakefile +13 -0
  7. data/app/assets/javascripts/i18n/translations.js.erb +6 -0
  8. data/app/assets/javascripts/i18n.js +661 -0
  9. data/config/i18n-js.yml +22 -0
  10. data/i18n-js.gemspec +28 -0
  11. data/lib/i18n/js/engine.rb +6 -0
  12. data/lib/i18n/js/file_dependency_processor.rb +17 -0
  13. data/lib/i18n/js/railtie.rb +17 -0
  14. data/lib/i18n/js/translator.rb +56 -0
  15. data/lib/i18n/js/version.rb +10 -0
  16. data/lib/i18n-js/engine.rb +63 -0
  17. data/lib/i18n-js/middleware.rb +59 -0
  18. data/lib/i18n-js/railtie.rb +13 -0
  19. data/lib/i18n-js/rake.rb +16 -0
  20. data/lib/i18n-js/version.rb +10 -0
  21. data/lib/i18n-js.rb +22 -0
  22. data/lib/tasks/i18n-js.rake +17 -0
  23. data/spec/file_dependency_processor_spec.rb +32 -0
  24. data/spec/fixtures/custom_path.yml +2 -0
  25. data/spec/fixtures/default.yml +2 -0
  26. data/spec/fixtures/js_file_per_locale.yml +1 -0
  27. data/spec/fixtures/locales.yml +76 -0
  28. data/spec/fixtures/multiple_conditions.yml +3 -0
  29. data/spec/fixtures/no_config.yml +2 -0
  30. data/spec/fixtures/simple_scope.yml +2 -0
  31. data/spec/i18n_js_spec.rb +11 -0
  32. data/spec/i18n_spec.js +820 -0
  33. data/spec/i18n_spec.rb +205 -0
  34. data/spec/js/currency.spec.js +60 -0
  35. data/spec/js/current_locale.spec.js +19 -0
  36. data/spec/js/dates.spec.js +218 -0
  37. data/spec/js/defaults.spec.js +23 -0
  38. data/spec/js/interpolation.spec.js +28 -0
  39. data/spec/js/jasmine/MIT.LICENSE +20 -0
  40. data/spec/js/jasmine/jasmine-html.js +190 -0
  41. data/spec/js/jasmine/jasmine.css +166 -0
  42. data/spec/js/jasmine/jasmine.js +2476 -0
  43. data/spec/js/jasmine/jasmine_favicon.png +0 -0
  44. data/spec/js/localization.spec.js +41 -0
  45. data/spec/js/numbers.spec.js +124 -0
  46. data/spec/js/placeholder.spec.js +24 -0
  47. data/spec/js/pluralization.spec.js +105 -0
  48. data/spec/js/prepare_options.spec.js +41 -0
  49. data/spec/js/specs.html +46 -0
  50. data/spec/js/translate.spec.js +119 -0
  51. data/spec/js/translations.js +115 -0
  52. data/spec/resources/custom_path.yml +4 -0
  53. data/spec/resources/default.yml +4 -0
  54. data/spec/resources/js_file_per_locale.yml +3 -0
  55. data/spec/resources/locales.yml +76 -0
  56. data/spec/resources/multiple_conditions.yml +6 -0
  57. data/spec/resources/multiple_files.yml +6 -0
  58. data/spec/resources/no_config.yml +2 -0
  59. data/spec/resources/no_scope.yml +3 -0
  60. data/spec/resources/simple_scope.yml +4 -0
  61. data/spec/spec_helper.rb +40 -0
  62. data/spec/translator_spec.rb +45 -0
  63. data/vendor/assets/javascripts/i18n/translations.js.erb +9 -0
  64. data/vendor/assets/javascripts/i18n.js +531 -0
  65. data/vendor/assets/javascripts/underscore.js +1059 -0
  66. metadata +240 -0
@@ -0,0 +1,531 @@
1
+ // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf
2
+ if (!Array.prototype.indexOf) {
3
+ Array.prototype.indexOf = function(searchElement /*, fromIndex */) {
4
+ "use strict";
5
+
6
+ if (this === void 0 || this === null) {
7
+ throw new TypeError();
8
+ }
9
+
10
+ var t = Object(this);
11
+ var len = t.length >>> 0;
12
+
13
+ if (len === 0) {
14
+ return -1;
15
+ }
16
+
17
+ var n = 0;
18
+ if (arguments.length > 0) {
19
+ n = Number(arguments[1]);
20
+ if (n !== n) { // shortcut for verifying if it's NaN
21
+ n = 0;
22
+ } else if (n !== 0 && n !== (Infinity) && n !== -(Infinity)) {
23
+ n = (n > 0 || -1) * Math.floor(Math.abs(n));
24
+ }
25
+ }
26
+
27
+ if (n >= len) {
28
+ return -1;
29
+ }
30
+
31
+ var k = n >= 0
32
+ ? n
33
+ : Math.max(len - Math.abs(n), 0);
34
+
35
+ for (; k < len; k++) {
36
+ if (k in t && t[k] === searchElement) {
37
+ return k;
38
+ }
39
+ }
40
+
41
+ return -1;
42
+ };
43
+ }
44
+
45
+ // Instantiate the object
46
+ var I18n = I18n || {};
47
+
48
+ // Set default locale to english
49
+ I18n.defaultLocale = "en";
50
+
51
+ // Set default handling of translation fallbacks to false
52
+ I18n.fallbacks = false;
53
+
54
+ // Set default separator
55
+ I18n.defaultSeparator = ".";
56
+
57
+ // Set current locale to null
58
+ I18n.locale = null;
59
+
60
+ // Set the placeholder format. Accepts `{{placeholder}}` and `%{placeholder}`.
61
+ I18n.PLACEHOLDER = /(?:\{\{|%\{)(.*?)(?:\}\}?)/gm;
62
+
63
+ I18n.fallbackRules = {
64
+ };
65
+
66
+ I18n.pluralizationRules = {
67
+ en: function (n) {
68
+ return n == 0 ? ["zero", "none", "other"] : n == 1 ? "one" : "other";
69
+ }
70
+ };
71
+
72
+ I18n.getFallbacks = function(locale) {
73
+ if (locale === I18n.defaultLocale) {
74
+ return [];
75
+ } else if (!I18n.fallbackRules[locale]) {
76
+ var rules = []
77
+ , components = locale.split("-");
78
+
79
+ for (var l = 1; l < components.length; l++) {
80
+ rules.push(components.slice(0, l).join("-"));
81
+ }
82
+
83
+ rules.push(I18n.defaultLocale);
84
+
85
+ I18n.fallbackRules[locale] = rules;
86
+ }
87
+
88
+ return I18n.fallbackRules[locale];
89
+ }
90
+
91
+ I18n.isValidNode = function(obj, node, undefined) {
92
+ return obj[node] !== null && obj[node] !== undefined;
93
+ };
94
+
95
+ I18n.lookup = function(scope, options) {
96
+ var options = options || {}
97
+ , lookupInitialScope = scope
98
+ , translations = this.prepareOptions(I18n.translations)
99
+ , locale = options.locale || I18n.currentLocale()
100
+ , messages = translations[locale] || {}
101
+ , options = this.prepareOptions(options)
102
+ , currentScope
103
+ ;
104
+
105
+ if (typeof(scope) == "object") {
106
+ scope = scope.join(this.defaultSeparator);
107
+ }
108
+
109
+ if (options.scope) {
110
+ scope = options.scope.toString() + this.defaultSeparator + scope;
111
+ }
112
+
113
+ scope = scope.split(this.defaultSeparator);
114
+
115
+ while (messages && scope.length > 0) {
116
+ currentScope = scope.shift();
117
+ messages = messages[currentScope];
118
+ }
119
+
120
+ if (!messages) {
121
+ if (I18n.fallbacks) {
122
+ var fallbacks = this.getFallbacks(locale);
123
+ for (var fallback = 0; fallback < fallbacks.length; fallbacks++) {
124
+ messages = I18n.lookup(lookupInitialScope, this.prepareOptions({locale: fallbacks[fallback]}, options));
125
+ if (messages) {
126
+ break;
127
+ }
128
+ }
129
+ }
130
+
131
+ if (!messages && this.isValidNode(options, "defaultValue")) {
132
+ messages = options.defaultValue;
133
+ }
134
+ }
135
+
136
+ return messages;
137
+ };
138
+
139
+ // Merge serveral hash options, checking if value is set before
140
+ // overwriting any value. The precedence is from left to right.
141
+ //
142
+ // I18n.prepareOptions({name: "John Doe"}, {name: "Mary Doe", role: "user"});
143
+ // #=> {name: "John Doe", role: "user"}
144
+ //
145
+ I18n.prepareOptions = function() {
146
+ var options = {}
147
+ , opts
148
+ , count = arguments.length
149
+ ;
150
+
151
+ for (var i = 0; i < count; i++) {
152
+ opts = arguments[i];
153
+
154
+ if (!opts) {
155
+ continue;
156
+ }
157
+
158
+ for (var key in opts) {
159
+ if (!this.isValidNode(options, key)) {
160
+ options[key] = opts[key];
161
+ }
162
+ }
163
+ }
164
+
165
+ return options;
166
+ };
167
+
168
+ I18n.interpolate = function(message, options) {
169
+ options = this.prepareOptions(options);
170
+ var matches = message.match(this.PLACEHOLDER)
171
+ , placeholder
172
+ , value
173
+ , name
174
+ ;
175
+
176
+ if (!matches) {
177
+ return message;
178
+ }
179
+
180
+ for (var i = 0; placeholder = matches[i]; i++) {
181
+ name = placeholder.replace(this.PLACEHOLDER, "$1");
182
+
183
+ value = options[name];
184
+
185
+ if (!this.isValidNode(options, name)) {
186
+ value = "[missing " + placeholder + " value]";
187
+ }
188
+
189
+ regex = new RegExp(placeholder.replace(/\{/gm, "\\{").replace(/\}/gm, "\\}"));
190
+ message = message.replace(regex, value);
191
+ }
192
+
193
+ return message;
194
+ };
195
+
196
+ I18n.translate = function(scope, options) {
197
+ options = this.prepareOptions(options);
198
+ var translation = this.lookup(scope, options);
199
+
200
+ try {
201
+ if (typeof(translation) == "object") {
202
+ if (typeof(options.count) == "number") {
203
+ return this.pluralize(options.count, scope, options);
204
+ } else {
205
+ return translation;
206
+ }
207
+ } else {
208
+ return this.interpolate(translation, options);
209
+ }
210
+ } catch(err) {
211
+ return this.missingTranslation(scope);
212
+ }
213
+ };
214
+
215
+ I18n.localize = function(scope, value) {
216
+ switch (scope) {
217
+ case "currency":
218
+ return this.toCurrency(value);
219
+ case "number":
220
+ scope = this.lookup("number.format");
221
+ return this.toNumber(value, scope);
222
+ case "percentage":
223
+ return this.toPercentage(value);
224
+ default:
225
+ if (scope.match(/^(date|time)/)) {
226
+ return this.toTime(scope, value);
227
+ } else {
228
+ return value.toString();
229
+ }
230
+ }
231
+ };
232
+
233
+ I18n.parseDate = function(date) {
234
+ var matches, convertedDate;
235
+
236
+ // we have a date, so just return it.
237
+ if (typeof(date) == "object") {
238
+ return date;
239
+ };
240
+
241
+ // it matches the following formats:
242
+ // yyyy-mm-dd
243
+ // yyyy-mm-dd[ T]hh:mm::ss
244
+ // yyyy-mm-dd[ T]hh:mm::ss
245
+ // yyyy-mm-dd[ T]hh:mm::ssZ
246
+ // yyyy-mm-dd[ T]hh:mm::ss+0000
247
+ //
248
+ matches = date.toString().match(/(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}):(\d{2}))?(Z|\+0000)?/);
249
+
250
+ if (matches) {
251
+ for (var i = 1; i <= 6; i++) {
252
+ matches[i] = parseInt(matches[i], 10) || 0;
253
+ }
254
+
255
+ // month starts on 0
256
+ matches[2] -= 1;
257
+
258
+ if (matches[7]) {
259
+ convertedDate = new Date(Date.UTC(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6]));
260
+ } else {
261
+ convertedDate = new Date(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6]);
262
+ }
263
+ } else if (typeof(date) == "number") {
264
+ // UNIX timestamp
265
+ convertedDate = new Date();
266
+ convertedDate.setTime(date);
267
+ } else if (date.match(/\d+ \d+:\d+:\d+ [+-]\d+ \d+/)) {
268
+ // a valid javascript format with timezone info
269
+ convertedDate = new Date();
270
+ convertedDate.setTime(Date.parse(date))
271
+ } else {
272
+ // an arbitrary javascript string
273
+ convertedDate = new Date();
274
+ convertedDate.setTime(Date.parse(date));
275
+ }
276
+
277
+ return convertedDate;
278
+ };
279
+
280
+ I18n.toTime = function(scope, d) {
281
+ var date = this.parseDate(d)
282
+ , format = this.lookup(scope)
283
+ ;
284
+
285
+ if (date.toString().match(/invalid/i)) {
286
+ return date.toString();
287
+ }
288
+
289
+ if (!format) {
290
+ return date.toString();
291
+ }
292
+
293
+ return this.strftime(date, format);
294
+ };
295
+
296
+ I18n.strftime = function(date, format) {
297
+ var options = this.lookup("date");
298
+
299
+ if (!options) {
300
+ return date.toString();
301
+ }
302
+
303
+ options.meridian = options.meridian || ["AM", "PM"];
304
+
305
+ var weekDay = date.getDay()
306
+ , day = date.getDate()
307
+ , year = date.getFullYear()
308
+ , month = date.getMonth() + 1
309
+ , hour = date.getHours()
310
+ , hour12 = hour
311
+ , meridian = hour > 11 ? 1 : 0
312
+ , secs = date.getSeconds()
313
+ , mins = date.getMinutes()
314
+ , offset = date.getTimezoneOffset()
315
+ , absOffsetHours = Math.floor(Math.abs(offset / 60))
316
+ , absOffsetMinutes = Math.abs(offset) - (absOffsetHours * 60)
317
+ , timezoneoffset = (offset > 0 ? "-" : "+") + (absOffsetHours.toString().length < 2 ? "0" + absOffsetHours : absOffsetHours) + (absOffsetMinutes.toString().length < 2 ? "0" + absOffsetMinutes : absOffsetMinutes)
318
+ ;
319
+
320
+ if (hour12 > 12) {
321
+ hour12 = hour12 - 12;
322
+ } else if (hour12 === 0) {
323
+ hour12 = 12;
324
+ }
325
+
326
+ var padding = function(n) {
327
+ var s = "0" + n.toString();
328
+ return s.substr(s.length - 2);
329
+ };
330
+
331
+ var f = format;
332
+ f = f.replace("%a", options.abbr_day_names[weekDay]);
333
+ f = f.replace("%A", options.day_names[weekDay]);
334
+ f = f.replace("%b", options.abbr_month_names[month]);
335
+ f = f.replace("%B", options.month_names[month]);
336
+ f = f.replace("%d", padding(day));
337
+ f = f.replace("%e", day);
338
+ f = f.replace("%-d", day);
339
+ f = f.replace("%H", padding(hour));
340
+ f = f.replace("%-H", hour);
341
+ f = f.replace("%I", padding(hour12));
342
+ f = f.replace("%-I", hour12);
343
+ f = f.replace("%m", padding(month));
344
+ f = f.replace("%-m", month);
345
+ f = f.replace("%M", padding(mins));
346
+ f = f.replace("%-M", mins);
347
+ f = f.replace("%p", options.meridian[meridian]);
348
+ f = f.replace("%S", padding(secs));
349
+ f = f.replace("%-S", secs);
350
+ f = f.replace("%w", weekDay);
351
+ f = f.replace("%y", padding(year));
352
+ f = f.replace("%-y", padding(year).replace(/^0+/, ""));
353
+ f = f.replace("%Y", year);
354
+ f = f.replace("%z", timezoneoffset);
355
+
356
+ return f;
357
+ };
358
+
359
+ I18n.toNumber = function(number, options) {
360
+ options = this.prepareOptions(
361
+ options,
362
+ this.lookup("number.format"),
363
+ {precision: 3, separator: ".", delimiter: ",", strip_insignificant_zeros: false}
364
+ );
365
+
366
+ var negative = number < 0
367
+ , string = Math.abs(number).toFixed(options.precision).toString()
368
+ , parts = string.split(".")
369
+ , precision
370
+ , buffer = []
371
+ , formattedNumber
372
+ ;
373
+
374
+ number = parts[0];
375
+ precision = parts[1];
376
+
377
+ while (number.length > 0) {
378
+ buffer.unshift(number.substr(Math.max(0, number.length - 3), 3));
379
+ number = number.substr(0, number.length -3);
380
+ }
381
+
382
+ formattedNumber = buffer.join(options.delimiter);
383
+
384
+ if (options.precision > 0) {
385
+ formattedNumber += options.separator + parts[1];
386
+ }
387
+
388
+ if (negative) {
389
+ formattedNumber = "-" + formattedNumber;
390
+ }
391
+
392
+ if (options.strip_insignificant_zeros) {
393
+ var regex = {
394
+ separator: new RegExp(options.separator.replace(/\./, "\\.") + "$")
395
+ , zeros: /0+$/
396
+ };
397
+
398
+ formattedNumber = formattedNumber
399
+ .replace(regex.zeros, "")
400
+ .replace(regex.separator, "")
401
+ ;
402
+ }
403
+
404
+ return formattedNumber;
405
+ };
406
+
407
+ I18n.toCurrency = function(number, options) {
408
+ options = this.prepareOptions(
409
+ options,
410
+ this.lookup("number.currency.format"),
411
+ this.lookup("number.format"),
412
+ {unit: "$", precision: 2, format: "%u%n", delimiter: ",", separator: "."}
413
+ );
414
+
415
+ number = this.toNumber(number, options);
416
+ number = options.format
417
+ .replace("%u", options.unit)
418
+ .replace("%n", number)
419
+ ;
420
+
421
+ return number;
422
+ };
423
+
424
+ I18n.toHumanSize = function(number, options) {
425
+ var kb = 1024
426
+ , size = number
427
+ , iterations = 0
428
+ , unit
429
+ , precision
430
+ ;
431
+
432
+ while (size >= kb && iterations < 4) {
433
+ size = size / kb;
434
+ iterations += 1;
435
+ }
436
+
437
+ if (iterations === 0) {
438
+ unit = this.t("number.human.storage_units.units.byte", {count: size});
439
+ precision = 0;
440
+ } else {
441
+ unit = this.t("number.human.storage_units.units." + [null, "kb", "mb", "gb", "tb"][iterations]);
442
+ precision = (size - Math.floor(size) === 0) ? 0 : 1;
443
+ }
444
+
445
+ options = this.prepareOptions(
446
+ options,
447
+ {precision: precision, format: "%n%u", delimiter: ""}
448
+ );
449
+
450
+ number = this.toNumber(size, options);
451
+ number = options.format
452
+ .replace("%u", unit)
453
+ .replace("%n", number)
454
+ ;
455
+
456
+ return number;
457
+ };
458
+
459
+ I18n.toPercentage = function(number, options) {
460
+ options = this.prepareOptions(
461
+ options,
462
+ this.lookup("number.percentage.format"),
463
+ this.lookup("number.format"),
464
+ {precision: 3, separator: ".", delimiter: ""}
465
+ );
466
+
467
+ number = this.toNumber(number, options);
468
+ return number + "%";
469
+ };
470
+
471
+ I18n.pluralizer = function(locale) {
472
+ pluralizer = this.pluralizationRules[locale];
473
+ if (pluralizer !== undefined) return pluralizer;
474
+ return this.pluralizationRules["en"];
475
+ };
476
+
477
+ I18n.findAndTranslateValidNode = function(keys, translation) {
478
+ for (i = 0; i < keys.length; i++) {
479
+ key = keys[i];
480
+ if (this.isValidNode(translation, key)) return translation[key];
481
+ }
482
+ return null;
483
+ };
484
+
485
+ I18n.pluralize = function(count, scope, options) {
486
+ var translation;
487
+
488
+ try {
489
+ translation = this.lookup(scope, options);
490
+ } catch (error) {}
491
+
492
+ if (!translation) {
493
+ return this.missingTranslation(scope);
494
+ }
495
+
496
+ var message;
497
+ options = this.prepareOptions(options);
498
+ options.count = count.toString();
499
+
500
+ pluralizer = this.pluralizer(this.currentLocale());
501
+ key = pluralizer(Math.abs(count));
502
+ keys = ((typeof key == "object") && (key instanceof Array)) ? key : [key];
503
+
504
+ message = this.findAndTranslateValidNode(keys, translation);
505
+ if (message == null) message = this.missingTranslation(scope, keys[0]);
506
+
507
+ return this.interpolate(message, options);
508
+ };
509
+
510
+ I18n.missingTranslation = function() {
511
+ var message = '[missing "' + this.currentLocale()
512
+ , count = arguments.length
513
+ ;
514
+
515
+ for (var i = 0; i < count; i++) {
516
+ message += "." + arguments[i];
517
+ }
518
+
519
+ message += '" translation]';
520
+
521
+ return message;
522
+ };
523
+
524
+ I18n.currentLocale = function() {
525
+ return (I18n.locale || I18n.defaultLocale);
526
+ };
527
+
528
+ // shortcuts
529
+ I18n.t = I18n.translate;
530
+ I18n.l = I18n.localize;
531
+ I18n.p = I18n.pluralize;