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
This commit is contained in:
parent
6c253c999d
commit
d0a6bfddd2
|
@ -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
|
|
@ -78,7 +78,8 @@
|
||||||
"build": "webpack --env=prod --bail --progress",
|
"build": "webpack --env=prod --bail --progress",
|
||||||
"build:dev": "webpack --env=dev --bail --progress",
|
"build:dev": "webpack --env=dev --bail --progress",
|
||||||
"start": "webpack-dev-server --env=dev --progress",
|
"start": "webpack-dev-server --env=dev --progress",
|
||||||
"test": "jest",
|
"test": "./bin/run_tests.sh",
|
||||||
|
"test:quick": "jest",
|
||||||
"test:watch": "jest --watchAll",
|
"test:watch": "jest --watchAll",
|
||||||
"json2pot": "rip json2pot ./i18n/extracted-messages/**/*.json -o ./i18n/messages.pot",
|
"json2pot": "rip json2pot ./i18n/extracted-messages/**/*.json -o ./i18n/messages.pot",
|
||||||
"po2json": "rip po2json -m ./i18n/extracted-messages/**/*.json",
|
"po2json": "rip po2json -m ./i18n/extracted-messages/**/*.json",
|
||||||
|
|
|
@ -14,20 +14,18 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getEnabledLanguages } from '../services/utils';
|
import { getEnabledLanguages } from '../selectors/i18n';
|
||||||
|
import { MESSAGES } from '../components/i18n/messages';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
detectLanguage(messages) {
|
detectLanguage() {
|
||||||
const configLanguages = getEnabledLanguages();
|
return (dispatch, getState) => {
|
||||||
|
const languages = getEnabledLanguages(getState());
|
||||||
let language;
|
let language;
|
||||||
// If the configuration contains only one language and there
|
// If the configuration contains only one language and there
|
||||||
// are messages for it, return it;
|
// are messages for it, return it;
|
||||||
if (
|
if (languages && languages.length === 1 && MESSAGES[languages[0]]) {
|
||||||
configLanguages &&
|
language = languages[0];
|
||||||
configLanguages.length === 1 &&
|
|
||||||
messages[configLanguages[0]]
|
|
||||||
) {
|
|
||||||
language = configLanguages[0];
|
|
||||||
} else {
|
} else {
|
||||||
const locale =
|
const locale =
|
||||||
localStorage.getItem('language') ||
|
localStorage.getItem('language') ||
|
||||||
|
@ -36,16 +34,11 @@ export default {
|
||||||
navigator.userLanguage;
|
navigator.userLanguage;
|
||||||
// If the locale contains the country but we can't find
|
// If the locale contains the country but we can't find
|
||||||
// messages for it then we only use the country part:
|
// messages for it then we only use the country part:
|
||||||
language = locale.match(/^[A-Za-z]+-[A-Za-z]+$/) && !messages[locale]
|
language = locale.match(/^[A-Za-z]+-[A-Za-z]+$/) && !MESSAGES[locale]
|
||||||
? locale.split('-')[0]
|
? locale.split('-')[0]
|
||||||
: locale;
|
: locale;
|
||||||
}
|
}
|
||||||
return {
|
dispatch(this.chooseLanguage(language));
|
||||||
type: 'DETECT_LANGUAGE',
|
|
||||||
payload: {
|
|
||||||
language,
|
|
||||||
messages
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ import { Redirect, Route, Switch, withRouter } from 'react-router-dom';
|
||||||
import DebugScreen from './debug/DebugScreen';
|
import DebugScreen from './debug/DebugScreen';
|
||||||
import DeploymentPlan from './deployment_plan/DeploymentPlan';
|
import DeploymentPlan from './deployment_plan/DeploymentPlan';
|
||||||
import { getCurrentPlanName } from '../selectors/plans';
|
import { getCurrentPlanName } from '../selectors/plans';
|
||||||
|
import { getEnabledLanguages } from '../selectors/i18n';
|
||||||
import Loader from './ui/Loader';
|
import Loader from './ui/Loader';
|
||||||
import LoginActions from '../actions/LoginActions';
|
import LoginActions from '../actions/LoginActions';
|
||||||
import NavBar from './NavBar';
|
import NavBar from './NavBar';
|
||||||
|
@ -49,7 +50,14 @@ class AuthenticatedContent extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { currentPlanName, intl, logoutUser, plansLoaded, user } = this.props;
|
const {
|
||||||
|
currentPlanName,
|
||||||
|
intl,
|
||||||
|
languages,
|
||||||
|
logoutUser,
|
||||||
|
plansLoaded,
|
||||||
|
user
|
||||||
|
} = this.props;
|
||||||
return (
|
return (
|
||||||
<Loader
|
<Loader
|
||||||
loaded={plansLoaded}
|
loaded={plansLoaded}
|
||||||
|
@ -57,7 +65,11 @@ class AuthenticatedContent extends React.Component {
|
||||||
global
|
global
|
||||||
>
|
>
|
||||||
<header>
|
<header>
|
||||||
<NavBar user={user} onLogout={logoutUser.bind(this)} />
|
<NavBar
|
||||||
|
user={user}
|
||||||
|
onLogout={logoutUser.bind(this)}
|
||||||
|
languages={languages}
|
||||||
|
/>
|
||||||
</header>
|
</header>
|
||||||
<div className="wrapper-fixed-body container-fluid">
|
<div className="wrapper-fixed-body container-fluid">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
|
@ -87,6 +99,7 @@ AuthenticatedContent.propTypes = {
|
||||||
fetchWorkflowExecutions: PropTypes.func,
|
fetchWorkflowExecutions: PropTypes.func,
|
||||||
initializeZaqarConnection: PropTypes.func.isRequired,
|
initializeZaqarConnection: PropTypes.func.isRequired,
|
||||||
intl: PropTypes.object,
|
intl: PropTypes.object,
|
||||||
|
languages: ImmutablePropTypes.map.isRequired,
|
||||||
logoutUser: PropTypes.func.isRequired,
|
logoutUser: PropTypes.func.isRequired,
|
||||||
plansLoaded: PropTypes.bool,
|
plansLoaded: PropTypes.bool,
|
||||||
user: ImmutablePropTypes.map
|
user: ImmutablePropTypes.map
|
||||||
|
@ -102,6 +115,7 @@ const mapDispatchToProps = (dispatch, ownProps) => ({
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
|
languages: getEnabledLanguages(state),
|
||||||
currentPlanName: getCurrentPlanName(state),
|
currentPlanName: getCurrentPlanName(state),
|
||||||
plansLoaded: state.plans.get('plansLoaded'),
|
plansLoaded: state.plans.get('plansLoaded'),
|
||||||
user: state.login.getIn(['token', 'user'])
|
user: state.login.getIn(['token', 'user'])
|
||||||
|
|
|
@ -20,7 +20,6 @@ import React from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
|
||||||
import { getEnabledLanguages } from '../services/utils';
|
|
||||||
import NavTab from './ui/NavTab';
|
import NavTab from './ui/NavTab';
|
||||||
import I18nDropdown from './i18n/I18nDropdown';
|
import I18nDropdown from './i18n/I18nDropdown';
|
||||||
import StatusDropdown from './StatusDropdown';
|
import StatusDropdown from './StatusDropdown';
|
||||||
|
@ -57,11 +56,9 @@ export default class NavBar extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderLanguageDropdown() {
|
_renderLanguageDropdown() {
|
||||||
const languages = getEnabledLanguages();
|
|
||||||
|
|
||||||
// Only include the I18nDropdown if there's more than one
|
// Only include the I18nDropdown if there's more than one
|
||||||
// language to choose from.
|
// language to choose from.
|
||||||
return Object.keys(languages).length > 1
|
return this.props.languages.size > 1
|
||||||
? <li>
|
? <li>
|
||||||
<I18nDropdown />
|
<I18nDropdown />
|
||||||
</li>
|
</li>
|
||||||
|
@ -135,6 +132,7 @@ export default class NavBar extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NavBar.propTypes = {
|
NavBar.propTypes = {
|
||||||
|
languages: ImmutablePropTypes.map.isRequired,
|
||||||
onLogout: PropTypes.func.isRequired,
|
onLogout: PropTypes.func.isRequired,
|
||||||
user: ImmutablePropTypes.map
|
user: ImmutablePropTypes.map
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,15 +16,16 @@
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import Dropdown from '../ui/dropdown/Dropdown';
|
import Dropdown from '../ui/dropdown/Dropdown';
|
||||||
import DropdownToggle from '../ui/dropdown/DropdownToggle';
|
import DropdownToggle from '../ui/dropdown/DropdownToggle';
|
||||||
import DropdownItem from '../ui/dropdown/DropdownItem';
|
import DropdownItem from '../ui/dropdown/DropdownItem';
|
||||||
|
import { getEnabledLanguages, getCurrentLanguage } from '../../selectors/i18n';
|
||||||
import I18nActions from '../../actions/I18nActions';
|
import I18nActions from '../../actions/I18nActions';
|
||||||
import { MESSAGES } from './messages';
|
import { MESSAGES } from './messages';
|
||||||
import { getEnabledLanguages } from '../../services/utils';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
language: {
|
language: {
|
||||||
|
@ -35,10 +36,10 @@ const messages = defineMessages({
|
||||||
|
|
||||||
class I18nDropdown extends React.Component {
|
class I18nDropdown extends React.Component {
|
||||||
_renderDropdownItems() {
|
_renderDropdownItems() {
|
||||||
const enabledLang = this.props.language;
|
const { currentLanguage, languages } = this.props;
|
||||||
return getEnabledLanguages()
|
return languages
|
||||||
.map((langName, langKey) => {
|
.map((langName, langKey) => {
|
||||||
const active = enabledLang === langKey;
|
const active = currentLanguage === langKey;
|
||||||
return MESSAGES[langKey] || langKey === 'en'
|
return MESSAGES[langKey] || langKey === 'en'
|
||||||
? <DropdownItem
|
? <DropdownItem
|
||||||
key={`lang-${langKey}`}
|
key={`lang-${langKey}`}
|
||||||
|
@ -66,12 +67,14 @@ class I18nDropdown extends React.Component {
|
||||||
|
|
||||||
I18nDropdown.propTypes = {
|
I18nDropdown.propTypes = {
|
||||||
chooseLanguage: PropTypes.func.isRequired,
|
chooseLanguage: PropTypes.func.isRequired,
|
||||||
language: PropTypes.string
|
currentLanguage: PropTypes.string,
|
||||||
|
languages: ImmutablePropTypes.map.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
return {
|
return {
|
||||||
language: state.i18n.get('language', 'en')
|
languages: getEnabledLanguages(state),
|
||||||
|
currentLanguage: getCurrentLanguage(state)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -22,8 +22,11 @@ import React from 'react';
|
||||||
import I18nActions from '../../actions/I18nActions';
|
import I18nActions from '../../actions/I18nActions';
|
||||||
|
|
||||||
// NOTE(hpokorny): src/components/i18n/messages.js is generated by webpack on the fly
|
// NOTE(hpokorny): src/components/i18n/messages.js is generated by webpack on the fly
|
||||||
import { MESSAGES, LOCALE_DATA } from './messages';
|
import { LOCALE_DATA } from './messages';
|
||||||
import { getLanguage, getMessages } from '../../selectors/i18n';
|
import {
|
||||||
|
getCurrentLanguage,
|
||||||
|
getCurrentLanguageMessages
|
||||||
|
} from '../../selectors/i18n';
|
||||||
|
|
||||||
class I18nProvider extends React.Component {
|
class I18nProvider extends React.Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -32,7 +35,7 @@ class I18nProvider extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.detectLanguage(MESSAGES);
|
this.props.detectLanguage();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -63,8 +66,8 @@ const mapDispatchToProps = dispatch => {
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
return {
|
return {
|
||||||
language: getLanguage(state),
|
language: getCurrentLanguage(state),
|
||||||
messages: getMessages(state)
|
messages: getCurrentLanguageMessages(state)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -16,12 +16,12 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { injectIntl } from 'react-intl';
|
import { injectIntl } from 'react-intl';
|
||||||
import { getEnabledLanguages } from '../../services/utils';
|
|
||||||
|
|
||||||
class LanguageInput extends React.Component {
|
class LanguageInput extends React.Component {
|
||||||
_renderOptions() {
|
_renderOptions() {
|
||||||
return getEnabledLanguages()
|
return this.props.languages
|
||||||
.map((langName, langKey) => {
|
.map((langName, langKey) => {
|
||||||
return (
|
return (
|
||||||
<option key={`lang-${langKey}`} value={langKey}>
|
<option key={`lang-${langKey}`} value={langKey}>
|
||||||
|
@ -63,6 +63,7 @@ LanguageInput.propTypes = {
|
||||||
autoFocus: PropTypes.bool,
|
autoFocus: PropTypes.bool,
|
||||||
chooseLanguage: PropTypes.func.isRequired,
|
chooseLanguage: PropTypes.func.isRequired,
|
||||||
language: PropTypes.string,
|
language: PropTypes.string,
|
||||||
|
languages: ImmutablePropTypes.map.isRequired,
|
||||||
name: PropTypes.string.isRequired
|
name: PropTypes.string.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ import ReactDOM from 'react-dom';
|
||||||
import { Redirect } from 'react-router-dom';
|
import { Redirect } from 'react-router-dom';
|
||||||
|
|
||||||
import FormErrorList from '../ui/forms/FormErrorList';
|
import FormErrorList from '../ui/forms/FormErrorList';
|
||||||
|
import { getCurrentLanguage, getEnabledLanguages } from '../../selectors/i18n';
|
||||||
import I18nActions from '../../actions/I18nActions';
|
import I18nActions from '../../actions/I18nActions';
|
||||||
import Loader from '../ui/Loader';
|
import Loader from '../ui/Loader';
|
||||||
import LoginInput from '../ui/forms/LoginInput';
|
import LoginInput from '../ui/forms/LoginInput';
|
||||||
|
@ -148,6 +149,7 @@ class Login extends React.Component {
|
||||||
name="language"
|
name="language"
|
||||||
chooseLanguage={this.props.chooseLanguage}
|
chooseLanguage={this.props.chooseLanguage}
|
||||||
language={this.props.language}
|
language={this.props.language}
|
||||||
|
languages={this.props.languages}
|
||||||
/>
|
/>
|
||||||
<LoginInput
|
<LoginInput
|
||||||
name="username"
|
name="username"
|
||||||
|
@ -205,6 +207,7 @@ Login.propTypes = {
|
||||||
isAuthenticated: PropTypes.bool.isRequired,
|
isAuthenticated: PropTypes.bool.isRequired,
|
||||||
isAuthenticating: PropTypes.bool.isRequired,
|
isAuthenticating: PropTypes.bool.isRequired,
|
||||||
language: PropTypes.string,
|
language: PropTypes.string,
|
||||||
|
languages: ImmutablePropTypes.map.isRequired,
|
||||||
location: PropTypes.object,
|
location: PropTypes.object,
|
||||||
userLoggedIn: PropTypes.bool.isRequired
|
userLoggedIn: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
@ -215,7 +218,8 @@ function mapStateToProps(state) {
|
||||||
formFieldErrors: state.login.getIn(['loginForm', 'formFieldErrors']),
|
formFieldErrors: state.login.getIn(['loginForm', 'formFieldErrors']),
|
||||||
isAuthenticated: state.login.isAuthenticated,
|
isAuthenticated: state.login.isAuthenticated,
|
||||||
isAuthenticating: state.login.get('isAuthenticating'),
|
isAuthenticating: state.login.get('isAuthenticating'),
|
||||||
language: state.i18n.get('language', 'en'),
|
language: getCurrentLanguage(state),
|
||||||
|
languages: getEnabledLanguages(state),
|
||||||
userLoggedIn: state.login.hasIn(['keystoneAccess', 'user'])
|
userLoggedIn: state.login.hasIn(['keystoneAccess', 'user'])
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { List, Record } from 'immutable';
|
||||||
|
|
||||||
|
export const AppConfig = Record({
|
||||||
|
zaqarDefaultQueue: 'tripleo',
|
||||||
|
zaqarLoggerQueue: 'tripleo-ui-logging',
|
||||||
|
excludedLanguages: List(),
|
||||||
|
loggers: List(['console', 'zaqar'])
|
||||||
|
});
|
|
@ -0,0 +1,26 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { AppConfig } from '../immutableRecords/appConfig';
|
||||||
|
|
||||||
|
const initialState = new AppConfig();
|
||||||
|
|
||||||
|
export default function appConfig(state = initialState, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,7 @@
|
||||||
import { combineReducers } from 'redux';
|
import { combineReducers } from 'redux';
|
||||||
import { reducer as formReducer } from 'redux-form';
|
import { reducer as formReducer } from 'redux-form';
|
||||||
|
|
||||||
|
import appConfig from './appConfig';
|
||||||
import environmentConfigurationReducer from './environmentConfigurationReducer';
|
import environmentConfigurationReducer from './environmentConfigurationReducer';
|
||||||
import filtersReducer from './filtersReducer';
|
import filtersReducer from './filtersReducer';
|
||||||
import i18nReducer from './i18nReducer';
|
import i18nReducer from './i18nReducer';
|
||||||
|
@ -33,6 +34,7 @@ import validationsReducer from './validationsReducer';
|
||||||
import workflowExecutionsReducer from './workflowExecutionsReducer';
|
import workflowExecutionsReducer from './workflowExecutionsReducer';
|
||||||
|
|
||||||
const appReducer = combineReducers({
|
const appReducer = combineReducers({
|
||||||
|
appConfig,
|
||||||
environmentConfiguration: environmentConfigurationReducer,
|
environmentConfiguration: environmentConfigurationReducer,
|
||||||
executions: workflowExecutionsReducer,
|
executions: workflowExecutionsReducer,
|
||||||
filters: filtersReducer,
|
filters: filtersReducer,
|
||||||
|
|
|
@ -14,20 +14,14 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Map } from 'immutable';
|
import { Record } from 'immutable';
|
||||||
|
|
||||||
const initialState = Map({
|
const InitialState = Record({
|
||||||
language: 'en',
|
language: 'en'
|
||||||
messages: {}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function i18nReducer(state = initialState, action) {
|
export default function i18nReducer(state = new InitialState(), action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'DETECT_LANGUAGE':
|
|
||||||
return state
|
|
||||||
.set('language', action.payload.language)
|
|
||||||
.set('messages', action.payload.messages);
|
|
||||||
|
|
||||||
case 'CHOOSE_LANGUAGE':
|
case 'CHOOSE_LANGUAGE':
|
||||||
return state.set('language', action.payload);
|
return state.set('language', action.payload);
|
||||||
|
|
||||||
|
|
|
@ -16,29 +16,40 @@
|
||||||
|
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { IntlProvider } from 'react-intl';
|
import { IntlProvider } from 'react-intl';
|
||||||
|
import { Map } from 'immutable';
|
||||||
|
|
||||||
const languageSelector = state => state.i18n.get('language');
|
import { LANGUAGE_NAMES } from '../constants/i18n';
|
||||||
|
// TODO(jtomasek): This should rather be in /constants
|
||||||
|
import { MESSAGES } from '../components/i18n/messages';
|
||||||
|
|
||||||
const messagesSelector = state => state.i18n.get('messages');
|
const getMessages = () => MESSAGES;
|
||||||
|
const getAvailableLanguages = () => LANGUAGE_NAMES;
|
||||||
|
export const getAppConfig = state => state.appConfig;
|
||||||
|
export const getCurrentLanguage = state => state.i18n.language;
|
||||||
|
|
||||||
export const getLanguage = createSelector(
|
export const getCurrentLanguageMessages = createSelector(
|
||||||
[languageSelector],
|
[getCurrentLanguage, getMessages],
|
||||||
language => language
|
|
||||||
);
|
|
||||||
|
|
||||||
export const getMessages = createSelector(
|
|
||||||
[languageSelector, messagesSelector],
|
|
||||||
(language, messages) => messages[language]
|
(language, messages) => messages[language]
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getIntl = createSelector(
|
export const getIntl = createSelector(
|
||||||
[languageSelector, messagesSelector],
|
[getCurrentLanguage, getCurrentLanguageMessages],
|
||||||
(language, messages) => {
|
(language, messages) => {
|
||||||
const intlProvider = new IntlProvider(
|
const intlProvider = new IntlProvider(
|
||||||
{ locale: language, messages: messages[language] },
|
{ locale: language, messages: messages },
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
const { intl } = intlProvider.getChildContext();
|
const { intl } = intlProvider.getChildContext();
|
||||||
return intl;
|
return intl;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const getEnabledLanguages = createSelector(
|
||||||
|
[getAppConfig, getAvailableLanguages],
|
||||||
|
(appConfig, languages) =>
|
||||||
|
// with immutablejs v 4.0.0 this can be replaced with
|
||||||
|
// Map(languages).deleteAll(appConfig.excludedLanguages).sort();
|
||||||
|
Map(languages)
|
||||||
|
.filterNot((language, key) => appConfig.excludedLanguages.includes(key))
|
||||||
|
.sort()
|
||||||
|
);
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
import { Map, List } from 'immutable';
|
import { Map, List } from 'immutable';
|
||||||
import store from '../store';
|
import store from '../store';
|
||||||
import { LANGUAGE_NAMES } from '../constants/i18n';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the public url of an openstack API,
|
* Returns the public url of an openstack API,
|
||||||
|
@ -62,13 +61,3 @@ export function getProjectId() {
|
||||||
export function getAppConfig() {
|
export function getAppConfig() {
|
||||||
return window.tripleOUiConfig || {};
|
return window.tripleOUiConfig || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getEnabledLanguages() {
|
|
||||||
const excludedLanguages = getAppConfig().excludedLanguages || [];
|
|
||||||
let configLanguages = Object.assign({}, LANGUAGE_NAMES);
|
|
||||||
excludedLanguages.map(language => {
|
|
||||||
delete configLanguages[language];
|
|
||||||
});
|
|
||||||
|
|
||||||
return Map(configLanguages).sort();
|
|
||||||
}
|
|
||||||
|
|
|
@ -16,10 +16,12 @@
|
||||||
|
|
||||||
import { applyMiddleware, createStore } from 'redux';
|
import { applyMiddleware, createStore } from 'redux';
|
||||||
import cookie from 'react-cookie';
|
import cookie from 'react-cookie';
|
||||||
|
import { fromJS } from 'immutable';
|
||||||
import thunkMiddleware from 'redux-thunk';
|
import thunkMiddleware from 'redux-thunk';
|
||||||
import createLogger from 'redux-logger';
|
import createLogger from 'redux-logger';
|
||||||
import logger, { predicate } from './services/logging/LoggingService';
|
import logger, { predicate } from './services/logging/LoggingService';
|
||||||
|
|
||||||
|
import { AppConfig } from './immutableRecords/appConfig';
|
||||||
import appReducer from './reducers/appReducer';
|
import appReducer from './reducers/appReducer';
|
||||||
import { InitialPlanState } from './immutableRecords/plans';
|
import { InitialPlanState } from './immutableRecords/plans';
|
||||||
import { InitialLoginState } from './immutableRecords/login';
|
import { InitialLoginState } from './immutableRecords/login';
|
||||||
|
@ -27,6 +29,7 @@ import { getIntl } from './selectors/i18n';
|
||||||
|
|
||||||
const hydrateStore = () => {
|
const hydrateStore = () => {
|
||||||
return {
|
return {
|
||||||
|
appConfig: new AppConfig(window && fromJS(window.tripleOUiConfig)),
|
||||||
plans: new InitialPlanState({
|
plans: new InitialPlanState({
|
||||||
currentPlanName: getStoredPlanName()
|
currentPlanName: getStoredPlanName()
|
||||||
}),
|
}),
|
||||||
|
|
Loading…
Reference in New Issue