Skip to content

Commit

Permalink
Merge pull request #258 from jairo-bc/STRF-9553
Browse files Browse the repository at this point in the history
STRF-9553 Fallback languages in the chain
  • Loading branch information
jairo-bc authored Dec 20, 2021
2 parents cf416f8 + 1878dd0 commit ff3383c
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 13 deletions.
8 changes: 4 additions & 4 deletions lib/translator/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,16 @@ const DEFAULT_LOCALE = 'en';
* @param {Object} logger
* @param {Boolean} omitTransforming
*/
function Translator(acceptLanguage, allTranslations, logger = console, omitTransforming = false) {
function Translator(acceptLanguage, allTranslations, logger = console) {
this.logger = logger;
this.omitTransforming = omitTransforming;

const languages = this.omitTransforming ? allTranslations : Transformer.transform(allTranslations, DEFAULT_LOCALE, this.logger);
const locales = LocaleParser.getLocales(acceptLanguage);
const languages = Transformer.transform(allTranslations, locales, DEFAULT_LOCALE, this.logger);
/**
* @private
* @type {string}
*/
this._locale = LocaleParser.getPreferredLocale(acceptLanguage, languages, DEFAULT_LOCALE);
this._locale = LocaleParser.getPreferredLocale(locales, languages, DEFAULT_LOCALE);

this.setLanguage(languages);

Expand Down
6 changes: 3 additions & 3 deletions lib/translator/locale-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ const MessageFormat = require('messageformat');

/**
* Get preferred locale
* @param {string} acceptLanguage
* @param {string[]} locales
* @param {Object} languages
* @param {string} defaultLocale
* @returns {string}
*/
function getPreferredLocale(acceptLanguage, languages, defaultLocale) {
const locale = getLocales(acceptLanguage).find(locale => languages[locale]) || defaultLocale;
function getPreferredLocale(locales, languages, defaultLocale) {
const locale = locales.find(locale => languages[locale]) || defaultLocale;
try {
new MessageFormat(locale);

Expand Down
68 changes: 63 additions & 5 deletions lib/translator/transformer.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
/**
* Transform translations
* @param {Object} allTranslations
* @param {string[]} locales
* @param {string} defaultLocale
* @param {Object} logger
* @returns {Object.<string, Object>} Transformed translations
*/
function transform(allTranslations, defaultLocale, logger = console) {
return cascade(flatten(allTranslations, logger), defaultLocale);
function transform(allTranslations, locales, defaultLocale, logger = console) {
return cascade(flatten(allTranslations, logger), locales, defaultLocale);
}

/**
Expand Down Expand Up @@ -41,11 +42,14 @@ function flatten(allTranslations, logger = console) {

/**
* Cascade translations
*
* @param {Object} allTranslations Flattened translations
* @param {string[]} locales
* @param {string} defaultLocale
* @returns {Object.<string, Object>} Language objects
*/
function cascade(allTranslations, defaultLocale) {
function cascade(allTranslations, locales, defaultLocale) {
const availableLocales = addDefaultLocale(locales, defaultLocale);
return Object.entries(allTranslations)
.reduce(
(result, [locale, translations]) => {
Expand All @@ -55,6 +59,7 @@ function cascade(allTranslations, defaultLocale) {

const regionCodes = locale.split('-');
for (let regionIndex = regionCodes.length - 1; regionIndex >= 0; regionIndex--) {
// keeping parent locale logic as a source of truth to track "available" keys
const parentLocale = getParentLocale(regionCodes, regionIndex, defaultLocale);
const parentTranslations = allTranslations[parentLocale] || {};

Expand All @@ -65,8 +70,11 @@ function cascade(allTranslations, defaultLocale) {
result[locale].locales[key] = locale;
result[locale].translations[key] = translations[key];
} else if (!result[locale].translations[key]) {
result[locale].locales[key] = parentLocale;
result[locale].translations[key] = parentTranslations[key];
const preparedLocales = prepareLocales(availableLocales, locale);
const { nextAvailableLocale, nextAvailableTranslation } = getNextLocaleTranslation(preparedLocales, allTranslations, key);
// fallback to old logic in case no languages are present in lang header
result[locale].locales[key] = nextAvailableLocale || parentLocale;
result[locale].translations[key] = nextAvailableTranslation || parentTranslations[key];
}
});
}
Expand All @@ -77,6 +85,56 @@ function cascade(allTranslations, defaultLocale) {
);
}

/**
* Adding default locale in case it's absent
*
* @param {string[]} locales
* @param {string} defaultLocale
* @returns {string[]}
*/
function addDefaultLocale(locales, defaultLocale) {
// the object will be mutated, so making a copy of it.
const copiedLocales = [...locales];
if (locales[locales.length - 1] !== defaultLocale) {
copiedLocales.push(defaultLocale);
}
return copiedLocales;
}

/**
* Adding default locale in case it's absent
*
* @param {string[]} availableLocales
* @param {string} currentLocale
* @returns {string[]}
*/
function prepareLocales(availableLocales, currentLocale) {
const localeIndex = availableLocales.findIndex(locale => locale == currentLocale);
const locales = availableLocales.slice(localeIndex + 1);
return locales;
}

/**
* Returns next available pair (locale and translation) in the chain
*
* @param {string[]} locales
* @param {Object} allTranslations Flattened translations
* @param {string} key
* @returns {Object.<string, string>} selected locale and translation
*/
function getNextLocaleTranslation(locales, allTranslations, key) {
for (const locale of locales) {
if (allTranslations[locale] && allTranslations[locale][key]) {
return {
nextAvailableLocale: locale,
nextAvailableTranslation: allTranslations[locale][key],
}
}
}

return {};
}

/**
* Get parent locale
* @private
Expand Down
55 changes: 54 additions & 1 deletion spec/lib/translator.js
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,59 @@ describe('Translator', () => {
expect(translator.translate(key)).to.equal(translations.fr.level1.level2);
done();
})
})
});

describe('multiple languages support in accept language header', () => {
it('should sucessfully translate phrase and fallback to de', done => {
translations = {
en: {
search: 'Search',
},
'es-mx': {
test: 'Test',
},
de: {
search: 'German translation',
},
};
console.log('before');
const translator = Translator.create('es-mx,es,de,en', translations);
expect(translator.translate('search')).to.equal(translations.de.search);
done();
});

it('should sucessfully translate phrase and fallback to es-mx, when it is presented in header', done => {
translations = {
en: {
search: 'English translation',
},
'es-mx': {
search: 'Spanish Mexico Translation',
},
de: {
search: 'German translation',
},
};
const translator = Translator.create('es,es-mx,de,en', translations);
expect(translator.translate('search')).to.equal(translations['es-mx'].search);
done();
});

it('should sucessfully translate phrase and fallback to en when no en in header', done => {
translations = {
en: {
search: 'English translation',
},
'es-mx': {
Test: 'Spanish Mexico Translation',
},
pt: {
search: 'Portuguese translation',
},
};
const translator = Translator.create('es-mx,de', translations);
expect(translator.translate('search')).to.equal(translations.en.search);
done();
});
});
});

0 comments on commit ff3383c

Please sign in to comment.