3142 lines
106 KiB
JavaScript
3142 lines
106 KiB
JavaScript
/*!
|
|
* angular-translate - v2.8.1 - 2015-10-01
|
|
*
|
|
* Copyright (c) 2015 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.
|
|
*/
|
|
angular.module('pascalprecht.translate', ['ng'])
|
|
.run(runTranslate);
|
|
|
|
function runTranslate($translate) {
|
|
|
|
'use strict';
|
|
|
|
var key = $translate.storageKey(),
|
|
storage = $translate.storage();
|
|
|
|
var fallbackFromIncorrectStorageValue = function () {
|
|
var preferred = $translate.preferredLanguage();
|
|
if (angular.isString(preferred)) {
|
|
$translate.use(preferred);
|
|
// $translate.use() will also remember the language.
|
|
// So, we don't need to call storage.put() here.
|
|
} else {
|
|
storage.put(key, $translate.use());
|
|
}
|
|
};
|
|
|
|
fallbackFromIncorrectStorageValue.displayName = 'fallbackFromIncorrectStorageValue';
|
|
|
|
if (storage) {
|
|
if (!storage.get(key)) {
|
|
fallbackFromIncorrectStorageValue();
|
|
} else {
|
|
$translate.use(storage.get(key))['catch'](fallbackFromIncorrectStorageValue);
|
|
}
|
|
} else if (angular.isString($translate.preferredLanguage())) {
|
|
$translate.use($translate.preferredLanguage());
|
|
}
|
|
}
|
|
runTranslate.$inject = ['$translate'];
|
|
|
|
runTranslate.displayName = 'runTranslate';
|
|
|
|
/**
|
|
* @ngdoc object
|
|
* @name pascalprecht.translate.$translateSanitizationProvider
|
|
*
|
|
* @description
|
|
*
|
|
* Configurations for $translateSanitization
|
|
*/
|
|
angular.module('pascalprecht.translate').provider('$translateSanitization', $translateSanitizationProvider);
|
|
|
|
function $translateSanitizationProvider () {
|
|
|
|
'use strict';
|
|
|
|
var $sanitize,
|
|
currentStrategy = null, // TODO change to either 'sanitize', 'escape' or ['sanitize', 'escapeParameters'] in 3.0.
|
|
hasConfiguredStrategy = false,
|
|
hasShownNoStrategyConfiguredWarning = false,
|
|
strategies;
|
|
|
|
/**
|
|
* Definition of a sanitization strategy function
|
|
* @callback StrategyFunction
|
|
* @param {string|object} value - value to be sanitized (either a string or an interpolated value map)
|
|
* @param {string} mode - either 'text' for a string (translation) or 'params' for the interpolated params
|
|
* @return {string|object}
|
|
*/
|
|
|
|
/**
|
|
* @ngdoc property
|
|
* @name strategies
|
|
* @propertyOf pascalprecht.translate.$translateSanitizationProvider
|
|
*
|
|
* @description
|
|
* Following strategies are built-in:
|
|
* <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) {
|
|
if (mode === 'text') {
|
|
value = htmlSanitizeValue(value);
|
|
}
|
|
return value;
|
|
},
|
|
escape: function (value, mode) {
|
|
if (mode === 'text') {
|
|
value = htmlEscapeValue(value);
|
|
}
|
|
return value;
|
|
},
|
|
sanitizeParameters: function (value, mode) {
|
|
if (mode === 'params') {
|
|
value = mapInterpolationParameters(value, htmlSanitizeValue);
|
|
}
|
|
return value;
|
|
},
|
|
escapeParameters: function (value, mode) {
|
|
if (mode === 'params') {
|
|
value = mapInterpolationParameters(value, htmlEscapeValue);
|
|
}
|
|
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, selectedStrategies) {
|
|
angular.forEach(selectedStrategies, function (selectedStrategy) {
|
|
if (angular.isFunction(selectedStrategy)) {
|
|
value = selectedStrategy(value, mode);
|
|
} else if (angular.isFunction(strategies[selectedStrategy])) {
|
|
value = strategies[selectedStrategy](value, mode);
|
|
} 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);
|
|
} 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');
|
|
}
|
|
|
|
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.
|
|
* @returns {string|object} sanitized value
|
|
*/
|
|
sanitize: function (value, mode, strategy) {
|
|
if (!currentStrategy) {
|
|
showNoStrategyConfiguredWarning();
|
|
}
|
|
|
|
if (arguments.length < 3) {
|
|
strategy = currentStrategy;
|
|
}
|
|
|
|
if (!strategy) {
|
|
return value;
|
|
}
|
|
|
|
var selectedStrategies = angular.isArray(strategy) ? strategy : [strategy];
|
|
return applyStrategies(value, mode, 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 mapInterpolationParameters = function (value, iteratee) {
|
|
if (angular.isObject(value)) {
|
|
var result = angular.isArray(value) ? [] : {};
|
|
|
|
angular.forEach(value, function (propertyValue, propertyKey) {
|
|
result[propertyKey] = mapInterpolationParameters(propertyValue, iteratee);
|
|
});
|
|
|
|
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,
|
|
loaderCache,
|
|
directivePriority = 0,
|
|
statefulFilter = true,
|
|
uniformLanguageTagResolver = 'default',
|
|
languageTagResolver = {
|
|
'default': function (tag) {
|
|
return (tag || '').split('-').join('_');
|
|
},
|
|
java: function (tag) {
|
|
var temp = (tag || '').split('-').join('_');
|
|
var parts = temp.split('_');
|
|
return parts.length > 1 ? (parts[0].toLowerCase() + '_' + parts[1].toUpperCase()) : temp;
|
|
},
|
|
bcp47: function (tag) {
|
|
var temp = (tag || '').split('_').join('-');
|
|
var parts = temp.split('-');
|
|
return parts.length > 1 ? (parts[0].toLowerCase() + '-' + parts[1].toUpperCase()) : temp;
|
|
}
|
|
};
|
|
|
|
var version = '2.8.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) {
|
|
|
|
var avail = [],
|
|
locale = angular.lowercase(preferred),
|
|
i = 0,
|
|
n = $availableLanguageKeys.length;
|
|
|
|
for (; i < n; i++) {
|
|
avail.push(angular.lowercase($availableLanguageKeys[i]));
|
|
}
|
|
|
|
if (indexOf(avail, locale) > -1) {
|
|
return preferred;
|
|
}
|
|
|
|
if ($languageKeyAliases) {
|
|
var alias;
|
|
for (var langKeyAlias in $languageKeyAliases) {
|
|
var hasWildcardKey = false;
|
|
var hasExactKey = Object.prototype.hasOwnProperty.call($languageKeyAliases, langKeyAlias) &&
|
|
angular.lowercase(langKeyAlias) === angular.lowercase(preferred);
|
|
|
|
if (langKeyAlias.slice(-1) === '*') {
|
|
hasWildcardKey = langKeyAlias.slice(0, -1) === preferred.slice(0, langKeyAlias.length-1);
|
|
}
|
|
if (hasExactKey || hasWildcardKey) {
|
|
alias = $languageKeyAliases[langKeyAlias];
|
|
if (indexOf(avail, angular.lowercase(alias)) > -1) {
|
|
return alias;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (preferred) {
|
|
var parts = preferred.split('_');
|
|
|
|
if (parts.length > 1 && indexOf(avail, angular.lowercase(parts[0])) > -1) {
|
|
return parts[0];
|
|
}
|
|
}
|
|
|
|
// If everything fails, just return the preferred, unchanged.
|
|
return preferred;
|
|
};
|
|
|
|
/**
|
|
* @ngdoc function
|
|
* @name pascalprecht.translate.$translateProvider#translations
|
|
* @methodOf pascalprecht.translate.$translateProvider
|
|
*
|
|
* @description
|
|
* Registers a new translation table for specific language key.
|
|
*
|
|
* To register a translation table for specific language, pass a defined language
|
|
* key as first parameter.
|
|
*
|
|
* <pre>
|
|
* // register translation table for language: 'de_DE'
|
|
* $translateProvider.translations('de_DE', {
|
|
* 'GREETING': 'Hallo Welt!'
|
|
* });
|
|
*
|
|
* // register another one
|
|
* $translateProvider.translations('en_US', {
|
|
* 'GREETING': 'Hello world!'
|
|
* });
|
|
* </pre>
|
|
*
|
|
* When registering multiple translation tables for for the same language key,
|
|
* the actual translation table gets extended. This allows you to define module
|
|
* specific translation which only get added, once a specific module is loaded in
|
|
* your app.
|
|
*
|
|
* Invoking this method with no arguments returns the translation table which was
|
|
* registered with no language key. Invoking it with a language key returns the
|
|
* related translation table.
|
|
*
|
|
* @param {string} key A language key.
|
|
* @param {object} translationTable A plain old JavaScript object that represents a translation table.
|
|
*
|
|
*/
|
|
var translations = function (langKey, translationTable) {
|
|
|
|
if (!langKey && !translationTable) {
|
|
return $translationTable;
|
|
}
|
|
|
|
if (langKey && !translationTable) {
|
|
if (angular.isString(langKey)) {
|
|
return $translationTable[langKey];
|
|
}
|
|
} else {
|
|
if (!angular.isObject($translationTable[langKey])) {
|
|
$translationTable[langKey] = {};
|
|
}
|
|
angular.extend($translationTable[langKey], flatObject(translationTable));
|
|
}
|
|
return this;
|
|
};
|
|
|
|
this.translations = translations;
|
|
|
|
/**
|
|
* @ngdoc function
|
|
* @name pascalprecht.translate.$translateProvider#cloakClassName
|
|
* @methodOf pascalprecht.translate.$translateProvider
|
|
*
|
|
* @description
|
|
*
|
|
* Let's you change the class name for `translate-cloak` directive.
|
|
* Default class name is `translate-cloak`.
|
|
*
|
|
* @param {string} name translate-cloak class name
|
|
*/
|
|
this.cloakClassName = function (name) {
|
|
if (!name) {
|
|
return $cloakClassName;
|
|
}
|
|
$cloakClassName = name;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* @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#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);
|
|
}
|
|
|
|
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 object
|
|
* @name pascalprecht.translate.$translate
|
|
* @requires $interpolate
|
|
* @requires $log
|
|
* @requires $rootScope
|
|
* @requires $q
|
|
*
|
|
* @description
|
|
* The `$translate` service is the actual core of angular-translate. It expects a translation id
|
|
* and optional interpolate parameters to translate contents.
|
|
*
|
|
* <pre>
|
|
* $translate('HEADLINE_TEXT').then(function (translation) {
|
|
* $scope.translatedText = translation;
|
|
* });
|
|
* </pre>
|
|
*
|
|
* @param {string|array} translationId A token which represents a translation id
|
|
* This can be optionally an array of translation ids which
|
|
* results that the function returns an object where each key
|
|
* is the translation id and the value the translation.
|
|
* @param {object=} interpolateParams An object hash for dynamic values
|
|
* @param {string} interpolationId The id of the interpolation to use
|
|
* @returns {object} promise
|
|
*/
|
|
this.$get = [
|
|
'$log',
|
|
'$injector',
|
|
'$rootScope',
|
|
'$q',
|
|
function ($log, $injector, $rootScope, $q) {
|
|
|
|
var Storage,
|
|
defaultInterpolator = $injector.get($interpolationFactory || '$translateDefaultInterpolation'),
|
|
pendingLoader = false,
|
|
interpolatorHashMap = {},
|
|
langPromises = {},
|
|
fallbackIndex,
|
|
startFallbackIteration;
|
|
|
|
var $translate = function (translationId, interpolateParams, interpolationId, defaultTranslationText) {
|
|
|
|
// Duck detection: If the first argument is an array, a bunch of translations was requested.
|
|
// The result is an object.
|
|
if (angular.isArray(translationId)) {
|
|
// Inspired by Q.allSettled by Kris Kowal
|
|
// https://github.com/kriskowal/q/blob/b0fa72980717dc202ffc3cbf03b936e10ebbb9d7/q.js#L1553-1563
|
|
// This transforms all promises regardless resolved or rejected
|
|
var translateAll = function (translationIds) {
|
|
var results = {}; // storing the actual results
|
|
var promises = []; // promises to wait for
|
|
// Wraps the promise a) being always resolved and b) storing the link id->value
|
|
var translate = function (translationId) {
|
|
var deferred = $q.defer();
|
|
var regardless = function (value) {
|
|
results[translationId] = value;
|
|
deferred.resolve([translationId, value]);
|
|
};
|
|
// we don't care whether the promise was resolved or rejected; just store the values
|
|
$translate(translationId, interpolateParams, interpolationId, defaultTranslationText).then(regardless, regardless);
|
|
return deferred.promise;
|
|
};
|
|
for (var i = 0, c = translationIds.length; i < c; i++) {
|
|
promises.push(translate(translationIds[i]));
|
|
}
|
|
// wait for all (including storing to results)
|
|
return $q.all(promises).then(function () {
|
|
// return the results
|
|
return results;
|
|
});
|
|
};
|
|
return translateAll(translationId);
|
|
}
|
|
|
|
var deferred = $q.defer();
|
|
|
|
// trim off any whitespace
|
|
if (translationId) {
|
|
translationId = trim.apply(translationId);
|
|
}
|
|
|
|
var promiseToWaitFor = (function () {
|
|
var promise = $preferredLanguage ?
|
|
langPromises[$preferredLanguage] :
|
|
langPromises[$uses];
|
|
|
|
fallbackIndex = 0;
|
|
|
|
if ($storageFactory && !promise) {
|
|
// looks like there's no pending promise for $preferredLanguage or
|
|
// $uses. Maybe there's one pending for a language that comes from
|
|
// storage.
|
|
var langKey = Storage.get($storageKey);
|
|
promise = langPromises[langKey];
|
|
|
|
if ($fallbackLanguage && $fallbackLanguage.length) {
|
|
var index = indexOf($fallbackLanguage, langKey);
|
|
// maybe the language from storage is also defined as fallback language
|
|
// we increase the fallback language index to not search in that language
|
|
// as fallback, since it's probably the first used language
|
|
// in that case the index starts after the first element
|
|
fallbackIndex = (index === 0) ? 1 : 0;
|
|
|
|
// but we can make sure to ALWAYS fallback to preferred language at least
|
|
if (indexOf($fallbackLanguage, $preferredLanguage) < 0) {
|
|
$fallbackLanguage.push($preferredLanguage);
|
|
}
|
|
}
|
|
}
|
|
return promise;
|
|
}());
|
|
|
|
if (!promiseToWaitFor) {
|
|
// no promise to wait for? okay. Then there's no loader registered
|
|
// nor is a one pending for language that comes from storage.
|
|
// We can just translate.
|
|
determineTranslation(translationId, interpolateParams, interpolationId, defaultTranslationText).then(deferred.resolve, deferred.reject);
|
|
} else {
|
|
var promiseResolved = function () {
|
|
determineTranslation(translationId, interpolateParams, interpolationId, defaultTranslationText).then(deferred.resolve, deferred.reject);
|
|
};
|
|
promiseResolved.displayName = 'promiseResolved';
|
|
|
|
promiseToWaitFor['finally'](promiseResolved, deferred.reject);
|
|
}
|
|
return deferred.promise;
|
|
};
|
|
|
|
/**
|
|
* @name applyNotFoundIndicators
|
|
* @private
|
|
*
|
|
* @description
|
|
* Applies not fount indicators to given translation id, if needed.
|
|
* This function gets only executed, if a translation id doesn't exist,
|
|
* which is why a translation id is expected as argument.
|
|
*
|
|
* @param {string} translationId Translation id.
|
|
* @returns {string} Same as given translation id but applied with not found
|
|
* indicators.
|
|
*/
|
|
var applyNotFoundIndicators = function (translationId) {
|
|
// applying notFoundIndicators
|
|
if ($notFoundIndicatorLeft) {
|
|
translationId = [$notFoundIndicatorLeft, translationId].join(' ');
|
|
}
|
|
if ($notFoundIndicatorRight) {
|
|
translationId = [translationId, $notFoundIndicatorRight].join(' ');
|
|
}
|
|
return translationId;
|
|
};
|
|
|
|
/**
|
|
* @name useLanguage
|
|
* @private
|
|
*
|
|
* @description
|
|
* Makes actual use of a language by setting a given language key as used
|
|
* language and informs registered interpolators to also use the given
|
|
* key as locale.
|
|
*
|
|
* @param {key} Locale key.
|
|
*/
|
|
var useLanguage = function (key) {
|
|
$uses = key;
|
|
|
|
// 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 {
|
|
deferred.resolve(Interpolator.interpolate(translationTable[translationId], interpolateParams));
|
|
}
|
|
Interpolator.setLocale($uses);
|
|
} else {
|
|
deferred.reject();
|
|
}
|
|
};
|
|
onResolve.displayName = 'fallbackTranslationResolver';
|
|
|
|
getTranslationTable(langKey).then(onResolve, deferred.reject);
|
|
|
|
return deferred.promise;
|
|
};
|
|
|
|
/**
|
|
* @name getFallbackTranslationInstant
|
|
* @private
|
|
*
|
|
* @description
|
|
* Returns a translation
|
|
* This function is currently only used for fallback language translation.
|
|
*
|
|
* @param langKey The language to translate to.
|
|
* @param translationId
|
|
* @param interpolateParams
|
|
* @param Interpolator
|
|
* @returns {string} translation
|
|
*/
|
|
var getFallbackTranslationInstant = function (langKey, translationId, interpolateParams, Interpolator) {
|
|
var result, translationTable = $translationTable[langKey];
|
|
|
|
if (translationTable && Object.prototype.hasOwnProperty.call(translationTable, translationId)) {
|
|
Interpolator.setLocale(langKey);
|
|
result = Interpolator.interpolate(translationTable[translationId], interpolateParams);
|
|
if (result.substr(0, 2) === '@:') {
|
|
return getFallbackTranslationInstant(langKey, result.substr(2), interpolateParams, Interpolator);
|
|
}
|
|
Interpolator.setLocale($uses);
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
|
|
/**
|
|
* @name translateByHandler
|
|
* @private
|
|
*
|
|
* Translate by missing translation handler.
|
|
*
|
|
* @param translationId
|
|
* @returns translation created by $missingTranslationHandler or translationId is $missingTranslationHandler is
|
|
* absent
|
|
*/
|
|
var translateByHandler = function (translationId, interpolateParams) {
|
|
// If we have a handler factory - we might also call it here to determine if it provides
|
|
// a default text for a translationid that can't be found anywhere in our tables
|
|
if ($missingTranslationHandlerFactory) {
|
|
var resultString = $injector.get($missingTranslationHandlerFactory)(translationId, $uses, interpolateParams);
|
|
if (resultString !== undefined) {
|
|
return resultString;
|
|
} else {
|
|
return translationId;
|
|
}
|
|
} else {
|
|
return translationId;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @name resolveForFallbackLanguage
|
|
* @private
|
|
*
|
|
* Recursive helper function for fallbackTranslation that will sequentially look
|
|
* for a translation in the fallbackLanguages starting with fallbackLanguageIndex.
|
|
*
|
|
* @param fallbackLanguageIndex
|
|
* @param translationId
|
|
* @param interpolateParams
|
|
* @param Interpolator
|
|
* @returns {Q.promise} Promise that will resolve to the translation.
|
|
*/
|
|
var resolveForFallbackLanguage = function (fallbackLanguageIndex, translationId, interpolateParams, Interpolator, defaultTranslationText) {
|
|
var deferred = $q.defer();
|
|
|
|
if (fallbackLanguageIndex < $fallbackLanguage.length) {
|
|
var langKey = $fallbackLanguage[fallbackLanguageIndex];
|
|
getFallbackTranslation(langKey, translationId, interpolateParams, Interpolator).then(
|
|
deferred.resolve,
|
|
function () {
|
|
// Look in the next fallback language for a translation.
|
|
// It delays the resolving by passing another promise to resolve.
|
|
resolveForFallbackLanguage(fallbackLanguageIndex + 1, translationId, interpolateParams, Interpolator, defaultTranslationText).then(deferred.resolve);
|
|
}
|
|
);
|
|
} else {
|
|
// No translation found in any fallback language
|
|
// if a default translation text is set in the directive, then return this as a result
|
|
if (defaultTranslationText) {
|
|
deferred.resolve(defaultTranslationText);
|
|
} else {
|
|
// if no default translation is set and an error handler is defined, send it to the handler
|
|
// and then return the result
|
|
deferred.resolve(translateByHandler(translationId, interpolateParams));
|
|
}
|
|
}
|
|
return deferred.promise;
|
|
};
|
|
|
|
/**
|
|
* @name resolveForFallbackLanguageInstant
|
|
* @private
|
|
*
|
|
* Recursive helper function for fallbackTranslation that will sequentially look
|
|
* for a translation in the fallbackLanguages starting with fallbackLanguageIndex.
|
|
*
|
|
* @param fallbackLanguageIndex
|
|
* @param translationId
|
|
* @param interpolateParams
|
|
* @param Interpolator
|
|
* @returns {string} translation
|
|
*/
|
|
var resolveForFallbackLanguageInstant = function (fallbackLanguageIndex, translationId, interpolateParams, Interpolator) {
|
|
var result;
|
|
|
|
if (fallbackLanguageIndex < $fallbackLanguage.length) {
|
|
var langKey = $fallbackLanguage[fallbackLanguageIndex];
|
|
result = getFallbackTranslationInstant(langKey, translationId, interpolateParams, Interpolator);
|
|
if (!result) {
|
|
result = resolveForFallbackLanguageInstant(fallbackLanguageIndex + 1, translationId, interpolateParams, Interpolator);
|
|
}
|
|
}
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* Translates with the usage of the fallback languages.
|
|
*
|
|
* @param translationId
|
|
* @param interpolateParams
|
|
* @param Interpolator
|
|
* @returns {Q.promise} Promise, that resolves to the translation.
|
|
*/
|
|
var fallbackTranslation = function (translationId, interpolateParams, Interpolator, defaultTranslationText) {
|
|
// Start with the fallbackLanguage with index 0
|
|
return resolveForFallbackLanguage((startFallbackIteration>0 ? startFallbackIteration : fallbackIndex), translationId, interpolateParams, Interpolator, defaultTranslationText);
|
|
};
|
|
|
|
/**
|
|
* Translates with the usage of the fallback languages.
|
|
*
|
|
* @param translationId
|
|
* @param interpolateParams
|
|
* @param Interpolator
|
|
* @returns {String} translation
|
|
*/
|
|
var fallbackTranslationInstant = function (translationId, interpolateParams, Interpolator) {
|
|
// Start with the fallbackLanguage with index 0
|
|
return resolveForFallbackLanguageInstant((startFallbackIteration>0 ? startFallbackIteration : fallbackIndex), translationId, interpolateParams, Interpolator);
|
|
};
|
|
|
|
var determineTranslation = function (translationId, interpolateParams, interpolationId, defaultTranslationText) {
|
|
|
|
var deferred = $q.defer();
|
|
|
|
var table = $uses ? $translationTable[$uses] : $translationTable,
|
|
Interpolator = (interpolationId) ? interpolatorHashMap[interpolationId] : defaultInterpolator;
|
|
|
|
// if the translation id exists, we can just interpolate it
|
|
if (table && Object.prototype.hasOwnProperty.call(table, translationId)) {
|
|
var translation = table[translationId];
|
|
|
|
// If using link, rerun $translate with linked translationId and return it
|
|
if (translation.substr(0, 2) === '@:') {
|
|
|
|
$translate(translation.substr(2), interpolateParams, interpolationId, defaultTranslationText)
|
|
.then(deferred.resolve, deferred.reject);
|
|
} else {
|
|
deferred.resolve(Interpolator.interpolate(translation, interpolateParams));
|
|
}
|
|
} else {
|
|
var missingTranslationHandlerTranslation;
|
|
// for logging purposes only (as in $translateMissingTranslationHandlerLog), value is not returned to promise
|
|
if ($missingTranslationHandlerFactory && !pendingLoader) {
|
|
missingTranslationHandlerTranslation = translateByHandler(translationId, interpolateParams);
|
|
}
|
|
|
|
// since we couldn't translate the inital requested translation id,
|
|
// we try it now with one or more fallback languages, if fallback language(s) is
|
|
// configured.
|
|
if ($uses && $fallbackLanguage && $fallbackLanguage.length) {
|
|
fallbackTranslation(translationId, interpolateParams, Interpolator, defaultTranslationText)
|
|
.then(function (translation) {
|
|
deferred.resolve(translation);
|
|
}, function (_translationId) {
|
|
deferred.reject(applyNotFoundIndicators(_translationId));
|
|
});
|
|
} else if ($missingTranslationHandlerFactory && !pendingLoader && missingTranslationHandlerTranslation) {
|
|
// looks like the requested translation id doesn't exists.
|
|
// Now, if there is a registered handler for missing translations and no
|
|
// asyncLoader is pending, we execute the handler
|
|
if (defaultTranslationText) {
|
|
deferred.resolve(defaultTranslationText);
|
|
} else {
|
|
deferred.resolve(missingTranslationHandlerTranslation);
|
|
}
|
|
} else {
|
|
if (defaultTranslationText) {
|
|
deferred.resolve(defaultTranslationText);
|
|
} else {
|
|
deferred.reject(applyNotFoundIndicators(translationId));
|
|
}
|
|
}
|
|
}
|
|
return deferred.promise;
|
|
};
|
|
|
|
var determineTranslationInstant = function (translationId, interpolateParams, interpolationId) {
|
|
|
|
var result, table = $uses ? $translationTable[$uses] : $translationTable,
|
|
Interpolator = defaultInterpolator;
|
|
|
|
// if the interpolation id exists use custom interpolator
|
|
if (interpolatorHashMap && Object.prototype.hasOwnProperty.call(interpolatorHashMap, interpolationId)) {
|
|
Interpolator = interpolatorHashMap[interpolationId];
|
|
}
|
|
|
|
// if the translation id exists, we can just interpolate it
|
|
if (table && Object.prototype.hasOwnProperty.call(table, translationId)) {
|
|
var translation = table[translationId];
|
|
|
|
// If using link, rerun $translate with linked translationId and return it
|
|
if (translation.substr(0, 2) === '@:') {
|
|
result = determineTranslationInstant(translation.substr(2), interpolateParams, interpolationId);
|
|
} else {
|
|
result = Interpolator.interpolate(translation, interpolateParams);
|
|
}
|
|
} else {
|
|
var missingTranslationHandlerTranslation;
|
|
// for logging purposes only (as in $translateMissingTranslationHandlerLog), value is not returned to promise
|
|
if ($missingTranslationHandlerFactory && !pendingLoader) {
|
|
missingTranslationHandlerTranslation = translateByHandler(translationId, 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;
|
|
};
|
|
|
|
/**
|
|
* @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#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;
|
|
}
|
|
|
|
// Safari is "zh_cn"
|
|
if (key == "zh_cn") {key = "zh_CN"}
|
|
|
|
var deferred = $q.defer();
|
|
|
|
$rootScope.$emit('$translateChangeStart', {language: key});
|
|
|
|
// Try to get the aliased language key
|
|
var aliasedKey = negotiateLocale(key);
|
|
if (aliasedKey) {
|
|
key = aliasedKey;
|
|
}
|
|
|
|
// if there isn't a translation table for the language we've requested,
|
|
// we load it asynchronously
|
|
if (($forceAsyncReloadEnabled || !$translationTable[key]) && $loaderFactory && !langPromises[key]) {
|
|
$nextLang = key;
|
|
langPromises[key] = loadAsync(key).then(function (translation) {
|
|
translations(translation.key, translation.table);
|
|
deferred.resolve(translation.key);
|
|
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 ($nextLang === key && langPromises[key]) {
|
|
// we are already loading this asynchronously
|
|
// resolve our new deferred when the old langPromise is resolved
|
|
langPromises[key].then(function (translation) {
|
|
deferred.resolve(translation.key);
|
|
return translation;
|
|
}, function (key) {
|
|
deferred.reject(key);
|
|
return $q.reject(key);
|
|
});
|
|
} else {
|
|
deferred.resolve(key);
|
|
useLanguage(key);
|
|
}
|
|
|
|
return deferred.promise;
|
|
};
|
|
|
|
/**
|
|
* @ngdoc function
|
|
* @name pascalprecht.translate.$translate#storageKey
|
|
* @methodOf pascalprecht.translate.$translate
|
|
*
|
|
* @description
|
|
* Returns the key for the storage.
|
|
*
|
|
* @return {string} storage key
|
|
*/
|
|
$translate.storageKey = function () {
|
|
return storageKey();
|
|
};
|
|
|
|
/**
|
|
* @ngdoc function
|
|
* @name pascalprecht.translate.$translate#isPostCompilingEnabled
|
|
* @methodOf pascalprecht.translate.$translate
|
|
*
|
|
* @description
|
|
* Returns whether post compiling is enabled or not
|
|
*
|
|
* @return {bool} storage key
|
|
*/
|
|
$translate.isPostCompilingEnabled = function () {
|
|
return $postCompilingEnabled;
|
|
};
|
|
|
|
/**
|
|
* @ngdoc function
|
|
* @name pascalprecht.translate.$translate#isForceAsyncReloadEnabled
|
|
* @methodOf pascalprecht.translate.$translate
|
|
*
|
|
* @description
|
|
* Returns whether force async reload is enabled or not
|
|
*
|
|
* @return {boolean} forceAsyncReload value
|
|
*/
|
|
$translate.isForceAsyncReloadEnabled = function () {
|
|
return $forceAsyncReloadEnabled;
|
|
};
|
|
|
|
/**
|
|
* @ngdoc function
|
|
* @name pascalprecht.translate.$translate#refresh
|
|
* @methodOf pascalprecht.translate.$translate
|
|
*
|
|
* @description
|
|
* Refreshes a translation table pointed by the given langKey. If langKey is not specified,
|
|
* the module will drop all existent translation tables and load new version of those which
|
|
* are currently in use.
|
|
*
|
|
* Refresh means that the module will drop target translation table and try to load it again.
|
|
*
|
|
* In case there are no loaders registered the refresh() method will throw an Error.
|
|
*
|
|
* If the module is able to refresh translation tables refresh() method will broadcast
|
|
* $translateRefreshStart and $translateRefreshEnd events.
|
|
*
|
|
* @example
|
|
* // this will drop all currently existent translation tables and reload those which are
|
|
* // currently in use
|
|
* $translate.refresh();
|
|
* // this will refresh a translation table for the en_US language
|
|
* $translate.refresh('en_US');
|
|
*
|
|
* @param {string} langKey A language key of the table, which has to be refreshed
|
|
*
|
|
* @return {promise} Promise, which will be resolved in case a translation tables refreshing
|
|
* process is finished successfully, and reject if not.
|
|
*/
|
|
$translate.refresh = function (langKey) {
|
|
if (!$loaderFactory) {
|
|
throw new Error('Couldn\'t refresh translation table, no loader registered!');
|
|
}
|
|
|
|
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();
|
|
};
|
|
oneTranslationsLoaded.displayName = 'refreshPostProcessor';
|
|
|
|
loadAsync(langKey).then(oneTranslationsLoaded, reject);
|
|
|
|
} else {
|
|
reject();
|
|
}
|
|
return deferred.promise;
|
|
};
|
|
|
|
/**
|
|
* @ngdoc function
|
|
* @name pascalprecht.translate.$translate#instant
|
|
* @methodOf pascalprecht.translate.$translate
|
|
*
|
|
* @description
|
|
* Returns a translation instantly from the internal state of loaded translation. All rules
|
|
* regarding the current language, the preferred language of even fallback languages will be
|
|
* used except any promise handling. If a language was not found, an asynchronous loading
|
|
* will be invoked in the background.
|
|
*
|
|
* @param {string|array} translationId A token which represents a translation id
|
|
* This can be optionally an array of translation ids which
|
|
* results that the function's promise returns an object where
|
|
* each key is the translation id and the value the translation.
|
|
* @param {object} interpolateParams Params
|
|
* @param {string} interpolationId The id of the interpolation to use
|
|
*
|
|
* @return {string|object} translation
|
|
*/
|
|
$translate.instant = function (translationId, interpolateParams, interpolationId) {
|
|
|
|
// Detect undefined and null values to shorten the execution and prevent exceptions
|
|
if (translationId === null || angular.isUndefined(translationId)) {
|
|
return translationId;
|
|
}
|
|
|
|
// Duck detection: If the first argument is an array, a bunch of translations was requested.
|
|
// The result is an object.
|
|
if (angular.isArray(translationId)) {
|
|
var results = {};
|
|
for (var i = 0, c = translationId.length; i < c; i++) {
|
|
results[translationId[i]] = $translate.instant(translationId[i], interpolateParams, interpolationId);
|
|
}
|
|
return results;
|
|
}
|
|
|
|
// We discarded unacceptable values. So we just need to verify if translationId is empty String
|
|
if (angular.isString(translationId) && translationId.length < 1) {
|
|
return translationId;
|
|
}
|
|
|
|
// trim off any whitespace
|
|
if (translationId) {
|
|
translationId = trim.apply(translationId);
|
|
}
|
|
|
|
var result, possibleLangKeys = [];
|
|
if ($preferredLanguage) {
|
|
possibleLangKeys.push($preferredLanguage);
|
|
}
|
|
if ($uses) {
|
|
possibleLangKeys.push($uses);
|
|
}
|
|
if ($fallbackLanguage && $fallbackLanguage.length) {
|
|
possibleLangKeys = possibleLangKeys.concat($fallbackLanguage);
|
|
}
|
|
for (var j = 0, d = possibleLangKeys.length; j < d; j++) {
|
|
var possibleLangKey = possibleLangKeys[j];
|
|
if ($translationTable[possibleLangKey]) {
|
|
if (typeof $translationTable[possibleLangKey][translationId] !== 'undefined') {
|
|
result = determineTranslationInstant(translationId, interpolateParams, interpolationId);
|
|
} else if ($notFoundIndicatorLeft || $notFoundIndicatorRight) {
|
|
result = applyNotFoundIndicators(translationId);
|
|
}
|
|
}
|
|
if (typeof result !== 'undefined') {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!result && result !== '') {
|
|
// Return translation of default interpolator if not found anything.
|
|
result = defaultInterpolator.interpolate(translationId, interpolateParams);
|
|
if ($missingTranslationHandlerFactory && !pendingLoader) {
|
|
result = translateByHandler(translationId, 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;
|
|
};
|
|
|
|
// Whenever $translateReady is being fired, this will ensure the state of $isReady
|
|
var globalOnReadyListener = $rootScope.$on('$translateReady', function () {
|
|
$onReadyDeferred.resolve();
|
|
globalOnReadyListener(); // one time only
|
|
globalOnReadyListener = null;
|
|
});
|
|
var globalOnChangeListener = $rootScope.$on('$translateChangeEnd', function () {
|
|
$onReadyDeferred.resolve();
|
|
globalOnChangeListener(); // one time only
|
|
globalOnChangeListener = null;
|
|
});
|
|
|
|
if ($loaderFactory) {
|
|
|
|
// If at least one async loader is defined and there are no
|
|
// (default) translations available we should try to load them.
|
|
if (angular.equals($translationTable, {})) {
|
|
if ($translate.use()) {
|
|
$translate.use($translate.use());
|
|
}
|
|
}
|
|
|
|
// Also, if there are any fallback language registered, we start
|
|
// loading them asynchronously as soon as we can.
|
|
if ($fallbackLanguage && $fallbackLanguage.length) {
|
|
var processAsyncResult = function (translation) {
|
|
translations(translation.key, translation.table);
|
|
$rootScope.$emit('$translateChangeEnd', { language: translation.key });
|
|
return translation;
|
|
};
|
|
for (var i = 0, len = $fallbackLanguage.length; i < len; i++) {
|
|
var fallbackLanguageId = $fallbackLanguage[i];
|
|
if ($forceAsyncReloadEnabled || !$translationTable[fallbackLanguageId]) {
|
|
langPromises[fallbackLanguageId] = loadAsync(fallbackLanguageId).then(processAsyncResult);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
$rootScope.$emit('$translateReady', { language: $translate.use() });
|
|
}
|
|
|
|
return $translate;
|
|
}
|
|
];
|
|
}
|
|
$translate.$inject = ['$STORAGE_KEY', '$windowProvider', '$translateSanitizationProvider', 'pascalprechtTranslateOverrider'];
|
|
|
|
$translate.displayName = 'displayName';
|
|
|
|
/**
|
|
* @ngdoc object
|
|
* @name pascalprecht.translate.$translateDefaultInterpolation
|
|
* @requires $interpolate
|
|
*
|
|
* @description
|
|
* Uses angular's `$interpolate` services to interpolate strings against some values.
|
|
*
|
|
* Be aware to configure a proper sanitization strategy.
|
|
*
|
|
* See also:
|
|
* * {@link pascalprecht.translate.$translateSanitization}
|
|
*
|
|
* @return {object} $translateDefaultInterpolation Interpolator service
|
|
*/
|
|
angular.module('pascalprecht.translate').factory('$translateDefaultInterpolation', $translateDefaultInterpolation);
|
|
|
|
function $translateDefaultInterpolation ($interpolate, $translateSanitization) {
|
|
|
|
'use strict';
|
|
|
|
var $translateInterpolator = {},
|
|
$locale,
|
|
$identifier = 'default';
|
|
|
|
/**
|
|
* @ngdoc function
|
|
* @name pascalprecht.translate.$translateDefaultInterpolation#setLocale
|
|
* @methodOf pascalprecht.translate.$translateDefaultInterpolation
|
|
*
|
|
* @description
|
|
* Sets current locale (this is currently not use in this interpolation).
|
|
*
|
|
* @param {string} locale Language key or locale.
|
|
*/
|
|
$translateInterpolator.setLocale = function (locale) {
|
|
$locale = locale;
|
|
};
|
|
|
|
/**
|
|
* @ngdoc function
|
|
* @name pascalprecht.translate.$translateDefaultInterpolation#getInterpolationIdentifier
|
|
* @methodOf pascalprecht.translate.$translateDefaultInterpolation
|
|
*
|
|
* @description
|
|
* Returns an identifier for this interpolation service.
|
|
*
|
|
* @returns {string} $identifier
|
|
*/
|
|
$translateInterpolator.getInterpolationIdentifier = function () {
|
|
return $identifier;
|
|
};
|
|
|
|
/**
|
|
* @deprecated will be removed in 3.0
|
|
* @see {@link pascalprecht.translate.$translateSanitization}
|
|
*/
|
|
$translateInterpolator.useSanitizeValueStrategy = function (value) {
|
|
$translateSanitization.useStrategy(value);
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* @ngdoc function
|
|
* @name pascalprecht.translate.$translateDefaultInterpolation#interpolate
|
|
* @methodOf pascalprecht.translate.$translateDefaultInterpolation
|
|
*
|
|
* @description
|
|
* Interpolates given string agains given interpolate params using angulars
|
|
* `$interpolate` service.
|
|
*
|
|
* @returns {string} interpolated string.
|
|
*/
|
|
$translateInterpolator.interpolate = function (string, interpolationParams) {
|
|
interpolationParams = interpolationParams || {};
|
|
interpolationParams = $translateSanitization.sanitize(interpolationParams, 'params');
|
|
|
|
var interpolatedText = $interpolate(string)(interpolationParams);
|
|
interpolatedText = $translateSanitization.sanitize(interpolatedText, 'text');
|
|
|
|
return interpolatedText;
|
|
};
|
|
|
|
return $translateInterpolator;
|
|
}
|
|
$translateDefaultInterpolation.$inject = ['$interpolate', '$translateSanitization'];
|
|
|
|
$translateDefaultInterpolation.displayName = '$translateDefaultInterpolation';
|
|
|
|
angular.module('pascalprecht.translate').constant('$STORAGE_KEY', 'NG_TRANSLATE_LANG_KEY');
|
|
|
|
angular.module('pascalprecht.translate')
|
|
/**
|
|
* @ngdoc directive
|
|
* @name pascalprecht.translate.directive:translate
|
|
* @requires $compile
|
|
* @requires $filter
|
|
* @requires $interpolate
|
|
* @restrict A
|
|
*
|
|
* @description
|
|
* Translates given translation id either through attribute or DOM content.
|
|
* Internally it uses `translate` filter to translate translation id. It possible to
|
|
* pass an optional `translate-values` object literal as string into translation id.
|
|
*
|
|
* @param {string=} translate Translation id which could be either string or interpolated string.
|
|
* @param {string=} translate-values Values to pass into translation id. Can be passed as object literal string or interpolated object.
|
|
* @param {string=} translate-attr-ATTR translate Translation id and put it into ATTR attribute.
|
|
* @param {string=} translate-default will be used unless translation was successful
|
|
* @param {boolean=} translate-compile (default true if present) defines locally activation of {@link pascalprecht.translate.$translateProvider#methods_usePostCompiling}
|
|
*
|
|
* @example
|
|
<example module="ngView">
|
|
<file name="index.html">
|
|
<div ng-controller="TranslateCtrl">
|
|
|
|
<pre translate="TRANSLATION_ID"></pre>
|
|
<pre translate>TRANSLATION_ID</pre>
|
|
<pre translate translate-attr-title="TRANSLATION_ID"></pre>
|
|
<pre translate="{{translationId}}"></pre>
|
|
<pre translate>{{translationId}}</pre>
|
|
<pre translate="WITH_VALUES" translate-values="{value: 5}"></pre>
|
|
<pre translate translate-values="{value: 5}">WITH_VALUES</pre>
|
|
<pre translate="WITH_VALUES" translate-values="{{values}}"></pre>
|
|
<pre translate translate-values="{{values}}">WITH_VALUES</pre>
|
|
<pre translate translate-attr-title="WITH_VALUES" translate-values="{{values}}"></pre>
|
|
|
|
</div>
|
|
</file>
|
|
<file name="script.js">
|
|
angular.module('ngView', ['pascalprecht.translate'])
|
|
|
|
.config(function ($translateProvider) {
|
|
|
|
$translateProvider.translations('en',{
|
|
'TRANSLATION_ID': 'Hello there!',
|
|
'WITH_VALUES': 'The following value is dynamic: {{value}}'
|
|
}).preferredLanguage('en');
|
|
|
|
});
|
|
|
|
angular.module('ngView').controller('TranslateCtrl', function ($scope) {
|
|
$scope.translationId = 'TRANSLATION_ID';
|
|
|
|
$scope.values = {
|
|
value: 78
|
|
};
|
|
});
|
|
</file>
|
|
<file name="scenario.js">
|
|
it('should translate', function () {
|
|
inject(function ($rootScope, $compile) {
|
|
$rootScope.translationId = 'TRANSLATION_ID';
|
|
|
|
element = $compile('<p translate="TRANSLATION_ID"></p>')($rootScope);
|
|
$rootScope.$digest();
|
|
expect(element.text()).toBe('Hello there!');
|
|
|
|
element = $compile('<p translate="{{translationId}}"></p>')($rootScope);
|
|
$rootScope.$digest();
|
|
expect(element.text()).toBe('Hello there!');
|
|
|
|
element = $compile('<p translate>TRANSLATION_ID</p>')($rootScope);
|
|
$rootScope.$digest();
|
|
expect(element.text()).toBe('Hello there!');
|
|
|
|
element = $compile('<p translate>{{translationId}}</p>')($rootScope);
|
|
$rootScope.$digest();
|
|
expect(element.text()).toBe('Hello there!');
|
|
|
|
element = $compile('<p translate translate-attr-title="TRANSLATION_ID"></p>')($rootScope);
|
|
$rootScope.$digest();
|
|
expect(element.attr('title')).toBe('Hello there!');
|
|
});
|
|
});
|
|
</file>
|
|
</example>
|
|
*/
|
|
.directive('translate', translateDirective);
|
|
function translateDirective($translate, $q, $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 {
|
|
translationIds.translate = 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') {
|
|
observeAttributeTranslation(translateAttr);
|
|
}
|
|
}
|
|
|
|
iAttr.$observe('translateDefault', function (value) {
|
|
scope.defaultText = value;
|
|
});
|
|
|
|
if (translateValuesExist) {
|
|
iAttr.$observe('translateValues', function (interpolateParams) {
|
|
if (interpolateParams) {
|
|
scope.$parent.$watch(function () {
|
|
angular.extend(scope.interpolateParams, $parse(interpolateParams)(scope.$parent));
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
if (translateValueExist) {
|
|
var observeValueAttribute = function (attrName) {
|
|
iAttr.$observe(attrName, function (value) {
|
|
var attributeName = angular.lowercase(attrName.substr(14, 1)) + attrName.substr(15);
|
|
scope.interpolateParams[attributeName] = value;
|
|
});
|
|
};
|
|
for (var attr in iAttr) {
|
|
if (Object.prototype.hasOwnProperty.call(iAttr, attr) && attr.substr(0, 14) === 'translateValue' && attr !== 'translateValues') {
|
|
observeValueAttribute(attr);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Master update function
|
|
var updateTranslations = function () {
|
|
for (var key in translationIds) {
|
|
|
|
if (translationIds.hasOwnProperty(key) && 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)
|
|
.then(function (translation) {
|
|
applyTranslation(translation, scope, true, translateAttr);
|
|
}, function (translationId) {
|
|
applyTranslation(translationId, scope, false, translateAttr);
|
|
});
|
|
} else {
|
|
// as an empty string cannot be translated, we can solve this using successful=false
|
|
applyTranslation(translationId, scope, false, translateAttr);
|
|
}
|
|
};
|
|
|
|
var applyTranslation = function (value, scope, successful, translateAttr) {
|
|
if (translateAttr === 'translate') {
|
|
// default translate into innerHTML
|
|
if (!successful && typeof scope.defaultText !== 'undefined') {
|
|
value = scope.defaultText;
|
|
}
|
|
iElement.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
|
|
if (!successful && typeof scope.defaultText !== 'undefined') {
|
|
value = scope.defaultText;
|
|
}
|
|
var attributeName = iAttr.$attr[translateAttr];
|
|
if (attributeName.substr(0, 5) === 'data-') {
|
|
// ensure html5 data prefix is stripped
|
|
attributeName = attributeName.substr(5);
|
|
}
|
|
attributeName = attributeName.substr(15);
|
|
iElement.attr(attributeName, value);
|
|
}
|
|
};
|
|
|
|
if (translateValuesExist || translateValueExist || iAttr.translateDefault) {
|
|
scope.$watch('interpolateParams', updateTranslations, true);
|
|
}
|
|
|
|
// Ensures the text will be refreshed after the current language was changed
|
|
// w/ $translate.use(...)
|
|
var unbind = $rootScope.$on('$translateChangeSuccess', updateTranslations);
|
|
|
|
// ensure translation will be looked up at least one
|
|
if (iElement.text().length) {
|
|
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);
|
|
};
|
|
}
|
|
};
|
|
}
|
|
translateDirective.$inject = ['$translate', '$q', '$interpolate', '$compile', '$parse', '$rootScope'];
|
|
|
|
/**
|
|
* Returns the scope's namespace.
|
|
* @private
|
|
* @param scope
|
|
* @returns {string}
|
|
*/
|
|
function getTranslateNamespace(scope) {
|
|
'use strict';
|
|
if (scope.translateNamespace) {
|
|
return scope.translateNamespace;
|
|
}
|
|
if (scope.$parent) {
|
|
return getTranslateNamespace(scope.$parent);
|
|
}
|
|
}
|
|
|
|
translateDirective.displayName = 'translateDirective';
|
|
|
|
angular.module('pascalprecht.translate')
|
|
/**
|
|
* @ngdoc directive
|
|
* @name pascalprecht.translate.directive: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) {
|
|
|
|
'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) {
|
|
// Register a watcher for the defined translation allowing a fine tuned cloak
|
|
if (iAttr.translateCloak && iAttr.translateCloak.length) {
|
|
iAttr.$observe('translateCloak', function (translationId) {
|
|
$translate(translationId).then(removeCloak, applyCloak);
|
|
});
|
|
}
|
|
};
|
|
}
|
|
};
|
|
}
|
|
translateCloakDirective.$inject = ['$translate'];
|
|
|
|
translateCloakDirective.displayName = 'translateCloakDirective';
|
|
|
|
angular.module('pascalprecht.translate')
|
|
/**
|
|
* @ngdoc directive
|
|
* @name pascalprecht.translate.directive:translateNamespace
|
|
* @restrict A
|
|
*
|
|
* @description
|
|
* Translates given translation id either through attribute or DOM content.
|
|
* Internally it uses `translate` filter to translate translation id. It possible to
|
|
* pass an optional `translate-values` object literal as string into translation id.
|
|
*
|
|
* @param {string=} translate namespace name which could be either string or interpolated string.
|
|
*
|
|
* @example
|
|
<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 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) {
|
|
|
|
if (!angular.isObject(interpolateParams)) {
|
|
interpolateParams = $parse(interpolateParams)(this);
|
|
}
|
|
|
|
return $translate.instant(translationId, interpolateParams, interpolation);
|
|
};
|
|
|
|
if ($translate.statefulFilter()) {
|
|
translateFilter.$stateful = true;
|
|
}
|
|
|
|
return translateFilter;
|
|
}
|
|
translateFilterFactory.$inject = ['$parse', '$translate'];
|
|
|
|
translateFilterFactory.displayName = 'translateFilterFactory';
|
|
|
|
angular.module('pascalprecht.translate')
|
|
|
|
/**
|
|
* @ngdoc object
|
|
* @name pascalprecht.translate.$translationCache
|
|
* @requires $cacheFactory
|
|
*
|
|
* @description
|
|
* The first time a translation table is used, it is loaded in the translation cache for quick retrieval. You
|
|
* can load translation tables directly into the cache by consuming the
|
|
* `$translationCache` service directly.
|
|
*
|
|
* @return {object} $cacheFactory object.
|
|
*/
|
|
.factory('$translationCache', $translationCache);
|
|
|
|
function $translationCache($cacheFactory) {
|
|
|
|
'use strict';
|
|
|
|
return $cacheFactory('translations');
|
|
}
|
|
$translationCache.$inject = ['$cacheFactory'];
|
|
|
|
$translationCache.displayName = '$translationCache';
|
|
return 'pascalprecht.translate';
|
|
|
|
}));
|