i18n-js 2.1.2 → 3.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.editorconfig +24 -0
- data/.gitignore +5 -4
- data/.npmignore +27 -0
- data/.travis.yml +37 -0
- data/Appraisals +20 -0
- data/CHANGELOG.md +354 -0
- data/Gemfile +1 -1
- data/README.md +872 -0
- data/Rakefile +19 -7
- data/app/assets/javascripts/i18n/filtered.js.erb +23 -0
- data/app/assets/javascripts/i18n/shims.js +208 -0
- data/app/assets/javascripts/i18n/translations.js +3 -0
- data/app/assets/javascripts/i18n.js +1077 -0
- data/gemfiles/i18n_0_6.gemfile +7 -0
- data/gemfiles/i18n_0_7.gemfile +7 -0
- data/gemfiles/i18n_0_8.gemfile +7 -0
- data/gemfiles/i18n_0_9.gemfile +7 -0
- data/gemfiles/i18n_1_0.gemfile +7 -0
- data/i18n-js.gemspec +11 -9
- data/lib/i18n/js/dependencies.rb +59 -0
- data/lib/i18n/js/engine.rb +87 -0
- data/lib/i18n/js/fallback_locales.rb +70 -0
- data/lib/{i18n-js → i18n/js}/middleware.rb +32 -9
- data/lib/i18n/js/private/hash_with_symbol_keys.rb +36 -0
- data/lib/i18n/js/segment.rb +88 -0
- data/lib/i18n/js/utils.rb +52 -0
- data/lib/i18n/js/version.rb +7 -0
- data/lib/i18n/js.rb +242 -0
- data/lib/i18n-js.rb +1 -177
- data/lib/rails/generators/i18n/js/config/config_generator.rb +19 -0
- data/{config → lib/rails/generators/i18n/js/config/templates}/i18n-js.yml +11 -6
- data/lib/tasks/export.rake +8 -0
- data/package.json +25 -0
- data/spec/fixtures/custom_path.yml +5 -0
- data/spec/fixtures/default.yml +5 -0
- data/spec/fixtures/erb.yml +5 -0
- data/spec/fixtures/except_condition.yml +7 -0
- data/spec/fixtures/js_export_dir_custom.yml +7 -0
- data/spec/fixtures/js_export_dir_none.yml +6 -0
- data/spec/fixtures/js_extend_parent.yml +6 -0
- data/spec/fixtures/js_extend_segment.yml +6 -0
- data/spec/fixtures/js_file_per_locale.yml +7 -0
- data/spec/fixtures/js_file_per_locale_with_fallbacks_as_default_locale_symbol.yml +4 -0
- data/spec/fixtures/js_file_per_locale_with_fallbacks_as_hash.yml +6 -0
- data/spec/fixtures/js_file_per_locale_with_fallbacks_as_locale.yml +4 -0
- data/spec/fixtures/js_file_per_locale_with_fallbacks_as_locale_without_fallback_translations.yml +4 -0
- data/spec/fixtures/js_file_per_locale_with_fallbacks_enabled.yml +4 -0
- data/spec/fixtures/js_file_per_locale_without_fallbacks.yml +4 -0
- data/spec/fixtures/js_file_with_namespace_and_pretty_print.yml +7 -0
- data/spec/fixtures/js_sort_translation_keys_false.yml +6 -0
- data/spec/fixtures/js_sort_translation_keys_true.yml +6 -0
- data/spec/{resources → fixtures}/locales.yml +13 -1
- data/spec/fixtures/multiple_conditions.yml +7 -0
- data/spec/fixtures/multiple_conditions_per_locale.yml +7 -0
- data/spec/fixtures/multiple_files.yml +7 -0
- data/spec/{resources → fixtures}/no_config.yml +0 -0
- data/spec/fixtures/no_scope.yml +4 -0
- data/spec/fixtures/simple_scope.yml +5 -0
- data/spec/js/currency.spec.js +62 -0
- data/spec/js/current_locale.spec.js +19 -0
- data/spec/js/dates.spec.js +265 -0
- data/spec/js/defaults.spec.js +31 -0
- data/spec/js/extend.spec.js +110 -0
- data/spec/js/interpolation.spec.js +124 -0
- data/spec/js/jasmine/MIT.LICENSE +20 -0
- data/spec/js/jasmine/jasmine-html.js +190 -0
- data/spec/js/jasmine/jasmine.css +166 -0
- data/spec/js/jasmine/jasmine.js +2476 -0
- data/spec/js/jasmine/jasmine_favicon.png +0 -0
- data/spec/js/locales.spec.js +31 -0
- data/spec/js/localization.spec.js +48 -0
- data/spec/js/numbers.spec.js +170 -0
- data/spec/js/placeholder.spec.js +24 -0
- data/spec/js/pluralization.spec.js +211 -0
- data/spec/js/prepare_options.spec.js +41 -0
- data/spec/js/require.js +2083 -0
- data/spec/js/specs.html +49 -0
- data/spec/js/specs_requirejs.html +72 -0
- data/spec/js/translate.spec.js +277 -0
- data/spec/js/translations.js +164 -0
- data/spec/js/utility_functions.spec.js +20 -0
- data/spec/ruby/i18n/js/fallback_locales_spec.rb +84 -0
- data/spec/ruby/i18n/js/segment_spec.rb +157 -0
- data/spec/ruby/i18n/js/utils_spec.rb +106 -0
- data/spec/ruby/i18n/js_spec.rb +627 -0
- data/spec/spec_helper.rb +55 -14
- data/yarn.lock +131 -0
- metadata +188 -96
- data/.rspec +0 -1
- data/Gemfile.lock +0 -51
- data/README.rdoc +0 -305
- data/lib/i18n-js/engine.rb +0 -62
- data/lib/i18n-js/railtie.rb +0 -13
- data/lib/i18n-js/rake.rb +0 -16
- data/lib/i18n-js/version.rb +0 -10
- data/spec/i18n_spec.js +0 -768
- data/spec/i18n_spec.rb +0 -205
- data/spec/resources/custom_path.yml +0 -4
- data/spec/resources/default.yml +0 -4
- data/spec/resources/js_file_per_locale.yml +0 -3
- data/spec/resources/multiple_conditions.yml +0 -6
- data/spec/resources/multiple_files.yml +0 -6
- data/spec/resources/no_scope.yml +0 -3
- data/spec/resources/simple_scope.yml +0 -4
- data/vendor/assets/javascripts/i18n/translations.js.erb +0 -7
- data/vendor/assets/javascripts/i18n.js +0 -450
@@ -0,0 +1,1077 @@
|
|
1
|
+
// I18n.js
|
2
|
+
// =======
|
3
|
+
//
|
4
|
+
// This small library provides the Rails I18n API on the Javascript.
|
5
|
+
// You don't actually have to use Rails (or even Ruby) to use I18n.js.
|
6
|
+
// Just make sure you export all translations in an object like this:
|
7
|
+
//
|
8
|
+
// I18n.translations.en = {
|
9
|
+
// hello: "Hello World"
|
10
|
+
// };
|
11
|
+
//
|
12
|
+
// See tests for specific formatting like numbers and dates.
|
13
|
+
//
|
14
|
+
|
15
|
+
// Using UMD pattern from
|
16
|
+
// https://github.com/umdjs/umd#regular-module
|
17
|
+
// `returnExports.js` version
|
18
|
+
;(function (root, factory) {
|
19
|
+
if (typeof define === 'function' && define.amd) {
|
20
|
+
// AMD. Register as an anonymous module.
|
21
|
+
define("i18n", function(){ return factory(root);});
|
22
|
+
} else if (typeof module === 'object' && module.exports) {
|
23
|
+
// Node. Does not work with strict CommonJS, but
|
24
|
+
// only CommonJS-like environments that support module.exports,
|
25
|
+
// like Node.
|
26
|
+
module.exports = factory(root);
|
27
|
+
} else {
|
28
|
+
// Browser globals (root is window)
|
29
|
+
root.I18n = factory(root);
|
30
|
+
}
|
31
|
+
}(this, function(global) {
|
32
|
+
"use strict";
|
33
|
+
|
34
|
+
// Use previously defined object if exists in current scope
|
35
|
+
var I18n = global && global.I18n || {};
|
36
|
+
|
37
|
+
// Just cache the Array#slice function.
|
38
|
+
var slice = Array.prototype.slice;
|
39
|
+
|
40
|
+
// Apply number padding.
|
41
|
+
var padding = function(number) {
|
42
|
+
return ("0" + number.toString()).substr(-2);
|
43
|
+
};
|
44
|
+
|
45
|
+
// Improved toFixed number rounding function with support for unprecise floating points
|
46
|
+
// JavaScript's standard toFixed function does not round certain numbers correctly (for example 0.105 with precision 2).
|
47
|
+
var toFixed = function(number, precision) {
|
48
|
+
return decimalAdjust('round', number, -precision).toFixed(precision);
|
49
|
+
};
|
50
|
+
|
51
|
+
// Is a given variable an object?
|
52
|
+
// Borrowed from Underscore.js
|
53
|
+
var isObject = function(obj) {
|
54
|
+
var type = typeof obj;
|
55
|
+
return type === 'function' || type === 'object'
|
56
|
+
};
|
57
|
+
|
58
|
+
var isFunction = function(func) {
|
59
|
+
var type = typeof func;
|
60
|
+
return type === 'function'
|
61
|
+
};
|
62
|
+
|
63
|
+
// Check if value is different than undefined and null;
|
64
|
+
var isSet = function(value) {
|
65
|
+
return typeof(value) !== 'undefined' && value !== null;
|
66
|
+
};
|
67
|
+
|
68
|
+
// Is a given value an array?
|
69
|
+
// Borrowed from Underscore.js
|
70
|
+
var isArray = function(val) {
|
71
|
+
if (Array.isArray) {
|
72
|
+
return Array.isArray(val);
|
73
|
+
}
|
74
|
+
return Object.prototype.toString.call(val) === '[object Array]';
|
75
|
+
};
|
76
|
+
|
77
|
+
var isString = function(val) {
|
78
|
+
return typeof val === 'string' || Object.prototype.toString.call(val) === '[object String]';
|
79
|
+
};
|
80
|
+
|
81
|
+
var isNumber = function(val) {
|
82
|
+
return typeof val === 'number' || Object.prototype.toString.call(val) === '[object Number]';
|
83
|
+
};
|
84
|
+
|
85
|
+
var isBoolean = function(val) {
|
86
|
+
return val === true || val === false;
|
87
|
+
};
|
88
|
+
|
89
|
+
var isNull = function(val) {
|
90
|
+
return val === null;
|
91
|
+
};
|
92
|
+
|
93
|
+
var decimalAdjust = function(type, value, exp) {
|
94
|
+
// If the exp is undefined or zero...
|
95
|
+
if (typeof exp === 'undefined' || +exp === 0) {
|
96
|
+
return Math[type](value);
|
97
|
+
}
|
98
|
+
value = +value;
|
99
|
+
exp = +exp;
|
100
|
+
// If the value is not a number or the exp is not an integer...
|
101
|
+
if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {
|
102
|
+
return NaN;
|
103
|
+
}
|
104
|
+
// Shift
|
105
|
+
value = value.toString().split('e');
|
106
|
+
value = Math[type](+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp)));
|
107
|
+
// Shift back
|
108
|
+
value = value.toString().split('e');
|
109
|
+
return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp));
|
110
|
+
}
|
111
|
+
|
112
|
+
var lazyEvaluate = function(message, scope) {
|
113
|
+
if (isFunction(message)) {
|
114
|
+
return message(scope);
|
115
|
+
} else {
|
116
|
+
return message;
|
117
|
+
}
|
118
|
+
}
|
119
|
+
|
120
|
+
var merge = function (dest, obj) {
|
121
|
+
var key, value;
|
122
|
+
for (key in obj) if (obj.hasOwnProperty(key)) {
|
123
|
+
value = obj[key];
|
124
|
+
if (isString(value) || isNumber(value) || isBoolean(value) || isArray(value) || isNull(value)) {
|
125
|
+
dest[key] = value;
|
126
|
+
} else {
|
127
|
+
if (dest[key] == null) dest[key] = {};
|
128
|
+
merge(dest[key], value);
|
129
|
+
}
|
130
|
+
}
|
131
|
+
return dest;
|
132
|
+
};
|
133
|
+
|
134
|
+
// Set default days/months translations.
|
135
|
+
var DATE = {
|
136
|
+
day_names: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
|
137
|
+
, abbr_day_names: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
|
138
|
+
, month_names: [null, "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
|
139
|
+
, abbr_month_names: [null, "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
|
140
|
+
, meridian: ["AM", "PM"]
|
141
|
+
};
|
142
|
+
|
143
|
+
// Set default number format.
|
144
|
+
var NUMBER_FORMAT = {
|
145
|
+
precision: 3
|
146
|
+
, separator: "."
|
147
|
+
, delimiter: ","
|
148
|
+
, strip_insignificant_zeros: false
|
149
|
+
};
|
150
|
+
|
151
|
+
// Set default currency format.
|
152
|
+
var CURRENCY_FORMAT = {
|
153
|
+
unit: "$"
|
154
|
+
, precision: 2
|
155
|
+
, format: "%u%n"
|
156
|
+
, sign_first: true
|
157
|
+
, delimiter: ","
|
158
|
+
, separator: "."
|
159
|
+
};
|
160
|
+
|
161
|
+
// Set default percentage format.
|
162
|
+
var PERCENTAGE_FORMAT = {
|
163
|
+
unit: "%"
|
164
|
+
, precision: 3
|
165
|
+
, format: "%n%u"
|
166
|
+
, separator: "."
|
167
|
+
, delimiter: ""
|
168
|
+
};
|
169
|
+
|
170
|
+
// Set default size units.
|
171
|
+
var SIZE_UNITS = [null, "kb", "mb", "gb", "tb"];
|
172
|
+
|
173
|
+
// Other default options
|
174
|
+
var DEFAULT_OPTIONS = {
|
175
|
+
// Set default locale. This locale will be used when fallback is enabled and
|
176
|
+
// the translation doesn't exist in a particular locale.
|
177
|
+
defaultLocale: "en"
|
178
|
+
// Set the current locale to `en`.
|
179
|
+
, locale: "en"
|
180
|
+
// Set the translation key separator.
|
181
|
+
, defaultSeparator: "."
|
182
|
+
// Set the placeholder format. Accepts `{{placeholder}}` and `%{placeholder}`.
|
183
|
+
, placeholder: /(?:\{\{|%\{)(.*?)(?:\}\}?)/gm
|
184
|
+
// Set if engine should fallback to the default locale when a translation
|
185
|
+
// is missing.
|
186
|
+
, fallbacks: false
|
187
|
+
// Set the default translation object.
|
188
|
+
, translations: {}
|
189
|
+
// Set missing translation behavior. 'message' will display a message
|
190
|
+
// that the translation is missing, 'guess' will try to guess the string
|
191
|
+
, missingBehaviour: 'message'
|
192
|
+
// if you use missingBehaviour with 'message', but want to know that the
|
193
|
+
// string is actually missing for testing purposes, you can prefix the
|
194
|
+
// guessed string by setting the value here. By default, no prefix!
|
195
|
+
, missingTranslationPrefix: ''
|
196
|
+
};
|
197
|
+
|
198
|
+
// Set default locale. This locale will be used when fallback is enabled and
|
199
|
+
// the translation doesn't exist in a particular locale.
|
200
|
+
I18n.reset = function() {
|
201
|
+
var key;
|
202
|
+
for (key in DEFAULT_OPTIONS) {
|
203
|
+
this[key] = DEFAULT_OPTIONS[key];
|
204
|
+
}
|
205
|
+
};
|
206
|
+
|
207
|
+
// Much like `reset`, but only assign options if not already assigned
|
208
|
+
I18n.initializeOptions = function() {
|
209
|
+
var key;
|
210
|
+
for (key in DEFAULT_OPTIONS) if (!isSet(this[key])) {
|
211
|
+
this[key] = DEFAULT_OPTIONS[key];
|
212
|
+
}
|
213
|
+
};
|
214
|
+
I18n.initializeOptions();
|
215
|
+
|
216
|
+
// Return a list of all locales that must be tried before returning the
|
217
|
+
// missing translation message. By default, this will consider the inline option,
|
218
|
+
// current locale and fallback locale.
|
219
|
+
//
|
220
|
+
// I18n.locales.get("de-DE");
|
221
|
+
// // ["de-DE", "de", "en"]
|
222
|
+
//
|
223
|
+
// You can define custom rules for any locale. Just make sure you return a array
|
224
|
+
// containing all locales.
|
225
|
+
//
|
226
|
+
// // Default the Wookie locale to English.
|
227
|
+
// I18n.locales["wk"] = function(locale) {
|
228
|
+
// return ["en"];
|
229
|
+
// };
|
230
|
+
//
|
231
|
+
I18n.locales = {};
|
232
|
+
|
233
|
+
// Retrieve locales based on inline locale, current locale or default to
|
234
|
+
// I18n's detection.
|
235
|
+
I18n.locales.get = function(locale) {
|
236
|
+
var result = this[locale] || this[I18n.locale] || this["default"];
|
237
|
+
|
238
|
+
if (isFunction(result)) {
|
239
|
+
result = result(locale);
|
240
|
+
}
|
241
|
+
|
242
|
+
if (isArray(result) === false) {
|
243
|
+
result = [result];
|
244
|
+
}
|
245
|
+
|
246
|
+
return result;
|
247
|
+
};
|
248
|
+
|
249
|
+
// The default locale list.
|
250
|
+
I18n.locales["default"] = function(locale) {
|
251
|
+
var locales = []
|
252
|
+
, list = []
|
253
|
+
;
|
254
|
+
|
255
|
+
// Handle the inline locale option that can be provided to
|
256
|
+
// the `I18n.t` options.
|
257
|
+
if (locale) {
|
258
|
+
locales.push(locale);
|
259
|
+
}
|
260
|
+
|
261
|
+
// Add the current locale to the list.
|
262
|
+
if (!locale && I18n.locale) {
|
263
|
+
locales.push(I18n.locale);
|
264
|
+
}
|
265
|
+
|
266
|
+
// Add the default locale if fallback strategy is enabled.
|
267
|
+
if (I18n.fallbacks && I18n.defaultLocale) {
|
268
|
+
locales.push(I18n.defaultLocale);
|
269
|
+
}
|
270
|
+
|
271
|
+
// Locale code format 1:
|
272
|
+
// According to RFC4646 (http://www.ietf.org/rfc/rfc4646.txt)
|
273
|
+
// language codes for Traditional Chinese should be `zh-Hant`
|
274
|
+
//
|
275
|
+
// But due to backward compatibility
|
276
|
+
// We use older version of IETF language tag
|
277
|
+
// @see http://www.w3.org/TR/html401/struct/dirlang.html
|
278
|
+
// @see http://en.wikipedia.org/wiki/IETF_language_tag
|
279
|
+
//
|
280
|
+
// Format: `language-code = primary-code ( "-" subcode )*`
|
281
|
+
//
|
282
|
+
// primary-code uses ISO639-1
|
283
|
+
// @see http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
|
284
|
+
// @see http://www.iso.org/iso/home/standards/language_codes.htm
|
285
|
+
//
|
286
|
+
// subcode uses ISO 3166-1 alpha-2
|
287
|
+
// @see http://en.wikipedia.org/wiki/ISO_3166
|
288
|
+
// @see http://www.iso.org/iso/country_codes.htm
|
289
|
+
//
|
290
|
+
// @note
|
291
|
+
// subcode can be in upper case or lower case
|
292
|
+
// defining it in upper case is a convention only
|
293
|
+
|
294
|
+
|
295
|
+
// Locale code format 2:
|
296
|
+
// Format: `code = primary-code ( "-" region-code )*`
|
297
|
+
// primary-code uses ISO 639-1
|
298
|
+
// script-code uses ISO 15924
|
299
|
+
// region-code uses ISO 3166-1 alpha-2
|
300
|
+
// Example: zh-Hant-TW, en-HK, zh-Hant-CN
|
301
|
+
//
|
302
|
+
// It is similar to RFC4646 (or actually the same),
|
303
|
+
// but seems to be limited to language, script, region
|
304
|
+
|
305
|
+
// Compute each locale with its country code.
|
306
|
+
// So this will return an array containing
|
307
|
+
// `de-DE` and `de`
|
308
|
+
// or
|
309
|
+
// `zh-hans-tw`, `zh-hans`, `zh`
|
310
|
+
// locales.
|
311
|
+
locales.forEach(function(locale) {
|
312
|
+
var localeParts = locale.split("-");
|
313
|
+
var firstFallback = null;
|
314
|
+
var secondFallback = null;
|
315
|
+
if (localeParts.length === 3) {
|
316
|
+
firstFallback = [
|
317
|
+
localeParts[0],
|
318
|
+
localeParts[1]
|
319
|
+
].join("-");
|
320
|
+
secondFallback = localeParts[0];
|
321
|
+
}
|
322
|
+
else if (localeParts.length === 2) {
|
323
|
+
firstFallback = localeParts[0];
|
324
|
+
}
|
325
|
+
|
326
|
+
if (list.indexOf(locale) === -1) {
|
327
|
+
list.push(locale);
|
328
|
+
}
|
329
|
+
|
330
|
+
if (! I18n.fallbacks) {
|
331
|
+
return;
|
332
|
+
}
|
333
|
+
|
334
|
+
[
|
335
|
+
firstFallback,
|
336
|
+
secondFallback
|
337
|
+
].forEach(function(nullableFallbackLocale) {
|
338
|
+
// We don't want null values
|
339
|
+
if (typeof nullableFallbackLocale === "undefined") { return; }
|
340
|
+
if (nullableFallbackLocale === null) { return; }
|
341
|
+
// We don't want duplicate values
|
342
|
+
//
|
343
|
+
// Comparing with `locale` first is faster than
|
344
|
+
// checking whether value's presence in the list
|
345
|
+
if (nullableFallbackLocale === locale) { return; }
|
346
|
+
if (list.indexOf(nullableFallbackLocale) !== -1) { return; }
|
347
|
+
|
348
|
+
list.push(nullableFallbackLocale);
|
349
|
+
});
|
350
|
+
});
|
351
|
+
|
352
|
+
// No locales set? English it is.
|
353
|
+
if (!locales.length) {
|
354
|
+
locales.push("en");
|
355
|
+
}
|
356
|
+
|
357
|
+
return list;
|
358
|
+
};
|
359
|
+
|
360
|
+
// Hold pluralization rules.
|
361
|
+
I18n.pluralization = {};
|
362
|
+
|
363
|
+
// Return the pluralizer for a specific locale.
|
364
|
+
// If no specify locale is found, then I18n's default will be used.
|
365
|
+
I18n.pluralization.get = function(locale) {
|
366
|
+
return this[locale] || this[I18n.locale] || this["default"];
|
367
|
+
};
|
368
|
+
|
369
|
+
// The default pluralizer rule.
|
370
|
+
// It detects the `zero`, `one`, and `other` scopes.
|
371
|
+
I18n.pluralization["default"] = function(count) {
|
372
|
+
switch (count) {
|
373
|
+
case 0: return ["zero", "other"];
|
374
|
+
case 1: return ["one"];
|
375
|
+
default: return ["other"];
|
376
|
+
}
|
377
|
+
};
|
378
|
+
|
379
|
+
// Return current locale. If no locale has been set, then
|
380
|
+
// the current locale will be the default locale.
|
381
|
+
I18n.currentLocale = function() {
|
382
|
+
return this.locale || this.defaultLocale;
|
383
|
+
};
|
384
|
+
|
385
|
+
// Check if value is different than undefined and null;
|
386
|
+
I18n.isSet = isSet;
|
387
|
+
|
388
|
+
// Find and process the translation using the provided scope and options.
|
389
|
+
// This is used internally by some functions and should not be used as an
|
390
|
+
// public API.
|
391
|
+
I18n.lookup = function(scope, options) {
|
392
|
+
options = options || {}
|
393
|
+
|
394
|
+
var locales = this.locales.get(options.locale).slice()
|
395
|
+
, locale
|
396
|
+
, scopes
|
397
|
+
, fullScope
|
398
|
+
, translations
|
399
|
+
;
|
400
|
+
|
401
|
+
fullScope = this.getFullScope(scope, options);
|
402
|
+
|
403
|
+
while (locales.length) {
|
404
|
+
locale = locales.shift();
|
405
|
+
scopes = fullScope.split(this.defaultSeparator);
|
406
|
+
translations = this.translations[locale];
|
407
|
+
|
408
|
+
if (!translations) {
|
409
|
+
continue;
|
410
|
+
}
|
411
|
+
while (scopes.length) {
|
412
|
+
translations = translations[scopes.shift()];
|
413
|
+
|
414
|
+
if (translations === undefined || translations === null) {
|
415
|
+
break;
|
416
|
+
}
|
417
|
+
}
|
418
|
+
|
419
|
+
if (translations !== undefined && translations !== null) {
|
420
|
+
return translations;
|
421
|
+
}
|
422
|
+
}
|
423
|
+
|
424
|
+
if (isSet(options.defaultValue)) {
|
425
|
+
return lazyEvaluate(options.defaultValue, scope);
|
426
|
+
}
|
427
|
+
};
|
428
|
+
|
429
|
+
// lookup pluralization rule key into translations
|
430
|
+
I18n.pluralizationLookupWithoutFallback = function(count, locale, translations) {
|
431
|
+
var pluralizer = this.pluralization.get(locale)
|
432
|
+
, pluralizerKeys = pluralizer(count)
|
433
|
+
, pluralizerKey
|
434
|
+
, message;
|
435
|
+
|
436
|
+
if (isObject(translations)) {
|
437
|
+
while (pluralizerKeys.length) {
|
438
|
+
pluralizerKey = pluralizerKeys.shift();
|
439
|
+
if (isSet(translations[pluralizerKey])) {
|
440
|
+
message = translations[pluralizerKey];
|
441
|
+
break;
|
442
|
+
}
|
443
|
+
}
|
444
|
+
}
|
445
|
+
|
446
|
+
return message;
|
447
|
+
};
|
448
|
+
|
449
|
+
// Lookup dedicated to pluralization
|
450
|
+
I18n.pluralizationLookup = function(count, scope, options) {
|
451
|
+
options = options || {}
|
452
|
+
var locales = this.locales.get(options.locale).slice()
|
453
|
+
, locale
|
454
|
+
, scopes
|
455
|
+
, translations
|
456
|
+
, message
|
457
|
+
;
|
458
|
+
scope = this.getFullScope(scope, options);
|
459
|
+
|
460
|
+
while (locales.length) {
|
461
|
+
locale = locales.shift();
|
462
|
+
scopes = scope.split(this.defaultSeparator);
|
463
|
+
translations = this.translations[locale];
|
464
|
+
|
465
|
+
if (!translations) {
|
466
|
+
continue;
|
467
|
+
}
|
468
|
+
|
469
|
+
while (scopes.length) {
|
470
|
+
translations = translations[scopes.shift()];
|
471
|
+
if (!isObject(translations)) {
|
472
|
+
break;
|
473
|
+
}
|
474
|
+
if (scopes.length == 0) {
|
475
|
+
message = this.pluralizationLookupWithoutFallback(count, locale, translations);
|
476
|
+
}
|
477
|
+
}
|
478
|
+
if (message != null && message != undefined) {
|
479
|
+
break;
|
480
|
+
}
|
481
|
+
}
|
482
|
+
|
483
|
+
if (message == null || message == undefined) {
|
484
|
+
if (isSet(options.defaultValue)) {
|
485
|
+
if (isObject(options.defaultValue)) {
|
486
|
+
message = this.pluralizationLookupWithoutFallback(count, options.locale, options.defaultValue);
|
487
|
+
} else {
|
488
|
+
message = options.defaultValue;
|
489
|
+
}
|
490
|
+
translations = options.defaultValue;
|
491
|
+
}
|
492
|
+
}
|
493
|
+
|
494
|
+
return { message: message, translations: translations };
|
495
|
+
};
|
496
|
+
|
497
|
+
// Rails changed the way the meridian is stored.
|
498
|
+
// It started with `date.meridian` returning an array,
|
499
|
+
// then it switched to `time.am` and `time.pm`.
|
500
|
+
// This function abstracts this difference and returns
|
501
|
+
// the correct meridian or the default value when none is provided.
|
502
|
+
I18n.meridian = function() {
|
503
|
+
var time = this.lookup("time");
|
504
|
+
var date = this.lookup("date");
|
505
|
+
|
506
|
+
if (time && time.am && time.pm) {
|
507
|
+
return [time.am, time.pm];
|
508
|
+
} else if (date && date.meridian) {
|
509
|
+
return date.meridian;
|
510
|
+
} else {
|
511
|
+
return DATE.meridian;
|
512
|
+
}
|
513
|
+
};
|
514
|
+
|
515
|
+
// Merge serveral hash options, checking if value is set before
|
516
|
+
// overwriting any value. The precedence is from left to right.
|
517
|
+
//
|
518
|
+
// I18n.prepareOptions({name: "John Doe"}, {name: "Mary Doe", role: "user"});
|
519
|
+
// #=> {name: "John Doe", role: "user"}
|
520
|
+
//
|
521
|
+
I18n.prepareOptions = function() {
|
522
|
+
var args = slice.call(arguments)
|
523
|
+
, options = {}
|
524
|
+
, subject
|
525
|
+
;
|
526
|
+
|
527
|
+
while (args.length) {
|
528
|
+
subject = args.shift();
|
529
|
+
|
530
|
+
if (typeof(subject) != "object") {
|
531
|
+
continue;
|
532
|
+
}
|
533
|
+
|
534
|
+
for (var attr in subject) {
|
535
|
+
if (!subject.hasOwnProperty(attr)) {
|
536
|
+
continue;
|
537
|
+
}
|
538
|
+
|
539
|
+
if (isSet(options[attr])) {
|
540
|
+
continue;
|
541
|
+
}
|
542
|
+
|
543
|
+
options[attr] = subject[attr];
|
544
|
+
}
|
545
|
+
}
|
546
|
+
|
547
|
+
return options;
|
548
|
+
};
|
549
|
+
|
550
|
+
// Generate a list of translation options for default fallbacks.
|
551
|
+
// `defaultValue` is also deleted from options as it is returned as part of
|
552
|
+
// the translationOptions array.
|
553
|
+
I18n.createTranslationOptions = function(scope, options) {
|
554
|
+
var translationOptions = [{scope: scope}];
|
555
|
+
|
556
|
+
// Defaults should be an array of hashes containing either
|
557
|
+
// fallback scopes or messages
|
558
|
+
if (isSet(options.defaults)) {
|
559
|
+
translationOptions = translationOptions.concat(options.defaults);
|
560
|
+
}
|
561
|
+
|
562
|
+
// Maintain support for defaultValue. Since it is always a message
|
563
|
+
// insert it in to the translation options as such.
|
564
|
+
if (isSet(options.defaultValue)) {
|
565
|
+
translationOptions.push({ message: options.defaultValue });
|
566
|
+
}
|
567
|
+
|
568
|
+
return translationOptions;
|
569
|
+
};
|
570
|
+
|
571
|
+
// Translate the given scope with the provided options.
|
572
|
+
I18n.translate = function(scope, options) {
|
573
|
+
options = options || {}
|
574
|
+
|
575
|
+
var translationOptions = this.createTranslationOptions(scope, options);
|
576
|
+
|
577
|
+
var translation;
|
578
|
+
|
579
|
+
var optionsWithoutDefault = this.prepareOptions(options)
|
580
|
+
delete optionsWithoutDefault.defaultValue
|
581
|
+
|
582
|
+
// Iterate through the translation options until a translation
|
583
|
+
// or message is found.
|
584
|
+
var translationFound =
|
585
|
+
translationOptions.some(function(translationOption) {
|
586
|
+
if (isSet(translationOption.scope)) {
|
587
|
+
translation = this.lookup(translationOption.scope, optionsWithoutDefault);
|
588
|
+
} else if (isSet(translationOption.message)) {
|
589
|
+
translation = lazyEvaluate(translationOption.message, scope);
|
590
|
+
}
|
591
|
+
|
592
|
+
if (translation !== undefined && translation !== null) {
|
593
|
+
return true;
|
594
|
+
}
|
595
|
+
}, this);
|
596
|
+
|
597
|
+
if (!translationFound) {
|
598
|
+
return this.missingTranslation(scope, options);
|
599
|
+
}
|
600
|
+
|
601
|
+
if (typeof(translation) === "string") {
|
602
|
+
translation = this.interpolate(translation, options);
|
603
|
+
} else if (isArray(translation)) {
|
604
|
+
translation = translation.map(function(t) {
|
605
|
+
return (typeof(t) === "string" ? this.interpolate(t, options) : t);
|
606
|
+
}, this);
|
607
|
+
} else if (isObject(translation) && isSet(options.count)) {
|
608
|
+
translation = this.pluralize(options.count, scope, options);
|
609
|
+
}
|
610
|
+
|
611
|
+
return translation;
|
612
|
+
};
|
613
|
+
|
614
|
+
// This function interpolates the all variables in the given message.
|
615
|
+
I18n.interpolate = function(message, options) {
|
616
|
+
if (message === null) {
|
617
|
+
return message;
|
618
|
+
}
|
619
|
+
|
620
|
+
options = options || {}
|
621
|
+
var matches = message.match(this.placeholder)
|
622
|
+
, placeholder
|
623
|
+
, value
|
624
|
+
, name
|
625
|
+
, regex
|
626
|
+
;
|
627
|
+
|
628
|
+
if (!matches) {
|
629
|
+
return message;
|
630
|
+
}
|
631
|
+
|
632
|
+
var value;
|
633
|
+
|
634
|
+
while (matches.length) {
|
635
|
+
placeholder = matches.shift();
|
636
|
+
name = placeholder.replace(this.placeholder, "$1");
|
637
|
+
|
638
|
+
if (isSet(options[name])) {
|
639
|
+
value = options[name].toString().replace(/\$/gm, "_#$#_");
|
640
|
+
} else if (name in options) {
|
641
|
+
value = this.nullPlaceholder(placeholder, message, options);
|
642
|
+
} else {
|
643
|
+
value = this.missingPlaceholder(placeholder, message, options);
|
644
|
+
}
|
645
|
+
|
646
|
+
regex = new RegExp(placeholder.replace(/\{/gm, "\\{").replace(/\}/gm, "\\}"));
|
647
|
+
message = message.replace(regex, value);
|
648
|
+
}
|
649
|
+
|
650
|
+
return message.replace(/_#\$#_/g, "$");
|
651
|
+
};
|
652
|
+
|
653
|
+
// Pluralize the given scope using the `count` value.
|
654
|
+
// The pluralized translation may have other placeholders,
|
655
|
+
// which will be retrieved from `options`.
|
656
|
+
I18n.pluralize = function(count, scope, options) {
|
657
|
+
options = this.prepareOptions({count: String(count)}, options)
|
658
|
+
var pluralizer, message, result;
|
659
|
+
|
660
|
+
result = this.pluralizationLookup(count, scope, options);
|
661
|
+
if (result.translations == undefined || result.translations == null) {
|
662
|
+
return this.missingTranslation(scope, options);
|
663
|
+
}
|
664
|
+
|
665
|
+
if (result.message != undefined && result.message != null) {
|
666
|
+
return this.interpolate(result.message, options);
|
667
|
+
}
|
668
|
+
else {
|
669
|
+
pluralizer = this.pluralization.get(options.locale);
|
670
|
+
return this.missingTranslation(scope + '.' + pluralizer(count)[0], options);
|
671
|
+
}
|
672
|
+
};
|
673
|
+
|
674
|
+
// Return a missing translation message for the given parameters.
|
675
|
+
I18n.missingTranslation = function(scope, options) {
|
676
|
+
//guess intended string
|
677
|
+
if(this.missingBehaviour == 'guess'){
|
678
|
+
//get only the last portion of the scope
|
679
|
+
var s = scope.split('.').slice(-1)[0];
|
680
|
+
//replace underscore with space && camelcase with space and lowercase letter
|
681
|
+
return (this.missingTranslationPrefix.length > 0 ? this.missingTranslationPrefix : '') +
|
682
|
+
s.replace('_',' ').replace(/([a-z])([A-Z])/g,
|
683
|
+
function(match, p1, p2) {return p1 + ' ' + p2.toLowerCase()} );
|
684
|
+
}
|
685
|
+
|
686
|
+
var localeForTranslation = (options != null && options.locale != null) ? options.locale : this.currentLocale();
|
687
|
+
var fullScope = this.getFullScope(scope, options);
|
688
|
+
var fullScopeWithLocale = [localeForTranslation, fullScope].join(this.defaultSeparator);
|
689
|
+
|
690
|
+
return '[missing "' + fullScopeWithLocale + '" translation]';
|
691
|
+
};
|
692
|
+
|
693
|
+
// Return a missing placeholder message for given parameters
|
694
|
+
I18n.missingPlaceholder = function(placeholder, message, options) {
|
695
|
+
return "[missing " + placeholder + " value]";
|
696
|
+
};
|
697
|
+
|
698
|
+
I18n.nullPlaceholder = function() {
|
699
|
+
return I18n.missingPlaceholder.apply(I18n, arguments);
|
700
|
+
};
|
701
|
+
|
702
|
+
// Format number using localization rules.
|
703
|
+
// The options will be retrieved from the `number.format` scope.
|
704
|
+
// If this isn't present, then the following options will be used:
|
705
|
+
//
|
706
|
+
// - `precision`: `3`
|
707
|
+
// - `separator`: `"."`
|
708
|
+
// - `delimiter`: `","`
|
709
|
+
// - `strip_insignificant_zeros`: `false`
|
710
|
+
//
|
711
|
+
// You can also override these options by providing the `options` argument.
|
712
|
+
//
|
713
|
+
I18n.toNumber = function(number, options) {
|
714
|
+
options = this.prepareOptions(
|
715
|
+
options
|
716
|
+
, this.lookup("number.format")
|
717
|
+
, NUMBER_FORMAT
|
718
|
+
);
|
719
|
+
|
720
|
+
var negative = number < 0
|
721
|
+
, string = toFixed(Math.abs(number), options.precision).toString()
|
722
|
+
, parts = string.split(".")
|
723
|
+
, precision
|
724
|
+
, buffer = []
|
725
|
+
, formattedNumber
|
726
|
+
, format = options.format || "%n"
|
727
|
+
, sign = negative ? "-" : ""
|
728
|
+
;
|
729
|
+
|
730
|
+
number = parts[0];
|
731
|
+
precision = parts[1];
|
732
|
+
|
733
|
+
while (number.length > 0) {
|
734
|
+
buffer.unshift(number.substr(Math.max(0, number.length - 3), 3));
|
735
|
+
number = number.substr(0, number.length -3);
|
736
|
+
}
|
737
|
+
|
738
|
+
formattedNumber = buffer.join(options.delimiter);
|
739
|
+
|
740
|
+
if (options.strip_insignificant_zeros && precision) {
|
741
|
+
precision = precision.replace(/0+$/, "");
|
742
|
+
}
|
743
|
+
|
744
|
+
if (options.precision > 0 && precision) {
|
745
|
+
formattedNumber += options.separator + precision;
|
746
|
+
}
|
747
|
+
|
748
|
+
if (options.sign_first) {
|
749
|
+
format = "%s" + format;
|
750
|
+
}
|
751
|
+
else {
|
752
|
+
format = format.replace("%n", "%s%n");
|
753
|
+
}
|
754
|
+
|
755
|
+
formattedNumber = format
|
756
|
+
.replace("%u", options.unit)
|
757
|
+
.replace("%n", formattedNumber)
|
758
|
+
.replace("%s", sign)
|
759
|
+
;
|
760
|
+
|
761
|
+
return formattedNumber;
|
762
|
+
};
|
763
|
+
|
764
|
+
// Format currency with localization rules.
|
765
|
+
// The options will be retrieved from the `number.currency.format` and
|
766
|
+
// `number.format` scopes, in that order.
|
767
|
+
//
|
768
|
+
// Any missing option will be retrieved from the `I18n.toNumber` defaults and
|
769
|
+
// the following options:
|
770
|
+
//
|
771
|
+
// - `unit`: `"$"`
|
772
|
+
// - `precision`: `2`
|
773
|
+
// - `format`: `"%u%n"`
|
774
|
+
// - `delimiter`: `","`
|
775
|
+
// - `separator`: `"."`
|
776
|
+
//
|
777
|
+
// You can also override these options by providing the `options` argument.
|
778
|
+
//
|
779
|
+
I18n.toCurrency = function(number, options) {
|
780
|
+
options = this.prepareOptions(
|
781
|
+
options
|
782
|
+
, this.lookup("number.currency.format")
|
783
|
+
, this.lookup("number.format")
|
784
|
+
, CURRENCY_FORMAT
|
785
|
+
);
|
786
|
+
|
787
|
+
return this.toNumber(number, options);
|
788
|
+
};
|
789
|
+
|
790
|
+
// Localize several values.
|
791
|
+
// You can provide the following scopes: `currency`, `number`, or `percentage`.
|
792
|
+
// If you provide a scope that matches the `/^(date|time)/` regular expression
|
793
|
+
// then the `value` will be converted by using the `I18n.toTime` function.
|
794
|
+
//
|
795
|
+
// It will default to the value's `toString` function.
|
796
|
+
//
|
797
|
+
I18n.localize = function(scope, value, options) {
|
798
|
+
options || (options = {});
|
799
|
+
|
800
|
+
switch (scope) {
|
801
|
+
case "currency":
|
802
|
+
return this.toCurrency(value);
|
803
|
+
case "number":
|
804
|
+
scope = this.lookup("number.format");
|
805
|
+
return this.toNumber(value, scope);
|
806
|
+
case "percentage":
|
807
|
+
return this.toPercentage(value);
|
808
|
+
default:
|
809
|
+
var localizedValue;
|
810
|
+
|
811
|
+
if (scope.match(/^(date|time)/)) {
|
812
|
+
localizedValue = this.toTime(scope, value);
|
813
|
+
} else {
|
814
|
+
localizedValue = value.toString();
|
815
|
+
}
|
816
|
+
|
817
|
+
return this.interpolate(localizedValue, options);
|
818
|
+
}
|
819
|
+
};
|
820
|
+
|
821
|
+
// Parse a given `date` string into a JavaScript Date object.
|
822
|
+
// This function is time zone aware.
|
823
|
+
//
|
824
|
+
// The following string formats are recognized:
|
825
|
+
//
|
826
|
+
// yyyy-mm-dd
|
827
|
+
// yyyy-mm-dd[ T]hh:mm::ss
|
828
|
+
// yyyy-mm-dd[ T]hh:mm::ss
|
829
|
+
// yyyy-mm-dd[ T]hh:mm::ssZ
|
830
|
+
// yyyy-mm-dd[ T]hh:mm::ss+0000
|
831
|
+
// yyyy-mm-dd[ T]hh:mm::ss+00:00
|
832
|
+
// yyyy-mm-dd[ T]hh:mm::ss.123Z
|
833
|
+
//
|
834
|
+
I18n.parseDate = function(date) {
|
835
|
+
var matches, convertedDate, fraction;
|
836
|
+
// we have a date, so just return it.
|
837
|
+
if (typeof(date) == "object") {
|
838
|
+
return date;
|
839
|
+
};
|
840
|
+
|
841
|
+
matches = date.toString().match(/(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}):(\d{2})([\.,]\d{1,3})?)?(Z|\+00:?00)?/);
|
842
|
+
|
843
|
+
if (matches) {
|
844
|
+
for (var i = 1; i <= 6; i++) {
|
845
|
+
matches[i] = parseInt(matches[i], 10) || 0;
|
846
|
+
}
|
847
|
+
|
848
|
+
// month starts on 0
|
849
|
+
matches[2] -= 1;
|
850
|
+
|
851
|
+
fraction = matches[7] ? 1000 * ("0" + matches[7]) : null;
|
852
|
+
|
853
|
+
if (matches[8]) {
|
854
|
+
convertedDate = new Date(Date.UTC(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6], fraction));
|
855
|
+
} else {
|
856
|
+
convertedDate = new Date(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6], fraction);
|
857
|
+
}
|
858
|
+
} else if (typeof(date) == "number") {
|
859
|
+
// UNIX timestamp
|
860
|
+
convertedDate = new Date();
|
861
|
+
convertedDate.setTime(date);
|
862
|
+
} else if (date.match(/([A-Z][a-z]{2}) ([A-Z][a-z]{2}) (\d+) (\d+:\d+:\d+) ([+-]\d+) (\d+)/)) {
|
863
|
+
// This format `Wed Jul 20 13:03:39 +0000 2011` is parsed by
|
864
|
+
// webkit/firefox, but not by IE, so we must parse it manually.
|
865
|
+
convertedDate = new Date();
|
866
|
+
convertedDate.setTime(Date.parse([
|
867
|
+
RegExp.$1, RegExp.$2, RegExp.$3, RegExp.$6, RegExp.$4, RegExp.$5
|
868
|
+
].join(" ")));
|
869
|
+
} else if (date.match(/\d+ \d+:\d+:\d+ [+-]\d+ \d+/)) {
|
870
|
+
// a valid javascript format with timezone info
|
871
|
+
convertedDate = new Date();
|
872
|
+
convertedDate.setTime(Date.parse(date));
|
873
|
+
} else {
|
874
|
+
// an arbitrary javascript string
|
875
|
+
convertedDate = new Date();
|
876
|
+
convertedDate.setTime(Date.parse(date));
|
877
|
+
}
|
878
|
+
|
879
|
+
return convertedDate;
|
880
|
+
};
|
881
|
+
|
882
|
+
// Formats time according to the directives in the given format string.
|
883
|
+
// The directives begins with a percent (%) character. Any text not listed as a
|
884
|
+
// directive will be passed through to the output string.
|
885
|
+
//
|
886
|
+
// The accepted formats are:
|
887
|
+
//
|
888
|
+
// %a - The abbreviated weekday name (Sun)
|
889
|
+
// %A - The full weekday name (Sunday)
|
890
|
+
// %b - The abbreviated month name (Jan)
|
891
|
+
// %B - The full month name (January)
|
892
|
+
// %c - The preferred local date and time representation
|
893
|
+
// %d - Day of the month (01..31)
|
894
|
+
// %-d - Day of the month (1..31)
|
895
|
+
// %H - Hour of the day, 24-hour clock (00..23)
|
896
|
+
// %-H - Hour of the day, 24-hour clock (0..23)
|
897
|
+
// %I - Hour of the day, 12-hour clock (01..12)
|
898
|
+
// %-I - Hour of the day, 12-hour clock (1..12)
|
899
|
+
// %m - Month of the year (01..12)
|
900
|
+
// %-m - Month of the year (1..12)
|
901
|
+
// %M - Minute of the hour (00..59)
|
902
|
+
// %-M - Minute of the hour (0..59)
|
903
|
+
// %p - Meridian indicator (AM or PM)
|
904
|
+
// %S - Second of the minute (00..60)
|
905
|
+
// %-S - Second of the minute (0..60)
|
906
|
+
// %w - Day of the week (Sunday is 0, 0..6)
|
907
|
+
// %y - Year without a century (00..99)
|
908
|
+
// %-y - Year without a century (0..99)
|
909
|
+
// %Y - Year with century
|
910
|
+
// %z - Timezone offset (+0545)
|
911
|
+
//
|
912
|
+
I18n.strftime = function(date, format) {
|
913
|
+
var options = this.lookup("date")
|
914
|
+
, meridianOptions = I18n.meridian()
|
915
|
+
;
|
916
|
+
|
917
|
+
if (!options) {
|
918
|
+
options = {};
|
919
|
+
}
|
920
|
+
|
921
|
+
options = this.prepareOptions(options, DATE);
|
922
|
+
|
923
|
+
if (isNaN(date.getTime())) {
|
924
|
+
throw new Error('I18n.strftime() requires a valid date object, but received an invalid date.');
|
925
|
+
}
|
926
|
+
|
927
|
+
var weekDay = date.getDay()
|
928
|
+
, day = date.getDate()
|
929
|
+
, year = date.getFullYear()
|
930
|
+
, month = date.getMonth() + 1
|
931
|
+
, hour = date.getHours()
|
932
|
+
, hour12 = hour
|
933
|
+
, meridian = hour > 11 ? 1 : 0
|
934
|
+
, secs = date.getSeconds()
|
935
|
+
, mins = date.getMinutes()
|
936
|
+
, offset = date.getTimezoneOffset()
|
937
|
+
, absOffsetHours = Math.floor(Math.abs(offset / 60))
|
938
|
+
, absOffsetMinutes = Math.abs(offset) - (absOffsetHours * 60)
|
939
|
+
, timezoneoffset = (offset > 0 ? "-" : "+") +
|
940
|
+
(absOffsetHours.toString().length < 2 ? "0" + absOffsetHours : absOffsetHours) +
|
941
|
+
(absOffsetMinutes.toString().length < 2 ? "0" + absOffsetMinutes : absOffsetMinutes)
|
942
|
+
;
|
943
|
+
|
944
|
+
if (hour12 > 12) {
|
945
|
+
hour12 = hour12 - 12;
|
946
|
+
} else if (hour12 === 0) {
|
947
|
+
hour12 = 12;
|
948
|
+
}
|
949
|
+
|
950
|
+
format = format.replace("%a", options.abbr_day_names[weekDay]);
|
951
|
+
format = format.replace("%A", options.day_names[weekDay]);
|
952
|
+
format = format.replace("%b", options.abbr_month_names[month]);
|
953
|
+
format = format.replace("%B", options.month_names[month]);
|
954
|
+
format = format.replace("%d", padding(day));
|
955
|
+
format = format.replace("%e", day);
|
956
|
+
format = format.replace("%-d", day);
|
957
|
+
format = format.replace("%H", padding(hour));
|
958
|
+
format = format.replace("%-H", hour);
|
959
|
+
format = format.replace("%I", padding(hour12));
|
960
|
+
format = format.replace("%-I", hour12);
|
961
|
+
format = format.replace("%m", padding(month));
|
962
|
+
format = format.replace("%-m", month);
|
963
|
+
format = format.replace("%M", padding(mins));
|
964
|
+
format = format.replace("%-M", mins);
|
965
|
+
format = format.replace("%p", meridianOptions[meridian]);
|
966
|
+
format = format.replace("%S", padding(secs));
|
967
|
+
format = format.replace("%-S", secs);
|
968
|
+
format = format.replace("%w", weekDay);
|
969
|
+
format = format.replace("%y", padding(year));
|
970
|
+
format = format.replace("%-y", padding(year).replace(/^0+/, ""));
|
971
|
+
format = format.replace("%Y", year);
|
972
|
+
format = format.replace("%z", timezoneoffset);
|
973
|
+
|
974
|
+
return format;
|
975
|
+
};
|
976
|
+
|
977
|
+
// Convert the given dateString into a formatted date.
|
978
|
+
I18n.toTime = function(scope, dateString) {
|
979
|
+
var date = this.parseDate(dateString)
|
980
|
+
, format = this.lookup(scope)
|
981
|
+
;
|
982
|
+
|
983
|
+
if (date.toString().match(/invalid/i)) {
|
984
|
+
return date.toString();
|
985
|
+
}
|
986
|
+
|
987
|
+
if (!format) {
|
988
|
+
return date.toString();
|
989
|
+
}
|
990
|
+
|
991
|
+
return this.strftime(date, format);
|
992
|
+
};
|
993
|
+
|
994
|
+
// Convert a number into a formatted percentage value.
|
995
|
+
I18n.toPercentage = function(number, options) {
|
996
|
+
options = this.prepareOptions(
|
997
|
+
options
|
998
|
+
, this.lookup("number.percentage.format")
|
999
|
+
, this.lookup("number.format")
|
1000
|
+
, PERCENTAGE_FORMAT
|
1001
|
+
);
|
1002
|
+
|
1003
|
+
return this.toNumber(number, options);
|
1004
|
+
};
|
1005
|
+
|
1006
|
+
// Convert a number into a readable size representation.
|
1007
|
+
I18n.toHumanSize = function(number, options) {
|
1008
|
+
var kb = 1024
|
1009
|
+
, size = number
|
1010
|
+
, iterations = 0
|
1011
|
+
, unit
|
1012
|
+
, precision
|
1013
|
+
;
|
1014
|
+
|
1015
|
+
while (size >= kb && iterations < 4) {
|
1016
|
+
size = size / kb;
|
1017
|
+
iterations += 1;
|
1018
|
+
}
|
1019
|
+
|
1020
|
+
if (iterations === 0) {
|
1021
|
+
unit = this.t("number.human.storage_units.units.byte", {count: size});
|
1022
|
+
precision = 0;
|
1023
|
+
} else {
|
1024
|
+
unit = this.t("number.human.storage_units.units." + SIZE_UNITS[iterations]);
|
1025
|
+
precision = (size - Math.floor(size) === 0) ? 0 : 1;
|
1026
|
+
}
|
1027
|
+
|
1028
|
+
options = this.prepareOptions(
|
1029
|
+
options
|
1030
|
+
, {unit: unit, precision: precision, format: "%n%u", delimiter: ""}
|
1031
|
+
);
|
1032
|
+
|
1033
|
+
return this.toNumber(size, options);
|
1034
|
+
};
|
1035
|
+
|
1036
|
+
I18n.getFullScope = function(scope, options) {
|
1037
|
+
options = options || {}
|
1038
|
+
|
1039
|
+
// Deal with the scope as an array.
|
1040
|
+
if (isArray(scope)) {
|
1041
|
+
scope = scope.join(this.defaultSeparator);
|
1042
|
+
}
|
1043
|
+
|
1044
|
+
// Deal with the scope option provided through the second argument.
|
1045
|
+
//
|
1046
|
+
// I18n.t('hello', {scope: 'greetings'});
|
1047
|
+
//
|
1048
|
+
if (options.scope) {
|
1049
|
+
scope = [options.scope, scope].join(this.defaultSeparator);
|
1050
|
+
}
|
1051
|
+
|
1052
|
+
return scope;
|
1053
|
+
};
|
1054
|
+
/**
|
1055
|
+
* Merge obj1 with obj2 (shallow merge), without modifying inputs
|
1056
|
+
* @param {Object} obj1
|
1057
|
+
* @param {Object} obj2
|
1058
|
+
* @returns {Object} Merged values of obj1 and obj2
|
1059
|
+
*
|
1060
|
+
* In order to support ES3, `Object.prototype.hasOwnProperty.call` is used
|
1061
|
+
* Idea is from:
|
1062
|
+
* https://stackoverflow.com/questions/8157700/object-has-no-hasownproperty-method-i-e-its-undefined-ie8
|
1063
|
+
*/
|
1064
|
+
I18n.extend = function ( obj1, obj2 ) {
|
1065
|
+
if (typeof(obj1) === "undefined" && typeof(obj2) === "undefined") {
|
1066
|
+
return {};
|
1067
|
+
}
|
1068
|
+
return merge(obj1, obj2);
|
1069
|
+
};
|
1070
|
+
|
1071
|
+
// Set aliases, so we can save some typing.
|
1072
|
+
I18n.t = I18n.translate;
|
1073
|
+
I18n.l = I18n.localize;
|
1074
|
+
I18n.p = I18n.pluralize;
|
1075
|
+
|
1076
|
+
return I18n;
|
1077
|
+
}));
|