angular.module('myApp', ['pascalprecht.translate'], ['$translateProvider', function ($translateProvider) {

  $translateProvider.translations({
    'ANOTHER_TEXT': 'I had {{value}} beers.'
  });

}]);

angular.module('myApp').controller('Ctrl', ['$scope', function ($scope) {

  $scope.translationData = {
    value: 10
  };
  
}]);
<!doctype html>
<html ng-app="myApp" >
<head>
  <meta charset="utf-8">
  <title>AngularJS Plunker</title>
  <script>document.write('<base href="' + document.location + '" />');</script>
  <link rel="stylesheet" href="style.css">
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
  <script src="angular-translate.min.js"></script>
  <script src="app.js"></script>
</head>
<body ng-controller="Ctrl">
<p translate="ANOTHER_TEXT" values="{ value: 10}"></p>
// or
<p translate="ANOTHER_TEXT" values="{{translationData}}"></p>
</body>
</html>
/* Put your css in here */

/*!
 * angular-translate - v2.6.1 - 2015-03-01
 * http://github.com/angular-translate/angular-translate
 * Copyright (c) 2015 ; Licensed MIT
 */
/**
 * @ngdoc overview
 * @name pascalprecht.translate
 *
 * @description
 * The main module which holds everything together.
 */
angular.module('pascalprecht.translate', ['ng'])

.run(['$translate', function ($translate) {

    var key = $translate.storageKey(),
        storage = $translate.storage();

    var fallbackFromIncorrectStorageValue = function () {
        var preferred = $translate.preferredLanguage();
        if (angular.isString(preferred)) {
            $translate.use(preferred);
            // $translate.use() will also remember the language.
            // So, we don't need to call storage.put() here.
        } else {
            storage.put(key, $translate.use());
        }
    };

    if (storage) {
        if (!storage.get(key)) {
            fallbackFromIncorrectStorageValue();
        } else {
            $translate.use(storage.get(key))['catch'](fallbackFromIncorrectStorageValue);
        }
    } else if (angular.isString($translate.preferredLanguage())) {
        $translate.use($translate.preferredLanguage());
    }
}]);

/**
 * @ngdoc object
 * @name pascalprecht.translate.$translateProvider
 * @description
 *
 * $translateProvider allows developers to register translation-tables, asynchronous loaders
 * and similar to configure translation behavior directly inside of a module.
 *
 */
