diff --git a/guacamole/src/main/webapp/app/index/config/indexTranslationConfig.js b/guacamole/src/main/webapp/app/index/config/indexTranslationConfig.js index 3bc7543a2..dbea322d2 100644 --- a/guacamole/src/main/webapp/app/index/config/indexTranslationConfig.js +++ b/guacamole/src/main/webapp/app/index/config/indexTranslationConfig.js @@ -36,6 +36,9 @@ angular.module('index').config(['$injector', function($injector) { $translateProvider.fallbackLanguage(fallbackLanguages); $translateProvider.preferredLanguage(preferenceServiceProvider.preferences.language); + // Escape any HTML in translation strings + $translateProvider.useSanitizeValueStrategy('escape'); + // Load translations via translationLoader service $translateProvider.useLoader('translationLoader', { fallbackLanguages : fallbackLanguages diff --git a/guacamole/src/main/webapp/app/login/directives/login.js b/guacamole/src/main/webapp/app/login/directives/login.js index 0c836c05a..089afeea5 100644 --- a/guacamole/src/main/webapp/app/login/directives/login.js +++ b/guacamole/src/main/webapp/app/login/directives/login.js @@ -77,7 +77,7 @@ angular.module('login').directive('guacLogin', [function guacLogin() { * * @type String */ - $scope.loginError = false; + $scope.loginError = null; /** * All form values entered by the user, as parameter name/value pairs. diff --git a/guacamole/src/main/webapp/lib/plugins/angular-translate-interpolation-messageformat.js b/guacamole/src/main/webapp/lib/plugins/angular-translate-interpolation-messageformat.js index 2aa9470be..3c56e5224 100644 --- a/guacamole/src/main/webapp/lib/plugins/angular-translate-interpolation-messageformat.js +++ b/guacamole/src/main/webapp/lib/plugins/angular-translate-interpolation-messageformat.js @@ -1,62 +1,157 @@ /*! - * angular-translate - v2.2.0 - 2014-06-03 - * http://github.com/PascalPrecht/angular-translate - * Copyright (c) 2014 ; Licensed MIT + * angular-translate - v2.7.2 - 2015-06-01 + * http://github.com/angular-translate/angular-translate + * Copyright (c) 2015 ; Licensed MIT */ -angular.module('pascalprecht.translate').constant('TRANSLATE_MF_INTERPOLATION_CACHE', '$translateMessageFormatInterpolation').factory('$translateMessageFormatInterpolation', [ - '$cacheFactory', - 'TRANSLATE_MF_INTERPOLATION_CACHE', - function ($cacheFactory, TRANSLATE_MF_INTERPOLATION_CACHE) { - var $translateInterpolator = {}, $cache = $cacheFactory.get(TRANSLATE_MF_INTERPOLATION_CACHE), $mf = new MessageFormat(), $identifier = 'messageformat', $sanitizeValueStrategy = null, sanitizeValueStrategies = { - escaped: function (params) { - var result = {}; - for (var key in params) { - if (params.hasOwnProperty(key)) { - result[key] = angular.element('
').text(params[key]).html(); - } - } - return result; - } - }; - var sanitizeParams = function (params) { - var result; - if (angular.isFunction(sanitizeValueStrategies[$sanitizeValueStrategy])) { - result = sanitizeValueStrategies[$sanitizeValueStrategy](params); - } else { - result = params; - } - return result; - }; - if (!$cache) { - $cache = $cacheFactory(TRANSLATE_MF_INTERPOLATION_CACHE); - } - $cache.put('en', $mf); - $translateInterpolator.setLocale = function (locale) { - $mf = $cache.get(locale); - if (!$mf) { - $mf = new MessageFormat(locale); - $cache.put(locale, $mf); - } - }; - $translateInterpolator.getInterpolationIdentifier = function () { - return $identifier; - }; - $translateInterpolator.useSanitizeValueStrategy = function (value) { - $sanitizeValueStrategy = value; - return this; - }; - $translateInterpolator.interpolate = function (string, interpolateParams) { - interpolateParams = interpolateParams || {}; - if ($sanitizeValueStrategy) { - interpolateParams = sanitizeParams(interpolateParams); - } - var interpolatedText = $cache.get(string + angular.toJson(interpolateParams)); - if (!interpolatedText) { - interpolatedText = $mf.compile(string)(interpolateParams); - $cache.put(string + angular.toJson(interpolateParams), interpolatedText); - } - return interpolatedText; - }; - return $translateInterpolator; +(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 exports === 'object') { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like environments that support module.exports, + // like Node. + module.exports = factory(); + } else { + factory(); } -]); \ No newline at end of file +}(this, function () { + +angular.module('pascalprecht.translate') + +/** + * @ngdoc property + * @name pascalprecht.translate.TRANSLATE_MF_INTERPOLATION_CACHE + * @requires TRANSLATE_MF_INTERPOLATION_CACHE + * + * @description + * Uses MessageFormat.js to interpolate strings against some values. + */ +.constant('TRANSLATE_MF_INTERPOLATION_CACHE', '$translateMessageFormatInterpolation') + +/** + * @ngdoc object + * @name pascalprecht.translate.$translateMessageFormatInterpolation + * @requires pascalprecht.translate.TRANSLATE_MF_INTERPOLATION_CACHE + * + * @description + * Uses MessageFormat.js to interpolate strings against some values. + * + * Be aware to configure a proper sanitization strategy. + * + * See also: + * * {@link pascalprecht.translate.$translateSanitization} + * * {@link https://github.com/SlexAxton/messageformat.js} + * + * @return {object} $translateMessageFormatInterpolation Interpolator service + */ +.factory('$translateMessageFormatInterpolation', $translateMessageFormatInterpolation); + +function $translateMessageFormatInterpolation($translateSanitization, $cacheFactory, TRANSLATE_MF_INTERPOLATION_CACHE) { + + 'use strict'; + + var $translateInterpolator = {}, + $cache = $cacheFactory.get(TRANSLATE_MF_INTERPOLATION_CACHE), + // instantiate with default locale (which is 'en') + $mf = new MessageFormat('en'), + $identifier = 'messageformat'; + + if (!$cache) { + // create cache if it doesn't exist already + $cache = $cacheFactory(TRANSLATE_MF_INTERPOLATION_CACHE); + } + + $cache.put('en', $mf); + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateMessageFormatInterpolation#setLocale + * @methodOf pascalprecht.translate.$translateMessageFormatInterpolation + * + * @description + * Sets current locale (this is currently not use in this interpolation). + * + * @param {string} locale Language key or locale. + */ + $translateInterpolator.setLocale = function (locale) { + $mf = $cache.get(locale); + if (!$mf) { + $mf = new MessageFormat(locale); + $cache.put(locale, $mf); + } + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateMessageFormatInterpolation#getInterpolationIdentifier + * @methodOf pascalprecht.translate.$translateMessageFormatInterpolation + * + * @description + * Returns an identifier for this interpolation service. + * + * @returns {string} $identifier + */ + $translateInterpolator.getInterpolationIdentifier = function () { + return $identifier; + }; + + /** + * @deprecated will be removed in 3.0 + * @see {@link pascalprecht.translate.$translateSanitization} + */ + $translateInterpolator.useSanitizeValueStrategy = function (value) { + $translateSanitization.useStrategy(value); + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateMessageFormatInterpolation#interpolate + * @methodOf pascalprecht.translate.$translateMessageFormatInterpolation + * + * @description + * Interpolates given string agains given interpolate params using MessageFormat.js. + * + * @returns {string} interpolated string. + */ + $translateInterpolator.interpolate = function (string, interpolationParams) { + interpolationParams = interpolationParams || {}; + interpolationParams = $translateSanitization.sanitize(interpolationParams, 'params'); + + var interpolatedText = $cache.get(string + angular.toJson(interpolationParams)); + + // if given string wasn't interpolated yet, we do so now and never have to do it again + if (!interpolatedText) { + + // Ensure explicit type if possible + // MessageFormat checks the actual type (i.e. for amount based conditions) + for (var key in interpolationParams) { + if (interpolationParams.hasOwnProperty(key)) { + // ensure number + var number = parseInt(interpolationParams[key], 10); + if (angular.isNumber(number) && ('' + number) === interpolationParams[key]) { + interpolationParams[key] = number; + } + } + } + + interpolatedText = $mf.compile(string)(interpolationParams); + interpolatedText = $translateSanitization.sanitize(interpolatedText, 'text'); + + $cache.put(string + angular.toJson(interpolationParams), interpolatedText); + } + + return interpolatedText; + }; + + return $translateInterpolator; +} +$translateMessageFormatInterpolation.$inject = ['$translateSanitization', '$cacheFactory', 'TRANSLATE_MF_INTERPOLATION_CACHE']; + +$translateMessageFormatInterpolation.displayName = '$translateMessageFormatInterpolation'; +return 'pascalprecht.translate'; + +})); diff --git a/guacamole/src/main/webapp/lib/plugins/angular-translate-loader-static-files.js b/guacamole/src/main/webapp/lib/plugins/angular-translate-loader-static-files.js index 787838700..fa12f69db 100644 --- a/guacamole/src/main/webapp/lib/plugins/angular-translate-loader-static-files.js +++ b/guacamole/src/main/webapp/lib/plugins/angular-translate-loader-static-files.js @@ -1,31 +1,114 @@ /*! - * angular-translate - v2.2.0 - 2014-06-03 - * http://github.com/PascalPrecht/angular-translate - * Copyright (c) 2014 ; Licensed MIT + * angular-translate - v2.7.2 - 2015-06-01 + * http://github.com/angular-translate/angular-translate + * Copyright (c) 2015 ; Licensed MIT */ -angular.module('pascalprecht.translate').factory('$translateStaticFilesLoader', [ - '$q', - '$http', - function ($q, $http) { - return function (options) { - if (!options || (!angular.isString(options.prefix) || !angular.isString(options.suffix))) { - throw new Error('Couldn\'t load static files, no prefix or suffix specified!'); +(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 exports === 'object') { + // 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 () { + +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 and key. + */ +.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 deferred = $q.defer(); - $http({ + + $http(angular.extend({ url: [ - options.prefix, + file.prefix, options.key, - options.suffix + file.suffix ].join(''), method: 'GET', params: '' - }).success(function (data) { + }, options.$http)).success(function (data) { deferred.resolve(data); - }).error(function (data) { + }).error(function () { deferred.reject(options.key); }); + return deferred.promise; }; - } -]); \ No newline at end of file + + var deferred = $q.defer(), + 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 + })); + } + + $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]; + } + } + + deferred.resolve(mergedData); + }, function (data) { + deferred.reject(data); + }); + + return deferred.promise; + }; +} +$translateStaticFilesLoader.$inject = ['$q', '$http']; + +$translateStaticFilesLoader.displayName = '$translateStaticFilesLoader'; +return 'pascalprecht.translate'; + +})); diff --git a/guacamole/src/main/webapp/lib/plugins/angular-translate.js b/guacamole/src/main/webapp/lib/plugins/angular-translate.js index dbf3feb0f..e7183a0a9 100644 --- a/guacamole/src/main/webapp/lib/plugins/angular-translate.js +++ b/guacamole/src/main/webapp/lib/plugins/angular-translate.js @@ -1,883 +1,2904 @@ /*! - * angular-translate - v2.2.0 - 2014-06-03 - * http://github.com/PascalPrecht/angular-translate - * Copyright (c) 2014 ; Licensed MIT + * angular-translate - v2.7.2 - 2015-06-01 + * http://github.com/angular-translate/angular-translate + * Copyright (c) 2015 ; Licensed MIT */ -angular.module('pascalprecht.translate', ['ng']).run([ - '$translate', - function ($translate) { - var key = $translate.storageKey(), storage = $translate.storage(); - if (storage) { - if (!storage.get(key)) { - if (angular.isString($translate.preferredLanguage())) { - $translate.use($translate.preferredLanguage()); - } else { - storage.set(key, $translate.use()); - } - } else { - $translate.use(storage.get(key)); - } - } else if (angular.isString($translate.preferredLanguage())) { - $translate.use($translate.preferredLanguage()); - } +(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 exports === 'object') { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like environments that support module.exports, + // like Node. + module.exports = factory(); + } else { + factory(); } -]); -angular.module('pascalprecht.translate').provider('$translate', [ - '$STORAGE_KEY', - function ($STORAGE_KEY) { - var $translationTable = {}, $preferredLanguage, $availableLanguageKeys = [], $languageKeyAliases, $fallbackLanguage, $fallbackWasString, $uses, $nextLang, $storageFactory, $storageKey = $STORAGE_KEY, $storagePrefix, $missingTranslationHandlerFactory, $interpolationFactory, $interpolatorFactories = [], $interpolationSanitizationStrategy = false, $loaderFactory, $cloakClassName = 'translate-cloak', $loaderOptions, $notFoundIndicatorLeft, $notFoundIndicatorRight, $postCompilingEnabled = false, NESTED_OBJECT_DELIMITER = '.'; - var getLocale = function () { - var nav = window.navigator; - return (nav.language || nav.browserLanguage || nav.systemLanguage || nav.userLanguage || '').split('-').join('_'); +}(this, function () { + +/** + * @ngdoc overview + * @name pascalprecht.translate + * + * @description + * The main module which holds everything together. + */ +angular.module('pascalprecht.translate', ['ng']) + .run(runTranslate); + +function runTranslate($translate) { + + 'use strict'; + + var key = $translate.storageKey(), + storage = $translate.storage(); + + var fallbackFromIncorrectStorageValue = function () { + var preferred = $translate.preferredLanguage(); + if (angular.isString(preferred)) { + $translate.use(preferred); + // $translate.use() will also remember the language. + // So, we don't need to call storage.put() here. + } else { + storage.put(key, $translate.use()); + } + }; + + fallbackFromIncorrectStorageValue.displayName = 'fallbackFromIncorrectStorageValue'; + + if (storage) { + if (!storage.get(key)) { + fallbackFromIncorrectStorageValue(); + } else { + $translate.use(storage.get(key))['catch'](fallbackFromIncorrectStorageValue); + } + } else if (angular.isString($translate.preferredLanguage())) { + $translate.use($translate.preferredLanguage()); + } +} +runTranslate.$inject = ['$translate']; + +runTranslate.displayName = 'runTranslate'; + +/** + * @ngdoc object + * @name pascalprecht.translate.$translateSanitizationProvider + * + * @description + * + * Configurations for $translateSanitization + */ +angular.module('pascalprecht.translate').provider('$translateSanitization', $translateSanitizationProvider); + +function $translateSanitizationProvider () { + + 'use strict'; + + var $sanitize, + currentStrategy = null, // TODO change to either 'sanitize', 'escape' or ['sanitize', 'escapeParameters'] in 3.0. + hasConfiguredStrategy = false, + hasShownNoStrategyConfiguredWarning = false, + strategies; + + /** + * Definition of a sanitization strategy function + * @callback StrategyFunction + * @param {string|object} value - value to be sanitized (either a string or an interpolated value map) + * @param {string} mode - either 'text' for a string (translation) or 'params' for the interpolated params + * @return {string|object} + */ + + /** + * @ngdoc property + * @name strategies + * @propertyOf pascalprecht.translate.$translateSanitizationProvider + * + * @description + * Following strategies are built-in: + *
+ * // register translation table for language: 'de_DE'
+ * $translateProvider.translations('de_DE', {
+ * 'GREETING': 'Hallo Welt!'
+ * });
+ *
+ * // register another one
+ * $translateProvider.translations('en_US', {
+ * 'GREETING': 'Hello world!'
+ * });
+ *
+ *
+ * When registering multiple translation tables for for the same language key,
+ * the actual translation table gets extended. This allows you to define module
+ * specific translation which only get added, once a specific module is loaded in
+ * your app.
+ *
+ * Invoking this method with no arguments returns the translation table which was
+ * registered with no language key. Invoking it with a language key returns the
+ * related translation table.
+ *
+ * @param {string} key A language key.
+ * @param {object} translationTable A plain old JavaScript object that represents a translation table.
+ *
+ */
+ var translations = function (langKey, translationTable) {
+
+ if (!langKey && !translationTable) {
+ return $translationTable;
+ }
+
+ if (langKey && !translationTable) {
+ if (angular.isString(langKey)) {
+ return $translationTable[langKey];
+ }
+ } else {
+ if (!angular.isObject($translationTable[langKey])) {
+ $translationTable[langKey] = {};
+ }
+ angular.extend($translationTable[langKey], flatObject(translationTable));
+ }
+ return this;
+ };
+
+ this.translations = translations;
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#cloakClassName
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ *
+ * Let's you change the class name for `translate-cloak` directive.
+ * Default class name is `translate-cloak`.
+ *
+ * @param {string} name translate-cloak class name
+ */
+ this.cloakClassName = function (name) {
+ if (!name) {
+ return $cloakClassName;
+ }
+ $cloakClassName = name;
+ return this;
+ };
+
+ /**
+ * @name flatObject
+ * @private
+ *
+ * @description
+ * Flats an object. This function is used to flatten given translation data with
+ * namespaces, so they are later accessible via dot notation.
+ */
+ var flatObject = function (data, path, result, prevKey) {
+ var key, keyWithPath, keyWithShortPath, val;
+
+ if (!path) {
+ path = [];
+ }
+ if (!result) {
+ result = {};
+ }
+ for (key in data) {
+ if (!Object.prototype.hasOwnProperty.call(data, key)) {
+ continue;
+ }
+ val = data[key];
+ if (angular.isObject(val)) {
+ flatObject(val, path.concat(key), result, key);
+ } else {
+ keyWithPath = path.length ? ('' + path.join(NESTED_OBJECT_DELIMITER) + NESTED_OBJECT_DELIMITER + key) : key;
+ if(path.length && key === prevKey){
+ // Create shortcut path (foo.bar == foo.bar.bar)
+ keyWithShortPath = '' + path.join(NESTED_OBJECT_DELIMITER);
+ // Link it to original path
+ result[keyWithShortPath] = '@:' + keyWithPath;
+ }
+ result[keyWithPath] = val;
+ }
+ }
+ return result;
+ };
+ flatObject.displayName = 'flatObject';
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#addInterpolation
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Adds interpolation services to angular-translate, so it can manage them.
+ *
+ * @param {object} factory Interpolation service factory
+ */
+ this.addInterpolation = function (factory) {
+ $interpolatorFactories.push(factory);
+ return this;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#useMessageFormatInterpolation
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Tells angular-translate to use interpolation functionality of messageformat.js.
+ * This is useful when having high level pluralization and gender selection.
+ */
+ this.useMessageFormatInterpolation = function () {
+ return this.useInterpolation('$translateMessageFormatInterpolation');
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#useInterpolation
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Tells angular-translate which interpolation style to use as default, application-wide.
+ * Simply pass a factory/service name. The interpolation service has to implement
+ * the correct interface.
+ *
+ * @param {string} factory Interpolation service name.
+ */
+ this.useInterpolation = function (factory) {
+ $interpolationFactory = factory;
+ return this;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#useSanitizeStrategy
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Simply sets a sanitation strategy type.
+ *
+ * @param {string} value Strategy type.
+ */
+ this.useSanitizeValueStrategy = function (value) {
+ $translateSanitizationProvider.useStrategy(value);
+ return this;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#preferredLanguage
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Tells the module which of the registered translation tables to use for translation
+ * at initial startup by passing a language key. Similar to `$translateProvider#use`
+ * only that it says which language to **prefer**.
+ *
+ * @param {string} langKey A language key.
+ *
+ */
+ this.preferredLanguage = function(langKey) {
+ setupPreferredLanguage(langKey);
+ return this;
+
+ };
+ var setupPreferredLanguage = function (langKey) {
+ if (langKey) {
+ $preferredLanguage = langKey;
+ }
+ return $preferredLanguage;
+ };
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#translationNotFoundIndicator
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Sets an indicator which is used when a translation isn't found. E.g. when
+ * setting the indicator as 'X' and one tries to translate a translation id
+ * called `NOT_FOUND`, this will result in `X NOT_FOUND X`.
+ *
+ * Internally this methods sets a left indicator and a right indicator using
+ * `$translateProvider.translationNotFoundIndicatorLeft()` and
+ * `$translateProvider.translationNotFoundIndicatorRight()`.
+ *
+ * **Note**: These methods automatically add a whitespace between the indicators
+ * and the translation id.
+ *
+ * @param {string} indicator An indicator, could be any string.
+ */
+ this.translationNotFoundIndicator = function (indicator) {
+ this.translationNotFoundIndicatorLeft(indicator);
+ this.translationNotFoundIndicatorRight(indicator);
+ return this;
+ };
+
+ /**
+ * ngdoc function
+ * @name pascalprecht.translate.$translateProvider#translationNotFoundIndicatorLeft
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Sets an indicator which is used when a translation isn't found left to the
+ * translation id.
+ *
+ * @param {string} indicator An indicator.
+ */
+ this.translationNotFoundIndicatorLeft = function (indicator) {
+ if (!indicator) {
+ return $notFoundIndicatorLeft;
+ }
+ $notFoundIndicatorLeft = indicator;
+ return this;
+ };
+
+ /**
+ * ngdoc function
+ * @name pascalprecht.translate.$translateProvider#translationNotFoundIndicatorLeft
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Sets an indicator which is used when a translation isn't found right to the
+ * translation id.
+ *
+ * @param {string} indicator An indicator.
+ */
+ this.translationNotFoundIndicatorRight = function (indicator) {
+ if (!indicator) {
+ return $notFoundIndicatorRight;
+ }
+ $notFoundIndicatorRight = indicator;
+ return this;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#fallbackLanguage
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Tells the module which of the registered translation tables to use when missing translations
+ * at initial startup by passing a language key. Similar to `$translateProvider#use`
+ * only that it says which language to **fallback**.
+ *
+ * @param {string||array} langKey A language key.
+ *
+ */
+ this.fallbackLanguage = function (langKey) {
+ fallbackStack(langKey);
+ return this;
+ };
+
+ var fallbackStack = function (langKey) {
+ if (langKey) {
+ if (angular.isString(langKey)) {
+ $fallbackWasString = true;
+ $fallbackLanguage = [ langKey ];
+ } else if (angular.isArray(langKey)) {
+ $fallbackWasString = false;
+ $fallbackLanguage = langKey;
+ }
+ if (angular.isString($preferredLanguage) && indexOf($fallbackLanguage, $preferredLanguage) < 0) {
+ $fallbackLanguage.push($preferredLanguage);
+ }
+
+ return this;
+ } else {
+ if ($fallbackWasString) {
+ return $fallbackLanguage[0];
+ } else {
+ return $fallbackLanguage;
+ }
+ }
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#use
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Set which translation table to use for translation by given language key. When
+ * trying to 'use' a language which isn't provided, it'll throw an error.
+ *
+ * You actually don't have to use this method since `$translateProvider#preferredLanguage`
+ * does the job too.
+ *
+ * @param {string} langKey A language key.
+ */
+ this.use = function (langKey) {
+ if (langKey) {
+ if (!$translationTable[langKey] && (!$loaderFactory)) {
+ // only throw an error, when not loading translation data asynchronously
+ throw new Error('$translateProvider couldn\'t find translationTable for langKey: \'' + langKey + '\'');
+ }
+ $uses = langKey;
+ return this;
+ }
+ return $uses;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#storageKey
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Tells the module which key must represent the choosed language by a user in the storage.
+ *
+ * @param {string} key A key for the storage.
+ */
+ var storageKey = function(key) {
+ if (!key) {
+ if ($storagePrefix) {
+ return $storagePrefix + $storageKey;
+ }
+ return $storageKey;
+ }
+ $storageKey = key;
+ return this;
+ };
+
+ this.storageKey = storageKey;
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#useUrlLoader
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Tells angular-translate to use `$translateUrlLoader` extension service as loader.
+ *
+ * @param {string} url Url
+ * @param {Object=} options Optional configuration object
+ */
+ this.useUrlLoader = function (url, options) {
+ return this.useLoader('$translateUrlLoader', angular.extend({ url: url }, options));
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#useStaticFilesLoader
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Tells angular-translate to use `$translateStaticFilesLoader` extension service as loader.
+ *
+ * @param {Object=} options Optional configuration object
+ */
+ this.useStaticFilesLoader = function (options) {
+ return this.useLoader('$translateStaticFilesLoader', options);
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#useLoader
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Tells angular-translate to use any other service as loader.
+ *
+ * @param {string} loaderFactory Factory name to use
+ * @param {Object=} options Optional configuration object
+ */
+ this.useLoader = function (loaderFactory, options) {
+ $loaderFactory = loaderFactory;
+ $loaderOptions = options || {};
+ return this;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#useLocalStorage
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Tells angular-translate to use `$translateLocalStorage` service as storage layer.
+ *
+ */
+ this.useLocalStorage = function () {
+ return this.useStorage('$translateLocalStorage');
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#useCookieStorage
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Tells angular-translate to use `$translateCookieStorage` service as storage layer.
+ */
+ this.useCookieStorage = function () {
+ return this.useStorage('$translateCookieStorage');
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#useStorage
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Tells angular-translate to use custom service as storage layer.
+ */
+ this.useStorage = function (storageFactory) {
+ $storageFactory = storageFactory;
+ return this;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#storagePrefix
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Sets prefix for storage key.
+ *
+ * @param {string} prefix Storage key prefix
+ */
+ this.storagePrefix = function (prefix) {
+ if (!prefix) {
+ return prefix;
+ }
+ $storagePrefix = prefix;
+ return this;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#useMissingTranslationHandlerLog
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Tells angular-translate to use built-in log handler when trying to translate
+ * a translation Id which doesn't exist.
+ *
+ * This is actually a shortcut method for `useMissingTranslationHandler()`.
+ *
+ */
+ this.useMissingTranslationHandlerLog = function () {
+ return this.useMissingTranslationHandler('$translateMissingTranslationHandlerLog');
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#useMissingTranslationHandler
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Expects a factory name which later gets instantiated with `$injector`.
+ * This method can be used to tell angular-translate to use a custom
+ * missingTranslationHandler. Just build a factory which returns a function
+ * and expects a translation id as argument.
+ *
+ * Example:
+ *
+ * app.config(function ($translateProvider) {
+ * $translateProvider.useMissingTranslationHandler('customHandler');
+ * });
+ *
+ * app.factory('customHandler', function (dep1, dep2) {
+ * return function (translationId) {
+ * // something with translationId and dep1 and dep2
+ * };
+ * });
+ *
+ *
+ * @param {string} factory Factory name
+ */
+ this.useMissingTranslationHandler = function (factory) {
+ $missingTranslationHandlerFactory = factory;
+ return this;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#usePostCompiling
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * If post compiling is enabled, all translated values will be processed
+ * again with AngularJS' $compile.
+ *
+ * Example:
+ *
+ * app.config(function ($translateProvider) {
+ * $translateProvider.usePostCompiling(true);
+ * });
+ *
+ *
+ * @param {string} factory Factory name
+ */
+ this.usePostCompiling = function (value) {
+ $postCompilingEnabled = !(!value);
+ 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
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Tells angular-translate which language tag should be used as a result when determining
+ * the current browser language.
+ *
+ * This setting must be set before invoking {@link pascalprecht.translate.$translateProvider#methods_determinePreferredLanguage determinePreferredLanguage()}.
+ *
+ *
+ * $translateProvider
+ * .uniformLanguageTag('bcp47')
+ * .determinePreferredLanguage()
+ *
+ *
+ * The resolver currently supports:
+ * * default
+ * (traditionally: hyphens will be converted into underscores, i.e. en-US => en_US)
+ * en-US => en_US
+ * en_US => en_US
+ * en-us => en_us
+ * * java
+ * like default, but the second part will be always in uppercase
+ * en-US => en_US
+ * en_US => en_US
+ * en-us => en_US
+ * * BCP 47 (RFC 4646 & 4647)
+ * en-US => en-US
+ * en_US => en-US
+ * en-us => en-US
+ *
+ * See also:
+ * * http://en.wikipedia.org/wiki/IETF_language_tag
+ * * http://www.w3.org/International/core/langtags/
+ * * http://tools.ietf.org/html/bcp47
+ *
+ * @param {string|object} options - options (or standard)
+ * @param {string} options.standard - valid values are 'default', 'bcp47', 'java'
+ */
+ this.uniformLanguageTag = function (options) {
+
+ if (!options) {
+ options = {};
+ } else if (angular.isString(options)) {
+ options = {
+ standard: options
+ };
+ }
+
+ uniformLanguageTagResolver = options.standard;
+
+ return this;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#determinePreferredLanguage
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Tells angular-translate to try to determine on its own which language key
+ * to set as preferred language. When `fn` is given, angular-translate uses it
+ * to determine a language key, otherwise it uses the built-in `getLocale()`
+ * method.
+ *
+ * The `getLocale()` returns a language key in the format `[lang]_[country]` or
+ * `[lang]` depending on what the browser provides.
+ *
+ * Use this method at your own risk, since not all browsers return a valid
+ * locale (see {@link pascalprecht.translate.$translateProvider#methods_uniformLanguageTag uniformLanguageTag()}).
+ *
+ * @param {Function=} fn Function to determine a browser's locale
+ */
+ this.determinePreferredLanguage = function (fn) {
+
+ var locale = (fn && angular.isFunction(fn)) ? fn() : getLocale();
+
+ if (!$availableLanguageKeys.length) {
+ $preferredLanguage = locale;
+ } else {
+ $preferredLanguage = negotiateLocale(locale);
+ }
+
+ return this;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#registerAvailableLanguageKeys
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Registers a set of language keys the app will work with. Use this method in
+ * combination with
+ * {@link pascalprecht.translate.$translateProvider#determinePreferredLanguage determinePreferredLanguage}.
+ * When available languages keys are registered, angular-translate
+ * tries to find the best fitting language key depending on the browsers locale,
+ * considering your language key convention.
+ *
+ * @param {object} languageKeys Array of language keys the your app will use
+ * @param {object=} aliases Alias map.
+ */
+ this.registerAvailableLanguageKeys = function (languageKeys, aliases) {
+ if (languageKeys) {
+ $availableLanguageKeys = languageKeys;
+ if (aliases) {
+ $languageKeyAliases = aliases;
+ }
+ return this;
+ }
+ return $availableLanguageKeys;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#useLoaderCache
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Registers a cache for internal $http based loaders.
+ * {@link pascalprecht.translate.$translateProvider#determinePreferredLanguage determinePreferredLanguage}.
+ * When false the cache will be disabled (default). When true or undefined
+ * the cache will be a default (see $cacheFactory). When an object it will
+ * be treat as a cache object itself: the usage is $http({cache: cache})
+ *
+ * @param {object} cache boolean, string or cache-object
+ */
+ this.useLoaderCache = function (cache) {
+ if (cache === false) {
+ // disable cache
+ loaderCache = undefined;
+ } else if (cache === true) {
+ // enable cache using AJS defaults
+ loaderCache = true;
+ } else if (typeof(cache) === 'undefined') {
+ // enable cache using default
+ loaderCache = '$translationCache';
+ } else if (cache) {
+ // enable cache using given one (see $cacheFactory)
+ loaderCache = cache;
+ }
+ return this;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#directivePriority
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Sets the default priority of the translate directive. The standard value is `0`.
+ * Calling this function without an argument will return the current value.
+ *
+ * @param {number} priority for the translate-directive
+ */
+ this.directivePriority = function (priority) {
+ if (priority === undefined) {
+ // getter
+ return directivePriority;
+ } else {
+ // setter with chaining
+ directivePriority = priority;
+ return this;
+ }
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#statefulFilter
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Since AngularJS 1.3, filters which are not stateless (depending at the scope)
+ * have to explicit define this behavior.
+ * Sets whether the translate filter should be stateful or stateless. The standard value is `true`
+ * meaning being stateful.
+ * Calling this function without an argument will return the current value.
+ *
+ * @param {boolean} state - defines the state of the filter
+ */
+ this.statefulFilter = function (state) {
+ if (state === undefined) {
+ // getter
+ return statefulFilter;
+ } else {
+ // setter with chaining
+ statefulFilter = state;
+ return this;
+ }
+ };
+
+ /**
+ * @ngdoc object
+ * @name pascalprecht.translate.$translate
+ * @requires $interpolate
+ * @requires $log
+ * @requires $rootScope
+ * @requires $q
+ *
+ * @description
+ * The `$translate` service is the actual core of angular-translate. It expects a translation id
+ * and optional interpolate parameters to translate contents.
+ *
+ *
+ * $translate('HEADLINE_TEXT').then(function (translation) {
+ * $scope.translatedText = translation;
+ * });
+ *
+ *
+ * @param {string|array} translationId A token which represents a translation id
+ * This can be optionally an array of translation ids which
+ * results that the function returns an object where each key
+ * is the translation id and the value the translation.
+ * @param {object=} interpolateParams An object hash for dynamic values
+ * @param {string} interpolationId The id of the interpolation to use
+ * @returns {object} promise
+ */
+ this.$get = [
+ '$log',
+ '$injector',
+ '$rootScope',
+ '$q',
+ function ($log, $injector, $rootScope, $q) {
+
+ var Storage,
+ defaultInterpolator = $injector.get($interpolationFactory || '$translateDefaultInterpolation'),
+ pendingLoader = false,
+ interpolatorHashMap = {},
+ langPromises = {},
+ fallbackIndex,
+ startFallbackIteration;
+
+ var $translate = function (translationId, interpolateParams, interpolationId, defaultTranslationText) {
+
+ // Duck detection: If the first argument is an array, a bunch of translations was requested.
+ // The result is an object.
+ if (angular.isArray(translationId)) {
+ // Inspired by Q.allSettled by Kris Kowal
+ // https://github.com/kriskowal/q/blob/b0fa72980717dc202ffc3cbf03b936e10ebbb9d7/q.js#L1553-1563
+ // This transforms all promises regardless resolved or rejected
+ var translateAll = function (translationIds) {
+ var results = {}; // storing the actual results
+ var promises = []; // promises to wait for
+ // Wraps the promise a) being always resolved and b) storing the link id->value
+ var translate = function (translationId) {
+ var deferred = $q.defer();
+ var regardless = function (value) {
+ results[translationId] = value;
+ deferred.resolve([translationId, value]);
+ };
+ // we don't care whether the promise was resolved or rejected; just store the values
+ $translate(translationId, interpolateParams, interpolationId, defaultTranslationText).then(regardless, regardless);
+ return deferred.promise;
+ };
+ for (var i = 0, c = translationIds.length; i < c; i++) {
+ promises.push(translate(translationIds[i]));
+ }
+ // wait for all (including storing to results)
+ return $q.all(promises).then(function () {
+ // return the results
+ return results;
+ });
+ };
+ return translateAll(translationId);
+ }
+
+ var deferred = $q.defer();
+
+ // trim off any whitespace
+ if (translationId) {
+ translationId = trim.apply(translationId);
+ }
+
+ var promiseToWaitFor = (function () {
+ var promise = $preferredLanguage ?
+ langPromises[$preferredLanguage] :
+ langPromises[$uses];
+
+ fallbackIndex = 0;
+
+ if ($storageFactory && !promise) {
+ // looks like there's no pending promise for $preferredLanguage or
+ // $uses. Maybe there's one pending for a language that comes from
+ // storage.
+ var langKey = Storage.get($storageKey);
+ promise = langPromises[langKey];
+
+ if ($fallbackLanguage && $fallbackLanguage.length) {
+ var index = indexOf($fallbackLanguage, langKey);
+ // maybe the language from storage is also defined as fallback language
+ // we increase the fallback language index to not search in that language
+ // as fallback, since it's probably the first used language
+ // in that case the index starts after the first element
+ fallbackIndex = (index === 0) ? 1 : 0;
+
+ // but we can make sure to ALWAYS fallback to preferred language at least
+ if (indexOf($fallbackLanguage, $preferredLanguage) < 0) {
+ $fallbackLanguage.push($preferredLanguage);
+ }
+ }
+ }
+ return promise;
+ }());
+
+ if (!promiseToWaitFor) {
+ // no promise to wait for? okay. Then there's no loader registered
+ // nor is a one pending for language that comes from storage.
+ // We can just translate.
+ determineTranslation(translationId, interpolateParams, interpolationId, defaultTranslationText).then(deferred.resolve, deferred.reject);
+ } else {
+ 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
+ });
+ $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
+ * @returns {Q.promise}
+ */
+ var getFallbackTranslation = function (langKey, translationId, interpolateParams, Interpolator) {
+ var deferred = $q.defer();
+
+ 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);
+ } else {
+ deferred.reject();
+ }
+ };
+ onResolve.displayName = 'fallbackTranslationResolver';
+
+ getTranslationTable(langKey).then(onResolve, deferred.reject);
+
+ return deferred.promise;
+ };
+
+ /**
+ * @name getFallbackTranslationInstant
+ * @private
+ *
+ * @description
+ * Returns a translation
+ * This function is currently only used for fallback language translation.
+ *
+ * @param langKey The language to translate to.
+ * @param translationId
+ * @param interpolateParams
+ * @param Interpolator
+ * @returns {string} translation
+ */
+ var getFallbackTranslationInstant = function (langKey, translationId, interpolateParams, Interpolator) {
+ var result, translationTable = $translationTable[langKey];
+
+ if (translationTable && Object.prototype.hasOwnProperty.call(translationTable, translationId)) {
+ Interpolator.setLocale(langKey);
+ result = Interpolator.interpolate(translationTable[translationId], interpolateParams);
+ if (result.substr(0, 2) === '@:') {
+ return getFallbackTranslationInstant(langKey, result.substr(2), interpolateParams, Interpolator);
+ }
+ Interpolator.setLocale($uses);
+ }
+
+ return result;
+ };
+
+
+ /**
+ * @name translateByHandler
+ * @private
+ *
+ * Translate by missing translation handler.
+ *
+ * @param translationId
+ * @returns translation created by $missingTranslationHandler or translationId is $missingTranslationHandler is
+ * absent
+ */
+ var translateByHandler = function (translationId, 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
+ 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));
+ }
+ } 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);
+ }
+ } else {
+ if (defaultTranslationText) {
+ deferred.resolve(defaultTranslationText);
+ } else {
+ deferred.reject(applyNotFoundIndicators(translationId));
}
}
}
- }
- var parts = preferred.split('_');
- if (parts.length > 1 && avail.indexOf(angular.lowercase(parts[0])) > -1) {
- return parts[0];
- }
- return preferred;
- };
- var translations = function (langKey, translationTable) {
- if (!langKey && !translationTable) {
- return $translationTable;
- }
- if (langKey && !translationTable) {
- if (angular.isString(langKey)) {
- return $translationTable[langKey];
+ return deferred.promise;
+ };
+
+ var determineTranslationInstant = function (translationId, interpolateParams, interpolationId) {
+
+ var result, table = $uses ? $translationTable[$uses] : $translationTable,
+ Interpolator = defaultInterpolator;
+
+ // if the interpolation id exists use custom interpolator
+ if (interpolatorHashMap && Object.prototype.hasOwnProperty.call(interpolatorHashMap, interpolationId)) {
+ Interpolator = interpolatorHashMap[interpolationId];
}
- } else {
- if (!angular.isObject($translationTable[langKey])) {
- $translationTable[langKey] = {};
- }
- angular.extend($translationTable[langKey], flatObject(translationTable));
- }
- return this;
- };
- this.translations = translations;
- this.cloakClassName = function (name) {
- if (!name) {
- return $cloakClassName;
- }
- $cloakClassName = name;
- return this;
- };
- var flatObject = function (data, path, result, prevKey) {
- var key, keyWithPath, keyWithShortPath, val;
- if (!path) {
- path = [];
- }
- if (!result) {
- result = {};
- }
- for (key in data) {
- if (!data.hasOwnProperty(key)) {
- continue;
- }
- val = data[key];
- if (angular.isObject(val)) {
- flatObject(val, path.concat(key), result, key);
- } else {
- keyWithPath = path.length ? '' + path.join(NESTED_OBJECT_DELIMITER) + NESTED_OBJECT_DELIMITER + key : key;
- if (path.length && key === prevKey) {
- keyWithShortPath = '' + path.join(NESTED_OBJECT_DELIMITER);
- result[keyWithShortPath] = '@:' + keyWithPath;
+
+ // if the translation id exists, we can just interpolate it
+ if (table && Object.prototype.hasOwnProperty.call(table, translationId)) {
+ var translation = table[translationId];
+
+ // If using link, rerun $translate with linked translationId and return it
+ if (translation.substr(0, 2) === '@:') {
+ result = determineTranslationInstant(translation.substr(2), interpolateParams, interpolationId);
+ } else {
+ result = Interpolator.interpolate(translation, interpolateParams);
+ }
+ } else {
+ var missingTranslationHandlerTranslation;
+ // for logging purposes only (as in $translateMissingTranslationHandlerLog), value is not returned to promise
+ if ($missingTranslationHandlerFactory && !pendingLoader) {
+ missingTranslationHandlerTranslation = translateByHandler(translationId, 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[keyWithPath] = val;
}
- }
- return result;
- };
- this.addInterpolation = function (factory) {
- $interpolatorFactories.push(factory);
- return this;
- };
- this.useMessageFormatInterpolation = function () {
- return this.useInterpolation('$translateMessageFormatInterpolation');
- };
- this.useInterpolation = function (factory) {
- $interpolationFactory = factory;
- return this;
- };
- this.useSanitizeValueStrategy = function (value) {
- $interpolationSanitizationStrategy = value;
- return this;
- };
- this.preferredLanguage = function (langKey) {
- if (langKey) {
- $preferredLanguage = langKey;
- return this;
- }
- return $preferredLanguage;
- };
- this.translationNotFoundIndicator = function (indicator) {
- this.translationNotFoundIndicatorLeft(indicator);
- this.translationNotFoundIndicatorRight(indicator);
- return this;
- };
- this.translationNotFoundIndicatorLeft = function (indicator) {
- if (!indicator) {
- return $notFoundIndicatorLeft;
- }
- $notFoundIndicatorLeft = indicator;
- return this;
- };
- this.translationNotFoundIndicatorRight = function (indicator) {
- if (!indicator) {
- return $notFoundIndicatorRight;
- }
- $notFoundIndicatorRight = indicator;
- return this;
- };
- this.fallbackLanguage = function (langKey) {
- fallbackStack(langKey);
- return this;
- };
- var fallbackStack = function (langKey) {
- if (langKey) {
- if (angular.isString(langKey)) {
- $fallbackWasString = true;
- $fallbackLanguage = [langKey];
- } else if (angular.isArray(langKey)) {
- $fallbackWasString = false;
- $fallbackLanguage = langKey;
+
+ return result;
+ };
+
+ var clearNextLangAndPromise = function(key) {
+ if ($nextLang === key) {
+ $nextLang = undefined;
}
- if (angular.isString($preferredLanguage)) {
- $fallbackLanguage.push($preferredLanguage);
+ 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());
}
- return this;
- } else {
if ($fallbackWasString) {
return $fallbackLanguage[0];
} else {
return $fallbackLanguage;
}
- }
- };
- this.use = function (langKey) {
- if (langKey) {
- if (!$translationTable[langKey] && !$loaderFactory) {
- throw new Error('$translateProvider couldn\'t find translationTable for langKey: \'' + langKey + '\'');
- }
- $uses = langKey;
- return this;
- }
- return $uses;
- };
- var storageKey = function (key) {
- if (!key) {
- if ($storagePrefix) {
- return $storagePrefix + $storageKey;
- }
- return $storageKey;
- }
- $storageKey = key;
- };
- this.storageKey = storageKey;
- this.useUrlLoader = function (url) {
- return this.useLoader('$translateUrlLoader', { url: url });
- };
- this.useStaticFilesLoader = function (options) {
- return this.useLoader('$translateStaticFilesLoader', options);
- };
- this.useLoader = function (loaderFactory, options) {
- $loaderFactory = loaderFactory;
- $loaderOptions = options || {};
- return this;
- };
- this.useLocalStorage = function () {
- return this.useStorage('$translateLocalStorage');
- };
- this.useCookieStorage = function () {
- return this.useStorage('$translateCookieStorage');
- };
- this.useStorage = function (storageFactory) {
- $storageFactory = storageFactory;
- return this;
- };
- this.storagePrefix = function (prefix) {
- if (!prefix) {
- return prefix;
- }
- $storagePrefix = prefix;
- return this;
- };
- this.useMissingTranslationHandlerLog = function () {
- return this.useMissingTranslationHandler('$translateMissingTranslationHandlerLog');
- };
- this.useMissingTranslationHandler = function (factory) {
- $missingTranslationHandlerFactory = factory;
- return this;
- };
- this.usePostCompiling = function (value) {
- $postCompilingEnabled = !!value;
- return this;
- };
- this.determinePreferredLanguage = function (fn) {
- var locale = fn && angular.isFunction(fn) ? fn() : getLocale();
- if (!$availableLanguageKeys.length) {
- $preferredLanguage = locale;
- } else {
- $preferredLanguage = negotiateLocale(locale);
- }
- return this;
- };
- this.registerAvailableLanguageKeys = function (languageKeys, aliases) {
- if (languageKeys) {
- $availableLanguageKeys = languageKeys;
- if (aliases) {
- $languageKeyAliases = aliases;
- }
- return this;
- }
- return $availableLanguageKeys;
- };
- this.$get = [
- '$log',
- '$injector',
- '$rootScope',
- '$q',
- function ($log, $injector, $rootScope, $q) {
- var Storage, defaultInterpolator = $injector.get($interpolationFactory || '$translateDefaultInterpolation'), pendingLoader = false, interpolatorHashMap = {}, langPromises = {}, fallbackIndex, startFallbackIteration;
- var $translate = function (translationId, interpolateParams, interpolationId) {
- if (angular.isArray(translationId)) {
- var translateAll = function (translationIds) {
- var results = {};
- var promises = [];
- var translate = function (translationId) {
- var deferred = $q.defer();
- var regardless = function (value) {
- results[translationId] = value;
- deferred.resolve([
- translationId,
- value
- ]);
- };
- $translate(translationId, interpolateParams, interpolationId).then(regardless, regardless);
- return deferred.promise;
- };
- for (var i = 0, c = translationIds.length; i < c; i++) {
- promises.push(translate(translationIds[i]));
- }
- return $q.all(promises).then(function () {
- return results;
- });
- };
- return translateAll(translationId);
- }
- var deferred = $q.defer();
- if (translationId) {
- translationId = translationId.trim();
- }
- var promiseToWaitFor = function () {
- var promise = $preferredLanguage ? langPromises[$preferredLanguage] : langPromises[$uses];
- fallbackIndex = 0;
- if ($storageFactory && !promise) {
- var langKey = Storage.get($storageKey);
- promise = langPromises[langKey];
- if ($fallbackLanguage && $fallbackLanguage.length) {
- var index = indexOf($fallbackLanguage, langKey);
- fallbackIndex = index > -1 ? index += 1 : 0;
- $fallbackLanguage.push($preferredLanguage);
- }
- }
- return promise;
- }();
- if (!promiseToWaitFor) {
- determineTranslation(translationId, interpolateParams, interpolationId).then(deferred.resolve, deferred.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 {
- promiseToWaitFor.then(function () {
- determineTranslation(translationId, interpolateParams, interpolationId).then(deferred.resolve, deferred.reject);
- }, deferred.reject);
- }
- return deferred.promise;
- };
- var indexOf = function (array, searchElement) {
- for (var i = 0, len = array.length; i < len; i++) {
- if (array[i] === searchElement) {
- return i;
+ var langKeyPosition = indexOf($fallbackLanguage, langKey);
+ if (langKeyPosition > -1) {
+ startFallbackIteration = langKeyPosition;
}
}
- return -1;
- };
- var applyNotFoundIndicators = function (translationId) {
- if ($notFoundIndicatorLeft) {
- translationId = [
- $notFoundIndicatorLeft,
- translationId
- ].join(' ');
- }
- if ($notFoundIndicatorRight) {
- translationId = [
- translationId,
- $notFoundIndicatorRight
- ].join(' ');
- }
- return translationId;
- };
- var useLanguage = function (key) {
- $uses = key;
- $rootScope.$emit('$translateChangeSuccess');
- if ($storageFactory) {
- Storage.set($translate.storageKey(), $uses);
- }
- defaultInterpolator.setLocale($uses);
- angular.forEach(interpolatorHashMap, function (interpolator, id) {
- interpolatorHashMap[id].setLocale($uses);
- });
- $rootScope.$emit('$translateChangeEnd');
- };
- var loadAsync = function (key) {
- if (!key) {
- throw 'No language key specified for loading.';
- }
- var deferred = $q.defer();
- $rootScope.$emit('$translateLoadingStart');
- pendingLoader = true;
- $injector.get($loaderFactory)(angular.extend($loaderOptions, { key: key })).then(function (data) {
- var translationTable = {};
- $rootScope.$emit('$translateLoadingSuccess');
- 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');
+
+ }
+
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translate#proposedLanguage
+ * @methodOf pascalprecht.translate.$translate
+ *
+ * @description
+ * Returns the language key of language that is currently loaded asynchronously.
+ *
+ * @return {string} language key
+ */
+ $translate.proposedLanguage = function () {
+ return $nextLang;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translate#storage
+ * @methodOf pascalprecht.translate.$translate
+ *
+ * @description
+ * Returns registered storage.
+ *
+ * @return {object} Storage
+ */
+ $translate.storage = function () {
+ return Storage;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translate#use
+ * @methodOf pascalprecht.translate.$translate
+ *
+ * @description
+ * Tells angular-translate which language to use by given language key. This method is
+ * used to change language at runtime. It also takes care of storing the language
+ * key in a configured store to let your app remember the choosed language.
+ *
+ * When trying to 'use' a language which isn't available it tries to load it
+ * asynchronously with registered loaders.
+ *
+ * Returns promise object with loaded language file data
+ * @example
+ * $translate.use("en_US").then(function(data){
+ * $scope.text = $translate("HELLO");
+ * });
+ *
+ * @param {string} key Language key
+ * @return {string} Language key
+ */
+ $translate.use = function (key) {
+ if (!key) {
+ return $uses;
+ }
+
+ var deferred = $q.defer();
+
+ $rootScope.$emit('$translateChangeStart', {language: key});
+
+ // Try to get the aliased language key
+ var aliasedKey = negotiateLocale(key);
+ if (aliasedKey) {
+ key = aliasedKey;
+ }
+
+ // if there isn't a translation table for the language we've requested,
+ // we load it asynchronously
+ if (($forceAsyncReloadEnabled || !$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('$translateLoadingError');
+ $rootScope.$emit('$translateChangeError', {language: key});
deferred.reject(key);
- $rootScope.$emit('$translateLoadingEnd');
+ $rootScope.$emit('$translateChangeEnd', {language: key});
+ return $q.reject(key);
});
- return deferred.promise;
- };
- if ($storageFactory) {
- Storage = $injector.get($storageFactory);
- if (!Storage.get || !Storage.set) {
- throw new Error('Couldn\'t use storage \'' + $storageFactory + '\', missing get() or set() method!');
- }
+ 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);
}
- if (angular.isFunction(defaultInterpolator.useSanitizeValueStrategy)) {
- defaultInterpolator.useSanitizeValueStrategy($interpolationSanitizationStrategy);
+
+ return deferred.promise;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translate#storageKey
+ * @methodOf pascalprecht.translate.$translate
+ *
+ * @description
+ * Returns the key for the storage.
+ *
+ * @return {string} storage key
+ */
+ $translate.storageKey = function () {
+ return storageKey();
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translate#isPostCompilingEnabled
+ * @methodOf pascalprecht.translate.$translate
+ *
+ * @description
+ * Returns whether post compiling is enabled or not
+ *
+ * @return {bool} storage key
+ */
+ $translate.isPostCompilingEnabled = function () {
+ return $postCompilingEnabled;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translate#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#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!');
}
- if ($interpolatorFactories.length) {
- angular.forEach($interpolatorFactories, function (interpolatorFactory) {
- var interpolator = $injector.get(interpolatorFactory);
- interpolator.setLocale($preferredLanguage || $uses);
- if (angular.isFunction(interpolator.useSanitizeValueStrategy)) {
- interpolator.useSanitizeValueStrategy($interpolationSanitizationStrategy);
+
+ var deferred = $q.defer();
+
+ function resolve() {
+ deferred.resolve();
+ $rootScope.$emit('$translateRefreshEnd', {language: langKey});
+ }
+
+ function reject() {
+ deferred.reject();
+ $rootScope.$emit('$translateRefreshEnd', {language: langKey});
+ }
+
+ $rootScope.$emit('$translateRefreshStart', {language: langKey});
+
+ if (!langKey) {
+ // if there's no language key specified we refresh ALL THE THINGS!
+ var tables = [], loadingKeys = {};
+
+ // reload registered fallback languages
+ if ($fallbackLanguage && $fallbackLanguage.length) {
+ for (var i = 0, len = $fallbackLanguage.length; i < len; i++) {
+ tables.push(loadAsync($fallbackLanguage[i]));
+ loadingKeys[$fallbackLanguage[i]] = true;
}
- interpolatorHashMap[interpolator.getInterpolationIdentifier()] = interpolator;
- });
- }
- var getTranslationTable = function (langKey) {
- var deferred = $q.defer();
- if ($translationTable.hasOwnProperty(langKey)) {
- deferred.resolve($translationTable[langKey]);
- return deferred.promise;
- } else {
- langPromises[langKey].then(function (data) {
+ }
+
+ // 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);
- deferred.resolve(data.table);
- }, deferred.reject);
- }
- return deferred.promise;
- };
- var getFallbackTranslation = function (langKey, translationId, interpolateParams, Interpolator) {
- var deferred = $q.defer();
- getTranslationTable(langKey).then(function (translationTable) {
- if (translationTable.hasOwnProperty(translationId)) {
- Interpolator.setLocale(langKey);
- deferred.resolve(Interpolator.interpolate(translationTable[translationId], interpolateParams));
- Interpolator.setLocale($uses);
- } else {
- deferred.reject();
- }
- }, deferred.reject);
- return deferred.promise;
- };
- var getFallbackTranslationInstant = function (langKey, translationId, interpolateParams, Interpolator) {
- var result, translationTable = $translationTable[langKey];
- if (translationTable.hasOwnProperty(translationId)) {
- Interpolator.setLocale(langKey);
- result = Interpolator.interpolate(translationTable[translationId], interpolateParams);
- Interpolator.setLocale($uses);
- }
- return result;
- };
- var resolveForFallbackLanguage = function (fallbackLanguageIndex, translationId, interpolateParams, Interpolator) {
- var deferred = $q.defer();
- if (fallbackLanguageIndex < $fallbackLanguage.length) {
- var langKey = $fallbackLanguage[fallbackLanguageIndex];
- getFallbackTranslation(langKey, translationId, interpolateParams, Interpolator).then(function (translation) {
- deferred.resolve(translation);
- }, function () {
- var nextFallbackLanguagePromise = resolveForFallbackLanguage(fallbackLanguageIndex + 1, translationId, interpolateParams, Interpolator);
- deferred.resolve(nextFallbackLanguagePromise);
});
- } else {
- if ($missingTranslationHandlerFactory) {
- var resultString = $injector.get($missingTranslationHandlerFactory)(translationId, $uses);
- if (resultString !== undefined) {
- deferred.resolve(resultString);
- } else {
- deferred.resolve(translationId);
- }
- } else {
- deferred.resolve(translationId);
+ if ($uses) {
+ useLanguage($uses);
}
+ resolve();
+ };
+ allTranslationsLoaded.displayName = 'refreshPostProcessor';
+
+ $q.all(tables).then(allTranslationsLoaded, reject);
+
+ } 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);
+
+ } 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);
}
- return deferred.promise;
- };
- 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;
- };
- var fallbackTranslation = function (translationId, interpolateParams, Interpolator) {
- return resolveForFallbackLanguage(startFallbackIteration > 0 ? startFallbackIteration : fallbackIndex, translationId, interpolateParams, Interpolator);
- };
- var fallbackTranslationInstant = function (translationId, interpolateParams, Interpolator) {
- return resolveForFallbackLanguageInstant(startFallbackIteration > 0 ? startFallbackIteration : fallbackIndex, translationId, interpolateParams, Interpolator);
- };
- var determineTranslation = function (translationId, interpolateParams, interpolationId) {
- var deferred = $q.defer();
- var table = $uses ? $translationTable[$uses] : $translationTable, Interpolator = interpolationId ? interpolatorHashMap[interpolationId] : defaultInterpolator;
- if (table && table.hasOwnProperty(translationId)) {
- var translation = table[translationId];
- if (translation.substr(0, 2) === '@:') {
- $translate(translation.substr(2), interpolateParams, interpolationId).then(deferred.resolve, deferred.reject);
- } else {
- deferred.resolve(Interpolator.interpolate(translation, interpolateParams));
- }
- } else {
- if ($missingTranslationHandlerFactory && !pendingLoader) {
- $injector.get($missingTranslationHandlerFactory)(translationId, $uses);
- }
- if ($uses && $fallbackLanguage && $fallbackLanguage.length) {
- fallbackTranslation(translationId, interpolateParams, Interpolator).then(function (translation) {
- deferred.resolve(translation);
- }, function (_translationId) {
- deferred.reject(applyNotFoundIndicators(_translationId));
- });
- } else {
- deferred.reject(applyNotFoundIndicators(translationId));
- }
- }
- return deferred.promise;
- };
- var determineTranslationInstant = function (translationId, interpolateParams, interpolationId) {
- var result, table = $uses ? $translationTable[$uses] : $translationTable, Interpolator = interpolationId ? interpolatorHashMap[interpolationId] : defaultInterpolator;
- if (table && table.hasOwnProperty(translationId)) {
- var translation = table[translationId];
- if (translation.substr(0, 2) === '@:') {
- result = determineTranslationInstant(translation.substr(2), interpolateParams, interpolationId);
- } else {
- result = Interpolator.interpolate(translation, interpolateParams);
- }
- } else {
- if ($missingTranslationHandlerFactory && !pendingLoader) {
- $injector.get($missingTranslationHandlerFactory)(translationId, $uses);
- }
- if ($uses && $fallbackLanguage && $fallbackLanguage.length) {
- fallbackIndex = 0;
- result = fallbackTranslationInstant(translationId, interpolateParams, Interpolator);
- } else {
+ 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);
}
}
- return result;
- };
- $translate.preferredLanguage = function () {
- return $preferredLanguage;
- };
- $translate.cloakClassName = function () {
- return $cloakClassName;
- };
- $translate.fallbackLanguage = function (langKey) {
- if (langKey !== undefined && langKey !== null) {
- fallbackStack(langKey);
- if ($loaderFactory) {
- if ($fallbackLanguage && $fallbackLanguage.length) {
- for (var i = 0, len = $fallbackLanguage.length; i < len; i++) {
- if (!langPromises[$fallbackLanguage[i]]) {
- langPromises[$fallbackLanguage[i]] = loadAsync($fallbackLanguage[i]);
- }
- }
- }
- }
- $translate.use($translate.use());
- }
- if ($fallbackWasString) {
- return $fallbackLanguage[0];
- } else {
- return $fallbackLanguage;
- }
- };
- $translate.useFallbackLanguage = function (langKey) {
- if (langKey !== undefined && langKey !== null) {
- if (!langKey) {
- startFallbackIteration = 0;
- } else {
- var langKeyPosition = indexOf($fallbackLanguage, langKey);
- if (langKeyPosition > -1) {
- startFallbackIteration = langKeyPosition;
- }
- }
- }
- };
- $translate.proposedLanguage = function () {
- return $nextLang;
- };
- $translate.storage = function () {
- return Storage;
- };
- $translate.use = function (key) {
- if (!key) {
- return $uses;
- }
- var deferred = $q.defer();
- $rootScope.$emit('$translateChangeStart');
- var aliasedKey = negotiateLocale(key);
- if (aliasedKey) {
- key = aliasedKey;
- }
- if (!$translationTable[key] && $loaderFactory) {
- $nextLang = key;
- langPromises[key] = loadAsync(key).then(function (translation) {
- translations(translation.key, translation.table);
- deferred.resolve(translation.key);
- if ($nextLang === key) {
- useLanguage(translation.key);
- $nextLang = undefined;
- }
- }, function (key) {
- $nextLang = undefined;
- $rootScope.$emit('$translateChangeError');
- deferred.reject(key);
- $rootScope.$emit('$translateChangeEnd');
- });
- } else {
- deferred.resolve(key);
- useLanguage(key);
- }
- return deferred.promise;
- };
- $translate.storageKey = function () {
- return storageKey();
- };
- $translate.isPostCompilingEnabled = function () {
- return $postCompilingEnabled;
- };
- $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');
- }
- function reject() {
- deferred.reject();
- $rootScope.$emit('$translateRefreshEnd');
- }
- $rootScope.$emit('$translateRefreshStart');
- if (!langKey) {
- var tables = [];
- if ($fallbackLanguage && $fallbackLanguage.length) {
- for (var i = 0, len = $fallbackLanguage.length; i < len; i++) {
- tables.push(loadAsync($fallbackLanguage[i]));
- }
- }
- if ($uses) {
- tables.push(loadAsync($uses));
- }
- $q.all(tables).then(function (tableData) {
- angular.forEach(tableData, function (data) {
- if ($translationTable[data.key]) {
- delete $translationTable[data.key];
- }
- translations(data.key, data.table);
- });
- if ($uses) {
- useLanguage($uses);
- }
- resolve();
- });
- } else if ($translationTable[langKey]) {
- loadAsync(langKey).then(function (data) {
- translations(data.key, data.table);
- if (langKey === $uses) {
- useLanguage($uses);
- }
- resolve();
- }, reject);
- } else {
- reject();
- }
- return deferred.promise;
- };
- $translate.instant = function (translationId, interpolateParams, interpolationId) {
- if (translationId === null || angular.isUndefined(translationId)) {
- return translationId;
- }
- if (angular.isArray(translationId)) {
- var results = {};
- for (var i = 0, c = translationId.length; i < c; i++) {
- results[translationId[i]] = $translate.instant(translationId[i], interpolateParams, interpolationId);
- }
- return results;
- }
- if (angular.isString(translationId) && translationId.length < 1) {
- return translationId;
- }
- if (translationId) {
- translationId = translationId.trim();
- }
- 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);
- }
- }
- if (typeof result !== 'undefined') {
- break;
- }
- }
- if (!result && result !== '') {
- result = translationId;
- if ($missingTranslationHandlerFactory && !pendingLoader) {
- $injector.get($missingTranslationHandlerFactory)(translationId, $uses);
- }
- }
- return result;
- };
- if ($loaderFactory) {
- if (angular.equals($translationTable, {})) {
- $translate.use($translate.use());
- }
- if ($fallbackLanguage && $fallbackLanguage.length) {
- for (var i = 0, len = $fallbackLanguage.length; i < len; i++) {
- langPromises[$fallbackLanguage[i]] = loadAsync($fallbackLanguage[i]);
- }
+ if (typeof result !== 'undefined') {
+ break;
}
}
- return $translate;
- }
- ];
- }
-]);
-angular.module('pascalprecht.translate').factory('$translateDefaultInterpolation', [
- '$interpolate',
- function ($interpolate) {
- var $translateInterpolator = {}, $locale, $identifier = 'default', $sanitizeValueStrategy = null, sanitizeValueStrategies = {
- escaped: function (params) {
- var result = {};
- for (var key in params) {
- if (params.hasOwnProperty(key)) {
- result[key] = angular.element('').text(params[key]).html();
- }
+
+ if (!result && result !== '') {
+ // Return translation of default interpolator if not found anything.
+ result = defaultInterpolator.interpolate(translationId, interpolateParams);
+ if ($missingTranslationHandlerFactory && !pendingLoader) {
+ result = translateByHandler(translationId, interpolateParams);
}
- return result;
}
+
+ return result;
};
- var sanitizeParams = function (params) {
- var result;
- if (angular.isFunction(sanitizeValueStrategies[$sanitizeValueStrategy])) {
- result = sanitizeValueStrategies[$sanitizeValueStrategy](params);
- } else {
- result = params;
- }
- return result;
- };
- $translateInterpolator.setLocale = function (locale) {
- $locale = locale;
- };
- $translateInterpolator.getInterpolationIdentifier = function () {
- return $identifier;
- };
- $translateInterpolator.useSanitizeValueStrategy = function (value) {
- $sanitizeValueStrategy = value;
- return this;
- };
- $translateInterpolator.interpolate = function (string, interpolateParams) {
- if ($sanitizeValueStrategy) {
- interpolateParams = sanitizeParams(interpolateParams);
- }
- return $interpolate(string)(interpolateParams || {});
- };
- return $translateInterpolator;
- }
-]);
-angular.module('pascalprecht.translate').constant('$STORAGE_KEY', 'NG_TRANSLATE_LANG_KEY');
-angular.module('pascalprecht.translate').directive('translate', [
- '$translate',
- '$q',
- '$interpolate',
- '$compile',
- '$parse',
- '$rootScope',
- function ($translate, $q, $interpolate, $compile, $parse, $rootScope) {
- return {
- restrict: 'AE',
- scope: true,
- compile: function (tElement, tAttr) {
- var translateValuesExist = tAttr.translateValues ? tAttr.translateValues : undefined;
- var translateInterpolation = tAttr.translateInterpolation ? tAttr.translateInterpolation : undefined;
- var translateValueExist = tElement[0].outerHTML.match(/translate-value-+/i);
- return function linkFn(scope, iElement, iAttr) {
- scope.interpolateParams = {};
- iAttr.$observe('translate', function (translationId) {
- if (angular.equals(translationId, '') || !angular.isDefined(translationId)) {
- scope.translationId = $interpolate(iElement.text().replace(/^\s+|\s+$/g, ''))(scope.$parent);
- } else {
- scope.translationId = translationId;
+
+ /**
+ * @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 ($forceAsyncReloadEnabled || !$translationTable[fallbackLanguageId]) {
+ langPromises[fallbackLanguageId] = loadAsync(fallbackLanguageId).then(processAsyncResult);
}
- });
- iAttr.$observe('translateDefault', function (value) {
- scope.defaultText = value;
- });
- if (translateValuesExist) {
- iAttr.$observe('translateValues', function (interpolateParams) {
- if (interpolateParams) {
- scope.$parent.$watch(function () {
- angular.extend(scope.interpolateParams, $parse(interpolateParams)(scope.$parent));
+ }
+ }
+ }
+
+ return $translate;
+ }
+ ];
+}
+$translate.$inject = ['$STORAGE_KEY', '$windowProvider', '$translateSanitizationProvider', 'pascalprechtTranslateOverrider'];
+
+$translate.displayName = 'displayName';
+
+/**
+ * @ngdoc object
+ * @name pascalprecht.translate.$translateDefaultInterpolation
+ * @requires $interpolate
+ *
+ * @description
+ * Uses angular's `$interpolate` services to interpolate strings against some values.
+ *
+ * Be aware to configure a proper sanitization strategy.
+ *
+ * See also:
+ * * {@link pascalprecht.translate.$translateSanitization}
+ *
+ * @return {object} $translateDefaultInterpolation Interpolator service
+ */
+angular.module('pascalprecht.translate').factory('$translateDefaultInterpolation', $translateDefaultInterpolation);
+
+function $translateDefaultInterpolation ($interpolate, $translateSanitization) {
+
+ 'use strict';
+
+ var $translateInterpolator = {},
+ $locale,
+ $identifier = 'default';
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateDefaultInterpolation#setLocale
+ * @methodOf pascalprecht.translate.$translateDefaultInterpolation
+ *
+ * @description
+ * Sets current locale (this is currently not use in this interpolation).
+ *
+ * @param {string} locale Language key or locale.
+ */
+ $translateInterpolator.setLocale = function (locale) {
+ $locale = locale;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateDefaultInterpolation#getInterpolationIdentifier
+ * @methodOf pascalprecht.translate.$translateDefaultInterpolation
+ *
+ * @description
+ * Returns an identifier for this interpolation service.
+ *
+ * @returns {string} $identifier
+ */
+ $translateInterpolator.getInterpolationIdentifier = function () {
+ return $identifier;
+ };
+
+ /**
+ * @deprecated will be removed in 3.0
+ * @see {@link pascalprecht.translate.$translateSanitization}
+ */
+ $translateInterpolator.useSanitizeValueStrategy = function (value) {
+ $translateSanitization.useStrategy(value);
+ return this;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateDefaultInterpolation#interpolate
+ * @methodOf pascalprecht.translate.$translateDefaultInterpolation
+ *
+ * @description
+ * Interpolates given string agains given interpolate params using angulars
+ * `$interpolate` service.
+ *
+ * @returns {string} interpolated string.
+ */
+ $translateInterpolator.interpolate = function (string, interpolationParams) {
+ interpolationParams = interpolationParams || {};
+ interpolationParams = $translateSanitization.sanitize(interpolationParams, 'params');
+
+ var interpolatedText = $interpolate(string)(interpolationParams);
+ interpolatedText = $translateSanitization.sanitize(interpolatedText, 'text');
+
+ return interpolatedText;
+ };
+
+ return $translateInterpolator;
+}
+$translateDefaultInterpolation.$inject = ['$interpolate', '$translateSanitization'];
+
+$translateDefaultInterpolation.displayName = '$translateDefaultInterpolation';
+
+angular.module('pascalprecht.translate').constant('$STORAGE_KEY', 'NG_TRANSLATE_LANG_KEY');
+
+angular.module('pascalprecht.translate')
+/**
+ * @ngdoc directive
+ * @name pascalprecht.translate.directive:translate
+ * @requires $compile
+ * @requires $filter
+ * @requires $interpolate
+ * @restrict A
+ *
+ * @description
+ * Translates given translation id either through attribute or DOM content.
+ * Internally it uses `translate` filter to translate translation id. It possible to
+ * pass an optional `translate-values` object literal as string into translation id.
+ *
+ * @param {string=} translate Translation id which could be either string or interpolated string.
+ * @param {string=} translate-values Values to pass into translation id. Can be passed as object literal string or interpolated object.
+ * @param {string=} translate-attr-ATTR translate Translation id and put it into ATTR attribute.
+ * @param {string=} translate-default will be used unless translation was successful
+ * @param {boolean=} translate-compile (default true if present) defines locally activation of {@link pascalprecht.translate.$translateProvider#methods_usePostCompiling}
+ *
+ * @example
+ TRANSLATION_ID+ + +
{{translationId}}
+
+ WITH_VALUES+ +
WITH_VALUES+ + +
TRANSLATION_ID
')($rootScope); + $rootScope.$digest(); + expect(element.text()).toBe('Hello there!'); + + element = $compile('{{translationId}}
')($rootScope); + $rootScope.$digest(); + expect(element.text()).toBe('Hello there!'); + + element = $compile('')($rootScope); + $rootScope.$digest(); + expect(element.attr('title')).toBe('Hello there!'); + }); + }); +{{ 'TRANSLATION_ID' | translate }}
+ {{ translationId | translate }}
+ {{ 'WITH_VALUES' | translate:'{value: 5}' }}
+ {{ 'WITH_VALUES' | translate:values }}
+
+