diff --git a/docs/content/guide/de/12_asynchronous-loading.ngdoc b/docs/content/guide/de/12_asynchronous-loading.ngdoc index f0415ed12..1c7f45b7c 100644 --- a/docs/content/guide/de/12_asynchronous-loading.ngdoc +++ b/docs/content/guide/de/12_asynchronous-loading.ngdoc @@ -381,6 +381,32 @@ $translateProvider.preferredLanguage('en'); $translateProvider.useLocalStorage(); +## Asynchrones Laden erzwingen + +Wenn eine Kombination von `$translateProvider.translations()` und `$translateProvider.{{whatever}}Loader` +genutzt wird, erfolgt KEIN Aufruf des asynchrone Lademechanismus für alle Sprachen, bei denen schon +Übersetzungen per `$translateProvider.translations()` erzeugt wurden. + +Um dieses Verhalten zu verhindern, muss `$translateProvider.forceAsyncReload()` initialisiert werden: + +
+$translateProvider.translations('en', {
+    'HELLO_TEXT': 'Hello World!'
+});
+$translateProvider.useStaticFilesLoader({
+    'prefix': 'locale-',
+    'suffix': '.json'
+});
+$translateProvider.preferredLanguage('en');
+$translateProvider.forceAsyncReload(true);
+
+ +Durch diese Änderung wird bewirkt, das beide Datenquellen / Sprachtabellen genutzt werden und somit +der asynchrone Lademechanismus aufgerufen wird. Das Ergebnis ist eine zusammengesetzte Sprachtabelle. + +**Anmerkung:** Falls derselbe Sprachschlüssel in beiden Quellen definiert ist, wird ausschließlich der +Schlüssel aus der asynchronen Datenquelle genutzt! + Da wir keine Änderungen im HTML oder unseren Controllern machen müssen, sind wir schon fertig! Hier ist die funktionierende App: diff --git a/docs/content/guide/en/12_asynchronous-loading.ngdoc b/docs/content/guide/en/12_asynchronous-loading.ngdoc index 029cb628e..5c14407fd 100644 --- a/docs/content/guide/en/12_asynchronous-loading.ngdoc +++ b/docs/content/guide/en/12_asynchronous-loading.ngdoc @@ -442,6 +442,35 @@ $translateProvider.useStaticFilesLoader({ $translateProvider.preferredLanguage('en'); +## Force asynchronous reloading + +When using a combination of `$translateProvider.translations()` and +`$translateProvider.{{whatever}}Loader`, for each language keys declared +using `$translateProvider.translations()`, the asynchronous loader will +not be called. + +To get around this, you can enable the `$translateProvider.forceAsyncReload()` +like this: + +
+$translateProvider.translations('en', {
+    'HELLO_TEXT': 'Hello World!'
+});
+$translateProvider.useStaticFilesLoader({
+    'prefix': 'locale-',
+    'suffix': '.json'
+});
+$translateProvider.preferredLanguage('en');
+$translateProvider.forceAsyncReload(true);
+
+ +This way, even if the language key is already declared using +`$translateProvider.translations()` the asynchronous loader will be called +and translations from both sources will be merged. + +**Note:** If a same translation id is declared in both sources, the translation +from the asynchronous loader will be used. + Since we don't have to make any changes in our controllers or HTML, we are done! Take a look at the working app: diff --git a/docs/content/guide/fr/12_asynchronous-loading.ngdoc b/docs/content/guide/fr/12_asynchronous-loading.ngdoc index 256ac57fc..43f9a20f7 100644 --- a/docs/content/guide/fr/12_asynchronous-loading.ngdoc +++ b/docs/content/guide/fr/12_asynchronous-loading.ngdoc @@ -404,6 +404,35 @@ $translateProvider.useStaticFilesLoader({ $translateProvider.preferredLanguage('fr'); +## Forcer le rechargement asynchrone + +Lors de l'utilisation d'une combinaison de `$translateProvider.translations()` et +`$translateProvider.{{peu importe}}Loader`, pour chaque clé de langue déclarée à +l'aide de `$translateProvider.translations()`, le chargeur asynchrone ne sera +jamais appelé. + +Pour contourner ce problème, vous pouvez activer `$translateProvider.forceAsyncReload()` +comme ceci: + +
+$translateProvider.translations('en', {
+    'HELLO_TEXT': 'Salut tout le monde !!'
+});
+$translateProvider.useStaticFilesLoader({
+    'prefix': 'locale-',
+    'suffix': '.json'
+});
+$translateProvider.preferredLanguage('fr');
+$translateProvider.forceAsyncReload(true);
+
+ +De cette façon, même si la clé de la langue est déjà déclarée à l'aide de +`$translateProvider.translations()` le chargeur asynchrone sera appelé +et les traductions des deux sources seront fusionnés. + +**Note:** Si un même identifiant de traduction est déclaré dans les deux sources, +la traduction du chargeur asynchrone sera utilisée. + Étant donné que nous n'avons pas à faire de changements dans nos contrôleurs ou notre HTML, nous avons fini ! Jetez un oeil à l'application : diff --git a/src/service/translate.js b/src/service/translate.js index 9c72be694..a26720587 100644 --- a/src/service/translate.js +++ b/src/service/translate.js @@ -35,6 +35,7 @@ function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvide $notFoundIndicatorLeft, $notFoundIndicatorRight, $postCompilingEnabled = false, + $forceAsyncReloadEnabled = false, NESTED_OBJECT_DELIMITER = '.', loaderCache, directivePriority = 0, @@ -707,6 +708,30 @@ function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvide 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: + *
+   *  app.config(function ($translateProvider) {
+   *    $translateProvider.forceAsyncReload(true);
+   *  });
+   * 
+ * + * @param {boolean} value - valid values are true or false + */ + this.forceAsyncReload = function (value) { + $forceAsyncReloadEnabled = !(!value); + return this; + }; + /** * @ngdoc function * @name pascalprecht.translate.$translateProvider#uniformLanguageTag @@ -1663,7 +1688,7 @@ function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvide // if there isn't a translation table for the language we've requested, // we load it asynchronously - if (!$translationTable[key] && $loaderFactory && !langPromises[key]) { + if (($forceAsyncReloadEnabled || !$translationTable[key]) && $loaderFactory && !langPromises[key]) { $nextLang = key; langPromises[key] = loadAsync(key).then(function (translation) { translations(translation.key, translation.table); @@ -1725,6 +1750,20 @@ function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvide 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 @@ -1961,7 +2000,7 @@ function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvide }; for (var i = 0, len = $fallbackLanguage.length; i < len; i++) { var fallbackLanguageId = $fallbackLanguage[i]; - if (!$translationTable[fallbackLanguageId]) { + if ($forceAsyncReloadEnabled || !$translationTable[fallbackLanguageId]) { langPromises[fallbackLanguageId] = loadAsync(fallbackLanguageId).then(processAsyncResult); } } diff --git a/test/unit/service/translate.spec.js b/test/unit/service/translate.spec.js index 4edf07449..8dff99173 100644 --- a/test/unit/service/translate.spec.js +++ b/test/unit/service/translate.spec.js @@ -73,6 +73,22 @@ describe('pascalprecht.translate', function () { expect($translate.instant).toBeDefined(); }); + it('should have a method isForceAsyncReloadEnabled()', function () { + expect($translate.isForceAsyncReloadEnabled).toBeDefined(); + }); + + describe('$translate#isForceAsyncReloadEnabled()', function () { + + it('should be a function', function () { + expect(typeof $translate.isForceAsyncReloadEnabled).toBe('function'); + }); + + it('should return false by default', function () { + expect($translate.isForceAsyncReloadEnabled()).toBe(false); + }); + + }); + describe('$translate#preferredLanguage()', function () { it('should be a function', function () { @@ -1817,4 +1833,126 @@ describe('pascalprecht.translate', function () { }); + describe('$translateProvider.forceAsyncReload', function () { + + describe('Enabled', function () { + beforeEach(module('pascalprecht.translate', function ($translateProvider, $provide) { + $translateProvider + .translations('en', { + 'FOO': 'bar' + }) + .forceAsyncReload(true) + .useLoader('customLoader'); + + $provide.factory('customLoader', ['$q', '$timeout', function ($q, $timeout) { + return function (options) { + var deferred = $q.defer(); + + $timeout(function () { + deferred.resolve({ + 'BAR': 'foo' + }); + }); + + return deferred.promise; + }; + }]); + })); + + var $translate, $timeout, $rootScope; + + beforeEach(inject(function (_$translate_, _$timeout_, _$rootScope_) { + $translate = _$translate_; + $timeout = _$timeout_; + $rootScope = _$rootScope_; + })); + + it('should have forceAsyncReload enabled', function () { + expect($translate.isForceAsyncReloadEnabled()).toBe(true); + }); + + it('should return translation if translation id exist', function () { + var firstValue, secondValue; + + function fetchTranslation() { + $translate(['FOO', 'BAR']).then(function (translations) { + firstValue = translations.FOO; + secondValue = translations.BAR; + }); + } + + function changeLanguage(lang) { + $translate.use(lang); + } + + changeLanguage('en'); + $timeout.flush(); // Expect the `en` language to be loaded + $rootScope.$digest(); + + fetchTranslation(); + $rootScope.$digest(); + expect(firstValue).toEqual('bar'); // found + expect(secondValue).toEqual('foo'); // found + }); + }); + + describe('Disabled (default)', function () { + beforeEach(module('pascalprecht.translate', function ($translateProvider, $provide) { + $translateProvider + .translations('en', { + 'FOO': 'bar' + }) + .useLoader('customLoader'); + + $provide.factory('customLoader', ['$q', '$timeout', function ($q, $timeout) { + return function (options) { + var deferred = $q.defer(); + + $timeout(function () { + deferred.resolve({ + 'BAR': 'foo' + }); + }); + + return deferred.promise; + }; + }]); + })); + + var $translate, $rootScope; + + beforeEach(inject(function (_$translate_, _$rootScope_) { + $translate = _$translate_; + $rootScope = _$rootScope_; + })); + + it('should have forceAsyncReload disabled', function () { + expect($translate.isForceAsyncReloadEnabled()).toBe(false); + }); + + it('should return translation if translation id exist', function () { + var firstValue, secondValue; + + function fetchTranslation() { + $translate(['FOO', 'BAR']).then(function (translations) { + firstValue = translations.FOO; + secondValue = translations.BAR; + }); + } + + function changeLanguage(lang) { + $translate.use(lang); + } + + changeLanguage('en'); + $rootScope.$digest(); + + fetchTranslation(); + $rootScope.$digest(); + expect(firstValue).toEqual('bar'); // found + expect(secondValue).toEqual('BAR'); // not found + }); + }); + }); + });