angular.module('pascalprecht.translate').provider('$translate', ['$STORAGE_KEY', '$windowProvider', function ($STORAGE_KEY, $windowProvider) {

    var $translationTable = {},
        $preferredLanguage,
        $availableLanguageKeys = [],
        $languageKeyAliases,
        $fallbackLanguage,
        $fallbackWasString,
        $uses,
        $nextLang,
        $storageFactory,
        $storageKey = $STORAGE_KEY,
        $storagePrefix,
        $missingTranslationHandlerFactory,
        $interpolationFactory,
        $interpolatorFactories = [],
        $interpolationSanitizationStrategy = false,
        $loaderFactory,
        $cloakClassName = 'translate-cloak',
        $loaderOptions,
        $notFoundIndicatorLeft,
        $notFoundIndicatorRight,
        $postCompilingEnabled = false,
        NESTED_OBJECT_DELIMITER = '.',
        loaderCache,
        directivePriority = 0;

    var version = '2.6.1';

    // tries to determine the browsers language
    var getFirstBrowserLanguage = function () {
        var nav = $windowProvider.$get().navigator,
            browserLanguagePropertyKeys = ['language', 'browserLanguage', 'systemLanguage', 'userLanguage'],
            i,
            language;

        // support for HTML 5.1 "navigator.languages"
        if (angular.isArray(nav.languages)) {
            for (i = 0; i < nav.languages.length; i++) {
                language = nav.languages[i];
                if (language && language.length) {
                    return language;
                }
            }
        }

        // support for other well known properties in browsers
        for (i = 0; i < browserLanguagePropertyKeys.length; i++) {
            language = nav[browserLanguagePropertyKeys[i]];
            if (language && language.length) {
                return language;
            }
        }

        return null;
    };
    getFirstBrowserLanguage.displayName = 'angular-translate/service: getFirstBrowserLanguage';

    // tries to determine the browsers locale
    var getLocale = function () {
        return (getFirstBrowserLanguage() || '').split('-').join('_');
    };
    getLocale.displayName = 'angular-translate/service: getLocale';

    /**
     * @name indexOf
     * @private
     *
     * @description
     * indexOf polyfill. Kinda sorta.
     *
     * @param {array} array Array to search in.
     * @param {string} searchElement Element to search for.
     *
     * @returns {int} Index of search element.
     */
    var indexOf = function (array, searchElement) {
        for (var i = 0, len = array.length; i < len; i++) {
            if (array[i] === searchElement) {
                return i;
            }
        }
        return -1;
    };

    /**
     * @name trim
     * @private
     *
     * @description
     * trim polyfill
     *
     * @returns {string} The string stripped of whitespace from both ends
     */
    var trim = function () {
        return this.replace(/^\s+|\s+$/g, '');
    };

    var negotiateLocale = function (preferred) {

        var avail = [],
            locale = angular.lowercase(preferred),
            i = 0,
            n = $availableLanguageKeys.length;

        for (; i < n; i++) {
            avail.push(angular.lowercase($availableLanguageKeys[i]));
        }

        if (indexOf(avail, locale) > -1) {
            return preferred;
        }

        if ($languageKeyAliases) {
            var alias;
            for (var langKeyAlias in $languageKeyAliases) {
                var hasWildcardKey = false;
                var hasExactKey = Object.prototype.hasOwnProperty.call($languageKeyAliases, langKeyAlias) &&
                  angular.lowercase(langKeyAlias) === angular.lowercase(preferred);

                if (langKeyAlias.slice(-1) === '*') {
                    hasWildcardKey = langKeyAlias.slice(0, -1) === preferred.slice(0, langKeyAlias.length - 1);
                }
                if (hasExactKey || hasWildcardKey) {
                    alias = $languageKeyAliases[langKeyAlias];
                    if (indexOf(avail, angular.lowercase(alias)) > -1) {
                        return alias;
                    }
                }
            }
        }

        var parts = preferred.split('_');

        if (parts.length > 1 && indexOf(avail, angular.lowercase(parts[0])) > -1) {
            return parts[0];
        }

        // If everything fails, just return the preferred, unchanged.
        return preferred;
    };

    /**
     * @ngdoc function
     * @name pascalprecht.translate.$translateProvider#translations
     * @methodOf pascalprecht.translate.$translateProvider
     *
     * @description
     * Registers a new translation table for specific language key.
     *
     * To register a translation table for specific language, pass a defined language
     * key as first parameter.
     *
     * <pre>
     *  // register translation table for language: 'de_DE'
     *  $translateProvider.translations('de_DE', {
     *    'GREETING': 'Hallo Welt!'
     *  });
     *
     *  // register another one
     *  $translateProvider.translations('en_US', {
     *    'GREETING': 'Hello world!'
     *  });
     * </pre>
     *
     * When registering multiple translation tables for for the same language key,
     * the actual translation table gets extended. This allows you to define module
     * specific translation which only get added, once a specific module is loaded in
     * your app.
     *
     * Invoking this method with no arguments returns the translation table which was
     * registered with no language key. Invoking it with a language key returns the
     * related translation table.
     *
     * @param {string} key A language key.
     * @param {object} translationTable A plain old JavaScript object that represents a translation table.
     *
     */
    var translations = function (langKey, translationTable) {

        if (!langKey && !translationTable) {
            return $translationTable;
        }

        if (langKey && !translationTable) {
            if (angular.isString(langKey)) {
                return $translationTable[langKey];
            }
        } else {
            if (!angular.isObject($translationTable[langKey])) {
                $translationTable[langKey] = {};
            }
            angular.extend($translationTable[langKey], flatObject(translationTable));
        }
        return this;
    };

    this.translations = translations;

    /**
     * @ngdoc function
     * @name pascalprecht.translate.$translateProvider#cloakClassName
     * @methodOf pascalprecht.translate.$translateProvider
     *
     * @description
     *
     * Let's you change the class name for `translate-cloak` directive.
     * Default class name is `translate-cloak`.
     *
     * @param {string} name translate-cloak class name
     */
    this.cloakClassName = function (name) {
        if (!name) {
            return $cloakClassName;
        }
        $cloakClassName = name;
        return this;
    };

    /**
     * @name flatObject
     * @private
     *
     * @description
     * Flats an object. This function is used to flatten given translation data with
     * namespaces, so they are later accessible via dot notation.
     */
    var flatObject = function (data, path, result, prevKey) {
        var key, keyWithPath, keyWithShortPath, val;

        if (!path) {
            path = [];
        }
        if (!result) {
            result = {};
        }
        for (key in data) {
            if (!Object.prototype.hasOwnProperty.call(data, key)) {
                continue;
            }
            val = data[key];
            if (angular.isObject(val)) {
                flatObject(val, path.concat(key), result, key);
            } else {
                keyWithPath = path.length ? ('' + path.join(NESTED_OBJECT_DELIMITER) + NESTED_OBJECT_DELIMITER + key) : key;
                if (path.length && key === prevKey) {
                    // Create shortcut path (foo.bar == foo.bar.bar)
                    keyWithShortPath = '' + path.join(NESTED_OBJECT_DELIMITER);
                    // Link it to original path
                    result[keyWithShortPath] = '@:' + keyWithPath;
                }
                result[keyWithPath] = val;
            }
        }
        return result;
    };

    /**
     * @ngdoc function
     * @name pascalprecht.translate.$translateProvider#addInterpolation
     * @methodOf pascalprecht.translate.$translateProvider
     *
     * @description
     * Adds interpolation services to angular-translate, so it can manage them.
     *
     * @param {object} factory Interpolation service factory
     */
    this.addInterpolation = function (factory) {
        $interpolatorFactories.push(factory);
        return this;
    };

    /**
     * @ngdoc function
     * @name pascalprecht.translate.$translateProvider#useMessageFormatInterpolation
     * @methodOf pascalprecht.translate.$translateProvider
     *
     * @description
     * Tells angular-translate to use interpolation functionality of messageformat.js.
     * This is useful when having high level pluralization and gender selection.
     */
    this.useMessageFormatInterpolation = function () {
        return this.useInterpolation('$translateMessageFormatInterpolation');
    };

    /**
     * @ngdoc function
     * @name pascalprecht.translate.$translateProvider#useInterpolation
     * @methodOf pascalprecht.translate.$translateProvider
     *
     * @description
     * Tells angular-translate which interpolation style to use as default, application-wide.
     * Simply pass a factory/service name. The interpolation service has to implement
     * the correct interface.
     *
     * @param {string} factory Interpolation service name.
     */
    this.useInterpolation = function (factory) {
        $interpolationFactory = factory;
        return this;
    };

    /**
     * @ngdoc function
     * @name pascalprecht.translate.$translateProvider#useSanitizeStrategy
     * @methodOf pascalprecht.translate.$translateProvider
     *
     * @description
     * Simply sets a sanitation strategy type.
     *
     * @param {string} value Strategy type.
     */
    this.useSanitizeValueStrategy = function (value) {
        $interpolationSanitizationStrategy = value;
        return this;
    };

    /**
      * @ngdoc function
      * @name pascalprecht.translate.$translateProvider#preferredLanguage
      * @methodOf pascalprecht.translate.$translateProvider
      *
      * @description
      * Tells the module which of the registered translation tables to use for translation
      * at initial startup by passing a language key. Similar to `$translateProvider#use`
      * only that it says which language to **prefer**.
      *
      * @param {string} langKey A language key.
      *
      */
    this.preferredLanguage = function (langKey) {
        setupPreferredLanguage(langKey);
        return this;

    };
    var setupPreferredLanguage = function (langKey) {
        if (langKey) {
            $preferredLanguage = langKey;
        }
        return $preferredLanguage;
    };
    /**
     * @ngdoc function
     * @name pascalprecht.translate.$translateProvider#translationNotFoundIndicator
     * @methodOf pascalprecht.translate.$translateProvider
     *
     * @description
     * Sets an indicator which is used when a translation isn't found. E.g. when
     * setting the indicator as 'X' and one tries to translate a translation id
     * called `NOT_FOUND`, this will result in `X NOT_FOUND X`.
     *
     * Internally this methods sets a left indicator and a right indicator using
     * `$translateProvider.translationNotFoundIndicatorLeft()` and
     * `$translateProvider.translationNotFoundIndicatorRight()`.
     *
     * **Note**: These methods automatically add a whitespace between the indicators
     * and the translation id.
     *
     * @param {string} indicator An indicator, could be any string.
     */
    this.translationNotFoundIndicator = function (indicator) {
        this.translationNotFoundIndicatorLeft(indicator);
        this.translationNotFoundIndicatorRight(indicator);
        return this;
    };

    /**
     * ngdoc function
     * @name pascalprecht.translate.$translateProvider#translationNotFoundIndicatorLeft
     * @methodOf pascalprecht.translate.$translateProvider
     *
     * @description
     * Sets an indicator which is used when a translation isn't found left to the
     * translation id.
     *
     * @param {string} indicator An indicator.
     */
    this.translationNotFoundIndicatorLeft = function (indicator) {
        if (!indicator) {
            return $notFoundIndicatorLeft;
        }
        $notFoundIndicatorLeft = indicator;
        return this;
    };

    /**
     * ngdoc function
     * @name pascalprecht.translate.$translateProvider#translationNotFoundIndicatorLeft
     * @methodOf pascalprecht.translate.$translateProvider
     *
     * @description
     * Sets an indicator which is used when a translation isn't found right to the
     * translation id.
     *
     * @param {string} indicator An indicator.
     */
    this.translationNotFoundIndicatorRight = function (indicator) {
        if (!indicator) {
            return $notFoundIndicatorRight;
        }
        $notFoundIndicatorRight = indicator;
        return this;
    };

    /**
     * @ngdoc function
     * @name pascalprecht.translate.$translateProvider#fallbackLanguage
     * @methodOf pascalprecht.translate.$translateProvider
     *
     * @description
     * Tells the module which of the registered translation tables to use when missing translations
     * at initial startup by passing a language key. Similar to `$translateProvider#use`
     * only that it says which language to **fallback**.
     *
     * @param {string||array} langKey A language key.
     *
     */
    this.fallbackLanguage = function (langKey) {
        fallbackStack(langKey);
        return this;
    };

    var fallbackStack = function (langKey) {
        if (langKey) {
            if (angular.isString(langKey)) {
                $fallbackWasString = true;
                $fallbackLanguage = [langKey];
            } else if (angular.isArray(langKey)) {
                $fallbackWasString = false;
                $fallbackLanguage = langKey;
            }
            if (angular.isString($preferredLanguage) && indexOf($fallbackLanguage, $preferredLanguage) < 0) {
                $fallbackLanguage.push($preferredLanguage);
            }

            return this;
        } else {
            if ($fallbackWasString) {
                return $fallbackLanguage[0];
            } else {
                return $fallbackLanguage;
            }
        }
    };

    /**
     * @ngdoc function
     * @name pascalprecht.translate.$translateProvider#use
     * @methodOf pascalprecht.translate.$translateProvider
     *
     * @description
     * Set which translation table to use for translation by given language key. When
     * trying to 'use' a language which isn't provided, it'll throw an error.
     *
     * You actually don't have to use this method since `$translateProvider#preferredLanguage`
     * does the job too.
     *
     * @param {string} langKey A language key.
     */
    this.use = function (langKey) {
        if (langKey) {
            if (!$translationTable[langKey] && (!$loaderFactory)) {
                // only throw an error, when not loading translation data asynchronously
                throw new Error("$translateProvider couldn't find translationTable for langKey: '" + langKey + "'");
            }
            $uses = langKey;
            return this;
        }
        return $uses;
    };

    /**
      * @ngdoc function
      * @name pascalprecht.translate.$translateProvider#storageKey
      * @methodOf pascalprecht.translate.$translateProvider
      *
      * @description
      * Tells the module which key must represent the choosed language by a user in the storage.
      *
      * @param {string} key A key for the storage.
      */
    var storageKey = function (key) {
        if (!key) {
            if ($storagePrefix) {
                return $storagePrefix + $storageKey;
            }
            return $storageKey;
        }
        $storageKey = key;
    };

    this.storageKey = storageKey;

    /**
     * @ngdoc function
     * @name pascalprecht.translate.$translateProvider#useUrlLoader
     * @methodOf pascalprecht.translate.$translateProvider
     *
     * @description
     * Tells angular-translate to use `$translateUrlLoader` extension service as loader.
     *
     * @param {string} url Url
     * @param {Object=} options Optional configuration object
     */
    this.useUrlLoader = function (url, options) {
        return this.useLoader('$translateUrlLoader', angular.extend({ url: url }, options));
    };

    /**
     * @ngdoc function
     * @name pascalprecht.translate.$translateProvider#useStaticFilesLoader
     * @methodOf pascalprecht.translate.$translateProvider
     *
     * @description
     * Tells angular-translate to use `$translateStaticFilesLoader` extension service as loader.
     *
     * @param {Object=} options Optional configuration object
     */
    this.useStaticFilesLoader = function (options) {
        return this.useLoader('$translateStaticFilesLoader', options);
    };

    /**
     * @ngdoc function
     * @name pascalprecht.translate.$translateProvider#useLoader
     * @methodOf pascalprecht.translate.$translateProvider
     *
     * @description
     * Tells angular-translate to use any other service as loader.
     *
     * @param {string} loaderFactory Factory name to use
     * @param {Object=} options Optional configuration object
     */
    this.useLoader = function (loaderFactory, options) {
        $loaderFactory = loaderFactory;
        $loaderOptions = options || {};
        return this;
    };

    /**
     * @ngdoc function
     * @name pascalprecht.translate.$translateProvider#useLocalStorage
     * @methodOf pascalprecht.translate.$translateProvider
     *
     * @description
     * Tells angular-translate to use `$translateLocalStorage` service as storage layer.
     *
     */
    this.useLocalStorage = function () {
        return this.useStorage('$translateLocalStorage');
    };

    /**
     * @ngdoc function
     * @name pascalprecht.translate.$translateProvider#useCookieStorage
     * @methodOf pascalprecht.translate.$translateProvider
     *
     * @description
     * Tells angular-translate to use `$translateCookieStorage` service as storage layer.
     */
    this.useCookieStorage = function () {
        return this.useStorage('$translateCookieStorage');
    };

    /**
     * @ngdoc function
     * @name pascalprecht.translate.$translateProvider#useStorage
     * @methodOf pascalprecht.translate.$translateProvider
     *
     * @description
     * Tells angular-translate to use custom service as storage layer.
     */
    this.useStorage = function (storageFactory) {
        $storageFactory = storageFactory;
        return this;
    };

    /**
     * @ngdoc function
     * @name pascalprecht.translate.$translateProvider#storagePrefix
     * @methodOf pascalprecht.translate.$translateProvider
     *
     * @description
     * Sets prefix for storage key.
     *
     * @param {string} prefix Storage key prefix
     */
    this.storagePrefix = function (prefix) {
        if (!prefix) {
            return prefix;
        }
        $storagePrefix = prefix;
        return this;
    };

    /**
     * @ngdoc function
     * @name pascalprecht.translate.$translateProvider#useMissingTranslationHandlerLog
     * @methodOf pascalprecht.translate.$translateProvider
     *
     * @description
     * Tells angular-translate to use built-in log handler when trying to translate
     * a translation Id which doesn't exist.
     *
     * This is actually a shortcut method for `useMissingTranslationHandler()`.
     *
     */
    this.useMissingTranslationHandlerLog = function () {
        return this.useMissingTranslationHandler('$translateMissingTranslationHandlerLog');
    };

    /**
     * @ngdoc function
     * @name pascalprecht.translate.$translateProvider#useMissingTranslationHandler
     * @methodOf pascalprecht.translate.$translateProvider
     *
     * @description
     * Expects a factory name which later gets instantiated with `$injector`.
     * This method can be used to tell angular-translate to use a custom
     * missingTranslationHandler. Just build a factory which returns a function
     * and expects a translation id as argument.
     *
     * Example:
     * <pre>
     *  app.config(function ($translateProvider) {
     *    $translateProvider.useMissingTranslationHandler('customHandler');
     *  });
     *
     *  app.factory('customHandler', function (dep1, dep2) {
     *    return function (translationId) {
     *      // something with translationId and dep1 and dep2
     *    };
     *  });
     * </pre>
     *
     * @param {string} factory Factory name
     */
    this.useMissingTranslationHandler = function (factory) {
        $missingTranslationHandlerFactory = factory;
        return this;
    };

    /**
     * @ngdoc function
     * @name pascalprecht.translate.$translateProvider#usePostCompiling
     * @methodOf pascalprecht.translate.$translateProvider
     *
     * @description
     * If post compiling is enabled, all translated values will be processed
     * again with AngularJS' $compile.
     *
     * Example:
     * <pre>
     *  app.config(function ($translateProvider) {
     *    $translateProvider.usePostCompiling(true);
     *  });
     * </pre>
     *
     * @param {string} factory Factory name
     */
    this.usePostCompiling = function (value) {
        $postCompilingEnabled = !(!value);
        return this;
    };

    /**
     * @ngdoc function
     * @name pascalprecht.translate.$translateProvider#determinePreferredLanguage
     * @methodOf pascalprecht.translate.$translateProvider
     *
     * @description
     * Tells angular-translate to try to determine on its own which language key
     * to set as preferred language. When `fn` is given, angular-translate uses it
     * to determine a language key, otherwise it uses the built-in `getLocale()`
     * method.
     *
     * The `getLocale()` returns a language key in the format `[lang]_[country]` or
     * `[lang]` depending on what the browser provides.
     *
     * Use this method at your own risk, since not all browsers return a valid
     * locale.
     *
     * @param {object=} fn Function to determine a browser's locale
     */
    this.determinePreferredLanguage = function (fn) {

        var locale = (fn && angular.isFunction(fn)) ? fn() : getLocale();

        if (!$availableLanguageKeys.length) {
            $preferredLanguage = locale;
        } else {
            $preferredLanguage = negotiateLocale(locale);
        }

        return this;
    };

    /**
     * @ngdoc function
     * @name pascalprecht.translate.$translateProvider#registerAvailableLanguageKeys
     * @methodOf pascalprecht.translate.$translateProvider
     *
     * @description
     * Registers a set of language keys the app will work with. Use this method in
     * combination with
     * {@link pascalprecht.translate.$translateProvider#determinePreferredLanguage determinePreferredLanguage}.
     * When available languages keys are registered, angular-translate
     * tries to find the best fitting language key depending on the browsers locale,
     * considering your language key convention.
     *
     * @param {object} languageKeys Array of language keys the your app will use
     * @param {object=} aliases Alias map.
     */
    this.registerAvailableLanguageKeys = function (languageKeys, aliases) {
        if (languageKeys) {
            $availableLanguageKeys = languageKeys;
            if (aliases) {
                $languageKeyAliases = aliases;
            }
            return this;
        }
        return $availableLanguageKeys;
    };

    /**
     * @ngdoc function
     * @name pascalprecht.translate.$translateProvider#useLoaderCache
     * @methodOf pascalprecht.translate.$translateProvider
     *
     * @description
     * Registers a cache for internal $http based loaders.
     * {@link pascalprecht.translate.$translateProvider#determinePreferredLanguage determinePreferredLanguage}.
     * When false the cache will be disabled (default). When true or undefined
     * the cache will be a default (see $cacheFactory). When an object it will
     * be treat as a cache object itself: the usage is $http({cache: cache})
     *
     * @param {object} cache boolean, string or cache-object
     */
    this.useLoaderCache = function (cache) {
        if (cache === false) {
            // disable cache
            loaderCache = undefined;
        } else if (cache === true) {
            // enable cache using AJS defaults
            loaderCache = true;
        } else if (typeof (cache) === 'undefined') {
            // enable cache using default
            loaderCache = '$translationCache';
        } else if (cache) {
            // enable cache using given one (see $cacheFactory)
            loaderCache = cache;
        }
        return this;
    };

    /**
     * @ngdoc function
     * @name pascalprecht.translate.$translateProvider#directivePriority
     * @methodOf pascalprecht.translate.$translateProvider
     *
     * @description
     * Sets the default priority of the translate directive. The standard value is `0`.
     * Calling this function without an argument will return the current value.
     *
     * @param {number} priority for the translate-directive
     */
    this.directivePriority = function (priority) {
        if (priority === undefined) {
            // getter
            return directivePriority;
        } else {
            // setter with chaining
            directivePriority = priority;
            return this;
        }
    };

    /**
     * @ngdoc object
     * @name pascalprecht.translate.$translate
     * @requires $interpolate
     * @requires $log
     * @requires $rootScope
     * @requires $q
     *
     * @description
     * The `$translate` service is the actual core of angular-translate. It expects a translation id
     * and optional interpolate parameters to translate contents.
     *
     * <pre>
     *  $translate('HEADLINE_TEXT').then(function (translation) {
     *    $scope.translatedText = translation;
     *  });
     * </pre>
     *
     * @param {string|array} translationId A token which represents a translation id
     *                                     This can be optionally an array of translation ids which
     *                                     results that the function returns an object where each key
     *                                     is the translation id and the value the translation.
     * @param {object=} interpolateParams An object hash for dynamic values
     * @param {string} interpolationId The id of the interpolation to use
     * @returns {object} promise
     */
    this.$get = [
      '$log',
      '$injector',
      '$rootScope',
      '$q',
      function ($log, $injector, $rootScope, $q) {

          var Storage,
              defaultInterpolator = $injector.get($interpolationFactory || '$translateDefaultInterpolation'),
              pendingLoader = false,
              interpolatorHashMap = {},
              langPromises = {},
              fallbackIndex,
              startFallbackIteration;

          var $translate = function (translationId, interpolateParams, interpolationId, defaultTranslationText) {

              // Duck detection: If the first argument is an array, a bunch of translations was requested.
              // The result is an object.
              if (angular.isArray(translationId)) {
                  // Inspired by Q.allSettled by Kris Kowal
                  // https://github.com/kriskowal/q/blob/b0fa72980717dc202ffc3cbf03b936e10ebbb9d7/q.js#L1553-1563
                  // This transforms all promises regardless resolved or rejected
                  var translateAll = function (translationIds) {
                      var results = {}; // storing the actual results
                      var promises = []; // promises to wait for
                      // Wraps the promise a) being always resolved and b) storing the link id->value
                      var translate = function (translationId) {
                          var deferred = $q.defer();
                          var regardless = function (value) {
                              results[translationId] = value;
                              deferred.resolve([translationId, value]);
                          };
                          // we don't care whether the promise was resolved or rejected; just store the values
                          $translate(translationId, interpolateParams, interpolationId, defaultTranslationText).then(regardless, regardless);
                          return deferred.promise;
                      };
                      for (var i = 0, c = translationIds.length; i < c; i++) {
                          promises.push(translate(translationIds[i]));
                      }
                      // wait for all (including storing to results)
                      return $q.all(promises).then(function () {
                          // return the results
                          return results;
                      });
                  };
                  return translateAll(translationId);
              }

              var deferred = $q.defer();

              // trim off any whitespace
              if (translationId) {
                  translationId = trim.apply(translationId);
              }

              var promiseToWaitFor = (function () {
                  var promise = $preferredLanguage ?
                    langPromises[$preferredLanguage] :
                    langPromises[$uses];

                  fallbackIndex = 0;

                  if ($storageFactory && !promise) {
                      // looks like there's no pending promise for $preferredLanguage or
                      // $uses. Maybe there's one pending for a language that comes from
                      // storage.
                      var langKey = Storage.get($storageKey);
                      promise = langPromises[langKey];

                      if ($fallbackLanguage && $fallbackLanguage.length) {
                          var index = indexOf($fallbackLanguage, langKey);
                          // maybe the language from storage is also defined as fallback language
                          // we increase the fallback language index to not search in that language
                          // as fallback, since it's probably the first used language
                          // in that case the index starts after the first element
                          fallbackIndex = (index === 0) ? 1 : 0;

                          // but we can make sure to ALWAYS fallback to preferred language at least
                          if (indexOf($fallbackLanguage, $preferredLanguage) < 0) {
                              $fallbackLanguage.push($preferredLanguage);
                          }
                      }
                  }
                  return promise;
              }());

              if (!promiseToWaitFor) {
                  // no promise to wait for? okay. Then there's no loader registered
                  // nor is a one pending for language that comes from storage.
                  // We can just translate.
                  determineTranslation(translationId, interpolateParams, interpolationId, defaultTranslationText).then(deferred.resolve, deferred.reject);
              } else {
                  promiseToWaitFor.then(function () {
                      determineTranslation(translationId, interpolateParams, interpolationId, defaultTranslationText).then(deferred.resolve, deferred.reject);
                  }, deferred.reject);
              }
              return deferred.promise;
          };

          /**
           * @name applyNotFoundIndicators
           * @private
           *
           * @description
           * Applies not fount indicators to given translation id, if needed.
           * This function gets only executed, if a translation id doesn't exist,
           * which is why a translation id is expected as argument.
           *
           * @param {string} translationId Translation id.
           * @returns {string} Same as given translation id but applied with not found
           * indicators.
           */
          var applyNotFoundIndicators = function (translationId) {
              // applying notFoundIndicators
              if ($notFoundIndicatorLeft) {
                  translationId = [$notFoundIndicatorLeft, translationId].join(' ');
              }
              if ($notFoundIndicatorRight) {
                  translationId = [translationId, $notFoundIndicatorRight].join(' ');
              }
              return translationId;
          };

          /**
           * @name useLanguage
           * @private
           *
           * @description
           * Makes actual use of a language by setting a given language key as used
           * language and informs registered interpolators to also use the given
           * key as locale.
           *
           * @param {key} Locale key.
           */
          var useLanguage = function (key) {
              $uses = key;
              $rootScope.$emit('$translateChangeSuccess', { language: key });

              if ($storageFactory) {
                  Storage.put($translate.storageKey(), $uses);
              }
              // inform default interpolator
              defaultInterpolator.setLocale($uses);
              // inform all others too!
              angular.forEach(interpolatorHashMap, function (interpolator, id) {
                  interpolatorHashMap[id].setLocale($uses);
              });
              $rootScope.$emit('$translateChangeEnd', { language: key });
          };

          /**
           * @name loadAsync
           * @private
           *
           * @description
           * Kicks of registered async loader using `$injector` and applies existing
           * loader options. When resolved, it updates translation tables accordingly
           * or rejects with given language key.
           *
           * @param {string} key Language key.
           * @return {Promise} A promise.
           */
          var loadAsync = function (key) {
              if (!key) {
                  throw 'No language key specified for loading.';
              }

              var deferred = $q.defer();

              $rootScope.$emit('$translateLoadingStart', { language: key });
              pendingLoader = true;

              var cache = loaderCache;
              if (typeof (cache) === 'string') {
                  // getting on-demand instance of loader
                  cache = $injector.get(cache);
              }

              var loaderOptions = angular.extend({}, $loaderOptions, {
                  key: key,
                  $http: angular.extend({}, {
                      cache: cache
                  }, $loaderOptions.$http)
              });

              $injector.get($loaderFactory)(loaderOptions).then(function (data) {
                  var translationTable = {};
                  $rootScope.$emit('$translateLoadingSuccess', { language: key });

                  if (angular.isArray(data)) {
                      angular.forEach(data, function (table) {
                          angular.extend(translationTable, flatObject(table));
                      });
                  } else {
                      angular.extend(translationTable, flatObject(data));
                  }
                  pendingLoader = false;
                  deferred.resolve({
                      key: key,
                      table: translationTable
                  });
                  $rootScope.$emit('$translateLoadingEnd', { language: key });
              }, function (key) {
                  $rootScope.$emit('$translateLoadingError', { language: key });
                  deferred.reject(key);
                  $rootScope.$emit('$translateLoadingEnd', { language: key });
              });
              return deferred.promise;
          };

          if ($storageFactory) {
              Storage = $injector.get($storageFactory);

              if (!Storage.get || !Storage.put) {
                  throw new Error('Couldn\'t use storage \'' + $storageFactory + '\', missing get() or put() method!');
              }
          }

          // apply additional settings
          if (angular.isFunction(defaultInterpolator.useSanitizeValueStrategy)) {
              defaultInterpolator.useSanitizeValueStrategy($interpolationSanitizationStrategy);
          }

          // if we have additional interpolations that were added via
          // $translateProvider.addInterpolation(), we have to map'em
          if ($interpolatorFactories.length) {
              angular.forEach($interpolatorFactories, function (interpolatorFactory) {
                  var interpolator = $injector.get(interpolatorFactory);
                  // setting initial locale for each interpolation service
                  interpolator.setLocale($preferredLanguage || $uses);
                  // apply additional settings
                  if (angular.isFunction(interpolator.useSanitizeValueStrategy)) {
                      interpolator.useSanitizeValueStrategy($interpolationSanitizationStrategy);
                  }
                  // make'em recognizable through id
                  interpolatorHashMap[interpolator.getInterpolationIdentifier()] = interpolator;
              });
          }

          /**
           * @name getTranslationTable
           * @private
           *
           * @description
           * Returns a promise that resolves to the translation table
           * or is rejected if an error occurred.
           *
           * @param langKey
           * @returns {Q.promise}
           */
          var getTranslationTable = function (langKey) {
              var deferred = $q.defer();
              if (Object.prototype.hasOwnProperty.call($translationTable, langKey)) {
                  deferred.resolve($translationTable[langKey]);
              } else if (langPromises[langKey]) {
                  langPromises[langKey].then(function (data) {
                      translations(data.key, data.table);
                      deferred.resolve(data.table);
                  }, deferred.reject);
              } else {
                  deferred.reject();
              }
              return deferred.promise;
          };

          /**
           * @name getFallbackTranslation
           * @private
           *
           * @description
           * Returns a promise that will resolve to the translation
           * or be rejected if no translation was found for the language.
           * This function is currently only used for fallback language translation.
           *
           * @param langKey The language to translate to.
           * @param translationId
           * @param interpolateParams
           * @param Interpolator
           * @returns {Q.promise}
           */
          var getFallbackTranslation = function (langKey, translationId, interpolateParams, Interpolator) {
              var deferred = $q.defer();

              getTranslationTable(langKey).then(function (translationTable) {
                  if (Object.prototype.hasOwnProperty.call(translationTable, translationId)) {
                      Interpolator.setLocale(langKey);
                      var translation = translationTable[translationId];
                      if (translation.substr(0, 2) === '@:') {
                          getFallbackTranslation(langKey, translation.substr(2), interpolateParams, Interpolator)
                            .then(deferred.resolve, deferred.reject);
                      } else {
                          deferred.resolve(Interpolator.interpolate(translationTable[translationId], interpolateParams));
                      }
                      Interpolator.setLocale($uses);
                  } else {
                      deferred.reject();
                  }
              }, deferred.reject);

              return deferred.promise;
          };

          /**
           * @name getFallbackTranslationInstant
           * @private
           *
           * @description
           * Returns a translation
           * This function is currently only used for fallback language translation.
           *
           * @param langKey The language to translate to.
           * @param translationId
           * @param interpolateParams
           * @param Interpolator
           * @returns {string} translation
           */
          var getFallbackTranslationInstant = function (langKey, translationId, interpolateParams, Interpolator) {
              var result, translationTable = $translationTable[langKey];

              if (translationTable && Object.prototype.hasOwnProperty.call(translationTable, translationId)) {
                  Interpolator.setLocale(langKey);
                  result = Interpolator.interpolate(translationTable[translationId], interpolateParams);
                  if (result.substr(0, 2) === '@:') {
                      return getFallbackTranslationInstant(langKey, result.substr(2), interpolateParams, Interpolator);
                  }
                  Interpolator.setLocale($uses);
              }

              return result;
          };


          /**
           * @name translateByHandler
           * @private
           *
           * Translate by missing translation handler.
           *
           * @param translationId
           * @returns translation created by $missingTranslationHandler or translationId is $missingTranslationHandler is
           * absent
           */
          var translateByHandler = function (translationId) {
              // If we have a handler factory - we might also call it here to determine if it provides
              // a default text for a translationid that can't be found anywhere in our tables
              if ($missingTranslationHandlerFactory) {
                  var resultString = $injector.get($missingTranslationHandlerFactory)(translationId, $uses);
                  if (resultString !== undefined) {
                      return resultString;
                  } else {
                      return translationId;
                  }
              } else {
                  return translationId;
              }
          };

          /**
           * @name resolveForFallbackLanguage
           * @private
           *
           * Recursive helper function for fallbackTranslation that will sequentially look
           * for a translation in the fallbackLanguages starting with fallbackLanguageIndex.
           *
           * @param fallbackLanguageIndex
           * @param translationId
           * @param interpolateParams
           * @param Interpolator
           * @returns {Q.promise} Promise that will resolve to the translation.
           */
          var resolveForFallbackLanguage = function (fallbackLanguageIndex, translationId, interpolateParams, Interpolator, defaultTranslationText) {
              var deferred = $q.defer();

              if (fallbackLanguageIndex < $fallbackLanguage.length) {
                  var langKey = $fallbackLanguage[fallbackLanguageIndex];
                  getFallbackTranslation(langKey, translationId, interpolateParams, Interpolator).then(
                    deferred.resolve,
                    function () {
                        // Look in the next fallback language for a translation.
                        // It delays the resolving by passing another promise to resolve.
                        resolveForFallbackLanguage(fallbackLanguageIndex + 1, translationId, interpolateParams, Interpolator, defaultTranslationText).then(deferred.resolve);
                    }
                  );
              } else {
                  // No translation found in any fallback language
                  // if a default translation text is set in the directive, then return this as a result
                  if (defaultTranslationText) {
                      deferred.resolve(defaultTranslationText);
                  } else {
                      // if no default translation is set and an error handler is defined, send it to the handler
                      // and then return the result
                      deferred.resolve(translateByHandler(translationId));
                  }
              }
              return deferred.promise;
          };

          /**
           * @name resolveForFallbackLanguageInstant
           * @private
           *
           * Recursive helper function for fallbackTranslation that will sequentially look
           * for a translation in the fallbackLanguages starting with fallbackLanguageIndex.
           *
           * @param fallbackLanguageIndex
           * @param translationId
           * @param interpolateParams
           * @param Interpolator
           * @returns {string} translation
           */
          var resolveForFallbackLanguageInstant = function (fallbackLanguageIndex, translationId, interpolateParams, Interpolator) {
              var result;

              if (fallbackLanguageIndex < $fallbackLanguage.length) {
                  var langKey = $fallbackLanguage[fallbackLanguageIndex];
                  result = getFallbackTranslationInstant(langKey, translationId, interpolateParams, Interpolator);
                  if (!result) {
                      result = resolveForFallbackLanguageInstant(fallbackLanguageIndex + 1, translationId, interpolateParams, Interpolator);
                  }
              }
              return result;
          };

          /**
           * Translates with the usage of the fallback languages.
           *
           * @param translationId
           * @param interpolateParams
           * @param Interpolator
           * @returns {Q.promise} Promise, that resolves to the translation.
           */
          var fallbackTranslation = function (translationId, interpolateParams, Interpolator, defaultTranslationText) {
              // Start with the fallbackLanguage with index 0
              return resolveForFallbackLanguage((startFallbackIteration > 0 ? startFallbackIteration : fallbackIndex), translationId, interpolateParams, Interpolator, defaultTranslationText);
          };

          /**
           * Translates with the usage of the fallback languages.
           *
           * @param translationId
           * @param interpolateParams
           * @param Interpolator
           * @returns {String} translation
           */
          var fallbackTranslationInstant = function (translationId, interpolateParams, Interpolator) {
              // Start with the fallbackLanguage with index 0
              return resolveForFallbackLanguageInstant((startFallbackIteration > 0 ? startFallbackIteration : fallbackIndex), translationId, interpolateParams, Interpolator);
          };

          var determineTranslation = function (translationId, interpolateParams, interpolationId, defaultTranslationText) {

              var deferred = $q.defer();

              var table = $uses ? $translationTable[$uses] : $translationTable,
                  Interpolator = (interpolationId) ? interpolatorHashMap[interpolationId] : defaultInterpolator;

              // if the translation id exists, we can just interpolate it
              if (table && Object.prototype.hasOwnProperty.call(table, translationId)) {
                  var translation = table[translationId];

                  // If using link, rerun $translate with linked translationId and return it
                  if (translation.substr(0, 2) === '@:') {

                      $translate(translation.substr(2), interpolateParams, interpolationId, defaultTranslationText)
                        .then(deferred.resolve, deferred.reject);
                  } else {
                      deferred.resolve(Interpolator.interpolate(translation, interpolateParams));
                  }
              } else {
                  var missingTranslationHandlerTranslation;
                  // for logging purposes only (as in $translateMissingTranslationHandlerLog), value is not returned to promise
                  if ($missingTranslationHandlerFactory && !pendingLoader) {
                      missingTranslationHandlerTranslation = translateByHandler(translationId);
                  }

                  // since we couldn't translate the inital requested translation id,
                  // we try it now with one or more fallback languages, if fallback language(s) is
                  // configured.
                  if ($uses && $fallbackLanguage && $fallbackLanguage.length) {
                      fallbackTranslation(translationId, interpolateParams, Interpolator, defaultTranslationText)
                          .then(function (translation) {
                              deferred.resolve(translation);
                          }, function (_translationId) {
                              deferred.reject(applyNotFoundIndicators(_translationId));
                          });
                  } else if ($missingTranslationHandlerFactory && !pendingLoader && missingTranslationHandlerTranslation) {
                      // looks like the requested translation id doesn't exists.
                      // Now, if there is a registered handler for missing translations and no
                      // asyncLoader is pending, we execute the handler
                      if (defaultTranslationText) {
                          deferred.resolve(defaultTranslationText);
                      } else {
                          deferred.resolve(missingTranslationHandlerTranslation);
                      }
                  } else {
                      if (defaultTranslationText) {
                          deferred.resolve(defaultTranslationText);
                      } else {
                          deferred.reject(applyNotFoundIndicators(translationId));
                      }
                  }
              }
              return deferred.promise;
          };

          var determineTranslationInstant = function (translationId, interpolateParams, interpolationId) {

              var result, table = $uses ? $translationTable[$uses] : $translationTable,
                  Interpolator = defaultInterpolator;

              // if the interpolation id exists use custom interpolator
              if (interpolatorHashMap && Object.prototype.hasOwnProperty.call(interpolatorHashMap, interpolationId)) {
                  Interpolator = interpolatorHashMap[interpolationId];
              }

              // if the translation id exists, we can just interpolate it
              if (table && Object.prototype.hasOwnProperty.call(table, translationId)) {
                  var translation = table[translationId];

                  // If using link, rerun $translate with linked translationId and return it
                  if (translation.substr(0, 2) === '@:') {
                      result = determineTranslationInstant(translation.substr(2), interpolateParams, interpolationId);
                  } else {
                      result = Interpolator.interpolate(translation, interpolateParams);
                  }
              } else {
                  var missingTranslationHandlerTranslation;
                  // for logging purposes only (as in $translateMissingTranslationHandlerLog), value is not returned to promise
                  if ($missingTranslationHandlerFactory && !pendingLoader) {
                      missingTranslationHandlerTranslation = translateByHandler(translationId);
                  }

                  // since we couldn't translate the inital requested translation id,
                  // we try it now with one or more fallback languages, if fallback language(s) is
                  // configured.
                  if ($uses && $fallbackLanguage && $fallbackLanguage.length) {
                      fallbackIndex = 0;
                      result = fallbackTranslationInstant(translationId, interpolateParams, Interpolator);
                  } else if ($missingTranslationHandlerFactory && !pendingLoader && missingTranslationHandlerTranslation) {
                      // looks like the requested translation id doesn't exists.
                      // Now, if there is a registered handler for missing translations and no
                      // asyncLoader is pending, we execute the handler
                      result = missingTranslationHandlerTranslation;
                  } else {
                      result = applyNotFoundIndicators(translationId);
                  }
              }

              return result;
          };

          /**
           * @ngdoc function
           * @name pascalprecht.translate.$translate#preferredLanguage
           * @methodOf pascalprecht.translate.$translate
           *
           * @description
           * Returns the language key for the preferred language.
           *
           * @param {string} langKey language String or Array to be used as preferredLanguage (changing at runtime)
           *
           * @return {string} preferred language key
           */
          $translate.preferredLanguage = function (langKey) {
              if (langKey) {
                  setupPreferredLanguage(langKey);
              }
              return $preferredLanguage;
          };

          /**
           * @ngdoc function
           * @name pascalprecht.translate.$translate#cloakClassName
           * @methodOf pascalprecht.translate.$translate
           *
           * @description
           * Returns the configured class name for `translate-cloak` directive.
           *
           * @return {string} cloakClassName
           */
          $translate.cloakClassName = function () {
              return $cloakClassName;
          };

          /**
           * @ngdoc function
           * @name pascalprecht.translate.$translate#fallbackLanguage
           * @methodOf pascalprecht.translate.$translate
           *
           * @description
           * Returns the language key for the fallback languages or sets a new fallback stack.
           *
           * @param {string=} langKey language String or Array of fallback languages to be used (to change stack at runtime)
           *
           * @return {string||array} fallback language key
           */
          $translate.fallbackLanguage = function (langKey) {
              if (langKey !== undefined && langKey !== null) {
                  fallbackStack(langKey);

                  // as we might have an async loader initiated and a new translation language might have been defined
                  // we need to add the promise to the stack also. So - iterate.
                  if ($loaderFactory) {
                      if ($fallbackLanguage && $fallbackLanguage.length) {
                          for (var i = 0, len = $fallbackLanguage.length; i < len; i++) {
                              if (!langPromises[$fallbackLanguage[i]]) {
                                  langPromises[$fallbackLanguage[i]] = loadAsync($fallbackLanguage[i]);
                              }
                          }
                      }
                  }
                  $translate.use($translate.use());
              }
              if ($fallbackWasString) {
                  return $fallbackLanguage[0];
              } else {
                  return $fallbackLanguage;
              }

          };

          /**
           * @ngdoc function
           * @name pascalprecht.translate.$translate#useFallbackLanguage
           * @methodOf pascalprecht.translate.$translate
           *
           * @description
           * Sets the first key of the fallback language stack to be used for translation.
           * Therefore all languages in the fallback array BEFORE this key will be skipped!
           *
           * @param {string=} langKey Contains the langKey the iteration shall start with. Set to false if you want to
           * get back to the whole stack
           */
          $translate.useFallbackLanguage = function (langKey) {
              if (langKey !== undefined && langKey !== null) {
                  if (!langKey) {
                      startFallbackIteration = 0;
                  } else {
                      var langKeyPosition = indexOf($fallbackLanguage, langKey);
                      if (langKeyPosition > -1) {
                          startFallbackIteration = langKeyPosition;
                      }
                  }

              }

          };

          /**
           * @ngdoc function
           * @name pascalprecht.translate.$translate#proposedLanguage
           * @methodOf pascalprecht.translate.$translate
           *
           * @description
           * Returns the language key of language that is currently loaded asynchronously.
           *
           * @return {string} language key
           */
          $translate.proposedLanguage = function () {
              return $nextLang;
          };

          /**
           * @ngdoc function
           * @name pascalprecht.translate.$translate#storage
           * @methodOf pascalprecht.translate.$translate
           *
           * @description
           * Returns registered storage.
           *
           * @return {object} Storage
           */
          $translate.storage = function () {
              return Storage;
          };

          /**
           * @ngdoc function
           * @name pascalprecht.translate.$translate#use
           * @methodOf pascalprecht.translate.$translate
           *
           * @description
           * Tells angular-translate which language to use by given language key. This method is
           * used to change language at runtime. It also takes care of storing the language
           * key in a configured store to let your app remember the choosed language.
           *
           * When trying to 'use' a language which isn't available it tries to load it
           * asynchronously with registered loaders.
           *
           * Returns promise object with loaded language file data
           * @example
           * $translate.use("en_US").then(function(data){
           *   $scope.text = $translate("HELLO");
           * });
           *
           * @param {string} key Language key
           * @return {string} Language key
           */
          $translate.use = function (key) {
              if (!key) {
                  return $uses;
              }

              var deferred = $q.defer();

              $rootScope.$emit('$translateChangeStart', { language: key });

              // Try to get the aliased language key
              var aliasedKey = negotiateLocale(key);
              if (aliasedKey) {
                  key = aliasedKey;
              }

              // if there isn't a translation table for the language we've requested,
              // we load it asynchronously
              if (!$translationTable[key] && $loaderFactory && !langPromises[key]) {
                  $nextLang = key;
                  langPromises[key] = loadAsync(key).then(function (translation) {
                      translations(translation.key, translation.table);
                      deferred.resolve(translation.key);

                      useLanguage(translation.key);
                      if ($nextLang === key) {
                          $nextLang = undefined;
                      }
                      return translation;
                  }, function (key) {
                      if ($nextLang === key) {
                          $nextLang = undefined;
                      }
                      $rootScope.$emit('$translateChangeError', { language: key });
                      deferred.reject(key);
                      $rootScope.$emit('$translateChangeEnd', { language: key });
                  });
              } else {
                  deferred.resolve(key);
                  useLanguage(key);
              }

              return deferred.promise;
          };

          /**
           * @ngdoc function
           * @name pascalprecht.translate.$translate#storageKey
           * @methodOf pascalprecht.translate.$translate
           *
           * @description
           * Returns the key for the storage.
           *
           * @return {string} storage key
           */
          $translate.storageKey = function () {
              return storageKey();
          };

          /**
           * @ngdoc function
           * @name pascalprecht.translate.$translate#isPostCompilingEnabled
           * @methodOf pascalprecht.translate.$translate
           *
           * @description
           * Returns whether post compiling is enabled or not
           *
           * @return {bool} storage key
           */
          $translate.isPostCompilingEnabled = function () {
              return $postCompilingEnabled;
          };

          /**
           * @ngdoc function
           * @name pascalprecht.translate.$translate#refresh
           * @methodOf pascalprecht.translate.$translate
           *
           * @description
           * Refreshes a translation table pointed by the given langKey. If langKey is not specified,
           * the module will drop all existent translation tables and load new version of those which
           * are currently in use.
           *
           * Refresh means that the module will drop target translation table and try to load it again.
           *
           * In case there are no loaders registered the refresh() method will throw an Error.
           *
           * If the module is able to refresh translation tables refresh() method will broadcast
           * $translateRefreshStart and $translateRefreshEnd events.
           *
           * @example
           * // this will drop all currently existent translation tables and reload those which are
           * // currently in use
           * $translate.refresh();
           * // this will refresh a translation table for the en_US language
           * $translate.refresh('en_US');
           *
           * @param {string} langKey A language key of the table, which has to be refreshed
           *
           * @return {promise} Promise, which will be resolved in case a translation tables refreshing
           * process is finished successfully, and reject if not.
           */
          $translate.refresh = function (langKey) {
              if (!$loaderFactory) {
                  throw new Error('Couldn\'t refresh translation table, no loader registered!');
              }

              var deferred = $q.defer();

              function resolve() {
                  deferred.resolve();
                  $rootScope.$emit('$translateRefreshEnd', { language: langKey });
              }

              function reject() {
                  deferred.reject();
                  $rootScope.$emit('$translateRefreshEnd', { language: langKey });
              }

              $rootScope.$emit('$translateRefreshStart', { language: langKey });

              if (!langKey) {
                  // if there's no language key specified we refresh ALL THE THINGS!
                  var tables = [], loadingKeys = {};

                  // reload registered fallback languages
                  if ($fallbackLanguage && $fallbackLanguage.length) {
                      for (var i = 0, len = $fallbackLanguage.length; i < len; i++) {
                          tables.push(loadAsync($fallbackLanguage[i]));
                          loadingKeys[$fallbackLanguage[i]] = true;
                      }
                  }

                  // reload currently used language
                  if ($uses && !loadingKeys[$uses]) {
                      tables.push(loadAsync($uses));
                  }

                  $q.all(tables).then(function (tableData) {
                      angular.forEach(tableData, function (data) {
                          if ($translationTable[data.key]) {
                              delete $translationTable[data.key];
                          }
                          translations(data.key, data.table);
                      });
                      if ($uses) {
                          useLanguage($uses);
                      }
                      resolve();
                  });

              } else if ($translationTable[langKey]) {

                  loadAsync(langKey).then(function (data) {
                      translations(data.key, data.table);
                      if (langKey === $uses) {
                          useLanguage($uses);
                      }
                      resolve();
                  }, reject);

              } else {
                  reject();
              }
              return deferred.promise;
          };

          /**
           * @ngdoc function
           * @name pascalprecht.translate.$translate#instant
           * @methodOf pascalprecht.translate.$translate
           *
           * @description
           * Returns a translation instantly from the internal state of loaded translation. All rules
           * regarding the current language, the preferred language of even fallback languages will be
           * used except any promise handling. If a language was not found, an asynchronous loading
           * will be invoked in the background.
           *
           * @param {string|array} translationId A token which represents a translation id
           *                                     This can be optionally an array of translation ids which
           *                                     results that the function's promise returns an object where
           *                                     each key is the translation id and the value the translation.
           * @param {object} interpolateParams Params
           * @param {string} interpolationId The id of the interpolation to use
           *
           * @return {string} translation
           */
          $translate.instant = function (translationId, interpolateParams, interpolationId) {

              // Detect undefined and null values to shorten the execution and prevent exceptions
              if (translationId === null || angular.isUndefined(translationId)) {
                  return translationId;
              }

              // Duck detection: If the first argument is an array, a bunch of translations was requested.
              // The result is an object.
              if (angular.isArray(translationId)) {
                  var results = {};
                  for (var i = 0, c = translationId.length; i < c; i++) {
                      results[translationId[i]] = $translate.instant(translationId[i], interpolateParams, interpolationId);
                  }
                  return results;
              }

              // We discarded unacceptable values. So we just need to verify if translationId is empty String
              if (angular.isString(translationId) && translationId.length < 1) {
                  return translationId;
              }

              // trim off any whitespace
              if (translationId) {
                  translationId = trim.apply(translationId);
              }

              var result, possibleLangKeys = [];
              if ($preferredLanguage) {
                  possibleLangKeys.push($preferredLanguage);
              }
              if ($uses) {
                  possibleLangKeys.push($uses);
              }
              if ($fallbackLanguage && $fallbackLanguage.length) {
                  possibleLangKeys = possibleLangKeys.concat($fallbackLanguage);
              }
              for (var j = 0, d = possibleLangKeys.length; j < d; j++) {
                  var possibleLangKey = possibleLangKeys[j];
                  if ($translationTable[possibleLangKey]) {
                      if (typeof $translationTable[possibleLangKey][translationId] !== 'undefined') {
                          result = determineTranslationInstant(translationId, interpolateParams, interpolationId);
                      } else if ($notFoundIndicatorLeft || $notFoundIndicatorRight) {
                          result = applyNotFoundIndicators(translationId);
                      }
                  }
                  if (typeof result !== 'undefined') {
                      break;
                  }
              }

              if (!result && result !== '') {
                  // Return translation of default interpolator if not found anything.
                  result = defaultInterpolator.interpolate(translationId, interpolateParams);
                  if ($missingTranslationHandlerFactory && !pendingLoader) {
                      result = translateByHandler(translationId);
                  }
              }

              return result;
          };

          /**
           * @ngdoc function
           * @name pascalprecht.translate.$translate#versionInfo
           * @methodOf pascalprecht.translate.$translate
           *
           * @description
           * Returns the current version information for the angular-translate library
           *
           * @return {string} angular-translate version
           */
          $translate.versionInfo = function () {
              return version;
          };

          /**
           * @ngdoc function
           * @name pascalprecht.translate.$translate#loaderCache
           * @methodOf pascalprecht.translate.$translate
           *
           * @description
           * Returns the defined loaderCache.
           *
           * @return {boolean|string|object} current value of loaderCache
           */
          $translate.loaderCache = function () {
              return loaderCache;
          };

          // internal purpose only
          $translate.directivePriority = function () {
              return directivePriority;
          };

          if ($loaderFactory) {

              // If at least one async loader is defined and there are no
              // (default) translations available we should try to load them.
              if (angular.equals($translationTable, {})) {
                  $translate.use($translate.use());
              }

              // Also, if there are any fallback language registered, we start
              // loading them asynchronously as soon as we can.
              if ($fallbackLanguage && $fallbackLanguage.length) {
                  var processAsyncResult = function (translation) {
                      translations(translation.key, translation.table);
                      $rootScope.$emit('$translateChangeEnd', { language: translation.key });
                      return translation;
                  };
                  for (var i = 0, len = $fallbackLanguage.length; i < len; i++) {
                      langPromises[$fallbackLanguage[i]] = loadAsync($fallbackLanguage[i]).then(processAsyncResult);
                  }
              }
          }

          return $translate;
      }
    ];
}]);

