diff --git a/gsa/CMakeLists.txt b/gsa/CMakeLists.txt index 7b02288218..42e26009fc 100644 --- a/gsa/CMakeLists.txt +++ b/gsa/CMakeLists.txt @@ -115,6 +115,7 @@ set (GSA_JS_SRC_FILES ${GSA_SRC_DIR}/src/gmp/locale/date.js ${GSA_SRC_DIR}/src/gmp/locale/detector.js ${GSA_SRC_DIR}/src/gmp/locale/lang.js + ${GSA_SRC_DIR}/src/gmp/locale/languages.js ${GSA_SRC_DIR}/src/gmp/locale/index.js ${GSA_SRC_DIR}/src/gmp/log.js ${GSA_SRC_DIR}/src/gmp/model.js diff --git a/gsa/src/gmp/gmp.js b/gsa/src/gmp/gmp.js index 746d71e889..262e399c5d 100644 --- a/gsa/src/gmp/gmp.js +++ b/gsa/src/gmp/gmp.js @@ -73,7 +73,7 @@ import {getCommands} from './command.js'; import LoginCommand from './commands/login.js'; import {setLocale} from './locale/lang'; -import {BROWSER_LANGUAGE} from './locale/detector'; +import {BROWSER_LANGUAGE} from './locale/languages'; const log = logger.getLogger('gmp'); diff --git a/gsa/src/gmp/locale/__tests__/lang.js b/gsa/src/gmp/locale/__tests__/lang.js index 68f3c18ff3..42a2b78947 100644 --- a/gsa/src/gmp/locale/__tests__/lang.js +++ b/gsa/src/gmp/locale/__tests__/lang.js @@ -36,29 +36,20 @@ describe('setLocale tests', () => { expect(getLocale()).toEqual('de'); }); - test('should log error when changing to unkown locale', () => { - const origConsole = global.console; - const testConsole = { - error: jest.fn(), - warn: jest.fn(), - info: jest.fn(), - debug: jest.fn(), - trace: jest.fn(), - }; - - global.console = testConsole; - + test('should allow to use en-US for en', () => { setLocale('en'); expect(getLocale()).toEqual('en'); - expect(testConsole.error).not.toHaveBeenCalled(); - - setLocale('foo'); - expect(getLocale()).toEqual('foo'); + setLocale('de-CH'); + expect(getLocale()).toEqual('de-CH'); + }); - expect(testConsole.error).toHaveBeenCalled(); + test('should fallback to en for unkown locales', () => { + setLocale('en'); + expect(getLocale()).toEqual('en'); - global.console = origConsole; + setLocale('foo'); + expect(getLocale()).toEqual('en'); }); test('should notify language change listeners', () => { diff --git a/gsa/src/gmp/locale/__tests__/languages.js b/gsa/src/gmp/locale/__tests__/languages.js new file mode 100644 index 0000000000..3992c63aa8 --- /dev/null +++ b/gsa/src/gmp/locale/__tests__/languages.js @@ -0,0 +1,68 @@ +/* Greenbone Security Assistant + * + * Authors: + * Björn Ricks + * + * Copyright: + * Copyright (C) 2018 Greenbone Networks GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ +import 'core-js/fn/object/keys'; +import 'core-js/fn/object/values'; + +import {isString} from 'gmp/utils/identity'; + +import Languages, {getLanguageCodes} from '../languages'; + +describe('Language tests', () => { + + test('should contain list of languagegs', () => { + expect(Object.keys(Languages).length).toEqual(11); + + let called = false; + + for (const lang of Object.values(Languages)) { + called = true; + + expect(isString(lang.name)).toEqual(true); + expect(isString(lang.native_name)).toEqual(true); + } + + expect(called).toEqual(true); + }); + +}); + +describe('getLanguageCodes test', () => { + + test('should return list of language codes', () => { + const codes = getLanguageCodes(); + + expect(codes.length).toEqual(11); + + let called = false; + + for (const code of codes) { + called = true; + expect(isString(code)).toEqual(true); + } + + expect(called).toEqual(true); + }); + +}); + +// vim: set ts=2 sw=2 tw=80: diff --git a/gsa/src/gmp/locale/detector.js b/gsa/src/gmp/locale/detector.js index 617ae5c90f..275475092a 100644 --- a/gsa/src/gmp/locale/detector.js +++ b/gsa/src/gmp/locale/detector.js @@ -20,13 +20,14 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ +import {BROWSER_LANGUAGE} from './languages'; + import logger from '../log'; + import {isArray, isDefined} from '../utils/identity'; const log = logger.getLogger('gmp.locale.detector'); -export const BROWSER_LANGUAGE = 'Browser Language'; - const detectLanguageFromStorage = options => options.storage.locale; const detectLanguageFromNavigator = () => { diff --git a/gsa/src/gmp/locale/lang.js b/gsa/src/gmp/locale/lang.js index d8e5a8a400..fddd8d20af 100644 --- a/gsa/src/gmp/locale/lang.js +++ b/gsa/src/gmp/locale/lang.js @@ -20,6 +20,9 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ +import 'core-js/fn/string/includes'; +import 'core-js/fn/array/includes'; + import i18next from 'i18next'; import XHRBackend from 'i18next-xhr-backend'; @@ -29,6 +32,8 @@ import {isDefined} from '../utils/identity'; import {setLocale as setDateLocale} from './date'; import Detector from './detector'; +import {getLanguageCodes} from './languages'; +import {split} from 'gmp/utils/string'; const log = logger.getLogger('gmp.locale.lang'); @@ -41,17 +46,22 @@ const notifyLanguageChangeListeners = (lang, initial = false) => { } }; +const fallbackLng = 'en'; +const whitelist = getLanguageCodes(); + const I18N_OPTIONS = { storage: global.localStorage, nsSeparator: false, // don't use a namespace separator in keys keySeparator: false, // don't use a key separator in keys - fallbackLng: 'en', + fallbackLng, ns: ['gsa'], // use gsa as namespace defaultNS: 'gsa', fallbackNS: 'gsa', backend: { loadPath: '/locales/{{ns}}-{{lng}}.json', // e.g. /locales/gsa-en.json }, + whitelist, + nonExplicitWhitelist: true, }; i18next.on('languageChanged', lang => { @@ -103,9 +113,19 @@ export const getLocale = () => currentLocale; * to start automatic detection. */ export const setLocale = lang => { + if (isDefined(lang)) { + const code = lang.includes('-') ? split(lang, '-', 1)[0] : lang; + + if (!whitelist.includes(lang) && !whitelist.includes(code)) { + log.warn(`Unknown locale ${lang}. Possible locales are ${whitelist} + Falling back to ${fallbackLng}.`); + lang = fallbackLng; + } + } + i18next.changeLanguage(lang, err => { if (isDefined(err)) { - log.error('Error while setting language to', lang, err); + log.warn('Error while setting language to', lang, err); } }); }; diff --git a/gsa/src/gmp/locale/languages.js b/gsa/src/gmp/locale/languages.js new file mode 100644 index 0000000000..be947a2364 --- /dev/null +++ b/gsa/src/gmp/locale/languages.js @@ -0,0 +1,78 @@ +/* Greenbone Security Assistant + * + * Authors: + * Björn Ricks + * + * Copyright: + * Copyright (C) 2018 Greenbone Networks GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ +import 'core-js/fn/object/keys'; + +export const BROWSER_LANGUAGE = 'Browser Language'; + +export const getLanguageCodes = () => Object.keys(Languages); + +const Languages = { + ar: { + name: 'Arabic', + native_name: 'العربية', + }, + de: { + name: 'German', + native_name: 'Deutsch', + }, + en: { + name: 'English', + native_name: 'English', + }, + es: { + name: 'Spanish', + native_name: 'español', + }, + fr: { + name: 'French', + native_name: 'français', + }, + it: { + name: 'Italian', + native_name: 'italiano', + }, + ja: { + name: 'Japanese', + native_name: '日本語', + }, + 'pt-BR': { + name: 'Portuguese (Brazil)', + native_name: 'português (Brasil)', + }, + ru: { + name: 'Russian', + native_name: 'ру́сский', + }, + tr: { + name: 'Turkish', + native_name: 'Türkçe', + }, + 'zh-CN': { + name: 'Chinese (China)', + native_name: '中文 (中国)', + }, +}; + +export default Languages; + +// vim: set ts=2 sw=2 tw=80: diff --git a/gsa/src/setupTests.js b/gsa/src/setupTests.js index eac7be0ac3..c9f7124f67 100644 --- a/gsa/src/setupTests.js +++ b/gsa/src/setupTests.js @@ -20,6 +20,7 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ +import 'core-js/fn/string/starts-with'; import {configure} from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; @@ -29,7 +30,7 @@ import {initLocale} from 'gmp/locale/lang'; class FakeBackend { read(language, namespace, callback) { - if (language === 'en' || language === 'de') { + if (language.startsWith('en') || language.startsWith('de')) { // change language by calling the callback functioon return callback(); } diff --git a/gsa/src/web/utils/languages.js b/gsa/src/web/utils/languages.js index 58ad7b3fbd..355ad5d1bb 100644 --- a/gsa/src/web/utils/languages.js +++ b/gsa/src/web/utils/languages.js @@ -21,57 +21,12 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ import {_l} from 'gmp/locale/lang'; - -import {BROWSER_LANGUAGE} from 'gmp/locale/detector'; +import GmpLanguages, {BROWSER_LANGUAGE} from 'gmp/locale/languages'; export {BROWSER_LANGUAGE}; const Languages = { - ar: { - name: 'Arabic', - native_name: 'العربية', - }, - de: { - name: 'German', - native_name: 'Deutsch', - }, - en: { - name: 'English', - native_name: 'English', - }, - es: { - name: 'Spanish', - native_name: 'español', - }, - fr: { - name: 'French', - native_name: 'français', - }, - it: { - name: 'Italian', - native_name: 'italiano', - }, - ja: { - name: 'Japanese', - native_name: '日本語', - }, - pt_BR: { - name: 'Portuguese (Brazil)', - native_name: 'português (Brasil)', - }, - ru: { - name: 'Russian', - native_name: 'ру́сский', - }, - tr: { - name: 'Turkish', - native_name: 'Türkçe', - }, - zh_CN: { - name: 'Chinese (China)', - native_name: '中文 (中国)', - }, - + ...GmpLanguages, [BROWSER_LANGUAGE]: { name: _l('Browser Language'), },