diff options
| author | Luc Donnet | 2018-05-15 15:29:10 +0200 | 
|---|---|---|
| committer | GitHub | 2018-05-15 15:29:10 +0200 | 
| commit | fc2aa2640185a081231d2f726bb345e5bd7709a9 (patch) | |
| tree | bbf8632c24d01faee28a1a209b3e84f0041a275e /spec/javascript/support/i18n.js | |
| parent | 0ea92f772d7eb8facd7cedd50d50531e25664af5 (diff) | |
| parent | 342b883c73c08227fec95484c01b84c19cc0b626 (diff) | |
| download | chouette-core-fc2aa2640185a081231d2f726bb345e5bd7709a9.tar.bz2 | |
Merge pull request #567 from af83/6998-fix-jest-specs
6998 Fix JS specs
Diffstat (limited to 'spec/javascript/support/i18n.js')
| -rw-r--r-- | spec/javascript/support/i18n.js | 1067 | 
1 files changed, 1067 insertions, 0 deletions
| diff --git a/spec/javascript/support/i18n.js b/spec/javascript/support/i18n.js new file mode 100644 index 000000000..b6f14687d --- /dev/null +++ b/spec/javascript/support/i18n.js @@ -0,0 +1,1067 @@ +// I18n.js +// ======= +// +// This small library provides the Rails I18n API on the Javascript. +// You don't actually have to use Rails (or even Ruby) to use I18n.js. +// Just make sure you export all translations in an object like this: +// +//     I18n.translations.en = { +//       hello: "Hello World" +//     }; +// +// See tests for specific formatting like numbers and dates. +// + +// Using UMD pattern from +// https://github.com/umdjs/umd#regular-module +// `returnExports.js` version +;(function (root, factory) { +  if (typeof define === 'function' && define.amd) { +    // AMD. Register as an anonymous module. +    define("i18n", function(){ return factory(root);}); +  } else if (typeof module === 'object' && module.exports) { +    // Node. Does not work with strict CommonJS, but +    // only CommonJS-like environments that support module.exports, +    // like Node. +    module.exports = factory(root); +  } else { +    // Browser globals (root is window) +    root.I18n = factory(root); +  } +}(this, function(global) { +  "use strict"; + +  // Use previously defined object if exists in current scope +  var I18n = global && global.I18n || {}; + +  // Just cache the Array#slice function. +  var slice = Array.prototype.slice; + +  // Apply number padding. +  var padding = function(number) { +    return ("0" + number.toString()).substr(-2); +  }; + +  // Improved toFixed number rounding function with support for unprecise floating points +  // JavaScript's standard toFixed function does not round certain numbers correctly (for example 0.105 with precision 2). +  var toFixed = function(number, precision) { +    return decimalAdjust('round', number, -precision).toFixed(precision); +  }; + +  // Is a given variable an object? +  // Borrowed from Underscore.js +  var isObject = function(obj) { +    var type = typeof obj; +    return type === 'function' || type === 'object' +  }; + +  var isFunction = function(func) { +    var type = typeof func; +    return type === 'function' +  }; + +  // Check if value is different than undefined and null; +  var isSet = function(value) { +    return typeof(value) !== 'undefined' && value !== null; +  }; + +  // Is a given value an array? +  // Borrowed from Underscore.js +  var isArray = function(val) { +    if (Array.isArray) { +      return Array.isArray(val); +    }; +    return Object.prototype.toString.call(val) === '[object Array]'; +  }; + +  var isString = function(val) { +    return typeof value == 'string' || Object.prototype.toString.call(val) === '[object String]'; +  }; + +  var isNumber = function(val) { +    return typeof val == 'number' || Object.prototype.toString.call(val) === '[object Number]'; +  }; + +  var isBoolean = function(val) { +    return val === true || val === false; +  }; + +  var decimalAdjust = function(type, value, exp) { +    // If the exp is undefined or zero... +    if (typeof exp === 'undefined' || +exp === 0) { +      return Math[type](value); +    } +    value = +value; +    exp = +exp; +    // If the value is not a number or the exp is not an integer... +    if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) { +      return NaN; +    } +    // Shift +    value = value.toString().split('e'); +    value = Math[type](+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp))); +    // Shift back +    value = value.toString().split('e'); +    return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp)); +  } + +  var lazyEvaluate = function(message, scope) { +    if (isFunction(message)) { +      return message(scope); +    } else { +      return message; +    } +  } + +  var merge = function (dest, obj) { +    var key, value; +    for (key in obj) if (obj.hasOwnProperty(key)) { +      value = obj[key]; +      if (isString(value) || isNumber(value) || isBoolean(value) || isArray(value)) { +        dest[key] = value; +      } else { +        if (dest[key] == null) dest[key] = {}; +        merge(dest[key], value); +      } +    } +    return dest; +  }; + +  // Set default days/months translations. +  var DATE = { +      day_names: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] +    , abbr_day_names: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] +    , month_names: [null, "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] +    , abbr_month_names: [null, "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] +    , meridian: ["AM", "PM"] +  }; + +  // Set default number format. +  var NUMBER_FORMAT = { +      precision: 3 +    , separator: "." +    , delimiter: "," +    , strip_insignificant_zeros: false +  }; + +  // Set default currency format. +  var CURRENCY_FORMAT = { +      unit: "$" +    , precision: 2 +    , format: "%u%n" +    , sign_first: true +    , delimiter: "," +    , separator: "." +  }; + +  // Set default percentage format. +  var PERCENTAGE_FORMAT = { +      unit: "%" +    , precision: 3 +    , format: "%n%u" +    , separator: "." +    , delimiter: "" +  }; + +  // Set default size units. +  var SIZE_UNITS = [null, "kb", "mb", "gb", "tb"]; + +  // Other default options +  var DEFAULT_OPTIONS = { +    // Set default locale. This locale will be used when fallback is enabled and +    // the translation doesn't exist in a particular locale. +      defaultLocale: "en" +    // Set the current locale to `en`. +    , locale: "en" +    // Set the translation key separator. +    , defaultSeparator: "." +    // Set the placeholder format. Accepts `{{placeholder}}` and `%{placeholder}`. +    , placeholder: /(?:\{\{|%\{)(.*?)(?:\}\}?)/gm +    // Set if engine should fallback to the default locale when a translation +    // is missing. +    , fallbacks: false +    // Set the default translation object. +    , translations: {} +    // Set missing translation behavior. 'message' will display a message +    // that the translation is missing, 'guess' will try to guess the string +    , missingBehaviour: 'message' +    // if you use missingBehaviour with 'message', but want to know that the +    // string is actually missing for testing purposes, you can prefix the +    // guessed string by setting the value here. By default, no prefix! +    , missingTranslationPrefix: '' +  }; + +  // Set default locale. This locale will be used when fallback is enabled and +  // the translation doesn't exist in a particular locale. +  I18n.reset = function() { +    var key; +    for (key in DEFAULT_OPTIONS) { +      this[key] = DEFAULT_OPTIONS[key]; +    } +  }; + +  // Much like `reset`, but only assign options if not already assigned +  I18n.initializeOptions = function() { +    var key; +    for (key in DEFAULT_OPTIONS) if (!isSet(this[key])) { +      this[key] = DEFAULT_OPTIONS[key]; +    } +  }; +  I18n.initializeOptions(); + +  // Return a list of all locales that must be tried before returning the +  // missing translation message. By default, this will consider the inline option, +  // current locale and fallback locale. +  // +  //     I18n.locales.get("de-DE"); +  //     // ["de-DE", "de", "en"] +  // +  // You can define custom rules for any locale. Just make sure you return a array +  // containing all locales. +  // +  //     // Default the Wookie locale to English. +  //     I18n.locales["wk"] = function(locale) { +  //       return ["en"]; +  //     }; +  // +  I18n.locales = {}; + +  // Retrieve locales based on inline locale, current locale or default to +  // I18n's detection. +  I18n.locales.get = function(locale) { +    var result = this[locale] || this[I18n.locale] || this["default"]; + +    if (isFunction(result)) { +      result = result(locale); +    } + +    if (isArray(result) === false) { +      result = [result]; +    } + +    return result; +  }; + +  // The default locale list. +  I18n.locales["default"] = function(locale) { +    var locales = [] +      , list = [] +    ; + +    // Handle the inline locale option that can be provided to +    // the `I18n.t` options. +    if (locale) { +      locales.push(locale); +    } + +    // Add the current locale to the list. +    if (!locale && I18n.locale) { +      locales.push(I18n.locale); +    } + +    // Add the default locale if fallback strategy is enabled. +    if (I18n.fallbacks && I18n.defaultLocale) { +      locales.push(I18n.defaultLocale); +    } + +    // Locale code format 1: +    // According to RFC4646 (http://www.ietf.org/rfc/rfc4646.txt) +    // language codes for Traditional Chinese should be `zh-Hant` +    // +    // But due to backward compatibility +    // We use older version of IETF language tag +    // @see http://www.w3.org/TR/html401/struct/dirlang.html +    // @see http://en.wikipedia.org/wiki/IETF_language_tag +    // +    // Format: `language-code = primary-code ( "-" subcode )*` +    // +    // primary-code uses ISO639-1 +    // @see http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes +    // @see http://www.iso.org/iso/home/standards/language_codes.htm +    // +    // subcode uses ISO 3166-1 alpha-2 +    // @see http://en.wikipedia.org/wiki/ISO_3166 +    // @see http://www.iso.org/iso/country_codes.htm +    // +    // @note +    //   subcode can be in upper case or lower case +    //   defining it in upper case is a convention only + + +    // Locale code format 2: +    // Format: `code = primary-code ( "-" region-code )*` +    // primary-code uses ISO 639-1 +    // script-code uses ISO 15924 +    // region-code uses ISO 3166-1 alpha-2 +    // Example: zh-Hant-TW, en-HK, zh-Hant-CN +    // +    // It is similar to RFC4646 (or actually the same), +    // but seems to be limited to language, script, region + +    // Compute each locale with its country code. +    // So this will return an array containing +    // `de-DE` and `de` +    // or +    // `zh-hans-tw`, `zh-hans`, `zh` +    // locales. +    locales.forEach(function(locale) { +      var localeParts = locale.split("-"); +      var firstFallback = null; +      var secondFallback = null; +      if (localeParts.length === 3) { +        firstFallback = [ +          localeParts[0], +          localeParts[1] +        ].join("-"); +        secondFallback = localeParts[0]; +      } +      else if (localeParts.length === 2) { +        firstFallback = localeParts[0]; +      } + +      if (list.indexOf(locale) === -1) { +        list.push(locale); +      } + +      if (! I18n.fallbacks) { +        return; +      } + +      [ +        firstFallback, +        secondFallback +      ].forEach(function(nullableFallbackLocale) { +        // We don't want null values +        if (typeof nullableFallbackLocale === "undefined") { return; } +        if (nullableFallbackLocale === null) { return; } +        // We don't want duplicate values +        // +        // Comparing with `locale` first is faster than +        // checking whether value's presence in the list +        if (nullableFallbackLocale === locale) { return; } +        if (list.indexOf(nullableFallbackLocale) !== -1) { return; } + +        list.push(nullableFallbackLocale); +      }); +    }); + +    // No locales set? English it is. +    if (!locales.length) { +      locales.push("en"); +    } + +    return list; +  }; + +  // Hold pluralization rules. +  I18n.pluralization = {}; + +  // Return the pluralizer for a specific locale. +  // If no specify locale is found, then I18n's default will be used. +  I18n.pluralization.get = function(locale) { +    return this[locale] || this[I18n.locale] || this["default"]; +  }; + +  // The default pluralizer rule. +  // It detects the `zero`, `one`, and `other` scopes. +  I18n.pluralization["default"] = function(count) { +    switch (count) { +      case 0: return ["zero", "other"]; +      case 1: return ["one"]; +      default: return ["other"]; +    } +  }; + +  // Return current locale. If no locale has been set, then +  // the current locale will be the default locale. +  I18n.currentLocale = function() { +    return this.locale || this.defaultLocale; +  }; + +  // Check if value is different than undefined and null; +  I18n.isSet = isSet; + +  // Find and process the translation using the provided scope and options. +  // This is used internally by some functions and should not be used as an +  // public API. +  I18n.lookup = function(scope, options) { +    options = options || {} + +    var locales = this.locales.get(options.locale).slice() +      , requestedLocale = locales[0] +      , locale +      , scopes +      , fullScope +      , translations +    ; + +    fullScope = this.getFullScope(scope, options); + +    while (locales.length) { +      locale = locales.shift(); +      scopes = fullScope.split(this.defaultSeparator); +      translations = this.translations[locale]; + +      if (!translations) { +        continue; +      } +      while (scopes.length) { +        translations = translations[scopes.shift()]; + +        if (translations === undefined || translations === null) { +          break; +        } +      } + +      if (translations !== undefined && translations !== null) { +        return translations; +      } +    } + +    if (isSet(options.defaultValue)) { +      return lazyEvaluate(options.defaultValue, scope); +    } +  }; + +  // lookup pluralization rule key into translations +  I18n.pluralizationLookupWithoutFallback = function(count, locale, translations) { +    var pluralizer = this.pluralization.get(locale) +      , pluralizerKeys = pluralizer(count) +      , pluralizerKey +      , message; + +    if (isObject(translations)) { +      while (pluralizerKeys.length) { +        pluralizerKey = pluralizerKeys.shift(); +        if (isSet(translations[pluralizerKey])) { +          message = translations[pluralizerKey]; +          break; +        } +      } +    } + +    return message; +  }; + +  // Lookup dedicated to pluralization +  I18n.pluralizationLookup = function(count, scope, options) { +    options = options || {} +    var locales = this.locales.get(options.locale).slice() +      , requestedLocale = locales[0] +      , locale +      , scopes +      , translations +      , message +    ; +    scope = this.getFullScope(scope, options); + +    while (locales.length) { +      locale = locales.shift(); +      scopes = scope.split(this.defaultSeparator); +      translations = this.translations[locale]; + +      if (!translations) { +        continue; +      } + +      while (scopes.length) { +        translations = translations[scopes.shift()]; +        if (!isObject(translations)) { +          break; +        } +        if (scopes.length == 0) { +          message = this.pluralizationLookupWithoutFallback(count, locale, translations); +        } +      } +      if (message != null && message != undefined) { +        break; +      } +    } + +    if (message == null || message == undefined) { +      if (isSet(options.defaultValue)) { +        if (isObject(options.defaultValue)) { +          message = this.pluralizationLookupWithoutFallback(count, options.locale, options.defaultValue); +        } else { +          message = options.defaultValue; +        } +        translations = options.defaultValue; +      } +    } + +    return { message: message, translations: translations }; +  }; + +  // Rails changed the way the meridian is stored. +  // It started with `date.meridian` returning an array, +  // then it switched to `time.am` and `time.pm`. +  // This function abstracts this difference and returns +  // the correct meridian or the default value when none is provided. +  I18n.meridian = function() { +    var time = this.lookup("time"); +    var date = this.lookup("date"); + +    if (time && time.am && time.pm) { +      return [time.am, time.pm]; +    } else if (date && date.meridian) { +      return date.meridian; +    } else { +      return DATE.meridian; +    } +  }; + +  // Merge serveral hash options, checking if value is set before +  // overwriting any value. The precedence is from left to right. +  // +  //     I18n.prepareOptions({name: "John Doe"}, {name: "Mary Doe", role: "user"}); +  //     #=> {name: "John Doe", role: "user"} +  // +  I18n.prepareOptions = function() { +    var args = slice.call(arguments) +      , options = {} +      , subject +    ; + +    while (args.length) { +      subject = args.shift(); + +      if (typeof(subject) != "object") { +        continue; +      } + +      for (var attr in subject) { +        if (!subject.hasOwnProperty(attr)) { +          continue; +        } + +        if (isSet(options[attr])) { +          continue; +        } + +        options[attr] = subject[attr]; +      } +    } + +    return options; +  }; + +  // Generate a list of translation options for default fallbacks. +  // `defaultValue` is also deleted from options as it is returned as part of +  // the translationOptions array. +  I18n.createTranslationOptions = function(scope, options) { +    var translationOptions = [{scope: scope}]; + +    // Defaults should be an array of hashes containing either +    // fallback scopes or messages +    if (isSet(options.defaults)) { +      translationOptions = translationOptions.concat(options.defaults); +    } + +    // Maintain support for defaultValue. Since it is always a message +    // insert it in to the translation options as such. +    if (isSet(options.defaultValue)) { +      translationOptions.push({ message: options.defaultValue }); +    } + +    return translationOptions; +  }; + +  // Translate the given scope with the provided options. +  I18n.translate = function(scope, options) { +    options = options || {} + +    var translationOptions = this.createTranslationOptions(scope, options); + +    var translation; + +    var optionsWithoutDefault = this.prepareOptions(options) +    delete optionsWithoutDefault.defaultValue + +    // Iterate through the translation options until a translation +    // or message is found. +    var translationFound = +      translationOptions.some(function(translationOption) { +        if (isSet(translationOption.scope)) { +          translation = this.lookup(translationOption.scope, optionsWithoutDefault); +        } else if (isSet(translationOption.message)) { +          translation = lazyEvaluate(translationOption.message, scope); +        } + +        if (translation !== undefined && translation !== null) { +          return true; +        } +      }, this); + +    if (!translationFound) { +      return this.missingTranslation(scope, options); +    } + +    if (typeof(translation) === "string") { +      translation = this.interpolate(translation, options); +    } else if (isObject(translation) && isSet(options.count)) { +      translation = this.pluralize(options.count, scope, options); +    } + +    return translation; +  }; + +  // This function interpolates the all variables in the given message. +  I18n.interpolate = function(message, options) { +    options = options || {} +    var matches = message.match(this.placeholder) +      , placeholder +      , value +      , name +      , regex +    ; + +    if (!matches) { +      return message; +    } + +    var value; + +    while (matches.length) { +      placeholder = matches.shift(); +      name = placeholder.replace(this.placeholder, "$1"); + +      if (isSet(options[name])) { +        value = options[name].toString().replace(/\$/gm, "_#$#_"); +      } else if (name in options) { +        value = this.nullPlaceholder(placeholder, message, options); +      } else { +        value = this.missingPlaceholder(placeholder, message, options); +      } + +      regex = new RegExp(placeholder.replace(/\{/gm, "\\{").replace(/\}/gm, "\\}")); +      message = message.replace(regex, value); +    } + +    return message.replace(/_#\$#_/g, "$"); +  }; + +  // Pluralize the given scope using the `count` value. +  // The pluralized translation may have other placeholders, +  // which will be retrieved from `options`. +  I18n.pluralize = function(count, scope, options) { +    options = this.prepareOptions({count: String(count)}, options) +    var pluralizer, message, result; + +    result = this.pluralizationLookup(count, scope, options); +    if (result.translations == undefined || result.translations == null) { +      return this.missingTranslation(scope, options); +    } + +    if (result.message != undefined && result.message != null) { +      return this.interpolate(result.message, options); +    } +    else { +      pluralizer = this.pluralization.get(options.locale); +      return this.missingTranslation(scope + '.' + pluralizer(count)[0], options); +    } +  }; + +  // Return a missing translation message for the given parameters. +  I18n.missingTranslation = function(scope, options) { +    //guess intended string +    if(this.missingBehaviour == 'guess'){ +      //get only the last portion of the scope +      var s = scope.split('.').slice(-1)[0]; +      //replace underscore with space && camelcase with space and lowercase letter +      return (this.missingTranslationPrefix.length > 0 ? this.missingTranslationPrefix : '') + +          s.replace('_',' ').replace(/([a-z])([A-Z])/g, +          function(match, p1, p2) {return p1 + ' ' + p2.toLowerCase()} ); +    } + +    var localeForTranslation = (options != null && options.locale != null) ? options.locale : this.currentLocale(); +    var fullScope           = this.getFullScope(scope, options); +    var fullScopeWithLocale = [localeForTranslation, fullScope].join(this.defaultSeparator); + +    return '[missing "' + fullScopeWithLocale + '" translation]'; +  }; + +  // Return a missing placeholder message for given parameters +  I18n.missingPlaceholder = function(placeholder, message, options) { +    return "[missing " + placeholder + " value]"; +  }; + +  I18n.nullPlaceholder = function() { +    return I18n.missingPlaceholder.apply(I18n, arguments); +  }; + +  // Format number using localization rules. +  // The options will be retrieved from the `number.format` scope. +  // If this isn't present, then the following options will be used: +  // +  // - `precision`: `3` +  // - `separator`: `"."` +  // - `delimiter`: `","` +  // - `strip_insignificant_zeros`: `false` +  // +  // You can also override these options by providing the `options` argument. +  // +  I18n.toNumber = function(number, options) { +    options = this.prepareOptions( +        options +      , this.lookup("number.format") +      , NUMBER_FORMAT +    ); + +    var negative = number < 0 +      , string = toFixed(Math.abs(number), options.precision).toString() +      , parts = string.split(".") +      , precision +      , buffer = [] +      , formattedNumber +      , format = options.format || "%n" +      , sign = negative ? "-" : "" +    ; + +    number = parts[0]; +    precision = parts[1]; + +    while (number.length > 0) { +      buffer.unshift(number.substr(Math.max(0, number.length - 3), 3)); +      number = number.substr(0, number.length -3); +    } + +    formattedNumber = buffer.join(options.delimiter); + +    if (options.strip_insignificant_zeros && precision) { +      precision = precision.replace(/0+$/, ""); +    } + +    if (options.precision > 0 && precision) { +      formattedNumber += options.separator + precision; +    } + +    if (options.sign_first) { +      format = "%s" + format; +    } +    else { +      format = format.replace("%n", "%s%n"); +    } + +    formattedNumber = format +      .replace("%u", options.unit) +      .replace("%n", formattedNumber) +      .replace("%s", sign) +    ; + +    return formattedNumber; +  }; + +  // Format currency with localization rules. +  // The options will be retrieved from the `number.currency.format` and +  // `number.format` scopes, in that order. +  // +  // Any missing option will be retrieved from the `I18n.toNumber` defaults and +  // the following options: +  // +  // - `unit`: `"$"` +  // - `precision`: `2` +  // - `format`: `"%u%n"` +  // - `delimiter`: `","` +  // - `separator`: `"."` +  // +  // You can also override these options by providing the `options` argument. +  // +  I18n.toCurrency = function(number, options) { +    options = this.prepareOptions( +        options +      , this.lookup("number.currency.format") +      , this.lookup("number.format") +      , CURRENCY_FORMAT +    ); + +    return this.toNumber(number, options); +  }; + +  // Localize several values. +  // You can provide the following scopes: `currency`, `number`, or `percentage`. +  // If you provide a scope that matches the `/^(date|time)/` regular expression +  // then the `value` will be converted by using the `I18n.toTime` function. +  // +  // It will default to the value's `toString` function. +  // +  I18n.localize = function(scope, value, options) { +    options || (options = {}); + +    switch (scope) { +      case "currency": +        return this.toCurrency(value); +      case "number": +        scope = this.lookup("number.format"); +        return this.toNumber(value, scope); +      case "percentage": +        return this.toPercentage(value); +      default: +        var localizedValue; + +        if (scope.match(/^(date|time)/)) { +          localizedValue = this.toTime(scope, value); +        } else { +          localizedValue = value.toString(); +        } + +        return this.interpolate(localizedValue, options); +    } +  }; + +  // Parse a given `date` string into a JavaScript Date object. +  // This function is time zone aware. +  // +  // The following string formats are recognized: +  // +  //    yyyy-mm-dd +  //    yyyy-mm-dd[ T]hh:mm::ss +  //    yyyy-mm-dd[ T]hh:mm::ss +  //    yyyy-mm-dd[ T]hh:mm::ssZ +  //    yyyy-mm-dd[ T]hh:mm::ss+0000 +  //    yyyy-mm-dd[ T]hh:mm::ss+00:00 +  //    yyyy-mm-dd[ T]hh:mm::ss.123Z +  // +  I18n.parseDate = function(date) { +    var matches, convertedDate, fraction; +    // we have a date, so just return it. +    if (typeof(date) == "object") { +      return date; +    }; + +    matches = date.toString().match(/(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}):(\d{2})([\.,]\d{1,3})?)?(Z|\+00:?00)?/); + +    if (matches) { +      for (var i = 1; i <= 6; i++) { +        matches[i] = parseInt(matches[i], 10) || 0; +      } + +      // month starts on 0 +      matches[2] -= 1; + +      fraction = matches[7] ? 1000 * ("0" + matches[7]) : null; + +      if (matches[8]) { +        convertedDate = new Date(Date.UTC(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6], fraction)); +      } else { +        convertedDate = new Date(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6], fraction); +      } +    } else if (typeof(date) == "number") { +      // UNIX timestamp +      convertedDate = new Date(); +      convertedDate.setTime(date); +    } else if (date.match(/([A-Z][a-z]{2}) ([A-Z][a-z]{2}) (\d+) (\d+:\d+:\d+) ([+-]\d+) (\d+)/)) { +      // This format `Wed Jul 20 13:03:39 +0000 2011` is parsed by +      // webkit/firefox, but not by IE, so we must parse it manually. +      convertedDate = new Date(); +      convertedDate.setTime(Date.parse([ +        RegExp.$1, RegExp.$2, RegExp.$3, RegExp.$6, RegExp.$4, RegExp.$5 +      ].join(" "))); +    } else if (date.match(/\d+ \d+:\d+:\d+ [+-]\d+ \d+/)) { +      // a valid javascript format with timezone info +      convertedDate = new Date(); +      convertedDate.setTime(Date.parse(date)); +    } else { +      // an arbitrary javascript string +      convertedDate = new Date(); +      convertedDate.setTime(Date.parse(date)); +    } + +    return convertedDate; +  }; + +  // Formats time according to the directives in the given format string. +  // The directives begins with a percent (%) character. Any text not listed as a +  // directive will be passed through to the output string. +  // +  // The accepted formats are: +  // +  //     %a  - The abbreviated weekday name (Sun) +  //     %A  - The full weekday name (Sunday) +  //     %b  - The abbreviated month name (Jan) +  //     %B  - The full month name (January) +  //     %c  - The preferred local date and time representation +  //     %d  - Day of the month (01..31) +  //     %-d - Day of the month (1..31) +  //     %H  - Hour of the day, 24-hour clock (00..23) +  //     %-H - Hour of the day, 24-hour clock (0..23) +  //     %I  - Hour of the day, 12-hour clock (01..12) +  //     %-I - Hour of the day, 12-hour clock (1..12) +  //     %m  - Month of the year (01..12) +  //     %-m - Month of the year (1..12) +  //     %M  - Minute of the hour (00..59) +  //     %-M - Minute of the hour (0..59) +  //     %p  - Meridian indicator (AM  or  PM) +  //     %S  - Second of the minute (00..60) +  //     %-S - Second of the minute (0..60) +  //     %w  - Day of the week (Sunday is 0, 0..6) +  //     %y  - Year without a century (00..99) +  //     %-y - Year without a century (0..99) +  //     %Y  - Year with century +  //     %z  - Timezone offset (+0545) +  // +  I18n.strftime = function(date, format) { +    var options = this.lookup("date") +      , meridianOptions = I18n.meridian() +    ; + +    if (!options) { +      options = {}; +    } + +    options = this.prepareOptions(options, DATE); + +    if (isNaN(date.getTime())) { +      throw new Error('I18n.strftime() requires a valid date object, but received an invalid date.'); +    } + +    var weekDay = date.getDay() +      , day = date.getDate() +      , year = date.getFullYear() +      , month = date.getMonth() + 1 +      , hour = date.getHours() +      , hour12 = hour +      , meridian = hour > 11 ? 1 : 0 +      , secs = date.getSeconds() +      , mins = date.getMinutes() +      , offset = date.getTimezoneOffset() +      , absOffsetHours = Math.floor(Math.abs(offset / 60)) +      , absOffsetMinutes = Math.abs(offset) - (absOffsetHours * 60) +      , timezoneoffset = (offset > 0 ? "-" : "+") + +          (absOffsetHours.toString().length < 2 ? "0" + absOffsetHours : absOffsetHours) + +          (absOffsetMinutes.toString().length < 2 ? "0" + absOffsetMinutes : absOffsetMinutes) +    ; + +    if (hour12 > 12) { +      hour12 = hour12 - 12; +    } else if (hour12 === 0) { +      hour12 = 12; +    } + +    format = format.replace("%a", options.abbr_day_names[weekDay]); +    format = format.replace("%A", options.day_names[weekDay]); +    format = format.replace("%b", options.abbr_month_names[month]); +    format = format.replace("%B", options.month_names[month]); +    format = format.replace("%d", padding(day)); +    format = format.replace("%e", day); +    format = format.replace("%-d", day); +    format = format.replace("%H", padding(hour)); +    format = format.replace("%-H", hour); +    format = format.replace("%I", padding(hour12)); +    format = format.replace("%-I", hour12); +    format = format.replace("%m", padding(month)); +    format = format.replace("%-m", month); +    format = format.replace("%M", padding(mins)); +    format = format.replace("%-M", mins); +    format = format.replace("%p", meridianOptions[meridian]); +    format = format.replace("%S", padding(secs)); +    format = format.replace("%-S", secs); +    format = format.replace("%w", weekDay); +    format = format.replace("%y", padding(year)); +    format = format.replace("%-y", padding(year).replace(/^0+/, "")); +    format = format.replace("%Y", year); +    format = format.replace("%z", timezoneoffset); + +    return format; +  }; + +  // Convert the given dateString into a formatted date. +  I18n.toTime = function(scope, dateString) { +    var date = this.parseDate(dateString) +      , format = this.lookup(scope) +    ; + +    if (date.toString().match(/invalid/i)) { +      return date.toString(); +    } + +    if (!format) { +      return date.toString(); +    } + +    return this.strftime(date, format); +  }; + +  // Convert a number into a formatted percentage value. +  I18n.toPercentage = function(number, options) { +    options = this.prepareOptions( +        options +      , this.lookup("number.percentage.format") +      , this.lookup("number.format") +      , PERCENTAGE_FORMAT +    ); + +    return this.toNumber(number, options); +  }; + +  // Convert a number into a readable size representation. +  I18n.toHumanSize = function(number, options) { +    var kb = 1024 +      , size = number +      , iterations = 0 +      , unit +      , precision +    ; + +    while (size >= kb && iterations < 4) { +      size = size / kb; +      iterations += 1; +    } + +    if (iterations === 0) { +      unit = this.t("number.human.storage_units.units.byte", {count: size}); +      precision = 0; +    } else { +      unit = this.t("number.human.storage_units.units." + SIZE_UNITS[iterations]); +      precision = (size - Math.floor(size) === 0) ? 0 : 1; +    } + +    options = this.prepareOptions( +        options +      , {unit: unit, precision: precision, format: "%n%u", delimiter: ""} +    ); + +    return this.toNumber(size, options); +  }; + +  I18n.getFullScope = function(scope, options) { +    options = options || {} + +    // Deal with the scope as an array. +    if (isArray(scope)) { +      scope = scope.join(this.defaultSeparator); +    } + +    // Deal with the scope option provided through the second argument. +    // +    //    I18n.t('hello', {scope: 'greetings'}); +    // +    if (options.scope) { +      scope = [options.scope, scope].join(this.defaultSeparator); +    } + +    return scope; +  }; +  /** +   * Merge obj1 with obj2 (shallow merge), without modifying inputs +   * @param {Object} obj1 +   * @param {Object} obj2 +   * @returns {Object} Merged values of obj1 and obj2 +   * +   * In order to support ES3, `Object.prototype.hasOwnProperty.call` is used +   * Idea is from: +   * https://stackoverflow.com/questions/8157700/object-has-no-hasownproperty-method-i-e-its-undefined-ie8 +   */ +  I18n.extend = function ( obj1, obj2 ) { +    if (typeof(obj1) === "undefined" && typeof(obj2) === "undefined") { +      return {}; +    } +    return merge(obj1, obj2); +  }; + +  // Set aliases, so we can save some typing. +  I18n.t = I18n.translate; +  I18n.l = I18n.localize; +  I18n.p = I18n.pluralize; + +  return I18n; +})); | 