/**
 * @ngdoc object
 * @name pascalprecht.translate.$translateDefaultInterpolation
 * @requires $interpolate
 *
 * @description
 * Uses angular's `$interpolate` services to interpolate strings against some values.
 *
 * @return {object} $translateInterpolator Interpolator service
 */
angular.module('pascalprecht.translate').factory('$translateDefaultInterpolation', ['$interpolate', function ($interpolate) {

    var $translateInterpolator = {},
        $locale,
        $identifier = 'default',
        $sanitizeValueStrategy = null,
        // map of all sanitize strategies
        sanitizeValueStrategies = {
            escaped: function (params) {
                var result = {};
                for (var key in params) {
                    if (Object.prototype.hasOwnProperty.call(params, key)) {
                        if (angular.isNumber(params[key])) {
                            result[key] = params[key];
                        } else {
                            result[key] = angular.element('<div></div>').text(params[key]).html();
                        }
                    }
                }
                return result;
            }
        };

    var sanitizeParams = function (params) {
        var result;
        if (angular.isFunction(sanitizeValueStrategies[$sanitizeValueStrategy])) {
            result = sanitizeValueStrategies[$sanitizeValueStrategy](params);
        } else {
            result = params;
        }
        return result;
    };

    /**
     * @ngdoc function
     * @name pascalprecht.translate.$translateDefaultInterpolation#setLocale
     * @methodOf pascalprecht.translate.$translateDefaultInterpolation
     *
     * @description
     * Sets current locale (this is currently not use in this interpolation).
     *
     * @param {string} locale Language key or locale.
     */
    $translateInterpolator.setLocale = function (locale) {
        $locale = locale;
    };

    /**
     * @ngdoc function
     * @name pascalprecht.translate.$translateDefaultInterpolation#getInterpolationIdentifier
     * @methodOf pascalprecht.translate.$translateDefaultInterpolation
     *
     * @description
     * Returns an identifier for this interpolation service.
     *
     * @returns {string} $identifier
     */
    $translateInterpolator.getInterpolationIdentifier = function () {
        return $identifier;
    };

    $translateInterpolator.useSanitizeValueStrategy = function (value) {
        $sanitizeValueStrategy = value;
        return this;
    };

    /**
     * @ngdoc function
     * @name pascalprecht.translate.$translateDefaultInterpolation#interpolate
     * @methodOf pascalprecht.translate.$translateDefaultInterpolation
     *
     * @description
     * Interpolates given string agains given interpolate params using angulars
     * `$interpolate` service.
     *
     * @returns {string} interpolated string.
     */
    $translateInterpolator.interpolate = function (string, interpolateParams) {
        if ($sanitizeValueStrategy) {
            interpolateParams = sanitizeParams(interpolateParams);
        }
        return $interpolate(string)(interpolateParams || {});
    };

    return $translateInterpolator;
}]);

