diff --git a/www/index.html b/www/index.html index f3a35bcf937d4a0bf288605648554cac67b99853..924ec2b5b1ae23220c01cc7a7d098ee3cf473148 100644 --- a/www/index.html +++ b/www/index.html @@ -15,10 +15,10 @@ <!-- ionic/angularjs js --> <script src="lib/ionic/js/ionic.bundle.js"></script> <script src="lib/ionic/js/angular/angular-resource.min.js"></script> - <script src="lib/angular-sanitize/angular-sanitize.min.js"></script> - <script src="lib/angular-translate/angular-translate.min.js"></script> - <script src="lib/angular-translate-loader-static-files/angular-translate-loader-static-files.min.js"></script> - <script src="lib/angular-messages/angular-messages.min.js"></script> + <script src="lib/ionic/js/angular/angular-sanitize.min.js"></script> + <script src="lib/ionic/js/angular/angular-translate.min.js"></script> + <script src="lib/ionic/js/angular/angular-translate-loader-static-files.min.js"></script> + <script src="lib/ionic/js/angular/angular-messages.min.js"></script> <script src="js/vendor/moment.min.js"></script> <script src="js/vendor/numeral.js"></script> <script src="js/vendor/socket-io.js"></script> diff --git a/www/js/controllers/home-controllers.js b/www/js/controllers/home-controllers.js index cc46ae6bd4b20b30fdc29a00b43f0cfe3fb2ae76..4b2694a96b97d800300438e2f6cea7259fd743cf 100644 --- a/www/js/controllers/home-controllers.js +++ b/www/js/controllers/home-controllers.js @@ -37,15 +37,9 @@ angular.module('cesium.home.controllers', ['cesium.services']) ; function LoginController($scope, $ionicModal, Wallet, CryptoUtils, UIUtils, $q, $state, $timeout, $ionicSideMenuDelegate) { - // Form data for the login modal - $scope.loginData = { - username: null, - password: null - }; - $scope.loginForm = null; - // Login modal $scope.loginModal = "undefined"; + $scope.loginData = {}; // Create the login modal that we will use later $ionicModal.fromTemplateUrl('templates/login.html', { @@ -116,7 +110,7 @@ function LoginController($scope, $ionicModal, Wallet, CryptoUtils, UIUtils, $q, if(!$scope.loginForm.$valid) { return; } - $scope.loginForm = null; + delete $scope.loginForm; $scope.closeLogin(); UIUtils.loading.show(); @@ -239,15 +233,15 @@ function HomeController($scope, $ionicSlideBoxDelegate, $ionicModal, $state, BMA $scope.slide(0); $scope.newAccountModal.show(); // TODO: remove default - $timeout(function() { + /*$timeout(function() { $scope.accountData.currency = $scope.knownCurrencies[0]; $scope.accountData.isMember = true; $scope.next(); $scope.next(); - }, 300); + }, 300);*/ } - if ($scope.newAccountModal == "undefined") { + if (!$scope.newAccountModal) { UIUtils.loading.show(); // Create the account modal that we will use later $ionicModal.fromTemplateUrl('templates/account/new_account.html', { @@ -298,7 +292,8 @@ function HomeController($scope, $ionicSlideBoxDelegate, $ionicModal, $state, BMA if(!$scope.accountForm.$valid) { return; } - $scope.accountForm = null; + delete $scope.accountForm; + delete $scope.accountData; }; // TODO: remove auto add account when done diff --git a/www/lib/ionic/js/angular/angular-messages.js b/www/lib/ionic/js/angular/angular-messages.js new file mode 100644 index 0000000000000000000000000000000000000000..e031385f447a4821c9b3ff7ba87481927b6aca75 --- /dev/null +++ b/www/lib/ionic/js/angular/angular-messages.js @@ -0,0 +1,400 @@ +/** + * @license AngularJS v1.3.13 + * (c) 2010-2014 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, angular, undefined) {'use strict'; + +/** + * @ngdoc module + * @name ngMessages + * @description + * + * The `ngMessages` module provides enhanced support for displaying messages within templates + * (typically within forms or when rendering message objects that return key/value data). + * Instead of relying on JavaScript code and/or complex ng-if statements within your form template to + * show and hide error messages specific to the state of an input field, the `ngMessages` and + * `ngMessage` directives are designed to handle the complexity, inheritance and priority + * sequencing based on the order of how the messages are defined in the template. + * + * Currently, the ngMessages module only contains the code for the `ngMessages` + * and `ngMessage` directives. + * + * # Usage + * The `ngMessages` directive listens on a key/value collection which is set on the ngMessages attribute. + * Since the {@link ngModel ngModel} directive exposes an `$error` object, this error object can be + * used with `ngMessages` to display control error messages in an easier way than with just regular angular + * template directives. + * + * ```html + * <form name="myForm"> + * <input type="text" ng-model="field" name="myField" required minlength="5" /> + * <div ng-messages="myForm.myField.$error"> + * <div ng-message="required">You did not enter a field</div> + * <div ng-message="minlength">The value entered is too short</div> + * </div> + * </form> + * ``` + * + * Now whatever key/value entries are present within the provided object (in this case `$error`) then + * the ngMessages directive will render the inner first ngMessage directive (depending if the key values + * match the attribute value present on each ngMessage directive). In other words, if your errors + * object contains the following data: + * + * ```javascript + * <!-- keep in mind that ngModel automatically sets these error flags --> + * myField.$error = { minlength : true, required : false }; + * ``` + * + * Then the `required` message will be displayed first. When required is false then the `minlength` message + * will be displayed right after (since these messages are ordered this way in the template HTML code). + * The prioritization of each message is determined by what order they're present in the DOM. + * Therefore, instead of having custom JavaScript code determine the priority of what errors are + * present before others, the presentation of the errors are handled within the template. + * + * By default, ngMessages will only display one error at a time. However, if you wish to display all + * messages then the `ng-messages-multiple` attribute flag can be used on the element containing the + * ngMessages directive to make this happen. + * + * ```html + * <div ng-messages="myForm.myField.$error" ng-messages-multiple>...</div> + * ``` + * + * ## Reusing and Overriding Messages + * In addition to prioritization, ngMessages also allows for including messages from a remote or an inline + * template. This allows for generic collection of messages to be reused across multiple parts of an + * application. + * + * ```html + * <script type="text/ng-template" id="error-messages"> + * <div ng-message="required">This field is required</div> + * <div ng-message="minlength">This field is too short</div> + * </script> + * <div ng-messages="myForm.myField.$error" ng-messages-include="error-messages"></div> + * ``` + * + * However, including generic messages may not be useful enough to match all input fields, therefore, + * `ngMessages` provides the ability to override messages defined in the remote template by redefining + * then within the directive container. + * + * ```html + * <!-- a generic template of error messages known as "my-custom-messages" --> + * <script type="text/ng-template" id="my-custom-messages"> + * <div ng-message="required">This field is required</div> + * <div ng-message="minlength">This field is too short</div> + * </script> + * + * <form name="myForm"> + * <input type="email" + * id="email" + * name="myEmail" + * ng-model="email" + * minlength="5" + * required /> + * + * <div ng-messages="myForm.myEmail.$error" ng-messages-include="my-custom-messages"> + * <!-- this required message has overridden the template message --> + * <div ng-message="required">You did not enter your email address</div> + * + * <!-- this is a brand new message and will appear last in the prioritization --> + * <div ng-message="email">Your email address is invalid</div> + * </div> + * </form> + * ``` + * + * In the example HTML code above the message that is set on required will override the corresponding + * required message defined within the remote template. Therefore, with particular input fields (such + * email addresses, date fields, autocomplete inputs, etc...), specialized error messages can be applied + * while more generic messages can be used to handle other, more general input errors. + * + * ## Animations + * If the `ngAnimate` module is active within the application then both the `ngMessages` and + * `ngMessage` directives will trigger animations whenever any messages are added and removed + * from the DOM by the `ngMessages` directive. + * + * Whenever the `ngMessages` directive contains one or more visible messages then the `.ng-active` CSS + * class will be added to the element. The `.ng-inactive` CSS class will be applied when there are no + * animations present. Therefore, CSS transitions and keyframes as well as JavaScript animations can + * hook into the animations whenever these classes are added/removed. + * + * Let's say that our HTML code for our messages container looks like so: + * + * ```html + * <div ng-messages="myMessages" class="my-messages"> + * <div ng-message="alert" class="some-message">...</div> + * <div ng-message="fail" class="some-message">...</div> + * </div> + * ``` + * + * Then the CSS animation code for the message container looks like so: + * + * ```css + * .my-messages { + * transition:1s linear all; + * } + * .my-messages.ng-active { + * // messages are visible + * } + * .my-messages.ng-inactive { + * // messages are hidden + * } + * ``` + * + * Whenever an inner message is attached (becomes visible) or removed (becomes hidden) then the enter + * and leave animation is triggered for each particular element bound to the `ngMessage` directive. + * + * Therefore, the CSS code for the inner messages looks like so: + * + * ```css + * .some-message { + * transition:1s linear all; + * } + * + * .some-message.ng-enter {} + * .some-message.ng-enter.ng-enter-active {} + * + * .some-message.ng-leave {} + * .some-message.ng-leave.ng-leave-active {} + * ``` + * + * {@link ngAnimate Click here} to learn how to use JavaScript animations or to learn more about ngAnimate. + */ +angular.module('ngMessages', []) + + /** + * @ngdoc directive + * @module ngMessages + * @name ngMessages + * @restrict AE + * + * @description + * `ngMessages` is a directive that is designed to show and hide messages based on the state + * of a key/value object that it listens on. The directive itself compliments error message + * reporting with the `ngModel` $error object (which stores a key/value state of validation errors). + * + * `ngMessages` manages the state of internal messages within its container element. The internal + * messages use the `ngMessage` directive and will be inserted/removed from the page depending + * on if they're present within the key/value object. By default, only one message will be displayed + * at a time and this depends on the prioritization of the messages within the template. (This can + * be changed by using the ng-messages-multiple on the directive container.) + * + * A remote template can also be used to promote message reusability and messages can also be + * overridden. + * + * {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`. + * + * @usage + * ```html + * <!-- using attribute directives --> + * <ANY ng-messages="expression"> + * <ANY ng-message="keyValue1">...</ANY> + * <ANY ng-message="keyValue2">...</ANY> + * <ANY ng-message="keyValue3">...</ANY> + * </ANY> + * + * <!-- or by using element directives --> + * <ng-messages for="expression"> + * <ng-message when="keyValue1">...</ng-message> + * <ng-message when="keyValue2">...</ng-message> + * <ng-message when="keyValue3">...</ng-message> + * </ng-messages> + * ``` + * + * @param {string} ngMessages an angular expression evaluating to a key/value object + * (this is typically the $error object on an ngModel instance). + * @param {string=} ngMessagesMultiple|multiple when set, all messages will be displayed with true + * @param {string=} ngMessagesInclude|include when set, the specified template will be included into the ng-messages container + * + * @example + * <example name="ngMessages-directive" module="ngMessagesExample" + * deps="angular-messages.js" + * animations="true" fixBase="true"> + * <file name="index.html"> + * <form name="myForm"> + * <label>Enter your name:</label> + * <input type="text" + * name="myName" + * ng-model="name" + * ng-minlength="5" + * ng-maxlength="20" + * required /> + * + * <pre>myForm.myName.$error = {{ myForm.myName.$error | json }}</pre> + * + * <div ng-messages="myForm.myName.$error" style="color:maroon"> + * <div ng-message="required">You did not enter a field</div> + * <div ng-message="minlength">Your field is too short</div> + * <div ng-message="maxlength">Your field is too long</div> + * </div> + * </form> + * </file> + * <file name="script.js"> + * angular.module('ngMessagesExample', ['ngMessages']); + * </file> + * </example> + */ + .directive('ngMessages', ['$compile', '$animate', '$templateRequest', + function($compile, $animate, $templateRequest) { + var ACTIVE_CLASS = 'ng-active'; + var INACTIVE_CLASS = 'ng-inactive'; + + return { + restrict: 'AE', + controller: function() { + this.$renderNgMessageClasses = angular.noop; + + var messages = []; + this.registerMessage = function(index, message) { + for (var i = 0; i < messages.length; i++) { + if (messages[i].type == message.type) { + if (index != i) { + var temp = messages[index]; + messages[index] = messages[i]; + if (index < messages.length) { + messages[i] = temp; + } else { + messages.splice(0, i); //remove the old one (and shift left) + } + } + return; + } + } + messages.splice(index, 0, message); //add the new one (and shift right) + }; + + this.renderMessages = function(values, multiple) { + values = values || {}; + + var found; + angular.forEach(messages, function(message) { + if ((!found || multiple) && truthyVal(values[message.type])) { + message.attach(); + found = true; + } else { + message.detach(); + } + }); + + this.renderElementClasses(found); + + function truthyVal(value) { + return value !== null && value !== false && value; + } + }; + }, + require: 'ngMessages', + link: function($scope, element, $attrs, ctrl) { + ctrl.renderElementClasses = function(bool) { + bool ? $animate.setClass(element, ACTIVE_CLASS, INACTIVE_CLASS) + : $animate.setClass(element, INACTIVE_CLASS, ACTIVE_CLASS); + }; + + //JavaScript treats empty strings as false, but ng-message-multiple by itself is an empty string + var multiple = angular.isString($attrs.ngMessagesMultiple) || + angular.isString($attrs.multiple); + + var cachedValues, watchAttr = $attrs.ngMessages || $attrs['for']; //for is a reserved keyword + $scope.$watchCollection(watchAttr, function(values) { + cachedValues = values; + ctrl.renderMessages(values, multiple); + }); + + var tpl = $attrs.ngMessagesInclude || $attrs.include; + if (tpl) { + $templateRequest(tpl) + .then(function processTemplate(html) { + var after, container = angular.element('<div/>').html(html); + angular.forEach(container.children(), function(elm) { + elm = angular.element(elm); + after ? after.after(elm) + : element.prepend(elm); //start of the container + after = elm; + $compile(elm)($scope); + }); + ctrl.renderMessages(cachedValues, multiple); + }); + } + } + }; + }]) + + + /** + * @ngdoc directive + * @name ngMessage + * @restrict AE + * @scope + * + * @description + * `ngMessage` is a directive with the purpose to show and hide a particular message. + * For `ngMessage` to operate, a parent `ngMessages` directive on a parent DOM element + * must be situated since it determines which messages are visible based on the state + * of the provided key/value map that `ngMessages` listens on. + * + * @usage + * ```html + * <!-- using attribute directives --> + * <ANY ng-messages="expression"> + * <ANY ng-message="keyValue1">...</ANY> + * <ANY ng-message="keyValue2">...</ANY> + * <ANY ng-message="keyValue3">...</ANY> + * </ANY> + * + * <!-- or by using element directives --> + * <ng-messages for="expression"> + * <ng-message when="keyValue1">...</ng-message> + * <ng-message when="keyValue2">...</ng-message> + * <ng-message when="keyValue3">...</ng-message> + * </ng-messages> + * ``` + * + * {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`. + * + * @param {string} ngMessage a string value corresponding to the message key. + */ + .directive('ngMessage', ['$animate', function($animate) { + var COMMENT_NODE = 8; + return { + require: '^ngMessages', + transclude: 'element', + terminal: true, + restrict: 'AE', + link: function($scope, $element, $attrs, ngMessages, $transclude) { + var index, element; + + var commentNode = $element[0]; + var parentNode = commentNode.parentNode; + for (var i = 0, j = 0; i < parentNode.childNodes.length; i++) { + var node = parentNode.childNodes[i]; + if (node.nodeType == COMMENT_NODE && node.nodeValue.indexOf('ngMessage') >= 0) { + if (node === commentNode) { + index = j; + break; + } + j++; + } + } + + ngMessages.registerMessage(index, { + type: $attrs.ngMessage || $attrs.when, + attach: function() { + if (!element) { + $transclude($scope, function(clone) { + $animate.enter(clone, null, $element); + element = clone; + }); + } + }, + detach: function(now) { + if (element) { + $animate.leave(element); + element = null; + } + } + }); + } + }; + }]); + + +})(window, window.angular); diff --git a/www/lib/ionic/js/angular/angular-messages.min.js b/www/lib/ionic/js/angular/angular-messages.min.js new file mode 100644 index 0000000000000000000000000000000000000000..9d50603f1849f7cbb93f550f716876db1fe9b759 --- /dev/null +++ b/www/lib/ionic/js/angular/angular-messages.min.js @@ -0,0 +1,10 @@ +/* + AngularJS v1.3.13 + (c) 2010-2014 Google, Inc. http://angularjs.org + License: MIT +*/ +(function(r,f,s){'use strict';f.module("ngMessages",[]).directive("ngMessages",["$compile","$animate","$templateRequest",function(q,k,l){return{restrict:"AE",controller:function(){this.$renderNgMessageClasses=f.noop;var b=[];this.registerMessage=function(d,a){for(var c=0;c<b.length;c++)if(b[c].type==a.type){if(d!=c){var g=b[d];b[d]=b[c];d<b.length?b[c]=g:b.splice(0,c)}return}b.splice(d,0,a)};this.renderMessages=function(d,a){d=d||{};var c;f.forEach(b,function(b){var e;if(e=!c||a)e=d[b.type],e=null!== +e&&!1!==e&&e;e?(b.attach(),c=!0):b.detach()});this.renderElementClasses(c)}},require:"ngMessages",link:function(b,d,a,c){c.renderElementClasses=function(b){b?k.setClass(d,"ng-active","ng-inactive"):k.setClass(d,"ng-inactive","ng-active")};var g=f.isString(a.ngMessagesMultiple)||f.isString(a.multiple),e;b.$watchCollection(a.ngMessages||a["for"],function(b){e=b;c.renderMessages(b,g)});(a=a.ngMessagesInclude||a.include)&&l(a).then(function(a){var h;a=f.element("<div/>").html(a);f.forEach(a.children(), +function(a){a=f.element(a);h?h.after(a):d.prepend(a);h=a;q(a)(b)});c.renderMessages(e,g)})}}}]).directive("ngMessage",["$animate",function(f){return{require:"^ngMessages",transclude:"element",terminal:!0,restrict:"AE",link:function(k,l,b,d,a){for(var c,g,e=l[0],n=e.parentNode,h=0,p=0;h<n.childNodes.length;h++){var m=n.childNodes[h];if(8==m.nodeType&&0<=m.nodeValue.indexOf("ngMessage")){if(m===e){c=p;break}p++}}d.registerMessage(c,{type:b.ngMessage||b.when,attach:function(){g||a(k,function(a){f.enter(a, +null,l);g=a})},detach:function(a){g&&(f.leave(g),g=null)}})}}}])})(window,window.angular); +//# sourceMappingURL=angular-messages.min.js.map diff --git a/www/lib/ionic/js/angular/angular-translate-loader-static-files.js b/www/lib/ionic/js/angular/angular-translate-loader-static-files.js new file mode 100644 index 0000000000000000000000000000000000000000..2378112e8ddfde35df35d242acf161df03d398d8 --- /dev/null +++ b/www/lib/ionic/js/angular/angular-translate-loader-static-files.js @@ -0,0 +1,107 @@ +/*! + * angular-translate - v2.10.0 - 2016-02-28 + * + * Copyright (c) 2016 The angular-translate team, Pascal Precht; Licensed MIT + */ +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module unless amdModuleId is set + define([], function () { + return (factory()); + }); + } else if (typeof exports === 'object') { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like environments that support module.exports, + // like Node. + module.exports = factory(); + } else { + factory(); + } +}(this, function () { + +angular.module('pascalprecht.translate') +/** + * @ngdoc object + * @name pascalprecht.translate.$translateStaticFilesLoader + * @requires $q + * @requires $http + * + * @description + * Creates a loading function for a typical static file url pattern: + * "lang-en_US.json", "lang-de_DE.json", etc. Using this builder, + * the response of these urls must be an object of key-value pairs. + * + * @param {object} options Options object, which gets prefix, suffix and key. + */ +.factory('$translateStaticFilesLoader', $translateStaticFilesLoader); + +function $translateStaticFilesLoader($q, $http) { + + 'use strict'; + + return function (options) { + + if (!options || (!angular.isArray(options.files) && (!angular.isString(options.prefix) || !angular.isString(options.suffix)))) { + throw new Error('Couldn\'t load static files, no files and prefix or suffix specified!'); + } + + if (!options.files) { + options.files = [{ + prefix: options.prefix, + suffix: options.suffix + }]; + } + + var load = function (file) { + if (!file || (!angular.isString(file.prefix) || !angular.isString(file.suffix))) { + throw new Error('Couldn\'t load static file, no prefix or suffix specified!'); + } + + return $http(angular.extend({ + url: [ + file.prefix, + options.key, + file.suffix + ].join(''), + method: 'GET', + params: '' + }, options.$http)) + .then(function(result) { + return result.data; + }, function () { + return $q.reject(options.key); + }); + }; + + var promises = [], + length = options.files.length; + + for (var i = 0; i < length; i++) { + promises.push(load({ + prefix: options.files[i].prefix, + key: options.key, + suffix: options.files[i].suffix + })); + } + + return $q.all(promises) + .then(function (data) { + var length = data.length, + mergedData = {}; + + for (var i = 0; i < length; i++) { + for (var key in data[i]) { + mergedData[key] = data[i][key]; + } + } + + return mergedData; + }); + }; +} +$translateStaticFilesLoader.$inject = ['$q', '$http']; + +$translateStaticFilesLoader.displayName = '$translateStaticFilesLoader'; +return 'pascalprecht.translate'; + +})); diff --git a/www/lib/ionic/js/angular/angular-translate-loader-static-files.min.js b/www/lib/ionic/js/angular/angular-translate-loader-static-files.min.js new file mode 100644 index 0000000000000000000000000000000000000000..dd907f1b6e547fed374b71393b757a88f1cee0e7 --- /dev/null +++ b/www/lib/ionic/js/angular/angular-translate-loader-static-files.min.js @@ -0,0 +1,6 @@ +/*! + * angular-translate - v2.10.0 - 2016-02-28 + * + * Copyright (c) 2016 The angular-translate team, Pascal Precht; Licensed MIT + */ +!function(a,b){"function"==typeof define&&define.amd?define([],function(){return b()}):"object"==typeof exports?module.exports=b():b()}(this,function(){function a(a,b){"use strict";return function(c){if(!(c&&(angular.isArray(c.files)||angular.isString(c.prefix)&&angular.isString(c.suffix))))throw new Error("Couldn't load static files, no files and prefix or suffix specified!");c.files||(c.files=[{prefix:c.prefix,suffix:c.suffix}]);for(var d=function(d){if(!d||!angular.isString(d.prefix)||!angular.isString(d.suffix))throw new Error("Couldn't load static file, no prefix or suffix specified!");return b(angular.extend({url:[d.prefix,c.key,d.suffix].join(""),method:"GET",params:""},c.$http)).then(function(a){return a.data},function(){return a.reject(c.key)})},e=[],f=c.files.length,g=0;f>g;g++)e.push(d({prefix:c.files[g].prefix,key:c.key,suffix:c.files[g].suffix}));return a.all(e).then(function(a){for(var b=a.length,c={},d=0;b>d;d++)for(var e in a[d])c[e]=a[d][e];return c})}}return angular.module("pascalprecht.translate").factory("$translateStaticFilesLoader",a),a.$inject=["$q","$http"],a.displayName="$translateStaticFilesLoader","pascalprecht.translate"}); \ No newline at end of file diff --git a/www/lib/ionic/js/angular/angular-translate.js b/www/lib/ionic/js/angular/angular-translate.js new file mode 100644 index 0000000000000000000000000000000000000000..b1ea61edbc5319550f52d9701e87e69809a944ca --- /dev/null +++ b/www/lib/ionic/js/angular/angular-translate.js @@ -0,0 +1,3263 @@ +/*! + * angular-translate - v2.10.0 - 2016-02-28 + * + * Copyright (c) 2016 The angular-translate team, Pascal Precht; Licensed MIT + */ +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module unless amdModuleId is set + define([], function () { + return (factory()); + }); + } else if (typeof exports === 'object') { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like environments that support module.exports, + // like Node. + module.exports = factory(); + } else { + factory(); + } +}(this, function () { + +/** + * @ngdoc overview + * @name pascalprecht.translate + * + * @description + * The main module which holds everything together. + */ +angular.module('pascalprecht.translate', ['ng']) + .run(runTranslate); + +function runTranslate($translate) { + + 'use strict'; + + var key = $translate.storageKey(), + storage = $translate.storage(); + + var fallbackFromIncorrectStorageValue = function () { + var preferred = $translate.preferredLanguage(); + if (angular.isString(preferred)) { + $translate.use(preferred); + // $translate.use() will also remember the language. + // So, we don't need to call storage.put() here. + } else { + storage.put(key, $translate.use()); + } + }; + + fallbackFromIncorrectStorageValue.displayName = 'fallbackFromIncorrectStorageValue'; + + if (storage) { + if (!storage.get(key)) { + fallbackFromIncorrectStorageValue(); + } else { + $translate.use(storage.get(key))['catch'](fallbackFromIncorrectStorageValue); + } + } else if (angular.isString($translate.preferredLanguage())) { + $translate.use($translate.preferredLanguage()); + } +} +runTranslate.$inject = ['$translate']; + +runTranslate.displayName = 'runTranslate'; + +/** + * @ngdoc object + * @name pascalprecht.translate.$translateSanitizationProvider + * + * @description + * + * Configurations for $translateSanitization + */ +angular.module('pascalprecht.translate').provider('$translateSanitization', $translateSanitizationProvider); + +function $translateSanitizationProvider () { + + 'use strict'; + + var $sanitize, + currentStrategy = null, // TODO change to either 'sanitize', 'escape' or ['sanitize', 'escapeParameters'] in 3.0. + hasConfiguredStrategy = false, + hasShownNoStrategyConfiguredWarning = false, + strategies; + + /** + * Definition of a sanitization strategy function + * @callback StrategyFunction + * @param {string|object} value - value to be sanitized (either a string or an interpolated value map) + * @param {string} mode - either 'text' for a string (translation) or 'params' for the interpolated params + * @return {string|object} + */ + + /** + * @ngdoc property + * @name strategies + * @propertyOf pascalprecht.translate.$translateSanitizationProvider + * + * @description + * Following strategies are built-in: + * <dl> + * <dt>sanitize</dt> + * <dd>Sanitizes HTML in the translation text using $sanitize</dd> + * <dt>escape</dt> + * <dd>Escapes HTML in the translation</dd> + * <dt>sanitizeParameters</dt> + * <dd>Sanitizes HTML in the values of the interpolation parameters using $sanitize</dd> + * <dt>escapeParameters</dt> + * <dd>Escapes HTML in the values of the interpolation parameters</dd> + * <dt>escaped</dt> + * <dd>Support legacy strategy name 'escaped' for backwards compatibility (will be removed in 3.0)</dd> + * </dl> + * + */ + + strategies = { + sanitize: function (value, mode) { + if (mode === 'text') { + value = htmlSanitizeValue(value); + } + return value; + }, + escape: function (value, mode) { + if (mode === 'text') { + value = htmlEscapeValue(value); + } + return value; + }, + sanitizeParameters: function (value, mode) { + if (mode === 'params') { + value = mapInterpolationParameters(value, htmlSanitizeValue); + } + return value; + }, + escapeParameters: function (value, mode) { + if (mode === 'params') { + value = mapInterpolationParameters(value, htmlEscapeValue); + } + return value; + } + }; + // Support legacy strategy name 'escaped' for backwards compatibility. + // TODO should be removed in 3.0 + strategies.escaped = strategies.escapeParameters; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateSanitizationProvider#addStrategy + * @methodOf pascalprecht.translate.$translateSanitizationProvider + * + * @description + * Adds a sanitization strategy to the list of known strategies. + * + * @param {string} strategyName - unique key for a strategy + * @param {StrategyFunction} strategyFunction - strategy function + * @returns {object} this + */ + this.addStrategy = function (strategyName, strategyFunction) { + strategies[strategyName] = strategyFunction; + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateSanitizationProvider#removeStrategy + * @methodOf pascalprecht.translate.$translateSanitizationProvider + * + * @description + * Removes a sanitization strategy from the list of known strategies. + * + * @param {string} strategyName - unique key for a strategy + * @returns {object} this + */ + this.removeStrategy = function (strategyName) { + delete strategies[strategyName]; + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateSanitizationProvider#useStrategy + * @methodOf pascalprecht.translate.$translateSanitizationProvider + * + * @description + * Selects a sanitization strategy. When an array is provided the strategies will be executed in order. + * + * @param {string|StrategyFunction|array} strategy The sanitization strategy / strategies which should be used. Either a name of an existing strategy, a custom strategy function, or an array consisting of multiple names and / or custom functions. + * @returns {object} this + */ + this.useStrategy = function (strategy) { + hasConfiguredStrategy = true; + currentStrategy = strategy; + return this; + }; + + /** + * @ngdoc object + * @name pascalprecht.translate.$translateSanitization + * @requires $injector + * @requires $log + * + * @description + * Sanitizes interpolation parameters and translated texts. + * + */ + this.$get = ['$injector', '$log', function ($injector, $log) { + + var cachedStrategyMap = {}; + + var applyStrategies = function (value, mode, selectedStrategies) { + angular.forEach(selectedStrategies, function (selectedStrategy) { + if (angular.isFunction(selectedStrategy)) { + value = selectedStrategy(value, mode); + } else if (angular.isFunction(strategies[selectedStrategy])) { + value = strategies[selectedStrategy](value, mode); + } else if (angular.isString(strategies[selectedStrategy])) { + if (!cachedStrategyMap[strategies[selectedStrategy]]) { + try { + cachedStrategyMap[strategies[selectedStrategy]] = $injector.get(strategies[selectedStrategy]); + } catch (e) { + cachedStrategyMap[strategies[selectedStrategy]] = function() {}; + throw new Error('pascalprecht.translate.$translateSanitization: Unknown sanitization strategy: \'' + selectedStrategy + '\''); + } + } + value = cachedStrategyMap[strategies[selectedStrategy]](value, mode); + } else { + throw new Error('pascalprecht.translate.$translateSanitization: Unknown sanitization strategy: \'' + selectedStrategy + '\''); + } + }); + return value; + }; + + // TODO: should be removed in 3.0 + var showNoStrategyConfiguredWarning = function () { + if (!hasConfiguredStrategy && !hasShownNoStrategyConfiguredWarning) { + $log.warn('pascalprecht.translate.$translateSanitization: No sanitization strategy has been configured. This can have serious security implications. See http://angular-translate.github.io/docs/#/guide/19_security for details.'); + hasShownNoStrategyConfiguredWarning = true; + } + }; + + if ($injector.has('$sanitize')) { + $sanitize = $injector.get('$sanitize'); + } + + return { + /** + * @ngdoc function + * @name pascalprecht.translate.$translateSanitization#useStrategy + * @methodOf pascalprecht.translate.$translateSanitization + * + * @description + * Selects a sanitization strategy. When an array is provided the strategies will be executed in order. + * + * @param {string|StrategyFunction|array} strategy The sanitization strategy / strategies which should be used. Either a name of an existing strategy, a custom strategy function, or an array consisting of multiple names and / or custom functions. + */ + useStrategy: (function (self) { + return function (strategy) { + self.useStrategy(strategy); + }; + })(this), + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateSanitization#sanitize + * @methodOf pascalprecht.translate.$translateSanitization + * + * @description + * Sanitizes a value. + * + * @param {string|object} value The value which should be sanitized. + * @param {string} mode The current sanitization mode, either 'params' or 'text'. + * @param {string|StrategyFunction|array} [strategy] Optional custom strategy which should be used instead of the currently selected strategy. + * @returns {string|object} sanitized value + */ + sanitize: function (value, mode, strategy) { + if (!currentStrategy) { + showNoStrategyConfiguredWarning(); + } + + if (arguments.length < 3) { + strategy = currentStrategy; + } + + if (!strategy) { + return value; + } + + var selectedStrategies = angular.isArray(strategy) ? strategy : [strategy]; + return applyStrategies(value, mode, selectedStrategies); + } + }; + }]; + + var htmlEscapeValue = function (value) { + var element = angular.element('<div></div>'); + element.text(value); // not chainable, see #1044 + return element.html(); + }; + + var htmlSanitizeValue = function (value) { + if (!$sanitize) { + throw new Error('pascalprecht.translate.$translateSanitization: Error cannot find $sanitize service. Either include the ngSanitize module (https://docs.angularjs.org/api/ngSanitize) or use a sanitization strategy which does not depend on $sanitize, such as \'escape\'.'); + } + return $sanitize(value); + }; + + var mapInterpolationParameters = function (value, iteratee, stack) { + if (angular.isObject(value)) { + var result = angular.isArray(value) ? [] : {}; + + if (!stack) { + stack = []; + } else { + if (stack.indexOf(value) > -1) { + throw new Error('pascalprecht.translate.$translateSanitization: Error cannot interpolate parameter due recursive object'); + } + } + + stack.push(value); + angular.forEach(value, function (propertyValue, propertyKey) { + result[propertyKey] = mapInterpolationParameters(propertyValue, iteratee, stack); + }); + stack.splice(-1, 1); // remove last + + return result; + } else if (angular.isNumber(value)) { + return value; + } else { + return iteratee(value); + } + }; +} + +/** + * @ngdoc object + * @name pascalprecht.translate.$translateProvider + * @description + * + * $translateProvider allows developers to register translation-tables, asynchronous loaders + * and similar to configure translation behavior directly inside of a module. + * + */ +angular.module('pascalprecht.translate') +.constant('pascalprechtTranslateOverrider', {}) +.provider('$translate', $translate); + +function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvider, pascalprechtTranslateOverrider) { + + 'use strict'; + + var $translationTable = {}, + $preferredLanguage, + $availableLanguageKeys = [], + $languageKeyAliases, + $fallbackLanguage, + $fallbackWasString, + $uses, + $nextLang, + $storageFactory, + $storageKey = $STORAGE_KEY, + $storagePrefix, + $missingTranslationHandlerFactory, + $interpolationFactory, + $interpolatorFactories = [], + $loaderFactory, + $cloakClassName = 'translate-cloak', + $loaderOptions, + $notFoundIndicatorLeft, + $notFoundIndicatorRight, + $postCompilingEnabled = false, + $forceAsyncReloadEnabled = false, + $nestedObjectDelimeter = '.', + $isReady = false, + loaderCache, + directivePriority = 0, + statefulFilter = true, + uniformLanguageTagResolver = 'default', + languageTagResolver = { + 'default': function (tag) { + return (tag || '').split('-').join('_'); + }, + java: function (tag) { + var temp = (tag || '').split('-').join('_'); + var parts = temp.split('_'); + return parts.length > 1 ? (parts[0].toLowerCase() + '_' + parts[1].toUpperCase()) : temp; + }, + bcp47: function (tag) { + var temp = (tag || '').split('_').join('-'); + var parts = temp.split('-'); + return parts.length > 1 ? (parts[0].toLowerCase() + '-' + parts[1].toUpperCase()) : temp; + } + }; + + var version = '2.10.0'; + + // tries to determine the browsers language + var getFirstBrowserLanguage = function () { + + // internal purpose only + if (angular.isFunction(pascalprechtTranslateOverrider.getLocale)) { + return pascalprechtTranslateOverrider.getLocale(); + } + + var nav = $windowProvider.$get().navigator, + browserLanguagePropertyKeys = ['language', 'browserLanguage', 'systemLanguage', 'userLanguage'], + i, + language; + + // support for HTML 5.1 "navigator.languages" + if (angular.isArray(nav.languages)) { + for (i = 0; i < nav.languages.length; i++) { + language = nav.languages[i]; + if (language && language.length) { + return language; + } + } + } + + // support for other well known properties in browsers + for (i = 0; i < browserLanguagePropertyKeys.length; i++) { + language = nav[browserLanguagePropertyKeys[i]]; + if (language && language.length) { + return language; + } + } + + return null; + }; + getFirstBrowserLanguage.displayName = 'angular-translate/service: getFirstBrowserLanguage'; + + // tries to determine the browsers locale + var getLocale = function () { + var locale = getFirstBrowserLanguage() || ''; + if (languageTagResolver[uniformLanguageTagResolver]) { + locale = languageTagResolver[uniformLanguageTagResolver](locale); + } + return locale; + }; + getLocale.displayName = 'angular-translate/service: getLocale'; + + /** + * @name indexOf + * @private + * + * @description + * indexOf polyfill. Kinda sorta. + * + * @param {array} array Array to search in. + * @param {string} searchElement Element to search for. + * + * @returns {int} Index of search element. + */ + var indexOf = function(array, searchElement) { + for (var i = 0, len = array.length; i < len; i++) { + if (array[i] === searchElement) { + return i; + } + } + return -1; + }; + + /** + * @name trim + * @private + * + * @description + * trim polyfill + * + * @returns {string} The string stripped of whitespace from both ends + */ + var trim = function() { + return this.toString().replace(/^\s+|\s+$/g, ''); + }; + + var negotiateLocale = function (preferred) { + if(!preferred) { + return; + } + + var avail = [], + locale = angular.lowercase(preferred), + i = 0, + n = $availableLanguageKeys.length; + + for (; i < n; i++) { + avail.push(angular.lowercase($availableLanguageKeys[i])); + } + + // Check for an exact match in our list of available keys + if (indexOf(avail, locale) > -1) { + return preferred; + } + + if ($languageKeyAliases) { + var alias; + for (var langKeyAlias in $languageKeyAliases) { + var hasWildcardKey = false; + var hasExactKey = Object.prototype.hasOwnProperty.call($languageKeyAliases, langKeyAlias) && + angular.lowercase(langKeyAlias) === angular.lowercase(preferred); + + if (langKeyAlias.slice(-1) === '*') { + hasWildcardKey = langKeyAlias.slice(0, -1) === preferred.slice(0, langKeyAlias.length-1); + } + if (hasExactKey || hasWildcardKey) { + alias = $languageKeyAliases[langKeyAlias]; + if (indexOf(avail, angular.lowercase(alias)) > -1) { + return alias; + } + } + } + } + + // Check for a language code without region + var parts = preferred.split('_'); + + if (parts.length > 1 && indexOf(avail, angular.lowercase(parts[0])) > -1) { + return parts[0]; + } + + // If everything fails, return undefined. + return; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#translations + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Registers a new translation table for specific language key. + * + * To register a translation table for specific language, pass a defined language + * key as first parameter. + * + * <pre> + * // register translation table for language: 'de_DE' + * $translateProvider.translations('de_DE', { + * 'GREETING': 'Hallo Welt!' + * }); + * + * // register another one + * $translateProvider.translations('en_US', { + * 'GREETING': 'Hello world!' + * }); + * </pre> + * + * When registering multiple translation tables for for the same language key, + * the actual translation table gets extended. This allows you to define module + * specific translation which only get added, once a specific module is loaded in + * your app. + * + * Invoking this method with no arguments returns the translation table which was + * registered with no language key. Invoking it with a language key returns the + * related translation table. + * + * @param {string} key A language key. + * @param {object} translationTable A plain old JavaScript object that represents a translation table. + * + */ + var translations = function (langKey, translationTable) { + + if (!langKey && !translationTable) { + return $translationTable; + } + + if (langKey && !translationTable) { + if (angular.isString(langKey)) { + return $translationTable[langKey]; + } + } else { + if (!angular.isObject($translationTable[langKey])) { + $translationTable[langKey] = {}; + } + angular.extend($translationTable[langKey], flatObject(translationTable)); + } + return this; + }; + + this.translations = translations; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#cloakClassName + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * + * Let's you change the class name for `translate-cloak` directive. + * Default class name is `translate-cloak`. + * + * @param {string} name translate-cloak class name + */ + this.cloakClassName = function (name) { + if (!name) { + return $cloakClassName; + } + $cloakClassName = name; + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#nestedObjectDelimeter + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * + * Let's you change the delimiter for namespaced translations. + * Default delimiter is `.`. + * + * @param {string} delimiter namespace separator + */ + this.nestedObjectDelimeter = function (delimiter) { + if (!delimiter) { + return $nestedObjectDelimeter; + } + $nestedObjectDelimeter = delimiter; + return this; + }; + + /** + * @name flatObject + * @private + * + * @description + * Flats an object. This function is used to flatten given translation data with + * namespaces, so they are later accessible via dot notation. + */ + var flatObject = function (data, path, result, prevKey) { + var key, keyWithPath, keyWithShortPath, val; + + if (!path) { + path = []; + } + if (!result) { + result = {}; + } + for (key in data) { + if (!Object.prototype.hasOwnProperty.call(data, key)) { + continue; + } + val = data[key]; + if (angular.isObject(val)) { + flatObject(val, path.concat(key), result, key); + } else { + keyWithPath = path.length ? ('' + path.join($nestedObjectDelimeter) + $nestedObjectDelimeter + key) : key; + if(path.length && key === prevKey){ + // Create shortcut path (foo.bar == foo.bar.bar) + keyWithShortPath = '' + path.join($nestedObjectDelimeter); + // Link it to original path + result[keyWithShortPath] = '@:' + keyWithPath; + } + result[keyWithPath] = val; + } + } + return result; + }; + flatObject.displayName = 'flatObject'; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#addInterpolation + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Adds interpolation services to angular-translate, so it can manage them. + * + * @param {object} factory Interpolation service factory + */ + this.addInterpolation = function (factory) { + $interpolatorFactories.push(factory); + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#useMessageFormatInterpolation + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Tells angular-translate to use interpolation functionality of messageformat.js. + * This is useful when having high level pluralization and gender selection. + */ + this.useMessageFormatInterpolation = function () { + return this.useInterpolation('$translateMessageFormatInterpolation'); + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#useInterpolation + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Tells angular-translate which interpolation style to use as default, application-wide. + * Simply pass a factory/service name. The interpolation service has to implement + * the correct interface. + * + * @param {string} factory Interpolation service name. + */ + this.useInterpolation = function (factory) { + $interpolationFactory = factory; + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#useSanitizeStrategy + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Simply sets a sanitation strategy type. + * + * @param {string} value Strategy type. + */ + this.useSanitizeValueStrategy = function (value) { + $translateSanitizationProvider.useStrategy(value); + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#preferredLanguage + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Tells the module which of the registered translation tables to use for translation + * at initial startup by passing a language key. Similar to `$translateProvider#use` + * only that it says which language to **prefer**. + * + * @param {string} langKey A language key. + */ + this.preferredLanguage = function(langKey) { + if (langKey) { + setupPreferredLanguage(langKey); + return this; + } + return $preferredLanguage; + }; + var setupPreferredLanguage = function (langKey) { + if (langKey) { + $preferredLanguage = langKey; + } + return $preferredLanguage; + }; + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#translationNotFoundIndicator + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Sets an indicator which is used when a translation isn't found. E.g. when + * setting the indicator as 'X' and one tries to translate a translation id + * called `NOT_FOUND`, this will result in `X NOT_FOUND X`. + * + * Internally this methods sets a left indicator and a right indicator using + * `$translateProvider.translationNotFoundIndicatorLeft()` and + * `$translateProvider.translationNotFoundIndicatorRight()`. + * + * **Note**: These methods automatically add a whitespace between the indicators + * and the translation id. + * + * @param {string} indicator An indicator, could be any string. + */ + this.translationNotFoundIndicator = function (indicator) { + this.translationNotFoundIndicatorLeft(indicator); + this.translationNotFoundIndicatorRight(indicator); + return this; + }; + + /** + * ngdoc function + * @name pascalprecht.translate.$translateProvider#translationNotFoundIndicatorLeft + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Sets an indicator which is used when a translation isn't found left to the + * translation id. + * + * @param {string} indicator An indicator. + */ + this.translationNotFoundIndicatorLeft = function (indicator) { + if (!indicator) { + return $notFoundIndicatorLeft; + } + $notFoundIndicatorLeft = indicator; + return this; + }; + + /** + * ngdoc function + * @name pascalprecht.translate.$translateProvider#translationNotFoundIndicatorLeft + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Sets an indicator which is used when a translation isn't found right to the + * translation id. + * + * @param {string} indicator An indicator. + */ + this.translationNotFoundIndicatorRight = function (indicator) { + if (!indicator) { + return $notFoundIndicatorRight; + } + $notFoundIndicatorRight = indicator; + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#fallbackLanguage + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Tells the module which of the registered translation tables to use when missing translations + * at initial startup by passing a language key. Similar to `$translateProvider#use` + * only that it says which language to **fallback**. + * + * @param {string||array} langKey A language key. + * + */ + this.fallbackLanguage = function (langKey) { + fallbackStack(langKey); + return this; + }; + + var fallbackStack = function (langKey) { + if (langKey) { + if (angular.isString(langKey)) { + $fallbackWasString = true; + $fallbackLanguage = [ langKey ]; + } else if (angular.isArray(langKey)) { + $fallbackWasString = false; + $fallbackLanguage = langKey; + } + if (angular.isString($preferredLanguage) && indexOf($fallbackLanguage, $preferredLanguage) < 0) { + $fallbackLanguage.push($preferredLanguage); + } + + return this; + } else { + if ($fallbackWasString) { + return $fallbackLanguage[0]; + } else { + return $fallbackLanguage; + } + } + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#use + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Set which translation table to use for translation by given language key. When + * trying to 'use' a language which isn't provided, it'll throw an error. + * + * You actually don't have to use this method since `$translateProvider#preferredLanguage` + * does the job too. + * + * @param {string} langKey A language key. + */ + this.use = function (langKey) { + if (langKey) { + if (!$translationTable[langKey] && (!$loaderFactory)) { + // only throw an error, when not loading translation data asynchronously + throw new Error('$translateProvider couldn\'t find translationTable for langKey: \'' + langKey + '\''); + } + $uses = langKey; + return this; + } + return $uses; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#storageKey + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Tells the module which key must represent the choosed language by a user in the storage. + * + * @param {string} key A key for the storage. + */ + var storageKey = function(key) { + if (!key) { + if ($storagePrefix) { + return $storagePrefix + $storageKey; + } + return $storageKey; + } + $storageKey = key; + return this; + }; + + this.storageKey = storageKey; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#useUrlLoader + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Tells angular-translate to use `$translateUrlLoader` extension service as loader. + * + * @param {string} url Url + * @param {Object=} options Optional configuration object + */ + this.useUrlLoader = function (url, options) { + return this.useLoader('$translateUrlLoader', angular.extend({ url: url }, options)); + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#useStaticFilesLoader + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Tells angular-translate to use `$translateStaticFilesLoader` extension service as loader. + * + * @param {Object=} options Optional configuration object + */ + this.useStaticFilesLoader = function (options) { + return this.useLoader('$translateStaticFilesLoader', options); + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#useLoader + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Tells angular-translate to use any other service as loader. + * + * @param {string} loaderFactory Factory name to use + * @param {Object=} options Optional configuration object + */ + this.useLoader = function (loaderFactory, options) { + $loaderFactory = loaderFactory; + $loaderOptions = options || {}; + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#useLocalStorage + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Tells angular-translate to use `$translateLocalStorage` service as storage layer. + * + */ + this.useLocalStorage = function () { + return this.useStorage('$translateLocalStorage'); + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#useCookieStorage + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Tells angular-translate to use `$translateCookieStorage` service as storage layer. + */ + this.useCookieStorage = function () { + return this.useStorage('$translateCookieStorage'); + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#useStorage + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Tells angular-translate to use custom service as storage layer. + */ + this.useStorage = function (storageFactory) { + $storageFactory = storageFactory; + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#storagePrefix + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Sets prefix for storage key. + * + * @param {string} prefix Storage key prefix + */ + this.storagePrefix = function (prefix) { + if (!prefix) { + return prefix; + } + $storagePrefix = prefix; + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#useMissingTranslationHandlerLog + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Tells angular-translate to use built-in log handler when trying to translate + * a translation Id which doesn't exist. + * + * This is actually a shortcut method for `useMissingTranslationHandler()`. + * + */ + this.useMissingTranslationHandlerLog = function () { + return this.useMissingTranslationHandler('$translateMissingTranslationHandlerLog'); + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#useMissingTranslationHandler + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Expects a factory name which later gets instantiated with `$injector`. + * This method can be used to tell angular-translate to use a custom + * missingTranslationHandler. Just build a factory which returns a function + * and expects a translation id as argument. + * + * Example: + * <pre> + * app.config(function ($translateProvider) { + * $translateProvider.useMissingTranslationHandler('customHandler'); + * }); + * + * app.factory('customHandler', function (dep1, dep2) { + * return function (translationId) { + * // something with translationId and dep1 and dep2 + * }; + * }); + * </pre> + * + * @param {string} factory Factory name + */ + this.useMissingTranslationHandler = function (factory) { + $missingTranslationHandlerFactory = factory; + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#usePostCompiling + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * If post compiling is enabled, all translated values will be processed + * again with AngularJS' $compile. + * + * Example: + * <pre> + * app.config(function ($translateProvider) { + * $translateProvider.usePostCompiling(true); + * }); + * </pre> + * + * @param {string} factory Factory name + */ + this.usePostCompiling = function (value) { + $postCompilingEnabled = !(!value); + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#forceAsyncReload + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * If force async reload is enabled, async loader will always be called + * even if $translationTable already contains the language key, adding + * possible new entries to the $translationTable. + * + * Example: + * <pre> + * app.config(function ($translateProvider) { + * $translateProvider.forceAsyncReload(true); + * }); + * </pre> + * + * @param {boolean} value - valid values are true or false + */ + this.forceAsyncReload = function (value) { + $forceAsyncReloadEnabled = !(!value); + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#uniformLanguageTag + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Tells angular-translate which language tag should be used as a result when determining + * the current browser language. + * + * This setting must be set before invoking {@link pascalprecht.translate.$translateProvider#methods_determinePreferredLanguage determinePreferredLanguage()}. + * + * <pre> + * $translateProvider + * .uniformLanguageTag('bcp47') + * .determinePreferredLanguage() + * </pre> + * + * The resolver currently supports: + * * default + * (traditionally: hyphens will be converted into underscores, i.e. en-US => en_US) + * en-US => en_US + * en_US => en_US + * en-us => en_us + * * java + * like default, but the second part will be always in uppercase + * en-US => en_US + * en_US => en_US + * en-us => en_US + * * BCP 47 (RFC 4646 & 4647) + * en-US => en-US + * en_US => en-US + * en-us => en-US + * + * See also: + * * http://en.wikipedia.org/wiki/IETF_language_tag + * * http://www.w3.org/International/core/langtags/ + * * http://tools.ietf.org/html/bcp47 + * + * @param {string|object} options - options (or standard) + * @param {string} options.standard - valid values are 'default', 'bcp47', 'java' + */ + this.uniformLanguageTag = function (options) { + + if (!options) { + options = {}; + } else if (angular.isString(options)) { + options = { + standard: options + }; + } + + uniformLanguageTagResolver = options.standard; + + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#determinePreferredLanguage + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Tells angular-translate to try to determine on its own which language key + * to set as preferred language. When `fn` is given, angular-translate uses it + * to determine a language key, otherwise it uses the built-in `getLocale()` + * method. + * + * The `getLocale()` returns a language key in the format `[lang]_[country]` or + * `[lang]` depending on what the browser provides. + * + * Use this method at your own risk, since not all browsers return a valid + * locale (see {@link pascalprecht.translate.$translateProvider#methods_uniformLanguageTag uniformLanguageTag()}). + * + * @param {Function=} fn Function to determine a browser's locale + */ + this.determinePreferredLanguage = function (fn) { + + var locale = (fn && angular.isFunction(fn)) ? fn() : getLocale(); + + if (!$availableLanguageKeys.length) { + $preferredLanguage = locale; + } else { + $preferredLanguage = negotiateLocale(locale) || locale; + } + + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#registerAvailableLanguageKeys + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Registers a set of language keys the app will work with. Use this method in + * combination with + * {@link pascalprecht.translate.$translateProvider#determinePreferredLanguage determinePreferredLanguage}. + * When available languages keys are registered, angular-translate + * tries to find the best fitting language key depending on the browsers locale, + * considering your language key convention. + * + * @param {object} languageKeys Array of language keys the your app will use + * @param {object=} aliases Alias map. + */ + this.registerAvailableLanguageKeys = function (languageKeys, aliases) { + if (languageKeys) { + $availableLanguageKeys = languageKeys; + if (aliases) { + $languageKeyAliases = aliases; + } + return this; + } + return $availableLanguageKeys; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#useLoaderCache + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Registers a cache for internal $http based loaders. + * {@link pascalprecht.translate.$translationCache $translationCache}. + * When false the cache will be disabled (default). When true or undefined + * the cache will be a default (see $cacheFactory). When an object it will + * be treat as a cache object itself: the usage is $http({cache: cache}) + * + * @param {object} cache boolean, string or cache-object + */ + this.useLoaderCache = function (cache) { + if (cache === false) { + // disable cache + loaderCache = undefined; + } else if (cache === true) { + // enable cache using AJS defaults + loaderCache = true; + } else if (typeof(cache) === 'undefined') { + // enable cache using default + loaderCache = '$translationCache'; + } else if (cache) { + // enable cache using given one (see $cacheFactory) + loaderCache = cache; + } + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#directivePriority + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Sets the default priority of the translate directive. The standard value is `0`. + * Calling this function without an argument will return the current value. + * + * @param {number} priority for the translate-directive + */ + this.directivePriority = function (priority) { + if (priority === undefined) { + // getter + return directivePriority; + } else { + // setter with chaining + directivePriority = priority; + return this; + } + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#statefulFilter + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Since AngularJS 1.3, filters which are not stateless (depending at the scope) + * have to explicit define this behavior. + * Sets whether the translate filter should be stateful or stateless. The standard value is `true` + * meaning being stateful. + * Calling this function without an argument will return the current value. + * + * @param {boolean} state - defines the state of the filter + */ + this.statefulFilter = function (state) { + if (state === undefined) { + // getter + return statefulFilter; + } else { + // setter with chaining + statefulFilter = state; + return this; + } + }; + + /** + * @ngdoc object + * @name pascalprecht.translate.$translate + * @requires $interpolate + * @requires $log + * @requires $rootScope + * @requires $q + * + * @description + * The `$translate` service is the actual core of angular-translate. It expects a translation id + * and optional interpolate parameters to translate contents. + * + * <pre> + * $translate('HEADLINE_TEXT').then(function (translation) { + * $scope.translatedText = translation; + * }); + * </pre> + * + * @param {string|array} translationId A token which represents a translation id + * This can be optionally an array of translation ids which + * results that the function returns an object where each key + * is the translation id and the value the translation. + * @param {object=} interpolateParams An object hash for dynamic values + * @param {string} interpolationId The id of the interpolation to use + * @param {string} forceLanguage A language to be used instead of the current language + * @returns {object} promise + */ + this.$get = [ + '$log', + '$injector', + '$rootScope', + '$q', + function ($log, $injector, $rootScope, $q) { + + var Storage, + defaultInterpolator = $injector.get($interpolationFactory || '$translateDefaultInterpolation'), + pendingLoader = false, + interpolatorHashMap = {}, + langPromises = {}, + fallbackIndex, + startFallbackIteration; + + var $translate = function (translationId, interpolateParams, interpolationId, defaultTranslationText, forceLanguage) { + + var uses = (forceLanguage && forceLanguage !== $uses) ? // we don't want to re-negotiate $uses + (negotiateLocale(forceLanguage) || forceLanguage) : $uses; + + // Duck detection: If the first argument is an array, a bunch of translations was requested. + // The result is an object. + if (angular.isArray(translationId)) { + // Inspired by Q.allSettled by Kris Kowal + // https://github.com/kriskowal/q/blob/b0fa72980717dc202ffc3cbf03b936e10ebbb9d7/q.js#L1553-1563 + // This transforms all promises regardless resolved or rejected + var translateAll = function (translationIds) { + var results = {}; // storing the actual results + var promises = []; // promises to wait for + // Wraps the promise a) being always resolved and b) storing the link id->value + var translate = function (translationId) { + var deferred = $q.defer(); + var regardless = function (value) { + results[translationId] = value; + deferred.resolve([translationId, value]); + }; + // we don't care whether the promise was resolved or rejected; just store the values + $translate(translationId, interpolateParams, interpolationId, defaultTranslationText, forceLanguage).then(regardless, regardless); + return deferred.promise; + }; + for (var i = 0, c = translationIds.length; i < c; i++) { + promises.push(translate(translationIds[i])); + } + // wait for all (including storing to results) + return $q.all(promises).then(function () { + // return the results + return results; + }); + }; + return translateAll(translationId); + } + + var deferred = $q.defer(); + + // trim off any whitespace + if (translationId) { + translationId = trim.apply(translationId); + } + + var promiseToWaitFor = (function () { + var promise = $preferredLanguage ? + langPromises[$preferredLanguage] : + langPromises[uses]; + + fallbackIndex = 0; + + if ($storageFactory && !promise) { + // looks like there's no pending promise for $preferredLanguage or + // $uses. Maybe there's one pending for a language that comes from + // storage. + var langKey = Storage.get($storageKey); + promise = langPromises[langKey]; + + if ($fallbackLanguage && $fallbackLanguage.length) { + var index = indexOf($fallbackLanguage, langKey); + // maybe the language from storage is also defined as fallback language + // we increase the fallback language index to not search in that language + // as fallback, since it's probably the first used language + // in that case the index starts after the first element + fallbackIndex = (index === 0) ? 1 : 0; + + // but we can make sure to ALWAYS fallback to preferred language at least + if (indexOf($fallbackLanguage, $preferredLanguage) < 0) { + $fallbackLanguage.push($preferredLanguage); + } + } + } + return promise; + }()); + + if (!promiseToWaitFor) { + // no promise to wait for? okay. Then there's no loader registered + // nor is a one pending for language that comes from storage. + // We can just translate. + determineTranslation(translationId, interpolateParams, interpolationId, defaultTranslationText, uses).then(deferred.resolve, deferred.reject); + } else { + var promiseResolved = function () { + // $uses may have changed while waiting + if (!forceLanguage) { + uses = $uses; + } + determineTranslation(translationId, interpolateParams, interpolationId, defaultTranslationText, uses).then(deferred.resolve, deferred.reject); + }; + promiseResolved.displayName = 'promiseResolved'; + + promiseToWaitFor['finally'](promiseResolved); + } + return deferred.promise; + }; + + /** + * @name applyNotFoundIndicators + * @private + * + * @description + * Applies not fount indicators to given translation id, if needed. + * This function gets only executed, if a translation id doesn't exist, + * which is why a translation id is expected as argument. + * + * @param {string} translationId Translation id. + * @returns {string} Same as given translation id but applied with not found + * indicators. + */ + var applyNotFoundIndicators = function (translationId) { + // applying notFoundIndicators + if ($notFoundIndicatorLeft) { + translationId = [$notFoundIndicatorLeft, translationId].join(' '); + } + if ($notFoundIndicatorRight) { + translationId = [translationId, $notFoundIndicatorRight].join(' '); + } + return translationId; + }; + + /** + * @name useLanguage + * @private + * + * @description + * Makes actual use of a language by setting a given language key as used + * language and informs registered interpolators to also use the given + * key as locale. + * + * @param {key} Locale key. + */ + var useLanguage = function (key) { + $uses = key; + + // make sure to store new language key before triggering success event + if ($storageFactory) { + Storage.put($translate.storageKey(), $uses); + } + + $rootScope.$emit('$translateChangeSuccess', {language: key}); + + // inform default interpolator + defaultInterpolator.setLocale($uses); + + var eachInterpolator = function (interpolator, id) { + interpolatorHashMap[id].setLocale($uses); + }; + eachInterpolator.displayName = 'eachInterpolatorLocaleSetter'; + + // inform all others too! + angular.forEach(interpolatorHashMap, eachInterpolator); + $rootScope.$emit('$translateChangeEnd', {language: key}); + }; + + /** + * @name loadAsync + * @private + * + * @description + * Kicks of registered async loader using `$injector` and applies existing + * loader options. When resolved, it updates translation tables accordingly + * or rejects with given language key. + * + * @param {string} key Language key. + * @return {Promise} A promise. + */ + var loadAsync = function (key) { + if (!key) { + throw 'No language key specified for loading.'; + } + + var deferred = $q.defer(); + + $rootScope.$emit('$translateLoadingStart', {language: key}); + pendingLoader = true; + + var cache = loaderCache; + if (typeof(cache) === 'string') { + // getting on-demand instance of loader + cache = $injector.get(cache); + } + + var loaderOptions = angular.extend({}, $loaderOptions, { + key: key, + $http: angular.extend({}, { + cache: cache + }, $loaderOptions.$http) + }); + + var onLoaderSuccess = function (data) { + var translationTable = {}; + $rootScope.$emit('$translateLoadingSuccess', {language: key}); + + if (angular.isArray(data)) { + angular.forEach(data, function (table) { + angular.extend(translationTable, flatObject(table)); + }); + } else { + angular.extend(translationTable, flatObject(data)); + } + pendingLoader = false; + deferred.resolve({ + key: key, + table: translationTable + }); + $rootScope.$emit('$translateLoadingEnd', {language: key}); + }; + onLoaderSuccess.displayName = 'onLoaderSuccess'; + + var onLoaderError = function (key) { + $rootScope.$emit('$translateLoadingError', {language: key}); + deferred.reject(key); + $rootScope.$emit('$translateLoadingEnd', {language: key}); + }; + onLoaderError.displayName = 'onLoaderError'; + + $injector.get($loaderFactory)(loaderOptions) + .then(onLoaderSuccess, onLoaderError); + + return deferred.promise; + }; + + if ($storageFactory) { + Storage = $injector.get($storageFactory); + + if (!Storage.get || !Storage.put) { + throw new Error('Couldn\'t use storage \'' + $storageFactory + '\', missing get() or put() method!'); + } + } + + // if we have additional interpolations that were added via + // $translateProvider.addInterpolation(), we have to map'em + if ($interpolatorFactories.length) { + var eachInterpolationFactory = function (interpolatorFactory) { + var interpolator = $injector.get(interpolatorFactory); + // setting initial locale for each interpolation service + interpolator.setLocale($preferredLanguage || $uses); + // make'em recognizable through id + interpolatorHashMap[interpolator.getInterpolationIdentifier()] = interpolator; + }; + eachInterpolationFactory.displayName = 'interpolationFactoryAdder'; + + angular.forEach($interpolatorFactories, eachInterpolationFactory); + } + + /** + * @name getTranslationTable + * @private + * + * @description + * Returns a promise that resolves to the translation table + * or is rejected if an error occurred. + * + * @param langKey + * @returns {Q.promise} + */ + var getTranslationTable = function (langKey) { + var deferred = $q.defer(); + if (Object.prototype.hasOwnProperty.call($translationTable, langKey)) { + deferred.resolve($translationTable[langKey]); + } else if (langPromises[langKey]) { + var onResolve = function (data) { + translations(data.key, data.table); + deferred.resolve(data.table); + }; + onResolve.displayName = 'translationTableResolver'; + langPromises[langKey].then(onResolve, deferred.reject); + } else { + deferred.reject(); + } + return deferred.promise; + }; + + /** + * @name getFallbackTranslation + * @private + * + * @description + * Returns a promise that will resolve to the translation + * or be rejected if no translation was found for the language. + * This function is currently only used for fallback language translation. + * + * @param langKey The language to translate to. + * @param translationId + * @param interpolateParams + * @param Interpolator + * @returns {Q.promise} + */ + var getFallbackTranslation = function (langKey, translationId, interpolateParams, Interpolator) { + var deferred = $q.defer(); + + var onResolve = function (translationTable) { + if (Object.prototype.hasOwnProperty.call(translationTable, translationId)) { + Interpolator.setLocale(langKey); + var translation = translationTable[translationId]; + if (translation.substr(0, 2) === '@:') { + getFallbackTranslation(langKey, translation.substr(2), interpolateParams, Interpolator) + .then(deferred.resolve, deferred.reject); + } else { + deferred.resolve(Interpolator.interpolate(translationTable[translationId], interpolateParams)); + } + Interpolator.setLocale($uses); + } else { + deferred.reject(); + } + }; + onResolve.displayName = 'fallbackTranslationResolver'; + + getTranslationTable(langKey).then(onResolve, deferred.reject); + + return deferred.promise; + }; + + /** + * @name getFallbackTranslationInstant + * @private + * + * @description + * Returns a translation + * This function is currently only used for fallback language translation. + * + * @param langKey The language to translate to. + * @param translationId + * @param interpolateParams + * @param Interpolator + * @returns {string} translation + */ + var getFallbackTranslationInstant = function (langKey, translationId, interpolateParams, Interpolator) { + var result, translationTable = $translationTable[langKey]; + + if (translationTable && Object.prototype.hasOwnProperty.call(translationTable, translationId)) { + Interpolator.setLocale(langKey); + result = Interpolator.interpolate(translationTable[translationId], interpolateParams); + if (result.substr(0, 2) === '@:') { + return getFallbackTranslationInstant(langKey, result.substr(2), interpolateParams, Interpolator); + } + Interpolator.setLocale($uses); + } + + return result; + }; + + + /** + * @name translateByHandler + * @private + * + * Translate by missing translation handler. + * + * @param translationId + * @returns translation created by $missingTranslationHandler or translationId is $missingTranslationHandler is + * absent + */ + var translateByHandler = function (translationId, interpolateParams) { + // If we have a handler factory - we might also call it here to determine if it provides + // a default text for a translationid that can't be found anywhere in our tables + if ($missingTranslationHandlerFactory) { + var resultString = $injector.get($missingTranslationHandlerFactory)(translationId, $uses, interpolateParams); + if (resultString !== undefined) { + return resultString; + } else { + return translationId; + } + } else { + return translationId; + } + }; + + /** + * @name resolveForFallbackLanguage + * @private + * + * Recursive helper function for fallbackTranslation that will sequentially look + * for a translation in the fallbackLanguages starting with fallbackLanguageIndex. + * + * @param fallbackLanguageIndex + * @param translationId + * @param interpolateParams + * @param Interpolator + * @returns {Q.promise} Promise that will resolve to the translation. + */ + var resolveForFallbackLanguage = function (fallbackLanguageIndex, translationId, interpolateParams, Interpolator, defaultTranslationText) { + var deferred = $q.defer(); + + if (fallbackLanguageIndex < $fallbackLanguage.length) { + var langKey = $fallbackLanguage[fallbackLanguageIndex]; + getFallbackTranslation(langKey, translationId, interpolateParams, Interpolator).then( + deferred.resolve, + function () { + // Look in the next fallback language for a translation. + // It delays the resolving by passing another promise to resolve. + resolveForFallbackLanguage(fallbackLanguageIndex + 1, translationId, interpolateParams, Interpolator, defaultTranslationText).then(deferred.resolve); + } + ); + } else { + // No translation found in any fallback language + // if a default translation text is set in the directive, then return this as a result + if (defaultTranslationText) { + deferred.resolve(defaultTranslationText); + } else { + // if no default translation is set and an error handler is defined, send it to the handler + // and then return the result + deferred.resolve(translateByHandler(translationId, interpolateParams)); + } + } + return deferred.promise; + }; + + /** + * @name resolveForFallbackLanguageInstant + * @private + * + * Recursive helper function for fallbackTranslation that will sequentially look + * for a translation in the fallbackLanguages starting with fallbackLanguageIndex. + * + * @param fallbackLanguageIndex + * @param translationId + * @param interpolateParams + * @param Interpolator + * @returns {string} translation + */ + var resolveForFallbackLanguageInstant = function (fallbackLanguageIndex, translationId, interpolateParams, Interpolator) { + var result; + + if (fallbackLanguageIndex < $fallbackLanguage.length) { + var langKey = $fallbackLanguage[fallbackLanguageIndex]; + result = getFallbackTranslationInstant(langKey, translationId, interpolateParams, Interpolator); + if (!result) { + result = resolveForFallbackLanguageInstant(fallbackLanguageIndex + 1, translationId, interpolateParams, Interpolator); + } + } + return result; + }; + + /** + * Translates with the usage of the fallback languages. + * + * @param translationId + * @param interpolateParams + * @param Interpolator + * @returns {Q.promise} Promise, that resolves to the translation. + */ + var fallbackTranslation = function (translationId, interpolateParams, Interpolator, defaultTranslationText) { + // Start with the fallbackLanguage with index 0 + return resolveForFallbackLanguage((startFallbackIteration>0 ? startFallbackIteration : fallbackIndex), translationId, interpolateParams, Interpolator, defaultTranslationText); + }; + + /** + * Translates with the usage of the fallback languages. + * + * @param translationId + * @param interpolateParams + * @param Interpolator + * @returns {String} translation + */ + var fallbackTranslationInstant = function (translationId, interpolateParams, Interpolator) { + // Start with the fallbackLanguage with index 0 + return resolveForFallbackLanguageInstant((startFallbackIteration>0 ? startFallbackIteration : fallbackIndex), translationId, interpolateParams, Interpolator); + }; + + var determineTranslation = function (translationId, interpolateParams, interpolationId, defaultTranslationText, uses) { + + var deferred = $q.defer(); + + var table = uses ? $translationTable[uses] : $translationTable, + Interpolator = (interpolationId) ? interpolatorHashMap[interpolationId] : defaultInterpolator; + + // if the translation id exists, we can just interpolate it + if (table && Object.prototype.hasOwnProperty.call(table, translationId)) { + var translation = table[translationId]; + + // If using link, rerun $translate with linked translationId and return it + if (translation.substr(0, 2) === '@:') { + + $translate(translation.substr(2), interpolateParams, interpolationId, defaultTranslationText, uses) + .then(deferred.resolve, deferred.reject); + } else { + deferred.resolve(Interpolator.interpolate(translation, interpolateParams)); + } + } else { + var missingTranslationHandlerTranslation; + // for logging purposes only (as in $translateMissingTranslationHandlerLog), value is not returned to promise + if ($missingTranslationHandlerFactory && !pendingLoader) { + missingTranslationHandlerTranslation = translateByHandler(translationId, interpolateParams); + } + + // since we couldn't translate the inital requested translation id, + // we try it now with one or more fallback languages, if fallback language(s) is + // configured. + if (uses && $fallbackLanguage && $fallbackLanguage.length) { + fallbackTranslation(translationId, interpolateParams, Interpolator, defaultTranslationText) + .then(function (translation) { + deferred.resolve(translation); + }, function (_translationId) { + deferred.reject(applyNotFoundIndicators(_translationId)); + }); + } else if ($missingTranslationHandlerFactory && !pendingLoader && missingTranslationHandlerTranslation) { + // looks like the requested translation id doesn't exists. + // Now, if there is a registered handler for missing translations and no + // asyncLoader is pending, we execute the handler + if (defaultTranslationText) { + deferred.resolve(defaultTranslationText); + } else { + deferred.resolve(missingTranslationHandlerTranslation); + } + } else { + if (defaultTranslationText) { + deferred.resolve(defaultTranslationText); + } else { + deferred.reject(applyNotFoundIndicators(translationId)); + } + } + } + return deferred.promise; + }; + + var determineTranslationInstant = function (translationId, interpolateParams, interpolationId, uses) { + + var result, table = uses ? $translationTable[uses] : $translationTable, + Interpolator = defaultInterpolator; + + // if the interpolation id exists use custom interpolator + if (interpolatorHashMap && Object.prototype.hasOwnProperty.call(interpolatorHashMap, interpolationId)) { + Interpolator = interpolatorHashMap[interpolationId]; + } + + // if the translation id exists, we can just interpolate it + if (table && Object.prototype.hasOwnProperty.call(table, translationId)) { + var translation = table[translationId]; + + // If using link, rerun $translate with linked translationId and return it + if (translation.substr(0, 2) === '@:') { + result = determineTranslationInstant(translation.substr(2), interpolateParams, interpolationId, uses); + } else { + result = Interpolator.interpolate(translation, interpolateParams); + } + } else { + var missingTranslationHandlerTranslation; + // for logging purposes only (as in $translateMissingTranslationHandlerLog), value is not returned to promise + if ($missingTranslationHandlerFactory && !pendingLoader) { + missingTranslationHandlerTranslation = translateByHandler(translationId, interpolateParams); + } + + // since we couldn't translate the inital requested translation id, + // we try it now with one or more fallback languages, if fallback language(s) is + // configured. + if (uses && $fallbackLanguage && $fallbackLanguage.length) { + fallbackIndex = 0; + result = fallbackTranslationInstant(translationId, interpolateParams, Interpolator); + } else if ($missingTranslationHandlerFactory && !pendingLoader && missingTranslationHandlerTranslation) { + // looks like the requested translation id doesn't exists. + // Now, if there is a registered handler for missing translations and no + // asyncLoader is pending, we execute the handler + result = missingTranslationHandlerTranslation; + } else { + result = applyNotFoundIndicators(translationId); + } + } + + return result; + }; + + var clearNextLangAndPromise = function(key) { + if ($nextLang === key) { + $nextLang = undefined; + } + langPromises[key] = undefined; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#preferredLanguage + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns the language key for the preferred language. + * + * @param {string} langKey language String or Array to be used as preferredLanguage (changing at runtime) + * + * @return {string} preferred language key + */ + $translate.preferredLanguage = function (langKey) { + if(langKey) { + setupPreferredLanguage(langKey); + } + return $preferredLanguage; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#cloakClassName + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns the configured class name for `translate-cloak` directive. + * + * @return {string} cloakClassName + */ + $translate.cloakClassName = function () { + return $cloakClassName; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#nestedObjectDelimeter + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns the configured delimiter for nested namespaces. + * + * @return {string} nestedObjectDelimeter + */ + $translate.nestedObjectDelimeter = function () { + return $nestedObjectDelimeter; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#fallbackLanguage + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns the language key for the fallback languages or sets a new fallback stack. + * + * @param {string=} langKey language String or Array of fallback languages to be used (to change stack at runtime) + * + * @return {string||array} fallback language key + */ + $translate.fallbackLanguage = function (langKey) { + if (langKey !== undefined && langKey !== null) { + fallbackStack(langKey); + + // as we might have an async loader initiated and a new translation language might have been defined + // we need to add the promise to the stack also. So - iterate. + if ($loaderFactory) { + if ($fallbackLanguage && $fallbackLanguage.length) { + for (var i = 0, len = $fallbackLanguage.length; i < len; i++) { + if (!langPromises[$fallbackLanguage[i]]) { + langPromises[$fallbackLanguage[i]] = loadAsync($fallbackLanguage[i]); + } + } + } + } + $translate.use($translate.use()); + } + if ($fallbackWasString) { + return $fallbackLanguage[0]; + } else { + return $fallbackLanguage; + } + + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#useFallbackLanguage + * @methodOf pascalprecht.translate.$translate + * + * @description + * Sets the first key of the fallback language stack to be used for translation. + * Therefore all languages in the fallback array BEFORE this key will be skipped! + * + * @param {string=} langKey Contains the langKey the iteration shall start with. Set to false if you want to + * get back to the whole stack + */ + $translate.useFallbackLanguage = function (langKey) { + if (langKey !== undefined && langKey !== null) { + if (!langKey) { + startFallbackIteration = 0; + } else { + var langKeyPosition = indexOf($fallbackLanguage, langKey); + if (langKeyPosition > -1) { + startFallbackIteration = langKeyPosition; + } + } + + } + + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#proposedLanguage + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns the language key of language that is currently loaded asynchronously. + * + * @return {string} language key + */ + $translate.proposedLanguage = function () { + return $nextLang; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#storage + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns registered storage. + * + * @return {object} Storage + */ + $translate.storage = function () { + return Storage; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#negotiateLocale + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns a language key based on available languages and language aliases. If a + * language key cannot be resolved, returns undefined. + * + * If no or a falsy key is given, returns undefined. + * + * @param {string} [key] Language key + * @return {string|undefined} Language key or undefined if no language key is found. + */ + $translate.negotiateLocale = negotiateLocale; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#use + * @methodOf pascalprecht.translate.$translate + * + * @description + * Tells angular-translate which language to use by given language key. This method is + * used to change language at runtime. It also takes care of storing the language + * key in a configured store to let your app remember the choosed language. + * + * When trying to 'use' a language which isn't available it tries to load it + * asynchronously with registered loaders. + * + * Returns promise object with loaded language file data or string of the currently used language. + * + * If no or a falsy key is given it returns the currently used language key. + * The returned string will be ```undefined``` if setting up $translate hasn't finished. + * @example + * $translate.use("en_US").then(function(data){ + * $scope.text = $translate("HELLO"); + * }); + * + * @param {string} [key] Language key + * @return {object|string} Promise with loaded language data or the language key if a falsy param was given. + */ + $translate.use = function (key) { + if (!key) { + return $uses; + } + + var deferred = $q.defer(); + + $rootScope.$emit('$translateChangeStart', {language: key}); + + // Try to get the aliased language key + var aliasedKey = negotiateLocale(key); + if (aliasedKey) { + key = aliasedKey; + } + + // if there isn't a translation table for the language we've requested, + // we load it asynchronously + $nextLang = key; + if (($forceAsyncReloadEnabled || !$translationTable[key]) && $loaderFactory && !langPromises[key]) { + langPromises[key] = loadAsync(key).then(function (translation) { + translations(translation.key, translation.table); + deferred.resolve(translation.key); + if ($nextLang === key) { + useLanguage(translation.key); + } + return translation; + }, function (key) { + $rootScope.$emit('$translateChangeError', {language: key}); + deferred.reject(key); + $rootScope.$emit('$translateChangeEnd', {language: key}); + return $q.reject(key); + }); + langPromises[key]['finally'](function () { + clearNextLangAndPromise(key); + }); + } else if (langPromises[key]) { + // we are already loading this asynchronously + // resolve our new deferred when the old langPromise is resolved + langPromises[key].then(function (translation) { + if ($nextLang === translation.key) { + useLanguage(translation.key); + } + deferred.resolve(translation.key); + return translation; + }, function (key) { + // find first available fallback language if that request has failed + if (!$uses && $fallbackLanguage && $fallbackLanguage.length > 0) { + return $translate.use($fallbackLanguage[0]).then(deferred.resolve, deferred.reject); + } else { + return deferred.reject(key); + } + }); + } else { + deferred.resolve(key); + useLanguage(key); + } + + return deferred.promise; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#storageKey + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns the key for the storage. + * + * @return {string} storage key + */ + $translate.storageKey = function () { + return storageKey(); + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#isPostCompilingEnabled + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns whether post compiling is enabled or not + * + * @return {bool} storage key + */ + $translate.isPostCompilingEnabled = function () { + return $postCompilingEnabled; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#isForceAsyncReloadEnabled + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns whether force async reload is enabled or not + * + * @return {boolean} forceAsyncReload value + */ + $translate.isForceAsyncReloadEnabled = function () { + return $forceAsyncReloadEnabled; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#refresh + * @methodOf pascalprecht.translate.$translate + * + * @description + * Refreshes a translation table pointed by the given langKey. If langKey is not specified, + * the module will drop all existent translation tables and load new version of those which + * are currently in use. + * + * Refresh means that the module will drop target translation table and try to load it again. + * + * In case there are no loaders registered the refresh() method will throw an Error. + * + * If the module is able to refresh translation tables refresh() method will broadcast + * $translateRefreshStart and $translateRefreshEnd events. + * + * @example + * // this will drop all currently existent translation tables and reload those which are + * // currently in use + * $translate.refresh(); + * // this will refresh a translation table for the en_US language + * $translate.refresh('en_US'); + * + * @param {string} langKey A language key of the table, which has to be refreshed + * + * @return {promise} Promise, which will be resolved in case a translation tables refreshing + * process is finished successfully, and reject if not. + */ + $translate.refresh = function (langKey) { + if (!$loaderFactory) { + throw new Error('Couldn\'t refresh translation table, no loader registered!'); + } + + var deferred = $q.defer(); + + function resolve() { + deferred.resolve(); + $rootScope.$emit('$translateRefreshEnd', {language: langKey}); + } + + function reject() { + deferred.reject(); + $rootScope.$emit('$translateRefreshEnd', {language: langKey}); + } + + $rootScope.$emit('$translateRefreshStart', {language: langKey}); + + if (!langKey) { + // if there's no language key specified we refresh ALL THE THINGS! + var tables = [], loadingKeys = {}; + + // reload registered fallback languages + if ($fallbackLanguage && $fallbackLanguage.length) { + for (var i = 0, len = $fallbackLanguage.length; i < len; i++) { + tables.push(loadAsync($fallbackLanguage[i])); + loadingKeys[$fallbackLanguage[i]] = true; + } + } + + // reload currently used language + if ($uses && !loadingKeys[$uses]) { + tables.push(loadAsync($uses)); + } + + var allTranslationsLoaded = function (tableData) { + $translationTable = {}; + angular.forEach(tableData, function (data) { + translations(data.key, data.table); + }); + if ($uses) { + useLanguage($uses); + } + resolve(); + }; + allTranslationsLoaded.displayName = 'refreshPostProcessor'; + + $q.all(tables).then(allTranslationsLoaded, reject); + + } else if ($translationTable[langKey]) { + + var oneTranslationsLoaded = function (data) { + translations(data.key, data.table); + if (langKey === $uses) { + useLanguage($uses); + } + resolve(); + }; + oneTranslationsLoaded.displayName = 'refreshPostProcessor'; + + loadAsync(langKey).then(oneTranslationsLoaded, reject); + + } else { + reject(); + } + return deferred.promise; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#instant + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns a translation instantly from the internal state of loaded translation. All rules + * regarding the current language, the preferred language of even fallback languages will be + * used except any promise handling. If a language was not found, an asynchronous loading + * will be invoked in the background. + * + * @param {string|array} translationId A token which represents a translation id + * This can be optionally an array of translation ids which + * results that the function's promise returns an object where + * each key is the translation id and the value the translation. + * @param {object} interpolateParams Params + * @param {string} interpolationId The id of the interpolation to use + * @param {string} forceLanguage A language to be used instead of the current language + * + * @return {string|object} translation + */ + $translate.instant = function (translationId, interpolateParams, interpolationId, forceLanguage) { + + // we don't want to re-negotiate $uses + var uses = (forceLanguage && forceLanguage !== $uses) ? // we don't want to re-negotiate $uses + (negotiateLocale(forceLanguage) || forceLanguage) : $uses; + + // Detect undefined and null values to shorten the execution and prevent exceptions + if (translationId === null || angular.isUndefined(translationId)) { + return translationId; + } + + // Duck detection: If the first argument is an array, a bunch of translations was requested. + // The result is an object. + if (angular.isArray(translationId)) { + var results = {}; + for (var i = 0, c = translationId.length; i < c; i++) { + results[translationId[i]] = $translate.instant(translationId[i], interpolateParams, interpolationId, forceLanguage); + } + return results; + } + + // We discarded unacceptable values. So we just need to verify if translationId is empty String + if (angular.isString(translationId) && translationId.length < 1) { + return translationId; + } + + // trim off any whitespace + if (translationId) { + translationId = trim.apply(translationId); + } + + var result, possibleLangKeys = []; + if ($preferredLanguage) { + possibleLangKeys.push($preferredLanguage); + } + if (uses) { + possibleLangKeys.push(uses); + } + if ($fallbackLanguage && $fallbackLanguage.length) { + possibleLangKeys = possibleLangKeys.concat($fallbackLanguage); + } + for (var j = 0, d = possibleLangKeys.length; j < d; j++) { + var possibleLangKey = possibleLangKeys[j]; + if ($translationTable[possibleLangKey]) { + if (typeof $translationTable[possibleLangKey][translationId] !== 'undefined') { + result = determineTranslationInstant(translationId, interpolateParams, interpolationId, uses); + } + } + if (typeof result !== 'undefined') { + break; + } + } + + if (!result && result !== '') { + if ($notFoundIndicatorLeft || $notFoundIndicatorRight) { + result = applyNotFoundIndicators(translationId); + } else { + // Return translation of default interpolator if not found anything. + result = defaultInterpolator.interpolate(translationId, interpolateParams); + if ($missingTranslationHandlerFactory && !pendingLoader) { + result = translateByHandler(translationId, interpolateParams); + } + } + } + + return result; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#versionInfo + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns the current version information for the angular-translate library + * + * @return {string} angular-translate version + */ + $translate.versionInfo = function () { + return version; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#loaderCache + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns the defined loaderCache. + * + * @return {boolean|string|object} current value of loaderCache + */ + $translate.loaderCache = function () { + return loaderCache; + }; + + // internal purpose only + $translate.directivePriority = function () { + return directivePriority; + }; + + // internal purpose only + $translate.statefulFilter = function () { + return statefulFilter; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#isReady + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns whether the service is "ready" to translate (i.e. loading 1st language). + * + * See also {@link pascalprecht.translate.$translate#methods_onReady onReady()}. + * + * @return {boolean} current value of ready + */ + $translate.isReady = function () { + return $isReady; + }; + + var $onReadyDeferred = $q.defer(); + $onReadyDeferred.promise.then(function () { + $isReady = true; + }); + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#onReady + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns whether the service is "ready" to translate (i.e. loading 1st language). + * + * See also {@link pascalprecht.translate.$translate#methods_isReady isReady()}. + * + * @param {Function=} fn Function to invoke when service is ready + * @return {object} Promise resolved when service is ready + */ + $translate.onReady = function (fn) { + var deferred = $q.defer(); + if (angular.isFunction(fn)) { + deferred.promise.then(fn); + } + if ($isReady) { + deferred.resolve(); + } else { + $onReadyDeferred.promise.then(deferred.resolve); + } + return deferred.promise; + }; + + // Whenever $translateReady is being fired, this will ensure the state of $isReady + var globalOnReadyListener = $rootScope.$on('$translateReady', function () { + $onReadyDeferred.resolve(); + globalOnReadyListener(); // one time only + globalOnReadyListener = null; + }); + var globalOnChangeListener = $rootScope.$on('$translateChangeEnd', function () { + $onReadyDeferred.resolve(); + globalOnChangeListener(); // one time only + globalOnChangeListener = null; + }); + + if ($loaderFactory) { + + // If at least one async loader is defined and there are no + // (default) translations available we should try to load them. + if (angular.equals($translationTable, {})) { + if ($translate.use()) { + $translate.use($translate.use()); + } + } + + // Also, if there are any fallback language registered, we start + // loading them asynchronously as soon as we can. + if ($fallbackLanguage && $fallbackLanguage.length) { + var processAsyncResult = function (translation) { + translations(translation.key, translation.table); + $rootScope.$emit('$translateChangeEnd', { language: translation.key }); + return translation; + }; + for (var i = 0, len = $fallbackLanguage.length; i < len; i++) { + var fallbackLanguageId = $fallbackLanguage[i]; + if ($forceAsyncReloadEnabled || !$translationTable[fallbackLanguageId]) { + langPromises[fallbackLanguageId] = loadAsync(fallbackLanguageId).then(processAsyncResult); + } + } + } + } else { + $rootScope.$emit('$translateReady', { language: $translate.use() }); + } + + return $translate; + } + ]; +} +$translate.$inject = ['$STORAGE_KEY', '$windowProvider', '$translateSanitizationProvider', 'pascalprechtTranslateOverrider']; + +$translate.displayName = 'displayName'; + +/** + * @ngdoc object + * @name pascalprecht.translate.$translateDefaultInterpolation + * @requires $interpolate + * + * @description + * Uses angular's `$interpolate` services to interpolate strings against some values. + * + * Be aware to configure a proper sanitization strategy. + * + * See also: + * * {@link pascalprecht.translate.$translateSanitization} + * + * @return {object} $translateDefaultInterpolation Interpolator service + */ +angular.module('pascalprecht.translate').factory('$translateDefaultInterpolation', $translateDefaultInterpolation); + +function $translateDefaultInterpolation ($interpolate, $translateSanitization) { + + 'use strict'; + + var $translateInterpolator = {}, + $locale, + $identifier = 'default'; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateDefaultInterpolation#setLocale + * @methodOf pascalprecht.translate.$translateDefaultInterpolation + * + * @description + * Sets current locale (this is currently not use in this interpolation). + * + * @param {string} locale Language key or locale. + */ + $translateInterpolator.setLocale = function (locale) { + $locale = locale; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateDefaultInterpolation#getInterpolationIdentifier + * @methodOf pascalprecht.translate.$translateDefaultInterpolation + * + * @description + * Returns an identifier for this interpolation service. + * + * @returns {string} $identifier + */ + $translateInterpolator.getInterpolationIdentifier = function () { + return $identifier; + }; + + /** + * @deprecated will be removed in 3.0 + * @see {@link pascalprecht.translate.$translateSanitization} + */ + $translateInterpolator.useSanitizeValueStrategy = function (value) { + $translateSanitization.useStrategy(value); + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateDefaultInterpolation#interpolate + * @methodOf pascalprecht.translate.$translateDefaultInterpolation + * + * @description + * Interpolates given string agains given interpolate params using angulars + * `$interpolate` service. + * + * @returns {string} interpolated string. + */ + $translateInterpolator.interpolate = function (string, interpolationParams) { + interpolationParams = interpolationParams || {}; + interpolationParams = $translateSanitization.sanitize(interpolationParams, 'params'); + + var interpolatedText = $interpolate(string)(interpolationParams); + interpolatedText = $translateSanitization.sanitize(interpolatedText, 'text'); + + return interpolatedText; + }; + + return $translateInterpolator; +} +$translateDefaultInterpolation.$inject = ['$interpolate', '$translateSanitization']; + +$translateDefaultInterpolation.displayName = '$translateDefaultInterpolation'; + +angular.module('pascalprecht.translate').constant('$STORAGE_KEY', 'NG_TRANSLATE_LANG_KEY'); + +angular.module('pascalprecht.translate') +/** + * @ngdoc directive + * @name pascalprecht.translate.directive:translate + * @requires $compile + * @requires $filter + * @requires $interpolate + * @restrict AE + * + * @description + * Translates given translation id either through attribute or DOM content. + * Internally it uses `translate` filter to translate translation id. It possible to + * pass an optional `translate-values` object literal as string into translation id. + * + * @param {string=} translate Translation id which could be either string or interpolated string. + * @param {string=} translate-values Values to pass into translation id. Can be passed as object literal string or interpolated object. + * @param {string=} translate-attr-ATTR translate Translation id and put it into ATTR attribute. + * @param {string=} translate-default will be used unless translation was successful + * @param {boolean=} translate-compile (default true if present) defines locally activation of {@link pascalprecht.translate.$translateProvider#methods_usePostCompiling} + * + * @example + <example module="ngView"> + <file name="index.html"> + <div ng-controller="TranslateCtrl"> + + <pre translate="TRANSLATION_ID"></pre> + <pre translate>TRANSLATION_ID</pre> + <pre translate translate-attr-title="TRANSLATION_ID"></pre> + <pre translate="{{translationId}}"></pre> + <pre translate>{{translationId}}</pre> + <pre translate="WITH_VALUES" translate-values="{value: 5}"></pre> + <pre translate translate-values="{value: 5}">WITH_VALUES</pre> + <pre translate="WITH_VALUES" translate-values="{{values}}"></pre> + <pre translate translate-values="{{values}}">WITH_VALUES</pre> + <pre translate translate-attr-title="WITH_VALUES" translate-values="{{values}}"></pre> + + </div> + </file> + <file name="script.js"> + angular.module('ngView', ['pascalprecht.translate']) + + .config(function ($translateProvider) { + + $translateProvider.translations('en',{ + 'TRANSLATION_ID': 'Hello there!', + 'WITH_VALUES': 'The following value is dynamic: {{value}}' + }).preferredLanguage('en'); + + }); + + angular.module('ngView').controller('TranslateCtrl', function ($scope) { + $scope.translationId = 'TRANSLATION_ID'; + + $scope.values = { + value: 78 + }; + }); + </file> + <file name="scenario.js"> + it('should translate', function () { + inject(function ($rootScope, $compile) { + $rootScope.translationId = 'TRANSLATION_ID'; + + element = $compile('<p translate="TRANSLATION_ID"></p>')($rootScope); + $rootScope.$digest(); + expect(element.text()).toBe('Hello there!'); + + element = $compile('<p translate="{{translationId}}"></p>')($rootScope); + $rootScope.$digest(); + expect(element.text()).toBe('Hello there!'); + + element = $compile('<p translate>TRANSLATION_ID</p>')($rootScope); + $rootScope.$digest(); + expect(element.text()).toBe('Hello there!'); + + element = $compile('<p translate>{{translationId}}</p>')($rootScope); + $rootScope.$digest(); + expect(element.text()).toBe('Hello there!'); + + element = $compile('<p translate translate-attr-title="TRANSLATION_ID"></p>')($rootScope); + $rootScope.$digest(); + expect(element.attr('title')).toBe('Hello there!'); + }); + }); + </file> + </example> + */ +.directive('translate', translateDirective); +function translateDirective($translate, $q, $interpolate, $compile, $parse, $rootScope) { + + 'use strict'; + + /** + * @name trim + * @private + * + * @description + * trim polyfill + * + * @returns {string} The string stripped of whitespace from both ends + */ + var trim = function() { + return this.toString().replace(/^\s+|\s+$/g, ''); + }; + + return { + restrict: 'AE', + scope: true, + priority: $translate.directivePriority(), + compile: function (tElement, tAttr) { + + var translateValuesExist = (tAttr.translateValues) ? + tAttr.translateValues : undefined; + + var translateInterpolation = (tAttr.translateInterpolation) ? + tAttr.translateInterpolation : undefined; + + var translateValueExist = tElement[0].outerHTML.match(/translate-value-+/i); + + var interpolateRegExp = '^(.*)(' + $interpolate.startSymbol() + '.*' + $interpolate.endSymbol() + ')(.*)', + watcherRegExp = '^(.*)' + $interpolate.startSymbol() + '(.*)' + $interpolate.endSymbol() + '(.*)'; + + return function linkFn(scope, iElement, iAttr) { + + scope.interpolateParams = {}; + scope.preText = ''; + scope.postText = ''; + scope.translateNamespace = getTranslateNamespace(scope); + var translationIds = {}; + + var initInterpolationParams = function (interpolateParams, iAttr, tAttr) { + // initial setup + if (iAttr.translateValues) { + angular.extend(interpolateParams, $parse(iAttr.translateValues)(scope.$parent)); + } + // initially fetch all attributes if existing and fill the params + if (translateValueExist) { + for (var attr in tAttr) { + if (Object.prototype.hasOwnProperty.call(iAttr, attr) && attr.substr(0, 14) === 'translateValue' && attr !== 'translateValues') { + var attributeName = angular.lowercase(attr.substr(14, 1)) + attr.substr(15); + interpolateParams[attributeName] = tAttr[attr]; + } + } + } + }; + + // Ensures any change of the attribute "translate" containing the id will + // be re-stored to the scope's "translationId". + // If the attribute has no content, the element's text value (white spaces trimmed off) will be used. + var observeElementTranslation = function (translationId) { + + // Remove any old watcher + if (angular.isFunction(observeElementTranslation._unwatchOld)) { + observeElementTranslation._unwatchOld(); + observeElementTranslation._unwatchOld = undefined; + } + + if (angular.equals(translationId , '') || !angular.isDefined(translationId)) { + var iElementText = trim.apply(iElement.text()); + + // Resolve translation id by inner html if required + var interpolateMatches = iElementText.match(interpolateRegExp); + // Interpolate translation id if required + if (angular.isArray(interpolateMatches)) { + scope.preText = interpolateMatches[1]; + scope.postText = interpolateMatches[3]; + translationIds.translate = $interpolate(interpolateMatches[2])(scope.$parent); + var watcherMatches = iElementText.match(watcherRegExp); + if (angular.isArray(watcherMatches) && watcherMatches[2] && watcherMatches[2].length) { + observeElementTranslation._unwatchOld = scope.$watch(watcherMatches[2], function (newValue) { + translationIds.translate = newValue; + updateTranslations(); + }); + } + } else { + // do not assigne the translation id if it is empty. + translationIds.translate = !iElementText ? undefined : iElementText; + } + } else { + translationIds.translate = translationId; + } + updateTranslations(); + }; + + var observeAttributeTranslation = function (translateAttr) { + iAttr.$observe(translateAttr, function (translationId) { + translationIds[translateAttr] = translationId; + updateTranslations(); + }); + }; + + // initial setup with values + initInterpolationParams(scope.interpolateParams, iAttr, tAttr); + + var firstAttributeChangedEvent = true; + iAttr.$observe('translate', function (translationId) { + if (typeof translationId === 'undefined') { + // case of element "<translate>xyz</translate>" + observeElementTranslation(''); + } else { + // case of regular attribute + if (translationId !== '' || !firstAttributeChangedEvent) { + translationIds.translate = translationId; + updateTranslations(); + } + } + firstAttributeChangedEvent = false; + }); + + for (var translateAttr in iAttr) { + if (iAttr.hasOwnProperty(translateAttr) && translateAttr.substr(0, 13) === 'translateAttr') { + observeAttributeTranslation(translateAttr); + } + } + + iAttr.$observe('translateDefault', function (value) { + scope.defaultText = value; + updateTranslations(); + }); + + if (translateValuesExist) { + iAttr.$observe('translateValues', function (interpolateParams) { + if (interpolateParams) { + scope.$parent.$watch(function () { + angular.extend(scope.interpolateParams, $parse(interpolateParams)(scope.$parent)); + }); + } + }); + } + + if (translateValueExist) { + var observeValueAttribute = function (attrName) { + iAttr.$observe(attrName, function (value) { + var attributeName = angular.lowercase(attrName.substr(14, 1)) + attrName.substr(15); + scope.interpolateParams[attributeName] = value; + }); + }; + for (var attr in iAttr) { + if (Object.prototype.hasOwnProperty.call(iAttr, attr) && attr.substr(0, 14) === 'translateValue' && attr !== 'translateValues') { + observeValueAttribute(attr); + } + } + } + + // Master update function + var updateTranslations = function () { + for (var key in translationIds) { + + if (translationIds.hasOwnProperty(key) && translationIds[key] !== undefined) { + updateTranslation(key, translationIds[key], scope, scope.interpolateParams, scope.defaultText, scope.translateNamespace); + } + } + }; + + // Put translation processing function outside loop + var updateTranslation = function(translateAttr, translationId, scope, interpolateParams, defaultTranslationText, translateNamespace) { + if (translationId) { + // if translation id starts with '.' and translateNamespace given, prepend namespace + if (translateNamespace && translationId.charAt(0) === '.') { + translationId = translateNamespace + translationId; + } + + $translate(translationId, interpolateParams, translateInterpolation, defaultTranslationText, scope.translateLanguage) + .then(function (translation) { + applyTranslation(translation, scope, true, translateAttr); + }, function (translationId) { + applyTranslation(translationId, scope, false, translateAttr); + }); + } else { + // as an empty string cannot be translated, we can solve this using successful=false + applyTranslation(translationId, scope, false, translateAttr); + } + }; + + var applyTranslation = function (value, scope, successful, translateAttr) { + if (translateAttr === 'translate') { + // default translate into innerHTML + if (!successful && typeof scope.defaultText !== 'undefined') { + value = scope.defaultText; + } + iElement.empty().append(scope.preText + value + scope.postText); + var globallyEnabled = $translate.isPostCompilingEnabled(); + var locallyDefined = typeof tAttr.translateCompile !== 'undefined'; + var locallyEnabled = locallyDefined && tAttr.translateCompile !== 'false'; + if ((globallyEnabled && !locallyDefined) || locallyEnabled) { + $compile(iElement.contents())(scope); + } + } else { + // translate attribute + if (!successful && typeof scope.defaultText !== 'undefined') { + value = scope.defaultText; + } + var attributeName = iAttr.$attr[translateAttr]; + if (attributeName.substr(0, 5) === 'data-') { + // ensure html5 data prefix is stripped + attributeName = attributeName.substr(5); + } + attributeName = attributeName.substr(15); + iElement.attr(attributeName, value); + } + }; + + if (translateValuesExist || translateValueExist || iAttr.translateDefault) { + scope.$watch('interpolateParams', updateTranslations, true); + } + scope.$watch('translateLanguage', updateTranslations); + + // Ensures the text will be refreshed after the current language was changed + // w/ $translate.use(...) + var unbind = $rootScope.$on('$translateChangeSuccess', updateTranslations); + + // ensure translation will be looked up at least one + if (iElement.text().length) { + if (iAttr.translate) { + observeElementTranslation(iAttr.translate); + } else { + observeElementTranslation(''); + } + } else if (iAttr.translate) { + // ensure attribute will be not skipped + observeElementTranslation(iAttr.translate); + } + updateTranslations(); + scope.$on('$destroy', unbind); + }; + } + }; +} +translateDirective.$inject = ['$translate', '$q', '$interpolate', '$compile', '$parse', '$rootScope']; + +/** + * Returns the scope's namespace. + * @private + * @param scope + * @returns {string} + */ +function getTranslateNamespace(scope) { + 'use strict'; + if (scope.translateNamespace) { + return scope.translateNamespace; + } + if (scope.$parent) { + return getTranslateNamespace(scope.$parent); + } +} + +translateDirective.displayName = 'translateDirective'; + +angular.module('pascalprecht.translate') +/** + * @ngdoc directive + * @name pascalprecht.translate.directive:translateCloak + * @requires $rootScope + * @requires $translate + * @restrict A + * + * $description + * Adds a `translate-cloak` class name to the given element where this directive + * is applied initially and removes it, once a loader has finished loading. + * + * This directive can be used to prevent initial flickering when loading translation + * data asynchronously. + * + * The class name is defined in + * {@link pascalprecht.translate.$translateProvider#cloakClassName $translate.cloakClassName()}. + * + * @param {string=} translate-cloak If a translationId is provided, it will be used for showing + * or hiding the cloak. Basically it relies on the translation + * resolve. + */ +.directive('translateCloak', translateCloakDirective); + +function translateCloakDirective($translate, $rootScope) { + + 'use strict'; + + return { + compile: function (tElement) { + var applyCloak = function () { + tElement.addClass($translate.cloakClassName()); + }, + removeCloak = function () { + tElement.removeClass($translate.cloakClassName()); + }; + $translate.onReady(function () { + removeCloak(); + }); + applyCloak(); + + return function linkFn(scope, iElement, iAttr) { + if (iAttr.translateCloak && iAttr.translateCloak.length) { + // Register a watcher for the defined translation allowing a fine tuned cloak + iAttr.$observe('translateCloak', function (translationId) { + $translate(translationId).then(removeCloak, applyCloak); + }); + // Register for change events as this is being another indicicator revalidating the cloak) + $rootScope.$on('$translateChangeSuccess', function () { + $translate(iAttr.translateCloak).then(removeCloak, applyCloak); + }); + } + }; + } + }; +} +translateCloakDirective.$inject = ['$translate', '$rootScope']; + +translateCloakDirective.displayName = 'translateCloakDirective'; + +angular.module('pascalprecht.translate') +/** + * @ngdoc directive + * @name pascalprecht.translate.directive:translateNamespace + * @restrict A + * + * @description + * Translates given translation id either through attribute or DOM content. + * Internally it uses `translate` filter to translate translation id. It possible to + * pass an optional `translate-values` object literal as string into translation id. + * + * @param {string=} translate namespace name which could be either string or interpolated string. + * + * @example + <example module="ngView"> + <file name="index.html"> + <div translate-namespace="CONTENT"> + + <div> + <h1 translate>.HEADERS.TITLE</h1> + <h1 translate>.HEADERS.WELCOME</h1> + </div> + + <div translate-namespace=".HEADERS"> + <h1 translate>.TITLE</h1> + <h1 translate>.WELCOME</h1> + </div> + + </div> + </file> + <file name="script.js"> + angular.module('ngView', ['pascalprecht.translate']) + + .config(function ($translateProvider) { + + $translateProvider.translations('en',{ + 'TRANSLATION_ID': 'Hello there!', + 'CONTENT': { + 'HEADERS': { + TITLE: 'Title' + } + }, + 'CONTENT.HEADERS.WELCOME': 'Welcome' + }).preferredLanguage('en'); + + }); + + </file> + </example> + */ +.directive('translateNamespace', translateNamespaceDirective); + +function translateNamespaceDirective() { + + 'use strict'; + + return { + restrict: 'A', + scope: true, + compile: function () { + return { + pre: function (scope, iElement, iAttrs) { + scope.translateNamespace = getTranslateNamespace(scope); + + if (scope.translateNamespace && iAttrs.translateNamespace.charAt(0) === '.') { + scope.translateNamespace += iAttrs.translateNamespace; + } else { + scope.translateNamespace = iAttrs.translateNamespace; + } + } + }; + } + }; +} + +/** + * Returns the scope's namespace. + * @private + * @param scope + * @returns {string} + */ +function getTranslateNamespace(scope) { + 'use strict'; + if (scope.translateNamespace) { + return scope.translateNamespace; + } + if (scope.$parent) { + return getTranslateNamespace(scope.$parent); + } +} + +translateNamespaceDirective.displayName = 'translateNamespaceDirective'; + +angular.module('pascalprecht.translate') +/** + * @ngdoc directive + * @name pascalprecht.translate.directive:translateLanguage + * @restrict A + * + * @description + * Forces the language to the directives in the underlying scope. + * + * @param {string=} translate language that will be negotiated. + * + * @example + <example module="ngView"> + <file name="index.html"> + <div> + + <div> + <h1 translate>HELLO</h1> + </div> + + <div translate-language="de"> + <h1 translate>HELLO</h1> + </div> + + </div> + </file> + <file name="script.js"> + angular.module('ngView', ['pascalprecht.translate']) + + .config(function ($translateProvider) { + + $translateProvider + .translations('en',{ + 'HELLO': 'Hello world!' + }) + .translations('de',{ + 'HELLO': 'Hallo Welt!' + }) + .translations(.preferredLanguage('en'); + + }); + + </file> + </example> + */ +.directive('translateLanguage', translateLanguageDirective); + +function translateLanguageDirective() { + + 'use strict'; + + return { + restrict: 'A', + scope: true, + compile: function () { + return function linkFn(scope, iElement, iAttrs) { + iAttrs.$observe('translateLanguage', function (newTranslateLanguage) { + scope.translateLanguage = newTranslateLanguage; + }); + }; + } + }; +} + +translateLanguageDirective.displayName = 'translateLanguageDirective'; + + +angular.module('pascalprecht.translate') +/** + * @ngdoc filter + * @name pascalprecht.translate.filter:translate + * @requires $parse + * @requires pascalprecht.translate.$translate + * @function + * + * @description + * Uses `$translate` service to translate contents. Accepts interpolate parameters + * to pass dynamized values though translation. + * + * @param {string} translationId A translation id to be translated. + * @param {*=} interpolateParams Optional object literal (as hash or string) to pass values into translation. + * + * @returns {string} Translated text. + * + * @example + <example module="ngView"> + <file name="index.html"> + <div ng-controller="TranslateCtrl"> + + <pre>{{ 'TRANSLATION_ID' | translate }}</pre> + <pre>{{ translationId | translate }}</pre> + <pre>{{ 'WITH_VALUES' | translate:'{value: 5}' }}</pre> + <pre>{{ 'WITH_VALUES' | translate:values }}</pre> + + </div> + </file> + <file name="script.js"> + angular.module('ngView', ['pascalprecht.translate']) + + .config(function ($translateProvider) { + + $translateProvider.translations('en', { + 'TRANSLATION_ID': 'Hello there!', + 'WITH_VALUES': 'The following value is dynamic: {{value}}' + }); + $translateProvider.preferredLanguage('en'); + + }); + + angular.module('ngView').controller('TranslateCtrl', function ($scope) { + $scope.translationId = 'TRANSLATION_ID'; + + $scope.values = { + value: 78 + }; + }); + </file> + </example> + */ +.filter('translate', translateFilterFactory); + +function translateFilterFactory($parse, $translate) { + + 'use strict'; + + var translateFilter = function (translationId, interpolateParams, interpolation, forceLanguage) { + + if (!angular.isObject(interpolateParams)) { + interpolateParams = $parse(interpolateParams)(this); + } + + return $translate.instant(translationId, interpolateParams, interpolation, forceLanguage); + }; + + if ($translate.statefulFilter()) { + translateFilter.$stateful = true; + } + + return translateFilter; +} +translateFilterFactory.$inject = ['$parse', '$translate']; + +translateFilterFactory.displayName = 'translateFilterFactory'; + +angular.module('pascalprecht.translate') + +/** + * @ngdoc object + * @name pascalprecht.translate.$translationCache + * @requires $cacheFactory + * + * @description + * The first time a translation table is used, it is loaded in the translation cache for quick retrieval. You + * can load translation tables directly into the cache by consuming the + * `$translationCache` service directly. + * + * @return {object} $cacheFactory object. + */ + .factory('$translationCache', $translationCache); + +function $translationCache($cacheFactory) { + + 'use strict'; + + return $cacheFactory('translations'); +} +$translationCache.$inject = ['$cacheFactory']; + +$translationCache.displayName = '$translationCache'; +return 'pascalprecht.translate'; + +})); diff --git a/www/lib/ionic/js/angular/angular-translate.min.js b/www/lib/ionic/js/angular/angular-translate.min.js new file mode 100644 index 0000000000000000000000000000000000000000..aecaaa7178713851ea251970f85708cff576a3ce --- /dev/null +++ b/www/lib/ionic/js/angular/angular-translate.min.js @@ -0,0 +1,6 @@ +/*! + * angular-translate - v2.10.0 - 2016-02-28 + * + * Copyright (c) 2016 The angular-translate team, Pascal Precht; Licensed MIT + */ +!function(a,b){"function"==typeof define&&define.amd?define([],function(){return b()}):"object"==typeof exports?module.exports=b():b()}(this,function(){function a(a){"use strict";var b=a.storageKey(),c=a.storage(),d=function(){var d=a.preferredLanguage();angular.isString(d)?a.use(d):c.put(b,a.use())};d.displayName="fallbackFromIncorrectStorageValue",c?c.get(b)?a.use(c.get(b))["catch"](d):d():angular.isString(a.preferredLanguage())&&a.use(a.preferredLanguage())}function b(){"use strict";var a,b,c=null,d=!1,e=!1;b={sanitize:function(a,b){return"text"===b&&(a=g(a)),a},escape:function(a,b){return"text"===b&&(a=f(a)),a},sanitizeParameters:function(a,b){return"params"===b&&(a=h(a,g)),a},escapeParameters:function(a,b){return"params"===b&&(a=h(a,f)),a}},b.escaped=b.escapeParameters,this.addStrategy=function(a,c){return b[a]=c,this},this.removeStrategy=function(a){return delete b[a],this},this.useStrategy=function(a){return d=!0,c=a,this},this.$get=["$injector","$log",function(f,g){var h={},i=function(a,c,d){return angular.forEach(d,function(d){if(angular.isFunction(d))a=d(a,c);else if(angular.isFunction(b[d]))a=b[d](a,c);else{if(!angular.isString(b[d]))throw new Error("pascalprecht.translate.$translateSanitization: Unknown sanitization strategy: '"+d+"'");if(!h[b[d]])try{h[b[d]]=f.get(b[d])}catch(e){throw h[b[d]]=function(){},new Error("pascalprecht.translate.$translateSanitization: Unknown sanitization strategy: '"+d+"'")}a=h[b[d]](a,c)}}),a},j=function(){d||e||(g.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."),e=!0)};return f.has("$sanitize")&&(a=f.get("$sanitize")),{useStrategy:function(a){return function(b){a.useStrategy(b)}}(this),sanitize:function(a,b,d){if(c||j(),arguments.length<3&&(d=c),!d)return a;var e=angular.isArray(d)?d:[d];return i(a,b,e)}}}];var f=function(a){var b=angular.element("<div></div>");return b.text(a),b.html()},g=function(b){if(!a)throw new Error("pascalprecht.translate.$translateSanitization: Error cannot find $sanitize service. Either include the ngSanitize module (https://docs.angularjs.org/api/ngSanitize) or use a sanitization strategy which does not depend on $sanitize, such as 'escape'.");return a(b)},h=function(a,b,c){if(angular.isObject(a)){var d=angular.isArray(a)?[]:{};if(c){if(c.indexOf(a)>-1)throw new Error("pascalprecht.translate.$translateSanitization: Error cannot interpolate parameter due recursive object")}else c=[];return c.push(a),angular.forEach(a,function(a,e){d[e]=h(a,b,c)}),c.splice(-1,1),d}return angular.isNumber(a)?a:b(a)}}function c(a,b,c,d){"use strict";var e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t={},u=[],v=a,w=[],x="translate-cloak",y=!1,z=!1,A=".",B=!1,C=0,D=!0,E="default",F={"default":function(a){return(a||"").split("-").join("_")},java:function(a){var b=(a||"").split("-").join("_"),c=b.split("_");return c.length>1?c[0].toLowerCase()+"_"+c[1].toUpperCase():b},bcp47:function(a){var b=(a||"").split("_").join("-"),c=b.split("-");return c.length>1?c[0].toLowerCase()+"-"+c[1].toUpperCase():b}},G="2.10.0",H=function(){if(angular.isFunction(d.getLocale))return d.getLocale();var a,c,e=b.$get().navigator,f=["language","browserLanguage","systemLanguage","userLanguage"];if(angular.isArray(e.languages))for(a=0;a<e.languages.length;a++)if(c=e.languages[a],c&&c.length)return c;for(a=0;a<f.length;a++)if(c=e[f[a]],c&&c.length)return c;return null};H.displayName="angular-translate/service: getFirstBrowserLanguage";var I=function(){var a=H()||"";return F[E]&&(a=F[E](a)),a};I.displayName="angular-translate/service: getLocale";var J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K=function(){return this.toString().replace(/^\s+|\s+$/g,"")},L=function(a){if(a){for(var b=[],c=angular.lowercase(a),d=0,e=u.length;e>d;d++)b.push(angular.lowercase(u[d]));if(J(b,c)>-1)return a;if(f){var g;for(var h in f){var i=!1,j=Object.prototype.hasOwnProperty.call(f,h)&&angular.lowercase(h)===angular.lowercase(a);if("*"===h.slice(-1)&&(i=h.slice(0,-1)===a.slice(0,h.length-1)),(j||i)&&(g=f[h],J(b,angular.lowercase(g))>-1))return g}}var k=a.split("_");return k.length>1&&J(b,angular.lowercase(k[0]))>-1?k[0]:void 0}},M=function(a,b){if(!a&&!b)return t;if(a&&!b){if(angular.isString(a))return t[a]}else angular.isObject(t[a])||(t[a]={}),angular.extend(t[a],N(b));return this};this.translations=M,this.cloakClassName=function(a){return a?(x=a,this):x},this.nestedObjectDelimeter=function(a){return a?(A=a,this):A};var N=function(a,b,c,d){var e,f,g,h;b||(b=[]),c||(c={});for(e in a)Object.prototype.hasOwnProperty.call(a,e)&&(h=a[e],angular.isObject(h)?N(h,b.concat(e),c,e):(f=b.length?""+b.join(A)+A+e:e,b.length&&e===d&&(g=""+b.join(A),c[g]="@:"+f),c[f]=h));return c};N.displayName="flatObject",this.addInterpolation=function(a){return w.push(a),this},this.useMessageFormatInterpolation=function(){return this.useInterpolation("$translateMessageFormatInterpolation")},this.useInterpolation=function(a){return n=a,this},this.useSanitizeValueStrategy=function(a){return c.useStrategy(a),this},this.preferredLanguage=function(a){return a?(O(a),this):e};var O=function(a){return a&&(e=a),e};this.translationNotFoundIndicator=function(a){return this.translationNotFoundIndicatorLeft(a),this.translationNotFoundIndicatorRight(a),this},this.translationNotFoundIndicatorLeft=function(a){return a?(q=a,this):q},this.translationNotFoundIndicatorRight=function(a){return a?(r=a,this):r},this.fallbackLanguage=function(a){return P(a),this};var P=function(a){return a?(angular.isString(a)?(h=!0,g=[a]):angular.isArray(a)&&(h=!1,g=a),angular.isString(e)&&J(g,e)<0&&g.push(e),this):h?g[0]:g};this.use=function(a){if(a){if(!t[a]&&!o)throw new Error("$translateProvider couldn't find translationTable for langKey: '"+a+"'");return i=a,this}return i};var Q=function(a){return a?(v=a,this):l?l+v:v};this.storageKey=Q,this.useUrlLoader=function(a,b){return this.useLoader("$translateUrlLoader",angular.extend({url:a},b))},this.useStaticFilesLoader=function(a){return this.useLoader("$translateStaticFilesLoader",a)},this.useLoader=function(a,b){return o=a,p=b||{},this},this.useLocalStorage=function(){return this.useStorage("$translateLocalStorage")},this.useCookieStorage=function(){return this.useStorage("$translateCookieStorage")},this.useStorage=function(a){return k=a,this},this.storagePrefix=function(a){return a?(l=a,this):a},this.useMissingTranslationHandlerLog=function(){return this.useMissingTranslationHandler("$translateMissingTranslationHandlerLog")},this.useMissingTranslationHandler=function(a){return m=a,this},this.usePostCompiling=function(a){return y=!!a,this},this.forceAsyncReload=function(a){return z=!!a,this},this.uniformLanguageTag=function(a){return a?angular.isString(a)&&(a={standard:a}):a={},E=a.standard,this},this.determinePreferredLanguage=function(a){var b=a&&angular.isFunction(a)?a():I();return e=u.length?L(b)||b:b,this},this.registerAvailableLanguageKeys=function(a,b){return a?(u=a,b&&(f=b),this):u},this.useLoaderCache=function(a){return a===!1?s=void 0:a===!0?s=!0:"undefined"==typeof a?s="$translationCache":a&&(s=a),this},this.directivePriority=function(a){return void 0===a?C:(C=a,this)},this.statefulFilter=function(a){return void 0===a?D:(D=a,this)},this.$get=["$log","$injector","$rootScope","$q",function(a,b,c,d){var f,l,u,E=b.get(n||"$translateDefaultInterpolation"),F=!1,H={},I={},R=function(a,b,c,h,j){var m=j&&j!==i?L(j)||j:i;if(angular.isArray(a)){var n=function(a){for(var e={},f=[],g=function(a){var f=d.defer(),g=function(b){e[a]=b,f.resolve([a,b])};return R(a,b,c,h,j).then(g,g),f.promise},i=0,k=a.length;k>i;i++)f.push(g(a[i]));return d.all(f).then(function(){return e})};return n(a)}var o=d.defer();a&&(a=K.apply(a));var p=function(){var a=e?I[e]:I[m];if(l=0,k&&!a){var b=f.get(v);if(a=I[b],g&&g.length){var c=J(g,b);l=0===c?1:0,J(g,e)<0&&g.push(e)}}return a}();if(p){var q=function(){j||(m=i),ca(a,b,c,h,m).then(o.resolve,o.reject)};q.displayName="promiseResolved",p["finally"](q)}else ca(a,b,c,h,m).then(o.resolve,o.reject);return o.promise},S=function(a){return q&&(a=[q,a].join(" ")),r&&(a=[a,r].join(" ")),a},T=function(a){i=a,k&&f.put(R.storageKey(),i),c.$emit("$translateChangeSuccess",{language:a}),E.setLocale(i);var b=function(a,b){H[b].setLocale(i)};b.displayName="eachInterpolatorLocaleSetter",angular.forEach(H,b),c.$emit("$translateChangeEnd",{language:a})},U=function(a){if(!a)throw"No language key specified for loading.";var e=d.defer();c.$emit("$translateLoadingStart",{language:a}),F=!0;var f=s;"string"==typeof f&&(f=b.get(f));var g=angular.extend({},p,{key:a,$http:angular.extend({},{cache:f},p.$http)}),h=function(b){var d={};c.$emit("$translateLoadingSuccess",{language:a}),angular.isArray(b)?angular.forEach(b,function(a){angular.extend(d,N(a))}):angular.extend(d,N(b)),F=!1,e.resolve({key:a,table:d}),c.$emit("$translateLoadingEnd",{language:a})};h.displayName="onLoaderSuccess";var i=function(a){c.$emit("$translateLoadingError",{language:a}),e.reject(a),c.$emit("$translateLoadingEnd",{language:a})};return i.displayName="onLoaderError",b.get(o)(g).then(h,i),e.promise};if(k&&(f=b.get(k),!f.get||!f.put))throw new Error("Couldn't use storage '"+k+"', missing get() or put() method!");if(w.length){var V=function(a){var c=b.get(a);c.setLocale(e||i),H[c.getInterpolationIdentifier()]=c};V.displayName="interpolationFactoryAdder",angular.forEach(w,V)}var W=function(a){var b=d.defer();if(Object.prototype.hasOwnProperty.call(t,a))b.resolve(t[a]);else if(I[a]){var c=function(a){M(a.key,a.table),b.resolve(a.table)};c.displayName="translationTableResolver",I[a].then(c,b.reject)}else b.reject();return b.promise},X=function(a,b,c,e){var f=d.defer(),g=function(d){if(Object.prototype.hasOwnProperty.call(d,b)){e.setLocale(a);var g=d[b];"@:"===g.substr(0,2)?X(a,g.substr(2),c,e).then(f.resolve,f.reject):f.resolve(e.interpolate(d[b],c)),e.setLocale(i)}else f.reject()};return g.displayName="fallbackTranslationResolver",W(a).then(g,f.reject),f.promise},Y=function(a,b,c,d){var e,f=t[a];if(f&&Object.prototype.hasOwnProperty.call(f,b)){if(d.setLocale(a),e=d.interpolate(f[b],c),"@:"===e.substr(0,2))return Y(a,e.substr(2),c,d);d.setLocale(i)}return e},Z=function(a,c){if(m){var d=b.get(m)(a,i,c);return void 0!==d?d:a}return a},$=function(a,b,c,e,f){var h=d.defer();if(a<g.length){var i=g[a];X(i,b,c,e).then(h.resolve,function(){$(a+1,b,c,e,f).then(h.resolve)})}else f?h.resolve(f):h.resolve(Z(b,c));return h.promise},_=function(a,b,c,d){var e;if(a<g.length){var f=g[a];e=Y(f,b,c,d),e||(e=_(a+1,b,c,d))}return e},aa=function(a,b,c,d){return $(u>0?u:l,a,b,c,d)},ba=function(a,b,c){return _(u>0?u:l,a,b,c)},ca=function(a,b,c,e,f){var h=d.defer(),i=f?t[f]:t,j=c?H[c]:E;if(i&&Object.prototype.hasOwnProperty.call(i,a)){var k=i[a];"@:"===k.substr(0,2)?R(k.substr(2),b,c,e,f).then(h.resolve,h.reject):h.resolve(j.interpolate(k,b))}else{var l;m&&!F&&(l=Z(a,b)),f&&g&&g.length?aa(a,b,j,e).then(function(a){h.resolve(a)},function(a){h.reject(S(a))}):m&&!F&&l?e?h.resolve(e):h.resolve(l):e?h.resolve(e):h.reject(S(a))}return h.promise},da=function(a,b,c,d){var e,f=d?t[d]:t,h=E;if(H&&Object.prototype.hasOwnProperty.call(H,c)&&(h=H[c]),f&&Object.prototype.hasOwnProperty.call(f,a)){var i=f[a];e="@:"===i.substr(0,2)?da(i.substr(2),b,c,d):h.interpolate(i,b)}else{var j;m&&!F&&(j=Z(a,b)),d&&g&&g.length?(l=0,e=ba(a,b,h)):e=m&&!F&&j?j:S(a)}return e},ea=function(a){j===a&&(j=void 0),I[a]=void 0};R.preferredLanguage=function(a){return a&&O(a),e},R.cloakClassName=function(){return x},R.nestedObjectDelimeter=function(){return A},R.fallbackLanguage=function(a){if(void 0!==a&&null!==a){if(P(a),o&&g&&g.length)for(var b=0,c=g.length;c>b;b++)I[g[b]]||(I[g[b]]=U(g[b]));R.use(R.use())}return h?g[0]:g},R.useFallbackLanguage=function(a){if(void 0!==a&&null!==a)if(a){var b=J(g,a);b>-1&&(u=b)}else u=0},R.proposedLanguage=function(){return j},R.storage=function(){return f},R.negotiateLocale=L,R.use=function(a){if(!a)return i;var b=d.defer();c.$emit("$translateChangeStart",{language:a});var e=L(a);return e&&(a=e),j=a,!z&&t[a]||!o||I[a]?I[a]?I[a].then(function(a){return j===a.key&&T(a.key),b.resolve(a.key),a},function(a){return!i&&g&&g.length>0?R.use(g[0]).then(b.resolve,b.reject):b.reject(a)}):(b.resolve(a),T(a)):(I[a]=U(a).then(function(c){return M(c.key,c.table),b.resolve(c.key),j===a&&T(c.key),c},function(a){return c.$emit("$translateChangeError",{language:a}),b.reject(a),c.$emit("$translateChangeEnd",{language:a}),d.reject(a)}),I[a]["finally"](function(){ea(a)})),b.promise},R.storageKey=function(){return Q()},R.isPostCompilingEnabled=function(){return y},R.isForceAsyncReloadEnabled=function(){return z},R.refresh=function(a){function b(){f.resolve(),c.$emit("$translateRefreshEnd",{language:a})}function e(){f.reject(),c.$emit("$translateRefreshEnd",{language:a})}if(!o)throw new Error("Couldn't refresh translation table, no loader registered!");var f=d.defer();if(c.$emit("$translateRefreshStart",{language:a}),a)if(t[a]){var h=function(c){M(c.key,c.table),a===i&&T(i),b()};h.displayName="refreshPostProcessor",U(a).then(h,e)}else e();else{var j=[],k={};if(g&&g.length)for(var l=0,m=g.length;m>l;l++)j.push(U(g[l])),k[g[l]]=!0;i&&!k[i]&&j.push(U(i));var n=function(a){t={},angular.forEach(a,function(a){M(a.key,a.table)}),i&&T(i),b()};n.displayName="refreshPostProcessor",d.all(j).then(n,e)}return f.promise},R.instant=function(a,b,c,d){var f=d&&d!==i?L(d)||d:i;if(null===a||angular.isUndefined(a))return a;if(angular.isArray(a)){for(var h={},j=0,k=a.length;k>j;j++)h[a[j]]=R.instant(a[j],b,c,d);return h}if(angular.isString(a)&&a.length<1)return a;a&&(a=K.apply(a));var l,n=[];e&&n.push(e),f&&n.push(f),g&&g.length&&(n=n.concat(g));for(var o=0,p=n.length;p>o;o++){var s=n[o];if(t[s]&&"undefined"!=typeof t[s][a]&&(l=da(a,b,c,f)),"undefined"!=typeof l)break}return l||""===l||(q||r?l=S(a):(l=E.interpolate(a,b),m&&!F&&(l=Z(a,b)))),l},R.versionInfo=function(){return G},R.loaderCache=function(){return s},R.directivePriority=function(){return C},R.statefulFilter=function(){return D},R.isReady=function(){return B};var fa=d.defer();fa.promise.then(function(){B=!0}),R.onReady=function(a){var b=d.defer();return angular.isFunction(a)&&b.promise.then(a),B?b.resolve():fa.promise.then(b.resolve),b.promise};var ga=c.$on("$translateReady",function(){fa.resolve(),ga(),ga=null}),ha=c.$on("$translateChangeEnd",function(){fa.resolve(),ha(),ha=null});if(o){if(angular.equals(t,{})&&R.use()&&R.use(R.use()),g&&g.length)for(var ia=function(a){return M(a.key,a.table),c.$emit("$translateChangeEnd",{language:a.key}),a},ja=0,ka=g.length;ka>ja;ja++){var la=g[ja];(z||!t[la])&&(I[la]=U(la).then(ia))}}else c.$emit("$translateReady",{language:R.use()});return R}]}function d(a,b){"use strict";var c,d={},e="default";return d.setLocale=function(a){c=a},d.getInterpolationIdentifier=function(){return e},d.useSanitizeValueStrategy=function(a){return b.useStrategy(a),this},d.interpolate=function(c,d){d=d||{},d=b.sanitize(d,"params");var e=a(c)(d);return e=b.sanitize(e,"text")},d}function e(a,b,c,d,e,g){"use strict";var h=function(){return this.toString().replace(/^\s+|\s+$/g,"")};return{restrict:"AE",scope:!0,priority:a.directivePriority(),compile:function(b,i){var j=i.translateValues?i.translateValues:void 0,k=i.translateInterpolation?i.translateInterpolation:void 0,l=b[0].outerHTML.match(/translate-value-+/i),m="^(.*)("+c.startSymbol()+".*"+c.endSymbol()+")(.*)",n="^(.*)"+c.startSymbol()+"(.*)"+c.endSymbol()+"(.*)";return function(b,o,p){b.interpolateParams={},b.preText="",b.postText="",b.translateNamespace=f(b);var q={},r=function(a,c,d){if(c.translateValues&&angular.extend(a,e(c.translateValues)(b.$parent)),l)for(var f in d)if(Object.prototype.hasOwnProperty.call(c,f)&&"translateValue"===f.substr(0,14)&&"translateValues"!==f){var g=angular.lowercase(f.substr(14,1))+f.substr(15);a[g]=d[f]}},s=function(a){if(angular.isFunction(s._unwatchOld)&&(s._unwatchOld(),s._unwatchOld=void 0),angular.equals(a,"")||!angular.isDefined(a)){var d=h.apply(o.text()),e=d.match(m);if(angular.isArray(e)){b.preText=e[1],b.postText=e[3],q.translate=c(e[2])(b.$parent);var f=d.match(n);angular.isArray(f)&&f[2]&&f[2].length&&(s._unwatchOld=b.$watch(f[2],function(a){q.translate=a,y()}))}else q.translate=d?d:void 0}else q.translate=a;y()},t=function(a){p.$observe(a,function(b){q[a]=b,y()})};r(b.interpolateParams,p,i);var u=!0;p.$observe("translate",function(a){"undefined"==typeof a?s(""):""===a&&u||(q.translate=a,y()),u=!1});for(var v in p)p.hasOwnProperty(v)&&"translateAttr"===v.substr(0,13)&&t(v);if(p.$observe("translateDefault",function(a){b.defaultText=a,y()}),j&&p.$observe("translateValues",function(a){a&&b.$parent.$watch(function(){angular.extend(b.interpolateParams,e(a)(b.$parent))})}),l){var w=function(a){p.$observe(a,function(c){var d=angular.lowercase(a.substr(14,1))+a.substr(15);b.interpolateParams[d]=c})};for(var x in p)Object.prototype.hasOwnProperty.call(p,x)&&"translateValue"===x.substr(0,14)&&"translateValues"!==x&&w(x)}var y=function(){for(var a in q)q.hasOwnProperty(a)&&void 0!==q[a]&&z(a,q[a],b,b.interpolateParams,b.defaultText,b.translateNamespace)},z=function(b,c,d,e,f,g){c?(g&&"."===c.charAt(0)&&(c=g+c),a(c,e,k,f,d.translateLanguage).then(function(a){A(a,d,!0,b)},function(a){A(a,d,!1,b)})):A(c,d,!1,b)},A=function(b,c,e,f){if("translate"===f){e||"undefined"==typeof c.defaultText||(b=c.defaultText),o.empty().append(c.preText+b+c.postText);var g=a.isPostCompilingEnabled(),h="undefined"!=typeof i.translateCompile,j=h&&"false"!==i.translateCompile;(g&&!h||j)&&d(o.contents())(c)}else{e||"undefined"==typeof c.defaultText||(b=c.defaultText);var k=p.$attr[f];"data-"===k.substr(0,5)&&(k=k.substr(5)),k=k.substr(15),o.attr(k,b)}};(j||l||p.translateDefault)&&b.$watch("interpolateParams",y,!0),b.$watch("translateLanguage",y);var B=g.$on("$translateChangeSuccess",y);o.text().length?s(p.translate?p.translate:""):p.translate&&s(p.translate),y(),b.$on("$destroy",B)}}}}function f(a){"use strict";return a.translateNamespace?a.translateNamespace:a.$parent?f(a.$parent):void 0}function g(a,b){"use strict";return{compile:function(c){var d=function(){c.addClass(a.cloakClassName())},e=function(){c.removeClass(a.cloakClassName())};return a.onReady(function(){e()}),d(),function(c,f,g){g.translateCloak&&g.translateCloak.length&&(g.$observe("translateCloak",function(b){a(b).then(e,d)}),b.$on("$translateChangeSuccess",function(){a(g.translateCloak).then(e,d)}))}}}}function h(){"use strict";return{restrict:"A",scope:!0,compile:function(){return{pre:function(a,b,c){a.translateNamespace=f(a),a.translateNamespace&&"."===c.translateNamespace.charAt(0)?a.translateNamespace+=c.translateNamespace:a.translateNamespace=c.translateNamespace}}}}}function f(a){"use strict";return a.translateNamespace?a.translateNamespace:a.$parent?f(a.$parent):void 0}function i(){"use strict";return{restrict:"A",scope:!0,compile:function(){return function(a,b,c){c.$observe("translateLanguage",function(b){a.translateLanguage=b})}}}}function j(a,b){"use strict";var c=function(c,d,e,f){return angular.isObject(d)||(d=a(d)(this)),b.instant(c,d,e,f)};return b.statefulFilter()&&(c.$stateful=!0),c}function k(a){"use strict";return a("translations")}return angular.module("pascalprecht.translate",["ng"]).run(a),a.$inject=["$translate"],a.displayName="runTranslate",angular.module("pascalprecht.translate").provider("$translateSanitization",b),angular.module("pascalprecht.translate").constant("pascalprechtTranslateOverrider",{}).provider("$translate",c),c.$inject=["$STORAGE_KEY","$windowProvider","$translateSanitizationProvider","pascalprechtTranslateOverrider"],c.displayName="displayName",angular.module("pascalprecht.translate").factory("$translateDefaultInterpolation",d),d.$inject=["$interpolate","$translateSanitization"],d.displayName="$translateDefaultInterpolation",angular.module("pascalprecht.translate").constant("$STORAGE_KEY","NG_TRANSLATE_LANG_KEY"),angular.module("pascalprecht.translate").directive("translate",e),e.$inject=["$translate","$q","$interpolate","$compile","$parse","$rootScope"],e.displayName="translateDirective",angular.module("pascalprecht.translate").directive("translateCloak",g),g.$inject=["$translate","$rootScope"],g.displayName="translateCloakDirective",angular.module("pascalprecht.translate").directive("translateNamespace",h),h.displayName="translateNamespaceDirective",angular.module("pascalprecht.translate").directive("translateLanguage",i),i.displayName="translateLanguageDirective",angular.module("pascalprecht.translate").filter("translate",j),j.$inject=["$parse","$translate"],j.displayName="translateFilterFactory",angular.module("pascalprecht.translate").factory("$translationCache",k),k.$inject=["$cacheFactory"],k.displayName="$translationCache","pascalprecht.translate"}); \ No newline at end of file