diff --git a/src/languages/en.js b/src/languages/en.js new file mode 100644 index 000000000000..b97674c9a4e1 --- /dev/null +++ b/src/languages/en.js @@ -0,0 +1,3 @@ +export default { + hello: 'Hello', +}; diff --git a/src/languages/es-ES.js b/src/languages/es-ES.js new file mode 100644 index 000000000000..02a61bed5e1e --- /dev/null +++ b/src/languages/es-ES.js @@ -0,0 +1,2 @@ +export default { +}; diff --git a/src/languages/es.js b/src/languages/es.js new file mode 100644 index 000000000000..201a74f4f84a --- /dev/null +++ b/src/languages/es.js @@ -0,0 +1,3 @@ +export default { + hello: 'Hola', +}; diff --git a/src/languages/translations.js b/src/languages/translations.js new file mode 100644 index 000000000000..c8dd8c8ab0e0 --- /dev/null +++ b/src/languages/translations.js @@ -0,0 +1,9 @@ +import en from './en'; +import es from './es'; +import esES from './es-ES'; + +export default { + en, + es, + 'es-ES': esES, +}; diff --git a/src/libs/translate.js b/src/libs/translate.js new file mode 100644 index 000000000000..2d301400fcf5 --- /dev/null +++ b/src/libs/translate.js @@ -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 + translate, +}; diff --git a/tests/unit/TranslateTest.js b/tests/unit/TranslateTest.js new file mode 100644 index 000000000000..f46a93bc3ad5 --- /dev/null +++ b/tests/unit/TranslateTest.js @@ -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); + }); +});