angular.module('pascalprecht.translate').constant('$STORAGE_KEY', 'NG_TRANSLATE_LANG_KEY');

angular.module('pascalprecht.translate')
/**
 * @ngdoc directive
 * @name pascalprecht.translate.directive:translate
 * @requires $compile
 * @requires $filter
 * @requires $interpolate
 * @restrict A
 *
 * @description
 * Translates given translation id either through attribute or DOM content.
 * Internally it uses `translate` filter to translate translation id. It possible to
 * pass an optional `translate-values` object literal as string into translation id.
 *
 * @param {string=} translate Translation id which could be either string or interpolated string.
 * @param {string=} translate-values Values to pass into translation id. Can be passed as object literal string or interpolated object.
 * @param {string=} translate-attr-ATTR translate Translation id and put it into ATTR attribute.
 * @param {string=} translate-default will be used unless translation was successful
 * @param {boolean=} translate-compile (default true if present) defines locally activation of {@link pascalprecht.translate.$translate#usePostCompiling}
 *
 * @example
   <example module="ngView">
    <file name="index.html">
      <div ng-controller="TranslateCtrl">

        <pre translate="TRANSLATION_ID"></pre>
        <pre translate>TRANSLATION_ID</pre>
        <pre translate translate-attr-title="TRANSLATION_ID"></pre>
        <pre translate="{{translationId}}"></pre>
        <pre translate>{{translationId}}</pre>
        <pre translate="WITH_VALUES" translate-values="{value: 5}"></pre>
        <pre translate translate-values="{value: 5}">WITH_VALUES</pre>
        <pre translate="WITH_VALUES" translate-values="{{values}}"></pre>
        <pre translate translate-values="{{values}}">WITH_VALUES</pre>
        <pre translate translate-attr-title="WITH_VALUES" translate-values="{{values}}"></pre>

      </div>
    </file>
    <file name="script.js">
      angular.module('ngView', ['pascalprecht.translate'])

      .config(function ($translateProvider) {

        $translateProvider.translations('en',{
          'TRANSLATION_ID': 'Hello there!',
          'WITH_VALUES': 'The following value is dynamic: {{value}}'
        }).preferredLanguage('en');

      });

      angular.module('ngView').controller('TranslateCtrl', function ($scope) {
        $scope.translationId = 'TRANSLATION_ID';

        $scope.values = {
          value: 78
        };
      });
    </file>
    <file name="scenario.js">
      it('should translate', function () {
        inject(function ($rootScope, $compile) {
          $rootScope.translationId = 'TRANSLATION_ID';

          element = $compile('<p translate="TRANSLATION_ID"></p>')($rootScope);
          $rootScope.$digest();
          expect(element.text()).toBe('Hello there!');

          element = $compile('<p translate="{{translationId}}"></p>')($rootScope);
          $rootScope.$digest();
          expect(element.text()).toBe('Hello there!');

          element = $compile('<p translate>TRANSLATION_ID</p>')($rootScope);
          $rootScope.$digest();
          expect(element.text()).toBe('Hello there!');

          element = $compile('<p translate>{{translationId}}</p>')($rootScope);
          $rootScope.$digest();
          expect(element.text()).toBe('Hello there!');

          element = $compile('<p translate translate-attr-title="TRANSLATION_ID"></p>')($rootScope);
          $rootScope.$digest();
          expect(element.attr('title')).toBe('Hello there!');
        });
      });
    </file>
   </example>
 */
