Skip to content
This repository has been archived by the owner on Jan 29, 2024. It is now read-only.

Commit

Permalink
fix($translate): reassign language promises in refresh, update transl…
Browse files Browse the repository at this point in the history
…ation tables at the appropriate time, and simplify the routine

When using `$translatePartialLoader`, the language promise is invalid upon adding a new part and calling `$translate.refresh()`.
Calling `$translate()` immediately after adding a new part results in the language key being returned from the already-resolved promise that was created on the original invokation of `loadAsync`, rather than waiting for the new part to be fetched from the server.
Calling `$translate.use()` with `$forceAsyncReloadEnabled=true` configured is a workaround with the current code, but it is logically incorrect to force a language change when a refresh is the appropriate action. This change remedies the problem by assigning the promise from loadAsync to the appropriate langPromise upon refresh.

With this fix in-place, it was revealed the asynchronous processing promise queue (generated by aggregating the results of the individual language promises in the code branch where all languages are refreshed) executes the logic to update the translation tables after all of the individual table promises are already resolved and their queues have been processed. This means also, if someone is using the partial-loader, any calls to `$translate` will be contained in a newly-added part that are made before the entire refresh has finished will erroneously have a cache miss because the determineTranslation call is waiting on the individual table promises, which resolve all of their handlers (`promise.then`) before the outer handler (`$q.all().then`) begins processing its handler to actually update the translation tables. This is resolved by moving the translation table update handler to the individual event queues for each table, thereby guaranteeing the table has been updated by the time determineTranslation is executed because the handlers are executed in the order they are registered and the table-updating handler will be the first thing registered. After really digging in and understanding refresh I also noticed that there was some duplicate and overly-complex logic, so I refactored it to be a little simpler and shorter.
  • Loading branch information
Michael Twardochleb authored and knalli committed Feb 11, 2017
1 parent 8b2cea8 commit 351eb8f
Showing 1 changed file with 50 additions and 50 deletions.
100 changes: 50 additions & 50 deletions src/service/translate.js
Original file line number Diff line number Diff line change
Expand Up @@ -1551,7 +1551,7 @@ function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvide
Interpolator = (interpolationId) ? interpolatorHashMap[interpolationId] : defaultInterpolator;

// if the translation id exists, we can just interpolate it
if (table && Object.prototype.hasOwnProperty.call(table, translationId) && table[translationId] !== null ) {
if (table && Object.prototype.hasOwnProperty.call(table, translationId) && table[translationId] !== null) {
var translation = table[translationId];

// If using link, rerun $translate with linked translationId and return it
Expand Down Expand Up @@ -2032,68 +2032,68 @@ function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvide
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 = {};
var deferred = $q.defer(), updatedLanguages = {};

//private helper
function loadNewData(languageKey) {
var promise = loadAsync(languageKey);
//update the load promise cache for this language
langPromises[languageKey] = promise;
//register a data handler for the promise
promise.then(function (data) {
//clear the cache for this language
$translationTable[languageKey] = {};
//add the new data for this language
translations(languageKey, data.table);
//track that we updated this language
updatedLanguages[languageKey] = true;
},
//handle rejection to appease the $q validation
angular.noop);
return promise;
}

// 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;
//set up post-processing
deferred.promise.then(
function () {
for (var key in $translationTable) {
if ($translationTable.hasOwnProperty(key)) {
//delete cache entries that were not updated
if (!(key in updatedLanguages)) {
delete $translationTable[key];
}
}
}
}

// 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';
},
//handle rejection to appease the $q validation
angular.noop
).finally(
function () {
$rootScope.$emit('$translateRefreshEnd', {language : langKey});
}
);

$q.all(tables).then(allTranslationsLoaded, reject);
if (!langKey) {
// if there's no language key specified we refresh ALL THE THINGS!
var languagesToReload = $fallbackLanguage && $fallbackLanguage.slice() || [];
if ($uses && languagesToReload.indexOf($uses) === -1) {
languagesToReload.push($uses);
}
$q.all(languagesToReload.map(loadNewData)).then(deferred.resolve, deferred.reject);

} else if ($translationTable[langKey]) {

var oneTranslationsLoaded = function (data) {
translations(data.key, data.table);
if (langKey === $uses) {
useLanguage($uses);
}
resolve();
return data;
};
oneTranslationsLoaded.displayName = 'refreshPostProcessor';

loadAsync(langKey).then(oneTranslationsLoaded, reject);
//just refresh the specified language cache
loadNewData(langKey).then(deferred.resolve, deferred.reject);

} else {
reject();
deferred.reject();
}

return deferred.promise;
};

Expand Down

0 comments on commit 351eb8f

Please sign in to comment.