/*! * angular-translate - v2.8.1 - 2015-10-01 * * Copyright (c) 2015 The angular-translate team, Pascal Precht; Licensed MIT */ (function (root, factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module unless amdModuleId is set define([], function () { return (factory()); }); } else if (typeof exports === 'object') { // Node. Does not work with strict CommonJS, but // only CommonJS-like environments that support module.exports, // like Node. module.exports = factory(); } else { factory(); } }(this, function () { angular.module('pascalprecht.translate') /** * @ngdoc object * @name pascalprecht.translate.$translatePartialLoaderProvider * * @description * By using a $translatePartialLoaderProvider you can configure a list of a needed * translation parts directly during the configuration phase of your application's * lifetime. All parts you add by using this provider would be loaded by * angular-translate at the startup as soon as possible. */ .provider('$translatePartialLoader', $translatePartialLoader); function $translatePartialLoader() { 'use strict'; /** * @constructor * @name Part * * @description * Represents Part object to add and set parts at runtime. */ function Part(name, priority) { this.name = name; this.isActive = true; this.tables = {}; this.priority = priority || 0; } /** * @name parseUrl * @method * * @description * Returns a parsed url template string and replaces given target lang * and part name it. * * @param {string|function} urlTemplate - Either a string containing an url pattern (with * '{part}' and '{lang}') or a function(part, lang) * returning a string. * @param {string} targetLang - Language key for language to be used. * @return {string} Parsed url template string */ Part.prototype.parseUrl = function(urlTemplate, targetLang) { if (angular.isFunction(urlTemplate)) { return urlTemplate(this.name, targetLang); } return urlTemplate.replace(/\{part\}/g, this.name).replace(/\{lang\}/g, targetLang); }; Part.prototype.getTable = function(lang, $q, $http, $httpOptions, urlTemplate, errorHandler) { if (!this.tables[lang]) { var self = this; return $http(angular.extend({ method : 'GET', url: this.parseUrl(urlTemplate, lang) }, $httpOptions)) .then(function(result){ self.tables[lang] = result.data; return result.data; }, function() { if (errorHandler) { return errorHandler(self.name, lang) .then(function(data) { self.tables[lang] = data; return data; }, function() { return $q.reject(self.name); }); } else { return $q.reject(self.name); } }); } else { return $q.when(this.tables[lang]); } }; var parts = {}; function hasPart(name) { return Object.prototype.hasOwnProperty.call(parts, name); } function isStringValid(str) { return angular.isString(str) && str !== ''; } function isPartAvailable(name) { if (!isStringValid(name)) { throw new TypeError('Invalid type of a first argument, a non-empty string expected.'); } return (hasPart(name) && parts[name].isActive); } function deepExtend(dst, src) { for (var property in src) { if (src[property] && src[property].constructor && src[property].constructor === Object) { dst[property] = dst[property] || {}; deepExtend(dst[property], src[property]); } else { dst[property] = src[property]; } } return dst; } function getPrioritizedParts() { var prioritizedParts = []; for(var part in parts) { if (parts[part].isActive) { prioritizedParts.push(parts[part]); } } prioritizedParts.sort(function (a, b) { return a.priority - b.priority; }); return prioritizedParts; } /** * @ngdoc function * @name pascalprecht.translate.$translatePartialLoaderProvider#addPart * @methodOf pascalprecht.translate.$translatePartialLoaderProvider * * @description * Registers a new part of the translation table to be loaded once the * `angular-translate` gets into runtime phase. It does not actually load any * translation data, but only registers a part to be loaded in the future. * * @param {string} name A name of the part to add * @param {int} [priority=0] Sets the load priority of this part. * * @returns {object} $translatePartialLoaderProvider, so this method is chainable * @throws {TypeError} The method could throw a **TypeError** if you pass the param * of the wrong type. Please, note that the `name` param has to be a * non-empty **string**. */ this.addPart = function(name, priority) { if (!isStringValid(name)) { throw new TypeError('Couldn\'t add part, part name has to be a string!'); } if (!hasPart(name)) { parts[name] = new Part(name, priority); } parts[name].isActive = true; return this; }; /** * @ngdocs function * @name pascalprecht.translate.$translatePartialLoaderProvider#setPart * @methodOf pascalprecht.translate.$translatePartialLoaderProvider * * @description * Sets a translation table to the specified part. This method does not make the * specified part available, but only avoids loading this part from the server. * * @param {string} lang A language of the given translation table * @param {string} part A name of the target part * @param {object} table A translation table to set to the specified part * * @return {object} $translatePartialLoaderProvider, so this method is chainable * @throws {TypeError} The method could throw a **TypeError** if you pass params * of the wrong type. Please, note that the `lang` and `part` params have to be a * non-empty **string**s and the `table` param has to be an object. */ this.setPart = function (lang, part, table) { if (!isStringValid(lang)) { throw new TypeError('Couldn\'t set part.`lang` parameter has to be a string!'); } if (!isStringValid(part)) { throw new TypeError('Couldn\'t set part.`part` parameter has to be a string!'); } if (typeof table !== 'object' || table === null) { throw new TypeError('Couldn\'t set part. `table` parameter has to be an object!'); } if (!hasPart(part)) { parts[part] = new Part(part); parts[part].isActive = false; } parts[part].tables[lang] = table; return this; }; /** * @ngdoc function * @name pascalprecht.translate.$translatePartialLoaderProvider#deletePart * @methodOf pascalprecht.translate.$translatePartialLoaderProvider * * @description * Removes the previously added part of the translation data. So, `angular-translate` will not * load it at the startup. * * @param {string} name A name of the part to delete * * @returns {object} $translatePartialLoaderProvider, so this method is chainable * * @throws {TypeError} The method could throw a **TypeError** if you pass the param of the wrong * type. Please, note that the `name` param has to be a non-empty **string**. */ this.deletePart = function (name) { if (!isStringValid(name)) { throw new TypeError('Couldn\'t delete part, first arg has to be string.'); } if (hasPart(name)) { parts[name].isActive = false; } return this; }; /** * @ngdoc function * @name pascalprecht.translate.$translatePartialLoaderProvider#isPartAvailable * @methodOf pascalprecht.translate.$translatePartialLoaderProvider * * @description * Checks if the specific part is available. A part becomes available after it was added by the * `addPart` method. Available parts would be loaded from the server once the `angular-translate` * asks the loader to that. * * @param {string} name A name of the part to check * * @returns {boolean} Returns **true** if the part is available now and **false** if not. * * @throws {TypeError} The method could throw a **TypeError** if you pass the param of the wrong * type. Please, note that the `name` param has to be a non-empty **string**. */ this.isPartAvailable = isPartAvailable; /** * @ngdoc object * @name pascalprecht.translate.$translatePartialLoader * * @requires $q * @requires $http * @requires $injector * @requires $rootScope * @requires $translate * * @description * * @param {object} options Options object * * @throws {TypeError} */ this.$get = ['$rootScope', '$injector', '$q', '$http', function($rootScope, $injector, $q, $http) { /** * @ngdoc event * @name pascalprecht.translate.$translatePartialLoader#$translatePartialLoaderStructureChanged * @eventOf pascalprecht.translate.$translatePartialLoader * @eventType broadcast on root scope * * @description * A $translatePartialLoaderStructureChanged event is called when a state of the loader was * changed somehow. It could mean either some part is added or some part is deleted. Anyway when * you get this event the translation table is not longer current and has to be updated. * * @param {string} name A name of the part which is a reason why the event was fired */ var service = function(options) { if (!isStringValid(options.key)) { throw new TypeError('Unable to load data, a key is not a non-empty string.'); } if (!isStringValid(options.urlTemplate) && !angular.isFunction(options.urlTemplate)) { throw new TypeError('Unable to load data, a urlTemplate is not a non-empty string or not a function.'); } var errorHandler = options.loadFailureHandler; if (errorHandler !== undefined) { if (!angular.isString(errorHandler)) { throw new Error('Unable to load data, a loadFailureHandler is not a string.'); } else { errorHandler = $injector.get(errorHandler); } } var loaders = [], deferred = $q.defer(), prioritizedParts = getPrioritizedParts(); angular.forEach(prioritizedParts, function(part) { loaders.push( part.getTable(options.key, $q, $http, options.$http, options.urlTemplate, errorHandler) ); part.urlTemplate = options.urlTemplate; }); $q.all(loaders) .then(function() { var table = {}; angular.forEach(prioritizedParts, function(part) { deepExtend(table, part.tables[options.key]); }); deferred.resolve(table); }, function() { deferred.reject(options.key); } ); return deferred.promise; }; /** * @ngdoc function * @name pascalprecht.translate.$translatePartialLoader#addPart * @methodOf pascalprecht.translate.$translatePartialLoader * * @description * Registers a new part of the translation table. This method does not actually perform any xhr * requests to get translation data. The new parts will be loaded in order of priority from the server next time * `angular-translate` asks the loader to load translations. * * @param {string} name A name of the part to add * @param {int} [priority=0] Sets the load priority of this part. * * @returns {object} $translatePartialLoader, so this method is chainable * * @fires {$translatePartialLoaderStructureChanged} The $translatePartialLoaderStructureChanged * event would be fired by this method in case the new part affected somehow on the loaders * state. This way it means that there are a new translation data available to be loaded from * the server. * * @throws {TypeError} The method could throw a **TypeError** if you pass the param of the wrong * type. Please, note that the `name` param has to be a non-empty **string**. */ service.addPart = function(name, priority) { if (!isStringValid(name)) { throw new TypeError('Couldn\'t add part, first arg has to be a string'); } if (!hasPart(name)) { parts[name] = new Part(name, priority); $rootScope.$emit('$translatePartialLoaderStructureChanged', name); } else if (!parts[name].isActive) { parts[name].isActive = true; $rootScope.$emit('$translatePartialLoaderStructureChanged', name); } return service; }; /** * @ngdoc function * @name pascalprecht.translate.$translatePartialLoader#deletePart * @methodOf pascalprecht.translate.$translatePartialLoader * * @description * Deletes the previously added part of the translation data. The target part could be deleted * either logically or physically. When the data is deleted logically it is not actually deleted * from the browser, but the loader marks it as not active and prevents it from affecting on the * translations. If the deleted in such way part is added again, the loader will use the * previously loaded data rather than loading it from the server once more time. But if the data * is deleted physically, the loader will completely remove all information about it. So in case * of recycling this part will be loaded from the server again. * * @param {string} name A name of the part to delete * @param {boolean=} [removeData=false] An indicator if the loader has to remove a loaded * translation data physically. If the `removeData` if set to **false** the loaded data will not be * deleted physically and might be reused in the future to prevent an additional xhr requests. * * @returns {object} $translatePartialLoader, so this method is chainable * * @fires {$translatePartialLoaderStructureChanged} The $translatePartialLoaderStructureChanged * event would be fired by this method in case a part deletion process affects somehow on the * loaders state. This way it means that some part of the translation data is now deprecated and * the translation table has to be recompiled with the remaining translation parts. * * @throws {TypeError} The method could throw a **TypeError** if you pass some param of the * wrong type. Please, note that the `name` param has to be a non-empty **string** and * the `removeData` param has to be either **undefined** or **boolean**. */ service.deletePart = function(name, removeData) { if (!isStringValid(name)) { throw new TypeError('Couldn\'t delete part, first arg has to be string'); } if (removeData === undefined) { removeData = false; } else if (typeof removeData !== 'boolean') { throw new TypeError('Invalid type of a second argument, a boolean expected.'); } if (hasPart(name)) { var wasActive = parts[name].isActive; if (removeData) { var $translate = $injector.get('$translate'); var cache = $translate.loaderCache(); if (typeof(cache) === 'string') { // getting on-demand instance of loader cache = $injector.get(cache); } // Purging items from cache... if (typeof(cache) === 'object') { angular.forEach(parts[name].tables, function(value, key) { cache.remove(parts[name].parseUrl(parts[name].urlTemplate, key)); }); } delete parts[name]; } else { parts[name].isActive = false; } if (wasActive) { $rootScope.$emit('$translatePartialLoaderStructureChanged', name); } } return service; }; /** * @ngdoc function * @name pascalprecht.translate.$translatePartialLoader#isPartLoaded * @methodOf pascalprecht.translate.$translatePartialLoader * * @description * Checks if the registered translation part is loaded into the translation table. * * @param {string} name A name of the part * @param {string} lang A key of the language * * @returns {boolean} Returns **true** if the translation of the part is loaded to the translation table and **false** if not. * * @throws {TypeError} The method could throw a **TypeError** if you pass the param of the wrong * type. Please, note that the `name` and `lang` params have to be non-empty **string**. */ service.isPartLoaded = function(name, lang) { return angular.isDefined(parts[name]) && angular.isDefined(parts[name].tables[lang]); }; /** * @ngdoc function * @name pascalprecht.translate.$translatePartialLoader#getRegisteredParts * @methodOf pascalprecht.translate.$translatePartialLoader * * @description * Gets names of the parts that were added with the `addPart`. * * @returns {array} Returns array of registered parts, if none were registered then an empty array is returned. */ service.getRegisteredParts = function() { var registeredParts = []; angular.forEach(parts, function(p){ if(p.isActive) { registeredParts.push(p.name); } }); return registeredParts; }; /** * @ngdoc function * @name pascalprecht.translate.$translatePartialLoader#isPartAvailable * @methodOf pascalprecht.translate.$translatePartialLoader * * @description * Checks if a target translation part is available. The part becomes available just after it was * added by the `addPart` method. Part's availability does not mean that it was loaded from the * server, but only that it was added to the loader. The available part might be loaded next * time the loader is called. * * @param {string} name A name of the part to delete * * @returns {boolean} Returns **true** if the part is available now and **false** if not. * * @throws {TypeError} The method could throw a **TypeError** if you pass the param of the wrong * type. Please, note that the `name` param has to be a non-empty **string**. */ service.isPartAvailable = isPartAvailable; return service; }]; } $translatePartialLoader.displayName = '$translatePartialLoader'; return 'pascalprecht.translate'; }));