PageSourceSearch

https://www.wiremaze.com/assets/wm-smile/javascripts/i18n-16c1564f97.js

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

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.