/*! * angular-translate - v2.18.1 - 2018-05-19 * * Copyright (c) 2018 The angular-translate team, Pascal Precht; Licensed MIT */ (function (root, factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module unless amdModuleId is set define([], function () { return (factory()); }); } else if (typeof module === 'object' && module.exports) { // Node. Does not work with strict CommonJS, but // only CommonJS-like environments that support module.exports, // like Node. module.exports = factory(); } else { factory(); } }(this, function () { /** * @ngdoc overview * @name pascalprecht.translate * * @description * The main module which holds everything together. */ runTranslate.$inject = ['$translate']; $translate.$inject = ['$STORAGE_KEY', '$windowProvider', '$translateSanitizationProvider', 'pascalprechtTranslateOverrider']; $translateDefaultInterpolation.$inject = ['$interpolate', '$translateSanitization']; translateDirective.$inject = ['$translate', '$interpolate', '$compile', '$parse', '$rootScope']; translateAttrDirective.$inject = ['$translate', '$rootScope']; translateCloakDirective.$inject = ['$translate', '$rootScope']; translateFilterFactory.$inject = ['$parse', '$translate']; $translationCache.$inject = ['$cacheFactory']; 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.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, $sce, 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} langKey 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;
};
/**
* @ngdoc function
* @name pascalprecht.translate.$translateProvider#nestedObjectDelimeter
* @methodOf pascalprecht.translate.$translateProvider
*
* @description
*
* Let's you change the delimiter for namespaced translations.
* Default delimiter is `.`.
*
* @param {string} delimiter namespace separator
*/
this.nestedObjectDelimeter = function (delimiter) {
if (!delimiter) {
return $nestedObjectDelimeter;
}
$nestedObjectDelimeter = delimiter;
return this;
};
/**
* @name flatObject
* @private
*
* @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($nestedObjectDelimeter) + $nestedObjectDelimeter + key) : key;
if (path.length && key === prevKey) {
// Create shortcut path (foo.bar == foo.bar.bar)
keyWithShortPath = '' + path.join($nestedObjectDelimeter);
// 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**.
* It is recommended to call this after {@link pascalprecht.translate.$translate#fallbackLanguage fallbackLanguage()}.
*
* @param {string} langKey A language key.
*/
this.preferredLanguage = function (langKey) {
if (langKey) {
setupPreferredLanguage(langKey);
return this;
}
return $preferredLanguage;
};
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#resolveClientLocale
* @methodOf pascalprecht.translate.$translateProvider
*
* @description
* This returns the current browser/client's language key. The result is processed with the configured uniform tag resolver.
*
* @returns {string} the current client/browser language key
*/
this.resolveClientLocale = function () {
return getLocale();
};
/**
* @ngdoc function
* @name pascalprecht.translate.$translateProvider#storageKey
* @methodOf pascalprecht.translate.$translateProvider
*
* @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 => en
* en-US => en-US
* en_US => en-US
* en-us => en-US
* sr-latn => sr-Latn
* sr-latn-rs => sr-Latn-RS
*
* 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) || 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.$translationCache $translationCache}.
* When false the cache will be disabled (default). When true or undefined
* the cache will be a default (see $cacheFactory). When an object it will
* be treat as a cache object itself: the usage is $http({cache: cache})
*
* @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 function
* @name pascalprecht.translate.$translateProvider#postProcess
* @methodOf pascalprecht.translate.$translateProvider
*
* @description
* The post processor will be intercept right after the translation result. It can modify the result.
*
* @param {object} fn Function or service name (string) to be called after the translation value has been set / resolved. The function itself will enrich every value being processed and then continue the normal resolver process
*/
this.postProcess = function (fn) {
if (fn) {
postProcessFn = fn;
} else {
postProcessFn = undefined;
}
return this;
};
/**
* @ngdoc function
* @name pascalprecht.translate.$translateProvider#keepContent
* @methodOf pascalprecht.translate.$translateProvider
*
* @description
* If keepContent is set to true than translate directive will always use innerHTML
* as a default translation
*
* Example:
*
* app.config(function ($translateProvider) {
* $translateProvider.keepContent(true);
* });
*
*
* @param {boolean} value - valid values are true or false
*/
this.keepContent = function (value) {
$keepContent = !(!value);
return this;
};
/**
* @ngdoc object
* @name pascalprecht.translate.$translate
* @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=undefined] The id of the interpolation to use (use default unless set via useInterpolation())
* @param {string=} [defaultTranslationText=undefined] the optional default translation text that is written as
* as default text in case it is not found in any configured language
* @param {string=} [forceLanguage=false] A language to be used instead of the current language
* @param {string=} [sanitizeStrategy=undefined] force sanitize strategy for this call instead of using the configured one (use default unless set)
* @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, forceLanguage, sanitizeStrategy) {
if (!$uses && $preferredLanguage) {
$uses = $preferredLanguage;
}
var uses = (forceLanguage && forceLanguage !== $uses) ? // we don't want to re-negotiate $uses
(negotiateLocale(forceLanguage) || forceLanguage) : $uses;
// Check forceLanguage is present
if (forceLanguage) {
loadTranslationsIfMissing(forceLanguage);
}
// Duck detection: If the first argument is an array, a bunch of translations was requested.
// The result is an object.
if (angular.isArray(translationId)) {
// Inspired by Q.allSettled by Kris Kowal
// https://github.com/kriskowal/q/blob/b0fa72980717dc202ffc3cbf03b936e10ebbb9d7/q.js#L1553-1563
// This transforms all promises regardless resolved or rejected
var translateAll = function (translationIds) {
var results = {}; // storing the actual results
var promises = []; // promises to wait for
// Wraps the promise a) being always resolved and b) storing the link id->value
var translate = function (translationId) {
var deferred = $q.defer();
var regardless = function (value) {
results[translationId] = value;
deferred.resolve([translationId, value]);
};
// we don't care whether the promise was resolved or rejected; just store the values
$translate(translationId, interpolateParams, interpolationId, defaultTranslationText, forceLanguage, sanitizeStrategy).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 = langPromises[uses] || langPromises[$preferredLanguage];
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, uses, sanitizeStrategy).then(deferred.resolve, deferred.reject);
} else {
var promiseResolved = function () {
// $uses may have changed while waiting
if (!forceLanguage) {
uses = $uses;
}
determineTranslation(translationId, interpolateParams, interpolationId, defaultTranslationText, uses, sanitizeStrategy).then(deferred.resolve, deferred.reject);
};
promiseResolved.displayName = 'promiseResolved';
promiseToWaitFor['finally'](promiseResolved)['catch'](angular.noop); // we don't care about errors here, already handled
}
return deferred.promise;
};
/**
* @name applyNotFoundIndicators
* @private
*
* @description
* Applies not fount indicators to given translation id, if needed.
* This function gets only executed, if a translation id doesn't exist,
* which is why a translation id is expected as argument.
*
* @param {string} translationId Translation id.
* @returns {string} Same as given translation id but applied with not found
* indicators.
*/
var applyNotFoundIndicators = function (translationId) {
// applying notFoundIndicators
if ($notFoundIndicatorLeft) {
translationId = [$notFoundIndicatorLeft, translationId].join(' ');
}
if ($notFoundIndicatorRight) {
translationId = [translationId, $notFoundIndicatorRight].join(' ');
}
return translationId;
};
/**
* @name useLanguage
* @private
*
* @description
* Makes actual use of a language by setting a given language key as used
* language and informs registered interpolators to also use the given
* key as locale.
*
* @param {string} key Locale key.
*/
var useLanguage = function (key) {
$uses = key;
// make sure to store new language key before triggering success event
if ($storageFactory) {
Storage.put($translate.storageKey(), $uses);
}
$rootScope.$emit('$translateChangeSuccess', {language : key});
// inform default interpolator
defaultInterpolator.setLocale($uses);
var eachInterpolator = function (interpolator, id) {
interpolatorHashMap[id].setLocale($uses);
};
eachInterpolator.displayName = 'eachInterpolatorLocaleSetter';
// inform all others too!
angular.forEach(interpolatorHashMap, eachInterpolator);
$rootScope.$emit('$translateChangeEnd', {language : key});
};
/**
* @name loadAsync
* @private
*
* @description
* Kicks off registered async loader using `$injector` and applies existing
* loader options. When resolved, it updates translation tables accordingly
* or rejects with given language key.
*
* @param {string} key Language key.
* @return {Promise} A promise.
*/
var loadAsync = function (key) {
if (!key) {
throw 'No language key specified for loading.';
}
var deferred = $q.defer();
$rootScope.$emit('$translateLoadingStart', {language : key});
pendingLoader = true;
var cache = loaderCache;
if (typeof(cache) === 'string') {
// getting on-demand instance of loader
cache = $injector.get(cache);
}
var loaderOptions = angular.extend({}, $loaderOptions, {
key : key,
$http : angular.extend({}, {
cache : cache
}, $loaderOptions.$http)
});
var onLoaderSuccess = function (data) {
var translationTable = {};
$rootScope.$emit('$translateLoadingSuccess', {language : key});
if (angular.isArray(data)) {
angular.forEach(data, function (table) {
angular.extend(translationTable, flatObject(table));
});
} else {
angular.extend(translationTable, flatObject(data));
}
pendingLoader = false;
deferred.resolve({
key : key,
table : translationTable
});
$rootScope.$emit('$translateLoadingEnd', {language : key});
};
onLoaderSuccess.displayName = 'onLoaderSuccess';
var onLoaderError = function (key) {
$rootScope.$emit('$translateLoadingError', {language : key});
deferred.reject(key);
$rootScope.$emit('$translateLoadingEnd', {language : key});
};
onLoaderError.displayName = 'onLoaderError';
$injector.get($loaderFactory)(loaderOptions)
.then(onLoaderSuccess, onLoaderError);
return deferred.promise;
};
if ($storageFactory) {
Storage = $injector.get($storageFactory);
if (!Storage.get || !Storage.put) {
throw new Error('Couldn\'t use storage \'' + $storageFactory + '\', missing get() or put() method!');
}
}
// if we have additional interpolations that were added via
// $translateProvider.addInterpolation(), we have to map'em
if ($interpolatorFactories.length) {
var eachInterpolationFactory = function (interpolatorFactory) {
var interpolator = $injector.get(interpolatorFactory);
// setting initial locale for each interpolation service
interpolator.setLocale($preferredLanguage || $uses);
// make'em recognizable through id
interpolatorHashMap[interpolator.getInterpolationIdentifier()] = interpolator;
};
eachInterpolationFactory.displayName = 'interpolationFactoryAdder';
angular.forEach($interpolatorFactories, eachInterpolationFactory);
}
/**
* @name getTranslationTable
* @private
*
* @description
* Returns a promise that resolves to the translation table
* or is rejected if an error occurred.
*
* @param langKey
* @returns {Q.promise}
*/
var getTranslationTable = function (langKey) {
var deferred = $q.defer();
if (Object.prototype.hasOwnProperty.call($translationTable, langKey)) {
deferred.resolve($translationTable[langKey]);
} else if (langPromises[langKey]) {
var onResolve = function (data) {
translations(data.key, data.table);
deferred.resolve(data.table);
};
onResolve.displayName = 'translationTableResolver';
langPromises[langKey].then(onResolve, deferred.reject);
} else {
deferred.reject();
}
return deferred.promise;
};
/**
* @name getFallbackTranslation
* @private
*
* @description
* Returns a promise that will resolve to the translation
* or be rejected if no translation was found for the language.
* This function is currently only used for fallback language translation.
*
* @param langKey The language to translate to.
* @param translationId
* @param interpolateParams
* @param Interpolator
* @param sanitizeStrategy
* @returns {Q.promise}
*/
var getFallbackTranslation = function (langKey, translationId, interpolateParams, Interpolator, sanitizeStrategy) {
var deferred = $q.defer();
var onResolve = function (translationTable) {
if (Object.prototype.hasOwnProperty.call(translationTable, translationId) && translationTable[translationId] !== null) {
Interpolator.setLocale(langKey);
var translation = translationTable[translationId];
if (translation.substr(0, 2) === '@:') {
getFallbackTranslation(langKey, translation.substr(2), interpolateParams, Interpolator, sanitizeStrategy)
.then(deferred.resolve, deferred.reject);
} else {
var interpolatedValue = Interpolator.interpolate(translationTable[translationId], interpolateParams, 'service', sanitizeStrategy, translationId);
interpolatedValue = applyPostProcessing(translationId, translationTable[translationId], interpolatedValue, interpolateParams, langKey);
deferred.resolve(interpolatedValue);
}
Interpolator.setLocale($uses);
} else {
deferred.reject();
}
};
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
* @param sanitizeStrategy sanitize strategy override
*
* @returns {string} translation
*/
var getFallbackTranslationInstant = function (langKey, translationId, interpolateParams, Interpolator, sanitizeStrategy) {
var result, translationTable = $translationTable[langKey];
if (translationTable && Object.prototype.hasOwnProperty.call(translationTable, translationId) && translationTable[translationId] !== null) {
Interpolator.setLocale(langKey);
result = Interpolator.interpolate(translationTable[translationId], interpolateParams, 'filter', sanitizeStrategy, translationId);
result = applyPostProcessing(translationId, translationTable[translationId], result, interpolateParams, langKey, sanitizeStrategy);
// workaround for TrustedValueHolderType
if (!angular.isString(result) && angular.isFunction(result.$$unwrapTrustedValue)) {
var result2 = result.$$unwrapTrustedValue();
if (result2.substr(0, 2) === '@:') {
return getFallbackTranslationInstant(langKey, result2.substr(2), interpolateParams, Interpolator, sanitizeStrategy);
}
} else if (result.substr(0, 2) === '@:') {
return getFallbackTranslationInstant(langKey, result.substr(2), interpolateParams, Interpolator, sanitizeStrategy);
}
Interpolator.setLocale($uses);
}
return result;
};
/**
* @name translateByHandler
* @private
*
* Translate by missing translation handler.
*
* @param translationId
* @param interpolateParams
* @param defaultTranslationText
* @param sanitizeStrategy sanitize strategy override
*
* @returns translation created by $missingTranslationHandler or translationId is $missingTranslationHandler is
* absent
*/
var translateByHandler = function (translationId, interpolateParams, defaultTranslationText, sanitizeStrategy) {
// If we have a handler factory - we might also call it here to determine if it provides
// a default text for a translationid that can't be found anywhere in our tables
if ($missingTranslationHandlerFactory) {
return $injector.get($missingTranslationHandlerFactory)(translationId, $uses, interpolateParams, defaultTranslationText, sanitizeStrategy);
} else {
return translationId;
}
};
/**
* @name resolveForFallbackLanguage
* @private
*
* Recursive helper function for fallbackTranslation that will sequentially look
* for a translation in the fallbackLanguages starting with fallbackLanguageIndex.
*
* @param fallbackLanguageIndex
* @param translationId
* @param interpolateParams
* @param Interpolator
* @param defaultTranslationText
* @param sanitizeStrategy
* @returns {Q.promise} Promise that will resolve to the translation.
*/
var resolveForFallbackLanguage = function (fallbackLanguageIndex, translationId, interpolateParams, Interpolator, defaultTranslationText, sanitizeStrategy) {
var deferred = $q.defer();
if (fallbackLanguageIndex < $fallbackLanguage.length) {
var langKey = $fallbackLanguage[fallbackLanguageIndex];
getFallbackTranslation(langKey, translationId, interpolateParams, Interpolator, sanitizeStrategy).then(
function (data) {
deferred.resolve(data);
},
function () {
// Look in the next fallback language for a translation.
// It delays the resolving by passing another promise to resolve.
return resolveForFallbackLanguage(fallbackLanguageIndex + 1, translationId, interpolateParams, Interpolator, defaultTranslationText, sanitizeStrategy).then(deferred.resolve, deferred.reject);
}
);
} else {
// No translation found in any fallback language
// if a default translation text is set in the directive, then return this as a result
if (defaultTranslationText) {
deferred.resolve(defaultTranslationText);
} else {
var missingTranslationHandlerTranslation = translateByHandler(translationId, interpolateParams, defaultTranslationText);
// if no default translation is set and an error handler is defined, send it to the handler
// and then return the result if it isn't undefined
if ($missingTranslationHandlerFactory && missingTranslationHandlerTranslation) {
deferred.resolve(missingTranslationHandlerTranslation);
} else {
deferred.reject(applyNotFoundIndicators(translationId));
}
}
}
return deferred.promise;
};
/**
* @name resolveForFallbackLanguageInstant
* @private
*
* Recursive helper function for fallbackTranslation that will sequentially look
* for a translation in the fallbackLanguages starting with fallbackLanguageIndex.
*
* @param fallbackLanguageIndex
* @param translationId
* @param interpolateParams
* @param Interpolator
* @param sanitizeStrategy
* @returns {string} translation
*/
var resolveForFallbackLanguageInstant = function (fallbackLanguageIndex, translationId, interpolateParams, Interpolator, sanitizeStrategy) {
var result;
if (fallbackLanguageIndex < $fallbackLanguage.length) {
var langKey = $fallbackLanguage[fallbackLanguageIndex];
result = getFallbackTranslationInstant(langKey, translationId, interpolateParams, Interpolator, sanitizeStrategy);
if (!result && result !== '') {
result = resolveForFallbackLanguageInstant(fallbackLanguageIndex + 1, translationId, interpolateParams, Interpolator);
}
}
return result;
};
/**
* Translates with the usage of the fallback languages.
*
* @param translationId
* @param interpolateParams
* @param Interpolator
* @param defaultTranslationText
* @param sanitizeStrategy
* @returns {Q.promise} Promise, that resolves to the translation.
*/
var fallbackTranslation = function (translationId, interpolateParams, Interpolator, defaultTranslationText, sanitizeStrategy) {
// Start with the fallbackLanguage with index 0
return resolveForFallbackLanguage((startFallbackIteration > 0 ? startFallbackIteration : fallbackIndex), translationId, interpolateParams, Interpolator, defaultTranslationText, sanitizeStrategy);
};
/**
* Translates with the usage of the fallback languages.
*
* @param translationId
* @param interpolateParams
* @param Interpolator
* @param sanitizeStrategy
* @returns {String} translation
*/
var fallbackTranslationInstant = function (translationId, interpolateParams, Interpolator, sanitizeStrategy) {
// Start with the fallbackLanguage with index 0
return resolveForFallbackLanguageInstant((startFallbackIteration > 0 ? startFallbackIteration : fallbackIndex), translationId, interpolateParams, Interpolator, sanitizeStrategy);
};
var determineTranslation = function (translationId, interpolateParams, interpolationId, defaultTranslationText, uses, sanitizeStrategy) {
var deferred = $q.defer();
var table = uses ? $translationTable[uses] : $translationTable,
Interpolator = (interpolationId) ? interpolatorHashMap[interpolationId] : defaultInterpolator;
// if the translation id exists, we can just interpolate it
if (table && Object.prototype.hasOwnProperty.call(table, translationId) && table[translationId] !== null) {
var translation = table[translationId];
// If using link, rerun $translate with linked translationId and return it
if (translation.substr(0, 2) === '@:') {
$translate(translation.substr(2), interpolateParams, interpolationId, defaultTranslationText, uses, sanitizeStrategy)
.then(deferred.resolve, deferred.reject);
} else {
//
var resolvedTranslation = Interpolator.interpolate(translation, interpolateParams, 'service', sanitizeStrategy, translationId);
resolvedTranslation = applyPostProcessing(translationId, translation, resolvedTranslation, interpolateParams, uses);
deferred.resolve(resolvedTranslation);
}
} else {
var missingTranslationHandlerTranslation;
// for logging purposes only (as in $translateMissingTranslationHandlerLog), value is not returned to promise
if ($missingTranslationHandlerFactory && !pendingLoader) {
missingTranslationHandlerTranslation = translateByHandler(translationId, interpolateParams, defaultTranslationText);
}
// since we couldn't translate the inital requested translation id,
// we try it now with one or more fallback languages, if fallback language(s) is
// configured.
if (uses && $fallbackLanguage && $fallbackLanguage.length) {
fallbackTranslation(translationId, interpolateParams, Interpolator, defaultTranslationText, sanitizeStrategy)
.then(function (translation) {
deferred.resolve(translation);
}, function (_translationId) {
deferred.reject(applyNotFoundIndicators(_translationId));
});
} else if ($missingTranslationHandlerFactory && !pendingLoader && missingTranslationHandlerTranslation) {
// looks like the requested translation id doesn't exists.
// Now, if there is a registered handler for missing translations and no
// asyncLoader is pending, we execute the handler
if (defaultTranslationText) {
deferred.resolve(defaultTranslationText);
} else {
deferred.resolve(missingTranslationHandlerTranslation);
}
} else {
if (defaultTranslationText) {
deferred.resolve(defaultTranslationText);
} else {
deferred.reject(applyNotFoundIndicators(translationId));
}
}
}
return deferred.promise;
};
var determineTranslationInstant = function (translationId, interpolateParams, interpolationId, uses, sanitizeStrategy) {
var result, table = uses ? $translationTable[uses] : $translationTable,
Interpolator = defaultInterpolator;
// if the interpolation id exists use custom interpolator
if (interpolatorHashMap && Object.prototype.hasOwnProperty.call(interpolatorHashMap, interpolationId)) {
Interpolator = interpolatorHashMap[interpolationId];
}
// if the translation id exists, we can just interpolate it
if (table && Object.prototype.hasOwnProperty.call(table, translationId) && table[translationId] !== null) {
var translation = table[translationId];
// If using link, rerun $translate with linked translationId and return it
if (translation.substr(0, 2) === '@:') {
result = determineTranslationInstant(translation.substr(2), interpolateParams, interpolationId, uses, sanitizeStrategy);
} else {
result = Interpolator.interpolate(translation, interpolateParams, 'filter', sanitizeStrategy, translationId);
result = applyPostProcessing(translationId, translation, result, interpolateParams, uses, sanitizeStrategy);
}
} else {
var missingTranslationHandlerTranslation;
// for logging purposes only (as in $translateMissingTranslationHandlerLog), value is not returned to promise
if ($missingTranslationHandlerFactory && !pendingLoader) {
missingTranslationHandlerTranslation = translateByHandler(translationId, interpolateParams, sanitizeStrategy);
}
// since we couldn't translate the inital requested translation id,
// we try it now with one or more fallback languages, if fallback language(s) is
// configured.
if (uses && $fallbackLanguage && $fallbackLanguage.length) {
fallbackIndex = 0;
result = fallbackTranslationInstant(translationId, interpolateParams, Interpolator, sanitizeStrategy);
} else if ($missingTranslationHandlerFactory && !pendingLoader && missingTranslationHandlerTranslation) {
// looks like the requested translation id doesn't exists.
// Now, if there is a registered handler for missing translations and no
// asyncLoader is pending, we execute the handler
result = missingTranslationHandlerTranslation;
} else {
result = applyNotFoundIndicators(translationId);
}
}
return result;
};
var clearNextLangAndPromise = function (key) {
if ($nextLang === key) {
$nextLang = undefined;
}
langPromises[key] = undefined;
};
var applyPostProcessing = function (translationId, translation, resolvedTranslation, interpolateParams, uses, sanitizeStrategy) {
var fn = postProcessFn;
if (fn) {
if (typeof(fn) === 'string') {
// getting on-demand instance
fn = $injector.get(fn);
}
if (fn) {
return fn(translationId, translation, resolvedTranslation, interpolateParams, uses, sanitizeStrategy);
}
}
return resolvedTranslation;
};
var loadTranslationsIfMissing = function (key) {
if (!$translationTable[key] && $loaderFactory && !langPromises[key]) {
langPromises[key] = loadAsync(key).then(function (translation) {
translations(translation.key, translation.table);
return translation;
});
}
};
/**
* @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#nestedObjectDelimeter
* @methodOf pascalprecht.translate.$translate
*
* @description
* Returns the configured delimiter for nested namespaces.
*
* @return {string} nestedObjectDelimeter
*/
$translate.nestedObjectDelimeter = function () {
return $nestedObjectDelimeter;
};
/**
* @ngdoc function
* @name pascalprecht.translate.$translate#fallbackLanguage
* @methodOf pascalprecht.translate.$translate
*
* @description
* Returns the language key for the fallback languages or sets a new fallback stack.
* It is recommended to call this before {@link pascalprecht.translate.$translateProvider#preferredLanguage preferredLanguage()}.
*
* @param {string=} langKey language String or Array of fallback languages to be used (to change stack at runtime)
*
* @return {string||array} fallback language key
*/
$translate.fallbackLanguage = function (langKey) {
if (langKey !== undefined && langKey !== null) {
fallbackStack(langKey);
// as we might have an async loader initiated and a new translation language might have been defined
// we need to add the promise to the stack also. So - iterate.
if ($loaderFactory) {
if ($fallbackLanguage && $fallbackLanguage.length) {
for (var i = 0, len = $fallbackLanguage.length; i < len; i++) {
if (!langPromises[$fallbackLanguage[i]]) {
langPromises[$fallbackLanguage[i]] = loadAsync($fallbackLanguage[i]);
}
}
}
}
$translate.use($translate.use());
}
if ($fallbackWasString) {
return $fallbackLanguage[0];
} else {
return $fallbackLanguage;
}
};
/**
* @ngdoc function
* @name pascalprecht.translate.$translate#useFallbackLanguage
* @methodOf pascalprecht.translate.$translate
*
* @description
* Sets the first key of the fallback language stack to be used for translation.
* Therefore all languages in the fallback array BEFORE this key will be skipped!
*
* @param {string=} langKey Contains the langKey the iteration shall start with. Set to false if you want to
* get back to the whole stack
*/
$translate.useFallbackLanguage = function (langKey) {
if (langKey !== undefined && langKey !== null) {
if (!langKey) {
startFallbackIteration = 0;
} else {
var langKeyPosition = indexOf($fallbackLanguage, langKey);
if (langKeyPosition > -1) {
startFallbackIteration = langKeyPosition;
}
}
}
};
/**
* @ngdoc function
* @name pascalprecht.translate.$translate#proposedLanguage
* @methodOf pascalprecht.translate.$translate
*
* @description
* Returns the language key of language that is currently loaded asynchronously.
*
* @return {string} language key
*/
$translate.proposedLanguage = function () {
return $nextLang;
};
/**
* @ngdoc function
* @name pascalprecht.translate.$translate#storage
* @methodOf pascalprecht.translate.$translate
*
* @description
* Returns registered storage.
*
* @return {object} Storage
*/
$translate.storage = function () {
return Storage;
};
/**
* @ngdoc function
* @name pascalprecht.translate.$translate#negotiateLocale
* @methodOf pascalprecht.translate.$translate
*
* @description
* Returns a language key based on available languages and language aliases. If a
* language key cannot be resolved, returns undefined.
*
* If no or a falsy key is given, returns undefined.
*
* @param {string} [key] Language key
* @return {string|undefined} Language key or undefined if no language key is found.
*/
$translate.negotiateLocale = negotiateLocale;
/**
* @ngdoc function
* @name pascalprecht.translate.$translate#use
* @methodOf pascalprecht.translate.$translate
*
* @description
* Tells angular-translate which language to use by given language key. This method is
* used to change language at runtime. It also takes care of storing the language
* key in a configured store to let your app remember the choosed language.
*
* When trying to 'use' a language which isn't available it tries to load it
* asynchronously with registered loaders.
*
* Returns promise object with loaded language file data or string of the currently used language.
*
* If no or a falsy key is given it returns the currently used language key.
* The returned string will be ```undefined``` if setting up $translate hasn't finished.
* @example
* $translate.use("en_US").then(function(data){
* $scope.text = $translate("HELLO");
* });
*
* @param {string=} key Language key
* @return {object|string} Promise with loaded language data or the language key if a falsy param was given.
*/
$translate.use = function (key) {
if (!key) {
return $uses;
}
var deferred = $q.defer();
deferred.promise.then(null, angular.noop); // AJS "Possibly unhandled rejection"
$rootScope.$emit('$translateChangeStart', {language : key});
// Try to get the aliased language key
var aliasedKey = negotiateLocale(key);
// Ensure only registered language keys will be loaded
if ($availableLanguageKeys.length > 0 && !aliasedKey) {
return $q.reject(key);
}
if (aliasedKey) {
key = aliasedKey;
}
// if there isn't a translation table for the language we've requested,
// we load it asynchronously
$nextLang = key;
if (($forceAsyncReloadEnabled || !$translationTable[key]) && $loaderFactory && !langPromises[key]) {
langPromises[key] = loadAsync(key).then(function (translation) {
translations(translation.key, translation.table);
deferred.resolve(translation.key);
if ($nextLang === key) {
useLanguage(translation.key);
}
return translation;
}, function (key) {
$rootScope.$emit('$translateChangeError', {language : key});
deferred.reject(key);
$rootScope.$emit('$translateChangeEnd', {language : key});
return $q.reject(key);
});
langPromises[key]['finally'](function () {
clearNextLangAndPromise(key);
})['catch'](angular.noop); // we don't care about errors (clearing)
} else if (langPromises[key]) {
// we are already loading this asynchronously
// resolve our new deferred when the old langPromise is resolved
langPromises[key].then(function (translation) {
if ($nextLang === translation.key) {
useLanguage(translation.key);
}
deferred.resolve(translation.key);
return translation;
}, function (key) {
// find first available fallback language if that request has failed
if (!$uses && $fallbackLanguage && $fallbackLanguage.length > 0 && $fallbackLanguage[0] !== key) {
return $translate.use($fallbackLanguage[0]).then(deferred.resolve, deferred.reject);
} else {
return deferred.reject(key);
}
});
} else {
deferred.resolve(key);
useLanguage(key);
}
return deferred.promise;
};
/**
* @ngdoc function
* @name pascalprecht.translate.$translate#resolveClientLocale
* @methodOf pascalprecht.translate.$translate
*
* @description
* This returns the current browser/client's language key. The result is processed with the configured uniform tag resolver.
*
* @returns {string} the current client/browser language key
*/
$translate.resolveClientLocale = function () {
return getLocale();
};
/**
* @ngdoc function
* @name pascalprecht.translate.$translate#storageKey
* @methodOf pascalprecht.translate.$translate
*
* @description
* Returns the key for the storage.
*
* @return {string} storage key
*/
$translate.storageKey = function () {
return storageKey();
};
/**
* @ngdoc function
* @name pascalprecht.translate.$translate#isPostCompilingEnabled
* @methodOf pascalprecht.translate.$translate
*
* @description
* Returns whether post compiling is enabled or not
*
* @return {bool} storage key
*/
$translate.isPostCompilingEnabled = function () {
return $postCompilingEnabled;
};
/**
* @ngdoc function
* @name pascalprecht.translate.$translate#isForceAsyncReloadEnabled
* @methodOf pascalprecht.translate.$translate
*
* @description
* Returns whether force async reload is enabled or not
*
* @return {boolean} forceAsyncReload value
*/
$translate.isForceAsyncReloadEnabled = function () {
return $forceAsyncReloadEnabled;
};
/**
* @ngdoc function
* @name pascalprecht.translate.$translate#isKeepContent
* @methodOf pascalprecht.translate.$translate
*
* @description
* Returns whether keepContent or not
*
* @return {boolean} keepContent value
*/
$translate.isKeepContent = function () {
return $keepContent;
};
/**
* @ngdoc function
* @name pascalprecht.translate.$translate#refresh
* @methodOf pascalprecht.translate.$translate
*
* @description
* Refreshes a translation table pointed by the given langKey. If langKey is not specified,
* the module will drop all existent translation tables and load new version of those which
* are currently in use.
*
* Refresh means that the module will drop target translation table and try to load it again.
*
* In case there are no loaders registered the refresh() method will throw an Error.
*
* If the module is able to refresh translation tables refresh() method will broadcast
* $translateRefreshStart and $translateRefreshEnd events.
*
* @example
* // this will drop all currently existent translation tables and reload those which are
* // currently in use
* $translate.refresh();
* // this will refresh a translation table for the en_US language
* $translate.refresh('en_US');
*
* @param {string} langKey A language key of the table, which has to be refreshed
*
* @return {promise} Promise, which will be resolved in case a translation tables refreshing
* process is finished successfully, and reject if not.
*/
$translate.refresh = function (langKey) {
if (!$loaderFactory) {
throw new Error('Couldn\'t refresh translation table, no loader registered!');
}
$rootScope.$emit('$translateRefreshStart', {language : langKey});
var deferred = $q.defer(), updatedLanguages = {};
//private helper
function loadNewData(languageKey) {
var promise = loadAsync(languageKey);
//update the load promise cache for this language
langPromises[languageKey] = promise;
//register a data handler for the promise
promise.then(function (data) {
//clear the cache for this language
$translationTable[languageKey] = {};
//add the new data for this language
translations(languageKey, data.table);
//track that we updated this language
updatedLanguages[languageKey] = true;
},
//handle rejection to appease the $q validation
angular.noop);
return promise;
}
//set up post-processing
deferred.promise.then(
function () {
for (var key in $translationTable) {
if ($translationTable.hasOwnProperty(key)) {
//delete cache entries that were not updated
if (!(key in updatedLanguages)) {
delete $translationTable[key];
}
}
}
if ($uses) {
useLanguage($uses);
}
},
//handle rejection to appease the $q validation
angular.noop
)['finally'](
function () {
$rootScope.$emit('$translateRefreshEnd', {language : langKey});
}
);
if (!langKey) {
// if there's no language key specified we refresh ALL THE THINGS!
var languagesToReload = $fallbackLanguage && $fallbackLanguage.slice() || [];
if ($uses && languagesToReload.indexOf($uses) === -1) {
languagesToReload.push($uses);
}
$q.all(languagesToReload.map(loadNewData)).then(deferred.resolve, deferred.reject);
} else if ($translationTable[langKey]) {
//just refresh the specified language cache
loadNewData(langKey).then(deferred.resolve, deferred.reject);
} else {
deferred.reject();
}
return deferred.promise;
};
/**
* @ngdoc function
* @name pascalprecht.translate.$translate#instant
* @methodOf pascalprecht.translate.$translate
*
* @description
* Returns a translation instantly from the internal state of loaded translation. All rules
* regarding the current language, the preferred language of even fallback languages will be
* used except any promise handling. If a language was not found, an asynchronous loading
* will be invoked in the background.
*
* @param {string|array} translationId A token which represents a translation id
* This can be optionally an array of translation ids which
* results that the function's promise returns an object where
* each key is the translation id and the value the translation.
* @param {object=} [interpolateParams={}] Params
* @param {string=} [interpolationId=undefined] The id of the interpolation to use (use default unless set via useInterpolation())
* @param {string=} [forceLanguage=false] A language to be used instead of the current language
* @param {string=} [sanitizeStrategy=undefined] force sanitize strategy for this call instead of using the configured one (use default unless set)
*
* @return {string|object} translation
*/
$translate.instant = function (translationId, interpolateParams, interpolationId, forceLanguage, sanitizeStrategy) {
// we don't want to re-negotiate $uses
var uses = (forceLanguage && forceLanguage !== $uses) ? // we don't want to re-negotiate $uses
(negotiateLocale(forceLanguage) || forceLanguage) : $uses;
// Detect undefined and null values to shorten the execution and prevent exceptions
if (translationId === null || angular.isUndefined(translationId)) {
return translationId;
}
// Check forceLanguage is present
if (forceLanguage) {
loadTranslationsIfMissing(forceLanguage);
}
// Duck detection: If the first argument is an array, a bunch of translations was requested.
// The result is an object.
if (angular.isArray(translationId)) {
var results = {};
for (var i = 0, c = translationId.length; i < c; i++) {
results[translationId[i]] = $translate.instant(translationId[i], interpolateParams, interpolationId, forceLanguage, sanitizeStrategy);
}
return results;
}
// We discarded unacceptable values. So we just need to verify if translationId is empty String
if (angular.isString(translationId) && translationId.length < 1) {
return translationId;
}
// trim off any whitespace
if (translationId) {
translationId = trim.apply(translationId);
}
var result, possibleLangKeys = [];
if ($preferredLanguage) {
possibleLangKeys.push($preferredLanguage);
}
if (uses) {
possibleLangKeys.push(uses);
}
if ($fallbackLanguage && $fallbackLanguage.length) {
possibleLangKeys = possibleLangKeys.concat($fallbackLanguage);
}
for (var j = 0, d = possibleLangKeys.length; j < d; j++) {
var possibleLangKey = possibleLangKeys[j];
if ($translationTable[possibleLangKey]) {
if (typeof $translationTable[possibleLangKey][translationId] !== 'undefined') {
result = determineTranslationInstant(translationId, interpolateParams, interpolationId, uses, sanitizeStrategy);
}
}
if (typeof result !== 'undefined') {
break;
}
}
if (!result && result !== '') {
if ($notFoundIndicatorLeft || $notFoundIndicatorRight) {
result = applyNotFoundIndicators(translationId);
} else {
// Return translation of default interpolator if not found anything.
result = defaultInterpolator.interpolate(translationId, interpolateParams, 'filter', sanitizeStrategy);
// looks like the requested translation id doesn't exists.
// Now, if there is a registered handler for missing translations and no
// asyncLoader is pending, we execute the handler
var missingTranslationHandlerTranslation;
if ($missingTranslationHandlerFactory && !pendingLoader) {
missingTranslationHandlerTranslation = translateByHandler(translationId, interpolateParams, sanitizeStrategy);
}
if ($missingTranslationHandlerFactory && !pendingLoader && missingTranslationHandlerTranslation) {
result = missingTranslationHandlerTranslation;
}
}
}
return result;
};
/**
* @ngdoc function
* @name pascalprecht.translate.$translate#versionInfo
* @methodOf pascalprecht.translate.$translate
*
* @description
* Returns the current version information for the angular-translate library
*
* @return {string} angular-translate version
*/
$translate.versionInfo = function () {
return version;
};
/**
* @ngdoc function
* @name pascalprecht.translate.$translate#loaderCache
* @methodOf pascalprecht.translate.$translate
*
* @description
* Returns the defined loaderCache.
*
* @return {boolean|string|object} current value of loaderCache
*/
$translate.loaderCache = function () {
return loaderCache;
};
// internal purpose only
$translate.directivePriority = function () {
return directivePriority;
};
// internal purpose only
$translate.statefulFilter = function () {
return statefulFilter;
};
/**
* @ngdoc function
* @name pascalprecht.translate.$translate#isReady
* @methodOf pascalprecht.translate.$translate
*
* @description
* Returns whether the service is "ready" to translate (i.e. loading 1st language).
*
* See also {@link pascalprecht.translate.$translate#methods_onReady onReady()}.
*
* @return {boolean} current value of ready
*/
$translate.isReady = function () {
return $isReady;
};
var $onReadyDeferred = $q.defer();
$onReadyDeferred.promise.then(function () {
$isReady = true;
});
/**
* @ngdoc function
* @name pascalprecht.translate.$translate#onReady
* @methodOf pascalprecht.translate.$translate
*
* @description
* Calls the function provided or resolved the returned promise after the service is "ready" to translate (i.e. loading 1st language).
*
* See also {@link pascalprecht.translate.$translate#methods_isReady isReady()}.
*
* @param {Function=} fn Function to invoke when service is ready
* @return {object} Promise resolved when service is ready
*/
$translate.onReady = function (fn) {
var deferred = $q.defer();
if (angular.isFunction(fn)) {
deferred.promise.then(fn);
}
if ($isReady) {
deferred.resolve();
} else {
$onReadyDeferred.promise.then(deferred.resolve);
}
return deferred.promise;
};
/**
* @ngdoc function
* @name pascalprecht.translate.$translate#getAvailableLanguageKeys
* @methodOf pascalprecht.translate.$translate
*
* @description
* This function simply returns the registered language keys being defined before in the config phase
* With this, an application can use the array to provide a language selection dropdown or similar
* without any additional effort
*
* @returns {object} returns the list of possibly registered language keys and mapping or null if not defined
*/
$translate.getAvailableLanguageKeys = function () {
if ($availableLanguageKeys.length > 0) {
return $availableLanguageKeys;
}
return null;
};
/**
* @ngdoc function
* @name pascalprecht.translate.$translate#getTranslationTable
* @methodOf pascalprecht.translate.$translate
*
* @description
* Returns translation table by the given language key.
*
* Unless a language is provided it returns a translation table of the current one.
* Note: If translation dictionary is currently downloading or in progress
* it will return null.
*
* @param {string} langKey A token which represents a translation id
*
* @return {object} a copy of angular-translate $translationTable
*/
$translate.getTranslationTable = function (langKey) {
langKey = langKey || $translate.use();
if (langKey && $translationTable[langKey]) {
return angular.copy($translationTable[langKey]);
}
return null;
};
// Whenever $translateReady is being fired, this will ensure the state of $isReady
var globalOnReadyListener = $rootScope.$on('$translateReady', function () {
$onReadyDeferred.resolve();
globalOnReadyListener(); // one time only
globalOnReadyListener = null;
});
var globalOnChangeListener = $rootScope.$on('$translateChangeEnd', function () {
$onReadyDeferred.resolve();
globalOnChangeListener(); // one time only
globalOnChangeListener = null;
});
if ($loaderFactory) {
// If at least one async loader is defined and there are no
// (default) translations available we should try to load them.
if (angular.equals($translationTable, {})) {
if ($translate.use()) {
$translate.use($translate.use());
}
}
// Also, if there are any fallback language registered, we start
// loading them asynchronously as soon as we can.
if ($fallbackLanguage && $fallbackLanguage.length) {
var processAsyncResult = function (translation) {
translations(translation.key, translation.table);
$rootScope.$emit('$translateChangeEnd', {language : translation.key});
return translation;
};
for (var i = 0, len = $fallbackLanguage.length; i < len; i++) {
var fallbackLanguageId = $fallbackLanguage[i];
if ($forceAsyncReloadEnabled || !$translationTable[fallbackLanguageId]) {
langPromises[fallbackLanguageId] = loadAsync(fallbackLanguageId).then(processAsyncResult);
}
}
}
} else {
$rootScope.$emit('$translateReady', {language : $translate.use()});
}
return $translate;
}];
}
$translate.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 value agains given interpolate params using angulars
* `$interpolate` service.
*
* Since AngularJS 1.5, `value` must not be a string but can be anything input.
*
* @param {string} value translation
* @param {object} [interpolationParams={}] interpolation params
* @param {string} [context=undefined] current context (filter, directive, service)
* @param {string} [sanitizeStrategy=undefined] sanitize strategy (use default unless set)
* @param {string} translationId current translationId
*
* @returns {string} interpolated string
*/
$translateInterpolator.interpolate = function (value, interpolationParams, context, sanitizeStrategy, translationId) { // jshint ignore:line
interpolationParams = interpolationParams || {};
interpolationParams = $translateSanitization.sanitize(interpolationParams, 'params', sanitizeStrategy, context);
var interpolatedText;
if (angular.isNumber(value)) {
// numbers are safe
interpolatedText = '' + value;
} else if (angular.isString(value)) {
// strings must be interpolated (that's the job here)
interpolatedText = $interpolate(value)(interpolationParams);
interpolatedText = $translateSanitization.sanitize(interpolatedText, 'text', sanitizeStrategy, context);
} else {
// neither a number or a string, cant interpolate => empty string
interpolatedText = '';
}
return interpolatedText;
};
return $translateInterpolator;
}
$translateDefaultInterpolation.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 $interpolate,
* @requires $compile,
* @requires $parse,
* @requires $rootScope
* @restrict AE
*
* @description
* Translates given translation id either through attribute or DOM content.
* Internally it uses $translate service to translate the translation id. It possible to
* pass an optional `translate-values` object literal as string into translation id.
*
* @param {string=} translate Translation id which could be either string or interpolated string.
* @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 {string=} translate-sanitize-strategy defines locally sanitize strategy
* @param {boolean=} translate-compile (default true if present) defines locally activation of {@link pascalprecht.translate.$translateProvider#methods_usePostCompiling}
* @param {boolean=} translate-keep-content (default true if present) defines that in case a KEY could not be translated, that the existing content is left in the innerHTML}
*
* @example
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!'); element = $compile('')($rootScope); $rootScope.$digest(); expect(element.text()).toBe('The interpolation key is camel cased: Hello'); }); });{{ 'TRANSLATION_ID' | translate }}
{{ translationId | translate }}
{{ 'WITH_VALUES' | translate:'{value: 5}' }}
{{ 'WITH_VALUES' | translate:values }}