Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce translations and translate library for internationalization #1969

Merged
merged 4 commits into from
Mar 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/languages/en.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
hello: 'Hello',
};
2 changes: 2 additions & 0 deletions src/languages/es-ES.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export default {
tugbadogan marked this conversation as resolved.
Show resolved Hide resolved
};
3 changes: 3 additions & 0 deletions src/languages/es.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
hello: 'Hola',
};
9 changes: 9 additions & 0 deletions src/languages/translations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import en from './en';
import es from './es';
import esES from './es-ES';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we not name this es-ES instead of esES ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In JS, variable/module/function names can't contain -. Maybe, we can use _ here instead.


export default {
en,
es,
'es-ES': esES,
};
59 changes: 59 additions & 0 deletions src/libs/translate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import lodashGet from 'lodash.get';
import Str from 'expensify-common/lib/str';
import Log from './Log';
import Config from '../CONFIG';
import translations from '../languages/translations';

/**
* Return translated string for given locale and phrase
*
* @param {String} locale eg 'en', 'es-ES'
* @param {String|Array} phrase
* @param {Object} variables
* @returns {string}
*/
function translate(locale, phrase, variables = {}) {
const localeLanguage = locale.substring(0, 2);
const fullLocale = lodashGet(translations, locale, {});
const language = lodashGet(translations, localeLanguage, {});
const defaultLanguage = lodashGet(translations, 'en', {});

let translationValue;

// Search phrase in full locale
translationValue = lodashGet(fullLocale, phrase);
if (translationValue) {
return Str.result(translationValue, variables);
}

// Phrase is not found in full locale, search it in language
translationValue = lodashGet(language, phrase);
if (translationValue) {
return Str.result(translationValue, variables);
}
if (localeLanguage !== 'en') {
Log.alert(`${phrase} was not found in the ${localeLanguage} locale`, 0, {}, false);
}

// Phrase is not translated, search it in default language (en)
translationValue = lodashGet(defaultLanguage, phrase);
if (translationValue) {
return Str.result(translationValue, variables);
}

// Phrase is not found in default language, on production log an alert to server
// on development throw an error
if (Config.IS_IN_PRODUCTION) {
const phraseString = Array.isArray(phrase) ? phrase.join('.') : phrase;
Log.alert(`${phraseString} was not found in the en locale`, 0, {}, false);
return phraseString;
}
throw new Error(`${phrase} was not found in the default language`);
}

export {

// Ignoring this lint error in case of we want to export more functions from this library
// eslint-disable-next-line import/prefer-default-export
tugbadogan marked this conversation as resolved.
Show resolved Hide resolved
translate,
};
54 changes: 54 additions & 0 deletions tests/unit/TranslateTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const translate = require('../../src/libs/translate');
const translations = require('../../src/languages/translations');
const CONFIG = require('../../src/CONFIG');

translations.default = {
en: {
testKey1: 'English',
testKey2: 'Test Word 2',
testKey3: 'Test Word 3',
testKeyGroup: {
testFunction: ({testVariable}) => `With variable ${testVariable}`,
},
},
es: {
testKey1: 'Spanish',
testKey2: 'Spanish Word 2',
},
'es-ES': {testKey1: 'Spanish ES'},
};

describe('translate', () => {
it('Test present key in full locale', () => {
expect(translate.translate('es-ES', 'testKey1')).toBe('Spanish ES');
});

it('Test when key is not found in full locale, but present in language', () => {
expect(translate.translate('es-ES', 'testKey2')).toBe('Spanish Word 2');
expect(translate.translate('es', 'testKey2')).toBe('Spanish Word 2');
});

it('Test when key is not found in full locale and language, but present in default', () => {
expect(translate.translate('es-ES', 'testKey3')).toBe('Test Word 3');
});

test('Test when key is not found in default', () => {
expect(() => translate.translate('es-ES', 'testKey4')).toThrow(Error);
expect(() => translate.translate('es-ES', ['a', 'b', 'c'])).toThrow(Error);
});

test('Test when key is not found in default (Production Mode)', () => {
const ORIGINAL_IS_IN_PRODUCTION = CONFIG.default.IS_IN_PRODUCTION;
CONFIG.default.IS_IN_PRODUCTION = true;
expect(translate.translate('es-ES', 'testKey4')).toBe('testKey4');
expect(translate.translate('es-ES', ['a', 'b', 'c'])).toBe('a.b.c');
CONFIG.default.IS_IN_PRODUCTION = ORIGINAL_IS_IN_PRODUCTION;
});

it('Test when translation value is a function', () => {
const expectedValue = 'With variable Test Variable';
const testVariable = 'Test Variable';
expect(translate.translate('en', 'testKeyGroup.testFunction', {testVariable})).toBe(expectedValue);
expect(translate.translate('en', ['testKeyGroup', 'testFunction'], {testVariable})).toBe(expectedValue);
});
});