PageSourceSearch

https://caseinpointmethod.com/javascripts/i18n.js?1790610245

js caseinpointmethod.com collected 2026-10-04 16:30:41 UTC 22,411 bytes, 751 lines download raw bytes

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

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.