commonroutesApp/www/lib/angular-translate/angular-translate.js
www/lib/angular-translate/angular-translate.jsRaw
1/*!
2 * angular-translate - v2.12.1 - 2016-09-15
3 *
4 * Copyright (c) 2016 The angular-translate team, Pascal Precht; Licensed MIT
5 */
6(function (root, factory) {
7 if (typeof define === 'function' && define.amd) {
8 // AMD. Register as an anonymous module unless amdModuleId is set
9 define([], function () {
10 return (factory());
11 });
12 } else if (typeof exports === 'object') {
13 // Node. Does not work with strict CommonJS, but
14 // only CommonJS-like environments that support module.exports,
15 // like Node.
16 module.exports = factory();
17 } else {
18 factory();
19 }
20}(this, function () {
21
22/**
23 * @ngdoc overview
24 * @name pascalprecht.translate
25 *
26 * @description
27 * The main module which holds everything together.
28 */
29runTranslate.$inject = ['$translate'];
30$translate.$inject = ['$STORAGE_KEY', '$windowProvider', '$translateSanitizationProvider', 'pascalprechtTranslateOverrider'];
31$translateDefaultInterpolation.$inject = ['$interpolate', '$translateSanitization'];
32translateDirective.$inject = ['$translate', '$interpolate', '$compile', '$parse', '$rootScope'];
33translateAttrDirective.$inject = ['$translate', '$rootScope'];
34translateCloakDirective.$inject = ['$translate', '$rootScope'];
35translateFilterFactory.$inject = ['$parse', '$translate'];
36$translationCache.$inject = ['$cacheFactory'];
37angular.module('pascalprecht.translate', ['ng'])
38 .run(runTranslate);
39
40function runTranslate($translate) {
41
42 'use strict';
43
44 var key = $translate.storageKey(),
45 storage = $translate.storage();
46
47 var fallbackFromIncorrectStorageValue = function () {
48 var preferred = $translate.preferredLanguage();
49 if (angular.isString(preferred)) {
50 $translate.use(preferred);
51 // $translate.use() will also remember the language.
52 // So, we don't need to call storage.put() here.
53 } else {
54 storage.put(key, $translate.use());
55 }
56 };
57
58 fallbackFromIncorrectStorageValue.displayName = 'fallbackFromIncorrectStorageValue';
59
60 if (storage) {
61 if (!storage.get(key)) {
62 fallbackFromIncorrectStorageValue();
63 } else {
64 $translate.use(storage.get(key))['catch'](fallbackFromIncorrectStorageValue);
65 }
66 } else if (angular.isString($translate.preferredLanguage())) {
67 $translate.use($translate.preferredLanguage());
68 }
69}
70
71runTranslate.displayName = 'runTranslate';
72
73/**
74 * @ngdoc object
75 * @name pascalprecht.translate.$translateSanitizationProvider
76 *
77 * @description
78 *
79 * Configurations for $translateSanitization
80 */
81angular.module('pascalprecht.translate').provider('$translateSanitization', $translateSanitizationProvider);
82
83function $translateSanitizationProvider () {
84
85 'use strict';
86
87 var $sanitize,
88 $sce,
89 currentStrategy = null, // TODO change to either 'sanitize', 'escape' or ['sanitize', 'escapeParameters'] in 3.0.
90 hasConfiguredStrategy = false,
91 hasShownNoStrategyConfiguredWarning = false,
92 strategies;
93
94 /**
95 * Definition of a sanitization strategy function
96 * @callback StrategyFunction
97 * @param {string|object} value - value to be sanitized (either a string or an interpolated value map)
98 * @param {string} mode - either 'text' for a string (translation) or 'params' for the interpolated params
99 * @return {string|object}
100 */
101
102 /**
103 * @ngdoc property
104 * @name strategies
105 * @propertyOf pascalprecht.translate.$translateSanitizationProvider
106 *
107 * @description
108 * Following strategies are built-in:
109 * <dl>
110 * <dt>sanitize</dt>
111 * <dd>Sanitizes HTML in the translation text using $sanitize</dd>
112 * <dt>escape</dt>
113 * <dd>Escapes HTML in the translation</dd>
114 * <dt>sanitizeParameters</dt>
115 * <dd>Sanitizes HTML in the values of the interpolation parameters using $sanitize</dd>
116 * <dt>escapeParameters</dt>
117 * <dd>Escapes HTML in the values of the interpolation parameters</dd>
118 * <dt>escaped</dt>
119 * <dd>Support legacy strategy name 'escaped' for backwards compatibility (will be removed in 3.0)</dd>
120 * </dl>
121 *
122 */
123
124 strategies = {
125 sanitize: function (value, mode/*, context*/) {
126 if (mode === 'text') {
127 value = htmlSanitizeValue(value);
128 }
129 return value;
130 },
131 escape: function (value, mode/*, context*/) {
132 if (mode === 'text') {
133 value = htmlEscapeValue(value);
134 }
135 return value;
136 },
137 sanitizeParameters: function (value, mode/*, context*/) {
138 if (mode === 'params') {
139 value = mapInterpolationParameters(value, htmlSanitizeValue);
140 }
141 return value;
142 },
143 escapeParameters: function (value, mode/*, context*/) {
144 if (mode === 'params') {
145 value = mapInterpolationParameters(value, htmlEscapeValue);
146 }
147 return value;
148 },
149 sce: function (value, mode, context) {
150 if (mode === 'text') {
151 value = htmlTrustValue(value);
152 } else if (mode === 'params') {
153 if (context !== 'filter') {
154 // do html escape in filter context #1101
155 value = mapInterpolationParameters(value, htmlEscapeValue);
156 }
157 }
158 return value;
159 },
160 sceParameters: function (value, mode/*, context*/) {
161 if (mode === 'params') {
162 value = mapInterpolationParameters(value, htmlTrustValue);
163 }
164 return value;
165 }
166 };
167 // Support legacy strategy name 'escaped' for backwards compatibility.
168 // TODO should be removed in 3.0
169 strategies.escaped = strategies.escapeParameters;
170
171 /**
172 * @ngdoc function
173 * @name pascalprecht.translate.$translateSanitizationProvider#addStrategy
174 * @methodOf pascalprecht.translate.$translateSanitizationProvider
175 *
176 * @description
177 * Adds a sanitization strategy to the list of known strategies.
178 *
179 * @param {string} strategyName - unique key for a strategy
180 * @param {StrategyFunction} strategyFunction - strategy function
181 * @returns {object} this
182 */
183 this.addStrategy = function (strategyName, strategyFunction) {
184 strategies[strategyName] = strategyFunction;
185 return this;
186 };
187
188 /**
189 * @ngdoc function
190 * @name pascalprecht.translate.$translateSanitizationProvider#removeStrategy
191 * @methodOf pascalprecht.translate.$translateSanitizationProvider
192 *
193 * @description
194 * Removes a sanitization strategy from the list of known strategies.
195 *
196 * @param {string} strategyName - unique key for a strategy
197 * @returns {object} this
198 */
199 this.removeStrategy = function (strategyName) {
200 delete strategies[strategyName];
201 return this;
202 };
203
204 /**
205 * @ngdoc function
206 * @name pascalprecht.translate.$translateSanitizationProvider#useStrategy
207 * @methodOf pascalprecht.translate.$translateSanitizationProvider
208 *
209 * @description
210 * Selects a sanitization strategy. When an array is provided the strategies will be executed in order.
211 *
212 * @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.
213 * @returns {object} this
214 */
215 this.useStrategy = function (strategy) {
216 hasConfiguredStrategy = true;
217 currentStrategy = strategy;
218 return this;
219 };
220
221 /**
222 * @ngdoc object
223 * @name pascalprecht.translate.$translateSanitization
224 * @requires $injector
225 * @requires $log
226 *
227 * @description
228 * Sanitizes interpolation parameters and translated texts.
229 *
230 */
231 this.$get = ['$injector', '$log', function ($injector, $log) {
232
233 var cachedStrategyMap = {};
234
235 var applyStrategies = function (value, mode, context, selectedStrategies) {
236 angular.forEach(selectedStrategies, function (selectedStrategy) {
237 if (angular.isFunction(selectedStrategy)) {
238 value = selectedStrategy(value, mode, context);
239 } else if (angular.isFunction(strategies[selectedStrategy])) {
240 value = strategies[selectedStrategy](value, mode, context);
241 } else if (angular.isString(strategies[selectedStrategy])) {
242 if (!cachedStrategyMap[strategies[selectedStrategy]]) {
243 try {
244 cachedStrategyMap[strategies[selectedStrategy]] = $injector.get(strategies[selectedStrategy]);
245 } catch (e) {
246 cachedStrategyMap[strategies[selectedStrategy]] = function() {};
247 throw new Error('pascalprecht.translate.$translateSanitization: Unknown sanitization strategy: \'' + selectedStrategy + '\'');
248 }
249 }
250 value = cachedStrategyMap[strategies[selectedStrategy]](value, mode, context);
251 } else {
252 throw new Error('pascalprecht.translate.$translateSanitization: Unknown sanitization strategy: \'' + selectedStrategy + '\'');
253 }
254 });
255 return value;
256 };
257
258 // TODO: should be removed in 3.0
259 var showNoStrategyConfiguredWarning = function () {
260 if (!hasConfiguredStrategy && !hasShownNoStrategyConfiguredWarning) {
261 $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.');
262 hasShownNoStrategyConfiguredWarning = true;
263 }
264 };
265
266 if ($injector.has('$sanitize')) {
267 $sanitize = $injector.get('$sanitize');
268 }
269 if ($injector.has('$sce')) {
270 $sce = $injector.get('$sce');
271 }
272
273 return {
274 /**
275 * @ngdoc function
276 * @name pascalprecht.translate.$translateSanitization#useStrategy
277 * @methodOf pascalprecht.translate.$translateSanitization
278 *
279 * @description
280 * Selects a sanitization strategy. When an array is provided the strategies will be executed in order.
281 *
282 * @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.
283 */
284 useStrategy: (function (self) {
285 return function (strategy) {
286 self.useStrategy(strategy);
287 };
288 })(this),
289
290 /**
291 * @ngdoc function
292 * @name pascalprecht.translate.$translateSanitization#sanitize
293 * @methodOf pascalprecht.translate.$translateSanitization
294 *
295 * @description
296 * Sanitizes a value.
297 *
298 * @param {string|object} value The value which should be sanitized.
299 * @param {string} mode The current sanitization mode, either 'params' or 'text'.
300 * @param {string|StrategyFunction|array} [strategy] Optional custom strategy which should be used instead of the currently selected strategy.
301 * @param {string} [context] The context of this call: filter, service. Default is service
302 * @returns {string|object} sanitized value
303 */
304 sanitize: function (value, mode, strategy, context) {
305 if (!currentStrategy) {
306 showNoStrategyConfiguredWarning();
307 }
308
309 if (!strategy && strategy !== null) {
310 strategy = currentStrategy;
311 }
312
313 if (!strategy) {
314 return value;
315 }
316
317 if (!context) {
318 context = 'service';
319 }
320
321 var selectedStrategies = angular.isArray(strategy) ? strategy : [strategy];
322 return applyStrategies(value, mode, context, selectedStrategies);
323 }
324 };
325 }];
326
327 var htmlEscapeValue = function (value) {
328 var element = angular.element('<div></div>');
329 element.text(value); // not chainable, see #1044
330 return element.html();
331 };
332
333 var htmlSanitizeValue = function (value) {
334 if (!$sanitize) {
335 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\'.');
336 }
337 return $sanitize(value);
338 };
339
340 var htmlTrustValue = function (value) {
341 if (!$sce) {
342 throw new Error('pascalprecht.translate.$translateSanitization: Error cannot find $sce service.');
343 }
344 return $sce.trustAsHtml(value);
345 };
346
347 var mapInterpolationParameters = function (value, iteratee, stack) {
348 if (angular.isDate(value)) {
349 return value;
350 } else if (angular.isObject(value)) {
351 var result = angular.isArray(value) ? [] : {};
352
353 if (!stack) {
354 stack = [];
355 } else {
356 if (stack.indexOf(value) > -1) {
357 throw new Error('pascalprecht.translate.$translateSanitization: Error cannot interpolate parameter due recursive object');
358 }
359 }
360
361 stack.push(value);
362 angular.forEach(value, function (propertyValue, propertyKey) {
363
364 /* Skipping function properties. */
365 if (angular.isFunction(propertyValue)) {
366 return;
367 }
368
369 result[propertyKey] = mapInterpolationParameters(propertyValue, iteratee, stack);
370 });
371 stack.splice(-1, 1); // remove last
372
373 return result;
374 } else if (angular.isNumber(value)) {
375 return value;
376 } else {
377 return iteratee(value);
378 }
379 };
380}
381
382/**
383 * @ngdoc object
384 * @name pascalprecht.translate.$translateProvider
385 * @description
386 *
387 * $translateProvider allows developers to register translation-tables, asynchronous loaders
388 * and similar to configure translation behavior directly inside of a module.
389 *
390 */
391angular.module('pascalprecht.translate')
392.constant('pascalprechtTranslateOverrider', {})
393.provider('$translate', $translate);
394
395function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvider, pascalprechtTranslateOverrider) {
396
397 'use strict';
398
399 var $translationTable = {},
400 $preferredLanguage,
401 $availableLanguageKeys = [],
402 $languageKeyAliases,
403 $fallbackLanguage,
404 $fallbackWasString,
405 $uses,
406 $nextLang,
407 $storageFactory,
408 $storageKey = $STORAGE_KEY,
409 $storagePrefix,
410 $missingTranslationHandlerFactory,
411 $interpolationFactory,
412 $interpolatorFactories = [],
413 $loaderFactory,
414 $cloakClassName = 'translate-cloak',
415 $loaderOptions,
416 $notFoundIndicatorLeft,
417 $notFoundIndicatorRight,
418 $postCompilingEnabled = false,
419 $forceAsyncReloadEnabled = false,
420 $nestedObjectDelimeter = '.',
421 $isReady = false,
422 $keepContent = false,
423 loaderCache,
424 directivePriority = 0,
425 statefulFilter = true,
426 postProcessFn,
427 uniformLanguageTagResolver = 'default',
428 languageTagResolver = {
429 'default': function (tag) {
430 return (tag || '').split('-').join('_');
431 },
432 java: function (tag) {
433 var temp = (tag || '').split('-').join('_');
434 var parts = temp.split('_');
435 return parts.length > 1 ? (parts[0].toLowerCase() + '_' + parts[1].toUpperCase()) : temp;
436 },
437 bcp47: function (tag) {
438 var temp = (tag || '').split('_').join('-');
439 var parts = temp.split('-');
440 return parts.length > 1 ? (parts[0].toLowerCase() + '-' + parts[1].toUpperCase()) : temp;
441 },
442 'iso639-1': function (tag) {
443 var temp = (tag || '').split('_').join('-');
444 var parts = temp.split('-');
445 return parts[0].toLowerCase();
446 }
447 };
448
449 var version = '2.12.1';
450
451 // tries to determine the browsers language
452 var getFirstBrowserLanguage = function () {
453
454 // internal purpose only
455 if (angular.isFunction(pascalprechtTranslateOverrider.getLocale)) {
456 return pascalprechtTranslateOverrider.getLocale();
457 }
458
459 var nav = $windowProvider.$get().navigator,
460 browserLanguagePropertyKeys = ['language', 'browserLanguage', 'systemLanguage', 'userLanguage'],
461 i,
462 language;
463
464 // support for HTML 5.1 "navigator.languages"
465 if (angular.isArray(nav.languages)) {
466 for (i = 0; i < nav.languages.length; i++) {
467 language = nav.languages[i];
468 if (language && language.length) {
469 return language;
470 }
471 }
472 }
473
474 // support for other well known properties in browsers
475 for (i = 0; i < browserLanguagePropertyKeys.length; i++) {
476 language = nav[browserLanguagePropertyKeys[i]];
477 if (language && language.length) {
478 return language;
479 }
480 }
481
482 return null;
483 };
484 getFirstBrowserLanguage.displayName = 'angular-translate/service: getFirstBrowserLanguage';
485
486 // tries to determine the browsers locale
487 var getLocale = function () {
488 var locale = getFirstBrowserLanguage() || '';
489 if (languageTagResolver[uniformLanguageTagResolver]) {
490 locale = languageTagResolver[uniformLanguageTagResolver](locale);
491 }
492 return locale;
493 };
494 getLocale.displayName = 'angular-translate/service: getLocale';
495
496 /**
497 * @name indexOf
498 * @private
499 *
500 * @description
501 * indexOf polyfill. Kinda sorta.
502 *
503 * @param {array} array Array to search in.
504 * @param {string} searchElement Element to search for.
505 *
506 * @returns {int} Index of search element.
507 */
508 var indexOf = function(array, searchElement) {
509 for (var i = 0, len = array.length; i < len; i++) {
510 if (array[i] === searchElement) {
511 return i;
512 }
513 }
514 return -1;
515 };
516
517 /**
518 * @name trim
519 * @private
520 *
521 * @description
522 * trim polyfill
523 *
524 * @returns {string} The string stripped of whitespace from both ends
525 */
526 var trim = function() {
527 return this.toString().replace(/^\s+|\s+$/g, '');
528 };
529
530 var negotiateLocale = function (preferred) {
531 if(!preferred) {
532 return;
533 }
534
535 var avail = [],
536 locale = angular.lowercase(preferred),
537 i = 0,
538 n = $availableLanguageKeys.length;
539
540 for (; i < n; i++) {
541 avail.push(angular.lowercase($availableLanguageKeys[i]));
542 }
543
544 // Check for an exact match in our list of available keys
545 if (indexOf(avail, locale) > -1) {
546 return preferred;
547 }
548
549 if ($languageKeyAliases) {
550 var alias;
551 for (var langKeyAlias in $languageKeyAliases) {
552 if ($languageKeyAliases.hasOwnProperty(langKeyAlias)) {
553 var hasWildcardKey = false;
554 var hasExactKey = Object.prototype.hasOwnProperty.call($languageKeyAliases, langKeyAlias) &&
555 angular.lowercase(langKeyAlias) === angular.lowercase(preferred);
556
557 if (langKeyAlias.slice(-1) === '*') {
558 hasWildcardKey = langKeyAlias.slice(0, -1) === preferred.slice(0, langKeyAlias.length - 1);
559 }
560 if (hasExactKey || hasWildcardKey) {
561 alias = $languageKeyAliases[langKeyAlias];
562 if (indexOf(avail, angular.lowercase(alias)) > -1) {
563 return alias;
564 }
565 }
566 }
567 }
568 }
569
570 // Check for a language code without region
571 var parts = preferred.split('_');
572
573 if (parts.length > 1 && indexOf(avail, angular.lowercase(parts[0])) > -1) {
574 return parts[0];
575 }
576
577 // If everything fails, return undefined.
578 return;
579 };
580
581 /**
582 * @ngdoc function
583 * @name pascalprecht.translate.$translateProvider#translations
584 * @methodOf pascalprecht.translate.$translateProvider
585 *
586 * @description
587 * Registers a new translation table for specific language key.
588 *
589 * To register a translation table for specific language, pass a defined language
590 * key as first parameter.
591 *
592 * <pre>
593 * // register translation table for language: 'de_DE'
594 * $translateProvider.translations('de_DE', {
595 * 'GREETING': 'Hallo Welt!'
596 * });
597 *
598 * // register another one
599 * $translateProvider.translations('en_US', {
600 * 'GREETING': 'Hello world!'
601 * });
602 * </pre>
603 *
604 * When registering multiple translation tables for for the same language key,
605 * the actual translation table gets extended. This allows you to define module
606 * specific translation which only get added, once a specific module is loaded in
607 * your app.
608 *
609 * Invoking this method with no arguments returns the translation table which was
610 * registered with no language key. Invoking it with a language key returns the
611 * related translation table.
612 *
613 * @param {string} langKey A language key.
614 * @param {object} translationTable A plain old JavaScript object that represents a translation table.
615 *
616 */
617 var translations = function (langKey, translationTable) {
618
619 if (!langKey && !translationTable) {
620 return $translationTable;
621 }
622
623 if (langKey && !translationTable) {
624 if (angular.isString(langKey)) {
625 return $translationTable[langKey];
626 }
627 } else {
628 if (!angular.isObject($translationTable[langKey])) {
629 $translationTable[langKey] = {};
630 }
631 angular.extend($translationTable[langKey], flatObject(translationTable));
632 }
633 return this;
634 };
635
636 this.translations = translations;
637
638 /**
639 * @ngdoc function
640 * @name pascalprecht.translate.$translateProvider#cloakClassName
641 * @methodOf pascalprecht.translate.$translateProvider
642 *
643 * @description
644 *
645 * Let's you change the class name for `translate-cloak` directive.
646 * Default class name is `translate-cloak`.
647 *
648 * @param {string} name translate-cloak class name
649 */
650 this.cloakClassName = function (name) {
651 if (!name) {
652 return $cloakClassName;
653 }
654 $cloakClassName = name;
655 return this;
656 };
657
658 /**
659 * @ngdoc function
660 * @name pascalprecht.translate.$translateProvider#nestedObjectDelimeter
661 * @methodOf pascalprecht.translate.$translateProvider
662 *
663 * @description
664 *
665 * Let's you change the delimiter for namespaced translations.
666 * Default delimiter is `.`.
667 *
668 * @param {string} delimiter namespace separator
669 */
670 this.nestedObjectDelimeter = function (delimiter) {
671 if (!delimiter) {
672 return $nestedObjectDelimeter;
673 }
674 $nestedObjectDelimeter = delimiter;
675 return this;
676 };
677
678 /**
679 * @name flatObject
680 * @private
681 *
682 * @description
683 * Flats an object. This function is used to flatten given translation data with
684 * namespaces, so they are later accessible via dot notation.
685 */
686 var flatObject = function (data, path, result, prevKey) {
687 var key, keyWithPath, keyWithShortPath, val;
688
689 if (!path) {
690 path = [];
691 }
692 if (!result) {
693 result = {};
694 }
695 for (key in data) {
696 if (!Object.prototype.hasOwnProperty.call(data, key)) {
697 continue;
698 }
699 val = data[key];
700 if (angular.isObject(val)) {
701 flatObject(val, path.concat(key), result, key);
702 } else {
703 keyWithPath = path.length ? ('' + path.join($nestedObjectDelimeter) + $nestedObjectDelimeter + key) : key;
704 if(path.length && key === prevKey){
705 // Create shortcut path (foo.bar == foo.bar.bar)
706 keyWithShortPath = '' + path.join($nestedObjectDelimeter);
707 // Link it to original path
708 result[keyWithShortPath] = '@:' + keyWithPath;
709 }
710 result[keyWithPath] = val;
711 }
712 }
713 return result;
714 };
715 flatObject.displayName = 'flatObject';
716
717 /**
718 * @ngdoc function
719 * @name pascalprecht.translate.$translateProvider#addInterpolation
720 * @methodOf pascalprecht.translate.$translateProvider
721 *
722 * @description
723 * Adds interpolation services to angular-translate, so it can manage them.
724 *
725 * @param {object} factory Interpolation service factory
726 */
727 this.addInterpolation = function (factory) {
728 $interpolatorFactories.push(factory);
729 return this;
730 };
731
732 /**
733 * @ngdoc function
734 * @name pascalprecht.translate.$translateProvider#useMessageFormatInterpolation
735 * @methodOf pascalprecht.translate.$translateProvider
736 *
737 * @description
738 * Tells angular-translate to use interpolation functionality of messageformat.js.
739 * This is useful when having high level pluralization and gender selection.
740 */
741 this.useMessageFormatInterpolation = function () {
742 return this.useInterpolation('$translateMessageFormatInterpolation');
743 };
744
745 /**
746 * @ngdoc function
747 * @name pascalprecht.translate.$translateProvider#useInterpolation
748 * @methodOf pascalprecht.translate.$translateProvider
749 *
750 * @description
751 * Tells angular-translate which interpolation style to use as default, application-wide.
752 * Simply pass a factory/service name. The interpolation service has to implement
753 * the correct interface.
754 *
755 * @param {string} factory Interpolation service name.
756 */
757 this.useInterpolation = function (factory) {
758 $interpolationFactory = factory;
759 return this;
760 };
761
762 /**
763 * @ngdoc function
764 * @name pascalprecht.translate.$translateProvider#useSanitizeStrategy
765 * @methodOf pascalprecht.translate.$translateProvider
766 *
767 * @description
768 * Simply sets a sanitation strategy type.
769 *
770 * @param {string} value Strategy type.
771 */
772 this.useSanitizeValueStrategy = function (value) {
773 $translateSanitizationProvider.useStrategy(value);
774 return this;
775 };
776
777 /**
778 * @ngdoc function
779 * @name pascalprecht.translate.$translateProvider#preferredLanguage
780 * @methodOf pascalprecht.translate.$translateProvider
781 *
782 * @description
783 * Tells the module which of the registered translation tables to use for translation
784 * at initial startup by passing a language key. Similar to `$translateProvider#use`
785 * only that it says which language to **prefer**.
786 *
787 * @param {string} langKey A language key.
788 */
789 this.preferredLanguage = function(langKey) {
790 if (langKey) {
791 setupPreferredLanguage(langKey);
792 return this;
793 }
794 return $preferredLanguage;
795 };
796 var setupPreferredLanguage = function (langKey) {
797 if (langKey) {
798 $preferredLanguage = langKey;
799 }
800 return $preferredLanguage;
801 };
802 /**
803 * @ngdoc function
804 * @name pascalprecht.translate.$translateProvider#translationNotFoundIndicator
805 * @methodOf pascalprecht.translate.$translateProvider
806 *
807 * @description
808 * Sets an indicator which is used when a translation isn't found. E.g. when
809 * setting the indicator as 'X' and one tries to translate a translation id
810 * called `NOT_FOUND`, this will result in `X NOT_FOUND X`.
811 *
812 * Internally this methods sets a left indicator and a right indicator using
813 * `$translateProvider.translationNotFoundIndicatorLeft()` and
814 * `$translateProvider.translationNotFoundIndicatorRight()`.
815 *
816 * **Note**: These methods automatically add a whitespace between the indicators
817 * and the translation id.
818 *
819 * @param {string} indicator An indicator, could be any string.
820 */
821 this.translationNotFoundIndicator = function (indicator) {
822 this.translationNotFoundIndicatorLeft(indicator);
823 this.translationNotFoundIndicatorRight(indicator);
824 return this;
825 };
826
827 /**
828 * ngdoc function
829 * @name pascalprecht.translate.$translateProvider#translationNotFoundIndicatorLeft
830 * @methodOf pascalprecht.translate.$translateProvider
831 *
832 * @description
833 * Sets an indicator which is used when a translation isn't found left to the
834 * translation id.
835 *
836 * @param {string} indicator An indicator.
837 */
838 this.translationNotFoundIndicatorLeft = function (indicator) {
839 if (!indicator) {
840 return $notFoundIndicatorLeft;
841 }
842 $notFoundIndicatorLeft = indicator;
843 return this;
844 };
845
846 /**
847 * ngdoc function
848 * @name pascalprecht.translate.$translateProvider#translationNotFoundIndicatorLeft
849 * @methodOf pascalprecht.translate.$translateProvider
850 *
851 * @description
852 * Sets an indicator which is used when a translation isn't found right to the
853 * translation id.
854 *
855 * @param {string} indicator An indicator.
856 */
857 this.translationNotFoundIndicatorRight = function (indicator) {
858 if (!indicator) {
859 return $notFoundIndicatorRight;
860 }
861 $notFoundIndicatorRight = indicator;
862 return this;
863 };
864
865 /**
866 * @ngdoc function
867 * @name pascalprecht.translate.$translateProvider#fallbackLanguage
868 * @methodOf pascalprecht.translate.$translateProvider
869 *
870 * @description
871 * Tells the module which of the registered translation tables to use when missing translations
872 * at initial startup by passing a language key. Similar to `$translateProvider#use`
873 * only that it says which language to **fallback**.
874 *
875 * @param {string||array} langKey A language key.
876 *
877 */
878 this.fallbackLanguage = function (langKey) {
879 fallbackStack(langKey);
880 return this;
881 };
882
883 var fallbackStack = function (langKey) {
884 if (langKey) {
885 if (angular.isString(langKey)) {
886 $fallbackWasString = true;
887 $fallbackLanguage = [ langKey ];
888 } else if (angular.isArray(langKey)) {
889 $fallbackWasString = false;
890 $fallbackLanguage = langKey;
891 }
892 if (angular.isString($preferredLanguage) && indexOf($fallbackLanguage, $preferredLanguage) < 0) {
893 $fallbackLanguage.push($preferredLanguage);
894 }
895
896 return this;
897 } else {
898 if ($fallbackWasString) {
899 return $fallbackLanguage[0];
900 } else {
901 return $fallbackLanguage;
902 }
903 }
904 };
905
906 /**
907 * @ngdoc function
908 * @name pascalprecht.translate.$translateProvider#use
909 * @methodOf pascalprecht.translate.$translateProvider
910 *
911 * @description
912 * Set which translation table to use for translation by given language key. When
913 * trying to 'use' a language which isn't provided, it'll throw an error.
914 *
915 * You actually don't have to use this method since `$translateProvider#preferredLanguage`
916 * does the job too.
917 *
918 * @param {string} langKey A language key.
919 */
920 this.use = function (langKey) {
921 if (langKey) {
922 if (!$translationTable[langKey] && (!$loaderFactory)) {
923 // only throw an error, when not loading translation data asynchronously
924 throw new Error('$translateProvider couldn\'t find translationTable for langKey: \'' + langKey + '\'');
925 }
926 $uses = langKey;
927 return this;
928 }
929 return $uses;
930 };
931
932 /**
933 * @ngdoc function
934 * @name pascalprecht.translate.$translateProvider#resolveClientLocale
935 * @methodOf pascalprecht.translate.$translateProvider
936 *
937 * @description
938 * This returns the current browser/client's language key. The result is processed with the configured uniform tag resolver.
939 *
940 * @returns {string} the current client/browser language key
941 */
942 this.resolveClientLocale = function () {
943 return getLocale();
944 };
945
946 /**
947 * @ngdoc function
948 * @name pascalprecht.translate.$translateProvider#storageKey
949 * @methodOf pascalprecht.translate.$translateProvider
950 *
951 * @description
952 * Tells the module which key must represent the choosed language by a user in the storage.
953 *
954 * @param {string} key A key for the storage.
955 */
956 var storageKey = function(key) {
957 if (!key) {
958 if ($storagePrefix) {
959 return $storagePrefix + $storageKey;
960 }
961 return $storageKey;
962 }
963 $storageKey = key;
964 return this;
965 };
966
967 this.storageKey = storageKey;
968
969 /**
970 * @ngdoc function
971 * @name pascalprecht.translate.$translateProvider#useUrlLoader
972 * @methodOf pascalprecht.translate.$translateProvider
973 *
974 * @description
975 * Tells angular-translate to use `$translateUrlLoader` extension service as loader.
976 *
977 * @param {string} url Url
978 * @param {Object=} options Optional configuration object
979 */
980 this.useUrlLoader = function (url, options) {
981 return this.useLoader('$translateUrlLoader', angular.extend({ url: url }, options));
982 };
983
984 /**
985 * @ngdoc function
986 * @name pascalprecht.translate.$translateProvider#useStaticFilesLoader
987 * @methodOf pascalprecht.translate.$translateProvider
988 *
989 * @description
990 * Tells angular-translate to use `$translateStaticFilesLoader` extension service as loader.
991 *
992 * @param {Object=} options Optional configuration object
993 */
994 this.useStaticFilesLoader = function (options) {
995 return this.useLoader('$translateStaticFilesLoader', options);
996 };
997
998 /**
999 * @ngdoc function
1000 * @name pascalprecht.translate.$translateProvider#useLoader
1001 * @methodOf pascalprecht.translate.$translateProvider
1002 *
1003 * @description
1004 * Tells angular-translate to use any other service as loader.
1005 *
1006 * @param {string} loaderFactory Factory name to use
1007 * @param {Object=} options Optional configuration object
1008 */
1009 this.useLoader = function (loaderFactory, options) {
1010 $loaderFactory = loaderFactory;
1011 $loaderOptions = options || {};
1012 return this;
1013 };
1014
1015 /**
1016 * @ngdoc function
1017 * @name pascalprecht.translate.$translateProvider#useLocalStorage
1018 * @methodOf pascalprecht.translate.$translateProvider
1019 *
1020 * @description
1021 * Tells angular-translate to use `$translateLocalStorage` service as storage layer.
1022 *
1023 */
1024 this.useLocalStorage = function () {
1025 return this.useStorage('$translateLocalStorage');
1026 };
1027
1028 /**
1029 * @ngdoc function
1030 * @name pascalprecht.translate.$translateProvider#useCookieStorage
1031 * @methodOf pascalprecht.translate.$translateProvider
1032 *
1033 * @description
1034 * Tells angular-translate to use `$translateCookieStorage` service as storage layer.
1035 */
1036 this.useCookieStorage = function () {
1037 return this.useStorage('$translateCookieStorage');
1038 };
1039
1040 /**
1041 * @ngdoc function
1042 * @name pascalprecht.translate.$translateProvider#useStorage
1043 * @methodOf pascalprecht.translate.$translateProvider
1044 *
1045 * @description
1046 * Tells angular-translate to use custom service as storage layer.
1047 */
1048 this.useStorage = function (storageFactory) {
1049 $storageFactory = storageFactory;
1050 return this;
1051 };
1052
1053 /**
1054 * @ngdoc function
1055 * @name pascalprecht.translate.$translateProvider#storagePrefix
1056 * @methodOf pascalprecht.translate.$translateProvider
1057 *
1058 * @description
1059 * Sets prefix for storage key.
1060 *
1061 * @param {string} prefix Storage key prefix
1062 */
1063 this.storagePrefix = function (prefix) {
1064 if (!prefix) {
1065 return prefix;
1066 }
1067 $storagePrefix = prefix;
1068 return this;
1069 };
1070
1071 /**
1072 * @ngdoc function
1073 * @name pascalprecht.translate.$translateProvider#useMissingTranslationHandlerLog
1074 * @methodOf pascalprecht.translate.$translateProvider
1075 *
1076 * @description
1077 * Tells angular-translate to use built-in log handler when trying to translate
1078 * a translation Id which doesn't exist.
1079 *
1080 * This is actually a shortcut method for `useMissingTranslationHandler()`.
1081 *
1082 */
1083 this.useMissingTranslationHandlerLog = function () {
1084 return this.useMissingTranslationHandler('$translateMissingTranslationHandlerLog');
1085 };
1086
1087 /**
1088 * @ngdoc function
1089 * @name pascalprecht.translate.$translateProvider#useMissingTranslationHandler
1090 * @methodOf pascalprecht.translate.$translateProvider
1091 *
1092 * @description
1093 * Expects a factory name which later gets instantiated with `$injector`.
1094 * This method can be used to tell angular-translate to use a custom
1095 * missingTranslationHandler. Just build a factory which returns a function
1096 * and expects a translation id as argument.
1097 *
1098 * Example:
1099 * <pre>
1100 * app.config(function ($translateProvider) {
1101 * $translateProvider.useMissingTranslationHandler('customHandler');
1102 * });
1103 *
1104 * app.factory('customHandler', function (dep1, dep2) {
1105 * return function (translationId) {
1106 * // something with translationId and dep1 and dep2
1107 * };
1108 * });
1109 * </pre>
1110 *
1111 * @param {string} factory Factory name
1112 */
1113 this.useMissingTranslationHandler = function (factory) {
1114 $missingTranslationHandlerFactory = factory;
1115 return this;
1116 };
1117
1118 /**
1119 * @ngdoc function
1120 * @name pascalprecht.translate.$translateProvider#usePostCompiling
1121 * @methodOf pascalprecht.translate.$translateProvider
1122 *
1123 * @description
1124 * If post compiling is enabled, all translated values will be processed
1125 * again with AngularJS' $compile.
1126 *
1127 * Example:
1128 * <pre>
1129 * app.config(function ($translateProvider) {
1130 * $translateProvider.usePostCompiling(true);
1131 * });
1132 * </pre>
1133 *
1134 * @param {string} factory Factory name
1135 */
1136 this.usePostCompiling = function (value) {
1137 $postCompilingEnabled = !(!value);
1138 return this;
1139 };
1140
1141 /**
1142 * @ngdoc function
1143 * @name pascalprecht.translate.$translateProvider#forceAsyncReload
1144 * @methodOf pascalprecht.translate.$translateProvider
1145 *
1146 * @description
1147 * If force async reload is enabled, async loader will always be called
1148 * even if $translationTable already contains the language key, adding
1149 * possible new entries to the $translationTable.
1150 *
1151 * Example:
1152 * <pre>
1153 * app.config(function ($translateProvider) {
1154 * $translateProvider.forceAsyncReload(true);
1155 * });
1156 * </pre>
1157 *
1158 * @param {boolean} value - valid values are true or false
1159 */
1160 this.forceAsyncReload = function (value) {
1161 $forceAsyncReloadEnabled = !(!value);
1162 return this;
1163 };
1164
1165 /**
1166 * @ngdoc function
1167 * @name pascalprecht.translate.$translateProvider#uniformLanguageTag
1168 * @methodOf pascalprecht.translate.$translateProvider
1169 *
1170 * @description
1171 * Tells angular-translate which language tag should be used as a result when determining
1172 * the current browser language.
1173 *
1174 * This setting must be set before invoking {@link pascalprecht.translate.$translateProvider#methods_determinePreferredLanguage determinePreferredLanguage()}.
1175 *
1176 * <pre>
1177 * $translateProvider
1178 * .uniformLanguageTag('bcp47')
1179 * .determinePreferredLanguage()
1180 * </pre>
1181 *
1182 * The resolver currently supports:
1183 * * default
1184 * (traditionally: hyphens will be converted into underscores, i.e. en-US => en_US)
1185 * en-US => en_US
1186 * en_US => en_US
1187 * en-us => en_us
1188 * * java
1189 * like default, but the second part will be always in uppercase
1190 * en-US => en_US
1191 * en_US => en_US
1192 * en-us => en_US
1193 * * BCP 47 (RFC 4646 & 4647)
1194 * en-US => en-US
1195 * en_US => en-US
1196 * en-us => en-US
1197 *
1198 * See also:
1199 * * http://en.wikipedia.org/wiki/IETF_language_tag
1200 * * http://www.w3.org/International/core/langtags/
1201 * * http://tools.ietf.org/html/bcp47
1202 *
1203 * @param {string|object} options - options (or standard)
1204 * @param {string} options.standard - valid values are 'default', 'bcp47', 'java'
1205 */
1206 this.uniformLanguageTag = function (options) {
1207
1208 if (!options) {
1209 options = {};
1210 } else if (angular.isString(options)) {
1211 options = {
1212 standard: options
1213 };
1214 }
1215
1216 uniformLanguageTagResolver = options.standard;
1217
1218 return this;
1219 };
1220
1221 /**
1222 * @ngdoc function
1223 * @name pascalprecht.translate.$translateProvider#determinePreferredLanguage
1224 * @methodOf pascalprecht.translate.$translateProvider
1225 *
1226 * @description
1227 * Tells angular-translate to try to determine on its own which language key
1228 * to set as preferred language. When `fn` is given, angular-translate uses it
1229 * to determine a language key, otherwise it uses the built-in `getLocale()`
1230 * method.
1231 *
1232 * The `getLocale()` returns a language key in the format `[lang]_[country]` or
1233 * `[lang]` depending on what the browser provides.
1234 *
1235 * Use this method at your own risk, since not all browsers return a valid
1236 * locale (see {@link pascalprecht.translate.$translateProvider#methods_uniformLanguageTag uniformLanguageTag()}).
1237 *
1238 * @param {Function=} fn Function to determine a browser's locale
1239 */
1240 this.determinePreferredLanguage = function (fn) {
1241
1242 var locale = (fn && angular.isFunction(fn)) ? fn() : getLocale();
1243
1244 if (!$availableLanguageKeys.length) {
1245 $preferredLanguage = locale;
1246 } else {
1247 $preferredLanguage = negotiateLocale(locale) || locale;
1248 }
1249
1250 return this;
1251 };
1252
1253 /**
1254 * @ngdoc function
1255 * @name pascalprecht.translate.$translateProvider#registerAvailableLanguageKeys
1256 * @methodOf pascalprecht.translate.$translateProvider
1257 *
1258 * @description
1259 * Registers a set of language keys the app will work with. Use this method in
1260 * combination with
1261 * {@link pascalprecht.translate.$translateProvider#determinePreferredLanguage determinePreferredLanguage}.
1262 * When available languages keys are registered, angular-translate
1263 * tries to find the best fitting language key depending on the browsers locale,
1264 * considering your language key convention.
1265 *
1266 * @param {object} languageKeys Array of language keys the your app will use
1267 * @param {object=} aliases Alias map.
1268 */
1269 this.registerAvailableLanguageKeys = function (languageKeys, aliases) {
1270 if (languageKeys) {
1271 $availableLanguageKeys = languageKeys;
1272 if (aliases) {
1273 $languageKeyAliases = aliases;
1274 }
1275 return this;
1276 }
1277 return $availableLanguageKeys;
1278 };
1279
1280 /**
1281 * @ngdoc function
1282 * @name pascalprecht.translate.$translateProvider#useLoaderCache
1283 * @methodOf pascalprecht.translate.$translateProvider
1284 *
1285 * @description
1286 * Registers a cache for internal $http based loaders.
1287 * {@link pascalprecht.translate.$translationCache $translationCache}.
1288 * When false the cache will be disabled (default). When true or undefined
1289 * the cache will be a default (see $cacheFactory). When an object it will
1290 * be treat as a cache object itself: the usage is $http({cache: cache})
1291 *
1292 * @param {object} cache boolean, string or cache-object
1293 */
1294 this.useLoaderCache = function (cache) {
1295 if (cache === false) {
1296 // disable cache
1297 loaderCache = undefined;
1298 } else if (cache === true) {
1299 // enable cache using AJS defaults
1300 loaderCache = true;
1301 } else if (typeof(cache) === 'undefined') {
1302 // enable cache using default
1303 loaderCache = '$translationCache';
1304 } else if (cache) {
1305 // enable cache using given one (see $cacheFactory)
1306 loaderCache = cache;
1307 }
1308 return this;
1309 };
1310
1311 /**
1312 * @ngdoc function
1313 * @name pascalprecht.translate.$translateProvider#directivePriority
1314 * @methodOf pascalprecht.translate.$translateProvider
1315 *
1316 * @description
1317 * Sets the default priority of the translate directive. The standard value is `0`.
1318 * Calling this function without an argument will return the current value.
1319 *
1320 * @param {number} priority for the translate-directive
1321 */
1322 this.directivePriority = function (priority) {
1323 if (priority === undefined) {
1324 // getter
1325 return directivePriority;
1326 } else {
1327 // setter with chaining
1328 directivePriority = priority;
1329 return this;
1330 }
1331 };
1332
1333 /**
1334 * @ngdoc function
1335 * @name pascalprecht.translate.$translateProvider#statefulFilter
1336 * @methodOf pascalprecht.translate.$translateProvider
1337 *
1338 * @description
1339 * Since AngularJS 1.3, filters which are not stateless (depending at the scope)
1340 * have to explicit define this behavior.
1341 * Sets whether the translate filter should be stateful or stateless. The standard value is `true`
1342 * meaning being stateful.
1343 * Calling this function without an argument will return the current value.
1344 *
1345 * @param {boolean} state - defines the state of the filter
1346 */
1347 this.statefulFilter = function (state) {
1348 if (state === undefined) {
1349 // getter
1350 return statefulFilter;
1351 } else {
1352 // setter with chaining
1353 statefulFilter = state;
1354 return this;
1355 }
1356 };
1357
1358 /**
1359 * @ngdoc function
1360 * @name pascalprecht.translate.$translateProvider#postProcess
1361 * @methodOf pascalprecht.translate.$translateProvider
1362 *
1363 * @description
1364 * The post processor will be intercept right after the translation result. It can modify the result.
1365 *
1366 * @param {object} fn Function or service name (string) to be called after the translation value has been set / resolved. The function itself will enrich every value being processed and then continue the normal resolver process
1367 */
1368 this.postProcess = function (fn) {
1369 if (fn) {
1370 postProcessFn = fn;
1371 } else {
1372 postProcessFn = undefined;
1373 }
1374 return this;
1375 };
1376
1377 /**
1378 * @ngdoc function
1379 * @name pascalprecht.translate.$translateProvider#keepContent
1380 * @methodOf pascalprecht.translate.$translateProvider
1381 *
1382 * @description
1383 * If keepContent is set to true than translate directive will always use innerHTML
1384 * as a default translation
1385 *
1386 * Example:
1387 * <pre>
1388 * app.config(function ($translateProvider) {
1389 * $translateProvider.keepContent(true);
1390 * });
1391 * </pre>
1392 *
1393 * @param {boolean} value - valid values are true or false
1394 */
1395 this.keepContent = function (value) {
1396 $keepContent = !(!value);
1397 return this;
1398 };
1399
1400 /**
1401 * @ngdoc object
1402 * @name pascalprecht.translate.$translate
1403 * @requires $interpolate
1404 * @requires $log
1405 * @requires $rootScope
1406 * @requires $q
1407 *
1408 * @description
1409 * The `$translate` service is the actual core of angular-translate. It expects a translation id
1410 * and optional interpolate parameters to translate contents.
1411 *
1412 * <pre>
1413 * $translate('HEADLINE_TEXT').then(function (translation) {
1414 * $scope.translatedText = translation;
1415 * });
1416 * </pre>
1417 *
1418 * @param {string|array} translationId A token which represents a translation id
1419 * This can be optionally an array of translation ids which
1420 * results that the function returns an object where each key
1421 * is the translation id and the value the translation.
1422 * @param {object=} interpolateParams An object hash for dynamic values
1423 * @param {string} interpolationId The id of the interpolation to use
1424 * @param {string} defaultTranslationText the optional default translation text that is written as
1425 * as default text in case it is not found in any configured language
1426 * @param {string} forceLanguage A language to be used instead of the current language
1427 * @returns {object} promise
1428 */
1429 this.$get = [
1430 '$log',
1431 '$injector',
1432 '$rootScope',
1433 '$q',
1434 function ($log, $injector, $rootScope, $q) {
1435
1436 var Storage,
1437 defaultInterpolator = $injector.get($interpolationFactory || '$translateDefaultInterpolation'),
1438 pendingLoader = false,
1439 interpolatorHashMap = {},
1440 langPromises = {},
1441 fallbackIndex,
1442 startFallbackIteration;
1443
1444 var $translate = function (translationId, interpolateParams, interpolationId, defaultTranslationText, forceLanguage) {
1445 if (!$uses && $preferredLanguage) {
1446 $uses = $preferredLanguage;
1447 }
1448 var uses = (forceLanguage && forceLanguage !== $uses) ? // we don't want to re-negotiate $uses
1449 (negotiateLocale(forceLanguage) || forceLanguage) : $uses;
1450
1451 // Check forceLanguage is present
1452 if (forceLanguage) {
1453 loadTranslationsIfMissing(forceLanguage);
1454 }
1455
1456 // Duck detection: If the first argument is an array, a bunch of translations was requested.
1457 // The result is an object.
1458 if (angular.isArray(translationId)) {
1459 // Inspired by Q.allSettled by Kris Kowal
1460 // https://github.com/kriskowal/q/blob/b0fa72980717dc202ffc3cbf03b936e10ebbb9d7/q.js#L1553-1563
1461 // This transforms all promises regardless resolved or rejected
1462 var translateAll = function (translationIds) {
1463 var results = {}; // storing the actual results
1464 var promises = []; // promises to wait for
1465 // Wraps the promise a) being always resolved and b) storing the link id->value
1466 var translate = function (translationId) {
1467 var deferred = $q.defer();
1468 var regardless = function (value) {
1469 results[translationId] = value;
1470 deferred.resolve([translationId, value]);
1471 };
1472 // we don't care whether the promise was resolved or rejected; just store the values
1473 $translate(translationId, interpolateParams, interpolationId, defaultTranslationText, forceLanguage).then(regardless, regardless);
1474 return deferred.promise;
1475 };
1476 for (var i = 0, c = translationIds.length; i < c; i++) {
1477 promises.push(translate(translationIds[i]));
1478 }
1479 // wait for all (including storing to results)
1480 return $q.all(promises).then(function () {
1481 // return the results
1482 return results;
1483 });
1484 };
1485 return translateAll(translationId);
1486 }
1487
1488 var deferred = $q.defer();
1489
1490 // trim off any whitespace
1491 if (translationId) {
1492 translationId = trim.apply(translationId);
1493 }
1494
1495 var promiseToWaitFor = (function () {
1496 var promise = $preferredLanguage ?
1497 langPromises[$preferredLanguage] :
1498 langPromises[uses];
1499
1500 fallbackIndex = 0;
1501
1502 if ($storageFactory && !promise) {
1503 // looks like there's no pending promise for $preferredLanguage or
1504 // $uses. Maybe there's one pending for a language that comes from
1505 // storage.
1506 var langKey = Storage.get($storageKey);
1507 promise = langPromises[langKey];
1508
1509 if ($fallbackLanguage && $fallbackLanguage.length) {
1510 var index = indexOf($fallbackLanguage, langKey);
1511 // maybe the language from storage is also defined as fallback language
1512 // we increase the fallback language index to not search in that language
1513 // as fallback, since it's probably the first used language
1514 // in that case the index starts after the first element
1515 fallbackIndex = (index === 0) ? 1 : 0;
1516
1517 // but we can make sure to ALWAYS fallback to preferred language at least
1518 if (indexOf($fallbackLanguage, $preferredLanguage) < 0) {
1519 $fallbackLanguage.push($preferredLanguage);
1520 }
1521 }
1522 }
1523 return promise;
1524 }());
1525
1526 if (!promiseToWaitFor) {
1527 // no promise to wait for? okay. Then there's no loader registered
1528 // nor is a one pending for language that comes from storage.
1529 // We can just translate.
1530 determineTranslation(translationId, interpolateParams, interpolationId, defaultTranslationText, uses).then(deferred.resolve, deferred.reject);
1531 } else {
1532 var promiseResolved = function () {
1533 // $uses may have changed while waiting
1534 if (!forceLanguage) {
1535 uses = $uses;
1536 }
1537 determineTranslation(translationId, interpolateParams, interpolationId, defaultTranslationText, uses).then(deferred.resolve, deferred.reject);
1538 };
1539 promiseResolved.displayName = 'promiseResolved';
1540
1541 promiseToWaitFor['finally'](promiseResolved);
1542 }
1543 return deferred.promise;
1544 };
1545
1546 /**
1547 * @name applyNotFoundIndicators
1548 * @private
1549 *
1550 * @description
1551 * Applies not fount indicators to given translation id, if needed.
1552 * This function gets only executed, if a translation id doesn't exist,
1553 * which is why a translation id is expected as argument.
1554 *
1555 * @param {string} translationId Translation id.
1556 * @returns {string} Same as given translation id but applied with not found
1557 * indicators.
1558 */
1559 var applyNotFoundIndicators = function (translationId) {
1560 // applying notFoundIndicators
1561 if ($notFoundIndicatorLeft) {
1562 translationId = [$notFoundIndicatorLeft, translationId].join(' ');
1563 }
1564 if ($notFoundIndicatorRight) {
1565 translationId = [translationId, $notFoundIndicatorRight].join(' ');
1566 }
1567 return translationId;
1568 };
1569
1570 /**
1571 * @name useLanguage
1572 * @private
1573 *
1574 * @description
1575 * Makes actual use of a language by setting a given language key as used
1576 * language and informs registered interpolators to also use the given
1577 * key as locale.
1578 *
1579 * @param {string} key Locale key.
1580 */
1581 var useLanguage = function (key) {
1582 $uses = key;
1583
1584 // make sure to store new language key before triggering success event
1585 if ($storageFactory) {
1586 Storage.put($translate.storageKey(), $uses);
1587 }
1588
1589 $rootScope.$emit('$translateChangeSuccess', {language: key});
1590
1591 // inform default interpolator
1592 defaultInterpolator.setLocale($uses);
1593
1594 var eachInterpolator = function (interpolator, id) {
1595 interpolatorHashMap[id].setLocale($uses);
1596 };
1597 eachInterpolator.displayName = 'eachInterpolatorLocaleSetter';
1598
1599 // inform all others too!
1600 angular.forEach(interpolatorHashMap, eachInterpolator);
1601 $rootScope.$emit('$translateChangeEnd', {language: key});
1602 };
1603
1604 /**
1605 * @name loadAsync
1606 * @private
1607 *
1608 * @description
1609 * Kicks of registered async loader using `$injector` and applies existing
1610 * loader options. When resolved, it updates translation tables accordingly
1611 * or rejects with given language key.
1612 *
1613 * @param {string} key Language key.
1614 * @return {Promise} A promise.
1615 */
1616 var loadAsync = function (key) {
1617 if (!key) {
1618 throw 'No language key specified for loading.';
1619 }
1620
1621 var deferred = $q.defer();
1622
1623 $rootScope.$emit('$translateLoadingStart', {language: key});
1624 pendingLoader = true;
1625
1626 var cache = loaderCache;
1627 if (typeof(cache) === 'string') {
1628 // getting on-demand instance of loader
1629 cache = $injector.get(cache);
1630 }
1631
1632 var loaderOptions = angular.extend({}, $loaderOptions, {
1633 key: key,
1634 $http: angular.extend({}, {
1635 cache: cache
1636 }, $loaderOptions.$http)
1637 });
1638
1639 var onLoaderSuccess = function (data) {
1640 var translationTable = {};
1641 $rootScope.$emit('$translateLoadingSuccess', {language: key});
1642
1643 if (angular.isArray(data)) {
1644 angular.forEach(data, function (table) {
1645 angular.extend(translationTable, flatObject(table));
1646 });
1647 } else {
1648 angular.extend(translationTable, flatObject(data));
1649 }
1650 pendingLoader = false;
1651 deferred.resolve({
1652 key: key,
1653 table: translationTable
1654 });
1655 $rootScope.$emit('$translateLoadingEnd', {language: key});
1656 };
1657 onLoaderSuccess.displayName = 'onLoaderSuccess';
1658
1659 var onLoaderError = function (key) {
1660 $rootScope.$emit('$translateLoadingError', {language: key});
1661 deferred.reject(key);
1662 $rootScope.$emit('$translateLoadingEnd', {language: key});
1663 };
1664 onLoaderError.displayName = 'onLoaderError';
1665
1666 $injector.get($loaderFactory)(loaderOptions)
1667 .then(onLoaderSuccess, onLoaderError);
1668
1669 return deferred.promise;
1670 };
1671
1672 if ($storageFactory) {
1673 Storage = $injector.get($storageFactory);
1674
1675 if (!Storage.get || !Storage.put) {
1676 throw new Error('Couldn\'t use storage \'' + $storageFactory + '\', missing get() or put() method!');
1677 }
1678 }
1679
1680 // if we have additional interpolations that were added via
1681 // $translateProvider.addInterpolation(), we have to map'em
1682 if ($interpolatorFactories.length) {
1683 var eachInterpolationFactory = function (interpolatorFactory) {
1684 var interpolator = $injector.get(interpolatorFactory);
1685 // setting initial locale for each interpolation service
1686 interpolator.setLocale($preferredLanguage || $uses);
1687 // make'em recognizable through id
1688 interpolatorHashMap[interpolator.getInterpolationIdentifier()] = interpolator;
1689 };
1690 eachInterpolationFactory.displayName = 'interpolationFactoryAdder';
1691
1692 angular.forEach($interpolatorFactories, eachInterpolationFactory);
1693 }
1694
1695 /**
1696 * @name getTranslationTable
1697 * @private
1698 *
1699 * @description
1700 * Returns a promise that resolves to the translation table
1701 * or is rejected if an error occurred.
1702 *
1703 * @param langKey
1704 * @returns {Q.promise}
1705 */
1706 var getTranslationTable = function (langKey) {
1707 var deferred = $q.defer();
1708 if (Object.prototype.hasOwnProperty.call($translationTable, langKey)) {
1709 deferred.resolve($translationTable[langKey]);
1710 } else if (langPromises[langKey]) {
1711 var onResolve = function (data) {
1712 translations(data.key, data.table);
1713 deferred.resolve(data.table);
1714 };
1715 onResolve.displayName = 'translationTableResolver';
1716 langPromises[langKey].then(onResolve, deferred.reject);
1717 } else {
1718 deferred.reject();
1719 }
1720 return deferred.promise;
1721 };
1722
1723 /**
1724 * @name getFallbackTranslation
1725 * @private
1726 *
1727 * @description
1728 * Returns a promise that will resolve to the translation
1729 * or be rejected if no translation was found for the language.
1730 * This function is currently only used for fallback language translation.
1731 *
1732 * @param langKey The language to translate to.
1733 * @param translationId
1734 * @param interpolateParams
1735 * @param Interpolator
1736 * @returns {Q.promise}
1737 */
1738 var getFallbackTranslation = function (langKey, translationId, interpolateParams, Interpolator) {
1739 var deferred = $q.defer();
1740
1741 var onResolve = function (translationTable) {
1742 if (Object.prototype.hasOwnProperty.call(translationTable, translationId)) {
1743 Interpolator.setLocale(langKey);
1744 var translation = translationTable[translationId];
1745 if (translation.substr(0, 2) === '@:') {
1746 getFallbackTranslation(langKey, translation.substr(2), interpolateParams, Interpolator)
1747 .then(deferred.resolve, deferred.reject);
1748 } else {
1749 var interpolatedValue = Interpolator.interpolate(translationTable[translationId], interpolateParams, 'service');
1750 interpolatedValue = applyPostProcessing(translationId, translationTable[translationId], interpolatedValue, interpolateParams, langKey);
1751
1752 deferred.resolve(interpolatedValue);
1753
1754 }
1755 Interpolator.setLocale($uses);
1756 } else {
1757 deferred.reject();
1758 }
1759 };
1760 onResolve.displayName = 'fallbackTranslationResolver';
1761
1762 getTranslationTable(langKey).then(onResolve, deferred.reject);
1763
1764 return deferred.promise;
1765 };
1766
1767 /**
1768 * @name getFallbackTranslationInstant
1769 * @private
1770 *
1771 * @description
1772 * Returns a translation
1773 * This function is currently only used for fallback language translation.
1774 *
1775 * @param langKey The language to translate to.
1776 * @param translationId
1777 * @param interpolateParams
1778 * @param Interpolator
1779 * @returns {string} translation
1780 */
1781 var getFallbackTranslationInstant = function (langKey, translationId, interpolateParams, Interpolator) {
1782 var result, translationTable = $translationTable[langKey];
1783
1784 if (translationTable && Object.prototype.hasOwnProperty.call(translationTable, translationId)) {
1785 Interpolator.setLocale(langKey);
1786 result = Interpolator.interpolate(translationTable[translationId], interpolateParams, 'filter');
1787 result = applyPostProcessing(translationId, translationTable[translationId], result, interpolateParams, langKey);
1788 if (result.substr(0, 2) === '@:') {
1789 return getFallbackTranslationInstant(langKey, result.substr(2), interpolateParams, Interpolator);
1790 }
1791 Interpolator.setLocale($uses);
1792 }
1793
1794 return result;
1795 };
1796
1797
1798 /**
1799 * @name translateByHandler
1800 * @private
1801 *
1802 * Translate by missing translation handler.
1803 *
1804 * @param translationId
1805 * @param interpolateParams
1806 * @param defaultTranslationText
1807 * @returns translation created by $missingTranslationHandler or translationId is $missingTranslationHandler is
1808 * absent
1809 */
1810 var translateByHandler = function (translationId, interpolateParams, defaultTranslationText) {
1811 // If we have a handler factory - we might also call it here to determine if it provides
1812 // a default text for a translationid that can't be found anywhere in our tables
1813 if ($missingTranslationHandlerFactory) {
1814 var resultString = $injector.get($missingTranslationHandlerFactory)(translationId, $uses, interpolateParams, defaultTranslationText);
1815 if (resultString !== undefined) {
1816 return resultString;
1817 } else {
1818 return translationId;
1819 }
1820 } else {
1821 return translationId;
1822 }
1823 };
1824
1825 /**
1826 * @name resolveForFallbackLanguage
1827 * @private
1828 *
1829 * Recursive helper function for fallbackTranslation that will sequentially look
1830 * for a translation in the fallbackLanguages starting with fallbackLanguageIndex.
1831 *
1832 * @param fallbackLanguageIndex
1833 * @param translationId
1834 * @param interpolateParams
1835 * @param Interpolator
1836 * @returns {Q.promise} Promise that will resolve to the translation.
1837 */
1838 var resolveForFallbackLanguage = function (fallbackLanguageIndex, translationId, interpolateParams, Interpolator, defaultTranslationText) {
1839 var deferred = $q.defer();
1840
1841 if (fallbackLanguageIndex < $fallbackLanguage.length) {
1842 var langKey = $fallbackLanguage[fallbackLanguageIndex];
1843 getFallbackTranslation(langKey, translationId, interpolateParams, Interpolator).then(
1844 function (data) {
1845 deferred.resolve(data);
1846 },
1847 function () {
1848 // Look in the next fallback language for a translation.
1849 // It delays the resolving by passing another promise to resolve.
1850 return resolveForFallbackLanguage(fallbackLanguageIndex + 1, translationId, interpolateParams, Interpolator, defaultTranslationText).then(deferred.resolve, deferred.reject);
1851 }
1852 );
1853 } else {
1854 // No translation found in any fallback language
1855 // if a default translation text is set in the directive, then return this as a result
1856 if (defaultTranslationText) {
1857 deferred.resolve(defaultTranslationText);
1858 } else {
1859 // if no default translation is set and an error handler is defined, send it to the handler
1860 // and then return the result
1861 if ($missingTranslationHandlerFactory) {
1862 deferred.resolve(translateByHandler(translationId, interpolateParams));
1863 } else {
1864 deferred.reject(translateByHandler(translationId, interpolateParams));
1865 }
1866
1867 }
1868 }
1869 return deferred.promise;
1870 };
1871
1872 /**
1873 * @name resolveForFallbackLanguageInstant
1874 * @private
1875 *
1876 * Recursive helper function for fallbackTranslation that will sequentially look
1877 * for a translation in the fallbackLanguages starting with fallbackLanguageIndex.
1878 *
1879 * @param fallbackLanguageIndex
1880 * @param translationId
1881 * @param interpolateParams
1882 * @param Interpolator
1883 * @returns {string} translation
1884 */
1885 var resolveForFallbackLanguageInstant = function (fallbackLanguageIndex, translationId, interpolateParams, Interpolator) {
1886 var result;
1887
1888 if (fallbackLanguageIndex < $fallbackLanguage.length) {
1889 var langKey = $fallbackLanguage[fallbackLanguageIndex];
1890 result = getFallbackTranslationInstant(langKey, translationId, interpolateParams, Interpolator);
1891 if (!result) {
1892 result = resolveForFallbackLanguageInstant(fallbackLanguageIndex + 1, translationId, interpolateParams, Interpolator);
1893 }
1894 }
1895 return result;
1896 };
1897
1898 /**
1899 * Translates with the usage of the fallback languages.
1900 *
1901 * @param translationId
1902 * @param interpolateParams
1903 * @param Interpolator
1904 * @returns {Q.promise} Promise, that resolves to the translation.
1905 */
1906 var fallbackTranslation = function (translationId, interpolateParams, Interpolator, defaultTranslationText) {
1907 // Start with the fallbackLanguage with index 0
1908 return resolveForFallbackLanguage((startFallbackIteration>0 ? startFallbackIteration : fallbackIndex), translationId, interpolateParams, Interpolator, defaultTranslationText);
1909 };
1910
1911 /**
1912 * Translates with the usage of the fallback languages.
1913 *
1914 * @param translationId
1915 * @param interpolateParams
1916 * @param Interpolator
1917 * @returns {String} translation
1918 */
1919 var fallbackTranslationInstant = function (translationId, interpolateParams, Interpolator) {
1920 // Start with the fallbackLanguage with index 0
1921 return resolveForFallbackLanguageInstant((startFallbackIteration>0 ? startFallbackIteration : fallbackIndex), translationId, interpolateParams, Interpolator);
1922 };
1923
1924 var determineTranslation = function (translationId, interpolateParams, interpolationId, defaultTranslationText, uses) {
1925
1926 var deferred = $q.defer();
1927
1928 var table = uses ? $translationTable[uses] : $translationTable,
1929 Interpolator = (interpolationId) ? interpolatorHashMap[interpolationId] : defaultInterpolator;
1930
1931 // if the translation id exists, we can just interpolate it
1932 if (table && Object.prototype.hasOwnProperty.call(table, translationId)) {
1933 var translation = table[translationId];
1934
1935 // If using link, rerun $translate with linked translationId and return it
1936 if (translation.substr(0, 2) === '@:') {
1937
1938 $translate(translation.substr(2), interpolateParams, interpolationId, defaultTranslationText, uses)
1939 .then(deferred.resolve, deferred.reject);
1940 } else {
1941 //
1942 var resolvedTranslation = Interpolator.interpolate(translation, interpolateParams, 'service');
1943 resolvedTranslation = applyPostProcessing(translationId, translation, resolvedTranslation, interpolateParams, uses);
1944 deferred.resolve(resolvedTranslation);
1945 }
1946 } else {
1947 var missingTranslationHandlerTranslation;
1948 // for logging purposes only (as in $translateMissingTranslationHandlerLog), value is not returned to promise
1949 if ($missingTranslationHandlerFactory && !pendingLoader) {
1950 missingTranslationHandlerTranslation = translateByHandler(translationId, interpolateParams, defaultTranslationText);
1951 }
1952
1953 // since we couldn't translate the inital requested translation id,
1954 // we try it now with one or more fallback languages, if fallback language(s) is
1955 // configured.
1956 if (uses && $fallbackLanguage && $fallbackLanguage.length) {
1957 fallbackTranslation(translationId, interpolateParams, Interpolator, defaultTranslationText)
1958 .then(function (translation) {
1959 deferred.resolve(translation);
1960 }, function (_translationId) {
1961 deferred.reject(applyNotFoundIndicators(_translationId));
1962 });
1963 } else if ($missingTranslationHandlerFactory && !pendingLoader && missingTranslationHandlerTranslation) {
1964 // looks like the requested translation id doesn't exists.
1965 // Now, if there is a registered handler for missing translations and no
1966 // asyncLoader is pending, we execute the handler
1967 if (defaultTranslationText) {
1968 deferred.resolve(defaultTranslationText);
1969 } else {
1970 deferred.resolve(missingTranslationHandlerTranslation);
1971 }
1972 } else {
1973 if (defaultTranslationText) {
1974 deferred.resolve(defaultTranslationText);
1975 } else {
1976 deferred.reject(applyNotFoundIndicators(translationId));
1977 }
1978 }
1979 }
1980 return deferred.promise;
1981 };
1982
1983 var determineTranslationInstant = function (translationId, interpolateParams, interpolationId, uses) {
1984
1985 var result, table = uses ? $translationTable[uses] : $translationTable,
1986 Interpolator = defaultInterpolator;
1987
1988 // if the interpolation id exists use custom interpolator
1989 if (interpolatorHashMap && Object.prototype.hasOwnProperty.call(interpolatorHashMap, interpolationId)) {
1990 Interpolator = interpolatorHashMap[interpolationId];
1991 }
1992
1993 // if the translation id exists, we can just interpolate it
1994 if (table && Object.prototype.hasOwnProperty.call(table, translationId)) {
1995 var translation = table[translationId];
1996
1997 // If using link, rerun $translate with linked translationId and return it
1998 if (translation.substr(0, 2) === '@:') {
1999 result = determineTranslationInstant(translation.substr(2), interpolateParams, interpolationId, uses);
2000 } else {
2001 result = Interpolator.interpolate(translation, interpolateParams, 'filter');
2002 result = applyPostProcessing(translationId, translation, result, interpolateParams, uses);
2003 }
2004 } else {
2005 var missingTranslationHandlerTranslation;
2006 // for logging purposes only (as in $translateMissingTranslationHandlerLog), value is not returned to promise
2007 if ($missingTranslationHandlerFactory && !pendingLoader) {
2008 missingTranslationHandlerTranslation = translateByHandler(translationId, interpolateParams);
2009 }
2010
2011 // since we couldn't translate the inital requested translation id,
2012 // we try it now with one or more fallback languages, if fallback language(s) is
2013 // configured.
2014 if (uses && $fallbackLanguage && $fallbackLanguage.length) {
2015 fallbackIndex = 0;
2016 result = fallbackTranslationInstant(translationId, interpolateParams, Interpolator);
2017 } else if ($missingTranslationHandlerFactory && !pendingLoader && missingTranslationHandlerTranslation) {
2018 // looks like the requested translation id doesn't exists.
2019 // Now, if there is a registered handler for missing translations and no
2020 // asyncLoader is pending, we execute the handler
2021 result = missingTranslationHandlerTranslation;
2022 } else {
2023 result = applyNotFoundIndicators(translationId);
2024 }
2025 }
2026
2027 return result;
2028 };
2029
2030 var clearNextLangAndPromise = function(key) {
2031 if ($nextLang === key) {
2032 $nextLang = undefined;
2033 }
2034 langPromises[key] = undefined;
2035 };
2036
2037 var applyPostProcessing = function (translationId, translation, resolvedTranslation, interpolateParams, uses) {
2038 var fn = postProcessFn;
2039
2040 if (fn) {
2041
2042 if (typeof(fn) === 'string') {
2043 // getting on-demand instance
2044 fn = $injector.get(fn);
2045 }
2046 if (fn) {
2047 return fn(translationId, translation, resolvedTranslation, interpolateParams, uses);
2048 }
2049 }
2050
2051 return resolvedTranslation;
2052 };
2053
2054 var loadTranslationsIfMissing = function (key) {
2055 if (!$translationTable[key] && $loaderFactory && !langPromises[key]) {
2056 langPromises[key] = loadAsync(key).then(function (translation) {
2057 translations(translation.key, translation.table);
2058 return translation;
2059 });
2060 }
2061 };
2062
2063 /**
2064 * @ngdoc function
2065 * @name pascalprecht.translate.$translate#preferredLanguage
2066 * @methodOf pascalprecht.translate.$translate
2067 *
2068 * @description
2069 * Returns the language key for the preferred language.
2070 *
2071 * @param {string} langKey language String or Array to be used as preferredLanguage (changing at runtime)
2072 *
2073 * @return {string} preferred language key
2074 */
2075 $translate.preferredLanguage = function (langKey) {
2076 if(langKey) {
2077 setupPreferredLanguage(langKey);
2078 }
2079 return $preferredLanguage;
2080 };
2081
2082 /**
2083 * @ngdoc function
2084 * @name pascalprecht.translate.$translate#cloakClassName
2085 * @methodOf pascalprecht.translate.$translate
2086 *
2087 * @description
2088 * Returns the configured class name for `translate-cloak` directive.
2089 *
2090 * @return {string} cloakClassName
2091 */
2092 $translate.cloakClassName = function () {
2093 return $cloakClassName;
2094 };
2095
2096 /**
2097 * @ngdoc function
2098 * @name pascalprecht.translate.$translate#nestedObjectDelimeter
2099 * @methodOf pascalprecht.translate.$translate
2100 *
2101 * @description
2102 * Returns the configured delimiter for nested namespaces.
2103 *
2104 * @return {string} nestedObjectDelimeter
2105 */
2106 $translate.nestedObjectDelimeter = function () {
2107 return $nestedObjectDelimeter;
2108 };
2109
2110 /**
2111 * @ngdoc function
2112 * @name pascalprecht.translate.$translate#fallbackLanguage
2113 * @methodOf pascalprecht.translate.$translate
2114 *
2115 * @description
2116 * Returns the language key for the fallback languages or sets a new fallback stack.
2117 *
2118 * @param {string=} langKey language String or Array of fallback languages to be used (to change stack at runtime)
2119 *
2120 * @return {string||array} fallback language key
2121 */
2122 $translate.fallbackLanguage = function (langKey) {
2123 if (langKey !== undefined && langKey !== null) {
2124 fallbackStack(langKey);
2125
2126 // as we might have an async loader initiated and a new translation language might have been defined
2127 // we need to add the promise to the stack also. So - iterate.
2128 if ($loaderFactory) {
2129 if ($fallbackLanguage && $fallbackLanguage.length) {
2130 for (var i = 0, len = $fallbackLanguage.length; i < len; i++) {
2131 if (!langPromises[$fallbackLanguage[i]]) {
2132 langPromises[$fallbackLanguage[i]] = loadAsync($fallbackLanguage[i]);
2133 }
2134 }
2135 }
2136 }
2137 $translate.use($translate.use());
2138 }
2139 if ($fallbackWasString) {
2140 return $fallbackLanguage[0];
2141 } else {
2142 return $fallbackLanguage;
2143 }
2144
2145 };
2146
2147 /**
2148 * @ngdoc function
2149 * @name pascalprecht.translate.$translate#useFallbackLanguage
2150 * @methodOf pascalprecht.translate.$translate
2151 *
2152 * @description
2153 * Sets the first key of the fallback language stack to be used for translation.
2154 * Therefore all languages in the fallback array BEFORE this key will be skipped!
2155 *
2156 * @param {string=} langKey Contains the langKey the iteration shall start with. Set to false if you want to
2157 * get back to the whole stack
2158 */
2159 $translate.useFallbackLanguage = function (langKey) {
2160 if (langKey !== undefined && langKey !== null) {
2161 if (!langKey) {
2162 startFallbackIteration = 0;
2163 } else {
2164 var langKeyPosition = indexOf($fallbackLanguage, langKey);
2165 if (langKeyPosition > -1) {
2166 startFallbackIteration = langKeyPosition;
2167 }
2168 }
2169
2170 }
2171
2172 };
2173
2174 /**
2175 * @ngdoc function
2176 * @name pascalprecht.translate.$translate#proposedLanguage
2177 * @methodOf pascalprecht.translate.$translate
2178 *
2179 * @description
2180 * Returns the language key of language that is currently loaded asynchronously.
2181 *
2182 * @return {string} language key
2183 */
2184 $translate.proposedLanguage = function () {
2185 return $nextLang;
2186 };
2187
2188 /**
2189 * @ngdoc function
2190 * @name pascalprecht.translate.$translate#storage
2191 * @methodOf pascalprecht.translate.$translate
2192 *
2193 * @description
2194 * Returns registered storage.
2195 *
2196 * @return {object} Storage
2197 */
2198 $translate.storage = function () {
2199 return Storage;
2200 };
2201
2202 /**
2203 * @ngdoc function
2204 * @name pascalprecht.translate.$translate#negotiateLocale
2205 * @methodOf pascalprecht.translate.$translate
2206 *
2207 * @description
2208 * Returns a language key based on available languages and language aliases. If a
2209 * language key cannot be resolved, returns undefined.
2210 *
2211 * If no or a falsy key is given, returns undefined.
2212 *
2213 * @param {string} [key] Language key
2214 * @return {string|undefined} Language key or undefined if no language key is found.
2215 */
2216 $translate.negotiateLocale = negotiateLocale;
2217
2218 /**
2219 * @ngdoc function
2220 * @name pascalprecht.translate.$translate#use
2221 * @methodOf pascalprecht.translate.$translate
2222 *
2223 * @description
2224 * Tells angular-translate which language to use by given language key. This method is
2225 * used to change language at runtime. It also takes care of storing the language
2226 * key in a configured store to let your app remember the choosed language.
2227 *
2228 * When trying to 'use' a language which isn't available it tries to load it
2229 * asynchronously with registered loaders.
2230 *
2231 * Returns promise object with loaded language file data or string of the currently used language.
2232 *
2233 * If no or a falsy key is given it returns the currently used language key.
2234 * The returned string will be ```undefined``` if setting up $translate hasn't finished.
2235 * @example
2236 * $translate.use("en_US").then(function(data){
2237 * $scope.text = $translate("HELLO");
2238 * });
2239 *
2240 * @param {string} [key] Language key
2241 * @return {object|string} Promise with loaded language data or the language key if a falsy param was given.
2242 */
2243 $translate.use = function (key) {
2244 if (!key) {
2245 return $uses;
2246 }
2247
2248 var deferred = $q.defer();
2249
2250 $rootScope.$emit('$translateChangeStart', {language: key});
2251
2252 // Try to get the aliased language key
2253 var aliasedKey = negotiateLocale(key);
2254 // Ensure only registered language keys will be loaded
2255 if ($availableLanguageKeys.length > 0 && !aliasedKey) {
2256 return $q.reject(key);
2257 }
2258
2259 if (aliasedKey) {
2260 key = aliasedKey;
2261 }
2262
2263 // if there isn't a translation table for the language we've requested,
2264 // we load it asynchronously
2265 $nextLang = key;
2266 if (($forceAsyncReloadEnabled || !$translationTable[key]) && $loaderFactory && !langPromises[key]) {
2267 langPromises[key] = loadAsync(key).then(function (translation) {
2268 translations(translation.key, translation.table);
2269 deferred.resolve(translation.key);
2270 if ($nextLang === key) {
2271 useLanguage(translation.key);
2272 }
2273 return translation;
2274 }, function (key) {
2275 $rootScope.$emit('$translateChangeError', {language: key});
2276 deferred.reject(key);
2277 $rootScope.$emit('$translateChangeEnd', {language: key});
2278 return $q.reject(key);
2279 });
2280 langPromises[key]['finally'](function () {
2281 clearNextLangAndPromise(key);
2282 });
2283 } else if (langPromises[key]) {
2284 // we are already loading this asynchronously
2285 // resolve our new deferred when the old langPromise is resolved
2286 langPromises[key].then(function (translation) {
2287 if ($nextLang === translation.key) {
2288 useLanguage(translation.key);
2289 }
2290 deferred.resolve(translation.key);
2291 return translation;
2292 }, function (key) {
2293 // find first available fallback language if that request has failed
2294 if (!$uses && $fallbackLanguage && $fallbackLanguage.length > 0 && $fallbackLanguage[0] !== key) {
2295 return $translate.use($fallbackLanguage[0]).then(deferred.resolve, deferred.reject);
2296 } else {
2297 return deferred.reject(key);
2298 }
2299 });
2300 } else {
2301 deferred.resolve(key);
2302 useLanguage(key);
2303 }
2304
2305 return deferred.promise;
2306 };
2307
2308 /**
2309 * @ngdoc function
2310 * @name pascalprecht.translate.$translate#resolveClientLocale
2311 * @methodOf pascalprecht.translate.$translate
2312 *
2313 * @description
2314 * This returns the current browser/client's language key. The result is processed with the configured uniform tag resolver.
2315 *
2316 * @returns {string} the current client/browser language key
2317 */
2318 $translate.resolveClientLocale = function () {
2319 return getLocale();
2320 };
2321
2322 /**
2323 * @ngdoc function
2324 * @name pascalprecht.translate.$translate#storageKey
2325 * @methodOf pascalprecht.translate.$translate
2326 *
2327 * @description
2328 * Returns the key for the storage.
2329 *
2330 * @return {string} storage key
2331 */
2332 $translate.storageKey = function () {
2333 return storageKey();
2334 };
2335
2336 /**
2337 * @ngdoc function
2338 * @name pascalprecht.translate.$translate#isPostCompilingEnabled
2339 * @methodOf pascalprecht.translate.$translate
2340 *
2341 * @description
2342 * Returns whether post compiling is enabled or not
2343 *
2344 * @return {bool} storage key
2345 */
2346 $translate.isPostCompilingEnabled = function () {
2347 return $postCompilingEnabled;
2348 };
2349
2350 /**
2351 * @ngdoc function
2352 * @name pascalprecht.translate.$translate#isForceAsyncReloadEnabled
2353 * @methodOf pascalprecht.translate.$translate
2354 *
2355 * @description
2356 * Returns whether force async reload is enabled or not
2357 *
2358 * @return {boolean} forceAsyncReload value
2359 */
2360 $translate.isForceAsyncReloadEnabled = function () {
2361 return $forceAsyncReloadEnabled;
2362 };
2363
2364 /**
2365 * @ngdoc function
2366 * @name pascalprecht.translate.$translate#isKeepContent
2367 * @methodOf pascalprecht.translate.$translate
2368 *
2369 * @description
2370 * Returns whether keepContent or not
2371 *
2372 * @return {boolean} keepContent value
2373 */
2374 $translate.isKeepContent = function () {
2375 return $keepContent;
2376 };
2377
2378 /**
2379 * @ngdoc function
2380 * @name pascalprecht.translate.$translate#refresh
2381 * @methodOf pascalprecht.translate.$translate
2382 *
2383 * @description
2384 * Refreshes a translation table pointed by the given langKey. If langKey is not specified,
2385 * the module will drop all existent translation tables and load new version of those which
2386 * are currently in use.
2387 *
2388 * Refresh means that the module will drop target translation table and try to load it again.
2389 *
2390 * In case there are no loaders registered the refresh() method will throw an Error.
2391 *
2392 * If the module is able to refresh translation tables refresh() method will broadcast
2393 * $translateRefreshStart and $translateRefreshEnd events.
2394 *
2395 * @example
2396 * // this will drop all currently existent translation tables and reload those which are
2397 * // currently in use
2398 * $translate.refresh();
2399 * // this will refresh a translation table for the en_US language
2400 * $translate.refresh('en_US');
2401 *
2402 * @param {string} langKey A language key of the table, which has to be refreshed
2403 *
2404 * @return {promise} Promise, which will be resolved in case a translation tables refreshing
2405 * process is finished successfully, and reject if not.
2406 */
2407 $translate.refresh = function (langKey) {
2408 if (!$loaderFactory) {
2409 throw new Error('Couldn\'t refresh translation table, no loader registered!');
2410 }
2411
2412 var deferred = $q.defer();
2413
2414 function resolve() {
2415 deferred.resolve();
2416 $rootScope.$emit('$translateRefreshEnd', {language: langKey});
2417 }
2418
2419 function reject() {
2420 deferred.reject();
2421 $rootScope.$emit('$translateRefreshEnd', {language: langKey});
2422 }
2423
2424 $rootScope.$emit('$translateRefreshStart', {language: langKey});
2425
2426 if (!langKey) {
2427 // if there's no language key specified we refresh ALL THE THINGS!
2428 var tables = [], loadingKeys = {};
2429
2430 // reload registered fallback languages
2431 if ($fallbackLanguage && $fallbackLanguage.length) {
2432 for (var i = 0, len = $fallbackLanguage.length; i < len; i++) {
2433 tables.push(loadAsync($fallbackLanguage[i]));
2434 loadingKeys[$fallbackLanguage[i]] = true;
2435 }
2436 }
2437
2438 // reload currently used language
2439 if ($uses && !loadingKeys[$uses]) {
2440 tables.push(loadAsync($uses));
2441 }
2442
2443 var allTranslationsLoaded = function (tableData) {
2444 $translationTable = {};
2445 angular.forEach(tableData, function (data) {
2446 translations(data.key, data.table);
2447 });
2448 if ($uses) {
2449 useLanguage($uses);
2450 }
2451 resolve();
2452 };
2453 allTranslationsLoaded.displayName = 'refreshPostProcessor';
2454
2455 $q.all(tables).then(allTranslationsLoaded, reject);
2456
2457 } else if ($translationTable[langKey]) {
2458
2459 var oneTranslationsLoaded = function (data) {
2460 translations(data.key, data.table);
2461 if (langKey === $uses) {
2462 useLanguage($uses);
2463 }
2464 resolve();
2465 return data;
2466 };
2467 oneTranslationsLoaded.displayName = 'refreshPostProcessor';
2468
2469 loadAsync(langKey).then(oneTranslationsLoaded, reject);
2470
2471 } else {
2472 reject();
2473 }
2474 return deferred.promise;
2475 };
2476
2477 /**
2478 * @ngdoc function
2479 * @name pascalprecht.translate.$translate#instant
2480 * @methodOf pascalprecht.translate.$translate
2481 *
2482 * @description
2483 * Returns a translation instantly from the internal state of loaded translation. All rules
2484 * regarding the current language, the preferred language of even fallback languages will be
2485 * used except any promise handling. If a language was not found, an asynchronous loading
2486 * will be invoked in the background.
2487 *
2488 * @param {string|array} translationId A token which represents a translation id
2489 * This can be optionally an array of translation ids which
2490 * results that the function's promise returns an object where
2491 * each key is the translation id and the value the translation.
2492 * @param {object} interpolateParams Params
2493 * @param {string} interpolationId The id of the interpolation to use
2494 * @param {string} forceLanguage A language to be used instead of the current language
2495 *
2496 * @return {string|object} translation
2497 */
2498 $translate.instant = function (translationId, interpolateParams, interpolationId, forceLanguage) {
2499
2500 // we don't want to re-negotiate $uses
2501 var uses = (forceLanguage && forceLanguage !== $uses) ? // we don't want to re-negotiate $uses
2502 (negotiateLocale(forceLanguage) || forceLanguage) : $uses;
2503
2504 // Detect undefined and null values to shorten the execution and prevent exceptions
2505 if (translationId === null || angular.isUndefined(translationId)) {
2506 return translationId;
2507 }
2508
2509 // Check forceLanguage is present
2510 if (forceLanguage) {
2511 loadTranslationsIfMissing(forceLanguage);
2512 }
2513
2514 // Duck detection: If the first argument is an array, a bunch of translations was requested.
2515 // The result is an object.
2516 if (angular.isArray(translationId)) {
2517 var results = {};
2518 for (var i = 0, c = translationId.length; i < c; i++) {
2519 results[translationId[i]] = $translate.instant(translationId[i], interpolateParams, interpolationId, forceLanguage);
2520 }
2521 return results;
2522 }
2523
2524 // We discarded unacceptable values. So we just need to verify if translationId is empty String
2525 if (angular.isString(translationId) && translationId.length < 1) {
2526 return translationId;
2527 }
2528
2529 // trim off any whitespace
2530 if (translationId) {
2531 translationId = trim.apply(translationId);
2532 }
2533
2534 var result, possibleLangKeys = [];
2535 if ($preferredLanguage) {
2536 possibleLangKeys.push($preferredLanguage);
2537 }
2538 if (uses) {
2539 possibleLangKeys.push(uses);
2540 }
2541 if ($fallbackLanguage && $fallbackLanguage.length) {
2542 possibleLangKeys = possibleLangKeys.concat($fallbackLanguage);
2543 }
2544 for (var j = 0, d = possibleLangKeys.length; j < d; j++) {
2545 var possibleLangKey = possibleLangKeys[j];
2546 if ($translationTable[possibleLangKey]) {
2547 if (typeof $translationTable[possibleLangKey][translationId] !== 'undefined') {
2548 result = determineTranslationInstant(translationId, interpolateParams, interpolationId, uses);
2549 }
2550 }
2551 if (typeof result !== 'undefined') {
2552 break;
2553 }
2554 }
2555
2556 if (!result && result !== '') {
2557 if ($notFoundIndicatorLeft || $notFoundIndicatorRight) {
2558 result = applyNotFoundIndicators(translationId);
2559 } else {
2560 // Return translation of default interpolator if not found anything.
2561 result = defaultInterpolator.interpolate(translationId, interpolateParams, 'filter');
2562 if ($missingTranslationHandlerFactory && !pendingLoader) {
2563 result = translateByHandler(translationId, interpolateParams);
2564 }
2565 }
2566 }
2567
2568 return result;
2569 };
2570
2571 /**
2572 * @ngdoc function
2573 * @name pascalprecht.translate.$translate#versionInfo
2574 * @methodOf pascalprecht.translate.$translate
2575 *
2576 * @description
2577 * Returns the current version information for the angular-translate library
2578 *
2579 * @return {string} angular-translate version
2580 */
2581 $translate.versionInfo = function () {
2582 return version;
2583 };
2584
2585 /**
2586 * @ngdoc function
2587 * @name pascalprecht.translate.$translate#loaderCache
2588 * @methodOf pascalprecht.translate.$translate
2589 *
2590 * @description
2591 * Returns the defined loaderCache.
2592 *
2593 * @return {boolean|string|object} current value of loaderCache
2594 */
2595 $translate.loaderCache = function () {
2596 return loaderCache;
2597 };
2598
2599 // internal purpose only
2600 $translate.directivePriority = function () {
2601 return directivePriority;
2602 };
2603
2604 // internal purpose only
2605 $translate.statefulFilter = function () {
2606 return statefulFilter;
2607 };
2608
2609 /**
2610 * @ngdoc function
2611 * @name pascalprecht.translate.$translate#isReady
2612 * @methodOf pascalprecht.translate.$translate
2613 *
2614 * @description
2615 * Returns whether the service is "ready" to translate (i.e. loading 1st language).
2616 *
2617 * See also {@link pascalprecht.translate.$translate#methods_onReady onReady()}.
2618 *
2619 * @return {boolean} current value of ready
2620 */
2621 $translate.isReady = function () {
2622 return $isReady;
2623 };
2624
2625 var $onReadyDeferred = $q.defer();
2626 $onReadyDeferred.promise.then(function () {
2627 $isReady = true;
2628 });
2629
2630 /**
2631 * @ngdoc function
2632 * @name pascalprecht.translate.$translate#onReady
2633 * @methodOf pascalprecht.translate.$translate
2634 *
2635 * @description
2636 * Returns whether the service is "ready" to translate (i.e. loading 1st language).
2637 *
2638 * See also {@link pascalprecht.translate.$translate#methods_isReady isReady()}.
2639 *
2640 * @param {Function=} fn Function to invoke when service is ready
2641 * @return {object} Promise resolved when service is ready
2642 */
2643 $translate.onReady = function (fn) {
2644 var deferred = $q.defer();
2645 if (angular.isFunction(fn)) {
2646 deferred.promise.then(fn);
2647 }
2648 if ($isReady) {
2649 deferred.resolve();
2650 } else {
2651 $onReadyDeferred.promise.then(deferred.resolve);
2652 }
2653 return deferred.promise;
2654 };
2655
2656 /**
2657 * @ngdoc function
2658 * @name pascalprecht.translate.$translate#getAvailableLanguageKeys
2659 * @methodOf pascalprecht.translate.$translate
2660 *
2661 * @description
2662 * This function simply returns the registered language keys being defined before in the config phase
2663 * With this, an application can use the array to provide a language selection dropdown or similar
2664 * without any additional effort
2665 *
2666 * @returns {object} returns the list of possibly registered language keys and mapping or null if not defined
2667 */
2668 $translate.getAvailableLanguageKeys = function () {
2669 if ($availableLanguageKeys.length > 0) {
2670 return $availableLanguageKeys;
2671 }
2672 return null;
2673 };
2674
2675 // Whenever $translateReady is being fired, this will ensure the state of $isReady
2676 var globalOnReadyListener = $rootScope.$on('$translateReady', function () {
2677 $onReadyDeferred.resolve();
2678 globalOnReadyListener(); // one time only
2679 globalOnReadyListener = null;
2680 });
2681 var globalOnChangeListener = $rootScope.$on('$translateChangeEnd', function () {
2682 $onReadyDeferred.resolve();
2683 globalOnChangeListener(); // one time only
2684 globalOnChangeListener = null;
2685 });
2686
2687 if ($loaderFactory) {
2688
2689 // If at least one async loader is defined and there are no
2690 // (default) translations available we should try to load them.
2691 if (angular.equals($translationTable, {})) {
2692 if ($translate.use()) {
2693 $translate.use($translate.use());
2694 }
2695 }
2696
2697 // Also, if there are any fallback language registered, we start
2698 // loading them asynchronously as soon as we can.
2699 if ($fallbackLanguage && $fallbackLanguage.length) {
2700 var processAsyncResult = function (translation) {
2701 translations(translation.key, translation.table);
2702 $rootScope.$emit('$translateChangeEnd', { language: translation.key });
2703 return translation;
2704 };
2705 for (var i = 0, len = $fallbackLanguage.length; i < len; i++) {
2706 var fallbackLanguageId = $fallbackLanguage[i];
2707 if ($forceAsyncReloadEnabled || !$translationTable[fallbackLanguageId]) {
2708 langPromises[fallbackLanguageId] = loadAsync(fallbackLanguageId).then(processAsyncResult);
2709 }
2710 }
2711 }
2712 } else {
2713 $rootScope.$emit('$translateReady', { language: $translate.use() });
2714 }
2715
2716 return $translate;
2717 }
2718 ];
2719}
2720
2721$translate.displayName = 'displayName';
2722
2723/**
2724 * @ngdoc object
2725 * @name pascalprecht.translate.$translateDefaultInterpolation
2726 * @requires $interpolate
2727 *
2728 * @description
2729 * Uses angular's `$interpolate` services to interpolate strings against some values.
2730 *
2731 * Be aware to configure a proper sanitization strategy.
2732 *
2733 * See also:
2734 * * {@link pascalprecht.translate.$translateSanitization}
2735 *
2736 * @return {object} $translateDefaultInterpolation Interpolator service
2737 */
2738angular.module('pascalprecht.translate').factory('$translateDefaultInterpolation', $translateDefaultInterpolation);
2739
2740function $translateDefaultInterpolation ($interpolate, $translateSanitization) {
2741
2742 'use strict';
2743
2744 var $translateInterpolator = {},
2745 $locale,
2746 $identifier = 'default';
2747
2748 /**
2749 * @ngdoc function
2750 * @name pascalprecht.translate.$translateDefaultInterpolation#setLocale
2751 * @methodOf pascalprecht.translate.$translateDefaultInterpolation
2752 *
2753 * @description
2754 * Sets current locale (this is currently not use in this interpolation).
2755 *
2756 * @param {string} locale Language key or locale.
2757 */
2758 $translateInterpolator.setLocale = function (locale) {
2759 $locale = locale;
2760 };
2761
2762 /**
2763 * @ngdoc function
2764 * @name pascalprecht.translate.$translateDefaultInterpolation#getInterpolationIdentifier
2765 * @methodOf pascalprecht.translate.$translateDefaultInterpolation
2766 *
2767 * @description
2768 * Returns an identifier for this interpolation service.
2769 *
2770 * @returns {string} $identifier
2771 */
2772 $translateInterpolator.getInterpolationIdentifier = function () {
2773 return $identifier;
2774 };
2775
2776 /**
2777 * @deprecated will be removed in 3.0
2778 * @see {@link pascalprecht.translate.$translateSanitization}
2779 */
2780 $translateInterpolator.useSanitizeValueStrategy = function (value) {
2781 $translateSanitization.useStrategy(value);
2782 return this;
2783 };
2784
2785 /**
2786 * @ngdoc function
2787 * @name pascalprecht.translate.$translateDefaultInterpolation#interpolate
2788 * @methodOf pascalprecht.translate.$translateDefaultInterpolation
2789 *
2790 * @description
2791 * Interpolates given value agains given interpolate params using angulars
2792 * `$interpolate` service.
2793 *
2794 * Since AngularJS 1.5, `value` must not be a string but can be anything input.
2795 *
2796 * @returns {string} interpolated string.
2797 */
2798 $translateInterpolator.interpolate = function (value, interpolationParams, context) {
2799 interpolationParams = interpolationParams || {};
2800 interpolationParams = $translateSanitization.sanitize(interpolationParams, 'params', undefined, context);
2801
2802 var interpolatedText;
2803 if (angular.isNumber(value)) {
2804 // numbers are safe
2805 interpolatedText = '' + value;
2806 } else if (angular.isString(value)) {
2807 // strings must be interpolated (that's the job here)
2808 interpolatedText = $interpolate(value)(interpolationParams);
2809 interpolatedText = $translateSanitization.sanitize(interpolatedText, 'text', undefined, context);
2810 } else {
2811 // neither a number or a string, cant interpolate => empty string
2812 interpolatedText = '';
2813 }
2814
2815 return interpolatedText;
2816 };
2817
2818 return $translateInterpolator;
2819}
2820
2821$translateDefaultInterpolation.displayName = '$translateDefaultInterpolation';
2822
2823angular.module('pascalprecht.translate').constant('$STORAGE_KEY', 'NG_TRANSLATE_LANG_KEY');
2824
2825angular.module('pascalprecht.translate')
2826/**
2827 * @ngdoc directive
2828 * @name pascalprecht.translate.directive:translate
2829 * @requires $interpolate,
2830 * @requires $compile,
2831 * @requires $parse,
2832 * @requires $rootScope
2833 * @restrict AE
2834 *
2835 * @description
2836 * Translates given translation id either through attribute or DOM content.
2837 * Internally it uses $translate service to translate the translation id. It possible to
2838 * pass an optional `translate-values` object literal as string into translation id.
2839 *
2840 * @param {string=} translate Translation id which could be either string or interpolated string.
2841 * @param {string=} translate-values Values to pass into translation id. Can be passed as object literal string or interpolated object.
2842 * @param {string=} translate-attr-ATTR translate Translation id and put it into ATTR attribute.
2843 * @param {string=} translate-default will be used unless translation was successful
2844 * @param {boolean=} translate-compile (default true if present) defines locally activation of {@link pascalprecht.translate.$translateProvider#methods_usePostCompiling}
2845 * @param {boolean=} translate-keep-content (default true if present) defines that in case a KEY could not be translated, that the existing content is left in the innerHTML}
2846 *
2847 * @example
2848 <example module="ngView">
2849 <file name="index.html">
2850 <div ng-controller="TranslateCtrl">
2851
2852 <pre translate="TRANSLATION_ID"></pre>
2853 <pre translate>TRANSLATION_ID</pre>
2854 <pre translate translate-attr-title="TRANSLATION_ID"></pre>
2855 <pre translate="{{translationId}}"></pre>
2856 <pre translate>{{translationId}}</pre>
2857 <pre translate="WITH_VALUES" translate-values="{value: 5}"></pre>
2858 <pre translate translate-values="{value: 5}">WITH_VALUES</pre>
2859 <pre translate="WITH_VALUES" translate-values="{{values}}"></pre>
2860 <pre translate translate-values="{{values}}">WITH_VALUES</pre>
2861 <pre translate translate-attr-title="WITH_VALUES" translate-values="{{values}}"></pre>
2862 <pre translate="WITH_CAMEL_CASE_KEY" translate-value-camel-case-key="Hi"></pre>
2863
2864 </div>
2865 </file>
2866 <file name="script.js">
2867 angular.module('ngView', ['pascalprecht.translate'])
2868
2869 .config(function ($translateProvider) {
2870
2871 $translateProvider.translations('en',{
2872 'TRANSLATION_ID': 'Hello there!',
2873 'WITH_VALUES': 'The following value is dynamic: {{value}}',
2874 'WITH_CAMEL_CASE_KEY': 'The interpolation key is camel cased: {{camelCaseKey}}'
2875 }).preferredLanguage('en');
2876
2877 });
2878
2879 angular.module('ngView').controller('TranslateCtrl', function ($scope) {
2880 $scope.translationId = 'TRANSLATION_ID';
2881
2882 $scope.values = {
2883 value: 78
2884 };
2885 });
2886 </file>
2887 <file name="scenario.js">
2888 it('should translate', function () {
2889 inject(function ($rootScope, $compile) {
2890 $rootScope.translationId = 'TRANSLATION_ID';
2891
2892 element = $compile('<p translate="TRANSLATION_ID"></p>')($rootScope);
2893 $rootScope.$digest();
2894 expect(element.text()).toBe('Hello there!');
2895
2896 element = $compile('<p translate="{{translationId}}"></p>')($rootScope);
2897 $rootScope.$digest();
2898 expect(element.text()).toBe('Hello there!');
2899
2900 element = $compile('<p translate>TRANSLATION_ID</p>')($rootScope);
2901 $rootScope.$digest();
2902 expect(element.text()).toBe('Hello there!');
2903
2904 element = $compile('<p translate>{{translationId}}</p>')($rootScope);
2905 $rootScope.$digest();
2906 expect(element.text()).toBe('Hello there!');
2907
2908 element = $compile('<p translate translate-attr-title="TRANSLATION_ID"></p>')($rootScope);
2909 $rootScope.$digest();
2910 expect(element.attr('title')).toBe('Hello there!');
2911
2912 element = $compile('<p translate="WITH_CAMEL_CASE_KEY" translate-value-camel-case-key="Hello"></p>')($rootScope);
2913 $rootScope.$digest();
2914 expect(element.text()).toBe('The interpolation key is camel cased: Hello');
2915 });
2916 });
2917 </file>
2918 </example>
2919 */
2920.directive('translate', translateDirective);
2921function translateDirective($translate, $interpolate, $compile, $parse, $rootScope) {
2922
2923 'use strict';
2924
2925 /**
2926 * @name trim
2927 * @private
2928 *
2929 * @description
2930 * trim polyfill
2931 *
2932 * @returns {string} The string stripped of whitespace from both ends
2933 */
2934 var trim = function() {
2935 return this.toString().replace(/^\s+|\s+$/g, '');
2936 };
2937
2938 return {
2939 restrict: 'AE',
2940 scope: true,
2941 priority: $translate.directivePriority(),
2942 compile: function (tElement, tAttr) {
2943
2944 var translateValuesExist = (tAttr.translateValues) ?
2945 tAttr.translateValues : undefined;
2946
2947 var translateInterpolation = (tAttr.translateInterpolation) ?
2948 tAttr.translateInterpolation : undefined;
2949
2950 var translateValueExist = tElement[0].outerHTML.match(/translate-value-+/i);
2951
2952 var interpolateRegExp = '^(.*)(' + $interpolate.startSymbol() + '.*' + $interpolate.endSymbol() + ')(.*)',
2953 watcherRegExp = '^(.*)' + $interpolate.startSymbol() + '(.*)' + $interpolate.endSymbol() + '(.*)';
2954
2955 return function linkFn(scope, iElement, iAttr) {
2956
2957 scope.interpolateParams = {};
2958 scope.preText = '';
2959 scope.postText = '';
2960 scope.translateNamespace = getTranslateNamespace(scope);
2961 var translationIds = {};
2962
2963 var initInterpolationParams = function (interpolateParams, iAttr, tAttr) {
2964 // initial setup
2965 if (iAttr.translateValues) {
2966 angular.extend(interpolateParams, $parse(iAttr.translateValues)(scope.$parent));
2967 }
2968 // initially fetch all attributes if existing and fill the params
2969 if (translateValueExist) {
2970 for (var attr in tAttr) {
2971 if (Object.prototype.hasOwnProperty.call(iAttr, attr) && attr.substr(0, 14) === 'translateValue' && attr !== 'translateValues') {
2972 var attributeName = angular.lowercase(attr.substr(14, 1)) + attr.substr(15);
2973 interpolateParams[attributeName] = tAttr[attr];
2974 }
2975 }
2976 }
2977 };
2978
2979 // Ensures any change of the attribute "translate" containing the id will
2980 // be re-stored to the scope's "translationId".
2981 // If the attribute has no content, the element's text value (white spaces trimmed off) will be used.
2982 var observeElementTranslation = function (translationId) {
2983
2984 // Remove any old watcher
2985 if (angular.isFunction(observeElementTranslation._unwatchOld)) {
2986 observeElementTranslation._unwatchOld();
2987 observeElementTranslation._unwatchOld = undefined;
2988 }
2989
2990 if (angular.equals(translationId , '') || !angular.isDefined(translationId)) {
2991 var iElementText = trim.apply(iElement.text());
2992
2993 // Resolve translation id by inner html if required
2994 var interpolateMatches = iElementText.match(interpolateRegExp);
2995 // Interpolate translation id if required
2996 if (angular.isArray(interpolateMatches)) {
2997 scope.preText = interpolateMatches[1];
2998 scope.postText = interpolateMatches[3];
2999 translationIds.translate = $interpolate(interpolateMatches[2])(scope.$parent);
3000 var watcherMatches = iElementText.match(watcherRegExp);
3001 if (angular.isArray(watcherMatches) && watcherMatches[2] && watcherMatches[2].length) {
3002 observeElementTranslation._unwatchOld = scope.$watch(watcherMatches[2], function (newValue) {
3003 translationIds.translate = newValue;
3004 updateTranslations();
3005 });
3006 }
3007 } else {
3008 // do not assigne the translation id if it is empty.
3009 translationIds.translate = !iElementText ? undefined : iElementText;
3010 }
3011 } else {
3012 translationIds.translate = translationId;
3013 }
3014 updateTranslations();
3015 };
3016
3017 var observeAttributeTranslation = function (translateAttr) {
3018 iAttr.$observe(translateAttr, function (translationId) {
3019 translationIds[translateAttr] = translationId;
3020 updateTranslations();
3021 });
3022 };
3023
3024 // initial setup with values
3025 initInterpolationParams(scope.interpolateParams, iAttr, tAttr);
3026
3027 var firstAttributeChangedEvent = true;
3028 iAttr.$observe('translate', function (translationId) {
3029 if (typeof translationId === 'undefined') {
3030 // case of element "<translate>xyz</translate>"
3031 observeElementTranslation('');
3032 } else {
3033 // case of regular attribute
3034 if (translationId !== '' || !firstAttributeChangedEvent) {
3035 translationIds.translate = translationId;
3036 updateTranslations();
3037 }
3038 }
3039 firstAttributeChangedEvent = false;
3040 });
3041
3042 for (var translateAttr in iAttr) {
3043 if (iAttr.hasOwnProperty(translateAttr) && translateAttr.substr(0, 13) === 'translateAttr' && translateAttr.length > 13) {
3044 observeAttributeTranslation(translateAttr);
3045 }
3046 }
3047
3048 iAttr.$observe('translateDefault', function (value) {
3049 scope.defaultText = value;
3050 updateTranslations();
3051 });
3052
3053 if (translateValuesExist) {
3054 iAttr.$observe('translateValues', function (interpolateParams) {
3055 if (interpolateParams) {
3056 scope.$parent.$watch(function () {
3057 angular.extend(scope.interpolateParams, $parse(interpolateParams)(scope.$parent));
3058 });
3059 }
3060 });
3061 }
3062
3063 if (translateValueExist) {
3064 var observeValueAttribute = function (attrName) {
3065 iAttr.$observe(attrName, function (value) {
3066 var attributeName = angular.lowercase(attrName.substr(14, 1)) + attrName.substr(15);
3067 scope.interpolateParams[attributeName] = value;
3068 });
3069 };
3070 for (var attr in iAttr) {
3071 if (Object.prototype.hasOwnProperty.call(iAttr, attr) && attr.substr(0, 14) === 'translateValue' && attr !== 'translateValues') {
3072 observeValueAttribute(attr);
3073 }
3074 }
3075 }
3076
3077 // Master update function
3078 var updateTranslations = function () {
3079 for (var key in translationIds) {
3080 if (translationIds.hasOwnProperty(key) && translationIds[key] !== undefined) {
3081 updateTranslation(key, translationIds[key], scope, scope.interpolateParams, scope.defaultText, scope.translateNamespace);
3082 }
3083 }
3084 };
3085
3086 // Put translation processing function outside loop
3087 var updateTranslation = function(translateAttr, translationId, scope, interpolateParams, defaultTranslationText, translateNamespace) {
3088 if (translationId) {
3089 // if translation id starts with '.' and translateNamespace given, prepend namespace
3090 if (translateNamespace && translationId.charAt(0) === '.') {
3091 translationId = translateNamespace + translationId;
3092 }
3093
3094 $translate(translationId, interpolateParams, translateInterpolation, defaultTranslationText, scope.translateLanguage)
3095 .then(function (translation) {
3096 applyTranslation(translation, scope, true, translateAttr);
3097 }, function (translationId) {
3098 applyTranslation(translationId, scope, false, translateAttr);
3099 });
3100 } else {
3101 // as an empty string cannot be translated, we can solve this using successful=false
3102 applyTranslation(translationId, scope, false, translateAttr);
3103 }
3104 };
3105
3106 var applyTranslation = function (value, scope, successful, translateAttr) {
3107 if (!successful) {
3108 if (typeof scope.defaultText !== 'undefined') {
3109 value = scope.defaultText;
3110 }
3111 }
3112 if (translateAttr === 'translate') {
3113 // default translate into innerHTML
3114 if (successful || (!successful && !$translate.isKeepContent() && typeof iAttr.translateKeepContent === 'undefined')) {
3115 iElement.empty().append(scope.preText + value + scope.postText);
3116 }
3117 var globallyEnabled = $translate.isPostCompilingEnabled();
3118 var locallyDefined = typeof tAttr.translateCompile !== 'undefined';
3119 var locallyEnabled = locallyDefined && tAttr.translateCompile !== 'false';
3120 if ((globallyEnabled && !locallyDefined) || locallyEnabled) {
3121 $compile(iElement.contents())(scope);
3122 }
3123 } else {
3124 // translate attribute
3125 var attributeName = iAttr.$attr[translateAttr];
3126 if (attributeName.substr(0, 5) === 'data-') {
3127 // ensure html5 data prefix is stripped
3128 attributeName = attributeName.substr(5);
3129 }
3130 attributeName = attributeName.substr(15);
3131 iElement.attr(attributeName, value);
3132 }
3133 };
3134
3135 if (translateValuesExist || translateValueExist || iAttr.translateDefault) {
3136 scope.$watch('interpolateParams', updateTranslations, true);
3137 }
3138
3139 // Replaced watcher on translateLanguage with event listener
3140 scope.$on('translateLanguageChanged', updateTranslations);
3141
3142 // Ensures the text will be refreshed after the current language was changed
3143 // w/ $translate.use(...)
3144 var unbind = $rootScope.$on('$translateChangeSuccess', updateTranslations);
3145
3146 // ensure translation will be looked up at least one
3147 if (iElement.text().length) {
3148 if (iAttr.translate) {
3149 observeElementTranslation(iAttr.translate);
3150 } else {
3151 observeElementTranslation('');
3152 }
3153 } else if (iAttr.translate) {
3154 // ensure attribute will be not skipped
3155 observeElementTranslation(iAttr.translate);
3156 }
3157 updateTranslations();
3158 scope.$on('$destroy', unbind);
3159 };
3160 }
3161 };
3162}
3163
3164/**
3165 * Returns the scope's namespace.
3166 * @private
3167 * @param scope
3168 * @returns {string}
3169 */
3170function getTranslateNamespace(scope) {
3171 'use strict';
3172 if (scope.translateNamespace) {
3173 return scope.translateNamespace;
3174 }
3175 if (scope.$parent) {
3176 return getTranslateNamespace(scope.$parent);
3177 }
3178}
3179
3180translateDirective.displayName = 'translateDirective';
3181
3182angular.module('pascalprecht.translate')
3183/**
3184 * @ngdoc directive
3185 * @name pascalprecht.translate.directive:translate-attr
3186 * @restrict A
3187 *
3188 * @description
3189 * Translates attributes like translate-attr-ATTR, but with an object like ng-class.
3190 * Internally it uses `translate` service to translate translation id. It possible to
3191 * pass an optional `translate-values` object literal as string into translation id.
3192 *
3193 * @param {string=} translate-attr Object literal mapping attributes to translation ids.
3194 * @param {string=} translate-values Values to pass into the translation ids. Can be passed as object literal string.
3195 *
3196 * @example
3197 <example module="ngView">
3198 <file name="index.html">
3199 <div ng-controller="TranslateCtrl">
3200
3201 <input translate-attr="{ placeholder: translationId, title: 'WITH_VALUES' }" translate-values="{value: 5}" />
3202
3203 </div>
3204 </file>
3205 <file name="script.js">
3206 angular.module('ngView', ['pascalprecht.translate'])
3207
3208 .config(function ($translateProvider) {
3209
3210 $translateProvider.translations('en',{
3211 'TRANSLATION_ID': 'Hello there!',
3212 'WITH_VALUES': 'The following value is dynamic: {{value}}',
3213 }).preferredLanguage('en');
3214
3215 });
3216
3217 angular.module('ngView').controller('TranslateCtrl', function ($scope) {
3218 $scope.translationId = 'TRANSLATION_ID';
3219
3220 $scope.values = {
3221 value: 78
3222 };
3223 });
3224 </file>
3225 <file name="scenario.js">
3226 it('should translate', function () {
3227 inject(function ($rootScope, $compile) {
3228 $rootScope.translationId = 'TRANSLATION_ID';
3229
3230 element = $compile('<input translate-attr="{ placeholder: translationId, title: 'WITH_VALUES' }" translate-values="{ value: 5 }" />')($rootScope);
3231 $rootScope.$digest();
3232 expect(element.attr('placeholder)).toBe('Hello there!');
3233 expect(element.attr('title)).toBe('The following value is dynamic: 5');
3234 });
3235 });
3236 </file>
3237 </example>
3238 */
3239.directive('translateAttr', translateAttrDirective);
3240function translateAttrDirective($translate, $rootScope) {
3241
3242 'use strict';
3243
3244 return {
3245 restrict: 'A',
3246 priority: $translate.directivePriority(),
3247 link: function linkFn(scope, element, attr) {
3248
3249 var translateAttr,
3250 translateValues,
3251 previousAttributes = {};
3252
3253 // Main update translations function
3254 var updateTranslations = function () {
3255 angular.forEach(translateAttr, function (translationId, attributeName) {
3256 if (!translationId) {
3257 return;
3258 }
3259 previousAttributes[attributeName] = true;
3260
3261 // if translation id starts with '.' and translateNamespace given, prepend namespace
3262 if (scope.translateNamespace && translationId.charAt(0) === '.') {
3263 translationId = scope.translateNamespace + translationId;
3264 }
3265 $translate(translationId, translateValues, attr.translateInterpolation, undefined, scope.translateLanguage)
3266 .then(function (translation) {
3267 element.attr(attributeName, translation);
3268 }, function (translationId) {
3269 element.attr(attributeName, translationId);
3270 });
3271 });
3272
3273 // Removing unused attributes that were previously used
3274 angular.forEach(previousAttributes, function (flag, attributeName) {
3275 if (!translateAttr[attributeName]) {
3276 element.removeAttr(attributeName);
3277 delete previousAttributes[attributeName];
3278 }
3279 });
3280 };
3281
3282 // Watch for attribute changes
3283 watchAttribute(
3284 scope,
3285 attr.translateAttr,
3286 function (newValue) { translateAttr = newValue; },
3287 updateTranslations
3288 );
3289 // Watch for value changes
3290 watchAttribute(
3291 scope,
3292 attr.translateValues,
3293 function (newValue) { translateValues = newValue; },
3294 updateTranslations
3295 );
3296
3297 if (attr.translateValues) {
3298 scope.$watch(attr.translateValues, updateTranslations, true);
3299 }
3300
3301 // Replaced watcher on translateLanguage with event listener
3302 scope.$on('translateLanguageChanged', updateTranslations);
3303
3304 // Ensures the text will be refreshed after the current language was changed
3305 // w/ $translate.use(...)
3306 var unbind = $rootScope.$on('$translateChangeSuccess', updateTranslations);
3307
3308 updateTranslations();
3309 scope.$on('$destroy', unbind);
3310 }
3311 };
3312}
3313
3314function watchAttribute(scope, attribute, valueCallback, changeCallback) {
3315 'use strict';
3316 if (!attribute) {
3317 return;
3318 }
3319 if (attribute.substr(0, 2) === '::') {
3320 attribute = attribute.substr(2);
3321 } else {
3322 scope.$watch(attribute, function(newValue) {
3323 valueCallback(newValue);
3324 changeCallback();
3325 }, true);
3326 }
3327 valueCallback(scope.$eval(attribute));
3328}
3329
3330translateAttrDirective.displayName = 'translateAttrDirective';
3331
3332angular.module('pascalprecht.translate')
3333/**
3334 * @ngdoc directive
3335 * @name pascalprecht.translate.directive:translateCloak
3336 * @requires $rootScope
3337 * @requires $translate
3338 * @restrict A
3339 *
3340 * $description
3341 * Adds a `translate-cloak` class name to the given element where this directive
3342 * is applied initially and removes it, once a loader has finished loading.
3343 *
3344 * This directive can be used to prevent initial flickering when loading translation
3345 * data asynchronously.
3346 *
3347 * The class name is defined in
3348 * {@link pascalprecht.translate.$translateProvider#cloakClassName $translate.cloakClassName()}.
3349 *
3350 * @param {string=} translate-cloak If a translationId is provided, it will be used for showing
3351 * or hiding the cloak. Basically it relies on the translation
3352 * resolve.
3353 */
3354.directive('translateCloak', translateCloakDirective);
3355
3356function translateCloakDirective($translate, $rootScope) {
3357
3358 'use strict';
3359
3360 return {
3361 compile: function (tElement) {
3362 var applyCloak = function () {
3363 tElement.addClass($translate.cloakClassName());
3364 },
3365 removeCloak = function () {
3366 tElement.removeClass($translate.cloakClassName());
3367 };
3368 $translate.onReady(function () {
3369 removeCloak();
3370 });
3371 applyCloak();
3372
3373 return function linkFn(scope, iElement, iAttr) {
3374 if (iAttr.translateCloak && iAttr.translateCloak.length) {
3375 // Register a watcher for the defined translation allowing a fine tuned cloak
3376 iAttr.$observe('translateCloak', function (translationId) {
3377 $translate(translationId).then(removeCloak, applyCloak);
3378 });
3379 // Register for change events as this is being another indicicator revalidating the cloak)
3380 $rootScope.$on('$translateChangeSuccess', function () {
3381 $translate(iAttr.translateCloak).then(removeCloak, applyCloak);
3382 });
3383 }
3384 };
3385 }
3386 };
3387}
3388
3389translateCloakDirective.displayName = 'translateCloakDirective';
3390
3391angular.module('pascalprecht.translate')
3392/**
3393 * @ngdoc directive
3394 * @name pascalprecht.translate.directive:translateNamespace
3395 * @restrict A
3396 *
3397 * @description
3398 * Translates given translation id either through attribute or DOM content.
3399 * Internally it uses `translate` filter to translate translation id. It possible to
3400 * pass an optional `translate-values` object literal as string into translation id.
3401 *
3402 * @param {string=} translate namespace name which could be either string or interpolated string.
3403 *
3404 * @example
3405 <example module="ngView">
3406 <file name="index.html">
3407 <div translate-namespace="CONTENT">
3408
3409 <div>
3410 <h1 translate>.HEADERS.TITLE</h1>
3411 <h1 translate>.HEADERS.WELCOME</h1>
3412 </div>
3413
3414 <div translate-namespace=".HEADERS">
3415 <h1 translate>.TITLE</h1>
3416 <h1 translate>.WELCOME</h1>
3417 </div>
3418
3419 </div>
3420 </file>
3421 <file name="script.js">
3422 angular.module('ngView', ['pascalprecht.translate'])
3423
3424 .config(function ($translateProvider) {
3425
3426 $translateProvider.translations('en',{
3427 'TRANSLATION_ID': 'Hello there!',
3428 'CONTENT': {
3429 'HEADERS': {
3430 TITLE: 'Title'
3431 }
3432 },
3433 'CONTENT.HEADERS.WELCOME': 'Welcome'
3434 }).preferredLanguage('en');
3435
3436 });
3437
3438 </file>
3439 </example>
3440 */
3441.directive('translateNamespace', translateNamespaceDirective);
3442
3443function translateNamespaceDirective() {
3444
3445 'use strict';
3446
3447 return {
3448 restrict: 'A',
3449 scope: true,
3450 compile: function () {
3451 return {
3452 pre: function (scope, iElement, iAttrs) {
3453 scope.translateNamespace = getTranslateNamespace(scope);
3454
3455 if (scope.translateNamespace && iAttrs.translateNamespace.charAt(0) === '.') {
3456 scope.translateNamespace += iAttrs.translateNamespace;
3457 } else {
3458 scope.translateNamespace = iAttrs.translateNamespace;
3459 }
3460 }
3461 };
3462 }
3463 };
3464}
3465
3466/**
3467 * Returns the scope's namespace.
3468 * @private
3469 * @param scope
3470 * @returns {string}
3471 */
3472function getTranslateNamespace(scope) {
3473 'use strict';
3474 if (scope.translateNamespace) {
3475 return scope.translateNamespace;
3476 }
3477 if (scope.$parent) {
3478 return getTranslateNamespace(scope.$parent);
3479 }
3480}
3481
3482translateNamespaceDirective.displayName = 'translateNamespaceDirective';
3483
3484angular.module('pascalprecht.translate')
3485/**
3486 * @ngdoc directive
3487 * @name pascalprecht.translate.directive:translateLanguage
3488 * @restrict A
3489 *
3490 * @description
3491 * Forces the language to the directives in the underlying scope.
3492 *
3493 * @param {string=} translate language that will be negotiated.
3494 *
3495 * @example
3496 <example module="ngView">
3497 <file name="index.html">
3498 <div>
3499
3500 <div>
3501 <h1 translate>HELLO</h1>
3502 </div>
3503
3504 <div translate-language="de">
3505 <h1 translate>HELLO</h1>
3506 </div>
3507
3508 </div>
3509 </file>
3510 <file name="script.js">
3511 angular.module('ngView', ['pascalprecht.translate'])
3512
3513 .config(function ($translateProvider) {
3514
3515 $translateProvider
3516 .translations('en',{
3517 'HELLO': 'Hello world!'
3518 })
3519 .translations('de',{
3520 'HELLO': 'Hallo Welt!'
3521 })
3522 .preferredLanguage('en');
3523
3524 });
3525
3526 </file>
3527 </example>
3528 */
3529.directive('translateLanguage', translateLanguageDirective);
3530
3531function translateLanguageDirective() {
3532
3533 'use strict';
3534
3535 return {
3536 restrict: 'A',
3537 scope: true,
3538 compile: function () {
3539 return function linkFn(scope, iElement, iAttrs) {
3540
3541 iAttrs.$observe('translateLanguage', function (newTranslateLanguage) {
3542 scope.translateLanguage = newTranslateLanguage;
3543 });
3544
3545 scope.$watch('translateLanguage', function(){
3546 scope.$broadcast('translateLanguageChanged');
3547 });
3548 };
3549 }
3550 };
3551}
3552
3553translateLanguageDirective.displayName = 'translateLanguageDirective';
3554
3555angular.module('pascalprecht.translate')
3556/**
3557 * @ngdoc filter
3558 * @name pascalprecht.translate.filter:translate
3559 * @requires $parse
3560 * @requires pascalprecht.translate.$translate
3561 * @function
3562 *
3563 * @description
3564 * Uses `$translate` service to translate contents. Accepts interpolate parameters
3565 * to pass dynamized values though translation.
3566 *
3567 * @param {string} translationId A translation id to be translated.
3568 * @param {*=} interpolateParams Optional object literal (as hash or string) to pass values into translation.
3569 *
3570 * @returns {string} Translated text.
3571 *
3572 * @example
3573 <example module="ngView">
3574 <file name="index.html">
3575 <div ng-controller="TranslateCtrl">
3576
3577 <pre>{{ 'TRANSLATION_ID' | translate }}</pre>
3578 <pre>{{ translationId | translate }}</pre>
3579 <pre>{{ 'WITH_VALUES' | translate:'{value: 5}' }}</pre>
3580 <pre>{{ 'WITH_VALUES' | translate:values }}</pre>
3581
3582 </div>
3583 </file>
3584 <file name="script.js">
3585 angular.module('ngView', ['pascalprecht.translate'])
3586
3587 .config(function ($translateProvider) {
3588
3589 $translateProvider.translations('en', {
3590 'TRANSLATION_ID': 'Hello there!',
3591 'WITH_VALUES': 'The following value is dynamic: {{value}}'
3592 });
3593 $translateProvider.preferredLanguage('en');
3594
3595 });
3596
3597 angular.module('ngView').controller('TranslateCtrl', function ($scope) {
3598 $scope.translationId = 'TRANSLATION_ID';
3599
3600 $scope.values = {
3601 value: 78
3602 };
3603 });
3604 </file>
3605 </example>
3606 */
3607.filter('translate', translateFilterFactory);
3608
3609function translateFilterFactory($parse, $translate) {
3610
3611 'use strict';
3612
3613 var translateFilter = function (translationId, interpolateParams, interpolation, forceLanguage) {
3614 if (!angular.isObject(interpolateParams)) {
3615 interpolateParams = $parse(interpolateParams)(this);
3616 }
3617
3618 return $translate.instant(translationId, interpolateParams, interpolation, forceLanguage);
3619 };
3620
3621 if ($translate.statefulFilter()) {
3622 translateFilter.$stateful = true;
3623 }
3624
3625 return translateFilter;
3626}
3627
3628translateFilterFactory.displayName = 'translateFilterFactory';
3629
3630angular.module('pascalprecht.translate')
3631
3632/**
3633 * @ngdoc object
3634 * @name pascalprecht.translate.$translationCache
3635 * @requires $cacheFactory
3636 *
3637 * @description
3638 * The first time a translation table is used, it is loaded in the translation cache for quick retrieval. You
3639 * can load translation tables directly into the cache by consuming the
3640 * `$translationCache` service directly.
3641 *
3642 * @return {object} $cacheFactory object.
3643 */
3644 .factory('$translationCache', $translationCache);
3645
3646function $translationCache($cacheFactory) {
3647
3648 'use strict';
3649
3650 return $cacheFactory('translations');
3651}
3652
3653$translationCache.displayName = '$translationCache';
3654return 'pascalprecht.translate';
3655
3656}));