.directive('translate', ['$translate', '$q', '$interpolate', '$compile', '$parse', '$rootScope', function ($translate, $q, $interpolate, $compile, $parse, $rootScope) {

    /**
     * @name trim
     * @private
     *
     * @description
     * trim polyfill
     *
     * @returns {string} The string stripped of whitespace from both ends
     */
    var trim = function () {
        return this.replace(/^\s+|\s+$/g, '');
    };

    return {
        restrict: 'AE',
        scope: true,
        priority: $translate.directivePriority(),
        compile: function (tElement, tAttr) {

            var translateValuesExist = (tAttr.translateValues) ?
              tAttr.translateValues : undefined;

            var translateInterpolation = (tAttr.translateInterpolation) ?
              tAttr.translateInterpolation : undefined;

            var translateValueExist = tElement[0].outerHTML.match(/translate-value-+/i);

            var interpolateRegExp = '^(.*)(' + $interpolate.startSymbol() + '.*' + $interpolate.endSymbol() + ')(.*)',
                watcherRegExp = '^(.*)' + $interpolate.startSymbol() + '(.*)' + $interpolate.endSymbol() + '(.*)';

            return function linkFn(scope, iElement, iAttr) {

                scope.interpolateParams = {};
                scope.preText = '';
                scope.postText = '';
                var translationIds = {};

                // Ensures any change of the attribute "translate" containing the id will
                // be re-stored to the scope's "translationId".
                // If the attribute has no content, the element's text value (white spaces trimmed off) will be used.
                var observeElementTranslation = function (translationId) {

                    // Remove any old watcher
                    if (angular.isFunction(observeElementTranslation._unwatchOld)) {
                        observeElementTranslation._unwatchOld();
                        observeElementTranslation._unwatchOld = undefined;
                    }

                    if (angular.equals(translationId, '') || !angular.isDefined(translationId)) {
                        // Resolve translation id by inner html if required
                        var interpolateMatches = trim.apply(iElement.text()).match(interpolateRegExp);
                        // Interpolate translation id if required
                        if (angular.isArray(interpolateMatches)) {
                            scope.preText = interpolateMatches[1];
                            scope.postText = interpolateMatches[3];
                            translationIds.translate = $interpolate(interpolateMatches[2])(scope.$parent);
                            var watcherMatches = iElement.text().match(watcherRegExp);
                            if (angular.isArray(watcherMatches) && watcherMatches[2] && watcherMatches[2].length) {
                                observeElementTranslation._unwatchOld = scope.$watch(watcherMatches[2], function (newValue) {
                                    translationIds.translate = newValue;
                                    updateTranslations();
                                });
                            }
                        } else {
                            translationIds.translate = iElement.text().replace(/^\s+|\s+$/g, '');
                        }
                    } else {
                        translationIds.translate = translationId;
                    }
                    updateTranslations();
                };

                var observeAttributeTranslation = function (translateAttr) {
                    iAttr.$observe(translateAttr, function (translationId) {
                        translationIds[translateAttr] = translationId;
                        updateTranslations();
                    });
                };

                var firstAttributeChangedEvent = true;
                iAttr.$observe('translate', function (translationId) {
                    if (typeof translationId === 'undefined') {
                        // case of element "<translate>xyz</translate>"
                        observeElementTranslation('');
                    } else {
                        // case of regular attribute
                        if (translationId !== '' || !firstAttributeChangedEvent) {
                            translationIds.translate = translationId;
                            updateTranslations();
                        }
                    }
                    firstAttributeChangedEvent = false;
                });

                for (var translateAttr in iAttr) {
                    if (iAttr.hasOwnProperty(translateAttr) && translateAttr.substr(0, 13) === 'translateAttr') {
                        observeAttributeTranslation(translateAttr);
                    }
                }

                iAttr.$observe('translateDefault', function (value) {
                    scope.defaultText = value;
                });

                if (translateValuesExist) {
                    iAttr.$observe('translateValues', function (interpolateParams) {
                        if (interpolateParams) {
                            scope.$parent.$watch(function () {
                                angular.extend(scope.interpolateParams, $parse(interpolateParams)(scope.$parent));
                            });
                        }
                    });
                }

                if (translateValueExist) {
                    var observeValueAttribute = function (attrName) {
                        iAttr.$observe(attrName, function (value) {
                            var attributeName = angular.lowercase(attrName.substr(14, 1)) + attrName.substr(15);
                            scope.interpolateParams[attributeName] = value;
                        });
                    };
                    for (var attr in iAttr) {
                        if (Object.prototype.hasOwnProperty.call(iAttr, attr) && attr.substr(0, 14) === 'translateValue' && attr !== 'translateValues') {
                            observeValueAttribute(attr);
                        }
                    }
                }

                // Master update function
                var updateTranslations = function () {
                    for (var key in translationIds) {
                        if (translationIds.hasOwnProperty(key)) {
                            updateTranslation(key, translationIds[key], scope, scope.interpolateParams, scope.defaultText);
                        }
                    }
                };

                // Put translation processing function outside loop
                var updateTranslation = function (translateAttr, translationId, scope, interpolateParams, defaultTranslationText) {
                    if (translationId) {
                        $translate(translationId, interpolateParams, translateInterpolation, defaultTranslationText)
                          .then(function (translation) {
                              applyTranslation(translation, scope, true, translateAttr);
                          }, function (translationId) {
                              applyTranslation(translationId, scope, false, translateAttr);
                          });
                    } else {
                        // as an empty string cannot be translated, we can solve this using successful=false
                        applyTranslation(translationId, scope, false, translateAttr);
                    }
                };

                var applyTranslation = function (value, scope, successful, translateAttr) {
                    if (translateAttr === 'translate') {
                        // default translate into innerHTML
                        if (!successful && typeof scope.defaultText !== 'undefined') {
                            value = scope.defaultText;
                        }
                        iElement.html(scope.preText + value + scope.postText);
                        var globallyEnabled = $translate.isPostCompilingEnabled();
                        var locallyDefined = typeof tAttr.translateCompile !== 'undefined';
                        var locallyEnabled = locallyDefined && tAttr.translateCompile !== 'false';
                        if ((globallyEnabled && !locallyDefined) || locallyEnabled) {
                            $compile(iElement.contents())(scope);
                        }
                    } else {
                        // translate attribute
                        if (!successful && typeof scope.defaultText !== 'undefined') {
                            value = scope.defaultText;
                        }
                        var attributeName = iAttr.$attr[translateAttr].substr(15);
                        iElement.attr(attributeName, value);
                    }
                };

                scope.$watch('interpolateParams', updateTranslations, true);

                // Ensures the text will be refreshed after the current language was changed
                // w/ $translate.use(...)
                var unbind = $rootScope.$on('$translateChangeSuccess', updateTranslations);

                // ensure translation will be looked up at least one
                if (iElement.text().length) {
                    observeElementTranslation('');
                }
                updateTranslations();
                scope.$on('$destroy', unbind);
            };
        }
    };
}]);

