From d0a6bfddd2940fc445bebae6ffa564fd064abf22 Mon Sep 17 00:00:00 2001 From: Jiri Tomasek Date: Fri, 4 Aug 2017 14:48:15 +0200 Subject: [PATCH] App config and I18n selectors refactor * store appConfig in app store on application initialization so it is easily accessible * move getEnabledLanguages from services/utils into i18n selectors * do not duplicate messages in the state - the source is components/i18n/messages.js * convert i18n components to receive current language and available languages from store using selectors Change-Id: Id6fd0da41bb75caca721ea6082603501670c36b8 --- bin/run_tests.sh | 20 +++++++++ package.json | 3 +- src/js/actions/I18nActions.js | 51 ++++++++++------------- src/js/components/AuthenticatedContent.js | 18 +++++++- src/js/components/NavBar.js | 6 +-- src/js/components/i18n/I18nDropdown.js | 15 ++++--- src/js/components/i18n/I18nProvider.js | 13 +++--- src/js/components/login/LanguageInput.js | 5 ++- src/js/components/login/Login.js | 6 ++- src/js/immutableRecords/appConfig.js | 24 +++++++++++ src/js/reducers/appConfig.js | 26 ++++++++++++ src/js/reducers/appReducer.js | 2 + src/js/reducers/i18nReducer.js | 14 ++----- src/js/selectors/i18n.js | 33 ++++++++++----- src/js/services/utils.js | 11 ----- src/js/store.js | 3 ++ 16 files changed, 168 insertions(+), 82 deletions(-) create mode 100755 bin/run_tests.sh create mode 100644 src/js/immutableRecords/appConfig.js create mode 100644 src/js/reducers/appConfig.js diff --git a/bin/run_tests.sh b/bin/run_tests.sh new file mode 100755 index 00000000..e14df638 --- /dev/null +++ b/bin/run_tests.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +# Copyright 2017 Red Hat Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +set -e + +npm run build +jest diff --git a/package.json b/package.json index f1bd9177..c5948d90 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,8 @@ "build": "webpack --env=prod --bail --progress", "build:dev": "webpack --env=dev --bail --progress", "start": "webpack-dev-server --env=dev --progress", - "test": "jest", + "test": "./bin/run_tests.sh", + "test:quick": "jest", "test:watch": "jest --watchAll", "json2pot": "rip json2pot ./i18n/extracted-messages/**/*.json -o ./i18n/messages.pot", "po2json": "rip po2json -m ./i18n/extracted-messages/**/*.json", diff --git a/src/js/actions/I18nActions.js b/src/js/actions/I18nActions.js index 2b342d6b..98008990 100644 --- a/src/js/actions/I18nActions.js +++ b/src/js/actions/I18nActions.js @@ -14,38 +14,31 @@ * under the License. */ -import { getEnabledLanguages } from '../services/utils'; +import { getEnabledLanguages } from '../selectors/i18n'; +import { MESSAGES } from '../components/i18n/messages'; export default { - detectLanguage(messages) { - const configLanguages = getEnabledLanguages(); - let language; - // If the configuration contains only one language and there - // are messages for it, return it; - if ( - configLanguages && - configLanguages.length === 1 && - messages[configLanguages[0]] - ) { - language = configLanguages[0]; - } else { - const locale = - localStorage.getItem('language') || - (navigator.languages && navigator.languages[0]) || - navigator.language || - navigator.userLanguage; - // If the locale contains the country but we can't find - // messages for it then we only use the country part: - language = locale.match(/^[A-Za-z]+-[A-Za-z]+$/) && !messages[locale] - ? locale.split('-')[0] - : locale; - } - return { - type: 'DETECT_LANGUAGE', - payload: { - language, - messages + detectLanguage() { + return (dispatch, getState) => { + const languages = getEnabledLanguages(getState()); + let language; + // If the configuration contains only one language and there + // are messages for it, return it; + if (languages && languages.length === 1 && MESSAGES[languages[0]]) { + language = languages[0]; + } else { + const locale = + localStorage.getItem('language') || + (navigator.languages && navigator.languages[0]) || + navigator.language || + navigator.userLanguage; + // If the locale contains the country but we can't find + // messages for it then we only use the country part: + language = locale.match(/^[A-Za-z]+-[A-Za-z]+$/) && !MESSAGES[locale] + ? locale.split('-')[0] + : locale; } + dispatch(this.chooseLanguage(language)); }; }, diff --git a/src/js/components/AuthenticatedContent.js b/src/js/components/AuthenticatedContent.js index 3e81ca2e..ce24c281 100644 --- a/src/js/components/AuthenticatedContent.js +++ b/src/js/components/AuthenticatedContent.js @@ -24,6 +24,7 @@ import { Redirect, Route, Switch, withRouter } from 'react-router-dom'; import DebugScreen from './debug/DebugScreen'; import DeploymentPlan from './deployment_plan/DeploymentPlan'; import { getCurrentPlanName } from '../selectors/plans'; +import { getEnabledLanguages } from '../selectors/i18n'; import Loader from './ui/Loader'; import LoginActions from '../actions/LoginActions'; import NavBar from './NavBar'; @@ -49,7 +50,14 @@ class AuthenticatedContent extends React.Component { } render() { - const { currentPlanName, intl, logoutUser, plansLoaded, user } = this.props; + const { + currentPlanName, + intl, + languages, + logoutUser, + plansLoaded, + user + } = this.props; return (
- +
@@ -87,6 +99,7 @@ AuthenticatedContent.propTypes = { fetchWorkflowExecutions: PropTypes.func, initializeZaqarConnection: PropTypes.func.isRequired, intl: PropTypes.object, + languages: ImmutablePropTypes.map.isRequired, logoutUser: PropTypes.func.isRequired, plansLoaded: PropTypes.bool, user: ImmutablePropTypes.map @@ -102,6 +115,7 @@ const mapDispatchToProps = (dispatch, ownProps) => ({ }); const mapStateToProps = state => ({ + languages: getEnabledLanguages(state), currentPlanName: getCurrentPlanName(state), plansLoaded: state.plans.get('plansLoaded'), user: state.login.getIn(['token', 'user']) diff --git a/src/js/components/NavBar.js b/src/js/components/NavBar.js index d3307f6f..ac059fb6 100644 --- a/src/js/components/NavBar.js +++ b/src/js/components/NavBar.js @@ -20,7 +20,6 @@ import React from 'react'; import { Link } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { getEnabledLanguages } from '../services/utils'; import NavTab from './ui/NavTab'; import I18nDropdown from './i18n/I18nDropdown'; import StatusDropdown from './StatusDropdown'; @@ -57,11 +56,9 @@ export default class NavBar extends React.Component { } _renderLanguageDropdown() { - const languages = getEnabledLanguages(); - // Only include the I18nDropdown if there's more than one // language to choose from. - return Object.keys(languages).length > 1 + return this.props.languages.size > 1 ?
  • @@ -135,6 +132,7 @@ export default class NavBar extends React.Component { } } NavBar.propTypes = { + languages: ImmutablePropTypes.map.isRequired, onLogout: PropTypes.func.isRequired, user: ImmutablePropTypes.map }; diff --git a/src/js/components/i18n/I18nDropdown.js b/src/js/components/i18n/I18nDropdown.js index cc0a6d2f..7babf297 100644 --- a/src/js/components/i18n/I18nDropdown.js +++ b/src/js/components/i18n/I18nDropdown.js @@ -16,15 +16,16 @@ import { connect } from 'react-redux'; import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; +import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import React from 'react'; import Dropdown from '../ui/dropdown/Dropdown'; import DropdownToggle from '../ui/dropdown/DropdownToggle'; import DropdownItem from '../ui/dropdown/DropdownItem'; +import { getEnabledLanguages, getCurrentLanguage } from '../../selectors/i18n'; import I18nActions from '../../actions/I18nActions'; import { MESSAGES } from './messages'; -import { getEnabledLanguages } from '../../services/utils'; const messages = defineMessages({ language: { @@ -35,10 +36,10 @@ const messages = defineMessages({ class I18nDropdown extends React.Component { _renderDropdownItems() { - const enabledLang = this.props.language; - return getEnabledLanguages() + const { currentLanguage, languages } = this.props; + return languages .map((langName, langKey) => { - const active = enabledLang === langKey; + const active = currentLanguage === langKey; return MESSAGES[langKey] || langKey === 'en' ? { return { - language: state.i18n.get('language', 'en') + languages: getEnabledLanguages(state), + currentLanguage: getCurrentLanguage(state) }; }; diff --git a/src/js/components/i18n/I18nProvider.js b/src/js/components/i18n/I18nProvider.js index 944ee995..d5273b7d 100644 --- a/src/js/components/i18n/I18nProvider.js +++ b/src/js/components/i18n/I18nProvider.js @@ -22,8 +22,11 @@ import React from 'react'; import I18nActions from '../../actions/I18nActions'; // NOTE(hpokorny): src/components/i18n/messages.js is generated by webpack on the fly -import { MESSAGES, LOCALE_DATA } from './messages'; -import { getLanguage, getMessages } from '../../selectors/i18n'; +import { LOCALE_DATA } from './messages'; +import { + getCurrentLanguage, + getCurrentLanguageMessages +} from '../../selectors/i18n'; class I18nProvider extends React.Component { constructor() { @@ -32,7 +35,7 @@ class I18nProvider extends React.Component { } componentDidMount() { - this.props.detectLanguage(MESSAGES); + this.props.detectLanguage(); } render() { @@ -63,8 +66,8 @@ const mapDispatchToProps = dispatch => { const mapStateToProps = state => { return { - language: getLanguage(state), - messages: getMessages(state) + language: getCurrentLanguage(state), + messages: getCurrentLanguageMessages(state) }; }; diff --git a/src/js/components/login/LanguageInput.js b/src/js/components/login/LanguageInput.js index 696797a4..4eabdcd5 100644 --- a/src/js/components/login/LanguageInput.js +++ b/src/js/components/login/LanguageInput.js @@ -16,12 +16,12 @@ import React from 'react'; import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; import { injectIntl } from 'react-intl'; -import { getEnabledLanguages } from '../../services/utils'; class LanguageInput extends React.Component { _renderOptions() { - return getEnabledLanguages() + return this.props.languages .map((langName, langKey) => { return (