PageSourceSearch

https://www.viddsee.com/assets/i18n.js

js viddsee.com collected 2026-10-01 10:34:32 UTC 33,243 bytes, 1,077 lines download raw bytes

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", "
138November", "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 invali
924d 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}));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.