angular.module('pascalprecht.translate')
/**
 * @ngdoc directive
 * @name pascalprecht.translate.directive:translateCloak
 * @requires $rootScope
 * @requires $translate
 * @restrict A
 *
 * $description
 * Adds a `translate-cloak` class name to the given element where this directive
 * is applied initially and removes it, once a loader has finished loading.
 *
 * This directive can be used to prevent initial flickering when loading translation
 * data asynchronously.
 *
 * The class name is defined in
 * {@link pascalprecht.translate.$translateProvider#cloakClassName $translate.cloakClassName()}.
 *
 * @param {string=} translate-cloak If a translationId is provided, it will be used for showing
 *                                  or hiding the cloak. Basically it relies on the translation
 *                                  resolve.
 */
.directive('translateCloak', ['$rootScope', '$translate', function ($rootScope, $translate) {

    return {
        compile: function (tElement) {
            var applyCloak = function () {
                tElement.addClass($translate.cloakClassName());
            },
            removeCloak = function () {
                tElement.removeClass($translate.cloakClassName());
            },
            removeListener = $rootScope.$on('$translateChangeEnd', function () {
                removeCloak();
                removeListener();
                removeListener = null;
            });
            applyCloak();

            return function linkFn(scope, iElement, iAttr) {
                // Register a watcher for the defined translation allowing a fine tuned cloak
                if (iAttr.translateCloak && iAttr.translateCloak.length) {
                    iAttr.$observe('translateCloak', function (translationId) {
                        $translate(translationId).then(removeCloak, applyCloak);
                    });
                }
            };
        }
    };
}]);

