|
|
/*! * angular-translate - v2.12.1 - 2016-09-15 * * Copyright (c) 2016 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 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 () {
/** * @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: * <dl> * <dt>sanitize</dt> * <dd>Sanitizes HTML in the translation text using $sanitize</dd> * <dt>escape</dt> * <dd>Escapes HTML in the translation</dd> * <dt>sanitizeParameters</dt> * <dd>Sanitizes HTML in the values of the interpolation parameters using $sanitize</dd> * <dt>escapeParameters</dt> * <dd>Escapes HTML in the values of the interpolation parameters</dd> * <dt>escaped</dt> * <dd>Support legacy strategy name 'escaped' for backwards compatibility (will be removed in 3.0)</dd> * </dl> * */
strategies = { sanitize: function (value, mode/*, context*/) { if (mode === 'text') { value = htmlSanitizeValue(value); } return value; }, escape: function (value, mode/*, context*/) { if (mode === 'text') { value = htmlEscapeValue(value); } return value; }, sanitizeParameters: function (value, mode/*, context*/) { if (mode === 'params') { value = mapInterpolationParameters(value, htmlSanitizeValue); } return value; }, escapeParameters: function (value, mode/*, context*/) { if (mode === 'params') { value = mapInterpolationParameters(value, htmlEscapeValue); } return value; }, sce: function (value, mode, context) { if (mode === 'text') { value = htmlTrustValue(value); } else if (mode === 'params') { if (context !== 'filter') { // do html escape in filter context #1101
value = mapInterpolationParameters(value, htmlEscapeValue); } } return value; }, sceParameters: function (value, mode/*, context*/) { if (mode === 'params') { value = mapInterpolationParameters(value, htmlTrustValue); } return value; } }; // Support legacy strategy name 'escaped' for backwards compatibility.
// TODO should be removed in 3.0
strategies.escaped = strategies.escapeParameters;
/** * @ngdoc function * @name pascalprecht.translate.$translateSanitizationProvider#addStrategy * @methodOf pascalprecht.translate.$translateSanitizationProvider * * @description * Adds a sanitization strategy to the list of known strategies. * * @param {string} strategyName - unique key for a strategy * @param {StrategyFunction} strategyFunction - strategy function * @returns {object} this */ this.addStrategy = function (strategyName, strategyFunction) { strategies[strategyName] = strategyFunction; return this; };
/** * @ngdoc function * @name pascalprecht.translate.$translateSanitizationProvider#removeStrategy * @methodOf pascalprecht.translate.$translateSanitizationProvider * * @description * Removes a sanitization strategy from the list of known strategies. * * @param {string} strategyName - unique key for a strategy * @returns {object} this */ this.removeStrategy = function (strategyName) { delete strategies[strategyName]; return this; };
/** * @ngdoc function * @name pascalprecht.translate.$translateSanitizationProvider#useStrategy * @methodOf pascalprecht.translate.$translateSanitizationProvider * * @description * Selects a sanitization strategy. When an array is provided the strategies will be executed in order. * * @param {string|StrategyFunction|array} strategy The sanitization strategy / strategies which should be used. Either a name of an existing strategy, a custom strategy function, or an array consisting of multiple names and / or custom functions. * @returns {object} this */ this.useStrategy = function (strategy) { hasConfiguredStrategy = true; currentStrategy = strategy; return this; };
/** * @ngdoc object * @name pascalprecht.translate.$translateSanitization * @requires $injector * @requires $log * * @description * Sanitizes interpolation parameters and translated texts. * */ this.$get = ['$injector', '$log', function ($injector, $log) {
var cachedStrategyMap = {};
var applyStrategies = function (value, mode, context, selectedStrategies) { angular.forEach(selectedStrategies, function (selectedStrategy) { if (angular.isFunction(selectedStrategy)) { value = selectedStrategy(value, mode, context); } else if (angular.isFunction(strategies[selectedStrategy])) { value = strategies[selectedStrategy](value, mode, context); } else if (angular.isString(strategies[selectedStrategy])) { if (!cachedStrategyMap[strategies[selectedStrategy]]) { try { cachedStrategyMap[strategies[selectedStrategy]] = $injector.get(strategies[selectedStrategy]); } catch (e) { cachedStrategyMap[strategies[selectedStrategy]] = function() {}; throw new Error('pascalprecht.translate.$translateSanitization: Unknown sanitization strategy: \'' + selectedStrategy + '\''); } } value = cachedStrategyMap[strategies[selectedStrategy]](value, mode, context); } else { throw new Error('pascalprecht.translate.$translateSanitization: Unknown sanitization strategy: \'' + selectedStrategy + '\''); } }); return value; };
// TODO: should be removed in 3.0
var showNoStrategyConfiguredWarning = function () { if (!hasConfiguredStrategy && !hasShownNoStrategyConfiguredWarning) { $log.warn('pascalprecht.translate.$translateSanitization: No sanitization strategy has been configured. This can have serious security implications. See http://angular-translate.github.io/docs/#/guide/19_security for details.'); hasShownNoStrategyConfiguredWarning = true; } };
if ($injector.has('$sanitize')) { $sanitize = $injector.get('$sanitize'); } if ($injector.has('$sce')) { $sce = $injector.get('$sce'); }
return { /** * @ngdoc function * @name pascalprecht.translate.$translateSanitization#useStrategy * @methodOf pascalprecht.translate.$translateSanitization * * @description * Selects a sanitization strategy. When an array is provided the strategies will be executed in order. * * @param {string|StrategyFunction|array} strategy The sanitization strategy / strategies which should be used. Either a name of an existing strategy, a custom strategy function, or an array consisting of multiple names and / or custom functions. */ useStrategy: (function (self) { return function (strategy) { self.useStrategy(strategy); }; })(this),
/** * @ngdoc function * @name pascalprecht.translate.$translateSanitization#sanitize * @methodOf pascalprecht.translate.$translateSanitization * * @description * Sanitizes a value. * * @param {string|object} value The value which should be sanitized. * @param {string} mode The current sanitization mode, either 'params' or 'text'. * @param {string|StrategyFunction|array} [strategy] Optional custom strategy which should be used instead of the currently selected strategy. * @param {string} [context] The context of this call: filter, service. Default is service * @returns {string|object} sanitized value */ sanitize: function (value, mode, strategy, context) { if (!currentStrategy) { showNoStrategyConfiguredWarning(); }
if (!strategy && strategy !== null) { strategy = currentStrategy; }
if (!strategy) { return value; }
if (!context) { context = 'service'; }
var selectedStrategies = angular.isArray(strategy) ? strategy : [strategy]; return applyStrategies(value, mode, context, selectedStrategies); } }; }];
var htmlEscapeValue = function (value) { var element = angular.element('<div></div>'); element.text(value); // not chainable, see #1044
return element.html(); };
var htmlSanitizeValue = function (value) { if (!$sanitize) { throw new Error('pascalprecht.translate.$translateSanitization: Error cannot find $sanitize service. Either include the ngSanitize module (https://docs.angularjs.org/api/ngSanitize) or use a sanitization strategy which does not depend on $sanitize, such as \'escape\'.'); } return $sanitize(value); };
var htmlTrustValue = function (value) { if (!$sce) { throw new Error('pascalprecht.translate.$translateSanitization: Error cannot find $sce service.'); } return $sce.trustAsHtml(value); };
var mapInterpolationParameters = function (value, iteratee, stack) { if (angular.isDate(value)) { return value; } else if (angular.isObject(value)) { var result = angular.isArray(value) ? [] : {};
if (!stack) { stack = []; } else { if (stack.indexOf(value) > -1) { throw new Error('pascalprecht.translate.$translateSanitization: Error cannot interpolate parameter due recursive object'); } }
stack.push(value); angular.forEach(value, function (propertyValue, propertyKey) {
/* Skipping function properties. */ if (angular.isFunction(propertyValue)) { return; }
result[propertyKey] = mapInterpolationParameters(propertyValue, iteratee, stack); }); stack.splice(-1, 1); // remove last
return result; } else if (angular.isNumber(value)) { return value; } else { return iteratee(value); } }; }
/** * @ngdoc object * @name pascalprecht.translate.$translateProvider * @description * * $translateProvider allows developers to register translation-tables, asynchronous loaders * and similar to configure translation behavior directly inside of a module. * */ angular.module('pascalprecht.translate') .constant('pascalprechtTranslateOverrider', {}) .provider('$translate', $translate);
function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvider, pascalprechtTranslateOverrider) {
'use strict';
var $translationTable = {}, $preferredLanguage, $availableLanguageKeys = [], $languageKeyAliases, $fallbackLanguage, $fallbackWasString, $uses, $nextLang, $storageFactory, $storageKey = $STORAGE_KEY, $storagePrefix, $missingTranslationHandlerFactory, $interpolationFactory, $interpolatorFactories = [], $loaderFactory, $cloakClassName = 'translate-cloak', $loaderOptions, $notFoundIndicatorLeft, $notFoundIndicatorRight, $postCompilingEnabled = false, $forceAsyncReloadEnabled = false, $nestedObjectDelimeter = '.', $isReady = false, $keepContent = false, loaderCache, directivePriority = 0, statefulFilter = true, postProcessFn, uniformLanguageTagResolver = 'default', languageTagResolver = { 'default': function (tag) { return (tag || '').split('-').join('_'); }, java: function (tag) { var temp = (tag || '').split('-').join('_'); var parts = temp.split('_'); return parts.length > 1 ? (parts[0].toLowerCase() + '_' + parts[1].toUpperCase()) : temp; }, bcp47: function (tag) { var temp = (tag || '').split('_').join('-'); var parts = temp.split('-'); return parts.length > 1 ? (parts[0].toLowerCase() + '-' + parts[1].toUpperCase()) : temp; }, 'iso639-1': function (tag) { var temp = (tag || '').split('_').join('-'); var parts = temp.split('-'); return parts[0].toLowerCase(); } };
var version = '2.12.1';
// tries to determine the browsers language
var getFirstBrowserLanguage = function () {
// internal purpose only
if (angular.isFunction(pascalprechtTranslateOverrider.getLocale)) { return pascalprechtTranslateOverrider.getLocale(); }
var nav = $windowProvider.$get().navigator, browserLanguagePropertyKeys = ['language', 'browserLanguage', 'systemLanguage', 'userLanguage'], i, language;
// support for HTML 5.1 "navigator.languages"
if (angular.isArray(nav.languages)) { for (i = 0; i < nav.languages.length; i++) { language = nav.languages[i]; if (language && language.length) { return language; } } }
// support for other well known properties in browsers
for (i = 0; i < browserLanguagePropertyKeys.length; i++) { language = nav[browserLanguagePropertyKeys[i]]; if (language && language.length) { return language; } }
return null; }; getFirstBrowserLanguage.displayName = 'angular-translate/service: getFirstBrowserLanguage';
// tries to determine the browsers locale
var getLocale = function () { var locale = getFirstBrowserLanguage() || ''; if (languageTagResolver[uniformLanguageTagResolver]) { locale = languageTagResolver[uniformLanguageTagResolver](locale); } return locale; }; getLocale.displayName = 'angular-translate/service: getLocale';
/** * @name indexOf * @private * * @description * indexOf polyfill. Kinda sorta. * * @param {array} array Array to search in. * @param {string} searchElement Element to search for. * * @returns {int} Index of search element. */ var indexOf = function(array, searchElement) { for (var i = 0, len = array.length; i < len; i++) { if (array[i] === searchElement) { return i; } } return -1; };
/** * @name trim * @private * * @description * trim polyfill * * @returns {string} The string stripped of whitespace from both ends */ var trim = function() { return this.toString().replace(/^\s+|\s+$/g, ''); };
var negotiateLocale = function (preferred) { if(!preferred) { return; }
var avail = [], locale = angular.lowercase(preferred), i = 0, n = $availableLanguageKeys.length;
for (; i < n; i++) { avail.push(angular.lowercase($availableLanguageKeys[i])); }
// Check for an exact match in our list of available keys
if (indexOf(avail, locale) > -1) { return preferred; }
if ($languageKeyAliases) { var alias; for (var langKeyAlias in $languageKeyAliases) { if ($languageKeyAliases.hasOwnProperty(langKeyAlias)) { var hasWildcardKey = false; var hasExactKey = Object.prototype.hasOwnProperty.call($languageKeyAliases, langKeyAlias) && angular.lowercase(langKeyAlias) === angular.lowercase(preferred);
if (langKeyAlias.slice(-1) === '*') { hasWildcardKey = langKeyAlias.slice(0, -1) === preferred.slice(0, langKeyAlias.length - 1); } if (hasExactKey || hasWildcardKey) { alias = $languageKeyAliases[langKeyAlias]; if (indexOf(avail, angular.lowercase(alias)) > -1) { return alias; } } } } }
// Check for a language code without region
var parts = preferred.split('_');
if (parts.length > 1 && indexOf(avail, angular.lowercase(parts[0])) > -1) { return parts[0]; }
// If everything fails, return undefined.
return; };
/** * @ngdoc function * @name pascalprecht.translate.$translateProvider#translations * @methodOf pascalprecht.translate.$translateProvider * * @description * Registers a new translation table for specific language key. * * To register a translation table for specific language, pass a defined language * key as first parameter. * * <pre> * // register translation table for language: 'de_DE'
* $translateProvider.translations('de_DE', { * 'GREETING': 'Hallo Welt!' * }); * * // register another one
* $translateProvider.translations('en_US', { * 'GREETING': 'Hello world!' * }); * </pre> * * When registering multiple translation tables for for the same language key, * the actual translation table gets extended. This allows you to define module * specific translation which only get added, once a specific module is loaded in * your app. * * Invoking this method with no arguments returns the translation table which was * registered with no language key. Invoking it with a language key returns the * related translation table. * * @param {string} 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**. * * @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: * <pre> * app.config(function ($translateProvider) { * $translateProvider.useMissingTranslationHandler('customHandler'); * }); * * app.factory('customHandler', function (dep1, dep2) { * return function (translationId) { * // something with translationId and dep1 and dep2
* }; * }); * </pre> * * @param {string} factory Factory name */ this.useMissingTranslationHandler = function (factory) { $missingTranslationHandlerFactory = factory; return this; };
/** * @ngdoc function * @name pascalprecht.translate.$translateProvider#usePostCompiling * @methodOf pascalprecht.translate.$translateProvider * * @description * If post compiling is enabled, all translated values will be processed * again with AngularJS' $compile. * * Example: * <pre> * app.config(function ($translateProvider) { * $translateProvider.usePostCompiling(true); * }); * </pre> * * @param {string} factory Factory name */ this.usePostCompiling = function (value) { $postCompilingEnabled = !(!value); return this; };
/** * @ngdoc function * @name pascalprecht.translate.$translateProvider#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: * <pre> * app.config(function ($translateProvider) { * $translateProvider.forceAsyncReload(true); * }); * </pre> * * @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()}. * * <pre> * $translateProvider * .uniformLanguageTag('bcp47') * .determinePreferredLanguage() * </pre> * * 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) || 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: * <pre> * app.config(function ($translateProvider) { * $translateProvider.keepContent(true); * }); * </pre> * * @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. * * <pre> * $translate('HEADLINE_TEXT').then(function (translation) { * $scope.translatedText = translation; * }); * </pre> * * @param {string|array} translationId A token which represents a translation id * This can be optionally an array of translation ids which * results that the function returns an object where each key * is the translation id and the value the translation. * @param {object=} interpolateParams An object hash for dynamic values * @param {string} interpolationId The id of the interpolation to use * @param {string} defaultTranslationText the optional default translation text that is written as * as default text in case it is not found in any configured language * @param {string} forceLanguage A language to be used instead of the current language * @returns {object} promise */ this.$get = [ '$log', '$injector', '$rootScope', '$q', function ($log, $injector, $rootScope, $q) {
var Storage, defaultInterpolator = $injector.get($interpolationFactory || '$translateDefaultInterpolation'), pendingLoader = false, interpolatorHashMap = {}, langPromises = {}, fallbackIndex, startFallbackIteration;
var $translate = function (translationId, interpolateParams, interpolationId, defaultTranslationText, forceLanguage) { if (!$uses && $preferredLanguage) { $uses = $preferredLanguage; } var uses = (forceLanguage && forceLanguage !== $uses) ? // we don't want to re-negotiate $uses
(negotiateLocale(forceLanguage) || forceLanguage) : $uses;
// 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).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, uses).then(deferred.resolve, deferred.reject); } else { var promiseResolved = function () { // $uses may have changed while waiting
if (!forceLanguage) { uses = $uses; } determineTranslation(translationId, interpolateParams, interpolationId, defaultTranslationText, uses).then(deferred.resolve, deferred.reject); }; promiseResolved.displayName = 'promiseResolved';
promiseToWaitFor['finally'](promiseResolved); } 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 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 { var interpolatedValue = Interpolator.interpolate(translationTable[translationId], interpolateParams, 'service'); 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 * @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, 'filter'); result = applyPostProcessing(translationId, translationTable[translationId], result, interpolateParams, langKey); 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 * @param interpolateParams * @param defaultTranslationText * @returns translation created by $missingTranslationHandler or translationId is $missingTranslationHandler is * absent */ var translateByHandler = function (translationId, interpolateParams, defaultTranslationText) { // 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, defaultTranslationText); 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( 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).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 { // if no default translation is set and an error handler is defined, send it to the handler
// and then return the result
if ($missingTranslationHandlerFactory) { deferred.resolve(translateByHandler(translationId, interpolateParams)); } else { deferred.reject(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, uses) {
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, uses) .then(deferred.resolve, deferred.reject); } else { //
var resolvedTranslation = Interpolator.interpolate(translation, interpolateParams, 'service'); 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) .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) {
var result, table = uses ? $translationTable[uses] : $translationTable, Interpolator = defaultInterpolator;
// if the interpolation id exists use custom interpolator
if (interpolatorHashMap && Object.prototype.hasOwnProperty.call(interpolatorHashMap, interpolationId)) { Interpolator = interpolatorHashMap[interpolationId]; }
// if the translation id exists, we can just interpolate it
if (table && Object.prototype.hasOwnProperty.call(table, translationId)) { var translation = table[translationId];
// If using link, rerun $translate with linked translationId and return it
if (translation.substr(0, 2) === '@:') { result = determineTranslationInstant(translation.substr(2), interpolateParams, interpolationId, uses); } else { result = Interpolator.interpolate(translation, interpolateParams, 'filter'); result = applyPostProcessing(translationId, translation, result, interpolateParams, uses); } } 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); } }
return result; };
var clearNextLangAndPromise = function(key) { if ($nextLang === key) { $nextLang = undefined; } langPromises[key] = undefined; };
var applyPostProcessing = function (translationId, translation, resolvedTranslation, interpolateParams, uses) { 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); } }
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. * * @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();
$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); }); } 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!'); }
var deferred = $q.defer();
function resolve() { deferred.resolve(); $rootScope.$emit('$translateRefreshEnd', {language: langKey}); }
function reject() { deferred.reject(); $rootScope.$emit('$translateRefreshEnd', {language: langKey}); }
$rootScope.$emit('$translateRefreshStart', {language: langKey});
if (!langKey) { // if there's no language key specified we refresh ALL THE THINGS!
var tables = [], loadingKeys = {};
// reload registered fallback languages
if ($fallbackLanguage && $fallbackLanguage.length) { for (var i = 0, len = $fallbackLanguage.length; i < len; i++) { tables.push(loadAsync($fallbackLanguage[i])); loadingKeys[$fallbackLanguage[i]] = true; } }
// reload currently used language
if ($uses && !loadingKeys[$uses]) { tables.push(loadAsync($uses)); }
var allTranslationsLoaded = function (tableData) { $translationTable = {}; angular.forEach(tableData, function (data) { translations(data.key, data.table); }); if ($uses) { useLanguage($uses); } resolve(); }; allTranslationsLoaded.displayName = 'refreshPostProcessor';
$q.all(tables).then(allTranslationsLoaded, reject);
} else if ($translationTable[langKey]) {
var oneTranslationsLoaded = function (data) { translations(data.key, data.table); if (langKey === $uses) { useLanguage($uses); } resolve(); return data; }; 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 * @param {string} forceLanguage A language to be used instead of the current language * * @return {string|object} translation */ $translate.instant = function (translationId, interpolateParams, interpolationId, forceLanguage) {
// 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); } 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); } } 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'); if ($missingTranslationHandlerFactory && !pendingLoader) { result = translateByHandler(translationId, interpolateParams); } } }
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 * Returns whether 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; };
// 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. * * @returns {string} interpolated string. */ $translateInterpolator.interpolate = function (value, interpolationParams, context) { interpolationParams = interpolationParams || {}; interpolationParams = $translateSanitization.sanitize(interpolationParams, 'params', undefined, 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', undefined, 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 {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 <example module="ngView"> <file name="index.html"> <div ng-controller="TranslateCtrl">
<pre translate="TRANSLATION_ID"></pre> <pre translate>TRANSLATION_ID</pre> <pre translate translate-attr-title="TRANSLATION_ID"></pre> <pre translate="{{translationId}}"></pre> <pre translate>{{translationId}}</pre> <pre translate="WITH_VALUES" translate-values="{value: 5}"></pre> <pre translate translate-values="{value: 5}">WITH_VALUES</pre> <pre translate="WITH_VALUES" translate-values="{{values}}"></pre> <pre translate translate-values="{{values}}">WITH_VALUES</pre> <pre translate translate-attr-title="WITH_VALUES" translate-values="{{values}}"></pre> <pre translate="WITH_CAMEL_CASE_KEY" translate-value-camel-case-key="Hi"></pre>
</div> </file> <file name="script.js"> angular.module('ngView', ['pascalprecht.translate'])
.config(function ($translateProvider) {
$translateProvider.translations('en',{ 'TRANSLATION_ID': 'Hello there!', 'WITH_VALUES': 'The following value is dynamic: {{value}}', 'WITH_CAMEL_CASE_KEY': 'The interpolation key is camel cased: {{camelCaseKey}}' }).preferredLanguage('en');
});
angular.module('ngView').controller('TranslateCtrl', function ($scope) { $scope.translationId = 'TRANSLATION_ID';
$scope.values = { value: 78 }; }); </file> <file name="scenario.js"> it('should translate', function () { inject(function ($rootScope, $compile) { $rootScope.translationId = 'TRANSLATION_ID';
element = $compile('<p translate="TRANSLATION_ID"></p>')($rootScope); $rootScope.$digest(); expect(element.text()).toBe('Hello there!');
element = $compile('<p translate="{{translationId}}"></p>')($rootScope); $rootScope.$digest(); expect(element.text()).toBe('Hello there!');
element = $compile('<p translate>TRANSLATION_ID</p>')($rootScope); $rootScope.$digest(); expect(element.text()).toBe('Hello there!');
element = $compile('<p translate>{{translationId}}</p>')($rootScope); $rootScope.$digest(); expect(element.text()).toBe('Hello there!');
element = $compile('<p translate translate-attr-title="TRANSLATION_ID"></p>')($rootScope); $rootScope.$digest(); expect(element.attr('title')).toBe('Hello there!');
element = $compile('<p translate="WITH_CAMEL_CASE_KEY" translate-value-camel-case-key="Hello"></p>')($rootScope); $rootScope.$digest(); expect(element.text()).toBe('The interpolation key is camel cased: Hello'); }); }); </file> </example> */ .directive('translate', translateDirective); function translateDirective($translate, $interpolate, $compile, $parse, $rootScope) {
'use strict';
/** * @name trim * @private * * @description * trim polyfill * * @returns {string} The string stripped of whitespace from both ends */ var trim = function() { return this.toString().replace(/^\s+|\s+$/g, ''); };
return { restrict: 'AE', scope: true, priority: $translate.directivePriority(), compile: function (tElement, tAttr) {
var translateValuesExist = (tAttr.translateValues) ? tAttr.translateValues : undefined;
var translateInterpolation = (tAttr.translateInterpolation) ? tAttr.translateInterpolation : undefined;
var translateValueExist = tElement[0].outerHTML.match(/translate-value-+/i);
var interpolateRegExp = '^(.*)(' + $interpolate.startSymbol() + '.*' + $interpolate.endSymbol() + ')(.*)', watcherRegExp = '^(.*)' + $interpolate.startSymbol() + '(.*)' + $interpolate.endSymbol() + '(.*)';
return function linkFn(scope, iElement, iAttr) {
scope.interpolateParams = {}; scope.preText = ''; scope.postText = ''; scope.translateNamespace = getTranslateNamespace(scope); var translationIds = {};
var initInterpolationParams = function (interpolateParams, iAttr, tAttr) { // initial setup
if (iAttr.translateValues) { angular.extend(interpolateParams, $parse(iAttr.translateValues)(scope.$parent)); } // initially fetch all attributes if existing and fill the params
if (translateValueExist) { for (var attr in tAttr) { if (Object.prototype.hasOwnProperty.call(iAttr, attr) && attr.substr(0, 14) === 'translateValue' && attr !== 'translateValues') { var attributeName = angular.lowercase(attr.substr(14, 1)) + attr.substr(15); interpolateParams[attributeName] = tAttr[attr]; } } } };
// Ensures any change of the attribute "translate" containing the id will
// be re-stored to the scope's "translationId".
// If the attribute has no content, the element's text value (white spaces trimmed off) will be used.
var observeElementTranslation = function (translationId) {
// Remove any old watcher
if (angular.isFunction(observeElementTranslation._unwatchOld)) { observeElementTranslation._unwatchOld(); observeElementTranslation._unwatchOld = undefined; }
if (angular.equals(translationId , '') || !angular.isDefined(translationId)) { var iElementText = trim.apply(iElement.text());
// Resolve translation id by inner html if required
var interpolateMatches = iElementText.match(interpolateRegExp); // Interpolate translation id if required
if (angular.isArray(interpolateMatches)) { scope.preText = interpolateMatches[1]; scope.postText = interpolateMatches[3]; translationIds.translate = $interpolate(interpolateMatches[2])(scope.$parent); var watcherMatches = iElementText.match(watcherRegExp); if (angular.isArray(watcherMatches) && watcherMatches[2] && watcherMatches[2].length) { observeElementTranslation._unwatchOld = scope.$watch(watcherMatches[2], function (newValue) { translationIds.translate = newValue; updateTranslations(); }); } } else { // do not assigne the translation id if it is empty.
translationIds.translate = !iElementText ? undefined : iElementText; } } else { translationIds.translate = translationId; } updateTranslations(); };
var observeAttributeTranslation = function (translateAttr) { iAttr.$observe(translateAttr, function (translationId) { translationIds[translateAttr] = translationId; updateTranslations(); }); };
// initial setup with values
initInterpolationParams(scope.interpolateParams, iAttr, tAttr);
var firstAttributeChangedEvent = true; iAttr.$observe('translate', function (translationId) { if (typeof translationId === 'undefined') { // case of element "<translate>xyz</translate>"
observeElementTranslation(''); } else { // case of regular attribute
if (translationId !== '' || !firstAttributeChangedEvent) { translationIds.translate = translationId; updateTranslations(); } } firstAttributeChangedEvent = false; });
for (var translateAttr in iAttr) { if (iAttr.hasOwnProperty(translateAttr) && translateAttr.substr(0, 13) === 'translateAttr' && translateAttr.length > 13) { observeAttributeTranslation(translateAttr); } }
iAttr.$observe('translateDefault', function (value) { scope.defaultText = value; updateTranslations(); });
if (translateValuesExist) { iAttr.$observe('translateValues', function (interpolateParams) { if (interpolateParams) { scope.$parent.$watch(function () { angular.extend(scope.interpolateParams, $parse(interpolateParams)(scope.$parent)); }); } }); }
if (translateValueExist) { var observeValueAttribute = function (attrName) { iAttr.$observe(attrName, function (value) { var attributeName = angular.lowercase(attrName.substr(14, 1)) + attrName.substr(15); scope.interpolateParams[attributeName] = value; }); }; for (var attr in iAttr) { if (Object.prototype.hasOwnProperty.call(iAttr, attr) && attr.substr(0, 14) === 'translateValue' && attr !== 'translateValues') { observeValueAttribute(attr); } } }
// Master update function
var updateTranslations = function () { for (var key in translationIds) { if (translationIds.hasOwnProperty(key) && translationIds[key] !== undefined) { updateTranslation(key, translationIds[key], scope, scope.interpolateParams, scope.defaultText, scope.translateNamespace); } } };
// Put translation processing function outside loop
var updateTranslation = function(translateAttr, translationId, scope, interpolateParams, defaultTranslationText, translateNamespace) { if (translationId) { // if translation id starts with '.' and translateNamespace given, prepend namespace
if (translateNamespace && translationId.charAt(0) === '.') { translationId = translateNamespace + translationId; }
$translate(translationId, interpolateParams, translateInterpolation, defaultTranslationText, scope.translateLanguage) .then(function (translation) { applyTranslation(translation, scope, true, translateAttr); }, function (translationId) { applyTranslation(translationId, scope, false, translateAttr); }); } else { // as an empty string cannot be translated, we can solve this using successful=false
applyTranslation(translationId, scope, false, translateAttr); } };
var applyTranslation = function (value, scope, successful, translateAttr) { if (!successful) { if (typeof scope.defaultText !== 'undefined') { value = scope.defaultText; } } if (translateAttr === 'translate') { // default translate into innerHTML
if (successful || (!successful && !$translate.isKeepContent() && typeof iAttr.translateKeepContent === 'undefined')) { iElement.empty().append(scope.preText + value + scope.postText); } var globallyEnabled = $translate.isPostCompilingEnabled(); var locallyDefined = typeof tAttr.translateCompile !== 'undefined'; var locallyEnabled = locallyDefined && tAttr.translateCompile !== 'false'; if ((globallyEnabled && !locallyDefined) || locallyEnabled) { $compile(iElement.contents())(scope); } } else { // translate attribute
var attributeName = iAttr.$attr[translateAttr]; if (attributeName.substr(0, 5) === 'data-') { // ensure html5 data prefix is stripped
attributeName = attributeName.substr(5); } attributeName = attributeName.substr(15); iElement.attr(attributeName, value); } };
if (translateValuesExist || translateValueExist || iAttr.translateDefault) { scope.$watch('interpolateParams', updateTranslations, true); }
// Replaced watcher on translateLanguage with event listener
scope.$on('translateLanguageChanged', updateTranslations);
// Ensures the text will be refreshed after the current language was changed
// w/ $translate.use(...)
var unbind = $rootScope.$on('$translateChangeSuccess', updateTranslations);
// ensure translation will be looked up at least one
if (iElement.text().length) { if (iAttr.translate) { observeElementTranslation(iAttr.translate); } else { observeElementTranslation(''); } } else if (iAttr.translate) { // ensure attribute will be not skipped
observeElementTranslation(iAttr.translate); } updateTranslations(); scope.$on('$destroy', unbind); }; } }; }
/** * Returns the scope's namespace. * @private * @param scope * @returns {string} */ function getTranslateNamespace(scope) { 'use strict'; if (scope.translateNamespace) { return scope.translateNamespace; } if (scope.$parent) { return getTranslateNamespace(scope.$parent); } }
translateDirective.displayName = 'translateDirective';
angular.module('pascalprecht.translate') /** * @ngdoc directive * @name pascalprecht.translate.directive:translate-attr * @restrict A * * @description * Translates attributes like translate-attr-ATTR, but with an object like ng-class. * Internally it uses `translate` service to translate translation id. It possible to * pass an optional `translate-values` object literal as string into translation id. * * @param {string=} translate-attr Object literal mapping attributes to translation ids. * @param {string=} translate-values Values to pass into the translation ids. Can be passed as object literal string. * * @example <example module="ngView"> <file name="index.html"> <div ng-controller="TranslateCtrl">
<input translate-attr="{ placeholder: translationId, title: 'WITH_VALUES' }" translate-values="{value: 5}" />
</div> </file> <file name="script.js"> angular.module('ngView', ['pascalprecht.translate'])
.config(function ($translateProvider) {
$translateProvider.translations('en',{ 'TRANSLATION_ID': 'Hello there!', 'WITH_VALUES': 'The following value is dynamic: {{value}}', }).preferredLanguage('en');
});
angular.module('ngView').controller('TranslateCtrl', function ($scope) { $scope.translationId = 'TRANSLATION_ID';
$scope.values = { value: 78 }; }); </file> <file name="scenario.js"> it('should translate', function () { inject(function ($rootScope, $compile) { $rootScope.translationId = 'TRANSLATION_ID';
element = $compile('<input translate-attr="{ placeholder: translationId, title: 'WITH_VALUES' }" translate-values="{ value: 5 }" />')($rootScope); $rootScope.$digest(); expect(element.attr('placeholder)).toBe('Hello there!'); expect(element.attr('title)).toBe('The following value is dynamic: 5'); }); }); </file> </example> */ .directive('translateAttr', translateAttrDirective); function translateAttrDirective($translate, $rootScope) {
'use strict';
return { restrict: 'A', priority: $translate.directivePriority(), link: function linkFn(scope, element, attr) {
var translateAttr, translateValues, previousAttributes = {};
// Main update translations function
var updateTranslations = function () { angular.forEach(translateAttr, function (translationId, attributeName) { if (!translationId) { return; } previousAttributes[attributeName] = true;
// if translation id starts with '.' and translateNamespace given, prepend namespace
if (scope.translateNamespace && translationId.charAt(0) === '.') { translationId = scope.translateNamespace + translationId; } $translate(translationId, translateValues, attr.translateInterpolation, undefined, scope.translateLanguage) .then(function (translation) { element.attr(attributeName, translation); }, function (translationId) { element.attr(attributeName, translationId); }); });
// Removing unused attributes that were previously used
angular.forEach(previousAttributes, function (flag, attributeName) { if (!translateAttr[attributeName]) { element.removeAttr(attributeName); delete previousAttributes[attributeName]; } }); };
// Watch for attribute changes
watchAttribute( scope, attr.translateAttr, function (newValue) { translateAttr = newValue; }, updateTranslations ); // Watch for value changes
watchAttribute( scope, attr.translateValues, function (newValue) { translateValues = newValue; }, updateTranslations );
if (attr.translateValues) { scope.$watch(attr.translateValues, updateTranslations, true); }
// Replaced watcher on translateLanguage with event listener
scope.$on('translateLanguageChanged', updateTranslations);
// Ensures the text will be refreshed after the current language was changed
// w/ $translate.use(...)
var unbind = $rootScope.$on('$translateChangeSuccess', updateTranslations);
updateTranslations(); scope.$on('$destroy', unbind); } }; }
function watchAttribute(scope, attribute, valueCallback, changeCallback) { 'use strict'; if (!attribute) { return; } if (attribute.substr(0, 2) === '::') { attribute = attribute.substr(2); } else { scope.$watch(attribute, function(newValue) { valueCallback(newValue); changeCallback(); }, true); } valueCallback(scope.$eval(attribute)); }
translateAttrDirective.displayName = 'translateAttrDirective';
angular.module('pascalprecht.translate') /** * @ngdoc directive * @name pascalprecht.translate.directive:translateCloak * @requires $rootScope * @requires $translate * @restrict A * * $description * Adds a `translate-cloak` class name to the given element where this directive * is applied initially and removes it, once a loader has finished loading. * * This directive can be used to prevent initial flickering when loading translation * data asynchronously. * * The class name is defined in * {@link pascalprecht.translate.$translateProvider#cloakClassName $translate.cloakClassName()}. * * @param {string=} translate-cloak If a translationId is provided, it will be used for showing * or hiding the cloak. Basically it relies on the translation * resolve. */ .directive('translateCloak', translateCloakDirective);
function translateCloakDirective($translate, $rootScope) {
'use strict';
return { compile: function (tElement) { var applyCloak = function () { tElement.addClass($translate.cloakClassName()); }, removeCloak = function () { tElement.removeClass($translate.cloakClassName()); }; $translate.onReady(function () { removeCloak(); }); applyCloak();
return function linkFn(scope, iElement, iAttr) { if (iAttr.translateCloak && iAttr.translateCloak.length) { // Register a watcher for the defined translation allowing a fine tuned cloak
iAttr.$observe('translateCloak', function (translationId) { $translate(translationId).then(removeCloak, applyCloak); }); // Register for change events as this is being another indicicator revalidating the cloak)
$rootScope.$on('$translateChangeSuccess', function () { $translate(iAttr.translateCloak).then(removeCloak, applyCloak); }); } }; } }; }
translateCloakDirective.displayName = 'translateCloakDirective';
angular.module('pascalprecht.translate') /** * @ngdoc directive * @name pascalprecht.translate.directive:translateNamespace * @restrict A * * @description * Translates given translation id either through attribute or DOM content. * Internally it uses `translate` filter to translate translation id. It possible to * pass an optional `translate-values` object literal as string into translation id. * * @param {string=} translate namespace name which could be either string or interpolated string. * * @example <example module="ngView"> <file name="index.html"> <div translate-namespace="CONTENT">
<div> <h1 translate>.HEADERS.TITLE</h1> <h1 translate>.HEADERS.WELCOME</h1> </div>
<div translate-namespace=".HEADERS"> <h1 translate>.TITLE</h1> <h1 translate>.WELCOME</h1> </div>
</div> </file> <file name="script.js"> angular.module('ngView', ['pascalprecht.translate'])
.config(function ($translateProvider) {
$translateProvider.translations('en',{ 'TRANSLATION_ID': 'Hello there!', 'CONTENT': { 'HEADERS': { TITLE: 'Title' } }, 'CONTENT.HEADERS.WELCOME': 'Welcome' }).preferredLanguage('en');
});
</file> </example> */ .directive('translateNamespace', translateNamespaceDirective);
function translateNamespaceDirective() {
'use strict';
return { restrict: 'A', scope: true, compile: function () { return { pre: function (scope, iElement, iAttrs) { scope.translateNamespace = getTranslateNamespace(scope);
if (scope.translateNamespace && iAttrs.translateNamespace.charAt(0) === '.') { scope.translateNamespace += iAttrs.translateNamespace; } else { scope.translateNamespace = iAttrs.translateNamespace; } } }; } }; }
/** * Returns the scope's namespace. * @private * @param scope * @returns {string} */ function getTranslateNamespace(scope) { 'use strict'; if (scope.translateNamespace) { return scope.translateNamespace; } if (scope.$parent) { return getTranslateNamespace(scope.$parent); } }
translateNamespaceDirective.displayName = 'translateNamespaceDirective';
angular.module('pascalprecht.translate') /** * @ngdoc directive * @name pascalprecht.translate.directive:translateLanguage * @restrict A * * @description * Forces the language to the directives in the underlying scope. * * @param {string=} translate language that will be negotiated. * * @example <example module="ngView"> <file name="index.html"> <div>
<div> <h1 translate>HELLO</h1> </div>
<div translate-language="de"> <h1 translate>HELLO</h1> </div>
</div> </file> <file name="script.js"> angular.module('ngView', ['pascalprecht.translate'])
.config(function ($translateProvider) {
$translateProvider .translations('en',{ 'HELLO': 'Hello world!' }) .translations('de',{ 'HELLO': 'Hallo Welt!' }) .preferredLanguage('en');
});
</file> </example> */ .directive('translateLanguage', translateLanguageDirective);
function translateLanguageDirective() {
'use strict';
return { restrict: 'A', scope: true, compile: function () { return function linkFn(scope, iElement, iAttrs) {
iAttrs.$observe('translateLanguage', function (newTranslateLanguage) { scope.translateLanguage = newTranslateLanguage; });
scope.$watch('translateLanguage', function(){ scope.$broadcast('translateLanguageChanged'); }); }; } }; }
translateLanguageDirective.displayName = 'translateLanguageDirective';
angular.module('pascalprecht.translate') /** * @ngdoc filter * @name pascalprecht.translate.filter:translate * @requires $parse * @requires pascalprecht.translate.$translate * @function * * @description * Uses `$translate` service to translate contents. Accepts interpolate parameters * to pass dynamized values though translation. * * @param {string} translationId A translation id to be translated. * @param {*=} interpolateParams Optional object literal (as hash or string) to pass values into translation. * * @returns {string} Translated text. * * @example <example module="ngView"> <file name="index.html"> <div ng-controller="TranslateCtrl">
<pre>{{ 'TRANSLATION_ID' | translate }}</pre> <pre>{{ translationId | translate }}</pre> <pre>{{ 'WITH_VALUES' | translate:'{value: 5}' }}</pre> <pre>{{ 'WITH_VALUES' | translate:values }}</pre>
</div> </file> <file name="script.js"> angular.module('ngView', ['pascalprecht.translate'])
.config(function ($translateProvider) {
$translateProvider.translations('en', { 'TRANSLATION_ID': 'Hello there!', 'WITH_VALUES': 'The following value is dynamic: {{value}}' }); $translateProvider.preferredLanguage('en');
});
angular.module('ngView').controller('TranslateCtrl', function ($scope) { $scope.translationId = 'TRANSLATION_ID';
$scope.values = { value: 78 }; }); </file> </example> */ .filter('translate', translateFilterFactory);
function translateFilterFactory($parse, $translate) {
'use strict';
var translateFilter = function (translationId, interpolateParams, interpolation, forceLanguage) { if (!angular.isObject(interpolateParams)) { interpolateParams = $parse(interpolateParams)(this); }
return $translate.instant(translationId, interpolateParams, interpolation, forceLanguage); };
if ($translate.statefulFilter()) { translateFilter.$stateful = true; }
return translateFilter; }
translateFilterFactory.displayName = 'translateFilterFactory';
angular.module('pascalprecht.translate')
/** * @ngdoc object * @name pascalprecht.translate.$translationCache * @requires $cacheFactory * * @description * The first time a translation table is used, it is loaded in the translation cache for quick retrieval. You * can load translation tables directly into the cache by consuming the * `$translationCache` service directly. * * @return {object} $cacheFactory object. */ .factory('$translationCache', $translationCache);
function $translationCache($cacheFactory) {
'use strict';
return $cacheFactory('translations'); }
$translationCache.displayName = '$translationCache'; return 'pascalprecht.translate';
}));
|