├── .eslintrc.json ├── .github └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE ├── README.md ├── bower.json ├── dist ├── tmhDynamicLocale.js ├── tmhDynamicLocale.min.js └── tmhDynamicLocale.min.js.map ├── karma.conf.js ├── karma.min.conf.js ├── package-lock.json ├── package.json ├── src ├── tmhDynamicLocale.d.ts └── tmhDynamicLocale.js ├── test └── tmhDynamicLocaleSpec.js ├── tmhDynamicLocale.min.js └── tmhDynamicLocale.min.js.map /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true 5 | }, 6 | "extends": "eslint:recommended", 7 | "parserOptions": { 8 | "ecmaVersion": 5 9 | }, 10 | "rules": { 11 | "indent": [ 12 | "error", 13 | 2 14 | ], 15 | "linebreak-style": [ 16 | "error", 17 | "unix" 18 | ], 19 | "quotes": [ 20 | "error", 21 | "single" 22 | ], 23 | "semi": [ 24 | "error", 25 | "always" 26 | ] 27 | }, 28 | "globals": { 29 | "angular": false, 30 | "define": false 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '42 21 * * 1' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v2 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v1 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v1 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v1 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | components 3 | .rcs 4 | *.swp 5 | .idea 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 8.9.1 4 | 5 | before_install: 6 | - npm install -g grunt-cli bower 7 | 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | CONTRIBUTING 2 | ============ 3 | 4 | * Open a [Pull Request (PR)](https://github.com/lgalfaso/angular-dynamic-locale/pull/new/master) 5 | * Make sure your PR is on a **new branch** you created off of the latest version of master 6 | * Do **not** open a PR from your master branch 7 | * Open a PR to start a discussion even if the code isn't finished (easier to collect feedback this way) 8 | * Make sure all previous tests pass and add new tests for added behaviors 9 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | var BANNER = 5 | '/**\n' + 6 | ' * Angular Dynamic Locale - <%= pkg.version %>\n' + 7 | ' * https://github.com/lgalfaso/angular-dynamic-locale\n' + 8 | ' * License: MIT\n' + 9 | ' */\n'; 10 | 11 | module.exports = function(grunt) { 12 | //grunt plugins 13 | grunt.loadNpmTasks('grunt-contrib-clean'); 14 | grunt.loadNpmTasks('grunt-contrib-copy'); 15 | grunt.loadNpmTasks('grunt-contrib-jshint'); 16 | grunt.loadNpmTasks('grunt-eslint'); 17 | grunt.loadNpmTasks('grunt-karma'); 18 | grunt.loadNpmTasks('grunt-bump'); 19 | grunt.loadNpmTasks('grunt-npm'); 20 | grunt.loadNpmTasks('grunt-contrib-uglify'); 21 | grunt.loadNpmTasks('grunt-contrib-concat'); 22 | 23 | grunt.initConfig({ 24 | pkg: grunt.file.readJSON('package.json'), 25 | jshint: { 26 | all: ['Gruntfile.js', 'src/*.js', 'test/*.js'] 27 | }, 28 | eslint: { 29 | target: ['src/**/*.js', 'test/**/*.js'], 30 | }, 31 | karma: { 32 | unit: { configFile: 'karma.conf.js' }, 33 | 'unit.min': { 34 | configFile: 'karma.min.conf.js' 35 | }, 36 | autotest: { 37 | configFile: 'karma.conf.js', 38 | autoWatch: true, 39 | singleRun: false 40 | }, 41 | travis: { 42 | configFile: 'karma.conf.js', 43 | reporters: 'dots', 44 | browsers: ['PhantomJS'] 45 | } 46 | }, 47 | concat: { 48 | options: { 49 | banner: BANNER, 50 | }, 51 | dist: { 52 | src: ['src/tmhDynamicLocale.js'], 53 | dest: 'dist/tmhDynamicLocale.js', 54 | }, 55 | }, 56 | uglify: { 57 | all: { 58 | files: { 59 | 'tmhDynamicLocale.min.js': ['src/*.js'], 60 | 'dist/tmhDynamicLocale.min.js': ['src/*.js'] 61 | }, 62 | options: { 63 | banner: BANNER, 64 | sourceMap: true 65 | } 66 | } 67 | }, 68 | bump: { 69 | options: { 70 | files: ['package.json', 'bower.json'], 71 | commitFiles: ['package.json', 'bower.json'], 72 | tagName: '%VERSION%' 73 | } 74 | }, 75 | 'npm-publish': { 76 | options: { 77 | requires: ['jshint', 'karma:unit'], 78 | abortIfDirty: true 79 | } 80 | } 81 | }); 82 | grunt.registerTask('release', ['jshint', 'eslint', 'karma:unit', 'concat', 'uglify:all', 'karma:unit.min', 'publish']); 83 | }; 84 | }()); 85 | 86 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2013 Lucas Galfasó 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular Dynamic Locale 2 | 3 | 4 | ### Requirements 5 | 6 | * **AngularJS v1.0.7+** is currently required. 7 | 8 | ### Changing the locale 9 | 10 | This module defines two services, these are `tmhDynamicLocale` and 11 | `tmhDynamicLocaleCache`. 12 | 13 | The service `tmhDynamicLocale` provides has one method `set(newLocale)` to 14 | change the locale. 15 | 16 | ```javascript 17 | tmhDynamicLocale.set('it'); 18 | ``` 19 | 20 | Keep in mind that the locale will be changed asynchronously 21 | 22 | 23 | After the locale is changed, the event `'$localeChangeSuccess'` will be 24 | triggered. 25 | 26 | Calling `tmhDynamicLocale.set` will return a promise that will be resolved 27 | when the locale is loaded and will resolve to the new locale. 28 | 29 | The service `tmhDynamicLocaleCache` is a `$cache` of all the loaded locales, 30 | where the key is the locale id and the value is the locale object. 31 | 32 | 33 | This module expects for the angular locales to be present at 34 | `angular/i18n/angular-locale_{{locale}}.js`. 35 | If the locales are at another URL, this can be changed at 36 | `tmhDynamicLocaleProvider` using `localeLocationPattern(string)`. 37 | 38 | 39 | It is possible to specify a storage location for the locale using 40 | `tmhDynamicLocaleProvider.useStorage(storageName)`, the name of the 41 | storage must follow the same signature as `$cookieStore`. The default 42 | storage location is to use a `$cache`, this default storage is not persistent. 43 | 44 | It is possible to ask the storage to be `$cookieStore` using the shortcut 45 | `tmhDynamicLocaleProvider.useCookieStorage()`, internally this is 46 | exactly as performing `tmhDynamicLocaleProvider.useStorage('$cookieStore')` 47 | 48 | Also it is possible to set a default language using `tmhDynamicLocaleProvider.defaultLocale(locale)`. 49 | 50 | ## Installation 51 | 52 | Using npm: 53 | 54 | `npm install angular-dynamic-locale` 55 | 56 | Using bower: 57 | 58 | `bower install angular-dynamic-locale` 59 | 60 | ## Usage 61 | 62 | Add the js file to your html: 63 | 64 | ```html 65 | 66 | ``` 67 | 68 | Add the module to your dependencies: 69 | 70 | ```javascript 71 | angular.module('myApp', ['tmh.dynamicLocale', ...]) 72 | ``` 73 | 74 | Inject `tmhDynamicLocale` to your controller and set the desired locale: 75 | 76 | ```javascript 77 | angular.module('myApp').controller('myController', [..., 'tmhDynamicLocale', 78 | function(..., tmhDynamicLocale) { 79 | tmhDynamicLocale.set('en'); 80 | } 81 | ]) 82 | ``` 83 | 84 | Also, here's an example of how you may optionally override the default path (`angular/i18n/angular-locale_{{locale}}.js`) to the stored Angular i18n locale files in your project: 85 | 86 | ```javascript 87 | angular.module('myApp', ['tmh.dynamicLocale']) 88 | .config(function(tmhDynamicLocaleProvider) { 89 | tmhDynamicLocaleProvider.localeLocationPattern('/base/node_modules/angular-i18n/angular-locale_{{locale}}.js'); 90 | }); 91 | ``` 92 | 93 | Lastly, if you need to set a default locale, you can do it like this: 94 | 95 | ```javascript 96 | angular.module('myApp', ['tmh.dynamicLocale']) 97 | .config(function(tmhDynamicLocaleProvider) { 98 | tmhDynamicLocaleProvider.defaultLocale('en'); 99 | }); 100 | ``` 101 | 102 | ## Development 103 | 104 | ### Requirements 105 | 106 | 0. Install [Node.js](http://nodejs.org/) and NPM (should come with) 107 | 108 | 1. Install global dependencies `grunt-cli` and `bower`: 109 | 110 | ```bash 111 | $ npm install -g grunt-cli bower 112 | ``` 113 | 114 | 2. Install local dependencies: 115 | 116 | ```bash 117 | $ npm install 118 | ``` 119 | 120 | ### Running the tests 121 | 122 | ```bash 123 | $ grunt karma:unit 124 | ``` 125 | to run the test once 126 | 127 | or 128 | 129 | ```bash 130 | $ grunt karma:autotest 131 | ``` 132 | to run the tests continuously 133 | 134 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Lucas Galfaso", 3 | "name": "angular-dynamic-locale", 4 | "main": "dist/tmhDynamicLocale.js", 5 | "description": "Angular Dynamic Locale", 6 | "ignore": [ 7 | "node_modules", 8 | "bower_components", 9 | "test" 10 | ], 11 | "version": "0.1.38" 12 | } 13 | -------------------------------------------------------------------------------- /dist/tmhDynamicLocale.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Angular Dynamic Locale - 0.1.38 3 | * https://github.com/lgalfaso/angular-dynamic-locale 4 | * License: MIT 5 | */ 6 | (function (root, factory) { 7 | if (typeof define === 'function' && define.amd) { 8 | // AMD. Register as an anonymous module unless amdModuleId is set 9 | define([], function () { 10 | return (factory()); 11 | }); 12 | } else if (typeof exports === 'object') { 13 | // Node. Does not work with strict CommonJS, but 14 | // only CommonJS-like environments that support module.exports, 15 | // like Node. 16 | module.exports = factory(); 17 | } else { 18 | factory(); 19 | } 20 | }(this, function () { 21 | 'use strict'; 22 | angular.module('tmh.dynamicLocale', []).config(['$provide', function($provide) { 23 | function makeStateful($delegate) { 24 | $delegate.$stateful = true; 25 | return $delegate; 26 | } 27 | 28 | $provide.decorator('dateFilter', ['$delegate', makeStateful]); 29 | $provide.decorator('numberFilter', ['$delegate', makeStateful]); 30 | $provide.decorator('currencyFilter', ['$delegate', makeStateful]); 31 | 32 | }]) 33 | .constant('tmhDynamicLocale.STORAGE_KEY', 'tmhDynamicLocale.locale') 34 | .provider('tmhDynamicLocale', ['tmhDynamicLocale.STORAGE_KEY', function(STORAGE_KEY) { 35 | 36 | var defaultLocale, 37 | localeLocationPattern = 'angular/i18n/angular-locale_{{locale}}.js', 38 | nodeToAppend, 39 | storageFactory = 'tmhDynamicLocaleStorageCache', 40 | storage, 41 | storageKey = STORAGE_KEY, 42 | storageGet = 'get', 43 | storagePut = 'put', 44 | promiseCache = {}, 45 | activeLocale, 46 | extraProperties = {}; 47 | 48 | /** 49 | * Loads a script asynchronously 50 | * 51 | * @param {string} url The url for the script 52 | @ @param {function} callback A function to be called once the script is loaded 53 | */ 54 | function loadScript(url, callback, errorCallback, $timeout) { 55 | var script = document.createElement('script'), 56 | element = nodeToAppend ? nodeToAppend : document.getElementsByTagName('body')[0], 57 | removed = false; 58 | 59 | script.type = 'text/javascript'; 60 | if (script.readyState) { // IE 61 | script.onreadystatechange = function () { 62 | if (script.readyState === 'complete' || 63 | script.readyState === 'loaded') { 64 | script.onreadystatechange = null; 65 | $timeout( 66 | function () { 67 | if (removed) return; 68 | removed = true; 69 | if (script.parentNode === element) { 70 | element.removeChild(script); 71 | } 72 | callback(); 73 | }, 30, false); 74 | } 75 | }; 76 | } else { // Others 77 | script.onload = function () { 78 | if (removed) return; 79 | removed = true; 80 | if (script.parentNode === element) { 81 | element.removeChild(script); 82 | } 83 | callback(); 84 | }; 85 | script.onerror = function () { 86 | if (removed) return; 87 | removed = true; 88 | if (script.parentNode === element) { 89 | element.removeChild(script); 90 | } 91 | errorCallback(); 92 | }; 93 | } 94 | script.src = url; 95 | script.async = true; 96 | element.appendChild(script); 97 | } 98 | 99 | /** 100 | * Loads a locale and replaces the properties from the current locale with the new locale information 101 | * 102 | * @param {string} localeUrl The path to the new locale 103 | * @param {Object} $locale The locale at the curent scope 104 | * @param {string} localeId The locale id to load 105 | * @param {Object} $rootScope The application $rootScope 106 | * @param {Object} $q The application $q 107 | * @param {Object} localeCache The current locale cache 108 | * @param {Object} $timeout The application $timeout 109 | */ 110 | function loadLocale(localeUrl, $locale, localeId, $rootScope, $q, localeCache, $timeout) { 111 | 112 | function overrideValues(oldObject, newObject) { 113 | if (activeLocale !== localeId) { 114 | return; 115 | } 116 | angular.forEach(oldObject, function(value, key) { 117 | if (!newObject[key]) { 118 | delete oldObject[key]; 119 | } else if (angular.isArray(newObject[key])) { 120 | oldObject[key].length = newObject[key].length; 121 | } 122 | }); 123 | angular.forEach(newObject, function(value, key) { 124 | if (angular.isArray(newObject[key]) || angular.isObject(newObject[key])) { 125 | if (!oldObject[key]) { 126 | oldObject[key] = angular.isArray(newObject[key]) ? [] : {}; 127 | } 128 | overrideValues(oldObject[key], newObject[key]); 129 | } else { 130 | oldObject[key] = newObject[key]; 131 | } 132 | }); 133 | } 134 | 135 | 136 | if (promiseCache[localeId]) { 137 | activeLocale = localeId; 138 | return promiseCache[localeId]; 139 | } 140 | 141 | var cachedLocale, 142 | deferred = $q.defer(); 143 | if (localeId === activeLocale) { 144 | deferred.resolve($locale); 145 | } else if ((cachedLocale = localeCache.get(localeId))) { 146 | activeLocale = localeId; 147 | $rootScope.$evalAsync(function() { 148 | overrideValues($locale, cachedLocale); 149 | storage[storagePut](storageKey, localeId); 150 | $rootScope.$broadcast('$localeChangeSuccess', localeId, $locale); 151 | deferred.resolve($locale); 152 | }); 153 | } else { 154 | activeLocale = localeId; 155 | promiseCache[localeId] = deferred.promise; 156 | loadScript(localeUrl, function() { 157 | // Create a new injector with the new locale 158 | var localInjector = angular.injector(['ngLocale']), 159 | externalLocale = localInjector.get('$locale'); 160 | 161 | overrideValues($locale, externalLocale); 162 | localeCache.put(localeId, externalLocale); 163 | delete promiseCache[localeId]; 164 | 165 | $rootScope.$applyAsync(function() { 166 | storage[storagePut](storageKey, localeId); 167 | $rootScope.$broadcast('$localeChangeSuccess', localeId, $locale); 168 | deferred.resolve($locale); 169 | }); 170 | }, function() { 171 | delete promiseCache[localeId]; 172 | 173 | $rootScope.$applyAsync(function() { 174 | if (activeLocale === localeId) { 175 | activeLocale = $locale.id; 176 | } 177 | $rootScope.$broadcast('$localeChangeError', localeId); 178 | deferred.reject(localeId); 179 | }); 180 | }, $timeout); 181 | } 182 | return deferred.promise; 183 | } 184 | 185 | this.localeLocationPattern = function(value) { 186 | if (value) { 187 | localeLocationPattern = value; 188 | return this; 189 | } else { 190 | return localeLocationPattern; 191 | } 192 | }; 193 | 194 | this.appendScriptTo = function(nodeElement) { 195 | nodeToAppend = nodeElement; 196 | }; 197 | 198 | this.useStorage = function(storageName) { 199 | storageFactory = storageName; 200 | storageGet = 'get'; 201 | storagePut = 'put'; 202 | }; 203 | 204 | this.useCookieStorage = function() { 205 | if (angular.version.minor < 7) { 206 | this.useStorage('$cookieStore'); 207 | } else { 208 | this.useStorage('$cookies'); 209 | storageGet = 'getObject'; 210 | storagePut = 'putObject'; 211 | } 212 | }; 213 | 214 | this.defaultLocale = function(value) { 215 | defaultLocale = value; 216 | }; 217 | 218 | this.storageKey = function(value) { 219 | if (value) { 220 | storageKey = value; 221 | return this; 222 | } else { 223 | return storageKey; 224 | } 225 | }; 226 | 227 | this.addLocalePatternValue = function(key, value) { 228 | extraProperties[key] = value; 229 | }; 230 | 231 | this.$get = ['$rootScope', '$injector', '$interpolate', '$locale', '$q', 'tmhDynamicLocaleCache', '$timeout', function($rootScope, $injector, interpolate, locale, $q, tmhDynamicLocaleCache, $timeout) { 232 | var localeLocation = interpolate(localeLocationPattern); 233 | 234 | storage = $injector.get(storageFactory); 235 | $rootScope.$evalAsync(function() { 236 | var initialLocale; 237 | if ((initialLocale = (storage[storageGet](storageKey) || defaultLocale))) { 238 | loadLocaleFn(initialLocale); 239 | } 240 | }); 241 | return { 242 | /** 243 | * @ngdoc method 244 | * @description 245 | * @param {string} value Sets the locale to the new locale. Changing the locale will trigger 246 | * a background task that will retrieve the new locale and configure the current $locale 247 | * instance with the information from the new locale 248 | */ 249 | set: loadLocaleFn, 250 | /** 251 | * @ngdoc method 252 | * @description Returns the configured locale 253 | */ 254 | get: function() { 255 | return activeLocale; 256 | } 257 | }; 258 | 259 | function loadLocaleFn(localeId) { 260 | var baseProperties = {locale: localeId, angularVersion: angular.version.full}; 261 | return loadLocale(localeLocation(angular.extend({}, extraProperties, baseProperties)), locale, localeId, $rootScope, $q, tmhDynamicLocaleCache, $timeout); 262 | } 263 | }]; 264 | }]).provider('tmhDynamicLocaleCache', function() { 265 | this.$get = ['$cacheFactory', function($cacheFactory) { 266 | return $cacheFactory('tmh.dynamicLocales'); 267 | }]; 268 | }).provider('tmhDynamicLocaleStorageCache', function() { 269 | this.$get = ['$cacheFactory', function($cacheFactory) { 270 | return $cacheFactory('tmh.dynamicLocales.store'); 271 | }]; 272 | }).run(['tmhDynamicLocale', angular.noop]); 273 | 274 | return 'tmh.dynamicLocale'; 275 | 276 | })); 277 | -------------------------------------------------------------------------------- /dist/tmhDynamicLocale.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Angular Dynamic Locale - 0.1.38 3 | * https://github.com/lgalfaso/angular-dynamic-locale 4 | * License: MIT 5 | */ 6 | !function(e){"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?module.exports=e():e()}(function(){"use strict";return angular.module("tmh.dynamicLocale",[]).config(["$provide",function(e){function t(e){return e.$stateful=!0,e}e.decorator("dateFilter",["$delegate",t]),e.decorator("numberFilter",["$delegate",t]),e.decorator("currencyFilter",["$delegate",t])}]).constant("tmhDynamicLocale.STORAGE_KEY","tmhDynamicLocale.locale").provider("tmhDynamicLocale",["tmhDynamicLocale.STORAGE_KEY",function(e){var u,y,p,$,s="angular/i18n/angular-locale_{{locale}}.js",d="tmhDynamicLocaleStorageCache",v=e,f="get",L="put",S={},g={};function h(e,t,o,n,a,r,c){function i(n,a){$===o&&(angular.forEach(n,function(e,t){a[t]?angular.isArray(a[t])&&(n[t].length=a[t].length):delete n[t]}),angular.forEach(a,function(e,t){angular.isArray(a[t])||angular.isObject(a[t])?(n[t]||(n[t]=angular.isArray(a[t])?[]:{}),i(n[t],a[t])):n[t]=a[t]}))}if(S[o])return S[$=o];var l,u,s,d,f,g,h,m=a.defer();return o===$?m.resolve(t):(l=r.get(o))?($=o,n.$evalAsync(function(){i(t,l),p[L](v,o),n.$broadcast("$localeChangeSuccess",o,t),m.resolve(t)})):(S[$=o]=m.promise,a=e,u=function(){var e=angular.injector(["ngLocale"]).get("$locale");i(t,e),r.put(o,e),delete S[o],n.$applyAsync(function(){p[L](v,o),n.$broadcast("$localeChangeSuccess",o,t),m.resolve(t)})},s=function(){delete S[o],n.$applyAsync(function(){$===o&&($=t.id),n.$broadcast("$localeChangeError",o),m.reject(o)})},d=c,f=document.createElement("script"),g=y||document.getElementsByTagName("body")[0],h=!1,f.type="text/javascript",f.readyState?f.onreadystatechange=function(){"complete"!==f.readyState&&"loaded"!==f.readyState||(f.onreadystatechange=null,d(function(){h||(h=!0,f.parentNode===g&&g.removeChild(f),u())},30,!1))}:(f.onload=function(){h||(h=!0,f.parentNode===g&&g.removeChild(f),u())},f.onerror=function(){h||(h=!0,f.parentNode===g&&g.removeChild(f),s())}),f.src=a,f.async=!0,g.appendChild(f)),m.promise}this.localeLocationPattern=function(e){return e?(s=e,this):s},this.appendScriptTo=function(e){y=e},this.useStorage=function(e){d=e,f="get",L="put"},this.useCookieStorage=function(){angular.version.minor<7?this.useStorage("$cookieStore"):(this.useStorage("$cookies"),f="getObject",L="putObject")},this.defaultLocale=function(e){u=e},this.storageKey=function(e){return e?(v=e,this):v},this.addLocalePatternValue=function(e,t){g[e]=t},this.$get=["$rootScope","$injector","$interpolate","$locale","$q","tmhDynamicLocaleCache","$timeout",function(n,e,t,a,o,r,c){var i=t(s);return p=e.get(d),n.$evalAsync(function(){var e;(e=p[f](v)||u)&&l(e)}),{set:l,get:function(){return $}};function l(e){var t={locale:e,angularVersion:angular.version.full};return h(i(angular.extend({},g,t)),a,e,n,o,r,c)}}]}]).provider("tmhDynamicLocaleCache",function(){this.$get=["$cacheFactory",function(e){return e("tmh.dynamicLocales")}]}).provider("tmhDynamicLocaleStorageCache",function(){this.$get=["$cacheFactory",function(e){return e("tmh.dynamicLocales.store")}]}).run(["tmhDynamicLocale",angular.noop]),"tmh.dynamicLocale"}); 7 | //# sourceMappingURL=tmhDynamicLocale.min.js.map -------------------------------------------------------------------------------- /dist/tmhDynamicLocale.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"tmhDynamicLocale.min.js","sources":["../src/tmhDynamicLocale.js"],"names":["factory","define","amd","exports","module","angular","config","$provide","makeStateful","$delegate","$stateful","decorator","constant","provider","STORAGE_KEY","defaultLocale","nodeToAppend","storage","activeLocale","localeLocationPattern","storageFactory","storageKey","storageGet","storagePut","promiseCache","extraProperties","loadLocale","localeUrl","$locale","localeId","$rootScope","$q","localeCache","$timeout","overrideValues","oldObject","newObject","forEach","value","key","isArray","length","isObject","cachedLocale","callback","errorCallback","script","element","removed","deferred","defer","resolve","get","$evalAsync","$broadcast","promise","url","externalLocale","injector","put","$applyAsync","id","reject","document","createElement","getElementsByTagName","type","readyState","onreadystatechange","parentNode","removeChild","onload","onerror","src","async","appendChild","this","appendScriptTo","nodeElement","useStorage","storageName","useCookieStorage","version","minor","addLocalePatternValue","$get","$injector","interpolate","locale","tmhDynamicLocaleCache","localeLocation","initialLocale","loadLocaleFn","set","baseProperties","angularVersion","full","extend","$cacheFactory","run","noop"],"mappings":"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"} -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | config.set({ 3 | autoWatch: false, 4 | singleRun: true, 5 | logLevel: config.LOG_INFO, 6 | logColors: true, 7 | browsers: ['Chrome'], 8 | browserDisconnectTimeout: 10000, 9 | browserNoActivityTimeout: 10000, 10 | files: [ 11 | 'node_modules/angular/angular.js', 12 | 'node_modules/angular-cookies/angular-cookies.js', 13 | 'node_modules/angular-mocks/angular-mocks.js', 14 | {pattern: 'node_modules/angular-i18n/*.js', included: false, served: true}, 15 | 'src/*.js', 16 | 'test/*Spec.js' 17 | ], 18 | junitReporter: { 19 | outputFile: 'test_out/unit.xml', 20 | suite: 'unit' 21 | }, 22 | frameworks: ['jasmine'] 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /karma.min.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | config.set({ 3 | autoWatch: false, 4 | singleRun: true, 5 | logLevel: config.LOG_INFO, 6 | logColors: true, 7 | browserDisconnectTimeout: 10000, 8 | browserNoActivityTimeout: 10000, 9 | browsers: ['Chrome'], 10 | files: [ 11 | 'node_modules/angular/angular.js', 12 | 'node_modules/angular-cookies/angular-cookies.js', 13 | 'node_modules/angular-mocks/angular-mocks.js', 14 | {pattern: 'node_modules/angular-i18n/*.js', included: false, served: true}, 15 | '*.min.js', 16 | 'test/*Spec.js' 17 | ], 18 | junitReporter: { 19 | outputFile: 'test_out/unit.xml', 20 | suite: 'unit' 21 | }, 22 | frameworks: ['jasmine'] 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-dynamic-locale", 3 | "version": "0.1.38", 4 | "description": "A minimal module that adds the ability to dynamically change the locale", 5 | "license": "MIT", 6 | "devDependencies": { 7 | "angular": "1.8.2", 8 | "angular-cookies": "1.8.2", 9 | "angular-i18n": "1.8.2", 10 | "angular-mocks": "1.8.2", 11 | "eslint": "^8.7.0", 12 | "eslint-config-google": "^0.14.0", 13 | "grunt": "^1.4.1", 14 | "grunt-bump": "^0.8.0", 15 | "grunt-contrib-clean": "^2.0.0", 16 | "grunt-contrib-concat": "^2.1.0", 17 | "grunt-contrib-copy": "^1.0.0", 18 | "grunt-contrib-jshint": "^3.1.1", 19 | "grunt-contrib-uglify": "^5.1.0", 20 | "grunt-contrib-watch": "^1.1.0", 21 | "grunt-eslint": "^24.0.0", 22 | "grunt-karma": "^4.0.2", 23 | "grunt-npm": "0.0.2", 24 | "jasmine-core": "^4.0.1", 25 | "karma": "^6.3.11", 26 | "karma-chrome-launcher": "^3.1.1", 27 | "karma-firefox-launcher": "^2.1.2", 28 | "karma-jasmine": "4.0.2", 29 | "karma-phantomjs-launcher": "1.0.4" 30 | }, 31 | "main": "./src/tmhDynamicLocale.js", 32 | "scripts": { 33 | "test": "grunt karma:travis" 34 | }, 35 | "repository": { 36 | "type": "git", 37 | "url": "git://github.com/lgalfaso/angular-dynamic-locale.git" 38 | }, 39 | "types": "./src/tmhDynamicLocale.d.ts", 40 | "dependencies": { 41 | "@types/angular": "^1.8.4" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/tmhDynamicLocale.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for tmhDynamicLocale 2 | // Project: http://angularjs.org 3 | // Definitions by: Gerardo Lima 4 | // Definitions: https://github.com/lgalfaso/angular-dynamic-locale 5 | // TypeScript Version: 2.4.1 6 | 7 | import * as ng from 'angular'; 8 | 9 | export as namespace tmh; 10 | 11 | declare namespace tmh 12 | { 13 | interface IDynamicLocale 14 | { 15 | set(locale: string): ng.IPromise; 16 | get(): string; 17 | } 18 | interface IDynamicLocaleProvider 19 | { 20 | localeLocationPattern(locationPattern: string): void; 21 | defaultLocale(locale: string): void; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/tmhDynamicLocale.js: -------------------------------------------------------------------------------- 1 | (function (root, factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | // AMD. Register as an anonymous module unless amdModuleId is set 4 | define([], function () { 5 | return (factory()); 6 | }); 7 | } else if (typeof exports === 'object') { 8 | // Node. Does not work with strict CommonJS, but 9 | // only CommonJS-like environments that support module.exports, 10 | // like Node. 11 | module.exports = factory(); 12 | } else { 13 | factory(); 14 | } 15 | }(this, function () { 16 | 'use strict'; 17 | angular.module('tmh.dynamicLocale', []).config(['$provide', function($provide) { 18 | function makeStateful($delegate) { 19 | $delegate.$stateful = true; 20 | return $delegate; 21 | } 22 | 23 | $provide.decorator('dateFilter', ['$delegate', makeStateful]); 24 | $provide.decorator('numberFilter', ['$delegate', makeStateful]); 25 | $provide.decorator('currencyFilter', ['$delegate', makeStateful]); 26 | 27 | }]) 28 | .constant('tmhDynamicLocale.STORAGE_KEY', 'tmhDynamicLocale.locale') 29 | .provider('tmhDynamicLocale', ['tmhDynamicLocale.STORAGE_KEY', function(STORAGE_KEY) { 30 | 31 | var defaultLocale, 32 | localeLocationPattern = 'angular/i18n/angular-locale_{{locale}}.js', 33 | nodeToAppend, 34 | storageFactory = 'tmhDynamicLocaleStorageCache', 35 | storage, 36 | storageKey = STORAGE_KEY, 37 | storageGet = 'get', 38 | storagePut = 'put', 39 | promiseCache = {}, 40 | activeLocale, 41 | extraProperties = {}; 42 | 43 | /** 44 | * Loads a script asynchronously 45 | * 46 | * @param {string} url The url for the script 47 | @ @param {function} callback A function to be called once the script is loaded 48 | */ 49 | function loadScript(url, callback, errorCallback, $timeout) { 50 | var script = document.createElement('script'), 51 | element = nodeToAppend ? nodeToAppend : document.getElementsByTagName('body')[0], 52 | removed = false; 53 | 54 | script.type = 'text/javascript'; 55 | if (script.readyState) { // IE 56 | script.onreadystatechange = function () { 57 | if (script.readyState === 'complete' || 58 | script.readyState === 'loaded') { 59 | script.onreadystatechange = null; 60 | $timeout( 61 | function () { 62 | if (removed) return; 63 | removed = true; 64 | if (script.parentNode === element) { 65 | element.removeChild(script); 66 | } 67 | callback(); 68 | }, 30, false); 69 | } 70 | }; 71 | } else { // Others 72 | script.onload = function () { 73 | if (removed) return; 74 | removed = true; 75 | if (script.parentNode === element) { 76 | element.removeChild(script); 77 | } 78 | callback(); 79 | }; 80 | script.onerror = function () { 81 | if (removed) return; 82 | removed = true; 83 | if (script.parentNode === element) { 84 | element.removeChild(script); 85 | } 86 | errorCallback(); 87 | }; 88 | } 89 | script.src = url; 90 | script.async = true; 91 | element.appendChild(script); 92 | } 93 | 94 | /** 95 | * Loads a locale and replaces the properties from the current locale with the new locale information 96 | * 97 | * @param {string} localeUrl The path to the new locale 98 | * @param {Object} $locale The locale at the curent scope 99 | * @param {string} localeId The locale id to load 100 | * @param {Object} $rootScope The application $rootScope 101 | * @param {Object} $q The application $q 102 | * @param {Object} localeCache The current locale cache 103 | * @param {Object} $timeout The application $timeout 104 | */ 105 | function loadLocale(localeUrl, $locale, localeId, $rootScope, $q, localeCache, $timeout) { 106 | 107 | function overrideValues(oldObject, newObject) { 108 | if (activeLocale !== localeId) { 109 | return; 110 | } 111 | angular.forEach(oldObject, function(value, key) { 112 | if (!newObject[key]) { 113 | delete oldObject[key]; 114 | } else if (angular.isArray(newObject[key])) { 115 | oldObject[key].length = newObject[key].length; 116 | } 117 | }); 118 | angular.forEach(newObject, function(value, key) { 119 | if (angular.isArray(newObject[key]) || angular.isObject(newObject[key])) { 120 | if (!oldObject[key]) { 121 | oldObject[key] = angular.isArray(newObject[key]) ? [] : {}; 122 | } 123 | overrideValues(oldObject[key], newObject[key]); 124 | } else { 125 | oldObject[key] = newObject[key]; 126 | } 127 | }); 128 | } 129 | 130 | 131 | if (promiseCache[localeId]) { 132 | activeLocale = localeId; 133 | return promiseCache[localeId]; 134 | } 135 | 136 | var cachedLocale, 137 | deferred = $q.defer(); 138 | if (localeId === activeLocale) { 139 | deferred.resolve($locale); 140 | } else if ((cachedLocale = localeCache.get(localeId))) { 141 | activeLocale = localeId; 142 | $rootScope.$evalAsync(function() { 143 | overrideValues($locale, cachedLocale); 144 | storage[storagePut](storageKey, localeId); 145 | $rootScope.$broadcast('$localeChangeSuccess', localeId, $locale); 146 | deferred.resolve($locale); 147 | }); 148 | } else { 149 | activeLocale = localeId; 150 | promiseCache[localeId] = deferred.promise; 151 | loadScript(localeUrl, function() { 152 | // Create a new injector with the new locale 153 | var localInjector = angular.injector(['ngLocale']), 154 | externalLocale = localInjector.get('$locale'); 155 | 156 | overrideValues($locale, externalLocale); 157 | localeCache.put(localeId, externalLocale); 158 | delete promiseCache[localeId]; 159 | 160 | $rootScope.$applyAsync(function() { 161 | storage[storagePut](storageKey, localeId); 162 | $rootScope.$broadcast('$localeChangeSuccess', localeId, $locale); 163 | deferred.resolve($locale); 164 | }); 165 | }, function() { 166 | delete promiseCache[localeId]; 167 | 168 | $rootScope.$applyAsync(function() { 169 | if (activeLocale === localeId) { 170 | activeLocale = $locale.id; 171 | } 172 | $rootScope.$broadcast('$localeChangeError', localeId); 173 | deferred.reject(localeId); 174 | }); 175 | }, $timeout); 176 | } 177 | return deferred.promise; 178 | } 179 | 180 | this.localeLocationPattern = function(value) { 181 | if (value) { 182 | localeLocationPattern = value; 183 | return this; 184 | } else { 185 | return localeLocationPattern; 186 | } 187 | }; 188 | 189 | this.appendScriptTo = function(nodeElement) { 190 | nodeToAppend = nodeElement; 191 | }; 192 | 193 | this.useStorage = function(storageName) { 194 | storageFactory = storageName; 195 | storageGet = 'get'; 196 | storagePut = 'put'; 197 | }; 198 | 199 | this.useCookieStorage = function() { 200 | if (angular.version.minor < 7) { 201 | this.useStorage('$cookieStore'); 202 | } else { 203 | this.useStorage('$cookies'); 204 | storageGet = 'getObject'; 205 | storagePut = 'putObject'; 206 | } 207 | }; 208 | 209 | this.defaultLocale = function(value) { 210 | defaultLocale = value; 211 | }; 212 | 213 | this.storageKey = function(value) { 214 | if (value) { 215 | storageKey = value; 216 | return this; 217 | } else { 218 | return storageKey; 219 | } 220 | }; 221 | 222 | this.addLocalePatternValue = function(key, value) { 223 | extraProperties[key] = value; 224 | }; 225 | 226 | this.$get = ['$rootScope', '$injector', '$interpolate', '$locale', '$q', 'tmhDynamicLocaleCache', '$timeout', function($rootScope, $injector, interpolate, locale, $q, tmhDynamicLocaleCache, $timeout) { 227 | var localeLocation = interpolate(localeLocationPattern); 228 | 229 | storage = $injector.get(storageFactory); 230 | $rootScope.$evalAsync(function() { 231 | var initialLocale; 232 | if ((initialLocale = (storage[storageGet](storageKey) || defaultLocale))) { 233 | loadLocaleFn(initialLocale); 234 | } 235 | }); 236 | return { 237 | /** 238 | * @ngdoc method 239 | * @description 240 | * @param {string} value Sets the locale to the new locale. Changing the locale will trigger 241 | * a background task that will retrieve the new locale and configure the current $locale 242 | * instance with the information from the new locale 243 | */ 244 | set: loadLocaleFn, 245 | /** 246 | * @ngdoc method 247 | * @description Returns the configured locale 248 | */ 249 | get: function() { 250 | return activeLocale; 251 | } 252 | }; 253 | 254 | function loadLocaleFn(localeId) { 255 | var baseProperties = {locale: localeId, angularVersion: angular.version.full}; 256 | return loadLocale(localeLocation(angular.extend({}, extraProperties, baseProperties)), locale, localeId, $rootScope, $q, tmhDynamicLocaleCache, $timeout); 257 | } 258 | }]; 259 | }]).provider('tmhDynamicLocaleCache', function() { 260 | this.$get = ['$cacheFactory', function($cacheFactory) { 261 | return $cacheFactory('tmh.dynamicLocales'); 262 | }]; 263 | }).provider('tmhDynamicLocaleStorageCache', function() { 264 | this.$get = ['$cacheFactory', function($cacheFactory) { 265 | return $cacheFactory('tmh.dynamicLocales.store'); 266 | }]; 267 | }).run(['tmhDynamicLocale', angular.noop]); 268 | 269 | return 'tmh.dynamicLocale'; 270 | 271 | })); 272 | -------------------------------------------------------------------------------- /test/tmhDynamicLocaleSpec.js: -------------------------------------------------------------------------------- 1 | /* global describe:false, beforeEach:false, afterEach:false, inject:false, 2 | it:false, inject:false, expect:false, jasmine:false */ 3 | (function () { 4 | 'use strict'; 5 | 6 | // Minimal implementation to mock what was removed from Jasmine 1.x 7 | function createAsync(doneFn) { 8 | function Job() { 9 | this.next = []; 10 | } 11 | Job.prototype.done = function () { 12 | return this.runs(doneFn); 13 | }; 14 | Job.prototype.runs = function (fn) { 15 | var newJob = new Job(); 16 | this.next.push(function () { 17 | fn(); 18 | newJob.start(); 19 | }); 20 | return newJob; 21 | }; 22 | Job.prototype.waitsFor = function (fn, error, timeout) { 23 | var newJob = new Job(); 24 | timeout = timeout || 5000; 25 | this.next.push(function () { 26 | var counter = 0, 27 | intervalId = window.setInterval(function () { 28 | if (fn()) { 29 | window.clearInterval(intervalId); 30 | newJob.start(); 31 | } 32 | counter += 5; 33 | if (counter > timeout) { 34 | window.clearInterval(intervalId); 35 | throw new Error(error); 36 | } 37 | }, 5); 38 | }); 39 | return newJob; 40 | }; 41 | Job.prototype.start = function () { 42 | var i; 43 | for (i = 0; i < this.next.length; i += 1) { 44 | this.next[i](); 45 | } 46 | }; 47 | return new Job(); 48 | } 49 | 50 | describe('dynamicLocale', function() { 51 | beforeEach(module('tmh.dynamicLocale')); 52 | beforeEach(module(function(tmhDynamicLocaleProvider) { 53 | tmhDynamicLocaleProvider.localeLocationPattern('/base/node_modules/angular-i18n/angular-locale_{{locale}}.js'); 54 | })); 55 | 56 | afterEach(function (done) { 57 | inject(function($locale, $timeout, tmhDynamicLocale) { 58 | var job = createAsync(done); 59 | job 60 | .runs(function() { 61 | tmhDynamicLocale.set('en-us'); 62 | }) 63 | .waitsFor(function() { 64 | $timeout.flush(50); 65 | return $locale.id === 'en-us'; 66 | }, 'locale not reverted', 2000) 67 | .done(); 68 | job.start(); 69 | }); 70 | }); 71 | 72 | it('should (eventually) be able to change the locale', function(done) { 73 | inject(function($locale, $timeout, tmhDynamicLocale) { 74 | var job = createAsync(done); 75 | job 76 | .runs(function() { 77 | tmhDynamicLocale.set('es'); 78 | }) 79 | .waitsFor(function() { 80 | $timeout.flush(50); 81 | return $locale.id === 'es'; 82 | }, 'locale not updated', 2000) 83 | .runs(function() { 84 | expect($locale.id).toBe('es'); 85 | expect($locale.DATETIME_FORMATS.DAY['0']).toBe('domingo'); 86 | }) 87 | .done(); 88 | job.start(); 89 | }); 90 | }); 91 | 92 | it('should be able to change the locale back-and-forward within one digest', function(done) { 93 | inject(function($rootScope, $locale, $timeout, tmhDynamicLocale) { 94 | var job = createAsync(done); 95 | job 96 | .runs(function() { 97 | $rootScope.$apply(function() { 98 | tmhDynamicLocale.set('es'); 99 | tmhDynamicLocale.set('en'); 100 | tmhDynamicLocale.set('es'); 101 | }); 102 | }) 103 | .waitsFor(function() { 104 | $timeout.flush(50); 105 | return $locale.id === 'es'; 106 | }, 'locale not updated', 2000) 107 | .runs(function() { 108 | expect($locale.id).toBe('es'); 109 | expect($locale.DATETIME_FORMATS.DAY['0']).toBe('domingo'); 110 | }) 111 | .done(); 112 | job.start(); 113 | }); 114 | }); 115 | 116 | it('should trigger an event when there it changes the locale', function(done) { 117 | inject(function($timeout, $locale, tmhDynamicLocale, $rootScope) { 118 | var callback = jasmine.createSpy(); 119 | var job = createAsync(done); 120 | job 121 | .runs(function() { 122 | $rootScope.$apply(); 123 | $rootScope.$on('$localeChangeSuccess', callback); 124 | tmhDynamicLocale.set('es'); 125 | expect(callback.calls.count()).toBe(0); 126 | }) 127 | .waitsFor(function() { 128 | $timeout.flush(50); 129 | return $locale.id === 'es'; 130 | }, 'locale not updated', 2000) 131 | .runs(function() { 132 | expect(callback.calls.count()).toBe(1); 133 | expect(callback.calls.argsFor(0)[1]).toEqual('es'); 134 | expect(callback.calls.argsFor(0)[2]).toEqual($locale); 135 | }) 136 | .done(); 137 | job.start(); 138 | }); 139 | }); 140 | 141 | it('should trigger a failure even when the locale change fail', function(done) { 142 | inject(function($timeout, $locale, tmhDynamicLocale, $rootScope) { 143 | var job = createAsync(done); 144 | var callback = jasmine.createSpy(); 145 | 146 | job 147 | .runs(function() { 148 | $rootScope.$apply(); 149 | $rootScope.$on('$localeChangeError', callback); 150 | tmhDynamicLocale.set('invalidLocale').catch(function() {}); 151 | expect(callback.calls.count()).toBe(0); 152 | }) 153 | .waitsFor(function() { 154 | $timeout.flush(50); 155 | return callback.calls.count() !== 0; 156 | }, 'error not generated', 2000) 157 | .runs(function() { 158 | expect(callback.calls.count()).toBe(1); 159 | expect(callback.calls.argsFor(0)[1]).toEqual('invalidLocale'); 160 | }) 161 | .done(); 162 | job.start(); 163 | }); 164 | }); 165 | 166 | it('should return a promise that has the new locale', function(done) { 167 | inject(function($timeout, $locale, tmhDynamicLocale, $rootScope) { 168 | var job = createAsync(done); 169 | var callback = jasmine.createSpy(); 170 | 171 | job 172 | .runs(function() { 173 | tmhDynamicLocale.set('es').then(callback); 174 | expect(callback.calls.count()).toBe(0); 175 | }) 176 | .waitsFor(function() { 177 | $timeout.flush(50); 178 | return callback.calls.count() !== 0; 179 | }, 'locale not updated', 2000) 180 | .runs(function() { 181 | expect(callback.calls.argsFor(0)[0].id).toEqual('es'); 182 | expect(callback.calls.argsFor(0)[0]).toEqual($locale); 183 | tmhDynamicLocale.set('it'); 184 | }) 185 | .waitsFor(function() { 186 | $timeout.flush(50); 187 | return $locale.id === 'it'; 188 | }, 'locale not updated', 2000) 189 | .runs(function() { 190 | tmhDynamicLocale.set('es').then(callback); 191 | expect(callback.calls.count()).toBe(1); 192 | $rootScope.$apply(); 193 | expect(callback.calls.count()).toBe(2); 194 | expect(callback.calls.argsFor(1)[0].id).toBe('es'); 195 | expect(callback.calls.argsFor(1)[0]).toBe($locale); 196 | }) 197 | .done(); 198 | job.start(); 199 | }); 200 | }); 201 | 202 | it('should reject the returned promise if it fails to load the locale', function(done) { 203 | inject(function($timeout, $locale, tmhDynamicLocale) { 204 | var callback = jasmine.createSpy(); 205 | var errorCallback = jasmine.createSpy(); 206 | var job = createAsync(done); 207 | 208 | job 209 | .runs(function() { 210 | tmhDynamicLocale.set('invalidLocale').then(callback, errorCallback); 211 | }) 212 | .waitsFor(function() { 213 | $timeout.flush(50); 214 | return errorCallback.calls.count(); 215 | }, 'promise not rejected', 2000) 216 | .runs(function() { 217 | expect(callback.calls.count()).toBe(0); 218 | expect(errorCallback.calls.count()).toBe(1); 219 | expect(errorCallback.calls.argsFor(0)[0]).toBe('invalidLocale'); 220 | expect($locale.id).toBe('en-us'); 221 | }) 222 | .done(); 223 | job.start(); 224 | }); 225 | }); 226 | 227 | it('should be possible to retrieve the locale to be', function(done) { 228 | inject(function($timeout, $locale, tmhDynamicLocale) { 229 | var job = createAsync(done); 230 | 231 | job 232 | .runs(function() { 233 | tmhDynamicLocale.set('es'); 234 | expect(tmhDynamicLocale.get()).toBe('es'); 235 | }) 236 | .waitsFor(function() { 237 | $timeout.flush(50); 238 | return $locale.id === 'es'; 239 | }, 'locale not updated', 2000) 240 | .runs(function() { 241 | expect(tmhDynamicLocale.get()).toBe('es'); 242 | }) 243 | .done(); 244 | job.start(); 245 | }); 246 | }); 247 | 248 | it('should revert the configured locale when the new locale does not exist', function(done) { 249 | inject(function($timeout, $locale, tmhDynamicLocale) { 250 | var job = createAsync(done); 251 | var errorCallback = jasmine.createSpy(); 252 | 253 | job 254 | .runs(function() { 255 | tmhDynamicLocale.set('es'); 256 | }) 257 | .waitsFor(function() { 258 | $timeout.flush(50); 259 | return $locale.id === 'es'; 260 | }, 'locale not updated', 2000) 261 | .runs(function() { 262 | tmhDynamicLocale.set('invalidLocale').then(undefined, errorCallback); 263 | expect(tmhDynamicLocale.get()).toBe('invalidLocale'); 264 | }) 265 | .waitsFor(function() { 266 | $timeout.flush(50); 267 | return errorCallback.calls.count(); 268 | }, 'promise not rejected', 2000) 269 | .runs(function() { 270 | expect(tmhDynamicLocale.get()).toBe('es'); 271 | }) 272 | .done(); 273 | job.start(); 274 | }); 275 | }); 276 | 277 | it('should change the already formatted numbers in the page', function(done) { 278 | inject(function($timeout, $locale, tmhDynamicLocale, $rootScope, $compile) { 279 | var job = createAsync(done); 280 | var element = null; 281 | 282 | job 283 | .runs(function() { 284 | element = $compile('{{val | number}}')($rootScope); 285 | 286 | $rootScope.val = 1234.5678; 287 | $rootScope.$apply(); 288 | expect(element.text()).toBe('1,234.568'); 289 | 290 | tmhDynamicLocale.set('es'); 291 | }) 292 | .waitsFor(function() { 293 | $timeout.flush(50); 294 | return $locale.id === 'es'; 295 | }, 'locale not updated', 2000) 296 | .runs(function() { 297 | expect(element.text()).toBe('1.234,568'); 298 | }) 299 | .done(); 300 | job.start(); 301 | }); 302 | }); 303 | 304 | it('should keep already loaded locales at tmhDynamicLocaleCache', function(done) { 305 | inject(function($timeout, $locale, tmhDynamicLocale, tmhDynamicLocaleCache) { 306 | var job = createAsync(done); 307 | var esLocale = null; 308 | 309 | job 310 | .runs(function() { 311 | expect(tmhDynamicLocaleCache.info().size).toBe(0); 312 | tmhDynamicLocale.set('es'); 313 | expect(tmhDynamicLocaleCache.info().size).toBe(0); 314 | }) 315 | .waitsFor(function() { 316 | $timeout.flush(50); 317 | return $locale.id === 'es'; 318 | }, 'locale not updated', 2000) 319 | .runs(function() { 320 | expect(tmhDynamicLocaleCache.info().size).toBe(1); 321 | expect(tmhDynamicLocaleCache.get('es')).toEqual($locale); 322 | esLocale = angular.copy($locale); 323 | tmhDynamicLocale.set('it'); 324 | }) 325 | .waitsFor(function() { 326 | $timeout.flush(50); 327 | return $locale.id === 'it'; 328 | }, 'locale not updated', 2000) 329 | .runs(function() { 330 | expect(tmhDynamicLocaleCache.info().size).toBe(2); 331 | expect(tmhDynamicLocaleCache.get('es')).toEqual(esLocale); 332 | expect(tmhDynamicLocaleCache.get('it')).toEqual($locale); 333 | }) 334 | .done(); 335 | job.start(); 336 | }); 337 | }); 338 | 339 | it('should use the cache when possible', function(done) { 340 | inject(function($timeout, $locale, tmhDynamicLocale, tmhDynamicLocaleCache, $rootScope) { 341 | var job = createAsync(done); 342 | var callback = jasmine.createSpy(); 343 | 344 | job 345 | .runs(function() { 346 | tmhDynamicLocale.set('es'); 347 | }) 348 | .waitsFor(function() { 349 | $timeout.flush(50); 350 | return $locale.id === 'es'; 351 | }, 'locale not updated', 2000) 352 | .runs(function() { 353 | tmhDynamicLocale.set('it'); 354 | }) 355 | .waitsFor(function() { 356 | $timeout.flush(50); 357 | return $locale.id === 'it'; 358 | }, 'locale not updated', 2000) 359 | .runs(function() { 360 | tmhDynamicLocaleCache.get('es').DATETIME_FORMATS.DAY['0'] = 'Domingo'; 361 | $rootScope.$on('$localeChangeSuccess', callback); 362 | tmhDynamicLocale.set('es'); 363 | // Changing the locale should be done async even when this is done from the cache 364 | expect(callback.calls.count()).toBe(0); 365 | expect($locale.id).toBe('it'); 366 | $rootScope.$apply(); 367 | expect($locale.id).toBe('es'); 368 | expect($locale.DATETIME_FORMATS.DAY['0']).toBe('Domingo'); 369 | expect(callback.calls.count()).toBe(1); 370 | }) 371 | .done(); 372 | job.start(); 373 | }); 374 | }); 375 | 376 | it('should do a deep copy of the locale elements', function(done) { 377 | inject(function($timeout, $locale, tmhDynamicLocale, tmhDynamicLocaleCache) { 378 | var job = createAsync(done); 379 | 380 | job 381 | .runs(function() { 382 | tmhDynamicLocale.set('es'); 383 | }) 384 | .waitsFor(function() { 385 | $timeout.flush(50); 386 | return $locale.id === 'es'; 387 | }, 'locale not updated', 2000) 388 | .runs(function() { 389 | $locale.DATETIME_FORMATS.DAY['0'] = 'XXX'; 390 | expect($locale.DATETIME_FORMATS.DAY['0']).not.toBe(tmhDynamicLocaleCache.get('es').DATETIME_FORMATS.DAY['0']); 391 | }) 392 | .done(); 393 | job.start(); 394 | }); 395 | }); 396 | 397 | it('should be able to handle locales with extra elements', function(done) { 398 | inject(function($timeout, $locale, tmhDynamicLocale, tmhDynamicLocaleCache) { 399 | var job = createAsync(done); 400 | var weirdLocale; 401 | 402 | job 403 | .runs(function() { 404 | tmhDynamicLocale.set('es'); 405 | }) 406 | .waitsFor(function() { 407 | $timeout.flush(50); 408 | return $locale.id === 'es'; 409 | }, 'locale not updated', 2000) 410 | .runs(function() { 411 | weirdLocale = angular.copy($locale); 412 | weirdLocale.id = 'xx'; 413 | weirdLocale.EXTRA_PARAMETER = {foo: 'FOO'}; 414 | weirdLocale.DATETIME_FORMATS.DAY['7'] = 'One More Day'; 415 | tmhDynamicLocaleCache.put('xx', angular.copy(weirdLocale)); 416 | tmhDynamicLocale.set('xx'); 417 | }) 418 | .waitsFor(function() { 419 | $timeout.flush(50); 420 | return $locale.id === 'xx'; 421 | }, 'locale not updated', 2000) 422 | .runs(function() { 423 | expect($locale).toEqual(weirdLocale); 424 | expect($locale.EXTRA_PARAMETER).toEqual({foo: 'FOO'}); 425 | tmhDynamicLocale.set('es'); 426 | }) 427 | .waitsFor(function() { 428 | $timeout.flush(50); 429 | return $locale.id === 'es'; 430 | }, 'locale not updated', 2000) 431 | .runs(function() { 432 | expect($locale.EXTRA_PARAMETER).toBeUndefined(); 433 | expect($locale.DATETIME_FORMATS.DAY['7']).toBeUndefined(); 434 | expect($locale.DATETIME_FORMATS.DAY.length).toBe(7); 435 | }) 436 | .done(); 437 | job.start(); 438 | }); 439 | }); 440 | 441 | describe('having a default locale', function() { 442 | beforeEach(module(function(tmhDynamicLocaleProvider) { 443 | tmhDynamicLocaleProvider.defaultLocale('it'); 444 | })); 445 | it('should set the locale to the default locale', function(done) { 446 | inject(function($timeout, $locale, $rootScope) { 447 | var job = createAsync(done); 448 | 449 | job 450 | .runs(function() { 451 | expect($locale.id).toBe('en-us'); 452 | $rootScope.$apply(); 453 | }) 454 | .waitsFor(function() { 455 | $timeout.flush(50); 456 | return $locale.id === 'it'; 457 | }, 'locale not updated', 2000) 458 | .runs(function() { 459 | expect($locale.id).toBe('it'); 460 | }) 461 | .done(); 462 | job.start(); 463 | }); 464 | }); 465 | }); 466 | 467 | describe('having a cookie storage', function () { 468 | beforeEach(module('ngCookies')); 469 | beforeEach(module(function(tmhDynamicLocaleProvider) { 470 | tmhDynamicLocaleProvider.useCookieStorage(); 471 | })); 472 | 473 | if (angular.version.minor < 7) { 474 | it('should store the change in $cookieStore', function(done) { 475 | inject(function ($timeout, $locale, $cookieStore, tmhDynamicLocale) { 476 | $cookieStore.remove('tmhDynamicLocale.locale'); 477 | var job = createAsync(done); 478 | 479 | job 480 | .runs(function() { 481 | tmhDynamicLocale.set('es'); 482 | expect($cookieStore.get('tmhDynamicLocale.locale')).toBe(undefined); 483 | }) 484 | .waitsFor(function() { 485 | $timeout.flush(50); 486 | return $locale.id === 'es'; 487 | }, 'locale not updated', 2000) 488 | .runs(function() { 489 | expect($cookieStore.get('tmhDynamicLocale.locale')).toBe('es'); 490 | }) 491 | .done(); 492 | job.start(); 493 | }); 494 | }); 495 | } 496 | it('should store the change in $cookies', function(done) { 497 | inject(function ($timeout, $locale, $cookies, tmhDynamicLocale) { 498 | $cookies.remove('tmhDynamicLocale.locale'); 499 | var job = createAsync(done); 500 | 501 | job 502 | .runs(function() { 503 | tmhDynamicLocale.set('es'); 504 | expect($cookies.getObject('tmhDynamicLocale.locale')).toBe(undefined); 505 | }) 506 | .waitsFor(function() { 507 | $timeout.flush(50); 508 | return $locale.id === 'es'; 509 | }, 'locale not updated', 2000) 510 | .runs(function() { 511 | expect($cookies.getObject('tmhDynamicLocale.locale')).toBe('es'); 512 | }) 513 | .done(); 514 | job.start(); 515 | }); 516 | }); 517 | describe('reading the locale at initialization', function () { 518 | if (angular.version.minor < 7) { 519 | beforeEach(inject(function ($cookieStore, $rootScope) { 520 | $cookieStore.put('tmhDynamicLocale.locale', 'it'); 521 | $rootScope.$apply(); 522 | })); 523 | } else { 524 | beforeEach(inject(function ($cookies, $rootScope) { 525 | $cookies.putObject('tmhDynamicLocale.locale', 'it'); 526 | $rootScope.$apply(); 527 | })); 528 | } 529 | 530 | it('should load the locale on initialization', function(done) { 531 | inject(function ($timeout, $locale) { 532 | var job = createAsync(done); 533 | 534 | job 535 | .runs(function() { 536 | expect($locale.id).toBe('en-us'); 537 | }) 538 | .waitsFor(function() { 539 | $timeout.flush(50); 540 | return $locale.id === 'it'; 541 | }, 'locale not updated', 2000) 542 | .runs(function() { 543 | expect($locale.id).toBe('it'); 544 | }) 545 | .done(); 546 | job.start(); 547 | }); 548 | }); 549 | }); 550 | describe('and having a default language', function () { 551 | beforeEach(module(function(tmhDynamicLocaleProvider) { 552 | tmhDynamicLocaleProvider.defaultLocale('es'); 553 | })); 554 | if (angular.version.minor < 7) { 555 | beforeEach(inject(function ($cookieStore, $rootScope) { 556 | $cookieStore.put('tmhDynamicLocale.locale', 'it'); 557 | $rootScope.$apply(); 558 | })); 559 | } else { 560 | beforeEach(inject(function ($cookies, $rootScope) { 561 | $cookies.putObject('tmhDynamicLocale.locale', 'it'); 562 | $rootScope.$apply(); 563 | })); 564 | } 565 | 566 | it('should load the locale on initialization', function(done) { 567 | inject(function ($timeout, $locale) { 568 | var job = createAsync(done); 569 | 570 | job 571 | .runs(function() { 572 | expect($locale.id).toBe('en-us'); 573 | }) 574 | .waitsFor(function() { 575 | $timeout.flush(50); 576 | return $locale.id === 'it'; 577 | }, 'locale not updated', 2000) 578 | .runs(function() { 579 | expect($locale.id).toBe('it'); 580 | }) 581 | .done(); 582 | job.start(); 583 | }); 584 | }); 585 | }); 586 | describe('and changing the name of the storageKey', function () { 587 | beforeEach(module(function(tmhDynamicLocaleProvider) { 588 | tmhDynamicLocaleProvider.storageKey('customStorageKeyName'); 589 | })); 590 | 591 | if (angular.version.minor < 7) { 592 | it('should change the name of the storageKey', function(done) { 593 | inject(function ($timeout, $locale, $cookieStore, tmhDynamicLocale) { 594 | $cookieStore.remove('tmhDynamicLocale.locale'); 595 | $cookieStore.remove('customStorageKeyName'); 596 | var job = createAsync(done); 597 | 598 | job 599 | .runs(function() { 600 | tmhDynamicLocale.set('es'); 601 | expect($cookieStore.get('customStorageKeyName')).toBe(undefined); 602 | expect($cookieStore.get('tmhDynamicLocale.locale')).toBe(undefined); 603 | }) 604 | .waitsFor(function() { 605 | $timeout.flush(50); 606 | return $locale.id === 'es'; 607 | }, 'locale not updated', 2000) 608 | .runs(function() { 609 | expect($cookieStore.get('tmhDynamicLocale.locale')).toBe(undefined); 610 | expect($cookieStore.get('customStorageKeyName')).toBe('es'); 611 | }) 612 | .done(); 613 | job.start(); 614 | }); 615 | }); 616 | } 617 | 618 | 619 | it('should change the name of the storageKey', function(done) { 620 | inject(function ($timeout, $locale, $cookies, tmhDynamicLocale) { 621 | $cookies.remove('tmhDynamicLocale.locale'); 622 | $cookies.remove('customStorageKeyName'); 623 | var job = createAsync(done); 624 | 625 | job 626 | .runs(function() { 627 | tmhDynamicLocale.set('es'); 628 | expect($cookies.getObject('customStorageKeyName')).toBe(undefined); 629 | expect($cookies.getObject('tmhDynamicLocale.locale')).toBe(undefined); 630 | }) 631 | .waitsFor(function() { 632 | $timeout.flush(50); 633 | return $locale.id === 'es'; 634 | }, 'locale not updated', 2000) 635 | .runs(function() { 636 | expect($cookies.getObject('tmhDynamicLocale.locale')).toBe(undefined); 637 | expect($cookies.getObject('customStorageKeyName')).toBe('es'); 638 | }) 639 | .done(); 640 | job.start(); 641 | }); 642 | }); 643 | }); 644 | }); 645 | 646 | describe('loading locales using