angular.module('pascalprecht.translate')
/**
 * @ngdoc filter
 * @name pascalprecht.translate.filter:translate
 * @requires $parse
 * @requires pascalprecht.translate.$translate
 * @function
 *
 * @description
 * Uses `$translate` service to translate contents. Accepts interpolate parameters
 * to pass dynamized values though translation.
 *
 * @param {string} translationId A translation id to be translated.
 * @param {*=} interpolateParams Optional object literal (as hash or string) to pass values into translation.
 *
 * @returns {string} Translated text.
 *
 * @example
   <example module="ngView">
    <file name="index.html">
      <div ng-controller="TranslateCtrl">

        <pre>{{ 'TRANSLATION_ID' | translate }}</pre>
        <pre>{{ translationId | translate }}</pre>
        <pre>{{ 'WITH_VALUES' | translate:'{value: 5}' }}</pre>
        <pre>{{ 'WITH_VALUES' | translate:values }}</pre>

      </div>
    </file>
    <file name="script.js">
      angular.module('ngView', ['pascalprecht.translate'])

      .config(function ($translateProvider) {

        $translateProvider.translations('en', {
          'TRANSLATION_ID': 'Hello there!',
          'WITH_VALUES': 'The following value is dynamic: {{value}}'
        });
        $translateProvider.preferredLanguage('en');

      });

      angular.module('ngView').controller('TranslateCtrl', function ($scope) {
        $scope.translationId = 'TRANSLATION_ID';

        $scope.values = {
          value: 78
        };
      });
    </file>
   </example>
 */
.filter('translate', ['$parse', '$translate', function ($parse, $translate) {
    var translateFilter = function (translationId, interpolateParams, interpolation) {

        if (!angular.isObject(interpolateParams)) {
            interpolateParams = $parse(interpolateParams)(this);
        }

        return $translate.instant(translationId, interpolateParams, interpolation);
    };

    // Since AngularJS 1.3, filters which are not stateless (depending at the scope)
    // have to explicit define this behavior.
    translateFilter.$stateful = true;

    return translateFilter;
}]);