From fabe653c9f3a905572d9b2c837400b68a6bb090d Mon Sep 17 00:00:00 2001 From: Davide Alberani Date: Mon, 3 Apr 2017 19:46:35 +0200 Subject: [PATCH] update angular-translate --- .../angular-translate-loader-static-files.js | 112 + ...gular-translate-loader-static-files.min.js | 8 +- static/js/angular-translate.js | 3110 +++++++++++------ static/js/angular-translate.min.js | 8 +- 4 files changed, 2113 insertions(+), 1125 deletions(-) create mode 100644 static/js/angular-translate-loader-static-files.js diff --git a/static/js/angular-translate-loader-static-files.js b/static/js/angular-translate-loader-static-files.js new file mode 100644 index 0000000..ee0f563 --- /dev/null +++ b/static/js/angular-translate-loader-static-files.js @@ -0,0 +1,112 @@ +/*! + * angular-translate - v2.15.1 - 2017-04-03 + * + * Copyright (c) 2017 The angular-translate team, Pascal Precht; Licensed MIT + */ +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module unless amdModuleId is set + define([], function () { + return (factory()); + }); + } else if (typeof module === 'object' && module.exports) { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like environments that support module.exports, + // like Node. + module.exports = factory(); + } else { + factory(); + } +}(this, function () { + +$translateStaticFilesLoader.$inject = ['$q', '$http']; +angular.module('pascalprecht.translate') +/** + * @ngdoc object + * @name pascalprecht.translate.$translateStaticFilesLoader + * @requires $q + * @requires $http + * + * @description + * Creates a loading function for a typical static file url pattern: + * "lang-en_US.json", "lang-de_DE.json", etc. Using this builder, + * the response of these urls must be an object of key-value pairs. + * + * @param {object} options Options object, which gets prefix, suffix, key, and fileMap + */ +.factory('$translateStaticFilesLoader', $translateStaticFilesLoader); + +function $translateStaticFilesLoader($q, $http) { + + 'use strict'; + + return function (options) { + + if (!options || (!angular.isArray(options.files) && (!angular.isString(options.prefix) || !angular.isString(options.suffix)))) { + throw new Error('Couldn\'t load static files, no files and prefix or suffix specified!'); + } + + if (!options.files) { + options.files = [{ + prefix: options.prefix, + suffix: options.suffix + }]; + } + + var load = function (file) { + if (!file || (!angular.isString(file.prefix) || !angular.isString(file.suffix))) { + throw new Error('Couldn\'t load static file, no prefix or suffix specified!'); + } + + var fileUrl = [ + file.prefix, + options.key, + file.suffix + ].join(''); + + if (angular.isObject(options.fileMap) && options.fileMap[fileUrl]) { + fileUrl = options.fileMap[fileUrl]; + } + + return $http(angular.extend({ + url: fileUrl, + method: 'GET' + }, options.$http)) + .then(function(result) { + return result.data; + }, function () { + return $q.reject(options.key); + }); + }; + + var promises = [], + length = options.files.length; + + for (var i = 0; i < length; i++) { + promises.push(load({ + prefix: options.files[i].prefix, + key: options.key, + suffix: options.files[i].suffix + })); + } + + return $q.all(promises) + .then(function (data) { + var length = data.length, + mergedData = {}; + + for (var i = 0; i < length; i++) { + for (var key in data[i]) { + mergedData[key] = data[i][key]; + } + } + + return mergedData; + }); + }; +} + +$translateStaticFilesLoader.displayName = '$translateStaticFilesLoader'; +return 'pascalprecht.translate'; + +})); diff --git a/static/js/angular-translate-loader-static-files.min.js b/static/js/angular-translate-loader-static-files.min.js index 3c50ac2..622eeff 100644 --- a/static/js/angular-translate-loader-static-files.min.js +++ b/static/js/angular-translate-loader-static-files.min.js @@ -1,6 +1,6 @@ /*! - * angular-translate - v2.7.0 - 2015-05-02 - * http://github.com/angular-translate/angular-translate - * Copyright (c) 2015 ; Licensed MIT + * angular-translate - v2.15.1 - 2017-04-03 + * + * Copyright (c) 2017 The angular-translate team, Pascal Precht; Licensed MIT */ -!function(a,b){"function"==typeof define&&define.amd?define([],function(){return b()}):"object"==typeof exports?module.exports=b():b()}(this,function(){function a(a,b){"use strict";return function(c){if(!(c&&(angular.isArray(c.files)||angular.isString(c.prefix)&&angular.isString(c.suffix))))throw new Error("Couldn't load static files, no files and prefix or suffix specified!");c.files||(c.files=[{prefix:c.prefix,suffix:c.suffix}]);for(var d=function(d){if(!d||!angular.isString(d.prefix)||!angular.isString(d.suffix))throw new Error("Couldn't load static file, no prefix or suffix specified!");var e=a.defer();return b(angular.extend({url:[d.prefix,c.key,d.suffix].join(""),method:"GET",params:""},c.$http)).success(function(a){e.resolve(a)}).error(function(){e.reject(c.key)}),e.promise},e=a.defer(),f=[],g=c.files.length,h=0;g>h;h++)f.push(d({prefix:c.files[h].prefix,key:c.key,suffix:c.files[h].suffix}));return a.all(f).then(function(a){for(var b=a.length,c={},d=0;b>d;d++)for(var f in a[d])c[f]=a[d][f];e.resolve(c)},function(a){e.reject(a)}),e.promise}}return angular.module("pascalprecht.translate").factory("$translateStaticFilesLoader",a),a.$inject=["$q","$http"],a.displayName="$translateStaticFilesLoader","pascalprecht.translate"}); \ No newline at end of file +!function(a,b){"function"==typeof define&&define.amd?define([],function(){return b()}):"object"==typeof module&&module.exports?module.exports=b():b()}(0,function(){function a(a,b){"use strict";return function(c){if(!(c&&(angular.isArray(c.files)||angular.isString(c.prefix)&&angular.isString(c.suffix))))throw new Error("Couldn't load static files, no files and prefix or suffix specified!");c.files||(c.files=[{prefix:c.prefix,suffix:c.suffix}]);for(var d=[],e=c.files.length,f=0;f').text(value).html(); + var element = angular.element('
'); + element.text(value); // not chainable, see #1044 + return element.html(); }; var htmlSanitizeValue = function (value) { @@ -290,19 +337,46 @@ function $translateSanitizationProvider () { return $sanitize(value); }; - var mapInterpolationParameters = function (value, iteratee) { - if (angular.isObject(value)) { + var htmlTrustValue = function (value) { + if (!$sce) { + throw new Error('pascalprecht.translate.$translateSanitization: Error cannot find $sce service.'); + } + return $sce.trustAsHtml(value); + }; + + var mapInterpolationParameters = function (value, iteratee, stack) { + if (angular.isDate(value)) { + return value; + } else if (angular.isObject(value)) { var result = angular.isArray(value) ? [] : {}; + if (!stack) { + stack = []; + } else { + if (stack.indexOf(value) > -1) { + throw new Error('pascalprecht.translate.$translateSanitization: Error cannot interpolate parameter due recursive object'); + } + } + + stack.push(value); angular.forEach(value, function (propertyValue, propertyKey) { - result[propertyKey] = mapInterpolationParameters(propertyValue, iteratee); + + /* Skipping function properties. */ + if (angular.isFunction(propertyValue)) { + return; + } + + result[propertyKey] = mapInterpolationParameters(propertyValue, iteratee, stack); }); + stack.splice(-1, 1); // remove last return result; } else if (angular.isNumber(value)) { return value; - } else { + } else if (!angular.isUndefined(value) && value !== null) { return iteratee(value); + } else { + return value; } }; } @@ -317,55 +391,64 @@ function $translateSanitizationProvider () { * */ angular.module('pascalprecht.translate') -.constant('pascalprechtTranslateOverrider', {}) -.provider('$translate', $translate); + .constant('pascalprechtTranslateOverrider', {}) + .provider('$translate', $translate); function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvider, pascalprechtTranslateOverrider) { 'use strict'; var $translationTable = {}, - $preferredLanguage, - $availableLanguageKeys = [], - $languageKeyAliases, - $fallbackLanguage, - $fallbackWasString, - $uses, - $nextLang, - $storageFactory, - $storageKey = $STORAGE_KEY, - $storagePrefix, - $missingTranslationHandlerFactory, - $interpolationFactory, - $interpolatorFactories = [], - $loaderFactory, - $cloakClassName = 'translate-cloak', - $loaderOptions, - $notFoundIndicatorLeft, - $notFoundIndicatorRight, - $postCompilingEnabled = false, - NESTED_OBJECT_DELIMITER = '.', - loaderCache, - directivePriority = 0, - statefulFilter = true, - uniformLanguageTagResolver = 'default', - languageTagResolver = { - 'default': function (tag) { - return (tag || '').split('-').join('_'); - }, - java: function (tag) { - var temp = (tag || '').split('-').join('_'); - var parts = temp.split('_'); - return parts.length > 1 ? (parts[0].toLowerCase() + '_' + parts[1].toUpperCase()) : temp; - }, - bcp47: function (tag) { - var temp = (tag || '').split('_').join('-'); - var parts = temp.split('-'); - return parts.length > 1 ? (parts[0].toLowerCase() + '-' + parts[1].toUpperCase()) : temp; - } - }; + $preferredLanguage, + $availableLanguageKeys = [], + $languageKeyAliases, + $fallbackLanguage, + $fallbackWasString, + $uses, + $nextLang, + $storageFactory, + $storageKey = $STORAGE_KEY, + $storagePrefix, + $missingTranslationHandlerFactory, + $interpolationFactory, + $interpolatorFactories = [], + $loaderFactory, + $cloakClassName = 'translate-cloak', + $loaderOptions, + $notFoundIndicatorLeft, + $notFoundIndicatorRight, + $postCompilingEnabled = false, + $forceAsyncReloadEnabled = false, + $nestedObjectDelimeter = '.', + $isReady = false, + $keepContent = false, + loaderCache, + directivePriority = 0, + statefulFilter = true, + postProcessFn, + uniformLanguageTagResolver = 'default', + languageTagResolver = { + 'default' : function (tag) { + return (tag || '').split('-').join('_'); + }, + java : function (tag) { + var temp = (tag || '').split('-').join('_'); + var parts = temp.split('_'); + return parts.length > 1 ? (parts[0].toLowerCase() + '_' + parts[1].toUpperCase()) : temp; + }, + bcp47 : function (tag) { + var temp = (tag || '').split('_').join('-'); + var parts = temp.split('-'); + return parts.length > 1 ? (parts[0].toLowerCase() + '-' + parts[1].toUpperCase()) : temp; + }, + 'iso639-1' : function (tag) { + var temp = (tag || '').split('_').join('-'); + var parts = temp.split('-'); + return parts[0].toLowerCase(); + } + }; - var version = '2.7.0'; + var version = '2.15.1'; // tries to determine the browsers language var getFirstBrowserLanguage = function () { @@ -376,9 +459,9 @@ function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvide } var nav = $windowProvider.$get().navigator, - browserLanguagePropertyKeys = ['language', 'browserLanguage', 'systemLanguage', 'userLanguage'], - i, - language; + browserLanguagePropertyKeys = ['language', 'browserLanguage', 'systemLanguage', 'userLanguage'], + i, + language; // support for HTML 5.1 "navigator.languages" if (angular.isArray(nav.languages)) { @@ -424,7 +507,7 @@ function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvide * * @returns {int} Index of search element. */ - var indexOf = function(array, searchElement) { + var indexOf = function (array, searchElement) { for (var i = 0, len = array.length; i < len; i++) { if (array[i] === searchElement) { return i; @@ -442,21 +525,25 @@ function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvide * * @returns {string} The string stripped of whitespace from both ends */ - var trim = function() { - return this.replace(/^\s+|\s+$/g, ''); + var trim = function () { + return this.toString().replace(/^\s+|\s+$/g, ''); }; var negotiateLocale = function (preferred) { + if (!preferred) { + return; + } var avail = [], - locale = angular.lowercase(preferred), - i = 0, - n = $availableLanguageKeys.length; + locale = angular.lowercase(preferred), + i = 0, + n = $availableLanguageKeys.length; for (; i < n; i++) { avail.push(angular.lowercase($availableLanguageKeys[i])); } + // Check for an exact match in our list of available keys if (indexOf(avail, locale) > -1) { return preferred; } @@ -464,32 +551,33 @@ function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvide 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 ($languageKeyAliases.hasOwnProperty(langKeyAlias)) { + 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; + 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; + } } } } } - if (preferred) { - var parts = preferred.split('_'); + // Check for a language code without region + var parts = preferred.split('_'); - if (parts.length > 1 && indexOf(avail, angular.lowercase(parts[0])) > -1) { - return parts[0]; - } + if (parts.length > 1 && indexOf(avail, angular.lowercase(parts[0])) > -1) { + return parts[0]; } - // If everything fails, just return the preferred, unchanged. - return preferred; + // If everything fails, return undefined. + return; }; /** @@ -524,7 +612,7 @@ function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvide * registered with no language key. Invoking it with a language key returns the * related translation table. * - * @param {string} key A language key. + * @param {string} langKey A language key. * @param {object} translationTable A plain old JavaScript object that represents a translation table. * */ @@ -569,6 +657,26 @@ function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvide return this; }; + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#nestedObjectDelimeter + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * + * Let's you change the delimiter for namespaced translations. + * Default delimiter is `.`. + * + * @param {string} delimiter namespace separator + */ + this.nestedObjectDelimeter = function (delimiter) { + if (!delimiter) { + return $nestedObjectDelimeter; + } + $nestedObjectDelimeter = delimiter; + return this; + }; + /** * @name flatObject * @private @@ -594,10 +702,10 @@ function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvide 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){ + keyWithPath = path.length ? ('' + path.join($nestedObjectDelimeter) + $nestedObjectDelimeter + key) : key; + if (path.length && key === prevKey) { // Create shortcut path (foo.bar == foo.bar.bar) - keyWithShortPath = '' + path.join(NESTED_OBJECT_DELIMITER); + keyWithShortPath = '' + path.join($nestedObjectDelimeter); // Link it to original path result[keyWithShortPath] = '@:' + keyWithPath; } @@ -668,7 +776,7 @@ function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvide return this; }; - /** + /** * @ngdoc function * @name pascalprecht.translate.$translateProvider#preferredLanguage * @methodOf pascalprecht.translate.$translateProvider @@ -679,12 +787,13 @@ function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvide * only that it says which language to **prefer**. * * @param {string} langKey A language key. - * */ - this.preferredLanguage = function(langKey) { - setupPreferredLanguage(langKey); - return this; - + this.preferredLanguage = function (langKey) { + if (langKey) { + setupPreferredLanguage(langKey); + return this; + } + return $preferredLanguage; }; var setupPreferredLanguage = function (langKey) { if (langKey) { @@ -777,12 +886,12 @@ function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvide if (langKey) { if (angular.isString(langKey)) { $fallbackWasString = true; - $fallbackLanguage = [ langKey ]; + $fallbackLanguage = [langKey]; } else if (angular.isArray(langKey)) { $fallbackWasString = false; $fallbackLanguage = langKey; } - if (angular.isString($preferredLanguage) && indexOf($fallbackLanguage, $preferredLanguage) < 0) { + if (angular.isString($preferredLanguage) && indexOf($fallbackLanguage, $preferredLanguage) < 0) { $fallbackLanguage.push($preferredLanguage); } @@ -822,7 +931,21 @@ function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvide return $uses; }; - /** + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#resolveClientLocale + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * This returns the current browser/client's language key. The result is processed with the configured uniform tag resolver. + * + * @returns {string} the current client/browser language key + */ + this.resolveClientLocale = function () { + return getLocale(); + }; + + /** * @ngdoc function * @name pascalprecht.translate.$translateProvider#storageKey * @methodOf pascalprecht.translate.$translateProvider @@ -832,7 +955,7 @@ function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvide * * @param {string} key A key for the storage. */ - var storageKey = function(key) { + var storageKey = function (key) { if (!key) { if ($storagePrefix) { return $storagePrefix + $storageKey; @@ -840,6 +963,7 @@ function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvide return $storageKey; } $storageKey = key; + return this; }; this.storageKey = storageKey; @@ -856,7 +980,7 @@ function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvide * @param {Object=} options Optional configuration object */ this.useUrlLoader = function (url, options) { - return this.useLoader('$translateUrlLoader', angular.extend({ url: url }, options)); + return this.useLoader('$translateUrlLoader', angular.extend({url : url}, options)); }; /** @@ -1016,6 +1140,30 @@ function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvide return this; }; + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#forceAsyncReload + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * If force async reload is enabled, async loader will always be called + * even if $translationTable already contains the language key, adding + * possible new entries to the $translationTable. + * + * Example: + *
+   *  app.config(function ($translateProvider) {
+   *    $translateProvider.forceAsyncReload(true);
+   *  });
+   * 
+ * + * @param {boolean} value - valid values are true or false + */ + this.forceAsyncReload = function (value) { + $forceAsyncReloadEnabled = !(!value); + return this; + }; + /** * @ngdoc function * @name pascalprecht.translate.$translateProvider#uniformLanguageTag @@ -1063,7 +1211,7 @@ function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvide options = {}; } else if (angular.isString(options)) { options = { - standard: options + standard : options }; } @@ -1098,7 +1246,7 @@ function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvide if (!$availableLanguageKeys.length) { $preferredLanguage = locale; } else { - $preferredLanguage = negotiateLocale(locale); + $preferredLanguage = negotiateLocale(locale) || locale; } return this; @@ -1138,7 +1286,7 @@ function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvide * * @description * Registers a cache for internal $http based loaders. - * {@link pascalprecht.translate.$translateProvider#determinePreferredLanguage determinePreferredLanguage}. + * {@link pascalprecht.translate.$translationCache $translationCache}. * 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}) @@ -1209,6 +1357,48 @@ function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvide } }; + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#postProcess + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * The post processor will be intercept right after the translation result. It can modify the result. + * + * @param {object} fn Function or service name (string) to be called after the translation value has been set / resolved. The function itself will enrich every value being processed and then continue the normal resolver process + */ + this.postProcess = function (fn) { + if (fn) { + postProcessFn = fn; + } else { + postProcessFn = undefined; + } + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#keepContent + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * If keepContent is set to true than translate directive will always use innerHTML + * as a default translation + * + * Example: + *
+   *  app.config(function ($translateProvider) {
+   *    $translateProvider.keepContent(true);
+   *  });
+   * 
+ * + * @param {boolean} value - valid values are true or false + */ + this.keepContent = function (value) { + $keepContent = !(!value); + return this; + }; + /** * @ngdoc object * @name pascalprecht.translate.$translate @@ -1233,1055 +1423,1345 @@ function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvide * 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 + * @param {string} defaultTranslationText the optional default translation text that is written as + * as default text in case it is not found in any configured language + * @param {string} forceLanguage A language to be used instead of the current language * @returns {object} promise */ - this.$get = [ - '$log', - '$injector', - '$rootScope', - '$q', - function ($log, $injector, $rootScope, $q) { + 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 Storage, + defaultInterpolator = $injector.get($interpolationFactory || '$translateDefaultInterpolation'), + pendingLoader = false, + interpolatorHashMap = {}, + langPromises = {}, + fallbackIndex, + startFallbackIteration; - var $translate = function (translationId, interpolateParams, interpolationId, defaultTranslationText) { + var $translate = function (translationId, interpolateParams, interpolationId, defaultTranslationText, forceLanguage) { + if (!$uses && $preferredLanguage) { + $uses = $preferredLanguage; + } + var uses = (forceLanguage && forceLanguage !== $uses) ? // we don't want to re-negotiate $uses + (negotiateLocale(forceLanguage) || forceLanguage) : $uses; - // 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; + // Check forceLanguage is present + if (forceLanguage) { + loadTranslationsIfMissing(forceLanguage); + } + + // 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]); }; - 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; - }); + // we don't care whether the promise was resolved or rejected; just store the values + $translate(translationId, interpolateParams, interpolationId, defaultTranslationText, forceLanguage).then(regardless, regardless); + return deferred.promise; }; - 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); - } - } + for (var i = 0, c = translationIds.length; i < c; i++) { + promises.push(translate(translationIds[i])); } - 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 { - var promiseResolved = function () { - determineTranslation(translationId, interpolateParams, interpolationId, defaultTranslationText).then(deferred.resolve, deferred.reject); - }; - promiseResolved.displayName = 'promiseResolved'; - - promiseToWaitFor['finally'](promiseResolved, 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); - - var eachInterpolator = function (interpolator, id) { - interpolatorHashMap[id].setLocale($uses); - }; - eachInterpolator.displayName = 'eachInterpolatorLocaleSetter'; - - // inform all others too! - angular.forEach(interpolatorHashMap, eachInterpolator); - $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) - }); - - var onLoaderSuccess = 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 + // wait for all (including storing to results) + return $q.all(promises).then(function () { + // return the results + return results; }); - $rootScope.$emit('$translateLoadingEnd', {language: key}); }; - onLoaderSuccess.displayName = 'onLoaderSuccess'; + return translateAll(translationId); + } - var onLoaderError = function (key) { - $rootScope.$emit('$translateLoadingError', {language: key}); - deferred.reject(key); - $rootScope.$emit('$translateLoadingEnd', {language: key}); - }; - onLoaderError.displayName = 'onLoaderError'; + var deferred = $q.defer(); - $injector.get($loaderFactory)(loaderOptions) - .then(onLoaderSuccess, onLoaderError); + // trim off any whitespace + if (translationId) { + translationId = trim.apply(translationId); + } - return deferred.promise; - }; + var promiseToWaitFor = (function () { + var promise = $preferredLanguage ? + langPromises[$preferredLanguage] : + langPromises[uses]; - if ($storageFactory) { - Storage = $injector.get($storageFactory); + fallbackIndex = 0; - if (!Storage.get || !Storage.put) { - throw new Error('Couldn\'t use storage \'' + $storageFactory + '\', missing get() or put() method!'); + 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 we have additional interpolations that were added via - // $translateProvider.addInterpolation(), we have to map'em - if ($interpolatorFactories.length) { - var eachInterpolationFactory = function (interpolatorFactory) { - var interpolator = $injector.get(interpolatorFactory); - // setting initial locale for each interpolation service - interpolator.setLocale($preferredLanguage || $uses); - // make'em recognizable through id - interpolatorHashMap[interpolator.getInterpolationIdentifier()] = interpolator; + 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, uses).then(deferred.resolve, deferred.reject); + } else { + var promiseResolved = function () { + // $uses may have changed while waiting + if (!forceLanguage) { + uses = $uses; + } + determineTranslation(translationId, interpolateParams, interpolationId, defaultTranslationText, uses).then(deferred.resolve, deferred.reject); }; - eachInterpolationFactory.displayName = 'interpolationFactoryAdder'; + promiseResolved.displayName = 'promiseResolved'; - angular.forEach($interpolatorFactories, eachInterpolationFactory); + promiseToWaitFor['finally'](promiseResolved) + .catch(angular.noop); // we don't care about errors here, already handled + } + 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 {string} key Locale key. + */ + var useLanguage = function (key) { + $uses = key; + + // make sure to store new language key before triggering success event + if ($storageFactory) { + Storage.put($translate.storageKey(), $uses); } - /** - * @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]) { - var onResolve = function (data) { - translations(data.key, data.table); - deferred.resolve(data.table); - }; - onResolve.displayName = 'translationTableResolver'; - langPromises[langKey].then(onResolve, deferred.reject); + $rootScope.$emit('$translateChangeSuccess', {language : key}); + + // inform default interpolator + defaultInterpolator.setLocale($uses); + + var eachInterpolator = function (interpolator, id) { + interpolatorHashMap[id].setLocale($uses); + }; + eachInterpolator.displayName = 'eachInterpolatorLocaleSetter'; + + // inform all others too! + angular.forEach(interpolatorHashMap, eachInterpolator); + $rootScope.$emit('$translateChangeEnd', {language : key}); + }; + + /** + * @name loadAsync + * @private + * + * @description + * Kicks off 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) + }); + + var onLoaderSuccess = 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}); + }; + onLoaderSuccess.displayName = 'onLoaderSuccess'; + + var onLoaderError = function (key) { + $rootScope.$emit('$translateLoadingError', {language : key}); + deferred.reject(key); + $rootScope.$emit('$translateLoadingEnd', {language : key}); + }; + onLoaderError.displayName = 'onLoaderError'; + + $injector.get($loaderFactory)(loaderOptions) + .then(onLoaderSuccess, onLoaderError); + + 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!'); + } + } + + // if we have additional interpolations that were added via + // $translateProvider.addInterpolation(), we have to map'em + if ($interpolatorFactories.length) { + var eachInterpolationFactory = function (interpolatorFactory) { + var interpolator = $injector.get(interpolatorFactory); + // setting initial locale for each interpolation service + interpolator.setLocale($preferredLanguage || $uses); + // make'em recognizable through id + interpolatorHashMap[interpolator.getInterpolationIdentifier()] = interpolator; + }; + eachInterpolationFactory.displayName = 'interpolationFactoryAdder'; + + angular.forEach($interpolatorFactories, eachInterpolationFactory); + } + + /** + * @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]) { + var onResolve = function (data) { + translations(data.key, data.table); + deferred.resolve(data.table); + }; + onResolve.displayName = 'translationTableResolver'; + langPromises[langKey].then(onResolve, 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 + * @param sanitizeStrategy + * @returns {Q.promise} + */ + var getFallbackTranslation = function (langKey, translationId, interpolateParams, Interpolator, sanitizeStrategy) { + var deferred = $q.defer(); + + var onResolve = function (translationTable) { + if (Object.prototype.hasOwnProperty.call(translationTable, translationId) && translationTable[translationId] !== null) { + Interpolator.setLocale(langKey); + var translation = translationTable[translationId]; + if (translation.substr(0, 2) === '@:') { + getFallbackTranslation(langKey, translation.substr(2), interpolateParams, Interpolator, sanitizeStrategy) + .then(deferred.resolve, deferred.reject); + } else { + var interpolatedValue = Interpolator.interpolate(translationTable[translationId], interpolateParams, 'service', sanitizeStrategy, translationId); + interpolatedValue = applyPostProcessing(translationId, translationTable[translationId], interpolatedValue, interpolateParams, langKey); + + deferred.resolve(interpolatedValue); + + } + Interpolator.setLocale($uses); } else { deferred.reject(); } - return deferred.promise; }; + onResolve.displayName = 'fallbackTranslationResolver'; - /** - * @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(onResolve, deferred.reject); - var onResolve = 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); + 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 + * @param sanitizeStrategy sanitize strategy override + * + * @returns {string} translation + */ + var getFallbackTranslationInstant = function (langKey, translationId, interpolateParams, Interpolator, sanitizeStrategy) { + var result, translationTable = $translationTable[langKey]; + + if (translationTable && Object.prototype.hasOwnProperty.call(translationTable, translationId) && translationTable[translationId] !== null) { + Interpolator.setLocale(langKey); + result = Interpolator.interpolate(translationTable[translationId], interpolateParams, 'filter', sanitizeStrategy, translationId); + result = applyPostProcessing(translationId, translationTable[translationId], result, interpolateParams, langKey, sanitizeStrategy); + // workaround for TrustedValueHolderType + if (!angular.isString(result) && angular.isFunction(result.$$unwrapTrustedValue)) { + var result2 = result.$$unwrapTrustedValue(); + if (result2.substr(0, 2) === '@:') { + return getFallbackTranslationInstant(langKey, result2.substr(2), interpolateParams, Interpolator, sanitizeStrategy); + } + } else if (result.substr(0, 2) === '@:') { + return getFallbackTranslationInstant(langKey, result.substr(2), interpolateParams, Interpolator, sanitizeStrategy); + } + Interpolator.setLocale($uses); + } + + return result; + }; + + + /** + * @name translateByHandler + * @private + * + * Translate by missing translation handler. + * + * @param translationId + * @param interpolateParams + * @param defaultTranslationText + * @param sanitizeStrategy sanitize strategy override + * + * @returns translation created by $missingTranslationHandler or translationId is $missingTranslationHandler is + * absent + */ + var translateByHandler = function (translationId, interpolateParams, defaultTranslationText, sanitizeStrategy) { + // 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) { + return $injector.get($missingTranslationHandlerFactory)(translationId, $uses, interpolateParams, defaultTranslationText, sanitizeStrategy); + } 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 + * @param defaultTranslationText + * @param sanitizeStrategy + * @returns {Q.promise} Promise that will resolve to the translation. + */ + var resolveForFallbackLanguage = function (fallbackLanguageIndex, translationId, interpolateParams, Interpolator, defaultTranslationText, sanitizeStrategy) { + var deferred = $q.defer(); + + if (fallbackLanguageIndex < $fallbackLanguage.length) { + var langKey = $fallbackLanguage[fallbackLanguageIndex]; + getFallbackTranslation(langKey, translationId, interpolateParams, Interpolator, sanitizeStrategy).then( + function (data) { + deferred.resolve(data); + }, + function () { + // Look in the next fallback language for a translation. + // It delays the resolving by passing another promise to resolve. + return resolveForFallbackLanguage(fallbackLanguageIndex + 1, translationId, interpolateParams, Interpolator, defaultTranslationText, sanitizeStrategy).then(deferred.resolve, deferred.reject); + } + ); + } 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 { + var missingTranslationHandlerTranslation = translateByHandler(translationId, interpolateParams, defaultTranslationText); + + // if no default translation is set and an error handler is defined, send it to the handler + // and then return the result if it isn't undefined + if ($missingTranslationHandlerFactory && missingTranslationHandlerTranslation) { + deferred.resolve(missingTranslationHandlerTranslation); } else { - deferred.reject(); + deferred.reject(applyNotFoundIndicators(translationId)); } - }; - onResolve.displayName = 'fallbackTranslationResolver'; + } + } + return deferred.promise; + }; - getTranslationTable(langKey).then(onResolve, deferred.reject); + /** + * @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 + * @param sanitizeStrategy + * @returns {string} translation + */ + var resolveForFallbackLanguageInstant = function (fallbackLanguageIndex, translationId, interpolateParams, Interpolator, sanitizeStrategy) { + var result; - return deferred.promise; - }; + if (fallbackLanguageIndex < $fallbackLanguage.length) { + var langKey = $fallbackLanguage[fallbackLanguageIndex]; + result = getFallbackTranslationInstant(langKey, translationId, interpolateParams, Interpolator, sanitizeStrategy); + if (!result && result !== '') { + result = resolveForFallbackLanguageInstant(fallbackLanguageIndex + 1, translationId, interpolateParams, Interpolator); + } + } + return result; + }; - /** - * @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]; + /** + * Translates with the usage of the fallback languages. + * + * @param translationId + * @param interpolateParams + * @param Interpolator + * @param defaultTranslationText + * @param sanitizeStrategy + * @returns {Q.promise} Promise, that resolves to the translation. + */ + var fallbackTranslation = function (translationId, interpolateParams, Interpolator, defaultTranslationText, sanitizeStrategy) { + // Start with the fallbackLanguage with index 0 + return resolveForFallbackLanguage((startFallbackIteration > 0 ? startFallbackIteration : fallbackIndex), translationId, interpolateParams, Interpolator, defaultTranslationText, sanitizeStrategy); + }; - 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); + /** + * Translates with the usage of the fallback languages. + * + * @param translationId + * @param interpolateParams + * @param Interpolator + * @param sanitizeStrategy + * @returns {String} translation + */ + var fallbackTranslationInstant = function (translationId, interpolateParams, Interpolator, sanitizeStrategy) { + // Start with the fallbackLanguage with index 0 + return resolveForFallbackLanguageInstant((startFallbackIteration > 0 ? startFallbackIteration : fallbackIndex), translationId, interpolateParams, Interpolator, sanitizeStrategy); + }; + + var determineTranslation = function (translationId, interpolateParams, interpolationId, defaultTranslationText, uses, sanitizeStrategy) { + + 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) && table[translationId] !== null) { + 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, uses) + .then(deferred.resolve, deferred.reject); + } else { + // + var resolvedTranslation = Interpolator.interpolate(translation, interpolateParams, 'service', sanitizeStrategy, translationId); + resolvedTranslation = applyPostProcessing(translationId, translation, resolvedTranslation, interpolateParams, uses); + deferred.resolve(resolvedTranslation); + } + } else { + var missingTranslationHandlerTranslation; + // for logging purposes only (as in $translateMissingTranslationHandlerLog), value is not returned to promise + if ($missingTranslationHandlerFactory && !pendingLoader) { + missingTranslationHandlerTranslation = translateByHandler(translationId, interpolateParams, defaultTranslationText); } - 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, interpolateParams) { - // 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, interpolateParams); - 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 + // 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, sanitizeStrategy) + .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 { - // 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, interpolateParams)); - } - } - 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)); + deferred.resolve(missingTranslationHandlerTranslation); } } else { - var missingTranslationHandlerTranslation; - // for logging purposes only (as in $translateMissingTranslationHandlerLog), value is not returned to promise - if ($missingTranslationHandlerFactory && !pendingLoader) { - missingTranslationHandlerTranslation = translateByHandler(translationId, interpolateParams); - } - - // 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); - } + if (defaultTranslationText) { + deferred.resolve(defaultTranslationText); } else { - if (defaultTranslationText) { - deferred.resolve(defaultTranslationText); - } else { - deferred.reject(applyNotFoundIndicators(translationId)); - } + deferred.reject(applyNotFoundIndicators(translationId)); } } - return deferred.promise; - }; + } + return deferred.promise; + }; - var determineTranslationInstant = function (translationId, interpolateParams, interpolationId) { + var determineTranslationInstant = function (translationId, interpolateParams, interpolationId, uses, sanitizeStrategy) { - var result, table = $uses ? $translationTable[$uses] : $translationTable, - Interpolator = defaultInterpolator; + 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 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 the translation id exists, we can just interpolate it + if (table && Object.prototype.hasOwnProperty.call(table, translationId) && table[translationId] !== null) { + 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); - } + // If using link, rerun $translate with linked translationId and return it + if (translation.substr(0, 2) === '@:') { + result = determineTranslationInstant(translation.substr(2), interpolateParams, interpolationId, uses, sanitizeStrategy); } else { - var missingTranslationHandlerTranslation; - // for logging purposes only (as in $translateMissingTranslationHandlerLog), value is not returned to promise - if ($missingTranslationHandlerFactory && !pendingLoader) { - missingTranslationHandlerTranslation = translateByHandler(translationId, interpolateParams); - } - - // 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); - } + result = Interpolator.interpolate(translation, interpolateParams, 'filter', sanitizeStrategy, translationId); + result = applyPostProcessing(translationId, translation, result, interpolateParams, uses, sanitizeStrategy); + } + } else { + var missingTranslationHandlerTranslation; + // for logging purposes only (as in $translateMissingTranslationHandlerLog), value is not returned to promise + if ($missingTranslationHandlerFactory && !pendingLoader) { + missingTranslationHandlerTranslation = translateByHandler(translationId, interpolateParams, sanitizeStrategy); } - return result; - }; - - var clearNextLangAndPromise = function(key) { - if ($nextLang === key) { - $nextLang = undefined; - } - langPromises[key] = undefined; - }; - - /** - * @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]; + // 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, sanitizeStrategy); + } 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 { - return $fallbackLanguage; + result = applyNotFoundIndicators(translationId); } + } - }; + return result; + }; - /** - * @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; - } - } + var clearNextLangAndPromise = function (key) { + if ($nextLang === key) { + $nextLang = undefined; + } + langPromises[key] = undefined; + }; + var applyPostProcessing = function (translationId, translation, resolvedTranslation, interpolateParams, uses, sanitizeStrategy) { + var fn = postProcessFn; + + if (fn) { + + if (typeof(fn) === 'string') { + // getting on-demand instance + fn = $injector.get(fn); } - - }; - - /** - * @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; + if (fn) { + return fn(translationId, translation, resolvedTranslation, interpolateParams, uses, sanitizeStrategy); } + } - var deferred = $q.defer(); + return resolvedTranslation; + }; - $rootScope.$emit('$translateChangeStart', {language: key}); + var loadTranslationsIfMissing = function (key) { + if (!$translationTable[key] && $loaderFactory && !langPromises[key]) { + langPromises[key] = loadAsync(key).then(function (translation) { + translations(translation.key, translation.table); + return translation; + }); + } + }; - // Try to get the aliased language key - var aliasedKey = negotiateLocale(key); - if (aliasedKey) { - key = aliasedKey; - } + /** + * @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; + }; - // 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); - return translation; - }, function (key) { - $rootScope.$emit('$translateChangeError', {language: key}); - deferred.reject(key); - $rootScope.$emit('$translateChangeEnd', {language: key}); - return $q.reject(key); - }); - langPromises[key]['finally'](function () { - clearNextLangAndPromise(key); - }); - } else if ($nextLang === key && langPromises[key]) { - // we are already loading this asynchronously - // resolve our new deferred when the old langPromise is resolved - langPromises[key].then(function (translation) { - deferred.resolve(translation.key); - return translation; - }, function (key) { - deferred.reject(key); - return $q.reject(key); - }); - } else { - deferred.resolve(key); - useLanguage(key); - } + /** + * @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; + }; - return deferred.promise; - }; + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#nestedObjectDelimeter + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns the configured delimiter for nested namespaces. + * + * @return {string} nestedObjectDelimeter + */ + $translate.nestedObjectDelimeter = function () { + return $nestedObjectDelimeter; + }; - /** - * @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#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); - /** - * @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 + // 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++) { - tables.push(loadAsync($fallbackLanguage[i])); - loadingKeys[$fallbackLanguage[i]] = true; + if (!langPromises[$fallbackLanguage[i]]) { + langPromises[$fallbackLanguage[i]] = loadAsync($fallbackLanguage[i]); + } } } + } + $translate.use($translate.use()); + } + if ($fallbackWasString) { + return $fallbackLanguage[0]; + } else { + return $fallbackLanguage; + } - // reload currently used language - if ($uses && !loadingKeys[$uses]) { - tables.push(loadAsync($uses)); - } - - var allTranslationsLoaded = function (tableData) { - $translationTable = {}; - angular.forEach(tableData, function (data) { - translations(data.key, data.table); - }); - if ($uses) { - useLanguage($uses); - } - resolve(); - }; - allTranslationsLoaded.displayName = 'refreshPostProcessor'; - - $q.all(tables).then(allTranslationsLoaded); - - } else if ($translationTable[langKey]) { - - var oneTranslationsLoaded = function (data) { - translations(data.key, data.table); - if (langKey === $uses) { - useLanguage($uses); - } - resolve(); - }; - oneTranslationsLoaded.displayName = 'refreshPostProcessor'; - - loadAsync(langKey).then(oneTranslationsLoaded, reject); + }; + /** + * @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 { - 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|object} 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); + var langKeyPosition = indexOf($fallbackLanguage, langKey); + if (langKeyPosition > -1) { + startFallbackIteration = langKeyPosition; } - 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); + /** + * @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#negotiateLocale + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns a language key based on available languages and language aliases. If a + * language key cannot be resolved, returns undefined. + * + * If no or a falsy key is given, returns undefined. + * + * @param {string} [key] Language key + * @return {string|undefined} Language key or undefined if no language key is found. + */ + $translate.negotiateLocale = negotiateLocale; + + /** + * @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 or string of the currently used language. + * + * If no or a falsy key is given it returns the currently used language key. + * The returned string will be ```undefined``` if setting up $translate hasn't finished. + * @example + * $translate.use("en_US").then(function(data){ + * $scope.text = $translate("HELLO"); + * }); + * + * @param {string} [key] Language key + * @return {object|string} Promise with loaded language data or the language key if a falsy param was given. + */ + $translate.use = function (key) { + if (!key) { + return $uses; + } + + var deferred = $q.defer(); + deferred.promise.then(null, angular.noop); // AJS "Possibly unhandled rejection" + + $rootScope.$emit('$translateChangeStart', {language : key}); + + // Try to get the aliased language key + var aliasedKey = negotiateLocale(key); + // Ensure only registered language keys will be loaded + if ($availableLanguageKeys.length > 0 && !aliasedKey) { + return $q.reject(key); + } + + if (aliasedKey) { + key = aliasedKey; + } + + // if there isn't a translation table for the language we've requested, + // we load it asynchronously + $nextLang = key; + if (($forceAsyncReloadEnabled || !$translationTable[key]) && $loaderFactory && !langPromises[key]) { + langPromises[key] = loadAsync(key).then(function (translation) { + translations(translation.key, translation.table); + deferred.resolve(translation.key); + if ($nextLang === key) { + useLanguage(translation.key); + } + return translation; + }, function (key) { + $rootScope.$emit('$translateChangeError', {language : key}); + deferred.reject(key); + $rootScope.$emit('$translateChangeEnd', {language : key}); + return $q.reject(key); + }); + langPromises[key]['finally'](function () { + clearNextLangAndPromise(key); + }).catch(angular.noop); // we don't care about errors (clearing) + } else if (langPromises[key]) { + // we are already loading this asynchronously + // resolve our new deferred when the old langPromise is resolved + langPromises[key].then(function (translation) { + if ($nextLang === translation.key) { + useLanguage(translation.key); + } + deferred.resolve(translation.key); + return translation; + }, function (key) { + // find first available fallback language if that request has failed + if (!$uses && $fallbackLanguage && $fallbackLanguage.length > 0 && $fallbackLanguage[0] !== key) { + return $translate.use($fallbackLanguage[0]).then(deferred.resolve, deferred.reject); + } else { + return deferred.reject(key); + } + }); + } else { + deferred.resolve(key); + useLanguage(key); + } + + return deferred.promise; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#resolveClientLocale + * @methodOf pascalprecht.translate.$translate + * + * @description + * This returns the current browser/client's language key. The result is processed with the configured uniform tag resolver. + * + * @returns {string} the current client/browser language key + */ + $translate.resolveClientLocale = function () { + return getLocale(); + }; + + /** + * @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#isForceAsyncReloadEnabled + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns whether force async reload is enabled or not + * + * @return {boolean} forceAsyncReload value + */ + $translate.isForceAsyncReloadEnabled = function () { + return $forceAsyncReloadEnabled; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#isKeepContent + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns whether keepContent or not + * + * @return {boolean} keepContent value + */ + $translate.isKeepContent = function () { + return $keepContent; + }; + + /** + * @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!'); + } + + $rootScope.$emit('$translateRefreshStart', {language : langKey}); + + var deferred = $q.defer(), updatedLanguages = {}; + + //private helper + function loadNewData(languageKey) { + var promise = loadAsync(languageKey); + //update the load promise cache for this language + langPromises[languageKey] = promise; + //register a data handler for the promise + promise.then(function (data) { + //clear the cache for this language + $translationTable[languageKey] = {}; + //add the new data for this language + translations(languageKey, data.table); + //track that we updated this language + updatedLanguages[languageKey] = true; + }, + //handle rejection to appease the $q validation + angular.noop); + return promise; + } + + //set up post-processing + deferred.promise.then( + function () { + for (var key in $translationTable) { + if ($translationTable.hasOwnProperty(key)) { + //delete cache entries that were not updated + if (!(key in updatedLanguages)) { + delete $translationTable[key]; + } } } - if (typeof result !== 'undefined') { - break; + if ($uses) { + useLanguage($uses); + } + }, + //handle rejection to appease the $q validation + angular.noop + ).finally( + function () { + $rootScope.$emit('$translateRefreshEnd', {language : langKey}); + } + ); + + if (!langKey) { + // if there's no language key specified we refresh ALL THE THINGS! + var languagesToReload = $fallbackLanguage && $fallbackLanguage.slice() || []; + if ($uses && languagesToReload.indexOf($uses) === -1) { + languagesToReload.push($uses); + } + $q.all(languagesToReload.map(loadNewData)).then(deferred.resolve, deferred.reject); + + } else if ($translationTable[langKey]) { + //just refresh the specified language cache + loadNewData(langKey).then(deferred.resolve, deferred.reject); + + } else { + deferred.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 + * @param {string} forceLanguage A language to be used instead of the current language + * @param {string} sanitizeStrategy force sanitize strategy for this call instead of using the configured one + * + * @return {string|object} translation + */ + $translate.instant = function (translationId, interpolateParams, interpolationId, forceLanguage, sanitizeStrategy) { + + // we don't want to re-negotiate $uses + var uses = (forceLanguage && forceLanguage !== $uses) ? // we don't want to re-negotiate $uses + (negotiateLocale(forceLanguage) || forceLanguage) : $uses; + + // Detect undefined and null values to shorten the execution and prevent exceptions + if (translationId === null || angular.isUndefined(translationId)) { + return translationId; + } + + // Check forceLanguage is present + if (forceLanguage) { + loadTranslationsIfMissing(forceLanguage); + } + + // 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, forceLanguage, sanitizeStrategy); + } + 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, uses, sanitizeStrategy); } } + if (typeof result !== 'undefined') { + break; + } + } - if (!result && result !== '') { + if (!result && result !== '') { + if ($notFoundIndicatorLeft || $notFoundIndicatorRight) { + result = applyNotFoundIndicators(translationId); + } else { // Return translation of default interpolator if not found anything. - result = defaultInterpolator.interpolate(translationId, interpolateParams); + result = defaultInterpolator.interpolate(translationId, interpolateParams, 'filter', sanitizeStrategy); + + // 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 + var missingTranslationHandlerTranslation; if ($missingTranslationHandlerFactory && !pendingLoader) { - result = translateByHandler(translationId, interpolateParams); + missingTranslationHandlerTranslation = translateByHandler(translationId, interpolateParams, sanitizeStrategy); } - } - 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; - }; - - // internal purpose only - $translate.statefulFilter = function () { - return statefulFilter; - }; - - 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++) { - var fallbackLanguageId = $fallbackLanguage[i]; - if (!$translationTable[fallbackLanguageId]) { - langPromises[fallbackLanguageId] = loadAsync(fallbackLanguageId).then(processAsyncResult); - } + if ($missingTranslationHandlerFactory && !pendingLoader && missingTranslationHandlerTranslation) { + result = missingTranslationHandlerTranslation; } } } - return $translate; + 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; + }; + + // internal purpose only + $translate.statefulFilter = function () { + return statefulFilter; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#isReady + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns whether the service is "ready" to translate (i.e. loading 1st language). + * + * See also {@link pascalprecht.translate.$translate#methods_onReady onReady()}. + * + * @return {boolean} current value of ready + */ + $translate.isReady = function () { + return $isReady; + }; + + var $onReadyDeferred = $q.defer(); + $onReadyDeferred.promise.then(function () { + $isReady = true; + }); + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#onReady + * @methodOf pascalprecht.translate.$translate + * + * @description + * Calls the function provided or resolved the returned promise after the service is "ready" to translate (i.e. loading 1st language). + * + * See also {@link pascalprecht.translate.$translate#methods_isReady isReady()}. + * + * @param {Function=} fn Function to invoke when service is ready + * @return {object} Promise resolved when service is ready + */ + $translate.onReady = function (fn) { + var deferred = $q.defer(); + if (angular.isFunction(fn)) { + deferred.promise.then(fn); + } + if ($isReady) { + deferred.resolve(); + } else { + $onReadyDeferred.promise.then(deferred.resolve); + } + return deferred.promise; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#getAvailableLanguageKeys + * @methodOf pascalprecht.translate.$translate + * + * @description + * This function simply returns the registered language keys being defined before in the config phase + * With this, an application can use the array to provide a language selection dropdown or similar + * without any additional effort + * + * @returns {object} returns the list of possibly registered language keys and mapping or null if not defined + */ + $translate.getAvailableLanguageKeys = function () { + if ($availableLanguageKeys.length > 0) { + return $availableLanguageKeys; + } + return null; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#getTranslationTable + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns translation table by the given language key. + * + * Unless a language is provided it returns a translation table of the current one. + * Note: If translation dictionary is currently downloading or in progress + * it will return null. + * + * @param {string} langKey A token which represents a translation id + * + * @return {object} a copy of angular-translate $translationTable + */ + $translate.getTranslationTable = function (langKey) { + langKey = langKey || $translate.use(); + if (langKey && $translationTable[langKey]) { + return angular.copy($translationTable[langKey]); + } + return null; + }; + + // Whenever $translateReady is being fired, this will ensure the state of $isReady + var globalOnReadyListener = $rootScope.$on('$translateReady', function () { + $onReadyDeferred.resolve(); + globalOnReadyListener(); // one time only + globalOnReadyListener = null; + }); + var globalOnChangeListener = $rootScope.$on('$translateChangeEnd', function () { + $onReadyDeferred.resolve(); + globalOnChangeListener(); // one time only + globalOnChangeListener = null; + }); + + 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, {})) { + if ($translate.use()) { + $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++) { + var fallbackLanguageId = $fallbackLanguage[i]; + if ($forceAsyncReloadEnabled || !$translationTable[fallbackLanguageId]) { + langPromises[fallbackLanguageId] = loadAsync(fallbackLanguageId).then(processAsyncResult); + } + } + } + } else { + $rootScope.$emit('$translateReady', {language : $translate.use()}); } - ]; + + return $translate; + }]; } -$translate.$inject = ['$STORAGE_KEY', '$windowProvider', '$translateSanitizationProvider', 'pascalprechtTranslateOverrider']; $translate.displayName = 'displayName'; @@ -2353,24 +2833,41 @@ function $translateDefaultInterpolation ($interpolate, $translateSanitization) { * @methodOf pascalprecht.translate.$translateDefaultInterpolation * * @description - * Interpolates given string agains given interpolate params using angulars + * Interpolates given value agains given interpolate params using angulars * `$interpolate` service. * - * @returns {string} interpolated string. + * Since AngularJS 1.5, `value` must not be a string but can be anything input. + * + * @param {string} value translation + * @param {object} interpolationParams interpolation params + * @param {string} context current context (filter, directive, service) + * @param {string} sanitizeStrategy sanitize strategy + * @param {string} translationId current translationId + * + * @returns {string} interpolated string */ - $translateInterpolator.interpolate = function (string, interpolationParams) { + $translateInterpolator.interpolate = function (value, interpolationParams, context, sanitizeStrategy, translationId) { // jshint ignore:line interpolationParams = interpolationParams || {}; - interpolationParams = $translateSanitization.sanitize(interpolationParams, 'params'); + interpolationParams = $translateSanitization.sanitize(interpolationParams, 'params', sanitizeStrategy, context); - var interpolatedText = $interpolate(string)(interpolationParams); - interpolatedText = $translateSanitization.sanitize(interpolatedText, 'text'); + var interpolatedText; + if (angular.isNumber(value)) { + // numbers are safe + interpolatedText = '' + value; + } else if (angular.isString(value)) { + // strings must be interpolated (that's the job here) + interpolatedText = $interpolate(value)(interpolationParams); + interpolatedText = $translateSanitization.sanitize(interpolatedText, 'text', sanitizeStrategy, context); + } else { + // neither a number or a string, cant interpolate => empty string + interpolatedText = ''; + } return interpolatedText; }; return $translateInterpolator; } -$translateDefaultInterpolation.$inject = ['$interpolate', '$translateSanitization']; $translateDefaultInterpolation.displayName = '$translateDefaultInterpolation'; @@ -2380,14 +2877,15 @@ angular.module('pascalprecht.translate') /** * @ngdoc directive * @name pascalprecht.translate.directive:translate - * @requires $compile - * @requires $filter - * @requires $interpolate - * @restrict A + * @requires $interpolate, + * @requires $compile, + * @requires $parse, + * @requires $rootScope + * @restrict AE * * @description * Translates given translation id either through attribute or DOM content. - * Internally it uses `translate` filter to translate translation id. It possible to + * Internally it uses $translate service to translate the 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. @@ -2395,6 +2893,7 @@ angular.module('pascalprecht.translate') * @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.$translateProvider#methods_usePostCompiling} + * @param {boolean=} translate-keep-content (default true if present) defines that in case a KEY could not be translated, that the existing content is left in the innerHTML} * * @example @@ -2411,6 +2910,7 @@ angular.module('pascalprecht.translate')

         
WITH_VALUES

+        

 
       
     
@@ -2421,7 +2921,8 @@ angular.module('pascalprecht.translate')
 
         $translateProvider.translations('en',{
           'TRANSLATION_ID': 'Hello there!',
-          'WITH_VALUES': 'The following value is dynamic: {{value}}'
+          'WITH_VALUES': 'The following value is dynamic: {{value}}',
+          'WITH_CAMEL_CASE_KEY': 'The interpolation key is camel cased: {{camelCaseKey}}'
         }).preferredLanguage('en');
 
       });
@@ -2458,13 +2959,17 @@ angular.module('pascalprecht.translate')
           element = $compile('

')($rootScope); $rootScope.$digest(); expect(element.attr('title')).toBe('Hello there!'); + + element = $compile('

')($rootScope); + $rootScope.$digest(); + expect(element.text()).toBe('The interpolation key is camel cased: Hello'); }); });
*/ .directive('translate', translateDirective); -function translateDirective($translate, $q, $interpolate, $compile, $parse, $rootScope) { +function translateDirective($translate, $interpolate, $compile, $parse, $rootScope) { 'use strict'; @@ -2478,7 +2983,7 @@ function translateDirective($translate, $q, $interpolate, $compile, $parse, $roo * @returns {string} The string stripped of whitespace from both ends */ var trim = function() { - return this.replace(/^\s+|\s+$/g, ''); + return this.toString().replace(/^\s+|\s+$/g, ''); }; return { @@ -2503,6 +3008,7 @@ function translateDirective($translate, $q, $interpolate, $compile, $parse, $roo scope.interpolateParams = {}; scope.preText = ''; scope.postText = ''; + scope.translateNamespace = getTranslateNamespace(scope); var translationIds = {}; var initInterpolationParams = function (interpolateParams, iAttr, tAttr) { @@ -2533,14 +3039,16 @@ function translateDirective($translate, $q, $interpolate, $compile, $parse, $roo } if (angular.equals(translationId , '') || !angular.isDefined(translationId)) { + var iElementText = trim.apply(iElement.text()); + // Resolve translation id by inner html if required - var interpolateMatches = trim.apply(iElement.text()).match(interpolateRegExp); + var interpolateMatches = iElementText.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); + var watcherMatches = iElementText.match(watcherRegExp); if (angular.isArray(watcherMatches) && watcherMatches[2] && watcherMatches[2].length) { observeElementTranslation._unwatchOld = scope.$watch(watcherMatches[2], function (newValue) { translationIds.translate = newValue; @@ -2548,7 +3056,8 @@ function translateDirective($translate, $q, $interpolate, $compile, $parse, $roo }); } } else { - translationIds.translate = iElement.text().replace(/^\s+|\s+$/g,''); + // do not assigne the translation id if it is empty. + translationIds.translate = !iElementText ? undefined : iElementText; } } else { translationIds.translate = translationId; @@ -2582,13 +3091,14 @@ function translateDirective($translate, $q, $interpolate, $compile, $parse, $roo }); for (var translateAttr in iAttr) { - if (iAttr.hasOwnProperty(translateAttr) && translateAttr.substr(0, 13) === 'translateAttr') { + if (iAttr.hasOwnProperty(translateAttr) && translateAttr.substr(0, 13) === 'translateAttr' && translateAttr.length > 13) { observeAttributeTranslation(translateAttr); } } iAttr.$observe('translateDefault', function (value) { scope.defaultText = value; + updateTranslations(); }); if (translateValuesExist) { @@ -2618,17 +3128,21 @@ function translateDirective($translate, $q, $interpolate, $compile, $parse, $roo // Master update function var updateTranslations = function () { for (var key in translationIds) { - if (translationIds.hasOwnProperty(key) && translationIds[key] !== undefined) { - updateTranslation(key, translationIds[key], scope, scope.interpolateParams, scope.defaultText); + updateTranslation(key, translationIds[key], scope, scope.interpolateParams, scope.defaultText, scope.translateNamespace); } } }; // Put translation processing function outside loop - var updateTranslation = function(translateAttr, translationId, scope, interpolateParams, defaultTranslationText) { + var updateTranslation = function(translateAttr, translationId, scope, interpolateParams, defaultTranslationText, translateNamespace) { if (translationId) { - $translate(translationId, interpolateParams, translateInterpolation, defaultTranslationText) + // if translation id starts with '.' and translateNamespace given, prepend namespace + if (translateNamespace && translationId.charAt(0) === '.') { + translationId = translateNamespace + translationId; + } + + $translate(translationId, interpolateParams, translateInterpolation, defaultTranslationText, scope.translateLanguage) .then(function (translation) { applyTranslation(translation, scope, true, translateAttr); }, function (translationId) { @@ -2641,12 +3155,16 @@ function translateDirective($translate, $q, $interpolate, $compile, $parse, $roo }; var applyTranslation = function (value, scope, successful, translateAttr) { - if (translateAttr === 'translate') { - // default translate into innerHTML - if (!successful && typeof scope.defaultText !== 'undefined') { + if (!successful) { + if (typeof scope.defaultText !== 'undefined') { value = scope.defaultText; } - iElement.html(scope.preText + value + scope.postText); + } + if (translateAttr === 'translate') { + // default translate into innerHTML + if (successful || (!successful && !$translate.isKeepContent() && typeof iAttr.translateKeepContent === 'undefined')) { + iElement.empty().append(scope.preText + value + scope.postText); + } var globallyEnabled = $translate.isPostCompilingEnabled(); var locallyDefined = typeof tAttr.translateCompile !== 'undefined'; var locallyEnabled = locallyDefined && tAttr.translateCompile !== 'false'; @@ -2655,9 +3173,6 @@ function translateDirective($translate, $q, $interpolate, $compile, $parse, $roo } } else { // translate attribute - if (!successful && typeof scope.defaultText !== 'undefined') { - value = scope.defaultText; - } var attributeName = iAttr.$attr[translateAttr]; if (attributeName.substr(0, 5) === 'data-') { // ensure html5 data prefix is stripped @@ -2672,6 +3187,9 @@ function translateDirective($translate, $q, $interpolate, $compile, $parse, $roo scope.$watch('interpolateParams', updateTranslations, true); } + // Replaced watcher on translateLanguage with event listener + scope.$on('translateLanguageChanged', updateTranslations); + // Ensures the text will be refreshed after the current language was changed // w/ $translate.use(...) var unbind = $rootScope.$on('$translateChangeSuccess', updateTranslations); @@ -2683,6 +3201,9 @@ function translateDirective($translate, $q, $interpolate, $compile, $parse, $roo } else { observeElementTranslation(''); } + } else if (iAttr.translate) { + // ensure attribute will be not skipped + observeElementTranslation(iAttr.translate); } updateTranslations(); scope.$on('$destroy', unbind); @@ -2690,15 +3211,179 @@ function translateDirective($translate, $q, $interpolate, $compile, $parse, $roo } }; } -translateDirective.$inject = ['$translate', '$q', '$interpolate', '$compile', '$parse', '$rootScope']; + +/** + * Returns the scope's namespace. + * @private + * @param scope + * @returns {string} + */ +function getTranslateNamespace(scope) { + 'use strict'; + if (scope.translateNamespace) { + return scope.translateNamespace; + } + if (scope.$parent) { + return getTranslateNamespace(scope.$parent); + } +} translateDirective.displayName = 'translateDirective'; +angular.module('pascalprecht.translate') +/** + * @ngdoc directive + * @name pascalprecht.translate.directive:translate-attr + * @restrict A + * + * @description + * Translates attributes like translate-attr-ATTR, but with an object like ng-class. + * Internally it uses `translate` service to translate translation id. It possible to + * pass an optional `translate-values` object literal as string into translation id. + * + * @param {string=} translate-attr Object literal mapping attributes to translation ids. + * @param {string=} translate-values Values to pass into the translation ids. Can be passed as object literal string. + * + * @example + + +
+ + + +
+
+ + 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 + }; + }); + + + it('should translate', function () { + inject(function ($rootScope, $compile) { + $rootScope.translationId = 'TRANSLATION_ID'; + + element = $compile('')($rootScope); + $rootScope.$digest(); + expect(element.attr('placeholder)).toBe('Hello there!'); + expect(element.attr('title)).toBe('The following value is dynamic: 5'); + }); + }); + +
+ */ +.directive('translateAttr', translateAttrDirective); +function translateAttrDirective($translate, $rootScope) { + + 'use strict'; + + return { + restrict: 'A', + priority: $translate.directivePriority(), + link: function linkFn(scope, element, attr) { + + var translateAttr, + translateValues, + previousAttributes = {}; + + // Main update translations function + var updateTranslations = function () { + angular.forEach(translateAttr, function (translationId, attributeName) { + if (!translationId) { + return; + } + previousAttributes[attributeName] = true; + + // if translation id starts with '.' and translateNamespace given, prepend namespace + if (scope.translateNamespace && translationId.charAt(0) === '.') { + translationId = scope.translateNamespace + translationId; + } + $translate(translationId, translateValues, attr.translateInterpolation, undefined, scope.translateLanguage) + .then(function (translation) { + element.attr(attributeName, translation); + }, function (translationId) { + element.attr(attributeName, translationId); + }); + }); + + // Removing unused attributes that were previously used + angular.forEach(previousAttributes, function (flag, attributeName) { + if (!translateAttr[attributeName]) { + element.removeAttr(attributeName); + delete previousAttributes[attributeName]; + } + }); + }; + + // Watch for attribute changes + watchAttribute( + scope, + attr.translateAttr, + function (newValue) { translateAttr = newValue; }, + updateTranslations + ); + // Watch for value changes + watchAttribute( + scope, + attr.translateValues, + function (newValue) { translateValues = newValue; }, + updateTranslations + ); + + if (attr.translateValues) { + scope.$watch(attr.translateValues, updateTranslations, true); + } + + // Replaced watcher on translateLanguage with event listener + scope.$on('translateLanguageChanged', updateTranslations); + + // Ensures the text will be refreshed after the current language was changed + // w/ $translate.use(...) + var unbind = $rootScope.$on('$translateChangeSuccess', updateTranslations); + + updateTranslations(); + scope.$on('$destroy', unbind); + } + }; +} + +function watchAttribute(scope, attribute, valueCallback, changeCallback) { + 'use strict'; + if (!attribute) { + return; + } + if (attribute.substr(0, 2) === '::') { + attribute = attribute.substr(2); + } else { + scope.$watch(attribute, function(newValue) { + valueCallback(newValue); + changeCallback(); + }, true); + } + valueCallback(scope.$eval(attribute)); +} + +translateAttrDirective.displayName = 'translateAttrDirective'; + angular.module('pascalprecht.translate') /** * @ngdoc directive * @name pascalprecht.translate.directive:translateCloak - * @requires $rootScope * @requires $translate * @restrict A * @@ -2718,40 +3403,205 @@ angular.module('pascalprecht.translate') */ .directive('translateCloak', translateCloakDirective); -function translateCloakDirective($rootScope, $translate) { +function translateCloakDirective($translate, $rootScope) { 'use strict'; 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(); + compile : function (tElement) { + var applyCloak = function (element) { + element.addClass($translate.cloakClassName()); + }, + removeCloak = function (element) { + element.removeClass($translate.cloakClassName()); + }; + applyCloak(tElement); return function linkFn(scope, iElement, iAttr) { - // Register a watcher for the defined translation allowing a fine tuned cloak + //Create bound functions that incorporate the active DOM element. + var iRemoveCloak = removeCloak.bind(this, iElement), iApplyCloak = applyCloak.bind(this, iElement); if (iAttr.translateCloak && iAttr.translateCloak.length) { + // Register a watcher for the defined translation allowing a fine tuned cloak iAttr.$observe('translateCloak', function (translationId) { - $translate(translationId).then(removeCloak, applyCloak); + $translate(translationId).then(iRemoveCloak, iApplyCloak); }); + $rootScope.$on('$translateChangeSuccess', function () { + $translate(iAttr.translateCloak).then(iRemoveCloak, iApplyCloak); + }); + } else { + $translate.onReady(iRemoveCloak); } }; } }; } -translateCloakDirective.$inject = ['$rootScope', '$translate']; translateCloakDirective.displayName = 'translateCloakDirective'; +angular.module('pascalprecht.translate') +/** + * @ngdoc directive + * @name pascalprecht.translate.directive:translateNamespace + * @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 namespace name which could be either string or interpolated string. + * + * @example + + +
+ +
+

.HEADERS.TITLE

+

.HEADERS.WELCOME

+
+ +
+

.TITLE

+

.WELCOME

+
+ +
+
+ + angular.module('ngView', ['pascalprecht.translate']) + + .config(function ($translateProvider) { + + $translateProvider.translations('en',{ + 'TRANSLATION_ID': 'Hello there!', + 'CONTENT': { + 'HEADERS': { + TITLE: 'Title' + } + }, + 'CONTENT.HEADERS.WELCOME': 'Welcome' + }).preferredLanguage('en'); + + }); + + +
+ */ +.directive('translateNamespace', translateNamespaceDirective); + +function translateNamespaceDirective() { + + 'use strict'; + + return { + restrict: 'A', + scope: true, + compile: function () { + return { + pre: function (scope, iElement, iAttrs) { + scope.translateNamespace = getTranslateNamespace(scope); + + if (scope.translateNamespace && iAttrs.translateNamespace.charAt(0) === '.') { + scope.translateNamespace += iAttrs.translateNamespace; + } else { + scope.translateNamespace = iAttrs.translateNamespace; + } + } + }; + } + }; +} + +/** + * Returns the scope's namespace. + * @private + * @param scope + * @returns {string} + */ +function getTranslateNamespace(scope) { + 'use strict'; + if (scope.translateNamespace) { + return scope.translateNamespace; + } + if (scope.$parent) { + return getTranslateNamespace(scope.$parent); + } +} + +translateNamespaceDirective.displayName = 'translateNamespaceDirective'; + +angular.module('pascalprecht.translate') +/** + * @ngdoc directive + * @name pascalprecht.translate.directive:translateLanguage + * @restrict A + * + * @description + * Forces the language to the directives in the underlying scope. + * + * @param {string=} translate language that will be negotiated. + * + * @example + + +
+ +
+

HELLO

+
+ +
+

HELLO

+
+ +
+
+ + angular.module('ngView', ['pascalprecht.translate']) + + .config(function ($translateProvider) { + + $translateProvider + .translations('en',{ + 'HELLO': 'Hello world!' + }) + .translations('de',{ + 'HELLO': 'Hallo Welt!' + }) + .preferredLanguage('en'); + + }); + + +
+ */ +.directive('translateLanguage', translateLanguageDirective); + +function translateLanguageDirective() { + + 'use strict'; + + return { + restrict: 'A', + scope: true, + compile: function () { + return function linkFn(scope, iElement, iAttrs) { + + iAttrs.$observe('translateLanguage', function (newTranslateLanguage) { + scope.translateLanguage = newTranslateLanguage; + }); + + scope.$watch('translateLanguage', function(){ + scope.$broadcast('translateLanguageChanged'); + }); + }; + } + }; +} + +translateLanguageDirective.displayName = 'translateLanguageDirective'; + angular.module('pascalprecht.translate') /** * @ngdoc filter @@ -2810,13 +3660,15 @@ function translateFilterFactory($parse, $translate) { 'use strict'; - var translateFilter = function (translationId, interpolateParams, interpolation) { - + var translateFilter = function (translationId, interpolateParams, interpolation, forceLanguage) { if (!angular.isObject(interpolateParams)) { - interpolateParams = $parse(interpolateParams)(this); + var ctx = this || { + '__SCOPE_IS_NOT_AVAILABLE': 'More info at https://github.com/angular/angular.js/commit/8863b9d04c722b278fa93c5d66ad1e578ad6eb1f' + }; + interpolateParams = $parse(interpolateParams)(ctx); } - return $translate.instant(translationId, interpolateParams, interpolation); + return $translate.instant(translationId, interpolateParams, interpolation, forceLanguage); }; if ($translate.statefulFilter()) { @@ -2825,9 +3677,33 @@ function translateFilterFactory($parse, $translate) { return translateFilter; } -translateFilterFactory.$inject = ['$parse', '$translate']; translateFilterFactory.displayName = 'translateFilterFactory'; + +angular.module('pascalprecht.translate') + +/** + * @ngdoc object + * @name pascalprecht.translate.$translationCache + * @requires $cacheFactory + * + * @description + * The first time a translation table is used, it is loaded in the translation cache for quick retrieval. You + * can load translation tables directly into the cache by consuming the + * `$translationCache` service directly. + * + * @return {object} $cacheFactory object. + */ + .factory('$translationCache', $translationCache); + +function $translationCache($cacheFactory) { + + 'use strict'; + + return $cacheFactory('translations'); +} + +$translationCache.displayName = '$translationCache'; return 'pascalprecht.translate'; })); diff --git a/static/js/angular-translate.min.js b/static/js/angular-translate.min.js index f1104f5..43b0b5b 100644 --- a/static/js/angular-translate.min.js +++ b/static/js/angular-translate.min.js @@ -1,6 +1,6 @@ /*! - * angular-translate - v2.7.0 - 2015-05-02 - * http://github.com/angular-translate/angular-translate - * Copyright (c) 2015 ; Licensed MIT + * angular-translate - v2.15.1 - 2017-04-03 + * + * Copyright (c) 2017 The angular-translate team, Pascal Precht; Licensed MIT */ -!function(a,b){"function"==typeof define&&define.amd?define([],function(){return b()}):"object"==typeof exports?module.exports=b():b()}(this,function(){function a(a){"use strict";var b=a.storageKey(),c=a.storage(),d=function(){var d=a.preferredLanguage();angular.isString(d)?a.use(d):c.put(b,a.use())};d.displayName="fallbackFromIncorrectStorageValue",c?c.get(b)?a.use(c.get(b))["catch"](d):d():angular.isString(a.preferredLanguage())&&a.use(a.preferredLanguage())}function b(){"use strict";var a,b,c=null,d=!1,e=!1;b={sanitize:function(a,b){return"text"===b&&(a=g(a)),a},escape:function(a,b){return"text"===b&&(a=f(a)),a},sanitizeParameters:function(a,b){return"params"===b&&(a=h(a,g)),a},escapeParameters:function(a,b){return"params"===b&&(a=h(a,f)),a}},b.escaped=b.escapeParameters,this.addStrategy=function(a,c){return b[a]=c,this},this.removeStrategy=function(a){return delete b[a],this},this.useStrategy=function(a){return d=!0,c=a,this},this.$get=["$injector","$log",function(f,g){var h=function(a,c,d){return angular.forEach(d,function(d){if(angular.isFunction(d))a=d(a,c);else{if(!angular.isFunction(b[d]))throw new Error("pascalprecht.translate.$translateSanitization: Unknown sanitization strategy: '"+d+"'");a=b[d](a,c)}}),a},i=function(){d||e||(g.warn("pascalprecht.translate.$translateSanitization: No sanitization strategy has been configured. This can have serious security implications. See http://angular-translate.github.io/docs/#/guide/19_security for details."),e=!0)};return f.has("$sanitize")&&(a=f.get("$sanitize")),{useStrategy:function(a){return function(b){a.useStrategy(b)}}(this),sanitize:function(a,b,d){if(c||i(),arguments.length<3&&(d=c),!d)return a;var e=angular.isArray(d)?d:[d];return h(a,b,e)}}}];var f=function(a){return angular.element("
").text(a).html()},g=function(b){if(!a)throw new Error("pascalprecht.translate.$translateSanitization: Error cannot find $sanitize service. Either include the ngSanitize module (https://docs.angularjs.org/api/ngSanitize) or use a sanitization strategy which does not depend on $sanitize, such as 'escape'.");return a(b)},h=function(a,b){if(angular.isObject(a)){var c=angular.isArray(a)?[]:{};return angular.forEach(a,function(a,d){c[d]=h(a,b)}),c}return angular.isNumber(a)?a:b(a)}}function c(a,b,c,d){"use strict";var e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t={},u=[],v=a,w=[],x="translate-cloak",y=!1,z=".",A=0,B=!0,C="default",D={"default":function(a){return(a||"").split("-").join("_")},java:function(a){var b=(a||"").split("-").join("_"),c=b.split("_");return c.length>1?c[0].toLowerCase()+"_"+c[1].toUpperCase():b},bcp47:function(a){var b=(a||"").split("_").join("-"),c=b.split("-");return c.length>1?c[0].toLowerCase()+"-"+c[1].toUpperCase():b}},E="2.7.0",F=function(){if(angular.isFunction(d.getLocale))return d.getLocale();var a,c,e=b.$get().navigator,f=["language","browserLanguage","systemLanguage","userLanguage"];if(angular.isArray(e.languages))for(a=0;ac;c++)if(a[c]===b)return c;return-1},I=function(){return this.replace(/^\s+|\s+$/g,"")},J=function(a){for(var b=[],c=angular.lowercase(a),d=0,e=u.length;e>d;d++)b.push(angular.lowercase(u[d]));if(H(b,c)>-1)return a;if(f){var g;for(var h in f){var i=!1,j=Object.prototype.hasOwnProperty.call(f,h)&&angular.lowercase(h)===angular.lowercase(a);if("*"===h.slice(-1)&&(i=h.slice(0,-1)===a.slice(0,h.length-1)),(j||i)&&(g=f[h],H(b,angular.lowercase(g))>-1))return g}}if(a){var k=a.split("_");if(k.length>1&&H(b,angular.lowercase(k[0]))>-1)return k[0]}return a},K=function(a,b){if(!a&&!b)return t;if(a&&!b){if(angular.isString(a))return t[a]}else angular.isObject(t[a])||(t[a]={}),angular.extend(t[a],L(b));return this};this.translations=K,this.cloakClassName=function(a){return a?(x=a,this):x};var L=function(a,b,c,d){var e,f,g,h;b||(b=[]),c||(c={});for(e in a)Object.prototype.hasOwnProperty.call(a,e)&&(h=a[e],angular.isObject(h)?L(h,b.concat(e),c,e):(f=b.length?""+b.join(z)+z+e:e,b.length&&e===d&&(g=""+b.join(z),c[g]="@:"+f),c[f]=h));return c};L.displayName="flatObject",this.addInterpolation=function(a){return w.push(a),this},this.useMessageFormatInterpolation=function(){return this.useInterpolation("$translateMessageFormatInterpolation")},this.useInterpolation=function(a){return n=a,this},this.useSanitizeValueStrategy=function(a){return c.useStrategy(a),this},this.preferredLanguage=function(a){return M(a),this};var M=function(a){return a&&(e=a),e};this.translationNotFoundIndicator=function(a){return this.translationNotFoundIndicatorLeft(a),this.translationNotFoundIndicatorRight(a),this},this.translationNotFoundIndicatorLeft=function(a){return a?(q=a,this):q},this.translationNotFoundIndicatorRight=function(a){return a?(r=a,this):r},this.fallbackLanguage=function(a){return N(a),this};var N=function(a){return a?(angular.isString(a)?(h=!0,g=[a]):angular.isArray(a)&&(h=!1,g=a),angular.isString(e)&&H(g,e)<0&&g.push(e),this):h?g[0]:g};this.use=function(a){if(a){if(!t[a]&&!o)throw new Error("$translateProvider couldn't find translationTable for langKey: '"+a+"'");return i=a,this}return i};var O=function(a){return a?void(v=a):l?l+v:v};this.storageKey=O,this.useUrlLoader=function(a,b){return this.useLoader("$translateUrlLoader",angular.extend({url:a},b))},this.useStaticFilesLoader=function(a){return this.useLoader("$translateStaticFilesLoader",a)},this.useLoader=function(a,b){return o=a,p=b||{},this},this.useLocalStorage=function(){return this.useStorage("$translateLocalStorage")},this.useCookieStorage=function(){return this.useStorage("$translateCookieStorage")},this.useStorage=function(a){return k=a,this},this.storagePrefix=function(a){return a?(l=a,this):a},this.useMissingTranslationHandlerLog=function(){return this.useMissingTranslationHandler("$translateMissingTranslationHandlerLog")},this.useMissingTranslationHandler=function(a){return m=a,this},this.usePostCompiling=function(a){return y=!!a,this},this.uniformLanguageTag=function(a){return a?angular.isString(a)&&(a={standard:a}):a={},C=a.standard,this},this.determinePreferredLanguage=function(a){var b=a&&angular.isFunction(a)?a():G();return e=u.length?J(b):b,this},this.registerAvailableLanguageKeys=function(a,b){return a?(u=a,b&&(f=b),this):u},this.useLoaderCache=function(a){return a===!1?s=void 0:a===!0?s=!0:"undefined"==typeof a?s="$translationCache":a&&(s=a),this},this.directivePriority=function(a){return void 0===a?A:(A=a,this)},this.statefulFilter=function(a){return void 0===a?B:(B=a,this)},this.$get=["$log","$injector","$rootScope","$q",function(a,b,c,d){var f,l,u,z=b.get(n||"$translateDefaultInterpolation"),C=!1,D={},F={},G=function(a,b,c,h){if(angular.isArray(a)){var j=function(a){for(var e={},f=[],g=function(a){var f=d.defer(),g=function(b){e[a]=b,f.resolve([a,b])};return G(a,b,c,h).then(g,g),f.promise},i=0,j=a.length;j>i;i++)f.push(g(a[i]));return d.all(f).then(function(){return e})};return j(a)}var m=d.defer();a&&(a=I.apply(a));var n=function(){var a=e?F[e]:F[i];if(l=0,k&&!a){var b=f.get(v);if(a=F[b],g&&g.length){var c=H(g,b);l=0===c?1:0,H(g,e)<0&&g.push(e)}}return a}();if(n){var o=function(){_(a,b,c,h).then(m.resolve,m.reject)};o.displayName="promiseResolved",n["finally"](o,m.reject)}else _(a,b,c,h).then(m.resolve,m.reject);return m.promise},P=function(a){return q&&(a=[q,a].join(" ")),r&&(a=[a,r].join(" ")),a},Q=function(a){i=a,c.$emit("$translateChangeSuccess",{language:a}),k&&f.put(G.storageKey(),i),z.setLocale(i);var b=function(a,b){D[b].setLocale(i)};b.displayName="eachInterpolatorLocaleSetter",angular.forEach(D,b),c.$emit("$translateChangeEnd",{language:a})},R=function(a){if(!a)throw"No language key specified for loading.";var e=d.defer();c.$emit("$translateLoadingStart",{language:a}),C=!0;var f=s;"string"==typeof f&&(f=b.get(f));var g=angular.extend({},p,{key:a,$http:angular.extend({},{cache:f},p.$http)}),h=function(b){var d={};c.$emit("$translateLoadingSuccess",{language:a}),angular.isArray(b)?angular.forEach(b,function(a){angular.extend(d,L(a))}):angular.extend(d,L(b)),C=!1,e.resolve({key:a,table:d}),c.$emit("$translateLoadingEnd",{language:a})};h.displayName="onLoaderSuccess";var i=function(a){c.$emit("$translateLoadingError",{language:a}),e.reject(a),c.$emit("$translateLoadingEnd",{language:a})};return i.displayName="onLoaderError",b.get(o)(g).then(h,i),e.promise};if(k&&(f=b.get(k),!f.get||!f.put))throw new Error("Couldn't use storage '"+k+"', missing get() or put() method!");if(w.length){var S=function(a){var c=b.get(a);c.setLocale(e||i),D[c.getInterpolationIdentifier()]=c};S.displayName="interpolationFactoryAdder",angular.forEach(w,S)}var T=function(a){var b=d.defer();if(Object.prototype.hasOwnProperty.call(t,a))b.resolve(t[a]);else if(F[a]){var c=function(a){K(a.key,a.table),b.resolve(a.table)};c.displayName="translationTableResolver",F[a].then(c,b.reject)}else b.reject();return b.promise},U=function(a,b,c,e){var f=d.defer(),g=function(d){if(Object.prototype.hasOwnProperty.call(d,b)){e.setLocale(a);var g=d[b];"@:"===g.substr(0,2)?U(a,g.substr(2),c,e).then(f.resolve,f.reject):f.resolve(e.interpolate(d[b],c)),e.setLocale(i)}else f.reject()};return g.displayName="fallbackTranslationResolver",T(a).then(g,f.reject),f.promise},V=function(a,b,c,d){var e,f=t[a];if(f&&Object.prototype.hasOwnProperty.call(f,b)){if(d.setLocale(a),e=d.interpolate(f[b],c),"@:"===e.substr(0,2))return V(a,e.substr(2),c,d);d.setLocale(i)}return e},W=function(a,c){if(m){var d=b.get(m)(a,i,c);return void 0!==d?d:a}return a},X=function(a,b,c,e,f){var h=d.defer();if(a0?u:l,a,b,c,d)},$=function(a,b,c){return Y(u>0?u:l,a,b,c)},_=function(a,b,c,e){var f=d.defer(),h=i?t[i]:t,j=c?D[c]:z;if(h&&Object.prototype.hasOwnProperty.call(h,a)){var k=h[a];"@:"===k.substr(0,2)?G(k.substr(2),b,c,e).then(f.resolve,f.reject):f.resolve(j.interpolate(k,b))}else{var l;m&&!C&&(l=W(a,b)),i&&g&&g.length?Z(a,b,j,e).then(function(a){f.resolve(a)},function(a){f.reject(P(a))}):m&&!C&&l?f.resolve(e?e:l):e?f.resolve(e):f.reject(P(a))}return f.promise},ab=function(a,b,c){var d,e=i?t[i]:t,f=z;if(D&&Object.prototype.hasOwnProperty.call(D,c)&&(f=D[c]),e&&Object.prototype.hasOwnProperty.call(e,a)){var h=e[a];d="@:"===h.substr(0,2)?ab(h.substr(2),b,c):f.interpolate(h,b)}else{var j;m&&!C&&(j=W(a,b)),i&&g&&g.length?(l=0,d=$(a,b,f)):d=m&&!C&&j?j:P(a)}return d},bb=function(a){j===a&&(j=void 0),F[a]=void 0};if(G.preferredLanguage=function(a){return a&&M(a),e},G.cloakClassName=function(){return x},G.fallbackLanguage=function(a){if(void 0!==a&&null!==a){if(N(a),o&&g&&g.length)for(var b=0,c=g.length;c>b;b++)F[g[b]]||(F[g[b]]=R(g[b]));G.use(G.use())}return h?g[0]:g},G.useFallbackLanguage=function(a){if(void 0!==a&&null!==a)if(a){var b=H(g,a);b>-1&&(u=b)}else u=0},G.proposedLanguage=function(){return j},G.storage=function(){return f},G.use=function(a){if(!a)return i;var b=d.defer();c.$emit("$translateChangeStart",{language:a});var e=J(a);return e&&(a=e),t[a]||!o||F[a]?j===a&&F[a]?F[a].then(function(a){return b.resolve(a.key),a},function(a){return b.reject(a),d.reject(a)}):(b.resolve(a),Q(a)):(j=a,F[a]=R(a).then(function(a){return K(a.key,a.table),b.resolve(a.key),Q(a.key),a},function(a){return c.$emit("$translateChangeError",{language:a}),b.reject(a),c.$emit("$translateChangeEnd",{language:a}),d.reject(a)}),F[a]["finally"](function(){bb(a)})),b.promise},G.storageKey=function(){return O()},G.isPostCompilingEnabled=function(){return y},G.refresh=function(a){function b(){f.resolve(),c.$emit("$translateRefreshEnd",{language:a})}function e(){f.reject(),c.$emit("$translateRefreshEnd",{language:a})}if(!o)throw new Error("Couldn't refresh translation table, no loader registered!");var f=d.defer();if(c.$emit("$translateRefreshStart",{language:a}),a)if(t[a]){var h=function(c){K(c.key,c.table),a===i&&Q(i),b()};h.displayName="refreshPostProcessor",R(a).then(h,e)}else e();else{var j=[],k={};if(g&&g.length)for(var l=0,m=g.length;m>l;l++)j.push(R(g[l])),k[g[l]]=!0;i&&!k[i]&&j.push(R(i));var n=function(a){t={},angular.forEach(a,function(a){K(a.key,a.table)}),i&&Q(i),b()};n.displayName="refreshPostProcessor",d.all(j).then(n)}return f.promise},G.instant=function(a,b,c){if(null===a||angular.isUndefined(a))return a;if(angular.isArray(a)){for(var d={},f=0,h=a.length;h>f;f++)d[a[f]]=G.instant(a[f],b,c);return d}if(angular.isString(a)&&a.length<1)return a;a&&(a=I.apply(a));var j,k=[];e&&k.push(e),i&&k.push(i),g&&g.length&&(k=k.concat(g));for(var l=0,n=k.length;n>l;l++){var o=k[l];if(t[o]&&("undefined"!=typeof t[o][a]?j=ab(a,b,c):(q||r)&&(j=P(a))),"undefined"!=typeof j)break}return j||""===j||(j=z.interpolate(a,b),m&&!C&&(j=W(a,b))),j},G.versionInfo=function(){return E},G.loaderCache=function(){return s},G.directivePriority=function(){return A},G.statefulFilter=function(){return B},o&&(angular.equals(t,{})&&G.use(G.use()),g&&g.length))for(var cb=function(a){return K(a.key,a.table),c.$emit("$translateChangeEnd",{language:a.key}),a},db=0,eb=g.length;eb>db;db++){var fb=g[db];t[fb]||(F[fb]=R(fb).then(cb))}return G}]}function d(a,b){"use strict";var c,d={},e="default";return d.setLocale=function(a){c=a},d.getInterpolationIdentifier=function(){return e},d.useSanitizeValueStrategy=function(a){return b.useStrategy(a),this},d.interpolate=function(c,d){d=d||{},d=b.sanitize(d,"params");var e=a(c)(d);return e=b.sanitize(e,"text")},d}function e(a,b,c,d,e,f){"use strict";var g=function(){return this.replace(/^\s+|\s+$/g,"")};return{restrict:"AE",scope:!0,priority:a.directivePriority(),compile:function(b,h){var i=h.translateValues?h.translateValues:void 0,j=h.translateInterpolation?h.translateInterpolation:void 0,k=b[0].outerHTML.match(/translate-value-+/i),l="^(.*)("+c.startSymbol()+".*"+c.endSymbol()+")(.*)",m="^(.*)"+c.startSymbol()+"(.*)"+c.endSymbol()+"(.*)";return function(b,n,o){b.interpolateParams={},b.preText="",b.postText="";var p={},q=function(a,c,d){if(c.translateValues&&angular.extend(a,e(c.translateValues)(b.$parent)),k)for(var f in d)if(Object.prototype.hasOwnProperty.call(c,f)&&"translateValue"===f.substr(0,14)&&"translateValues"!==f){var g=angular.lowercase(f.substr(14,1))+f.substr(15);a[g]=d[f]}},r=function(a){if(angular.isFunction(r._unwatchOld)&&(r._unwatchOld(),r._unwatchOld=void 0),angular.equals(a,"")||!angular.isDefined(a)){var d=g.apply(n.text()).match(l);if(angular.isArray(d)){b.preText=d[1],b.postText=d[3],p.translate=c(d[2])(b.$parent);var e=n.text().match(m);angular.isArray(e)&&e[2]&&e[2].length&&(r._unwatchOld=b.$watch(e[2],function(a){p.translate=a,x()}))}else p.translate=n.text().replace(/^\s+|\s+$/g,"")}else p.translate=a;x()},s=function(a){o.$observe(a,function(b){p[a]=b,x()})};q(b.interpolateParams,o,h);var t=!0;o.$observe("translate",function(a){"undefined"==typeof a?r(""):""===a&&t||(p.translate=a,x()),t=!1});for(var u in o)o.hasOwnProperty(u)&&"translateAttr"===u.substr(0,13)&&s(u);if(o.$observe("translateDefault",function(a){b.defaultText=a}),i&&o.$observe("translateValues",function(a){a&&b.$parent.$watch(function(){angular.extend(b.interpolateParams,e(a)(b.$parent))})}),k){var v=function(a){o.$observe(a,function(c){var d=angular.lowercase(a.substr(14,1))+a.substr(15);b.interpolateParams[d]=c})};for(var w in o)Object.prototype.hasOwnProperty.call(o,w)&&"translateValue"===w.substr(0,14)&&"translateValues"!==w&&v(w)}var x=function(){for(var a in p)p.hasOwnProperty(a)&&void 0!==p[a]&&y(a,p[a],b,b.interpolateParams,b.defaultText)},y=function(b,c,d,e,f){c?a(c,e,j,f).then(function(a){z(a,d,!0,b)},function(a){z(a,d,!1,b)}):z(c,d,!1,b)},z=function(b,c,e,f){if("translate"===f){e||"undefined"==typeof c.defaultText||(b=c.defaultText),n.html(c.preText+b+c.postText);var g=a.isPostCompilingEnabled(),i="undefined"!=typeof h.translateCompile,j=i&&"false"!==h.translateCompile;(g&&!i||j)&&d(n.contents())(c)}else{e||"undefined"==typeof c.defaultText||(b=c.defaultText);var k=o.$attr[f];"data-"===k.substr(0,5)&&(k=k.substr(5)),k=k.substr(15),n.attr(k,b)}};(i||k||o.translateDefault)&&b.$watch("interpolateParams",x,!0);var A=f.$on("$translateChangeSuccess",x);n.text().length&&r(o.translate?o.translate:""),x(),b.$on("$destroy",A)}}}}function f(a,b){"use strict";return{compile:function(c){var d=function(){c.addClass(b.cloakClassName())},e=function(){c.removeClass(b.cloakClassName())},f=a.$on("$translateChangeEnd",function(){e(),f(),f=null});return d(),function(a,c,f){f.translateCloak&&f.translateCloak.length&&f.$observe("translateCloak",function(a){b(a).then(e,d)})}}}}function g(a,b){"use strict";var c=function(c,d,e){return angular.isObject(d)||(d=a(d)(this)),b.instant(c,d,e)};return b.statefulFilter()&&(c.$stateful=!0),c}return angular.module("pascalprecht.translate",["ng"]).run(a),a.$inject=["$translate"],a.displayName="runTranslate",angular.module("pascalprecht.translate").provider("$translateSanitization",b),angular.module("pascalprecht.translate").constant("pascalprechtTranslateOverrider",{}).provider("$translate",c),c.$inject=["$STORAGE_KEY","$windowProvider","$translateSanitizationProvider","pascalprechtTranslateOverrider"],c.displayName="displayName",angular.module("pascalprecht.translate").factory("$translateDefaultInterpolation",d),d.$inject=["$interpolate","$translateSanitization"],d.displayName="$translateDefaultInterpolation",angular.module("pascalprecht.translate").constant("$STORAGE_KEY","NG_TRANSLATE_LANG_KEY"),angular.module("pascalprecht.translate").directive("translate",e),e.$inject=["$translate","$q","$interpolate","$compile","$parse","$rootScope"],e.displayName="translateDirective",angular.module("pascalprecht.translate").directive("translateCloak",f),f.$inject=["$rootScope","$translate"],f.displayName="translateCloakDirective",angular.module("pascalprecht.translate").filter("translate",g),g.$inject=["$parse","$translate"],g.displayName="translateFilterFactory","pascalprecht.translate"}); \ No newline at end of file +!function(a,b){"function"==typeof define&&define.amd?define([],function(){return b()}):"object"==typeof module&&module.exports?module.exports=b():b()}(0,function(){function a(a){"use strict";var b=a.storageKey(),c=a.storage(),d=function(){var d=a.preferredLanguage();angular.isString(d)?a.use(d):c.put(b,a.use())};d.displayName="fallbackFromIncorrectStorageValue",c?c.get(b)?a.use(c.get(b)).catch(d):d():angular.isString(a.preferredLanguage())&&a.use(a.preferredLanguage())}function b(){"use strict";var a,b,c,d=null,e=!1,f=!1;c={sanitize:function(a,b){return"text"===b&&(a=h(a)),a},escape:function(a,b){return"text"===b&&(a=g(a)),a},sanitizeParameters:function(a,b){return"params"===b&&(a=j(a,h)),a},escapeParameters:function(a,b){return"params"===b&&(a=j(a,g)),a},sce:function(a,b,c){return"text"===b?a=i(a):"params"===b&&"filter"!==c&&(a=j(a,g)),a},sceParameters:function(a,b){return"params"===b&&(a=j(a,i)),a}},c.escaped=c.escapeParameters,this.addStrategy=function(a,b){return c[a]=b,this},this.removeStrategy=function(a){return delete c[a],this},this.useStrategy=function(a){return e=!0,d=a,this},this.$get=["$injector","$log",function(g,h){var i={},j=function(a,b,d,e){return angular.forEach(e,function(e){if(angular.isFunction(e))a=e(a,b,d);else if(angular.isFunction(c[e]))a=c[e](a,b,d);else{if(!angular.isString(c[e]))throw new Error("pascalprecht.translate.$translateSanitization: Unknown sanitization strategy: '"+e+"'");if(!i[c[e]])try{i[c[e]]=g.get(c[e])}catch(a){throw i[c[e]]=function(){},new Error("pascalprecht.translate.$translateSanitization: Unknown sanitization strategy: '"+e+"'")}a=i[c[e]](a,b,d)}}),a},k=function(){e||f||(h.warn("pascalprecht.translate.$translateSanitization: No sanitization strategy has been configured. This can have serious security implications. See http://angular-translate.github.io/docs/#/guide/19_security for details."),f=!0)};return g.has("$sanitize")&&(a=g.get("$sanitize")),g.has("$sce")&&(b=g.get("$sce")),{useStrategy:function(a){return function(b){a.useStrategy(b)}}(this),sanitize:function(a,b,c,e){if(d||k(),c||null===c||(c=d),!c)return a;e||(e="service");var f=angular.isArray(c)?c:[c];return j(a,b,e,f)}}}];var g=function(a){var b=angular.element("
");return b.text(a),b.html()},h=function(b){if(!a)throw new Error("pascalprecht.translate.$translateSanitization: Error cannot find $sanitize service. Either include the ngSanitize module (https://docs.angularjs.org/api/ngSanitize) or use a sanitization strategy which does not depend on $sanitize, such as 'escape'.");return a(b)},i=function(a){if(!b)throw new Error("pascalprecht.translate.$translateSanitization: Error cannot find $sce service.");return b.trustAsHtml(a)},j=function(a,b,c){if(angular.isDate(a))return a;if(angular.isObject(a)){var d=angular.isArray(a)?[]:{};if(c){if(c.indexOf(a)>-1)throw new Error("pascalprecht.translate.$translateSanitization: Error cannot interpolate parameter due recursive object")}else c=[];return c.push(a),angular.forEach(a,function(a,e){angular.isFunction(a)||(d[e]=j(a,b,c))}),c.splice(-1,1),d}return angular.isNumber(a)?a:angular.isUndefined(a)||null===a?a:b(a)}}function c(a,b,c,d){"use strict";var e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u={},v=[],w=a,x=[],y="translate-cloak",z=!1,A=!1,B=".",C=!1,D=!1,E=0,F=!0,G="default",H={default:function(a){return(a||"").split("-").join("_")},java:function(a){var b=(a||"").split("-").join("_"),c=b.split("_");return c.length>1?c[0].toLowerCase()+"_"+c[1].toUpperCase():b},bcp47:function(a){var b=(a||"").split("_").join("-"),c=b.split("-");return c.length>1?c[0].toLowerCase()+"-"+c[1].toUpperCase():b},"iso639-1":function(a){return(a||"").split("_").join("-").split("-")[0].toLowerCase()}},I=function(){if(angular.isFunction(d.getLocale))return d.getLocale();var a,c,e=b.$get().navigator,f=["language","browserLanguage","systemLanguage","userLanguage"];if(angular.isArray(e.languages))for(a=0;a-1)return a;if(f){var g;for(var h in f)if(f.hasOwnProperty(h)){var i=!1,j=Object.prototype.hasOwnProperty.call(f,h)&&angular.lowercase(h)===angular.lowercase(a);if("*"===h.slice(-1)&&(i=h.slice(0,-1)===a.slice(0,h.length-1)),(j||i)&&(g=f[h],K(b,angular.lowercase(g))>-1))return g}}var k=a.split("_");return k.length>1&&K(b,angular.lowercase(k[0]))>-1?k[0]:void 0}},N=function(a,b){if(!a&&!b)return u;if(a&&!b){if(angular.isString(a))return u[a]}else angular.isObject(u[a])||(u[a]={}),angular.extend(u[a],O(b));return this};this.translations=N,this.cloakClassName=function(a){return a?(y=a,this):y},this.nestedObjectDelimeter=function(a){return a?(B=a,this):B};var O=function(a,b,c,d){var e,f,g,h;b||(b=[]),c||(c={});for(e in a)Object.prototype.hasOwnProperty.call(a,e)&&(h=a[e],angular.isObject(h)?O(h,b.concat(e),c,e):(f=b.length?""+b.join(B)+B+e:e,b.length&&e===d&&(g=""+b.join(B),c[g]="@:"+f),c[f]=h));return c};O.displayName="flatObject",this.addInterpolation=function(a){return x.push(a),this},this.useMessageFormatInterpolation=function(){return this.useInterpolation("$translateMessageFormatInterpolation")},this.useInterpolation=function(a){return n=a,this},this.useSanitizeValueStrategy=function(a){return c.useStrategy(a),this},this.preferredLanguage=function(a){return a?(P(a),this):e};var P=function(a){return a&&(e=a),e};this.translationNotFoundIndicator=function(a){return this.translationNotFoundIndicatorLeft(a),this.translationNotFoundIndicatorRight(a),this},this.translationNotFoundIndicatorLeft=function(a){return a?(q=a,this):q},this.translationNotFoundIndicatorRight=function(a){return a?(r=a,this):r},this.fallbackLanguage=function(a){return Q(a),this};var Q=function(a){return a?(angular.isString(a)?(h=!0,g=[a]):angular.isArray(a)&&(h=!1,g=a),angular.isString(e)&&K(g,e)<0&&g.push(e),this):h?g[0]:g};this.use=function(a){if(a){if(!u[a]&&!o)throw new Error("$translateProvider couldn't find translationTable for langKey: '"+a+"'");return i=a,this}return i},this.resolveClientLocale=function(){return J()};var R=function(a){return a?(w=a,this):l?l+w:w};this.storageKey=R,this.useUrlLoader=function(a,b){return this.useLoader("$translateUrlLoader",angular.extend({url:a},b))},this.useStaticFilesLoader=function(a){return this.useLoader("$translateStaticFilesLoader",a)},this.useLoader=function(a,b){return o=a,p=b||{},this},this.useLocalStorage=function(){return this.useStorage("$translateLocalStorage")},this.useCookieStorage=function(){return this.useStorage("$translateCookieStorage")},this.useStorage=function(a){return k=a,this},this.storagePrefix=function(a){return a?(l=a,this):a},this.useMissingTranslationHandlerLog=function(){return this.useMissingTranslationHandler("$translateMissingTranslationHandlerLog")},this.useMissingTranslationHandler=function(a){return m=a,this},this.usePostCompiling=function(a){return z=!!a,this},this.forceAsyncReload=function(a){return A=!!a,this},this.uniformLanguageTag=function(a){return a?angular.isString(a)&&(a={standard:a}):a={},G=a.standard,this},this.determinePreferredLanguage=function(a){var b=a&&angular.isFunction(a)?a():J();return e=v.length?M(b)||b:b,this},this.registerAvailableLanguageKeys=function(a,b){return a?(v=a,b&&(f=b),this):v},this.useLoaderCache=function(a){return!1===a?s=void 0:!0===a?s=!0:void 0===a?s="$translationCache":a&&(s=a),this},this.directivePriority=function(a){return void 0===a?E:(E=a,this)},this.statefulFilter=function(a){return void 0===a?F:(F=a,this)},this.postProcess=function(a){return t=a||void 0,this},this.keepContent=function(a){return D=!!a,this},this.$get=["$log","$injector","$rootScope","$q",function(a,b,c,d){var f,l,G,H=b.get(n||"$translateDefaultInterpolation"),I=!1,S={},T={},U=function(a,b,c,h,j){!i&&e&&(i=e);var m=j&&j!==i?M(j)||j:i;if(j&&ja(j),angular.isArray(a)){return function(a){for(var e={},f=[],g=0,i=a.length;g0?G:l,a,b,c,d,e)},ea=function(a,b,c,d){return ca(G>0?G:l,a,b,c,d)},fa=function(a,b,c,e,f,h){var i=d.defer(),j=f?u[f]:u,k=c?S[c]:H;if(j&&Object.prototype.hasOwnProperty.call(j,a)&&null!==j[a]){var l=j[a];if("@:"===l.substr(0,2))U(l.substr(2),b,c,e,f).then(i.resolve,i.reject);else{var n=k.interpolate(l,b,"service",h,a);n=ia(a,l,n,b,f),i.resolve(n)}}else{var o;m&&!I&&(o=aa(a,b,e)),f&&g&&g.length?da(a,b,k,e,h).then(function(a){i.resolve(a)},function(a){i.reject(V(a))}):m&&!I&&o?e?i.resolve(e):i.resolve(o):e?i.resolve(e):i.reject(V(a))}return i.promise},ga=function(a,b,c,d,e){var f,h=d?u[d]:u,i=H;if(S&&Object.prototype.hasOwnProperty.call(S,c)&&(i=S[c]),h&&Object.prototype.hasOwnProperty.call(h,a)&&null!==h[a]){var j=h[a];"@:"===j.substr(0,2)?f=ga(j.substr(2),b,c,d,e):(f=i.interpolate(j,b,"filter",e,a),f=ia(a,j,f,b,d,e))}else{var k;m&&!I&&(k=aa(a,b,e)),d&&g&&g.length?(l=0,f=ea(a,b,i,e)):f=m&&!I&&k?k:V(a)}return f},ha=function(a){j===a&&(j=void 0),T[a]=void 0},ia=function(a,c,d,e,f,g){var h=t;return h&&("string"==typeof h&&(h=b.get(h)),h)?h(a,c,d,e,f,g):d},ja=function(a){u[a]||!o||T[a]||(T[a]=X(a).then(function(a){return N(a.key,a.table),a}))};U.preferredLanguage=function(a){return a&&P(a),e},U.cloakClassName=function(){return y},U.nestedObjectDelimeter=function(){return B},U.fallbackLanguage=function(a){if(void 0!==a&&null!==a){if(Q(a),o&&g&&g.length)for(var b=0,c=g.length;b-1&&(G=b)}else G=0},U.proposedLanguage=function(){return j},U.storage=function(){return f},U.negotiateLocale=M,U.use=function(a){if(!a)return i;var b=d.defer();b.promise.then(null,angular.noop),c.$emit("$translateChangeStart",{language:a});var e=M(a);return v.length>0&&!e?d.reject(a):(e&&(a=e),j=a,!A&&u[a]||!o||T[a]?T[a]?T[a].then(function(a){return j===a.key&&W(a.key),b.resolve(a.key),a},function(a){return!i&&g&&g.length>0&&g[0]!==a?U.use(g[0]).then(b.resolve,b.reject):b.reject(a)}):(b.resolve(a),W(a)):(T[a]=X(a).then(function(c){return N(c.key,c.table),b.resolve(c.key),j===a&&W(c.key),c},function(a){return c.$emit("$translateChangeError",{language:a}),b.reject(a),c.$emit("$translateChangeEnd",{language:a}),d.reject(a)}),T[a].finally(function(){ha(a)}).catch(angular.noop)),b.promise)},U.resolveClientLocale=function(){return J()},U.storageKey=function(){return R()},U.isPostCompilingEnabled=function(){return z},U.isForceAsyncReloadEnabled=function(){return A},U.isKeepContent=function(){return D},U.refresh=function(a){function b(a){var b=X(a);return T[a]=b,b.then(function(b){u[a]={},N(a,b.table),f[a]=!0},angular.noop),b}if(!o)throw new Error("Couldn't refresh translation table, no loader registered!");c.$emit("$translateRefreshStart",{language:a});var e=d.defer(),f={};if(e.promise.then(function(){for(var a in u)u.hasOwnProperty(a)&&(a in f||delete u[a]);i&&W(i)},angular.noop).finally(function(){c.$emit("$translateRefreshEnd",{language:a})}),a)u[a]?b(a).then(e.resolve,e.reject):e.reject();else{var h=g&&g.slice()||[];i&&-1===h.indexOf(i)&&h.push(i),d.all(h.map(b)).then(e.resolve,e.reject)}return e.promise},U.instant=function(a,b,c,d,f){var h=d&&d!==i?M(d)||d:i;if(null===a||angular.isUndefined(a))return a;if(d&&ja(d),angular.isArray(a)){for(var j={},k=0,l=a.length;k0?v:null},U.getTranslationTable=function(a){return a=a||U.use(),a&&u[a]?angular.copy(u[a]):null};var la=c.$on("$translateReady",function(){ka.resolve(),la(),la=null}),ma=c.$on("$translateChangeEnd",function(){ka.resolve(),ma(),ma=null});if(o){if(angular.equals(u,{})&&U.use()&&U.use(U.use()),g&&g.length)for(var na=function(a){return N(a.key,a.table),c.$emit("$translateChangeEnd",{language:a.key}),a},oa=0,pa=g.length;oa13&&function(a){p.$observe(a,function(b){q[a]=b,v()})}(t);if(p.$observe("translateDefault",function(a){h.defaultText=a,v()}),j&&p.$observe("translateValues",function(a){a&&h.$parent.$watch(function(){angular.extend(h.interpolateParams,d(a)(h.$parent))})}),l){for(var u in p)Object.prototype.hasOwnProperty.call(p,u)&&"translateValue"===u.substr(0,14)&&"translateValues"!==u&&function(a){p.$observe(a,function(b){var c=angular.lowercase(a.substr(14,1))+a.substr(15);h.interpolateParams[c]=b})}(u)}var v=function(){for(var a in q)q.hasOwnProperty(a)&&void 0!==q[a]&&w(a,q[a],h,h.interpolateParams,h.defaultText,h.translateNamespace)},w=function(b,c,d,e,f,g){c?(g&&"."===c.charAt(0)&&(c=g+c),a(c,e,k,f,d.translateLanguage).then(function(a){x(a,d,!0,b)},function(a){x(a,d,!1,b)})):x(c,d,!1,b)},x=function(b,d,e,f){if(e||void 0!==d.defaultText&&(b=d.defaultText),"translate"===f){(e||!e&&!a.isKeepContent()&&void 0===p.translateKeepContent)&&o.empty().append(d.preText+b+d.postText);var g=a.isPostCompilingEnabled(),h=void 0!==i.translateCompile,j=h&&"false"!==i.translateCompile;(g&&!h||j)&&c(o.contents())(d)}else{var k=p.$attr[f];"data-"===k.substr(0,5)&&(k=k.substr(5)),k=k.substr(15),o.attr(k,b)}};(j||l||p.translateDefault)&&h.$watch("interpolateParams",v,!0),h.$on("translateLanguageChanged",v);var y=e.$on("$translateChangeSuccess",v);o.text().length?r(p.translate?p.translate:""):p.translate&&r(p.translate),v(),h.$on("$destroy",y)}}}}function f(a){"use strict";return a.translateNamespace?a.translateNamespace:a.$parent?f(a.$parent):void 0}function g(a,b){"use strict";return{restrict:"A",priority:a.directivePriority(),link:function(c,d,e){var f,g,i={},j=function(){angular.forEach(f,function(b,f){b&&(i[f]=!0,c.translateNamespace&&"."===b.charAt(0)&&(b=c.translateNamespace+b),a(b,g,e.translateInterpolation,void 0,c.translateLanguage).then(function(a){d.attr(f,a)},function(a){d.attr(f,a)}))}),angular.forEach(i,function(a,b){f[b]||(d.removeAttr(b),delete i[b])})};h(c,e.translateAttr,function(a){f=a},j),h(c,e.translateValues,function(a){g=a},j),e.translateValues&&c.$watch(e.translateValues,j,!0),c.$on("translateLanguageChanged",j);var k=b.$on("$translateChangeSuccess",j);j(),c.$on("$destroy",k)}}}function h(a,b,c,d){"use strict";b&&("::"===b.substr(0,2)?b=b.substr(2):a.$watch(b,function(a){c(a),d()},!0),c(a.$eval(b)))}function i(a,b){"use strict";return{compile:function(c){var d=function(b){b.addClass(a.cloakClassName())},e=function(b){b.removeClass(a.cloakClassName())};return d(c),function(c,f,g){var h=e.bind(this,f),i=d.bind(this,f);g.translateCloak&&g.translateCloak.length?(g.$observe("translateCloak",function(b){a(b).then(h,i)}),b.$on("$translateChangeSuccess",function(){a(g.translateCloak).then(h,i)})):a.onReady(h)}}}}function j(){"use strict";return{restrict:"A",scope:!0,compile:function(){return{pre:function(a,b,c){a.translateNamespace=f(a),a.translateNamespace&&"."===c.translateNamespace.charAt(0)?a.translateNamespace+=c.translateNamespace:a.translateNamespace=c.translateNamespace}}}}}function f(a){"use strict";return a.translateNamespace?a.translateNamespace:a.$parent?f(a.$parent):void 0}function k(){"use strict";return{restrict:"A",scope:!0,compile:function(){return function(a,b,c){c.$observe("translateLanguage",function(b){a.translateLanguage=b}),a.$watch("translateLanguage",function(){a.$broadcast("translateLanguageChanged")})}}}}function l(a,b){"use strict";var c=function(c,d,e,f){if(!angular.isObject(d)){var g=this||{__SCOPE_IS_NOT_AVAILABLE:"More info at https://github.com/angular/angular.js/commit/8863b9d04c722b278fa93c5d66ad1e578ad6eb1f"};d=a(d)(g)}return b.instant(c,d,e,f)};return b.statefulFilter()&&(c.$stateful=!0),c}function m(a){"use strict";return a("translations")}return a.$inject=["$translate"],c.$inject=["$STORAGE_KEY","$windowProvider","$translateSanitizationProvider","pascalprechtTranslateOverrider"],d.$inject=["$interpolate","$translateSanitization"],e.$inject=["$translate","$interpolate","$compile","$parse","$rootScope"],g.$inject=["$translate","$rootScope"],i.$inject=["$translate","$rootScope"],l.$inject=["$parse","$translate"],m.$inject=["$cacheFactory"],angular.module("pascalprecht.translate",["ng"]).run(a),a.displayName="runTranslate",angular.module("pascalprecht.translate").provider("$translateSanitization",b),angular.module("pascalprecht.translate").constant("pascalprechtTranslateOverrider",{}).provider("$translate",c),c.displayName="displayName",angular.module("pascalprecht.translate").factory("$translateDefaultInterpolation",d),d.displayName="$translateDefaultInterpolation",angular.module("pascalprecht.translate").constant("$STORAGE_KEY","NG_TRANSLATE_LANG_KEY"),angular.module("pascalprecht.translate").directive("translate",e),e.displayName="translateDirective",angular.module("pascalprecht.translate").directive("translateAttr",g),g.displayName="translateAttrDirective",angular.module("pascalprecht.translate").directive("translateCloak",i),i.displayName="translateCloakDirective",angular.module("pascalprecht.translate").directive("translateNamespace",j),j.displayName="translateNamespaceDirective",angular.module("pascalprecht.translate").directive("translateLanguage",k),k.displayName="translateLanguageDirective",angular.module("pascalprecht.translate").filter("translate",l),l.displayName="translateFilterFactory",angular.module("pascalprecht.translate").factory("$translationCache",m),m.displayName="$translationCache","pascalprecht.translate"}); \ No newline at end of file