From 124ad35d63b811c65b6a4bef4b03c7683f73ab7d Mon Sep 17 00:00:00 2001 From: tugbadogan Date: Sun, 21 Mar 2021 18:09:04 +0000 Subject: [PATCH 1/4] Introduce translations and translate library for internationalization --- src/languages/en.js | 3 ++ src/languages/es-ES.js | 2 ++ src/languages/es.js | 3 ++ src/languages/translations.js | 9 ++++++ src/libs/translate.js | 57 +++++++++++++++++++++++++++++++++++ tests/unit/TranslateTest.js | 55 +++++++++++++++++++++++++++++++++ 6 files changed, 129 insertions(+) create mode 100644 src/languages/en.js create mode 100644 src/languages/es-ES.js create mode 100644 src/languages/es.js create mode 100644 src/languages/translations.js create mode 100644 src/libs/translate.js create mode 100644 tests/unit/TranslateTest.js 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..607d460585b1 --- /dev/null +++ b/src/libs/translate.js @@ -0,0 +1,57 @@ +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 key + * + * @param {String} locale + * @param {String|Array} key + * @param {Object} variables + * @returns {string} + */ +function translate(locale, key, 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 key in full locale + translationValue = lodashGet(fullLocale, key); + if (translationValue) { + return Str.result(translationValue, variables); + } + + // Key is not found in full locale, search it in language + translationValue = lodashGet(language, key); + if (translationValue) { + return Str.result(translationValue, variables); + } + if (localeLanguage !== 'en') { + Log.alert(`${key} was not found in the ${localeLanguage} locale`, 0, {}, false); + } + + // Key is not translated, search it in default language (en) + translationValue = lodashGet(defaultLanguage, key); + if (translationValue) { + return Str.result(translationValue, variables); + } + + // Key is not found in default language, on production log an alert to server + // on development throw an error + if (Config.IS_IN_PRODUCTION) { + const keyString = Array.isArray(key) ? key.join('.') : key; + Log.alert(`${keyString} was not found in the en locale`, 0, {}, false); + return keyString; + } + throw new Error(`${key} was not found in the default language`); +} + +export { + // 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..f53641abd6a5 --- /dev/null +++ b/tests/unit/TranslateTest.js @@ -0,0 +1,55 @@ +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' }, +}; + +import { translate } from '../../src/libs/translate'; + +describe('translate', () => { + it('Test present key in full locale', () => { + expect(translate('es-ES', 'testkey1')).toBe('Spanish ES'); + }); + + it('Test when key is not found in full locale, but present in language', () => { + expect(translate('es-ES', 'testKey2')).toBe('Spanish Word 2'); + expect(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('es-ES', 'testKey3')).toBe('Test Word 3'); + }); + + test('Test when key is not found in default', () => { + expect(() => translate('es-ES', 'testKey4')).toThrow(Error); + expect(() => translate('es-ES', ['a', 'b', 'c'])).toThrow(Error); + }); + + test('Test when key is not found in default (Production Mode)', () => { + CONFIG.default = { + IS_IN_PRODUCTION: true, + } + expect(translate('es-ES', 'testKey4')).toBe('testKey4'); + expect(translate('es-ES', ['a', 'b', 'c'])).toBe('a.b.c'); + }); + + it('Test when translation value is a function', () => { + const expectedValue = 'With variable Test Variable'; + const testVariable = 'Test Variable'; + expect(translate('en', 'testKeyGroup.testFunction', { testVariable })).toBe(expectedValue); + expect(translate('en', ['testKeyGroup', 'testFunction'], { testVariable })).toBe(expectedValue); + }); +}); From 19f5a11861451c6f20adb6fb9c493e2fec97c446 Mon Sep 17 00:00:00 2001 From: tugbadogan Date: Sun, 21 Mar 2021 19:59:06 +0000 Subject: [PATCH 2/4] Fixing lint errors --- tests/unit/TranslateTest.js | 91 ++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 46 deletions(-) diff --git a/tests/unit/TranslateTest.js b/tests/unit/TranslateTest.js index f53641abd6a5..b0819a75fb1e 100644 --- a/tests/unit/TranslateTest.js +++ b/tests/unit/TranslateTest.js @@ -1,55 +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}`, + 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' }, + es: { + testkey1: 'Spanish', + testKey2: 'Spanish Word 2', + }, + 'es-ES': {testkey1: 'Spanish ES'}, }; -import { translate } from '../../src/libs/translate'; - describe('translate', () => { - it('Test present key in full locale', () => { - expect(translate('es-ES', 'testkey1')).toBe('Spanish ES'); - }); - - it('Test when key is not found in full locale, but present in language', () => { - expect(translate('es-ES', 'testKey2')).toBe('Spanish Word 2'); - expect(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('es-ES', 'testKey3')).toBe('Test Word 3'); - }); - - test('Test when key is not found in default', () => { - expect(() => translate('es-ES', 'testKey4')).toThrow(Error); - expect(() => translate('es-ES', ['a', 'b', 'c'])).toThrow(Error); - }); - - test('Test when key is not found in default (Production Mode)', () => { - CONFIG.default = { - IS_IN_PRODUCTION: true, - } - expect(translate('es-ES', 'testKey4')).toBe('testKey4'); - expect(translate('es-ES', ['a', 'b', 'c'])).toBe('a.b.c'); - }); - - it('Test when translation value is a function', () => { - const expectedValue = 'With variable Test Variable'; - const testVariable = 'Test Variable'; - expect(translate('en', 'testKeyGroup.testFunction', { testVariable })).toBe(expectedValue); - expect(translate('en', ['testKeyGroup', 'testFunction'], { testVariable })).toBe(expectedValue); - }); + 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)', () => { + 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'); + }); + + 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); + }); }); From 13643b79f339d1a5ca9e71b534a7e83985cee977 Mon Sep 17 00:00:00 2001 From: tugbadogan Date: Mon, 22 Mar 2021 20:48:56 +0000 Subject: [PATCH 3/4] Addressing comments --- src/libs/translate.js | 4 +++- tests/unit/TranslateTest.js | 14 +++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/libs/translate.js b/src/libs/translate.js index 607d460585b1..2b32319d0654 100644 --- a/src/libs/translate.js +++ b/src/libs/translate.js @@ -7,7 +7,7 @@ import translations from '../languages/translations'; /** * Return translated string for given locale and key * - * @param {String} locale + * @param {String} locale eg 'en', 'es-ES' * @param {String|Array} key * @param {Object} variables * @returns {string} @@ -52,6 +52,8 @@ function translate(locale, key, variables = {}) { } 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 index b0819a75fb1e..f46a93bc3ad5 100644 --- a/tests/unit/TranslateTest.js +++ b/tests/unit/TranslateTest.js @@ -4,7 +4,7 @@ const CONFIG = require('../../src/CONFIG'); translations.default = { en: { - testkey1: 'English', + testKey1: 'English', testKey2: 'Test Word 2', testKey3: 'Test Word 3', testKeyGroup: { @@ -12,15 +12,15 @@ translations.default = { }, }, es: { - testkey1: 'Spanish', + testKey1: 'Spanish', testKey2: 'Spanish Word 2', }, - 'es-ES': {testkey1: 'Spanish ES'}, + 'es-ES': {testKey1: 'Spanish ES'}, }; describe('translate', () => { it('Test present key in full locale', () => { - expect(translate.translate('es-ES', 'testkey1')).toBe('Spanish ES'); + expect(translate.translate('es-ES', 'testKey1')).toBe('Spanish ES'); }); it('Test when key is not found in full locale, but present in language', () => { @@ -38,11 +38,11 @@ describe('translate', () => { }); test('Test when key is not found in default (Production Mode)', () => { - CONFIG.default = { - IS_IN_PRODUCTION: true, - }; + 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', () => { From 80b030453d37ced88c64163bc65e0147fc1a94f3 Mon Sep 17 00:00:00 2001 From: tugbadogan Date: Tue, 23 Mar 2021 13:01:38 +0000 Subject: [PATCH 4/4] Rename key variable to phrase --- src/libs/translate.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/libs/translate.js b/src/libs/translate.js index 2b32319d0654..2d301400fcf5 100644 --- a/src/libs/translate.js +++ b/src/libs/translate.js @@ -5,14 +5,14 @@ import Config from '../CONFIG'; import translations from '../languages/translations'; /** - * Return translated string for given locale and key + * Return translated string for given locale and phrase * * @param {String} locale eg 'en', 'es-ES' - * @param {String|Array} key + * @param {String|Array} phrase * @param {Object} variables * @returns {string} */ -function translate(locale, key, variables = {}) { +function translate(locale, phrase, variables = {}) { const localeLanguage = locale.substring(0, 2); const fullLocale = lodashGet(translations, locale, {}); const language = lodashGet(translations, localeLanguage, {}); @@ -20,35 +20,35 @@ function translate(locale, key, variables = {}) { let translationValue; - // Search key in full locale - translationValue = lodashGet(fullLocale, key); + // Search phrase in full locale + translationValue = lodashGet(fullLocale, phrase); if (translationValue) { return Str.result(translationValue, variables); } - // Key is not found in full locale, search it in language - translationValue = lodashGet(language, key); + // 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(`${key} was not found in the ${localeLanguage} locale`, 0, {}, false); + Log.alert(`${phrase} was not found in the ${localeLanguage} locale`, 0, {}, false); } - // Key is not translated, search it in default language (en) - translationValue = lodashGet(defaultLanguage, key); + // Phrase is not translated, search it in default language (en) + translationValue = lodashGet(defaultLanguage, phrase); if (translationValue) { return Str.result(translationValue, variables); } - // Key is not found in default language, on production log an alert to server + // 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 keyString = Array.isArray(key) ? key.join('.') : key; - Log.alert(`${keyString} was not found in the en locale`, 0, {}, false); - return keyString; + 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(`${key} was not found in the default language`); + throw new Error(`${phrase} was not found in the default language`); } export {