arnaucube
arnaucube/commonroutesAppPublic Self-hosted repository
commonroutesApp/www/lib/angular-translate/angular-translate.js
www/lib/angular-translate/angular-translate.jsRaw
   1/*!
   2 * angular-translate - v2.12.1 - 2016-09-15
   3 * 
   4 * Copyright (c) 2016 The angular-translate team, Pascal Precht; Licensed MIT
   5 */
   6(function (root, factory) {
   7  if (typeof define === 'function' && define.amd) {
   8    // AMD. Register as an anonymous module unless amdModuleId is set
   9    define([], function () {
  10      return (factory());
  11    });
  12  } else if (typeof exports === 'object') {
  13    // Node. Does not work with strict CommonJS, but
  14    // only CommonJS-like environments that support module.exports,
  15    // like Node.
  16    module.exports = factory();
  17  } else {
  18    factory();
  19  }
  20}(this, function () {
  21
  22/**
  23 * @ngdoc overview
  24 * @name pascalprecht.translate
  25 *
  26 * @description
  27 * The main module which holds everything together.
  28 */
  29runTranslate.$inject = ['$translate'];
  30$translate.$inject = ['$STORAGE_KEY', '$windowProvider', '$translateSanitizationProvider', 'pascalprechtTranslateOverrider'];
  31$translateDefaultInterpolation.$inject = ['$interpolate', '$translateSanitization'];
  32translateDirective.$inject = ['$translate', '$interpolate', '$compile', '$parse', '$rootScope'];
  33translateAttrDirective.$inject = ['$translate', '$rootScope'];
  34translateCloakDirective.$inject = ['$translate', '$rootScope'];
  35translateFilterFactory.$inject = ['$parse', '$translate'];
  36$translationCache.$inject = ['$cacheFactory'];
  37angular.module('pascalprecht.translate', ['ng'])
  38  .run(runTranslate);
  39
  40function runTranslate($translate) {
  41
  42  'use strict';
  43
  44  var key = $translate.storageKey(),
  45    storage = $translate.storage();
  46
  47  var fallbackFromIncorrectStorageValue = function () {
  48    var preferred = $translate.preferredLanguage();
  49    if (angular.isString(preferred)) {
  50      $translate.use(preferred);
  51      // $translate.use() will also remember the language.
  52      // So, we don't need to call storage.put() here.
  53    } else {
  54      storage.put(key, $translate.use());
  55    }
  56  };
  57
  58  fallbackFromIncorrectStorageValue.displayName = 'fallbackFromIncorrectStorageValue';
  59
  60  if (storage) {
  61    if (!storage.get(key)) {
  62      fallbackFromIncorrectStorageValue();
  63    } else {
  64      $translate.use(storage.get(key))['catch'](fallbackFromIncorrectStorageValue);
  65    }
  66  } else if (angular.isString($translate.preferredLanguage())) {
  67    $translate.use($translate.preferredLanguage());
  68  }
  69}
  70
  71runTranslate.displayName = 'runTranslate';
  72
  73/**
  74 * @ngdoc object
  75 * @name pascalprecht.translate.$translateSanitizationProvider
  76 *
  77 * @description
  78 *
  79 * Configurations for $translateSanitization
  80 */
  81angular.module('pascalprecht.translate').provider('$translateSanitization', $translateSanitizationProvider);
  82
  83function $translateSanitizationProvider () {
  84
  85  'use strict';
  86
  87  var $sanitize,
  88      $sce,
  89      currentStrategy = null, // TODO change to either 'sanitize', 'escape' or ['sanitize', 'escapeParameters'] in 3.0.
  90      hasConfiguredStrategy = false,
  91      hasShownNoStrategyConfiguredWarning = false,
  92      strategies;
  93
  94  /**
  95   * Definition of a sanitization strategy function
  96   * @callback StrategyFunction
  97   * @param {string|object} value - value to be sanitized (either a string or an interpolated value map)
  98   * @param {string} mode - either 'text' for a string (translation) or 'params' for the interpolated params
  99   * @return {string|object}
 100   */
 101
 102  /**
 103   * @ngdoc property
 104   * @name strategies
 105   * @propertyOf pascalprecht.translate.$translateSanitizationProvider
 106   *
 107   * @description
 108   * Following strategies are built-in:
 109   * <dl>
 110   *   <dt>sanitize</dt>
 111   *   <dd>Sanitizes HTML in the translation text using $sanitize</dd>
 112   *   <dt>escape</dt>
 113   *   <dd>Escapes HTML in the translation</dd>
 114   *   <dt>sanitizeParameters</dt>
 115   *   <dd>Sanitizes HTML in the values of the interpolation parameters using $sanitize</dd>
 116   *   <dt>escapeParameters</dt>
 117   *   <dd>Escapes HTML in the values of the interpolation parameters</dd>
 118   *   <dt>escaped</dt>
 119   *   <dd>Support legacy strategy name 'escaped' for backwards compatibility (will be removed in 3.0)</dd>
 120   * </dl>
 121   *
 122   */
 123
 124  strategies = {
 125    sanitize: function (value, mode/*, context*/) {
 126      if (mode === 'text') {
 127        value = htmlSanitizeValue(value);
 128      }
 129      return value;
 130    },
 131    escape: function (value, mode/*, context*/) {
 132      if (mode === 'text') {
 133        value = htmlEscapeValue(value);
 134      }
 135      return value;
 136    },
 137    sanitizeParameters: function (value, mode/*, context*/) {
 138      if (mode === 'params') {
 139        value = mapInterpolationParameters(value, htmlSanitizeValue);
 140      }
 141      return value;
 142    },
 143    escapeParameters: function (value, mode/*, context*/) {
 144      if (mode === 'params') {
 145        value = mapInterpolationParameters(value, htmlEscapeValue);
 146      }
 147      return value;
 148    },
 149    sce: function (value, mode, context) {
 150      if (mode === 'text') {
 151        value = htmlTrustValue(value);
 152      } else if (mode === 'params') {
 153        if (context !== 'filter') {
 154          // do html escape in filter context #1101
 155          value = mapInterpolationParameters(value, htmlEscapeValue);
 156        }
 157      }
 158      return value;
 159    },
 160    sceParameters: function (value, mode/*, context*/) {
 161      if (mode === 'params') {
 162        value = mapInterpolationParameters(value, htmlTrustValue);
 163      }
 164      return value;
 165    }
 166  };
 167  // Support legacy strategy name 'escaped' for backwards compatibility.
 168  // TODO should be removed in 3.0
 169  strategies.escaped = strategies.escapeParameters;
 170
 171  /**
 172   * @ngdoc function
 173   * @name pascalprecht.translate.$translateSanitizationProvider#addStrategy
 174   * @methodOf pascalprecht.translate.$translateSanitizationProvider
 175   *
 176   * @description
 177   * Adds a sanitization strategy to the list of known strategies.
 178   *
 179   * @param {string} strategyName - unique key for a strategy
 180   * @param {StrategyFunction} strategyFunction - strategy function
 181   * @returns {object} this
 182   */
 183  this.addStrategy = function (strategyName, strategyFunction) {
 184    strategies[strategyName] = strategyFunction;
 185    return this;
 186  };
 187
 188  /**
 189   * @ngdoc function
 190   * @name pascalprecht.translate.$translateSanitizationProvider#removeStrategy
 191   * @methodOf pascalprecht.translate.$translateSanitizationProvider
 192   *
 193   * @description
 194   * Removes a sanitization strategy from the list of known strategies.
 195   *
 196   * @param {string} strategyName - unique key for a strategy
 197   * @returns {object} this
 198   */
 199  this.removeStrategy = function (strategyName) {
 200    delete strategies[strategyName];
 201    return this;
 202  };
 203
 204  /**
 205   * @ngdoc function
 206   * @name pascalprecht.translate.$translateSanitizationProvider#useStrategy
 207   * @methodOf pascalprecht.translate.$translateSanitizationProvider
 208   *
 209   * @description
 210   * Selects a sanitization strategy. When an array is provided the strategies will be executed in order.
 211   *
 212   * @param {string|StrategyFunction|array} strategy The sanitization strategy / strategies which should be used. Either a name of an existing strategy, a custom strategy function, or an array consisting of multiple names and / or custom functions.
 213   * @returns {object} this
 214   */
 215  this.useStrategy = function (strategy) {
 216    hasConfiguredStrategy = true;
 217    currentStrategy = strategy;
 218    return this;
 219  };
 220
 221  /**
 222   * @ngdoc object
 223   * @name pascalprecht.translate.$translateSanitization
 224   * @requires $injector
 225   * @requires $log
 226   *
 227   * @description
 228   * Sanitizes interpolation parameters and translated texts.
 229   *
 230   */
 231  this.$get = ['$injector', '$log', function ($injector, $log) {
 232
 233    var cachedStrategyMap = {};
 234
 235    var applyStrategies = function (value, mode, context, selectedStrategies) {
 236      angular.forEach(selectedStrategies, function (selectedStrategy) {
 237        if (angular.isFunction(selectedStrategy)) {
 238          value = selectedStrategy(value, mode, context);
 239        } else if (angular.isFunction(strategies[selectedStrategy])) {
 240          value = strategies[selectedStrategy](value, mode, context);
 241        } else if (angular.isString(strategies[selectedStrategy])) {
 242          if (!cachedStrategyMap[strategies[selectedStrategy]]) {
 243            try {
 244              cachedStrategyMap[strategies[selectedStrategy]] = $injector.get(strategies[selectedStrategy]);
 245            } catch (e) {
 246              cachedStrategyMap[strategies[selectedStrategy]] = function() {};
 247              throw new Error('pascalprecht.translate.$translateSanitization: Unknown sanitization strategy: \'' + selectedStrategy + '\'');
 248            }
 249          }
 250          value = cachedStrategyMap[strategies[selectedStrategy]](value, mode, context);
 251        } else {
 252          throw new Error('pascalprecht.translate.$translateSanitization: Unknown sanitization strategy: \'' + selectedStrategy + '\'');
 253        }
 254      });
 255      return value;
 256    };
 257
 258    // TODO: should be removed in 3.0
 259    var showNoStrategyConfiguredWarning = function () {
 260      if (!hasConfiguredStrategy && !hasShownNoStrategyConfiguredWarning) {
 261        $log.warn('pascalprecht.translate.$translateSanitization: No sanitization strategy has been configured. This can have serious security implications. See http://angular-translate.github.io/docs/#/guide/19_security for details.');
 262        hasShownNoStrategyConfiguredWarning = true;
 263      }
 264    };
 265
 266    if ($injector.has('$sanitize')) {
 267      $sanitize = $injector.get('$sanitize');
 268    }
 269    if ($injector.has('$sce')) {
 270      $sce = $injector.get('$sce');
 271    }
 272
 273    return {
 274      /**
 275       * @ngdoc function
 276       * @name pascalprecht.translate.$translateSanitization#useStrategy
 277       * @methodOf pascalprecht.translate.$translateSanitization
 278       *
 279       * @description
 280       * Selects a sanitization strategy. When an array is provided the strategies will be executed in order.
 281       *
 282       * @param {string|StrategyFunction|array} strategy The sanitization strategy / strategies which should be used. Either a name of an existing strategy, a custom strategy function, or an array consisting of multiple names and / or custom functions.
 283       */
 284      useStrategy: (function (self) {
 285        return function (strategy) {
 286          self.useStrategy(strategy);
 287        };
 288      })(this),
 289
 290      /**
 291       * @ngdoc function
 292       * @name pascalprecht.translate.$translateSanitization#sanitize
 293       * @methodOf pascalprecht.translate.$translateSanitization
 294       *
 295       * @description
 296       * Sanitizes a value.
 297       *
 298       * @param {string|object} value The value which should be sanitized.
 299       * @param {string} mode The current sanitization mode, either 'params' or 'text'.
 300       * @param {string|StrategyFunction|array} [strategy] Optional custom strategy which should be used instead of the currently selected strategy.
 301       * @param {string} [context] The context of this call: filter, service. Default is service
 302       * @returns {string|object} sanitized value
 303       */
 304      sanitize: function (value, mode, strategy, context) {
 305        if (!currentStrategy) {
 306          showNoStrategyConfiguredWarning();
 307        }
 308
 309        if (!strategy && strategy !== null) {
 310          strategy = currentStrategy;
 311        }
 312
 313        if (!strategy) {
 314          return value;
 315        }
 316
 317        if (!context) {
 318          context = 'service';
 319        }
 320
 321        var selectedStrategies = angular.isArray(strategy) ? strategy : [strategy];
 322        return applyStrategies(value, mode, context, selectedStrategies);
 323      }
 324    };
 325  }];
 326
 327  var htmlEscapeValue = function (value) {
 328    var element = angular.element('<div></div>');
 329    element.text(value); // not chainable, see #1044
 330    return element.html();
 331  };
 332
 333  var htmlSanitizeValue = function (value) {
 334    if (!$sanitize) {
 335      throw new Error('pascalprecht.translate.$translateSanitization: Error cannot find $sanitize service. Either include the ngSanitize module (https://docs.angularjs.org/api/ngSanitize) or use a sanitization strategy which does not depend on $sanitize, such as \'escape\'.');
 336    }
 337    return $sanitize(value);
 338  };
 339
 340  var htmlTrustValue = function (value) {
 341    if (!$sce) {
 342      throw new Error('pascalprecht.translate.$translateSanitization: Error cannot find $sce service.');
 343    }
 344    return $sce.trustAsHtml(value);
 345  };
 346
 347  var mapInterpolationParameters = function (value, iteratee, stack) {
 348    if (angular.isDate(value)) {
 349      return value;
 350    } else if (angular.isObject(value)) {
 351      var result = angular.isArray(value) ? [] : {};
 352
 353      if (!stack) {
 354        stack = [];
 355      } else {
 356        if (stack.indexOf(value) > -1) {
 357          throw new Error('pascalprecht.translate.$translateSanitization: Error cannot interpolate parameter due recursive object');
 358        }
 359      }
 360
 361      stack.push(value);
 362      angular.forEach(value, function (propertyValue, propertyKey) {
 363
 364        /* Skipping function properties. */
 365        if (angular.isFunction(propertyValue)) {
 366          return;
 367        }
 368
 369        result[propertyKey] = mapInterpolationParameters(propertyValue, iteratee, stack);
 370      });
 371      stack.splice(-1, 1); // remove last
 372
 373      return result;
 374    } else if (angular.isNumber(value)) {
 375      return value;
 376    } else {
 377      return iteratee(value);
 378    }
 379  };
 380}
 381
 382/**
 383 * @ngdoc object
 384 * @name pascalprecht.translate.$translateProvider
 385 * @description
 386 *
 387 * $translateProvider allows developers to register translation-tables, asynchronous loaders
 388 * and similar to configure translation behavior directly inside of a module.
 389 *
 390 */
 391angular.module('pascalprecht.translate')
 392.constant('pascalprechtTranslateOverrider', {})
 393.provider('$translate', $translate);
 394
 395function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvider, pascalprechtTranslateOverrider) {
 396
 397  'use strict';
 398
 399  var $translationTable = {},
 400      $preferredLanguage,
 401      $availableLanguageKeys = [],
 402      $languageKeyAliases,
 403      $fallbackLanguage,
 404      $fallbackWasString,
 405      $uses,
 406      $nextLang,
 407      $storageFactory,
 408      $storageKey = $STORAGE_KEY,
 409      $storagePrefix,
 410      $missingTranslationHandlerFactory,
 411      $interpolationFactory,
 412      $interpolatorFactories = [],
 413      $loaderFactory,
 414      $cloakClassName = 'translate-cloak',
 415      $loaderOptions,
 416      $notFoundIndicatorLeft,
 417      $notFoundIndicatorRight,
 418      $postCompilingEnabled = false,
 419      $forceAsyncReloadEnabled = false,
 420      $nestedObjectDelimeter = '.',
 421      $isReady = false,
 422      $keepContent = false,
 423      loaderCache,
 424      directivePriority = 0,
 425      statefulFilter = true,
 426      postProcessFn,
 427      uniformLanguageTagResolver = 'default',
 428      languageTagResolver = {
 429        'default': function (tag) {
 430          return (tag || '').split('-').join('_');
 431        },
 432        java: function (tag) {
 433          var temp = (tag || '').split('-').join('_');
 434          var parts = temp.split('_');
 435          return parts.length > 1 ? (parts[0].toLowerCase() + '_' + parts[1].toUpperCase()) : temp;
 436        },
 437        bcp47: function (tag) {
 438          var temp = (tag || '').split('_').join('-');
 439          var parts = temp.split('-');
 440          return parts.length > 1 ? (parts[0].toLowerCase() + '-' + parts[1].toUpperCase()) : temp;
 441        },
 442        'iso639-1': function (tag) {
 443          var temp = (tag || '').split('_').join('-');
 444          var parts = temp.split('-');
 445          return parts[0].toLowerCase();
 446        }
 447      };
 448
 449  var version = '2.12.1';
 450
 451  // tries to determine the browsers language
 452  var getFirstBrowserLanguage = function () {
 453
 454    // internal purpose only
 455    if (angular.isFunction(pascalprechtTranslateOverrider.getLocale)) {
 456      return pascalprechtTranslateOverrider.getLocale();
 457    }
 458
 459    var nav = $windowProvider.$get().navigator,
 460        browserLanguagePropertyKeys = ['language', 'browserLanguage', 'systemLanguage', 'userLanguage'],
 461        i,
 462        language;
 463
 464    // support for HTML 5.1 "navigator.languages"
 465    if (angular.isArray(nav.languages)) {
 466      for (i = 0; i < nav.languages.length; i++) {
 467        language = nav.languages[i];
 468        if (language && language.length) {
 469          return language;
 470        }
 471      }
 472    }
 473
 474    // support for other well known properties in browsers
 475    for (i = 0; i < browserLanguagePropertyKeys.length; i++) {
 476      language = nav[browserLanguagePropertyKeys[i]];
 477      if (language && language.length) {
 478        return language;
 479      }
 480    }
 481
 482    return null;
 483  };
 484  getFirstBrowserLanguage.displayName = 'angular-translate/service: getFirstBrowserLanguage';
 485
 486  // tries to determine the browsers locale
 487  var getLocale = function () {
 488    var locale = getFirstBrowserLanguage() || '';
 489    if (languageTagResolver[uniformLanguageTagResolver]) {
 490      locale = languageTagResolver[uniformLanguageTagResolver](locale);
 491    }
 492    return locale;
 493  };
 494  getLocale.displayName = 'angular-translate/service: getLocale';
 495
 496  /**
 497   * @name indexOf
 498   * @private
 499   *
 500   * @description
 501   * indexOf polyfill. Kinda sorta.
 502   *
 503   * @param {array} array Array to search in.
 504   * @param {string} searchElement Element to search for.
 505   *
 506   * @returns {int} Index of search element.
 507   */
 508  var indexOf = function(array, searchElement) {
 509    for (var i = 0, len = array.length; i < len; i++) {
 510      if (array[i] === searchElement) {
 511        return i;
 512      }
 513    }
 514    return -1;
 515  };
 516
 517  /**
 518   * @name trim
 519   * @private
 520   *
 521   * @description
 522   * trim polyfill
 523   *
 524   * @returns {string} The string stripped of whitespace from both ends
 525   */
 526  var trim = function() {
 527    return this.toString().replace(/^\s+|\s+$/g, '');
 528  };
 529
 530  var negotiateLocale = function (preferred) {
 531    if(!preferred) {
 532      return;
 533    }
 534
 535    var avail = [],
 536        locale = angular.lowercase(preferred),
 537        i = 0,
 538        n = $availableLanguageKeys.length;
 539
 540    for (; i < n; i++) {
 541      avail.push(angular.lowercase($availableLanguageKeys[i]));
 542    }
 543
 544    // Check for an exact match in our list of available keys
 545    if (indexOf(avail, locale) > -1) {
 546      return preferred;
 547    }
 548
 549    if ($languageKeyAliases) {
 550      var alias;
 551      for (var langKeyAlias in $languageKeyAliases) {
 552        if ($languageKeyAliases.hasOwnProperty(langKeyAlias)) {
 553          var hasWildcardKey = false;
 554          var hasExactKey = Object.prototype.hasOwnProperty.call($languageKeyAliases, langKeyAlias) &&
 555            angular.lowercase(langKeyAlias) === angular.lowercase(preferred);
 556
 557          if (langKeyAlias.slice(-1) === '*') {
 558            hasWildcardKey = langKeyAlias.slice(0, -1) === preferred.slice(0, langKeyAlias.length - 1);
 559          }
 560          if (hasExactKey || hasWildcardKey) {
 561            alias = $languageKeyAliases[langKeyAlias];
 562            if (indexOf(avail, angular.lowercase(alias)) > -1) {
 563              return alias;
 564            }
 565          }
 566        }
 567      }
 568    }
 569
 570    // Check for a language code without region
 571    var parts = preferred.split('_');
 572
 573    if (parts.length > 1 && indexOf(avail, angular.lowercase(parts[0])) > -1) {
 574      return parts[0];
 575    }
 576
 577    // If everything fails, return undefined.
 578    return;
 579  };
 580
 581  /**
 582   * @ngdoc function
 583   * @name pascalprecht.translate.$translateProvider#translations
 584   * @methodOf pascalprecht.translate.$translateProvider
 585   *
 586   * @description
 587   * Registers a new translation table for specific language key.
 588   *
 589   * To register a translation table for specific language, pass a defined language
 590   * key as first parameter.
 591   *
 592   * <pre>
 593   *  // register translation table for language: 'de_DE'
 594   *  $translateProvider.translations('de_DE', {
 595   *    'GREETING': 'Hallo Welt!'
 596   *  });
 597   *
 598   *  // register another one
 599   *  $translateProvider.translations('en_US', {
 600   *    'GREETING': 'Hello world!'
 601   *  });
 602   * </pre>
 603   *
 604   * When registering multiple translation tables for for the same language key,
 605   * the actual translation table gets extended. This allows you to define module
 606   * specific translation which only get added, once a specific module is loaded in
 607   * your app.
 608   *
 609   * Invoking this method with no arguments returns the translation table which was
 610   * registered with no language key. Invoking it with a language key returns the
 611   * related translation table.
 612   *
 613   * @param {string} langKey A language key.
 614   * @param {object} translationTable A plain old JavaScript object that represents a translation table.
 615   *
 616   */
 617  var translations = function (langKey, translationTable) {
 618
 619    if (!langKey && !translationTable) {
 620      return $translationTable;
 621    }
 622
 623    if (langKey && !translationTable) {
 624      if (angular.isString(langKey)) {
 625        return $translationTable[langKey];
 626      }
 627    } else {
 628      if (!angular.isObject($translationTable[langKey])) {
 629        $translationTable[langKey] = {};
 630      }
 631      angular.extend($translationTable[langKey], flatObject(translationTable));
 632    }
 633    return this;
 634  };
 635
 636  this.translations = translations;
 637
 638  /**
 639   * @ngdoc function
 640   * @name pascalprecht.translate.$translateProvider#cloakClassName
 641   * @methodOf pascalprecht.translate.$translateProvider
 642   *
 643   * @description
 644   *
 645   * Let's you change the class name for `translate-cloak` directive.
 646   * Default class name is `translate-cloak`.
 647   *
 648   * @param {string} name translate-cloak class name
 649   */
 650  this.cloakClassName = function (name) {
 651    if (!name) {
 652      return $cloakClassName;
 653    }
 654    $cloakClassName = name;
 655    return this;
 656  };
 657
 658  /**
 659   * @ngdoc function
 660   * @name pascalprecht.translate.$translateProvider#nestedObjectDelimeter
 661   * @methodOf pascalprecht.translate.$translateProvider
 662   *
 663   * @description
 664   *
 665   * Let's you change the delimiter for namespaced translations.
 666   * Default delimiter is `.`.
 667   *
 668   * @param {string} delimiter namespace separator
 669   */
 670  this.nestedObjectDelimeter = function (delimiter) {
 671    if (!delimiter) {
 672      return $nestedObjectDelimeter;
 673    }
 674    $nestedObjectDelimeter = delimiter;
 675    return this;
 676  };
 677
 678  /**
 679   * @name flatObject
 680   * @private
 681   *
 682   * @description
 683   * Flats an object. This function is used to flatten given translation data with
 684   * namespaces, so they are later accessible via dot notation.
 685   */
 686  var flatObject = function (data, path, result, prevKey) {
 687    var key, keyWithPath, keyWithShortPath, val;
 688
 689    if (!path) {
 690      path = [];
 691    }
 692    if (!result) {
 693      result = {};
 694    }
 695    for (key in data) {
 696      if (!Object.prototype.hasOwnProperty.call(data, key)) {
 697        continue;
 698      }
 699      val = data[key];
 700      if (angular.isObject(val)) {
 701        flatObject(val, path.concat(key), result, key);
 702      } else {
 703        keyWithPath = path.length ? ('' + path.join($nestedObjectDelimeter) + $nestedObjectDelimeter + key) : key;
 704        if(path.length && key === prevKey){
 705          // Create shortcut path (foo.bar == foo.bar.bar)
 706          keyWithShortPath = '' + path.join($nestedObjectDelimeter);
 707          // Link it to original path
 708          result[keyWithShortPath] = '@:' + keyWithPath;
 709        }
 710        result[keyWithPath] = val;
 711      }
 712    }
 713    return result;
 714  };
 715  flatObject.displayName = 'flatObject';
 716
 717  /**
 718   * @ngdoc function
 719   * @name pascalprecht.translate.$translateProvider#addInterpolation
 720   * @methodOf pascalprecht.translate.$translateProvider
 721   *
 722   * @description
 723   * Adds interpolation services to angular-translate, so it can manage them.
 724   *
 725   * @param {object} factory Interpolation service factory
 726   */
 727  this.addInterpolation = function (factory) {
 728    $interpolatorFactories.push(factory);
 729    return this;
 730  };
 731
 732  /**
 733   * @ngdoc function
 734   * @name pascalprecht.translate.$translateProvider#useMessageFormatInterpolation
 735   * @methodOf pascalprecht.translate.$translateProvider
 736   *
 737   * @description
 738   * Tells angular-translate to use interpolation functionality of messageformat.js.
 739   * This is useful when having high level pluralization and gender selection.
 740   */
 741  this.useMessageFormatInterpolation = function () {
 742    return this.useInterpolation('$translateMessageFormatInterpolation');
 743  };
 744
 745  /**
 746   * @ngdoc function
 747   * @name pascalprecht.translate.$translateProvider#useInterpolation
 748   * @methodOf pascalprecht.translate.$translateProvider
 749   *
 750   * @description
 751   * Tells angular-translate which interpolation style to use as default, application-wide.
 752   * Simply pass a factory/service name. The interpolation service has to implement
 753   * the correct interface.
 754   *
 755   * @param {string} factory Interpolation service name.
 756   */
 757  this.useInterpolation = function (factory) {
 758    $interpolationFactory = factory;
 759    return this;
 760  };
 761
 762  /**
 763   * @ngdoc function
 764   * @name pascalprecht.translate.$translateProvider#useSanitizeStrategy
 765   * @methodOf pascalprecht.translate.$translateProvider
 766   *
 767   * @description
 768   * Simply sets a sanitation strategy type.
 769   *
 770   * @param {string} value Strategy type.
 771   */
 772  this.useSanitizeValueStrategy = function (value) {
 773    $translateSanitizationProvider.useStrategy(value);
 774    return this;
 775  };
 776
 777 /**
 778   * @ngdoc function
 779   * @name pascalprecht.translate.$translateProvider#preferredLanguage
 780   * @methodOf pascalprecht.translate.$translateProvider
 781   *
 782   * @description
 783   * Tells the module which of the registered translation tables to use for translation
 784   * at initial startup by passing a language key. Similar to `$translateProvider#use`
 785   * only that it says which language to **prefer**.
 786   *
 787   * @param {string} langKey A language key.
 788   */
 789  this.preferredLanguage = function(langKey) {
 790    if (langKey) {
 791      setupPreferredLanguage(langKey);
 792      return this;
 793    }
 794    return $preferredLanguage;
 795  };
 796  var setupPreferredLanguage = function (langKey) {
 797    if (langKey) {
 798      $preferredLanguage = langKey;
 799    }
 800    return $preferredLanguage;
 801  };
 802  /**
 803   * @ngdoc function
 804   * @name pascalprecht.translate.$translateProvider#translationNotFoundIndicator
 805   * @methodOf pascalprecht.translate.$translateProvider
 806   *
 807   * @description
 808   * Sets an indicator which is used when a translation isn't found. E.g. when
 809   * setting the indicator as 'X' and one tries to translate a translation id
 810   * called `NOT_FOUND`, this will result in `X NOT_FOUND X`.
 811   *
 812   * Internally this methods sets a left indicator and a right indicator using
 813   * `$translateProvider.translationNotFoundIndicatorLeft()` and
 814   * `$translateProvider.translationNotFoundIndicatorRight()`.
 815   *
 816   * **Note**: These methods automatically add a whitespace between the indicators
 817   * and the translation id.
 818   *
 819   * @param {string} indicator An indicator, could be any string.
 820   */
 821  this.translationNotFoundIndicator = function (indicator) {
 822    this.translationNotFoundIndicatorLeft(indicator);
 823    this.translationNotFoundIndicatorRight(indicator);
 824    return this;
 825  };
 826
 827  /**
 828   * ngdoc function
 829   * @name pascalprecht.translate.$translateProvider#translationNotFoundIndicatorLeft
 830   * @methodOf pascalprecht.translate.$translateProvider
 831   *
 832   * @description
 833   * Sets an indicator which is used when a translation isn't found left to the
 834   * translation id.
 835   *
 836   * @param {string} indicator An indicator.
 837   */
 838  this.translationNotFoundIndicatorLeft = function (indicator) {
 839    if (!indicator) {
 840      return $notFoundIndicatorLeft;
 841    }
 842    $notFoundIndicatorLeft = indicator;
 843    return this;
 844  };
 845
 846  /**
 847   * ngdoc function
 848   * @name pascalprecht.translate.$translateProvider#translationNotFoundIndicatorLeft
 849   * @methodOf pascalprecht.translate.$translateProvider
 850   *
 851   * @description
 852   * Sets an indicator which is used when a translation isn't found right to the
 853   * translation id.
 854   *
 855   * @param {string} indicator An indicator.
 856   */
 857  this.translationNotFoundIndicatorRight = function (indicator) {
 858    if (!indicator) {
 859      return $notFoundIndicatorRight;
 860    }
 861    $notFoundIndicatorRight = indicator;
 862    return this;
 863  };
 864
 865  /**
 866   * @ngdoc function
 867   * @name pascalprecht.translate.$translateProvider#fallbackLanguage
 868   * @methodOf pascalprecht.translate.$translateProvider
 869   *
 870   * @description
 871   * Tells the module which of the registered translation tables to use when missing translations
 872   * at initial startup by passing a language key. Similar to `$translateProvider#use`
 873   * only that it says which language to **fallback**.
 874   *
 875   * @param {string||array} langKey A language key.
 876   *
 877   */
 878  this.fallbackLanguage = function (langKey) {
 879    fallbackStack(langKey);
 880    return this;
 881  };
 882
 883  var fallbackStack = function (langKey) {
 884    if (langKey) {
 885      if (angular.isString(langKey)) {
 886        $fallbackWasString = true;
 887        $fallbackLanguage = [ langKey ];
 888      } else if (angular.isArray(langKey)) {
 889        $fallbackWasString = false;
 890        $fallbackLanguage = langKey;
 891      }
 892      if (angular.isString($preferredLanguage)  && indexOf($fallbackLanguage, $preferredLanguage) < 0) {
 893        $fallbackLanguage.push($preferredLanguage);
 894      }
 895
 896      return this;
 897    } else {
 898      if ($fallbackWasString) {
 899        return $fallbackLanguage[0];
 900      } else {
 901        return $fallbackLanguage;
 902      }
 903    }
 904  };
 905
 906  /**
 907   * @ngdoc function
 908   * @name pascalprecht.translate.$translateProvider#use
 909   * @methodOf pascalprecht.translate.$translateProvider
 910   *
 911   * @description
 912   * Set which translation table to use for translation by given language key. When
 913   * trying to 'use' a language which isn't provided, it'll throw an error.
 914   *
 915   * You actually don't have to use this method since `$translateProvider#preferredLanguage`
 916   * does the job too.
 917   *
 918   * @param {string} langKey A language key.
 919   */
 920  this.use = function (langKey) {
 921    if (langKey) {
 922      if (!$translationTable[langKey] && (!$loaderFactory)) {
 923        // only throw an error, when not loading translation data asynchronously
 924        throw new Error('$translateProvider couldn\'t find translationTable for langKey: \'' + langKey + '\'');
 925      }
 926      $uses = langKey;
 927      return this;
 928    }
 929    return $uses;
 930  };
 931
 932  /**
 933   * @ngdoc function
 934   * @name pascalprecht.translate.$translateProvider#resolveClientLocale
 935   * @methodOf pascalprecht.translate.$translateProvider
 936   *
 937   * @description
 938   * This returns the current browser/client's language key. The result is processed with the configured uniform tag resolver.
 939   *
 940   * @returns {string} the current client/browser language key
 941   */
 942  this.resolveClientLocale = function () {
 943    return getLocale();
 944  };
 945
 946 /**
 947   * @ngdoc function
 948   * @name pascalprecht.translate.$translateProvider#storageKey
 949   * @methodOf pascalprecht.translate.$translateProvider
 950   *
 951   * @description
 952   * Tells the module which key must represent the choosed language by a user in the storage.
 953   *
 954   * @param {string} key A key for the storage.
 955   */
 956  var storageKey = function(key) {
 957    if (!key) {
 958      if ($storagePrefix) {
 959        return $storagePrefix + $storageKey;
 960      }
 961      return $storageKey;
 962    }
 963    $storageKey = key;
 964    return this;
 965  };
 966
 967  this.storageKey = storageKey;
 968
 969  /**
 970   * @ngdoc function
 971   * @name pascalprecht.translate.$translateProvider#useUrlLoader
 972   * @methodOf pascalprecht.translate.$translateProvider
 973   *
 974   * @description
 975   * Tells angular-translate to use `$translateUrlLoader` extension service as loader.
 976   *
 977   * @param {string} url Url
 978   * @param {Object=} options Optional configuration object
 979   */
 980  this.useUrlLoader = function (url, options) {
 981    return this.useLoader('$translateUrlLoader', angular.extend({ url: url }, options));
 982  };
 983
 984  /**
 985   * @ngdoc function
 986   * @name pascalprecht.translate.$translateProvider#useStaticFilesLoader
 987   * @methodOf pascalprecht.translate.$translateProvider
 988   *
 989   * @description
 990   * Tells angular-translate to use `$translateStaticFilesLoader` extension service as loader.
 991   *
 992   * @param {Object=} options Optional configuration object
 993   */
 994  this.useStaticFilesLoader = function (options) {
 995    return this.useLoader('$translateStaticFilesLoader', options);
 996  };
 997
 998  /**
 999   * @ngdoc function
1000   * @name pascalprecht.translate.$translateProvider#useLoader
1001   * @methodOf pascalprecht.translate.$translateProvider
1002   *
1003   * @description
1004   * Tells angular-translate to use any other service as loader.
1005   *
1006   * @param {string} loaderFactory Factory name to use
1007   * @param {Object=} options Optional configuration object
1008   */
1009  this.useLoader = function (loaderFactory, options) {
1010    $loaderFactory = loaderFactory;
1011    $loaderOptions = options || {};
1012    return this;
1013  };
1014
1015  /**
1016   * @ngdoc function
1017   * @name pascalprecht.translate.$translateProvider#useLocalStorage
1018   * @methodOf pascalprecht.translate.$translateProvider
1019   *
1020   * @description
1021   * Tells angular-translate to use `$translateLocalStorage` service as storage layer.
1022   *
1023   */
1024  this.useLocalStorage = function () {
1025    return this.useStorage('$translateLocalStorage');
1026  };
1027
1028  /**
1029   * @ngdoc function
1030   * @name pascalprecht.translate.$translateProvider#useCookieStorage
1031   * @methodOf pascalprecht.translate.$translateProvider
1032   *
1033   * @description
1034   * Tells angular-translate to use `$translateCookieStorage` service as storage layer.
1035   */
1036  this.useCookieStorage = function () {
1037    return this.useStorage('$translateCookieStorage');
1038  };
1039
1040  /**
1041   * @ngdoc function
1042   * @name pascalprecht.translate.$translateProvider#useStorage
1043   * @methodOf pascalprecht.translate.$translateProvider
1044   *
1045   * @description
1046   * Tells angular-translate to use custom service as storage layer.
1047   */
1048  this.useStorage = function (storageFactory) {
1049    $storageFactory = storageFactory;
1050    return this;
1051  };
1052
1053  /**
1054   * @ngdoc function
1055   * @name pascalprecht.translate.$translateProvider#storagePrefix
1056   * @methodOf pascalprecht.translate.$translateProvider
1057   *
1058   * @description
1059   * Sets prefix for storage key.
1060   *
1061   * @param {string} prefix Storage key prefix
1062   */
1063  this.storagePrefix = function (prefix) {
1064    if (!prefix) {
1065      return prefix;
1066    }
1067    $storagePrefix = prefix;
1068    return this;
1069  };
1070
1071  /**
1072   * @ngdoc function
1073   * @name pascalprecht.translate.$translateProvider#useMissingTranslationHandlerLog
1074   * @methodOf pascalprecht.translate.$translateProvider
1075   *
1076   * @description
1077   * Tells angular-translate to use built-in log handler when trying to translate
1078   * a translation Id which doesn't exist.
1079   *
1080   * This is actually a shortcut method for `useMissingTranslationHandler()`.
1081   *
1082   */
1083  this.useMissingTranslationHandlerLog = function () {
1084    return this.useMissingTranslationHandler('$translateMissingTranslationHandlerLog');
1085  };
1086
1087  /**
1088   * @ngdoc function
1089   * @name pascalprecht.translate.$translateProvider#useMissingTranslationHandler
1090   * @methodOf pascalprecht.translate.$translateProvider
1091   *
1092   * @description
1093   * Expects a factory name which later gets instantiated with `$injector`.
1094   * This method can be used to tell angular-translate to use a custom
1095   * missingTranslationHandler. Just build a factory which returns a function
1096   * and expects a translation id as argument.
1097   *
1098   * Example:
1099   * <pre>
1100   *  app.config(function ($translateProvider) {
1101   *    $translateProvider.useMissingTranslationHandler('customHandler');
1102   *  });
1103   *
1104   *  app.factory('customHandler', function (dep1, dep2) {
1105   *    return function (translationId) {
1106   *      // something with translationId and dep1 and dep2
1107   *    };
1108   *  });
1109   * </pre>
1110   *
1111   * @param {string} factory Factory name
1112   */
1113  this.useMissingTranslationHandler = function (factory) {
1114    $missingTranslationHandlerFactory = factory;
1115    return this;
1116  };
1117
1118  /**
1119   * @ngdoc function
1120   * @name pascalprecht.translate.$translateProvider#usePostCompiling
1121   * @methodOf pascalprecht.translate.$translateProvider
1122   *
1123   * @description
1124   * If post compiling is enabled, all translated values will be processed
1125   * again with AngularJS' $compile.
1126   *
1127   * Example:
1128   * <pre>
1129   *  app.config(function ($translateProvider) {
1130   *    $translateProvider.usePostCompiling(true);
1131   *  });
1132   * </pre>
1133   *
1134   * @param {string} factory Factory name
1135   */
1136  this.usePostCompiling = function (value) {
1137    $postCompilingEnabled = !(!value);
1138    return this;
1139  };
1140
1141  /**
1142   * @ngdoc function
1143   * @name pascalprecht.translate.$translateProvider#forceAsyncReload
1144   * @methodOf pascalprecht.translate.$translateProvider
1145   *
1146   * @description
1147   * If force async reload is enabled, async loader will always be called
1148   * even if $translationTable already contains the language key, adding
1149   * possible new entries to the $translationTable.
1150   *
1151   * Example:
1152   * <pre>
1153   *  app.config(function ($translateProvider) {
1154   *    $translateProvider.forceAsyncReload(true);
1155   *  });
1156   * </pre>
1157   *
1158   * @param {boolean} value - valid values are true or false
1159   */
1160  this.forceAsyncReload = function (value) {
1161    $forceAsyncReloadEnabled = !(!value);
1162    return this;
1163  };
1164
1165  /**
1166   * @ngdoc function
1167   * @name pascalprecht.translate.$translateProvider#uniformLanguageTag
1168   * @methodOf pascalprecht.translate.$translateProvider
1169   *
1170   * @description
1171   * Tells angular-translate which language tag should be used as a result when determining
1172   * the current browser language.
1173   *
1174   * This setting must be set before invoking {@link pascalprecht.translate.$translateProvider#methods_determinePreferredLanguage determinePreferredLanguage()}.
1175   *
1176   * <pre>
1177   * $translateProvider
1178   *   .uniformLanguageTag('bcp47')
1179   *   .determinePreferredLanguage()
1180   * </pre>
1181   *
1182   * The resolver currently supports:
1183   * * default
1184   *     (traditionally: hyphens will be converted into underscores, i.e. en-US => en_US)
1185   *     en-US => en_US
1186   *     en_US => en_US
1187   *     en-us => en_us
1188   * * java
1189   *     like default, but the second part will be always in uppercase
1190   *     en-US => en_US
1191   *     en_US => en_US
1192   *     en-us => en_US
1193   * * BCP 47 (RFC 4646 & 4647)
1194   *     en-US => en-US
1195   *     en_US => en-US
1196   *     en-us => en-US
1197   *
1198   * See also:
1199   * * http://en.wikipedia.org/wiki/IETF_language_tag
1200   * * http://www.w3.org/International/core/langtags/
1201   * * http://tools.ietf.org/html/bcp47
1202   *
1203   * @param {string|object} options - options (or standard)
1204   * @param {string} options.standard - valid values are 'default', 'bcp47', 'java'
1205   */
1206  this.uniformLanguageTag = function (options) {
1207
1208    if (!options) {
1209      options = {};
1210    } else if (angular.isString(options)) {
1211      options = {
1212        standard: options
1213      };
1214    }
1215
1216    uniformLanguageTagResolver = options.standard;
1217
1218    return this;
1219  };
1220
1221  /**
1222   * @ngdoc function
1223   * @name pascalprecht.translate.$translateProvider#determinePreferredLanguage
1224   * @methodOf pascalprecht.translate.$translateProvider
1225   *
1226   * @description
1227   * Tells angular-translate to try to determine on its own which language key
1228   * to set as preferred language. When `fn` is given, angular-translate uses it
1229   * to determine a language key, otherwise it uses the built-in `getLocale()`
1230   * method.
1231   *
1232   * The `getLocale()` returns a language key in the format `[lang]_[country]` or
1233   * `[lang]` depending on what the browser provides.
1234   *
1235   * Use this method at your own risk, since not all browsers return a valid
1236   * locale (see {@link pascalprecht.translate.$translateProvider#methods_uniformLanguageTag uniformLanguageTag()}).
1237   *
1238   * @param {Function=} fn Function to determine a browser's locale
1239   */
1240  this.determinePreferredLanguage = function (fn) {
1241
1242    var locale = (fn && angular.isFunction(fn)) ? fn() : getLocale();
1243
1244    if (!$availableLanguageKeys.length) {
1245      $preferredLanguage = locale;
1246    } else {
1247      $preferredLanguage = negotiateLocale(locale) || locale;
1248    }
1249
1250    return this;
1251  };
1252
1253  /**
1254   * @ngdoc function
1255   * @name pascalprecht.translate.$translateProvider#registerAvailableLanguageKeys
1256   * @methodOf pascalprecht.translate.$translateProvider
1257   *
1258   * @description
1259   * Registers a set of language keys the app will work with. Use this method in
1260   * combination with
1261   * {@link pascalprecht.translate.$translateProvider#determinePreferredLanguage determinePreferredLanguage}.
1262   * When available languages keys are registered, angular-translate
1263   * tries to find the best fitting language key depending on the browsers locale,
1264   * considering your language key convention.
1265   *
1266   * @param {object} languageKeys Array of language keys the your app will use
1267   * @param {object=} aliases Alias map.
1268   */
1269  this.registerAvailableLanguageKeys = function (languageKeys, aliases) {
1270    if (languageKeys) {
1271      $availableLanguageKeys = languageKeys;
1272      if (aliases) {
1273        $languageKeyAliases = aliases;
1274      }
1275      return this;
1276    }
1277    return $availableLanguageKeys;
1278  };
1279
1280  /**
1281   * @ngdoc function
1282   * @name pascalprecht.translate.$translateProvider#useLoaderCache
1283   * @methodOf pascalprecht.translate.$translateProvider
1284   *
1285   * @description
1286   * Registers a cache for internal $http based loaders.
1287   * {@link pascalprecht.translate.$translationCache $translationCache}.
1288   * When false the cache will be disabled (default). When true or undefined
1289   * the cache will be a default (see $cacheFactory). When an object it will
1290   * be treat as a cache object itself: the usage is $http({cache: cache})
1291   *
1292   * @param {object} cache boolean, string or cache-object
1293   */
1294  this.useLoaderCache = function (cache) {
1295    if (cache === false) {
1296      // disable cache
1297      loaderCache = undefined;
1298    } else if (cache === true) {
1299      // enable cache using AJS defaults
1300      loaderCache = true;
1301    } else if (typeof(cache) === 'undefined') {
1302      // enable cache using default
1303      loaderCache = '$translationCache';
1304    } else if (cache) {
1305      // enable cache using given one (see $cacheFactory)
1306      loaderCache = cache;
1307    }
1308    return this;
1309  };
1310
1311  /**
1312   * @ngdoc function
1313   * @name pascalprecht.translate.$translateProvider#directivePriority
1314   * @methodOf pascalprecht.translate.$translateProvider
1315   *
1316   * @description
1317   * Sets the default priority of the translate directive. The standard value is `0`.
1318   * Calling this function without an argument will return the current value.
1319   *
1320   * @param {number} priority for the translate-directive
1321   */
1322  this.directivePriority = function (priority) {
1323    if (priority === undefined) {
1324      // getter
1325      return directivePriority;
1326    } else {
1327      // setter with chaining
1328      directivePriority = priority;
1329      return this;
1330    }
1331  };
1332
1333  /**
1334   * @ngdoc function
1335   * @name pascalprecht.translate.$translateProvider#statefulFilter
1336   * @methodOf pascalprecht.translate.$translateProvider
1337   *
1338   * @description
1339   * Since AngularJS 1.3, filters which are not stateless (depending at the scope)
1340   * have to explicit define this behavior.
1341   * Sets whether the translate filter should be stateful or stateless. The standard value is `true`
1342   * meaning being stateful.
1343   * Calling this function without an argument will return the current value.
1344   *
1345   * @param {boolean} state - defines the state of the filter
1346   */
1347  this.statefulFilter = function (state) {
1348    if (state === undefined) {
1349      // getter
1350      return statefulFilter;
1351    } else {
1352      // setter with chaining
1353      statefulFilter = state;
1354      return this;
1355    }
1356  };
1357
1358  /**
1359   * @ngdoc function
1360   * @name pascalprecht.translate.$translateProvider#postProcess
1361   * @methodOf pascalprecht.translate.$translateProvider
1362   *
1363   * @description
1364   * The post processor will be intercept right after the translation result. It can modify the result.
1365   *
1366   * @param {object} fn Function or service name (string) to be called after the translation value has been set / resolved. The function itself will enrich every value being processed and then continue the normal resolver process
1367   */
1368  this.postProcess = function (fn) {
1369    if (fn) {
1370      postProcessFn = fn;
1371    } else {
1372      postProcessFn = undefined;
1373    }
1374    return this;
1375  };
1376
1377  /**
1378   * @ngdoc function
1379   * @name pascalprecht.translate.$translateProvider#keepContent
1380   * @methodOf pascalprecht.translate.$translateProvider
1381   *
1382   * @description
1383   * If keepContent is set to true than translate directive will always use innerHTML
1384   * as a default translation
1385   *
1386   * Example:
1387   * <pre>
1388   *  app.config(function ($translateProvider) {
1389   *    $translateProvider.keepContent(true);
1390   *  });
1391   * </pre>
1392   *
1393   * @param {boolean} value - valid values are true or false
1394   */
1395  this.keepContent = function (value) {
1396    $keepContent = !(!value);
1397    return this;
1398  };
1399
1400  /**
1401   * @ngdoc object
1402   * @name pascalprecht.translate.$translate
1403   * @requires $interpolate
1404   * @requires $log
1405   * @requires $rootScope
1406   * @requires $q
1407   *
1408   * @description
1409   * The `$translate` service is the actual core of angular-translate. It expects a translation id
1410   * and optional interpolate parameters to translate contents.
1411   *
1412   * <pre>
1413   *  $translate('HEADLINE_TEXT').then(function (translation) {
1414   *    $scope.translatedText = translation;
1415   *  });
1416   * </pre>
1417   *
1418   * @param {string|array} translationId A token which represents a translation id
1419   *                                     This can be optionally an array of translation ids which
1420   *                                     results that the function returns an object where each key
1421   *                                     is the translation id and the value the translation.
1422   * @param {object=} interpolateParams An object hash for dynamic values
1423   * @param {string} interpolationId The id of the interpolation to use
1424   * @param {string} defaultTranslationText the optional default translation text that is written as
1425   *                                        as default text in case it is not found in any configured language
1426   * @param {string} forceLanguage A language to be used instead of the current language
1427   * @returns {object} promise
1428   */
1429  this.$get = [
1430    '$log',
1431    '$injector',
1432    '$rootScope',
1433    '$q',
1434    function ($log, $injector, $rootScope, $q) {
1435
1436      var Storage,
1437          defaultInterpolator = $injector.get($interpolationFactory || '$translateDefaultInterpolation'),
1438          pendingLoader = false,
1439          interpolatorHashMap = {},
1440          langPromises = {},
1441          fallbackIndex,
1442          startFallbackIteration;
1443
1444      var $translate = function (translationId, interpolateParams, interpolationId, defaultTranslationText, forceLanguage) {
1445        if (!$uses && $preferredLanguage) {
1446          $uses = $preferredLanguage;
1447        }
1448        var uses = (forceLanguage && forceLanguage !== $uses) ? // we don't want to re-negotiate $uses
1449              (negotiateLocale(forceLanguage) || forceLanguage) : $uses;
1450
1451        // Check forceLanguage is present
1452        if (forceLanguage) {
1453          loadTranslationsIfMissing(forceLanguage);
1454        }
1455
1456        // Duck detection: If the first argument is an array, a bunch of translations was requested.
1457        // The result is an object.
1458        if (angular.isArray(translationId)) {
1459          // Inspired by Q.allSettled by Kris Kowal
1460          // https://github.com/kriskowal/q/blob/b0fa72980717dc202ffc3cbf03b936e10ebbb9d7/q.js#L1553-1563
1461          // This transforms all promises regardless resolved or rejected
1462          var translateAll = function (translationIds) {
1463            var results = {}; // storing the actual results
1464            var promises = []; // promises to wait for
1465            // Wraps the promise a) being always resolved and b) storing the link id->value
1466            var translate = function (translationId) {
1467              var deferred = $q.defer();
1468              var regardless = function (value) {
1469                results[translationId] = value;
1470                deferred.resolve([translationId, value]);
1471              };
1472              // we don't care whether the promise was resolved or rejected; just store the values
1473              $translate(translationId, interpolateParams, interpolationId, defaultTranslationText, forceLanguage).then(regardless, regardless);
1474              return deferred.promise;
1475            };
1476            for (var i = 0, c = translationIds.length; i < c; i++) {
1477              promises.push(translate(translationIds[i]));
1478            }
1479            // wait for all (including storing to results)
1480            return $q.all(promises).then(function () {
1481              // return the results
1482              return results;
1483            });
1484          };
1485          return translateAll(translationId);
1486        }
1487
1488        var deferred = $q.defer();
1489
1490        // trim off any whitespace
1491        if (translationId) {
1492          translationId = trim.apply(translationId);
1493        }
1494
1495        var promiseToWaitFor = (function () {
1496          var promise = $preferredLanguage ?
1497            langPromises[$preferredLanguage] :
1498            langPromises[uses];
1499
1500          fallbackIndex = 0;
1501
1502          if ($storageFactory && !promise) {
1503            // looks like there's no pending promise for $preferredLanguage or
1504            // $uses. Maybe there's one pending for a language that comes from
1505            // storage.
1506            var langKey = Storage.get($storageKey);
1507            promise = langPromises[langKey];
1508
1509            if ($fallbackLanguage && $fallbackLanguage.length) {
1510                var index = indexOf($fallbackLanguage, langKey);
1511                // maybe the language from storage is also defined as fallback language
1512                // we increase the fallback language index to not search in that language
1513                // as fallback, since it's probably the first used language
1514                // in that case the index starts after the first element
1515                fallbackIndex = (index === 0) ? 1 : 0;
1516
1517                // but we can make sure to ALWAYS fallback to preferred language at least
1518                if (indexOf($fallbackLanguage, $preferredLanguage) < 0) {
1519                  $fallbackLanguage.push($preferredLanguage);
1520                }
1521            }
1522          }
1523          return promise;
1524        }());
1525
1526        if (!promiseToWaitFor) {
1527          // no promise to wait for? okay. Then there's no loader registered
1528          // nor is a one pending for language that comes from storage.
1529          // We can just translate.
1530          determineTranslation(translationId, interpolateParams, interpolationId, defaultTranslationText, uses).then(deferred.resolve, deferred.reject);
1531        } else {
1532          var promiseResolved = function () {
1533            // $uses may have changed while waiting
1534            if (!forceLanguage) {
1535              uses = $uses;
1536            }
1537            determineTranslation(translationId, interpolateParams, interpolationId, defaultTranslationText, uses).then(deferred.resolve, deferred.reject);
1538          };
1539          promiseResolved.displayName = 'promiseResolved';
1540
1541          promiseToWaitFor['finally'](promiseResolved);
1542        }
1543        return deferred.promise;
1544      };
1545
1546      /**
1547       * @name applyNotFoundIndicators
1548       * @private
1549       *
1550       * @description
1551       * Applies not fount indicators to given translation id, if needed.
1552       * This function gets only executed, if a translation id doesn't exist,
1553       * which is why a translation id is expected as argument.
1554       *
1555       * @param {string} translationId Translation id.
1556       * @returns {string} Same as given translation id but applied with not found
1557       * indicators.
1558       */
1559      var applyNotFoundIndicators = function (translationId) {
1560        // applying notFoundIndicators
1561        if ($notFoundIndicatorLeft) {
1562          translationId = [$notFoundIndicatorLeft, translationId].join(' ');
1563        }
1564        if ($notFoundIndicatorRight) {
1565          translationId = [translationId, $notFoundIndicatorRight].join(' ');
1566        }
1567        return translationId;
1568      };
1569
1570      /**
1571       * @name useLanguage
1572       * @private
1573       *
1574       * @description
1575       * Makes actual use of a language by setting a given language key as used
1576       * language and informs registered interpolators to also use the given
1577       * key as locale.
1578       *
1579       * @param {string} key Locale key.
1580       */
1581      var useLanguage = function (key) {
1582        $uses = key;
1583
1584        // make sure to store new language key before triggering success event
1585        if ($storageFactory) {
1586          Storage.put($translate.storageKey(), $uses);
1587        }
1588
1589        $rootScope.$emit('$translateChangeSuccess', {language: key});
1590
1591        // inform default interpolator
1592        defaultInterpolator.setLocale($uses);
1593
1594        var eachInterpolator = function (interpolator, id) {
1595          interpolatorHashMap[id].setLocale($uses);
1596        };
1597        eachInterpolator.displayName = 'eachInterpolatorLocaleSetter';
1598
1599        // inform all others too!
1600        angular.forEach(interpolatorHashMap, eachInterpolator);
1601        $rootScope.$emit('$translateChangeEnd', {language: key});
1602      };
1603
1604      /**
1605       * @name loadAsync
1606       * @private
1607       *
1608       * @description
1609       * Kicks of registered async loader using `$injector` and applies existing
1610       * loader options. When resolved, it updates translation tables accordingly
1611       * or rejects with given language key.
1612       *
1613       * @param {string} key Language key.
1614       * @return {Promise} A promise.
1615       */
1616      var loadAsync = function (key) {
1617        if (!key) {
1618          throw 'No language key specified for loading.';
1619        }
1620
1621        var deferred = $q.defer();
1622
1623        $rootScope.$emit('$translateLoadingStart', {language: key});
1624        pendingLoader = true;
1625
1626        var cache = loaderCache;
1627        if (typeof(cache) === 'string') {
1628          // getting on-demand instance of loader
1629          cache = $injector.get(cache);
1630        }
1631
1632        var loaderOptions = angular.extend({}, $loaderOptions, {
1633          key: key,
1634          $http: angular.extend({}, {
1635            cache: cache
1636          }, $loaderOptions.$http)
1637        });
1638
1639        var onLoaderSuccess = function (data) {
1640          var translationTable = {};
1641          $rootScope.$emit('$translateLoadingSuccess', {language: key});
1642
1643          if (angular.isArray(data)) {
1644            angular.forEach(data, function (table) {
1645              angular.extend(translationTable, flatObject(table));
1646            });
1647          } else {
1648            angular.extend(translationTable, flatObject(data));
1649          }
1650          pendingLoader = false;
1651          deferred.resolve({
1652            key: key,
1653            table: translationTable
1654          });
1655          $rootScope.$emit('$translateLoadingEnd', {language: key});
1656        };
1657        onLoaderSuccess.displayName = 'onLoaderSuccess';
1658
1659        var onLoaderError = function (key) {
1660          $rootScope.$emit('$translateLoadingError', {language: key});
1661          deferred.reject(key);
1662          $rootScope.$emit('$translateLoadingEnd', {language: key});
1663        };
1664        onLoaderError.displayName = 'onLoaderError';
1665
1666        $injector.get($loaderFactory)(loaderOptions)
1667          .then(onLoaderSuccess, onLoaderError);
1668
1669        return deferred.promise;
1670      };
1671
1672      if ($storageFactory) {
1673        Storage = $injector.get($storageFactory);
1674
1675        if (!Storage.get || !Storage.put) {
1676          throw new Error('Couldn\'t use storage \'' + $storageFactory + '\', missing get() or put() method!');
1677        }
1678      }
1679
1680      // if we have additional interpolations that were added via
1681      // $translateProvider.addInterpolation(), we have to map'em
1682      if ($interpolatorFactories.length) {
1683        var eachInterpolationFactory = function (interpolatorFactory) {
1684          var interpolator = $injector.get(interpolatorFactory);
1685          // setting initial locale for each interpolation service
1686          interpolator.setLocale($preferredLanguage || $uses);
1687          // make'em recognizable through id
1688          interpolatorHashMap[interpolator.getInterpolationIdentifier()] = interpolator;
1689        };
1690        eachInterpolationFactory.displayName = 'interpolationFactoryAdder';
1691
1692        angular.forEach($interpolatorFactories, eachInterpolationFactory);
1693      }
1694
1695      /**
1696       * @name getTranslationTable
1697       * @private
1698       *
1699       * @description
1700       * Returns a promise that resolves to the translation table
1701       * or is rejected if an error occurred.
1702       *
1703       * @param langKey
1704       * @returns {Q.promise}
1705       */
1706      var getTranslationTable = function (langKey) {
1707        var deferred = $q.defer();
1708        if (Object.prototype.hasOwnProperty.call($translationTable, langKey)) {
1709          deferred.resolve($translationTable[langKey]);
1710        } else if (langPromises[langKey]) {
1711          var onResolve = function (data) {
1712            translations(data.key, data.table);
1713            deferred.resolve(data.table);
1714          };
1715          onResolve.displayName = 'translationTableResolver';
1716          langPromises[langKey].then(onResolve, deferred.reject);
1717        } else {
1718          deferred.reject();
1719        }
1720        return deferred.promise;
1721      };
1722
1723      /**
1724       * @name getFallbackTranslation
1725       * @private
1726       *
1727       * @description
1728       * Returns a promise that will resolve to the translation
1729       * or be rejected if no translation was found for the language.
1730       * This function is currently only used for fallback language translation.
1731       *
1732       * @param langKey The language to translate to.
1733       * @param translationId
1734       * @param interpolateParams
1735       * @param Interpolator
1736       * @returns {Q.promise}
1737       */
1738      var getFallbackTranslation = function (langKey, translationId, interpolateParams, Interpolator) {
1739        var deferred = $q.defer();
1740
1741        var onResolve = function (translationTable) {
1742          if (Object.prototype.hasOwnProperty.call(translationTable, translationId)) {
1743            Interpolator.setLocale(langKey);
1744            var translation = translationTable[translationId];
1745            if (translation.substr(0, 2) === '@:') {
1746              getFallbackTranslation(langKey, translation.substr(2), interpolateParams, Interpolator)
1747                .then(deferred.resolve, deferred.reject);
1748            } else {
1749              var interpolatedValue = Interpolator.interpolate(translationTable[translationId], interpolateParams, 'service');
1750              interpolatedValue = applyPostProcessing(translationId, translationTable[translationId], interpolatedValue, interpolateParams, langKey);
1751
1752              deferred.resolve(interpolatedValue);
1753
1754            }
1755            Interpolator.setLocale($uses);
1756          } else {
1757            deferred.reject();
1758          }
1759        };
1760        onResolve.displayName = 'fallbackTranslationResolver';
1761
1762        getTranslationTable(langKey).then(onResolve, deferred.reject);
1763
1764        return deferred.promise;
1765      };
1766
1767      /**
1768       * @name getFallbackTranslationInstant
1769       * @private
1770       *
1771       * @description
1772       * Returns a translation
1773       * This function is currently only used for fallback language translation.
1774       *
1775       * @param langKey The language to translate to.
1776       * @param translationId
1777       * @param interpolateParams
1778       * @param Interpolator
1779       * @returns {string} translation
1780       */
1781      var getFallbackTranslationInstant = function (langKey, translationId, interpolateParams, Interpolator) {
1782        var result, translationTable = $translationTable[langKey];
1783
1784        if (translationTable && Object.prototype.hasOwnProperty.call(translationTable, translationId)) {
1785          Interpolator.setLocale(langKey);
1786          result = Interpolator.interpolate(translationTable[translationId], interpolateParams, 'filter');
1787          result = applyPostProcessing(translationId, translationTable[translationId], result, interpolateParams, langKey);
1788          if (result.substr(0, 2) === '@:') {
1789            return getFallbackTranslationInstant(langKey, result.substr(2), interpolateParams, Interpolator);
1790          }
1791          Interpolator.setLocale($uses);
1792        }
1793
1794        return result;
1795      };
1796
1797
1798      /**
1799       * @name translateByHandler
1800       * @private
1801       *
1802       * Translate by missing translation handler.
1803       *
1804       * @param translationId
1805       * @param interpolateParams
1806       * @param defaultTranslationText
1807       * @returns translation created by $missingTranslationHandler or translationId is $missingTranslationHandler is
1808       * absent
1809       */
1810      var translateByHandler = function (translationId, interpolateParams, defaultTranslationText) {
1811        // If we have a handler factory - we might also call it here to determine if it provides
1812        // a default text for a translationid that can't be found anywhere in our tables
1813        if ($missingTranslationHandlerFactory) {
1814          var resultString = $injector.get($missingTranslationHandlerFactory)(translationId, $uses, interpolateParams, defaultTranslationText);
1815          if (resultString !== undefined) {
1816            return resultString;
1817          } else {
1818            return translationId;
1819          }
1820        } else {
1821          return translationId;
1822        }
1823      };
1824
1825      /**
1826       * @name resolveForFallbackLanguage
1827       * @private
1828       *
1829       * Recursive helper function for fallbackTranslation that will sequentially look
1830       * for a translation in the fallbackLanguages starting with fallbackLanguageIndex.
1831       *
1832       * @param fallbackLanguageIndex
1833       * @param translationId
1834       * @param interpolateParams
1835       * @param Interpolator
1836       * @returns {Q.promise} Promise that will resolve to the translation.
1837       */
1838      var resolveForFallbackLanguage = function (fallbackLanguageIndex, translationId, interpolateParams, Interpolator, defaultTranslationText) {
1839        var deferred = $q.defer();
1840
1841        if (fallbackLanguageIndex < $fallbackLanguage.length) {
1842          var langKey = $fallbackLanguage[fallbackLanguageIndex];
1843          getFallbackTranslation(langKey, translationId, interpolateParams, Interpolator).then(
1844            function (data) {
1845                deferred.resolve(data);
1846            },
1847            function () {
1848              // Look in the next fallback language for a translation.
1849              // It delays the resolving by passing another promise to resolve.
1850              return resolveForFallbackLanguage(fallbackLanguageIndex + 1, translationId, interpolateParams, Interpolator, defaultTranslationText).then(deferred.resolve, deferred.reject);
1851            }
1852          );
1853        } else {
1854          // No translation found in any fallback language
1855          // if a default translation text is set in the directive, then return this as a result
1856          if (defaultTranslationText) {
1857            deferred.resolve(defaultTranslationText);
1858          } else {
1859            // if no default translation is set and an error handler is defined, send it to the handler
1860            // and then return the result
1861            if ($missingTranslationHandlerFactory) {
1862              deferred.resolve(translateByHandler(translationId, interpolateParams));
1863            } else {
1864              deferred.reject(translateByHandler(translationId, interpolateParams));
1865            }
1866
1867          }
1868        }
1869        return deferred.promise;
1870      };
1871
1872      /**
1873       * @name resolveForFallbackLanguageInstant
1874       * @private
1875       *
1876       * Recursive helper function for fallbackTranslation that will sequentially look
1877       * for a translation in the fallbackLanguages starting with fallbackLanguageIndex.
1878       *
1879       * @param fallbackLanguageIndex
1880       * @param translationId
1881       * @param interpolateParams
1882       * @param Interpolator
1883       * @returns {string} translation
1884       */
1885      var resolveForFallbackLanguageInstant = function (fallbackLanguageIndex, translationId, interpolateParams, Interpolator) {
1886        var result;
1887
1888        if (fallbackLanguageIndex < $fallbackLanguage.length) {
1889          var langKey = $fallbackLanguage[fallbackLanguageIndex];
1890          result = getFallbackTranslationInstant(langKey, translationId, interpolateParams, Interpolator);
1891          if (!result) {
1892            result = resolveForFallbackLanguageInstant(fallbackLanguageIndex + 1, translationId, interpolateParams, Interpolator);
1893          }
1894        }
1895        return result;
1896      };
1897
1898      /**
1899       * Translates with the usage of the fallback languages.
1900       *
1901       * @param translationId
1902       * @param interpolateParams
1903       * @param Interpolator
1904       * @returns {Q.promise} Promise, that resolves to the translation.
1905       */
1906      var fallbackTranslation = function (translationId, interpolateParams, Interpolator, defaultTranslationText) {
1907        // Start with the fallbackLanguage with index 0
1908        return resolveForFallbackLanguage((startFallbackIteration>0 ? startFallbackIteration : fallbackIndex), translationId, interpolateParams, Interpolator, defaultTranslationText);
1909      };
1910
1911      /**
1912       * Translates with the usage of the fallback languages.
1913       *
1914       * @param translationId
1915       * @param interpolateParams
1916       * @param Interpolator
1917       * @returns {String} translation
1918       */
1919      var fallbackTranslationInstant = function (translationId, interpolateParams, Interpolator) {
1920        // Start with the fallbackLanguage with index 0
1921        return resolveForFallbackLanguageInstant((startFallbackIteration>0 ? startFallbackIteration : fallbackIndex), translationId, interpolateParams, Interpolator);
1922      };
1923
1924      var determineTranslation = function (translationId, interpolateParams, interpolationId, defaultTranslationText, uses) {
1925
1926        var deferred = $q.defer();
1927
1928        var table = uses ? $translationTable[uses] : $translationTable,
1929            Interpolator = (interpolationId) ? interpolatorHashMap[interpolationId] : defaultInterpolator;
1930
1931        // if the translation id exists, we can just interpolate it
1932        if (table && Object.prototype.hasOwnProperty.call(table, translationId)) {
1933          var translation = table[translationId];
1934
1935          // If using link, rerun $translate with linked translationId and return it
1936          if (translation.substr(0, 2) === '@:') {
1937
1938            $translate(translation.substr(2), interpolateParams, interpolationId, defaultTranslationText, uses)
1939              .then(deferred.resolve, deferred.reject);
1940          } else {
1941            //
1942            var resolvedTranslation = Interpolator.interpolate(translation, interpolateParams, 'service');
1943            resolvedTranslation = applyPostProcessing(translationId, translation, resolvedTranslation, interpolateParams, uses);
1944            deferred.resolve(resolvedTranslation);
1945          }
1946        } else {
1947          var missingTranslationHandlerTranslation;
1948          // for logging purposes only (as in $translateMissingTranslationHandlerLog), value is not returned to promise
1949          if ($missingTranslationHandlerFactory && !pendingLoader) {
1950            missingTranslationHandlerTranslation = translateByHandler(translationId, interpolateParams, defaultTranslationText);
1951          }
1952
1953          // since we couldn't translate the inital requested translation id,
1954          // we try it now with one or more fallback languages, if fallback language(s) is
1955          // configured.
1956          if (uses && $fallbackLanguage && $fallbackLanguage.length) {
1957            fallbackTranslation(translationId, interpolateParams, Interpolator, defaultTranslationText)
1958                .then(function (translation) {
1959                  deferred.resolve(translation);
1960                }, function (_translationId) {
1961                  deferred.reject(applyNotFoundIndicators(_translationId));
1962                });
1963          } else if ($missingTranslationHandlerFactory && !pendingLoader && missingTranslationHandlerTranslation) {
1964            // looks like the requested translation id doesn't exists.
1965            // Now, if there is a registered handler for missing translations and no
1966            // asyncLoader is pending, we execute the handler
1967            if (defaultTranslationText) {
1968              deferred.resolve(defaultTranslationText);
1969              } else {
1970                deferred.resolve(missingTranslationHandlerTranslation);
1971              }
1972          } else {
1973            if (defaultTranslationText) {
1974              deferred.resolve(defaultTranslationText);
1975            } else {
1976              deferred.reject(applyNotFoundIndicators(translationId));
1977            }
1978          }
1979        }
1980        return deferred.promise;
1981      };
1982
1983      var determineTranslationInstant = function (translationId, interpolateParams, interpolationId, uses) {
1984
1985        var result, table = uses ? $translationTable[uses] : $translationTable,
1986            Interpolator = defaultInterpolator;
1987
1988        // if the interpolation id exists use custom interpolator
1989        if (interpolatorHashMap && Object.prototype.hasOwnProperty.call(interpolatorHashMap, interpolationId)) {
1990          Interpolator = interpolatorHashMap[interpolationId];
1991        }
1992
1993        // if the translation id exists, we can just interpolate it
1994        if (table && Object.prototype.hasOwnProperty.call(table, translationId)) {
1995          var translation = table[translationId];
1996
1997          // If using link, rerun $translate with linked translationId and return it
1998          if (translation.substr(0, 2) === '@:') {
1999            result = determineTranslationInstant(translation.substr(2), interpolateParams, interpolationId, uses);
2000          } else {
2001            result = Interpolator.interpolate(translation, interpolateParams, 'filter');
2002            result = applyPostProcessing(translationId, translation, result, interpolateParams, uses);
2003          }
2004        } else {
2005          var missingTranslationHandlerTranslation;
2006          // for logging purposes only (as in $translateMissingTranslationHandlerLog), value is not returned to promise
2007          if ($missingTranslationHandlerFactory && !pendingLoader) {
2008            missingTranslationHandlerTranslation = translateByHandler(translationId, interpolateParams);
2009          }
2010
2011          // since we couldn't translate the inital requested translation id,
2012          // we try it now with one or more fallback languages, if fallback language(s) is
2013          // configured.
2014          if (uses && $fallbackLanguage && $fallbackLanguage.length) {
2015            fallbackIndex = 0;
2016            result = fallbackTranslationInstant(translationId, interpolateParams, Interpolator);
2017          } else if ($missingTranslationHandlerFactory && !pendingLoader && missingTranslationHandlerTranslation) {
2018            // looks like the requested translation id doesn't exists.
2019            // Now, if there is a registered handler for missing translations and no
2020            // asyncLoader is pending, we execute the handler
2021            result = missingTranslationHandlerTranslation;
2022          } else {
2023            result = applyNotFoundIndicators(translationId);
2024          }
2025        }
2026
2027        return result;
2028      };
2029
2030      var clearNextLangAndPromise = function(key) {
2031        if ($nextLang === key) {
2032          $nextLang = undefined;
2033        }
2034        langPromises[key] = undefined;
2035      };
2036
2037      var applyPostProcessing = function (translationId, translation, resolvedTranslation, interpolateParams, uses) {
2038        var fn = postProcessFn;
2039
2040        if (fn) {
2041
2042          if (typeof(fn) === 'string') {
2043            // getting on-demand instance
2044            fn = $injector.get(fn);
2045          }
2046          if (fn) {
2047            return fn(translationId, translation, resolvedTranslation, interpolateParams, uses);
2048          }
2049        }
2050
2051        return resolvedTranslation;
2052      };
2053
2054      var loadTranslationsIfMissing = function (key) {
2055        if (!$translationTable[key] && $loaderFactory && !langPromises[key]) {
2056          langPromises[key] = loadAsync(key).then(function (translation) {
2057            translations(translation.key, translation.table);
2058            return translation;
2059          });
2060        }
2061      };
2062
2063      /**
2064       * @ngdoc function
2065       * @name pascalprecht.translate.$translate#preferredLanguage
2066       * @methodOf pascalprecht.translate.$translate
2067       *
2068       * @description
2069       * Returns the language key for the preferred language.
2070       *
2071       * @param {string} langKey language String or Array to be used as preferredLanguage (changing at runtime)
2072       *
2073       * @return {string} preferred language key
2074       */
2075      $translate.preferredLanguage = function (langKey) {
2076        if(langKey) {
2077          setupPreferredLanguage(langKey);
2078        }
2079        return $preferredLanguage;
2080      };
2081
2082      /**
2083       * @ngdoc function
2084       * @name pascalprecht.translate.$translate#cloakClassName
2085       * @methodOf pascalprecht.translate.$translate
2086       *
2087       * @description
2088       * Returns the configured class name for `translate-cloak` directive.
2089       *
2090       * @return {string} cloakClassName
2091       */
2092      $translate.cloakClassName = function () {
2093        return $cloakClassName;
2094      };
2095
2096      /**
2097       * @ngdoc function
2098       * @name pascalprecht.translate.$translate#nestedObjectDelimeter
2099       * @methodOf pascalprecht.translate.$translate
2100       *
2101       * @description
2102       * Returns the configured delimiter for nested namespaces.
2103       *
2104       * @return {string} nestedObjectDelimeter
2105       */
2106      $translate.nestedObjectDelimeter = function () {
2107        return $nestedObjectDelimeter;
2108      };
2109
2110      /**
2111       * @ngdoc function
2112       * @name pascalprecht.translate.$translate#fallbackLanguage
2113       * @methodOf pascalprecht.translate.$translate
2114       *
2115       * @description
2116       * Returns the language key for the fallback languages or sets a new fallback stack.
2117       *
2118       * @param {string=} langKey language String or Array of fallback languages to be used (to change stack at runtime)
2119       *
2120       * @return {string||array} fallback language key
2121       */
2122      $translate.fallbackLanguage = function (langKey) {
2123        if (langKey !== undefined && langKey !== null) {
2124          fallbackStack(langKey);
2125
2126          // as we might have an async loader initiated and a new translation language might have been defined
2127          // we need to add the promise to the stack also. So - iterate.
2128          if ($loaderFactory) {
2129            if ($fallbackLanguage && $fallbackLanguage.length) {
2130              for (var i = 0, len = $fallbackLanguage.length; i < len; i++) {
2131                if (!langPromises[$fallbackLanguage[i]]) {
2132                  langPromises[$fallbackLanguage[i]] = loadAsync($fallbackLanguage[i]);
2133                }
2134              }
2135            }
2136          }
2137          $translate.use($translate.use());
2138        }
2139        if ($fallbackWasString) {
2140          return $fallbackLanguage[0];
2141        } else {
2142          return $fallbackLanguage;
2143        }
2144
2145      };
2146
2147      /**
2148       * @ngdoc function
2149       * @name pascalprecht.translate.$translate#useFallbackLanguage
2150       * @methodOf pascalprecht.translate.$translate
2151       *
2152       * @description
2153       * Sets the first key of the fallback language stack to be used for translation.
2154       * Therefore all languages in the fallback array BEFORE this key will be skipped!
2155       *
2156       * @param {string=} langKey Contains the langKey the iteration shall start with. Set to false if you want to
2157       * get back to the whole stack
2158       */
2159      $translate.useFallbackLanguage = function (langKey) {
2160        if (langKey !== undefined && langKey !== null) {
2161          if (!langKey) {
2162            startFallbackIteration = 0;
2163          } else {
2164            var langKeyPosition = indexOf($fallbackLanguage, langKey);
2165            if (langKeyPosition > -1) {
2166              startFallbackIteration = langKeyPosition;
2167            }
2168          }
2169
2170        }
2171
2172      };
2173
2174      /**
2175       * @ngdoc function
2176       * @name pascalprecht.translate.$translate#proposedLanguage
2177       * @methodOf pascalprecht.translate.$translate
2178       *
2179       * @description
2180       * Returns the language key of language that is currently loaded asynchronously.
2181       *
2182       * @return {string} language key
2183       */
2184      $translate.proposedLanguage = function () {
2185        return $nextLang;
2186      };
2187
2188      /**
2189       * @ngdoc function
2190       * @name pascalprecht.translate.$translate#storage
2191       * @methodOf pascalprecht.translate.$translate
2192       *
2193       * @description
2194       * Returns registered storage.
2195       *
2196       * @return {object} Storage
2197       */
2198      $translate.storage = function () {
2199        return Storage;
2200      };
2201
2202      /**
2203       * @ngdoc function
2204       * @name pascalprecht.translate.$translate#negotiateLocale
2205       * @methodOf pascalprecht.translate.$translate
2206       *
2207       * @description
2208       * Returns a language key based on available languages and language aliases. If a
2209       * language key cannot be resolved, returns undefined.
2210       *
2211       * If no or a falsy key is given, returns undefined.
2212       *
2213       * @param {string} [key] Language key
2214       * @return {string|undefined} Language key or undefined if no language key is found.
2215       */
2216      $translate.negotiateLocale = negotiateLocale;
2217
2218      /**
2219       * @ngdoc function
2220       * @name pascalprecht.translate.$translate#use
2221       * @methodOf pascalprecht.translate.$translate
2222       *
2223       * @description
2224       * Tells angular-translate which language to use by given language key. This method is
2225       * used to change language at runtime. It also takes care of storing the language
2226       * key in a configured store to let your app remember the choosed language.
2227       *
2228       * When trying to 'use' a language which isn't available it tries to load it
2229       * asynchronously with registered loaders.
2230       *
2231       * Returns promise object with loaded language file data or string of the currently used language.
2232       *
2233       * If no or a falsy key is given it returns the currently used language key.
2234       * The returned string will be ```undefined``` if setting up $translate hasn't finished.
2235       * @example
2236       * $translate.use("en_US").then(function(data){
2237       *   $scope.text = $translate("HELLO");
2238       * });
2239       *
2240       * @param {string} [key] Language key
2241       * @return {object|string} Promise with loaded language data or the language key if a falsy param was given.
2242       */
2243      $translate.use = function (key) {
2244        if (!key) {
2245          return $uses;
2246        }
2247
2248        var deferred = $q.defer();
2249
2250        $rootScope.$emit('$translateChangeStart', {language: key});
2251
2252        // Try to get the aliased language key
2253        var aliasedKey = negotiateLocale(key);
2254        // Ensure only registered language keys will be loaded
2255        if ($availableLanguageKeys.length > 0 && !aliasedKey) {
2256          return $q.reject(key);
2257        }
2258
2259        if (aliasedKey) {
2260          key = aliasedKey;
2261        }
2262
2263        // if there isn't a translation table for the language we've requested,
2264        // we load it asynchronously
2265        $nextLang = key;
2266        if (($forceAsyncReloadEnabled || !$translationTable[key]) && $loaderFactory && !langPromises[key]) {
2267          langPromises[key] = loadAsync(key).then(function (translation) {
2268            translations(translation.key, translation.table);
2269            deferred.resolve(translation.key);
2270            if ($nextLang === key) {
2271              useLanguage(translation.key);
2272            }
2273            return translation;
2274          }, function (key) {
2275            $rootScope.$emit('$translateChangeError', {language: key});
2276            deferred.reject(key);
2277            $rootScope.$emit('$translateChangeEnd', {language: key});
2278            return $q.reject(key);
2279          });
2280          langPromises[key]['finally'](function () {
2281            clearNextLangAndPromise(key);
2282          });
2283        } else if (langPromises[key]) {
2284          // we are already loading this asynchronously
2285          // resolve our new deferred when the old langPromise is resolved
2286          langPromises[key].then(function (translation) {
2287            if ($nextLang === translation.key) {
2288              useLanguage(translation.key);
2289            }
2290            deferred.resolve(translation.key);
2291            return translation;
2292          }, function (key) {
2293            // find first available fallback language if that request has failed
2294            if (!$uses && $fallbackLanguage && $fallbackLanguage.length > 0 && $fallbackLanguage[0] !== key) {
2295              return $translate.use($fallbackLanguage[0]).then(deferred.resolve, deferred.reject);
2296            } else {
2297              return deferred.reject(key);
2298            }
2299          });
2300        } else {
2301          deferred.resolve(key);
2302          useLanguage(key);
2303        }
2304
2305        return deferred.promise;
2306      };
2307
2308      /**
2309       * @ngdoc function
2310       * @name pascalprecht.translate.$translate#resolveClientLocale
2311       * @methodOf pascalprecht.translate.$translate
2312       *
2313       * @description
2314       * This returns the current browser/client's language key. The result is processed with the configured uniform tag resolver.
2315       *
2316       * @returns {string} the current client/browser language key
2317       */
2318      $translate.resolveClientLocale = function () {
2319        return getLocale();
2320      };
2321
2322      /**
2323       * @ngdoc function
2324       * @name pascalprecht.translate.$translate#storageKey
2325       * @methodOf pascalprecht.translate.$translate
2326       *
2327       * @description
2328       * Returns the key for the storage.
2329       *
2330       * @return {string} storage key
2331       */
2332      $translate.storageKey = function () {
2333        return storageKey();
2334      };
2335
2336      /**
2337       * @ngdoc function
2338       * @name pascalprecht.translate.$translate#isPostCompilingEnabled
2339       * @methodOf pascalprecht.translate.$translate
2340       *
2341       * @description
2342       * Returns whether post compiling is enabled or not
2343       *
2344       * @return {bool} storage key
2345       */
2346      $translate.isPostCompilingEnabled = function () {
2347        return $postCompilingEnabled;
2348      };
2349
2350      /**
2351       * @ngdoc function
2352       * @name pascalprecht.translate.$translate#isForceAsyncReloadEnabled
2353       * @methodOf pascalprecht.translate.$translate
2354       *
2355       * @description
2356       * Returns whether force async reload is enabled or not
2357       *
2358       * @return {boolean} forceAsyncReload value
2359       */
2360      $translate.isForceAsyncReloadEnabled = function () {
2361        return $forceAsyncReloadEnabled;
2362      };
2363
2364      /**
2365       * @ngdoc function
2366       * @name pascalprecht.translate.$translate#isKeepContent
2367       * @methodOf pascalprecht.translate.$translate
2368       *
2369       * @description
2370       * Returns whether keepContent or not
2371       *
2372       * @return {boolean} keepContent value
2373       */
2374      $translate.isKeepContent = function () {
2375        return $keepContent;
2376      };
2377
2378      /**
2379       * @ngdoc function
2380       * @name pascalprecht.translate.$translate#refresh
2381       * @methodOf pascalprecht.translate.$translate
2382       *
2383       * @description
2384       * Refreshes a translation table pointed by the given langKey. If langKey is not specified,
2385       * the module will drop all existent translation tables and load new version of those which
2386       * are currently in use.
2387       *
2388       * Refresh means that the module will drop target translation table and try to load it again.
2389       *
2390       * In case there are no loaders registered the refresh() method will throw an Error.
2391       *
2392       * If the module is able to refresh translation tables refresh() method will broadcast
2393       * $translateRefreshStart and $translateRefreshEnd events.
2394       *
2395       * @example
2396       * // this will drop all currently existent translation tables and reload those which are
2397       * // currently in use
2398       * $translate.refresh();
2399       * // this will refresh a translation table for the en_US language
2400       * $translate.refresh('en_US');
2401       *
2402       * @param {string} langKey A language key of the table, which has to be refreshed
2403       *
2404       * @return {promise} Promise, which will be resolved in case a translation tables refreshing
2405       * process is finished successfully, and reject if not.
2406       */
2407      $translate.refresh = function (langKey) {
2408        if (!$loaderFactory) {
2409          throw new Error('Couldn\'t refresh translation table, no loader registered!');
2410        }
2411
2412        var deferred = $q.defer();
2413
2414        function resolve() {
2415          deferred.resolve();
2416          $rootScope.$emit('$translateRefreshEnd', {language: langKey});
2417        }
2418
2419        function reject() {
2420          deferred.reject();
2421          $rootScope.$emit('$translateRefreshEnd', {language: langKey});
2422        }
2423
2424        $rootScope.$emit('$translateRefreshStart', {language: langKey});
2425
2426        if (!langKey) {
2427          // if there's no language key specified we refresh ALL THE THINGS!
2428          var tables = [], loadingKeys = {};
2429
2430          // reload registered fallback languages
2431          if ($fallbackLanguage && $fallbackLanguage.length) {
2432            for (var i = 0, len = $fallbackLanguage.length; i < len; i++) {
2433              tables.push(loadAsync($fallbackLanguage[i]));
2434              loadingKeys[$fallbackLanguage[i]] = true;
2435            }
2436          }
2437
2438          // reload currently used language
2439          if ($uses && !loadingKeys[$uses]) {
2440            tables.push(loadAsync($uses));
2441          }
2442
2443          var allTranslationsLoaded = function (tableData) {
2444            $translationTable = {};
2445            angular.forEach(tableData, function (data) {
2446              translations(data.key, data.table);
2447            });
2448            if ($uses) {
2449              useLanguage($uses);
2450            }
2451            resolve();
2452          };
2453          allTranslationsLoaded.displayName = 'refreshPostProcessor';
2454
2455          $q.all(tables).then(allTranslationsLoaded, reject);
2456
2457        } else if ($translationTable[langKey]) {
2458
2459          var oneTranslationsLoaded = function (data) {
2460            translations(data.key, data.table);
2461            if (langKey === $uses) {
2462              useLanguage($uses);
2463            }
2464            resolve();
2465            return data;
2466          };
2467          oneTranslationsLoaded.displayName = 'refreshPostProcessor';
2468
2469          loadAsync(langKey).then(oneTranslationsLoaded, reject);
2470
2471        } else {
2472          reject();
2473        }
2474        return deferred.promise;
2475      };
2476
2477      /**
2478       * @ngdoc function
2479       * @name pascalprecht.translate.$translate#instant
2480       * @methodOf pascalprecht.translate.$translate
2481       *
2482       * @description
2483       * Returns a translation instantly from the internal state of loaded translation. All rules
2484       * regarding the current language, the preferred language of even fallback languages will be
2485       * used except any promise handling. If a language was not found, an asynchronous loading
2486       * will be invoked in the background.
2487       *
2488       * @param {string|array} translationId A token which represents a translation id
2489       *                                     This can be optionally an array of translation ids which
2490       *                                     results that the function's promise returns an object where
2491       *                                     each key is the translation id and the value the translation.
2492       * @param {object} interpolateParams Params
2493       * @param {string} interpolationId The id of the interpolation to use
2494       * @param {string} forceLanguage A language to be used instead of the current language
2495       *
2496       * @return {string|object} translation
2497       */
2498      $translate.instant = function (translationId, interpolateParams, interpolationId, forceLanguage) {
2499
2500        // we don't want to re-negotiate $uses
2501        var uses = (forceLanguage && forceLanguage !== $uses) ? // we don't want to re-negotiate $uses
2502              (negotiateLocale(forceLanguage) || forceLanguage) : $uses;
2503
2504        // Detect undefined and null values to shorten the execution and prevent exceptions
2505        if (translationId === null || angular.isUndefined(translationId)) {
2506          return translationId;
2507        }
2508
2509        // Check forceLanguage is present
2510        if (forceLanguage) {
2511          loadTranslationsIfMissing(forceLanguage);
2512        }
2513
2514        // Duck detection: If the first argument is an array, a bunch of translations was requested.
2515        // The result is an object.
2516        if (angular.isArray(translationId)) {
2517          var results = {};
2518          for (var i = 0, c = translationId.length; i < c; i++) {
2519            results[translationId[i]] = $translate.instant(translationId[i], interpolateParams, interpolationId, forceLanguage);
2520          }
2521          return results;
2522        }
2523
2524        // We discarded unacceptable values. So we just need to verify if translationId is empty String
2525        if (angular.isString(translationId) && translationId.length < 1) {
2526          return translationId;
2527        }
2528
2529        // trim off any whitespace
2530        if (translationId) {
2531          translationId = trim.apply(translationId);
2532        }
2533
2534        var result, possibleLangKeys = [];
2535        if ($preferredLanguage) {
2536          possibleLangKeys.push($preferredLanguage);
2537        }
2538        if (uses) {
2539          possibleLangKeys.push(uses);
2540        }
2541        if ($fallbackLanguage && $fallbackLanguage.length) {
2542          possibleLangKeys = possibleLangKeys.concat($fallbackLanguage);
2543        }
2544        for (var j = 0, d = possibleLangKeys.length; j < d; j++) {
2545          var possibleLangKey = possibleLangKeys[j];
2546          if ($translationTable[possibleLangKey]) {
2547            if (typeof $translationTable[possibleLangKey][translationId] !== 'undefined') {
2548              result = determineTranslationInstant(translationId, interpolateParams, interpolationId, uses);
2549            }
2550          }
2551          if (typeof result !== 'undefined') {
2552            break;
2553          }
2554        }
2555
2556        if (!result && result !== '') {
2557          if ($notFoundIndicatorLeft || $notFoundIndicatorRight) {
2558            result = applyNotFoundIndicators(translationId);
2559          } else {
2560            // Return translation of default interpolator if not found anything.
2561            result = defaultInterpolator.interpolate(translationId, interpolateParams, 'filter');
2562            if ($missingTranslationHandlerFactory && !pendingLoader) {
2563              result = translateByHandler(translationId, interpolateParams);
2564            }
2565          }
2566        }
2567
2568        return result;
2569      };
2570
2571      /**
2572       * @ngdoc function
2573       * @name pascalprecht.translate.$translate#versionInfo
2574       * @methodOf pascalprecht.translate.$translate
2575       *
2576       * @description
2577       * Returns the current version information for the angular-translate library
2578       *
2579       * @return {string} angular-translate version
2580       */
2581      $translate.versionInfo = function () {
2582        return version;
2583      };
2584
2585      /**
2586       * @ngdoc function
2587       * @name pascalprecht.translate.$translate#loaderCache
2588       * @methodOf pascalprecht.translate.$translate
2589       *
2590       * @description
2591       * Returns the defined loaderCache.
2592       *
2593       * @return {boolean|string|object} current value of loaderCache
2594       */
2595      $translate.loaderCache = function () {
2596        return loaderCache;
2597      };
2598
2599      // internal purpose only
2600      $translate.directivePriority = function () {
2601        return directivePriority;
2602      };
2603
2604      // internal purpose only
2605      $translate.statefulFilter = function () {
2606        return statefulFilter;
2607      };
2608
2609      /**
2610       * @ngdoc function
2611       * @name pascalprecht.translate.$translate#isReady
2612       * @methodOf pascalprecht.translate.$translate
2613       *
2614       * @description
2615       * Returns whether the service is "ready" to translate (i.e. loading 1st language).
2616       *
2617       * See also {@link pascalprecht.translate.$translate#methods_onReady onReady()}.
2618       *
2619       * @return {boolean} current value of ready
2620       */
2621      $translate.isReady = function () {
2622        return $isReady;
2623      };
2624
2625      var $onReadyDeferred = $q.defer();
2626      $onReadyDeferred.promise.then(function () {
2627        $isReady = true;
2628      });
2629
2630      /**
2631       * @ngdoc function
2632       * @name pascalprecht.translate.$translate#onReady
2633       * @methodOf pascalprecht.translate.$translate
2634       *
2635       * @description
2636       * Returns whether the service is "ready" to translate (i.e. loading 1st language).
2637       *
2638       * See also {@link pascalprecht.translate.$translate#methods_isReady isReady()}.
2639       *
2640       * @param {Function=} fn Function to invoke when service is ready
2641       * @return {object} Promise resolved when service is ready
2642       */
2643      $translate.onReady = function (fn) {
2644        var deferred = $q.defer();
2645        if (angular.isFunction(fn)) {
2646          deferred.promise.then(fn);
2647        }
2648        if ($isReady) {
2649          deferred.resolve();
2650        } else {
2651          $onReadyDeferred.promise.then(deferred.resolve);
2652        }
2653        return deferred.promise;
2654      };
2655
2656      /**
2657       * @ngdoc function
2658       * @name pascalprecht.translate.$translate#getAvailableLanguageKeys
2659       * @methodOf pascalprecht.translate.$translate
2660       *
2661       * @description
2662       * This function simply returns the registered language keys being defined before in the config phase
2663       * With this, an application can use the array to provide a language selection dropdown or similar
2664       * without any additional effort
2665       *
2666       * @returns {object} returns the list of possibly registered language keys and mapping or null if not defined
2667       */
2668      $translate.getAvailableLanguageKeys = function () {
2669        if ($availableLanguageKeys.length > 0) {
2670          return $availableLanguageKeys;
2671        }
2672        return null;
2673      };
2674
2675      // Whenever $translateReady is being fired, this will ensure the state of $isReady
2676      var globalOnReadyListener = $rootScope.$on('$translateReady', function () {
2677        $onReadyDeferred.resolve();
2678        globalOnReadyListener(); // one time only
2679        globalOnReadyListener = null;
2680      });
2681      var globalOnChangeListener = $rootScope.$on('$translateChangeEnd', function () {
2682        $onReadyDeferred.resolve();
2683        globalOnChangeListener(); // one time only
2684        globalOnChangeListener = null;
2685      });
2686
2687      if ($loaderFactory) {
2688
2689        // If at least one async loader is defined and there are no
2690        // (default) translations available we should try to load them.
2691        if (angular.equals($translationTable, {})) {
2692          if ($translate.use()) {
2693            $translate.use($translate.use());
2694          }
2695        }
2696
2697        // Also, if there are any fallback language registered, we start
2698        // loading them asynchronously as soon as we can.
2699        if ($fallbackLanguage && $fallbackLanguage.length) {
2700          var processAsyncResult = function (translation) {
2701            translations(translation.key, translation.table);
2702            $rootScope.$emit('$translateChangeEnd', { language: translation.key });
2703            return translation;
2704          };
2705          for (var i = 0, len = $fallbackLanguage.length; i < len; i++) {
2706            var fallbackLanguageId = $fallbackLanguage[i];
2707            if ($forceAsyncReloadEnabled || !$translationTable[fallbackLanguageId]) {
2708              langPromises[fallbackLanguageId] = loadAsync(fallbackLanguageId).then(processAsyncResult);
2709            }
2710          }
2711        }
2712      } else {
2713        $rootScope.$emit('$translateReady', { language: $translate.use() });
2714      }
2715
2716      return $translate;
2717    }
2718  ];
2719}
2720
2721$translate.displayName = 'displayName';
2722
2723/**
2724 * @ngdoc object
2725 * @name pascalprecht.translate.$translateDefaultInterpolation
2726 * @requires $interpolate
2727 *
2728 * @description
2729 * Uses angular's `$interpolate` services to interpolate strings against some values.
2730 *
2731 * Be aware to configure a proper sanitization strategy.
2732 *
2733 * See also:
2734 * * {@link pascalprecht.translate.$translateSanitization}
2735 *
2736 * @return {object} $translateDefaultInterpolation Interpolator service
2737 */
2738angular.module('pascalprecht.translate').factory('$translateDefaultInterpolation', $translateDefaultInterpolation);
2739
2740function $translateDefaultInterpolation ($interpolate, $translateSanitization) {
2741
2742  'use strict';
2743
2744  var $translateInterpolator = {},
2745      $locale,
2746      $identifier = 'default';
2747
2748  /**
2749   * @ngdoc function
2750   * @name pascalprecht.translate.$translateDefaultInterpolation#setLocale
2751   * @methodOf pascalprecht.translate.$translateDefaultInterpolation
2752   *
2753   * @description
2754   * Sets current locale (this is currently not use in this interpolation).
2755   *
2756   * @param {string} locale Language key or locale.
2757   */
2758  $translateInterpolator.setLocale = function (locale) {
2759    $locale = locale;
2760  };
2761
2762  /**
2763   * @ngdoc function
2764   * @name pascalprecht.translate.$translateDefaultInterpolation#getInterpolationIdentifier
2765   * @methodOf pascalprecht.translate.$translateDefaultInterpolation
2766   *
2767   * @description
2768   * Returns an identifier for this interpolation service.
2769   *
2770   * @returns {string} $identifier
2771   */
2772  $translateInterpolator.getInterpolationIdentifier = function () {
2773    return $identifier;
2774  };
2775
2776  /**
2777   * @deprecated will be removed in 3.0
2778   * @see {@link pascalprecht.translate.$translateSanitization}
2779   */
2780  $translateInterpolator.useSanitizeValueStrategy = function (value) {
2781    $translateSanitization.useStrategy(value);
2782    return this;
2783  };
2784
2785  /**
2786   * @ngdoc function
2787   * @name pascalprecht.translate.$translateDefaultInterpolation#interpolate
2788   * @methodOf pascalprecht.translate.$translateDefaultInterpolation
2789   *
2790   * @description
2791   * Interpolates given value agains given interpolate params using angulars
2792   * `$interpolate` service.
2793   *
2794   * Since AngularJS 1.5, `value` must not be a string but can be anything input.
2795   *
2796   * @returns {string} interpolated string.
2797   */
2798  $translateInterpolator.interpolate = function (value, interpolationParams, context) {
2799    interpolationParams = interpolationParams || {};
2800    interpolationParams = $translateSanitization.sanitize(interpolationParams, 'params', undefined, context);
2801
2802    var interpolatedText;
2803    if (angular.isNumber(value)) {
2804      // numbers are safe
2805      interpolatedText = '' + value;
2806    } else if (angular.isString(value)) {
2807      // strings must be interpolated (that's the job here)
2808      interpolatedText = $interpolate(value)(interpolationParams);
2809      interpolatedText = $translateSanitization.sanitize(interpolatedText, 'text', undefined, context);
2810    } else {
2811      // neither a number or a string, cant interpolate => empty string
2812      interpolatedText = '';
2813    }
2814
2815    return interpolatedText;
2816  };
2817
2818  return $translateInterpolator;
2819}
2820
2821$translateDefaultInterpolation.displayName = '$translateDefaultInterpolation';
2822
2823angular.module('pascalprecht.translate').constant('$STORAGE_KEY', 'NG_TRANSLATE_LANG_KEY');
2824
2825angular.module('pascalprecht.translate')
2826/**
2827 * @ngdoc directive
2828 * @name pascalprecht.translate.directive:translate
2829 * @requires $interpolate, 
2830 * @requires $compile, 
2831 * @requires $parse, 
2832 * @requires $rootScope
2833 * @restrict AE
2834 *
2835 * @description
2836 * Translates given translation id either through attribute or DOM content.
2837 * Internally it uses $translate service to translate the translation id. It possible to
2838 * pass an optional `translate-values` object literal as string into translation id.
2839 *
2840 * @param {string=} translate Translation id which could be either string or interpolated string.
2841 * @param {string=} translate-values Values to pass into translation id. Can be passed as object literal string or interpolated object.
2842 * @param {string=} translate-attr-ATTR translate Translation id and put it into ATTR attribute.
2843 * @param {string=} translate-default will be used unless translation was successful
2844 * @param {boolean=} translate-compile (default true if present) defines locally activation of {@link pascalprecht.translate.$translateProvider#methods_usePostCompiling}
2845 * @param {boolean=} translate-keep-content (default true if present) defines that in case a KEY could not be translated, that the existing content is left in the innerHTML}
2846 *
2847 * @example
2848   <example module="ngView">
2849    <file name="index.html">
2850      <div ng-controller="TranslateCtrl">
2851
2852        <pre translate="TRANSLATION_ID"></pre>
2853        <pre translate>TRANSLATION_ID</pre>
2854        <pre translate translate-attr-title="TRANSLATION_ID"></pre>
2855        <pre translate="{{translationId}}"></pre>
2856        <pre translate>{{translationId}}</pre>
2857        <pre translate="WITH_VALUES" translate-values="{value: 5}"></pre>
2858        <pre translate translate-values="{value: 5}">WITH_VALUES</pre>
2859        <pre translate="WITH_VALUES" translate-values="{{values}}"></pre>
2860        <pre translate translate-values="{{values}}">WITH_VALUES</pre>
2861        <pre translate translate-attr-title="WITH_VALUES" translate-values="{{values}}"></pre>
2862        <pre translate="WITH_CAMEL_CASE_KEY" translate-value-camel-case-key="Hi"></pre>
2863
2864      </div>
2865    </file>
2866    <file name="script.js">
2867      angular.module('ngView', ['pascalprecht.translate'])
2868
2869      .config(function ($translateProvider) {
2870
2871        $translateProvider.translations('en',{
2872          'TRANSLATION_ID': 'Hello there!',
2873          'WITH_VALUES': 'The following value is dynamic: {{value}}',
2874          'WITH_CAMEL_CASE_KEY': 'The interpolation key is camel cased: {{camelCaseKey}}'
2875        }).preferredLanguage('en');
2876
2877      });
2878
2879      angular.module('ngView').controller('TranslateCtrl', function ($scope) {
2880        $scope.translationId = 'TRANSLATION_ID';
2881
2882        $scope.values = {
2883          value: 78
2884        };
2885      });
2886    </file>
2887    <file name="scenario.js">
2888      it('should translate', function () {
2889        inject(function ($rootScope, $compile) {
2890          $rootScope.translationId = 'TRANSLATION_ID';
2891
2892          element = $compile('<p translate="TRANSLATION_ID"></p>')($rootScope);
2893          $rootScope.$digest();
2894          expect(element.text()).toBe('Hello there!');
2895
2896          element = $compile('<p translate="{{translationId}}"></p>')($rootScope);
2897          $rootScope.$digest();
2898          expect(element.text()).toBe('Hello there!');
2899
2900          element = $compile('<p translate>TRANSLATION_ID</p>')($rootScope);
2901          $rootScope.$digest();
2902          expect(element.text()).toBe('Hello there!');
2903
2904          element = $compile('<p translate>{{translationId}}</p>')($rootScope);
2905          $rootScope.$digest();
2906          expect(element.text()).toBe('Hello there!');
2907
2908          element = $compile('<p translate translate-attr-title="TRANSLATION_ID"></p>')($rootScope);
2909          $rootScope.$digest();
2910          expect(element.attr('title')).toBe('Hello there!');
2911
2912          element = $compile('<p translate="WITH_CAMEL_CASE_KEY" translate-value-camel-case-key="Hello"></p>')($rootScope);
2913          $rootScope.$digest();
2914          expect(element.text()).toBe('The interpolation key is camel cased: Hello');
2915        });
2916      });
2917    </file>
2918   </example>
2919 */
2920.directive('translate', translateDirective);
2921function translateDirective($translate, $interpolate, $compile, $parse, $rootScope) {
2922
2923  'use strict';
2924
2925  /**
2926   * @name trim
2927   * @private
2928   *
2929   * @description
2930   * trim polyfill
2931   *
2932   * @returns {string} The string stripped of whitespace from both ends
2933   */
2934  var trim = function() {
2935    return this.toString().replace(/^\s+|\s+$/g, '');
2936  };
2937
2938  return {
2939    restrict: 'AE',
2940    scope: true,
2941    priority: $translate.directivePriority(),
2942    compile: function (tElement, tAttr) {
2943
2944      var translateValuesExist = (tAttr.translateValues) ?
2945        tAttr.translateValues : undefined;
2946
2947      var translateInterpolation = (tAttr.translateInterpolation) ?
2948        tAttr.translateInterpolation : undefined;
2949
2950      var translateValueExist = tElement[0].outerHTML.match(/translate-value-+/i);
2951
2952      var interpolateRegExp = '^(.*)(' + $interpolate.startSymbol() + '.*' + $interpolate.endSymbol() + ')(.*)',
2953          watcherRegExp = '^(.*)' + $interpolate.startSymbol() + '(.*)' + $interpolate.endSymbol() + '(.*)';
2954
2955      return function linkFn(scope, iElement, iAttr) {
2956
2957        scope.interpolateParams = {};
2958        scope.preText = '';
2959        scope.postText = '';
2960        scope.translateNamespace = getTranslateNamespace(scope);
2961        var translationIds = {};
2962
2963        var initInterpolationParams = function (interpolateParams, iAttr, tAttr) {
2964          // initial setup
2965          if (iAttr.translateValues) {
2966            angular.extend(interpolateParams, $parse(iAttr.translateValues)(scope.$parent));
2967          }
2968          // initially fetch all attributes if existing and fill the params
2969          if (translateValueExist) {
2970            for (var attr in tAttr) {
2971              if (Object.prototype.hasOwnProperty.call(iAttr, attr) && attr.substr(0, 14) === 'translateValue' && attr !== 'translateValues') {
2972                var attributeName = angular.lowercase(attr.substr(14, 1)) + attr.substr(15);
2973                interpolateParams[attributeName] = tAttr[attr];
2974              }
2975            }
2976          }
2977        };
2978
2979        // Ensures any change of the attribute "translate" containing the id will
2980        // be re-stored to the scope's "translationId".
2981        // If the attribute has no content, the element's text value (white spaces trimmed off) will be used.
2982        var observeElementTranslation = function (translationId) {
2983
2984          // Remove any old watcher
2985          if (angular.isFunction(observeElementTranslation._unwatchOld)) {
2986            observeElementTranslation._unwatchOld();
2987            observeElementTranslation._unwatchOld = undefined;
2988          }
2989
2990          if (angular.equals(translationId , '') || !angular.isDefined(translationId)) {
2991            var iElementText = trim.apply(iElement.text());
2992
2993            // Resolve translation id by inner html if required
2994            var interpolateMatches = iElementText.match(interpolateRegExp);
2995            // Interpolate translation id if required
2996            if (angular.isArray(interpolateMatches)) {
2997              scope.preText = interpolateMatches[1];
2998              scope.postText = interpolateMatches[3];
2999              translationIds.translate = $interpolate(interpolateMatches[2])(scope.$parent);
3000              var watcherMatches = iElementText.match(watcherRegExp);
3001              if (angular.isArray(watcherMatches) && watcherMatches[2] && watcherMatches[2].length) {
3002                observeElementTranslation._unwatchOld = scope.$watch(watcherMatches[2], function (newValue) {
3003                  translationIds.translate = newValue;
3004                  updateTranslations();
3005                });
3006              }
3007            } else {
3008              // do not assigne the translation id if it is empty.
3009              translationIds.translate = !iElementText ? undefined : iElementText;
3010            }
3011          } else {
3012            translationIds.translate = translationId;
3013          }
3014          updateTranslations();
3015        };
3016
3017        var observeAttributeTranslation = function (translateAttr) {
3018          iAttr.$observe(translateAttr, function (translationId) {
3019            translationIds[translateAttr] = translationId;
3020            updateTranslations();
3021          });
3022        };
3023
3024        // initial setup with values
3025        initInterpolationParams(scope.interpolateParams, iAttr, tAttr);
3026
3027        var firstAttributeChangedEvent = true;
3028        iAttr.$observe('translate', function (translationId) {
3029          if (typeof translationId === 'undefined') {
3030            // case of element "<translate>xyz</translate>"
3031            observeElementTranslation('');
3032          } else {
3033            // case of regular attribute
3034            if (translationId !== '' || !firstAttributeChangedEvent) {
3035              translationIds.translate = translationId;
3036              updateTranslations();
3037            }
3038          }
3039          firstAttributeChangedEvent = false;
3040        });
3041
3042        for (var translateAttr in iAttr) {
3043          if (iAttr.hasOwnProperty(translateAttr) && translateAttr.substr(0, 13) === 'translateAttr' && translateAttr.length > 13) {
3044            observeAttributeTranslation(translateAttr);
3045          }
3046        }
3047
3048        iAttr.$observe('translateDefault', function (value) {
3049          scope.defaultText = value;
3050          updateTranslations();
3051        });
3052
3053        if (translateValuesExist) {
3054          iAttr.$observe('translateValues', function (interpolateParams) {
3055            if (interpolateParams) {
3056              scope.$parent.$watch(function () {
3057                angular.extend(scope.interpolateParams, $parse(interpolateParams)(scope.$parent));
3058              });
3059            }
3060          });
3061        }
3062
3063        if (translateValueExist) {
3064          var observeValueAttribute = function (attrName) {
3065            iAttr.$observe(attrName, function (value) {
3066              var attributeName = angular.lowercase(attrName.substr(14, 1)) + attrName.substr(15);
3067              scope.interpolateParams[attributeName] = value;
3068            });
3069          };
3070          for (var attr in iAttr) {
3071            if (Object.prototype.hasOwnProperty.call(iAttr, attr) && attr.substr(0, 14) === 'translateValue' && attr !== 'translateValues') {
3072              observeValueAttribute(attr);
3073            }
3074          }
3075        }
3076
3077        // Master update function
3078        var updateTranslations = function () {
3079          for (var key in translationIds) {
3080            if (translationIds.hasOwnProperty(key) && translationIds[key] !== undefined) {
3081              updateTranslation(key, translationIds[key], scope, scope.interpolateParams, scope.defaultText, scope.translateNamespace);
3082            }
3083          }
3084        };
3085
3086        // Put translation processing function outside loop
3087        var updateTranslation = function(translateAttr, translationId, scope, interpolateParams, defaultTranslationText, translateNamespace) {
3088          if (translationId) {
3089            // if translation id starts with '.' and translateNamespace given, prepend namespace
3090            if (translateNamespace && translationId.charAt(0) === '.') {
3091              translationId = translateNamespace + translationId;
3092            }
3093
3094            $translate(translationId, interpolateParams, translateInterpolation, defaultTranslationText, scope.translateLanguage)
3095              .then(function (translation) {
3096                applyTranslation(translation, scope, true, translateAttr);
3097              }, function (translationId) {
3098                applyTranslation(translationId, scope, false, translateAttr);
3099              });
3100          } else {
3101            // as an empty string cannot be translated, we can solve this using successful=false
3102            applyTranslation(translationId, scope, false, translateAttr);
3103          }
3104        };
3105
3106        var applyTranslation = function (value, scope, successful, translateAttr) {
3107          if (!successful) {
3108            if (typeof scope.defaultText !== 'undefined') {
3109              value = scope.defaultText;
3110            }
3111          }
3112          if (translateAttr === 'translate') {
3113            // default translate into innerHTML
3114            if (successful || (!successful && !$translate.isKeepContent() && typeof iAttr.translateKeepContent === 'undefined')) {
3115              iElement.empty().append(scope.preText + value + scope.postText);
3116            }
3117            var globallyEnabled = $translate.isPostCompilingEnabled();
3118            var locallyDefined = typeof tAttr.translateCompile !== 'undefined';
3119            var locallyEnabled = locallyDefined && tAttr.translateCompile !== 'false';
3120            if ((globallyEnabled && !locallyDefined) || locallyEnabled) {
3121              $compile(iElement.contents())(scope);
3122            }
3123          } else {
3124            // translate attribute
3125            var attributeName = iAttr.$attr[translateAttr];
3126            if (attributeName.substr(0, 5) === 'data-') {
3127              // ensure html5 data prefix is stripped
3128              attributeName = attributeName.substr(5);
3129            }
3130            attributeName = attributeName.substr(15);
3131            iElement.attr(attributeName, value);
3132          }
3133        };
3134
3135        if (translateValuesExist || translateValueExist || iAttr.translateDefault) {
3136          scope.$watch('interpolateParams', updateTranslations, true);
3137        }
3138
3139        // Replaced watcher on translateLanguage with event listener
3140        scope.$on('translateLanguageChanged', updateTranslations);
3141
3142        // Ensures the text will be refreshed after the current language was changed
3143        // w/ $translate.use(...)
3144        var unbind = $rootScope.$on('$translateChangeSuccess', updateTranslations);
3145
3146        // ensure translation will be looked up at least one
3147        if (iElement.text().length) {
3148          if (iAttr.translate) {
3149            observeElementTranslation(iAttr.translate);
3150          } else {
3151            observeElementTranslation('');
3152          }
3153        } else if (iAttr.translate) {
3154          // ensure attribute will be not skipped
3155          observeElementTranslation(iAttr.translate);
3156        }
3157        updateTranslations();
3158        scope.$on('$destroy', unbind);
3159      };
3160    }
3161  };
3162}
3163
3164/**
3165 * Returns the scope's namespace.
3166 * @private
3167 * @param scope
3168 * @returns {string}
3169 */
3170function getTranslateNamespace(scope) {
3171  'use strict';
3172  if (scope.translateNamespace) {
3173    return scope.translateNamespace;
3174  }
3175  if (scope.$parent) {
3176    return getTranslateNamespace(scope.$parent);
3177  }
3178}
3179
3180translateDirective.displayName = 'translateDirective';
3181
3182angular.module('pascalprecht.translate')
3183/**
3184 * @ngdoc directive
3185 * @name pascalprecht.translate.directive:translate-attr
3186 * @restrict A
3187 *
3188 * @description
3189 * Translates attributes like translate-attr-ATTR, but with an object like ng-class.
3190 * Internally it uses `translate` service to translate translation id. It possible to
3191 * pass an optional `translate-values` object literal as string into translation id.
3192 *
3193 * @param {string=} translate-attr Object literal mapping attributes to translation ids.
3194 * @param {string=} translate-values Values to pass into the translation ids. Can be passed as object literal string.
3195 *
3196 * @example
3197   <example module="ngView">
3198    <file name="index.html">
3199      <div ng-controller="TranslateCtrl">
3200
3201        <input translate-attr="{ placeholder: translationId, title: 'WITH_VALUES' }" translate-values="{value: 5}" />
3202
3203      </div>
3204    </file>
3205    <file name="script.js">
3206      angular.module('ngView', ['pascalprecht.translate'])
3207
3208      .config(function ($translateProvider) {
3209
3210        $translateProvider.translations('en',{
3211          'TRANSLATION_ID': 'Hello there!',
3212          'WITH_VALUES': 'The following value is dynamic: {{value}}',
3213        }).preferredLanguage('en');
3214
3215      });
3216
3217      angular.module('ngView').controller('TranslateCtrl', function ($scope) {
3218        $scope.translationId = 'TRANSLATION_ID';
3219
3220        $scope.values = {
3221          value: 78
3222        };
3223      });
3224    </file>
3225    <file name="scenario.js">
3226      it('should translate', function () {
3227        inject(function ($rootScope, $compile) {
3228          $rootScope.translationId = 'TRANSLATION_ID';
3229
3230          element = $compile('<input translate-attr="{ placeholder: translationId, title: 'WITH_VALUES' }" translate-values="{ value: 5 }" />')($rootScope);
3231          $rootScope.$digest();
3232          expect(element.attr('placeholder)).toBe('Hello there!');
3233          expect(element.attr('title)).toBe('The following value is dynamic: 5');
3234        });
3235      });
3236    </file>
3237   </example>
3238 */
3239.directive('translateAttr', translateAttrDirective);
3240function translateAttrDirective($translate, $rootScope) {
3241
3242  'use strict';
3243
3244  return {
3245    restrict: 'A',
3246    priority: $translate.directivePriority(),
3247    link: function linkFn(scope, element, attr) {
3248
3249      var translateAttr,
3250          translateValues,
3251          previousAttributes = {};
3252
3253      // Main update translations function
3254      var updateTranslations = function () {
3255        angular.forEach(translateAttr, function (translationId, attributeName) {
3256          if (!translationId) {
3257            return;
3258          }
3259          previousAttributes[attributeName] = true;
3260
3261          // if translation id starts with '.' and translateNamespace given, prepend namespace
3262          if (scope.translateNamespace && translationId.charAt(0) === '.') {
3263            translationId = scope.translateNamespace + translationId;
3264          }
3265          $translate(translationId, translateValues, attr.translateInterpolation, undefined, scope.translateLanguage)
3266            .then(function (translation) {
3267              element.attr(attributeName, translation);
3268            }, function (translationId) {
3269              element.attr(attributeName, translationId);
3270            });
3271        });
3272
3273        // Removing unused attributes that were previously used
3274        angular.forEach(previousAttributes, function (flag, attributeName) {
3275          if (!translateAttr[attributeName]) {
3276            element.removeAttr(attributeName);
3277            delete previousAttributes[attributeName];
3278          }
3279        });
3280      };
3281
3282      // Watch for attribute changes
3283      watchAttribute(
3284        scope,
3285        attr.translateAttr,
3286        function (newValue) { translateAttr = newValue; },
3287        updateTranslations
3288      );
3289      // Watch for value changes
3290      watchAttribute(
3291        scope,
3292        attr.translateValues,
3293        function (newValue) { translateValues = newValue; },
3294        updateTranslations
3295      );
3296
3297      if (attr.translateValues) {
3298        scope.$watch(attr.translateValues, updateTranslations, true);
3299      }
3300
3301      // Replaced watcher on translateLanguage with event listener
3302      scope.$on('translateLanguageChanged', updateTranslations);
3303
3304      // Ensures the text will be refreshed after the current language was changed
3305      // w/ $translate.use(...)
3306      var unbind = $rootScope.$on('$translateChangeSuccess', updateTranslations);
3307
3308      updateTranslations();
3309      scope.$on('$destroy', unbind);
3310    }
3311  };
3312}
3313
3314function watchAttribute(scope, attribute, valueCallback, changeCallback) {
3315  'use strict';
3316  if (!attribute) {
3317    return;
3318  }
3319  if (attribute.substr(0, 2) === '::') {
3320    attribute = attribute.substr(2);
3321  } else {
3322    scope.$watch(attribute, function(newValue) {
3323      valueCallback(newValue);
3324      changeCallback();
3325    }, true);
3326  }
3327  valueCallback(scope.$eval(attribute));
3328}
3329
3330translateAttrDirective.displayName = 'translateAttrDirective';
3331
3332angular.module('pascalprecht.translate')
3333/**
3334 * @ngdoc directive
3335 * @name pascalprecht.translate.directive:translateCloak
3336 * @requires $rootScope
3337 * @requires $translate
3338 * @restrict A
3339 *
3340 * $description
3341 * Adds a `translate-cloak` class name to the given element where this directive
3342 * is applied initially and removes it, once a loader has finished loading.
3343 *
3344 * This directive can be used to prevent initial flickering when loading translation
3345 * data asynchronously.
3346 *
3347 * The class name is defined in
3348 * {@link pascalprecht.translate.$translateProvider#cloakClassName $translate.cloakClassName()}.
3349 *
3350 * @param {string=} translate-cloak If a translationId is provided, it will be used for showing
3351 *                                  or hiding the cloak. Basically it relies on the translation
3352 *                                  resolve.
3353 */
3354.directive('translateCloak', translateCloakDirective);
3355
3356function translateCloakDirective($translate, $rootScope) {
3357
3358  'use strict';
3359
3360  return {
3361    compile: function (tElement) {
3362      var applyCloak = function () {
3363        tElement.addClass($translate.cloakClassName());
3364      },
3365      removeCloak = function () {
3366        tElement.removeClass($translate.cloakClassName());
3367      };
3368      $translate.onReady(function () {
3369        removeCloak();
3370      });
3371      applyCloak();
3372
3373      return function linkFn(scope, iElement, iAttr) {
3374        if (iAttr.translateCloak && iAttr.translateCloak.length) {
3375          // Register a watcher for the defined translation allowing a fine tuned cloak
3376          iAttr.$observe('translateCloak', function (translationId) {
3377            $translate(translationId).then(removeCloak, applyCloak);
3378          });
3379          // Register for change events as this is being another indicicator revalidating the cloak)
3380          $rootScope.$on('$translateChangeSuccess', function () {
3381            $translate(iAttr.translateCloak).then(removeCloak, applyCloak);
3382          });
3383        }
3384      };
3385    }
3386  };
3387}
3388
3389translateCloakDirective.displayName = 'translateCloakDirective';
3390
3391angular.module('pascalprecht.translate')
3392/**
3393 * @ngdoc directive
3394 * @name pascalprecht.translate.directive:translateNamespace
3395 * @restrict A
3396 *
3397 * @description
3398 * Translates given translation id either through attribute or DOM content.
3399 * Internally it uses `translate` filter to translate translation id. It possible to
3400 * pass an optional `translate-values` object literal as string into translation id.
3401 *
3402 * @param {string=} translate namespace name which could be either string or interpolated string.
3403 *
3404 * @example
3405   <example module="ngView">
3406    <file name="index.html">
3407      <div translate-namespace="CONTENT">
3408
3409        <div>
3410            <h1 translate>.HEADERS.TITLE</h1>
3411            <h1 translate>.HEADERS.WELCOME</h1>
3412        </div>
3413
3414        <div translate-namespace=".HEADERS">
3415            <h1 translate>.TITLE</h1>
3416            <h1 translate>.WELCOME</h1>
3417        </div>
3418
3419      </div>
3420    </file>
3421    <file name="script.js">
3422      angular.module('ngView', ['pascalprecht.translate'])
3423
3424      .config(function ($translateProvider) {
3425
3426        $translateProvider.translations('en',{
3427          'TRANSLATION_ID': 'Hello there!',
3428          'CONTENT': {
3429            'HEADERS': {
3430                TITLE: 'Title'
3431            }
3432          },
3433          'CONTENT.HEADERS.WELCOME': 'Welcome'
3434        }).preferredLanguage('en');
3435
3436      });
3437
3438    </file>
3439   </example>
3440 */
3441.directive('translateNamespace', translateNamespaceDirective);
3442
3443function translateNamespaceDirective() {
3444
3445  'use strict';
3446
3447  return {
3448    restrict: 'A',
3449    scope: true,
3450    compile: function () {
3451      return {
3452        pre: function (scope, iElement, iAttrs) {
3453          scope.translateNamespace = getTranslateNamespace(scope);
3454
3455          if (scope.translateNamespace && iAttrs.translateNamespace.charAt(0) === '.') {
3456            scope.translateNamespace += iAttrs.translateNamespace;
3457          } else {
3458            scope.translateNamespace = iAttrs.translateNamespace;
3459          }
3460        }
3461      };
3462    }
3463  };
3464}
3465
3466/**
3467 * Returns the scope's namespace.
3468 * @private
3469 * @param scope
3470 * @returns {string}
3471 */
3472function getTranslateNamespace(scope) {
3473  'use strict';
3474  if (scope.translateNamespace) {
3475    return scope.translateNamespace;
3476  }
3477  if (scope.$parent) {
3478    return getTranslateNamespace(scope.$parent);
3479  }
3480}
3481
3482translateNamespaceDirective.displayName = 'translateNamespaceDirective';
3483
3484angular.module('pascalprecht.translate')
3485/**
3486 * @ngdoc directive
3487 * @name pascalprecht.translate.directive:translateLanguage
3488 * @restrict A
3489 *
3490 * @description
3491 * Forces the language to the directives in the underlying scope.
3492 *
3493 * @param {string=} translate language that will be negotiated.
3494 *
3495 * @example
3496   <example module="ngView">
3497    <file name="index.html">
3498      <div>
3499
3500        <div>
3501            <h1 translate>HELLO</h1>
3502        </div>
3503
3504        <div translate-language="de">
3505            <h1 translate>HELLO</h1>
3506        </div>
3507
3508      </div>
3509    </file>
3510    <file name="script.js">
3511      angular.module('ngView', ['pascalprecht.translate'])
3512
3513      .config(function ($translateProvider) {
3514
3515        $translateProvider
3516          .translations('en',{
3517            'HELLO': 'Hello world!'
3518          })
3519          .translations('de',{
3520            'HELLO': 'Hallo Welt!'
3521          })
3522          .preferredLanguage('en');
3523
3524      });
3525
3526    </file>
3527   </example>
3528 */
3529.directive('translateLanguage', translateLanguageDirective);
3530
3531function translateLanguageDirective() {
3532
3533  'use strict';
3534
3535  return {
3536    restrict: 'A',
3537    scope: true,
3538    compile: function () {
3539      return function linkFn(scope, iElement, iAttrs) {
3540
3541        iAttrs.$observe('translateLanguage', function (newTranslateLanguage) {
3542          scope.translateLanguage = newTranslateLanguage;
3543        });
3544
3545        scope.$watch('translateLanguage', function(){
3546          scope.$broadcast('translateLanguageChanged');
3547        });
3548      };
3549    }
3550  };
3551}
3552
3553translateLanguageDirective.displayName = 'translateLanguageDirective';
3554
3555angular.module('pascalprecht.translate')
3556/**
3557 * @ngdoc filter
3558 * @name pascalprecht.translate.filter:translate
3559 * @requires $parse
3560 * @requires pascalprecht.translate.$translate
3561 * @function
3562 *
3563 * @description
3564 * Uses `$translate` service to translate contents. Accepts interpolate parameters
3565 * to pass dynamized values though translation.
3566 *
3567 * @param {string} translationId A translation id to be translated.
3568 * @param {*=} interpolateParams Optional object literal (as hash or string) to pass values into translation.
3569 *
3570 * @returns {string} Translated text.
3571 *
3572 * @example
3573   <example module="ngView">
3574    <file name="index.html">
3575      <div ng-controller="TranslateCtrl">
3576
3577        <pre>{{ 'TRANSLATION_ID' | translate }}</pre>
3578        <pre>{{ translationId | translate }}</pre>
3579        <pre>{{ 'WITH_VALUES' | translate:'{value: 5}' }}</pre>
3580        <pre>{{ 'WITH_VALUES' | translate:values }}</pre>
3581
3582      </div>
3583    </file>
3584    <file name="script.js">
3585      angular.module('ngView', ['pascalprecht.translate'])
3586
3587      .config(function ($translateProvider) {
3588
3589        $translateProvider.translations('en', {
3590          'TRANSLATION_ID': 'Hello there!',
3591          'WITH_VALUES': 'The following value is dynamic: {{value}}'
3592        });
3593        $translateProvider.preferredLanguage('en');
3594
3595      });
3596
3597      angular.module('ngView').controller('TranslateCtrl', function ($scope) {
3598        $scope.translationId = 'TRANSLATION_ID';
3599
3600        $scope.values = {
3601          value: 78
3602        };
3603      });
3604    </file>
3605   </example>
3606 */
3607.filter('translate', translateFilterFactory);
3608
3609function translateFilterFactory($parse, $translate) {
3610
3611  'use strict';
3612
3613  var translateFilter = function (translationId, interpolateParams, interpolation, forceLanguage) {
3614    if (!angular.isObject(interpolateParams)) {
3615      interpolateParams = $parse(interpolateParams)(this);
3616    }
3617
3618    return $translate.instant(translationId, interpolateParams, interpolation, forceLanguage);
3619  };
3620
3621  if ($translate.statefulFilter()) {
3622    translateFilter.$stateful = true;
3623  }
3624
3625  return translateFilter;
3626}
3627
3628translateFilterFactory.displayName = 'translateFilterFactory';
3629
3630angular.module('pascalprecht.translate')
3631
3632/**
3633 * @ngdoc object
3634 * @name pascalprecht.translate.$translationCache
3635 * @requires $cacheFactory
3636 *
3637 * @description
3638 * The first time a translation table is used, it is loaded in the translation cache for quick retrieval. You
3639 * can load translation tables directly into the cache by consuming the
3640 * `$translationCache` service directly.
3641 *
3642 * @return {object} $cacheFactory object.
3643 */
3644  .factory('$translationCache', $translationCache);
3645
3646function $translationCache($cacheFactory) {
3647
3648  'use strict';
3649
3650  return $cacheFactory('translations');
3651}
3652
3653$translationCache.displayName = '$translationCache';
3654return 'pascalprecht.translate';
3655
3656}));