socialcast-i18n-js 4.0.0.rc1 → 4.0.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,531 +1,661 @@
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";
1
+ //= require underscore
5
2
 
6
- if (this === void 0 || this === null) {
7
- throw new TypeError();
3
+ // I18n.js
4
+ // =======
5
+ //
6
+ // This small library provides the Rails I18n API on the Javascript.
7
+ //
8
+ var I18n = {};
9
+
10
+ ;(function(I18n){
11
+ "use strict";
12
+
13
+ I18n.reset = function() {
14
+ // Set default locale. This locale will be used when fallback is enabled and
15
+ // the translation doesn't exist in a particular locale.
16
+ this.defaultLocale = "en";
17
+
18
+ // Set the current locale to `en`.
19
+ this.locale = "en";
20
+
21
+ // Set the translation key separator.
22
+ this.defaultSeparator = ".";
23
+
24
+ // Set the placeholder format. Accepts `{{placeholder}}` and `%{placeholder}`.
25
+ this.placeholder = /(?:\{\{|%\{)(.*?)(?:\}\}?)/gm;
26
+
27
+ // Set if engine should fallback to the default locale when a translation
28
+ // is missing.
29
+ this.fallbacks = false;
30
+
31
+ // Set the default translation object.
32
+ this.translations = {};
33
+ };
34
+
35
+ // Return a list of all locales that must be tried before returning the
36
+ // missing translation message. By default, this will consider the inline option,
37
+ // current locale and fallback locale.
38
+ //
39
+ // I18n.locales.get("de-DE");
40
+ // // ["de-DE", "de", "en"]
41
+ //
42
+ // You can define custom rules for any locale. Just make sure you return a array
43
+ // containing all locales.
44
+ //
45
+ // // Default the Wookie locale to English.
46
+ // I18n.locales["wk"] = function(locale) {
47
+ // return ["en"];
48
+ // };
49
+ //
50
+ I18n.locales = {};
51
+
52
+ // Retrieve locales based on inline locale, current locale or default to
53
+ // I18n's detection.
54
+ I18n.locales.get = function(locale) {
55
+ var result = this[locale] || this[I18n.locale] || this["default"];
56
+
57
+ if (_.isFunction(result)) {
58
+ result = result(locale);
8
59
  }
9
60
 
10
- var t = Object(this);
11
- var len = t.length >>> 0;
61
+ if (!_.isArray(result)) {
62
+ result = [result];
63
+ }
64
+
65
+ return result;
66
+ };
12
67
 
13
- if (len === 0) {
14
- return -1;
68
+ // The default locale list.
69
+ I18n.locales["default"] = function(locale) {
70
+ var locales = []
71
+ , list = []
72
+ , countryCode
73
+ , count
74
+ ;
75
+
76
+ // Handle the inline locale option that can be provided to
77
+ // the `I18n.t` options.
78
+ if (locale) {
79
+ locales.push(locale);
15
80
  }
16
81
 
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
- }
82
+ // Add the current locale to the list.
83
+ if (!locale && I18n.locale) {
84
+ locales.push(I18n.locale);
25
85
  }
26
86
 
27
- if (n >= len) {
28
- return -1;
87
+ // Add the default locale if fallback strategy is enabled.
88
+ if (I18n.fallbacks && I18n.defaultLocale) {
89
+ locales.push(I18n.defaultLocale);
29
90
  }
30
91
 
31
- var k = n >= 0
32
- ? n
33
- : Math.max(len - Math.abs(n), 0);
92
+ // Compute each locale with its country code.
93
+ // So this will return an array containing both
94
+ // `de-DE` and `de` locales.
95
+ locales.forEach(function(locale){
96
+ countryCode = locale.split("-")[0];
97
+
98
+ if (!_.include(list, locale)) {
99
+ list.push(locale);
100
+ }
34
101
 
35
- for (; k < len; k++) {
36
- if (k in t && t[k] === searchElement) {
37
- return k;
102
+ if (I18n.fallbacks && countryCode && countryCode !== locale && !_.include(list, countryCode)) {
103
+ list.push(countryCode);
38
104
  }
105
+ });
106
+
107
+ // No locales set? English it is.
108
+ if (!locales.length) {
109
+ locales.push("en");
39
110
  }
40
111
 
41
- return -1;
112
+ return list;
42
113
  };
43
- }
44
114
 
45
- // Instantiate the object
46
- var I18n = I18n || {};
115
+ //
116
+ //
117
+ I18n.pluralization = {};
47
118
 
48
- // Set default locale to english
49
- I18n.defaultLocale = "en";
119
+ // Return the pluralizer for a specific locale.
120
+ // If no specify locale is found, then I18n's default will be used.
121
+ I18n.pluralization.get = function(locale) {
122
+ return this[locale] || this[I18n.locale] || this["default"];
123
+ };
50
124
 
51
- // Set default handling of translation fallbacks to false
52
- I18n.fallbacks = false;
125
+ // The default pluralizer rule.
126
+ // It detects the `zero`, `one`, and `other` scopes.
127
+ I18n.pluralization["default"] = function(count) {
128
+ switch (count) {
129
+ case 0: return ["zero", "other"];
130
+ case 1: return ["one"];
131
+ default: return ["other"];
132
+ }
133
+ };
53
134
 
54
- // Set default separator
55
- I18n.defaultSeparator = ".";
135
+ // Reset all default attributes. This is specially useful
136
+ // while running tests.
137
+ I18n.reset();
56
138
 
57
- // Set current locale to null
58
- I18n.locale = null;
139
+ // Return current locale. If no locale has been set, then
140
+ // the current locale will be the default locale.
141
+ I18n.currentLocale = function() {
142
+ return this.locale || this.defaultLocale;
143
+ };
144
+
145
+ // Check if value is different than undefined and null;
146
+ I18n.isSet = function(value) {
147
+ return !_.isUndefined(value) && !_.isNull(value);
148
+ };
59
149
 
60
- // Set the placeholder format. Accepts `{{placeholder}}` and `%{placeholder}`.
61
- I18n.PLACEHOLDER = /(?:\{\{|%\{)(.*?)(?:\}\}?)/gm;
150
+ // Find and process the translation using the provided scope and options.
151
+ // This is used internally by some functions and should not be used as an
152
+ // public API.
153
+ I18n.lookup = function(scope, options) {
154
+ options = this.prepareOptions(options);
155
+
156
+ var locales = this.locales.get(options.locale)
157
+ , requestedLocale = locales[0]
158
+ , locale
159
+ , scopes
160
+ , translations
161
+ ;
162
+
163
+ while (locales.length) {
164
+ locale = locales.shift();
165
+ scopes = scope.split(this.defaultSeparator);
166
+ translations = this.translations[locale];
167
+
168
+ if (_.isUndefined(translations)) {
169
+ continue;
170
+ }
62
171
 
63
- I18n.fallbackRules = {
64
- };
172
+ while (scopes.length) {
173
+ translations = translations[scopes.shift()];
65
174
 
66
- I18n.pluralizationRules = {
67
- en: function (n) {
68
- return n == 0 ? ["zero", "none", "other"] : n == 1 ? "one" : "other";
69
- }
70
- };
175
+ if (_.isUndefined(translations)) {
176
+ break;
177
+ }
178
+ }
71
179
 
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("-");
180
+ if (!_.isUndefined(translations)) {
181
+ return translations;
182
+ }
183
+ }
78
184
 
79
- for (var l = 1; l < components.length; l++) {
80
- rules.push(components.slice(0, l).join("-"));
185
+ if (this.isSet(options.defaultValue)) {
186
+ return options.defaultValue;
81
187
  }
188
+ };
82
189
 
83
- rules.push(I18n.defaultLocale);
190
+ // Merge serveral hash options, checking if value is set before
191
+ // overwriting any value. The precedence is from left to right.
192
+ //
193
+ // I18n.prepareOptions({name: "John Doe"}, {name: "Mary Doe", role: "user"});
194
+ // #=> {name: "John Doe", role: "user"}
195
+ //
196
+ I18n.prepareOptions = function() {
197
+ var args = Array.prototype.slice.call(arguments)
198
+ , options = {}
199
+ ;
84
200
 
85
- I18n.fallbackRules[locale] = rules;
86
- }
201
+ for (var i = 0, count = args.length; i < count; i++) {
202
+ var o = args.shift();
87
203
 
88
- return I18n.fallbackRules[locale];
89
- }
204
+ if (!_.isObject(o)) {
205
+ continue;
206
+ }
90
207
 
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;
208
+ for (var attr in o) {
209
+ if (!_.has(o, attr)) {
210
+ continue;
127
211
  }
212
+
213
+ if (this.isSet(options[attr])) {
214
+ continue;
215
+ }
216
+
217
+ options[attr] = o[attr];
128
218
  }
129
219
  }
130
220
 
131
- if (!messages && this.isValidNode(options, "defaultValue")) {
132
- messages = options.defaultValue;
221
+ return options;
222
+ };
223
+
224
+ // Translate the given scope with the provided options.
225
+ I18n.translate = function(scope, options) {
226
+ options = this.prepareOptions(options);
227
+ var translation = this.lookup(scope, options);
228
+
229
+ if (_.isUndefined(translation)) {
230
+ return this.missingTranslation(scope);
133
231
  }
134
- }
135
232
 
136
- return messages;
137
- };
233
+ if (_.isString(translation)) {
234
+ translation = this.interpolate(translation, options);
235
+ } else if (_.isObject(translation) && this.isSet(options.count)) {
236
+ translation = this.pluralize(options.count, translation, options);
237
+ }
138
238
 
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
- ;
239
+ return translation;
240
+ };
150
241
 
151
- for (var i = 0; i < count; i++) {
152
- opts = arguments[i];
242
+ // This function interpolates the all variables in the given message.
243
+ I18n.interpolate = function(message, options) {
244
+ options = this.prepareOptions(options);
245
+ var matches = message.match(this.placeholder)
246
+ , placeholder
247
+ , value
248
+ , name
249
+ , regex
250
+ ;
153
251
 
154
- if (!opts) {
155
- continue;
252
+ if (!matches) {
253
+ return message;
156
254
  }
157
255
 
158
- for (var key in opts) {
159
- if (!this.isValidNode(options, key)) {
160
- options[key] = opts[key];
256
+ while (matches.length) {
257
+ placeholder = matches.shift();
258
+ name = placeholder.replace(this.placeholder, "$1");
259
+ value = options[name];
260
+
261
+ if (!this.isSet(options[name])) {
262
+ value = "[missing " + placeholder + " value]";
161
263
  }
264
+
265
+ regex = new RegExp(placeholder.replace(/\{/gm, "\\{").replace(/\}/gm, "\\}"));
266
+ message = message.replace(regex, value);
162
267
  }
163
- }
164
268
 
165
- return options;
166
- };
269
+ return message;
270
+ };
271
+
272
+ // Pluralize the given scope using the `count` value.
273
+ // The pluralized translation may have other placeholders,
274
+ // which will be retrieved from `options`.
275
+ I18n.pluralize = function(count, scope, options) {
276
+ options = this.prepareOptions(options);
277
+ var translations, pluralizer, keys, key, message;
167
278
 
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
- ;
279
+ if (_.isObject(scope)) {
280
+ translations = scope;
281
+ } else {
282
+ translations = this.lookup(scope, options);
283
+ }
284
+
285
+ if (_.isUndefined(translations)) {
286
+ return this.missingTranslation(scope);
287
+ }
288
+
289
+ pluralizer = this.pluralization.get(options.locale);
290
+ keys = pluralizer(Math.abs(count));
291
+
292
+ while (keys.length) {
293
+ key = keys.shift();
294
+
295
+ if (this.isSet(translations[key])) {
296
+ message = translations[key];
297
+ break;
298
+ }
299
+ }
300
+
301
+ options.count = String(count);
302
+ return this.interpolate(message, options);
303
+ };
304
+
305
+ // Return a missing translation message for the given parameters.
306
+ I18n.missingTranslation = function(scope) {
307
+ var message = '[missing "' + this.currentLocale();
308
+
309
+ for (var i = 0; i < arguments.length; i++) {
310
+ message += "." + arguments[i];
311
+ }
312
+
313
+ message += '" translation]';
175
314
 
176
- if (!matches) {
177
315
  return message;
178
- }
316
+ };
317
+
318
+ // Format number using localization rules.
319
+ // The options will be retrieved from the `number.format` scope.
320
+ // If this isn't present, then the following options will be used:
321
+ //
322
+ // - `precision`: `3`
323
+ // - `separator`: `"."`
324
+ // - `delimiter`: `","`
325
+ // - `strip_insignificant_zeros`: `false`
326
+ //
327
+ // You can also override these options by providing the `options` argument.
328
+ //
329
+ I18n.toNumber = function(number, options) {
330
+ options = this.prepareOptions(
331
+ options,
332
+ this.lookup("number.format"),
333
+ {precision: 3, separator: ".", delimiter: ",", strip_insignificant_zeros: false}
334
+ );
335
+
336
+ var negative = number < 0
337
+ , string = Math.abs(number).toFixed(options.precision).toString()
338
+ , parts = string.split(".")
339
+ , precision
340
+ , buffer = []
341
+ , formattedNumber
342
+ ;
179
343
 
180
- for (var i = 0; placeholder = matches[i]; i++) {
181
- name = placeholder.replace(this.PLACEHOLDER, "$1");
344
+ number = parts[0];
345
+ precision = parts[1];
182
346
 
183
- value = options[name];
347
+ while (number.length > 0) {
348
+ buffer.unshift(number.substr(Math.max(0, number.length - 3), 3));
349
+ number = number.substr(0, number.length -3);
350
+ }
351
+
352
+ formattedNumber = buffer.join(options.delimiter);
184
353
 
185
- if (!this.isValidNode(options, name)) {
186
- value = "[missing " + placeholder + " value]";
354
+ if (options.precision > 0) {
355
+ formattedNumber += options.separator + parts[1];
187
356
  }
188
357
 
189
- regex = new RegExp(placeholder.replace(/\{/gm, "\\{").replace(/\}/gm, "\\}"));
190
- message = message.replace(regex, value);
191
- }
358
+ if (negative) {
359
+ formattedNumber = "-" + formattedNumber;
360
+ }
361
+
362
+ if (options.strip_insignificant_zeros) {
363
+ var regex = {
364
+ separator: new RegExp(options.separator.replace(/\./, "\\.") + "$")
365
+ , zeros: /0+$/
366
+ };
367
+
368
+ formattedNumber = formattedNumber
369
+ .replace(regex.zeros, "")
370
+ .replace(regex.separator, "")
371
+ ;
372
+ }
192
373
 
193
- return message;
194
- };
374
+ return formattedNumber;
375
+ };
195
376
 
196
- I18n.translate = function(scope, options) {
197
- options = this.prepareOptions(options);
198
- var translation = this.lookup(scope, options);
377
+ // Format currency with localization rules.
378
+ // The options will be retrieved from the `number.currency.format` and
379
+ // `number.format` scopes, in that order.
380
+ //
381
+ // Any missing option will be retrieved from the `I18n.toNumber` defaults and
382
+ // the following options:
383
+ //
384
+ // - `unit`: `"$"`
385
+ // - `precision`: `2`
386
+ // - `format`: `"%u%n"`
387
+ // - `delimiter`: `","`
388
+ // - `separator`: `"."`
389
+ //
390
+ // You can also override these options by providing the `options` argument.
391
+ //
392
+ I18n.toCurrency = function(number, options) {
393
+ options = this.prepareOptions(
394
+ options,
395
+ this.lookup("number.currency.format"),
396
+ this.lookup("number.format"),
397
+ {unit: "$", precision: 2, format: "%u%n", delimiter: ",", separator: "."}
398
+ );
399
+
400
+ number = this.toNumber(number, options);
401
+ number = options.format
402
+ .replace("%u", options.unit)
403
+ .replace("%n", number)
404
+ ;
405
+
406
+ return number;
407
+ };
408
+
409
+ // Localize several values.
410
+ // You can provide the following scopes: `currency`, `number`, or `percentage`.
411
+ // If you provide a scope that matches the `/^(date|time)/` regular expression
412
+ // then the `value` will be converted by using the `I18n.toTime` function.
413
+ //
414
+ // It will default to the value's `toString` function.
415
+ //
416
+ I18n.localize = function(scope, value) {
417
+ switch (scope) {
418
+ case "currency":
419
+ return this.toCurrency(value);
420
+ case "number":
421
+ scope = this.lookup("number.format");
422
+ return this.toNumber(value, scope);
423
+ case "percentage":
424
+ return this.toPercentage(value);
425
+ default:
426
+ if (scope.match(/^(date|time)/)) {
427
+ return this.toTime(scope, value);
428
+ } else {
429
+ return value.toString();
430
+ }
431
+ }
432
+ };
433
+
434
+ // Parse a given `date` string into a JavaScript Date object.
435
+ // This function is time zone aware.
436
+ //
437
+ // The following string formats are recognized:
438
+ //
439
+ // yyyy-mm-dd
440
+ // yyyy-mm-dd[ T]hh:mm::ss
441
+ // yyyy-mm-dd[ T]hh:mm::ss
442
+ // yyyy-mm-dd[ T]hh:mm::ssZ
443
+ // yyyy-mm-dd[ T]hh:mm::ss+0000
444
+ //
445
+ I18n.parseDate = function(date) {
446
+ var matches, convertedDate;
447
+
448
+ // we have a date, so just return it.
449
+ if (_.isDate(date)) {
450
+ return date;
451
+ };
452
+
453
+ matches = date.toString().match(/(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}):(\d{2}))?(Z|\+0000)?/);
454
+
455
+ if (matches) {
456
+ for (var i = 1; i <= 6; i++) {
457
+ matches[i] = parseInt(matches[i], 10) || 0;
458
+ }
199
459
 
200
- try {
201
- if (typeof(translation) == "object") {
202
- if (typeof(options.count) == "number") {
203
- return this.pluralize(options.count, scope, options);
460
+ // month starts on 0
461
+ matches[2] -= 1;
462
+
463
+ if (matches[7]) {
464
+ convertedDate = new Date(Date.UTC(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6]));
204
465
  } else {
205
- return translation;
466
+ convertedDate = new Date(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6]);
206
467
  }
468
+ } else if (typeof(date) == "number") {
469
+ // UNIX timestamp
470
+ convertedDate = new Date();
471
+ convertedDate.setTime(date);
472
+ } else if (date.match(/\d+ \d+:\d+:\d+ [+-]\d+ \d+/)) {
473
+ // a valid javascript format with timezone info
474
+ convertedDate = new Date();
475
+ convertedDate.setTime(Date.parse(date))
207
476
  } else {
208
- return this.interpolate(translation, options);
477
+ // an arbitrary javascript string
478
+ convertedDate = new Date();
479
+ convertedDate.setTime(Date.parse(date));
209
480
  }
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();
481
+
482
+ return convertedDate;
483
+ };
484
+
485
+ // Formats time according to the directives in the given format string.
486
+ // The directives begins with a percent (%) character. Any text not listed as a
487
+ // directive will be passed through to the output string.
488
+ //
489
+ // The accepted formats are:
490
+ //
491
+ // %a - The abbreviated weekday name (Sun)
492
+ // %A - The full weekday name (Sunday)
493
+ // %b - The abbreviated month name (Jan)
494
+ // %B - The full month name (January)
495
+ // %c - The preferred local date and time representation
496
+ // %d - Day of the month (01..31)
497
+ // %-d - Day of the month (1..31)
498
+ // %H - Hour of the day, 24-hour clock (00..23)
499
+ // %-H - Hour of the day, 24-hour clock (0..23)
500
+ // %I - Hour of the day, 12-hour clock (01..12)
501
+ // %-I - Hour of the day, 12-hour clock (1..12)
502
+ // %m - Month of the year (01..12)
503
+ // %-m - Month of the year (1..12)
504
+ // %M - Minute of the hour (00..59)
505
+ // %-M - Minute of the hour (0..59)
506
+ // %p - Meridian indicator (AM or PM)
507
+ // %S - Second of the minute (00..60)
508
+ // %-S - Second of the minute (0..60)
509
+ // %w - Day of the week (Sunday is 0, 0..6)
510
+ // %y - Year without a century (00..99)
511
+ // %-y - Year without a century (0..99)
512
+ // %Y - Year with century
513
+ // %z - Timezone offset (+0545)
514
+ //
515
+ I18n.strftime = function(date, format) {
516
+ var options = this.lookup("date");
517
+
518
+ if (!options) {
519
+ options = {
520
+ day_names: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
521
+ , abbr_day_names: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
522
+ , month_names: [null, "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
523
+ , abbr_month_names: [null, "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
229
524
  }
230
- }
231
- };
525
+ }
232
526
 
233
- I18n.parseDate = function(date) {
234
- var matches, convertedDate;
527
+ if (!options.meridian) {
528
+ options.meridian = ["AM", "PM"];
529
+ }
235
530
 
236
- // we have a date, so just return it.
237
- if (typeof(date) == "object") {
238
- return date;
531
+ var weekDay = date.getDay()
532
+ , day = date.getDate()
533
+ , year = date.getFullYear()
534
+ , month = date.getMonth() + 1
535
+ , hour = date.getHours()
536
+ , hour12 = hour
537
+ , meridian = hour > 11 ? 1 : 0
538
+ , secs = date.getSeconds()
539
+ , mins = date.getMinutes()
540
+ , offset = date.getTimezoneOffset()
541
+ , absOffsetHours = Math.floor(Math.abs(offset / 60))
542
+ , absOffsetMinutes = Math.abs(offset) - (absOffsetHours * 60)
543
+ , timezoneoffset = (offset > 0 ? "-" : "+") + (absOffsetHours.toString().length < 2 ? "0" + absOffsetHours : absOffsetHours) + (absOffsetMinutes.toString().length < 2 ? "0" + absOffsetMinutes : absOffsetMinutes)
544
+ ;
545
+
546
+ if (hour12 > 12) {
547
+ hour12 = hour12 - 12;
548
+ } else if (hour12 === 0) {
549
+ hour12 = 12;
550
+ }
551
+
552
+ var padding = function(n) {
553
+ var s = "0" + n.toString();
554
+ return s.substr(s.length - 2);
555
+ };
556
+
557
+ format = format.replace("%a", options.abbr_day_names[weekDay]);
558
+ format = format.replace("%A", options.day_names[weekDay]);
559
+ format = format.replace("%b", options.abbr_month_names[month]);
560
+ format = format.replace("%B", options.month_names[month]);
561
+ format = format.replace("%d", padding(day));
562
+ format = format.replace("%e", day);
563
+ format = format.replace("%-d", day);
564
+ format = format.replace("%H", padding(hour));
565
+ format = format.replace("%-H", hour);
566
+ format = format.replace("%I", padding(hour12));
567
+ format = format.replace("%-I", hour12);
568
+ format = format.replace("%m", padding(month));
569
+ format = format.replace("%-m", month);
570
+ format = format.replace("%M", padding(mins));
571
+ format = format.replace("%-M", mins);
572
+ format = format.replace("%p", options.meridian[meridian]);
573
+ format = format.replace("%S", padding(secs));
574
+ format = format.replace("%-S", secs);
575
+ format = format.replace("%w", weekDay);
576
+ format = format.replace("%y", padding(year));
577
+ format = format.replace("%-y", padding(year).replace(/^0+/, ""));
578
+ format = format.replace("%Y", year);
579
+ format = format.replace("%z", timezoneoffset);
580
+
581
+ return format;
239
582
  };
240
583
 
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
584
  //
248
- matches = date.toString().match(/(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}):(\d{2}))?(Z|\+0000)?/);
585
+ //
586
+ I18n.toTime = function(scope, d) {
587
+ var date = this.parseDate(d)
588
+ , format = this.lookup(scope)
589
+ ;
590
+
591
+ if (date.toString().match(/invalid/i)) {
592
+ return date.toString();
593
+ }
249
594
 
250
- if (matches) {
251
- for (var i = 1; i <= 6; i++) {
252
- matches[i] = parseInt(matches[i], 10) || 0;
595
+ if (!format) {
596
+ return date.toString();
253
597
  }
254
598
 
255
- // month starts on 0
256
- matches[2] -= 1;
599
+ return this.strftime(date, format);
600
+ };
601
+
602
+ //
603
+ //
604
+ I18n.toPercentage = function(number, options) {
605
+ options = this.prepareOptions(
606
+ options,
607
+ this.lookup("number.percentage.format"),
608
+ this.lookup("number.format"),
609
+ {precision: 3, separator: ".", delimiter: ""}
610
+ );
611
+
612
+ number = this.toNumber(number, options);
613
+ return number + "%";
614
+ };
615
+
616
+ //
617
+ //
618
+ I18n.toHumanSize = function(number, options) {
619
+ var kb = 1024
620
+ , size = number
621
+ , iterations = 0
622
+ , unit
623
+ , precision
624
+ ;
257
625
 
258
- if (matches[7]) {
259
- convertedDate = new Date(Date.UTC(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6]));
626
+ while (size >= kb && iterations < 4) {
627
+ size = size / kb;
628
+ iterations += 1;
629
+ }
630
+
631
+ if (iterations === 0) {
632
+ unit = this.t("number.human.storage_units.units.byte", {count: size});
633
+ precision = 0;
260
634
  } else {
261
- convertedDate = new Date(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6]);
635
+ unit = this.t("number.human.storage_units.units." + [null, "kb", "mb", "gb", "tb"][iterations]);
636
+ precision = (size - Math.floor(size) === 0) ? 0 : 1;
262
637
  }
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
638
 
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
- };
639
+ options = this.prepareOptions(
640
+ options,
641
+ {precision: precision, format: "%n%u", delimiter: ""}
642
+ );
397
643
 
398
- formattedNumber = formattedNumber
399
- .replace(regex.zeros, "")
400
- .replace(regex.separator, "")
644
+ number = this.toNumber(size, options);
645
+ number = options.format
646
+ .replace("%u", unit)
647
+ .replace("%n", number)
401
648
  ;
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;
649
+
650
+ return number;
651
+ };
652
+
653
+ // Set aliases, so we can save some typing.
654
+ I18n.t = I18n.translate;
655
+ I18n.l = I18n.localize;
656
+ I18n.p = I18n.pluralize;
657
+ })(I18n);
658
+
659
+ if(!_.isUndefined(exports)){
660
+ module.exports = I18n;
661
+ }