Adds basic internationalization support
This patch adds an I18NProvider component to implement react-intl's translation mechanisms and replaces text strings in the deployment_plan components with formatted messages. The patch also contains scripts to convert extracted messages to `.pot` files (which can be used with zanata.org) and back. See more details in the README changes. More components need to be updated once this patch has landed. Change-Id: Ida9fc65c65bedf377341220be1d7225d1ae58b2c Implements: blueprint tripleo-ui-i18n-support-for-js
This commit is contained in:
parent
9f2244f848
commit
57c98c657d
8
.babelrc
8
.babelrc
@ -1,3 +1,9 @@
|
||||
{
|
||||
"presets": ["es2015", "stage-0", "react"]
|
||||
"presets": ["es2015", "stage-0", "react"],
|
||||
"plugins": [
|
||||
["react-intl", {
|
||||
"messagesDir": "./i18n/extracted-messages/",
|
||||
"enforceDescriptions": false
|
||||
}]
|
||||
]
|
||||
}
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -9,3 +9,5 @@ tripleo-ui-*.tar.gz
|
||||
.DS_Store
|
||||
.idea
|
||||
app.conf
|
||||
messages.pot
|
||||
i18n/extracted-messages*
|
||||
|
44
README.md
44
README.md
@ -122,3 +122,47 @@ src/js/components/environment_configuration
|
||||
# Documentation
|
||||
|
||||
Use JSDoc docstrings in code to provide source for autogenerated documentation (http://usejsdoc.org/).
|
||||
|
||||
|
||||
# Translation
|
||||
|
||||
tripleo-ui uses the react-intl package for translation.
|
||||
|
||||
|
||||
## Adding translateable strings
|
||||
|
||||
Strings are prepared for translation using react-intl's `defineMessages` API. Check out `./src/js/components/deployment_plan/` for examples.
|
||||
|
||||
|
||||
## Extracting messages from components
|
||||
|
||||
Messages are extracted during the build process (`npm run build`) and stored in the `./i18n/extracted-messages/` folder. These files can be converted into a single `.pot` file with this command:
|
||||
|
||||
```
|
||||
npm run json2pot
|
||||
```
|
||||
|
||||
The resulting file (`./messages.pot`) can be uploaded to http://zanata.org to create/update the translation.
|
||||
|
||||
|
||||
## Using translated `.po` files
|
||||
|
||||
The translated language file (`messages.po`) then needs to be converted into one JSON file per language (Japanese in this example):
|
||||
|
||||
```
|
||||
npm run po2json -- messages.po -o ./i18n/locales/ja.json
|
||||
```
|
||||
|
||||
|
||||
## Adding a new language
|
||||
|
||||
The languages are defined in the ./src/js/components/i18n/I18NProvider component. To add another language, import the relevant locale data from the react-intl packages, as well as the JSON containing the translation and add the new language to the MESSAGES constant:
|
||||
|
||||
```
|
||||
import ja from 'react-intl/locale-data/ja';
|
||||
import jaMessages from '../../../../i18n/locales/ja.json';
|
||||
|
||||
const MESSAGES = {
|
||||
ja: jaMessages.messages
|
||||
};
|
||||
```
|
||||
|
1
i18n/locales/ja.json
Normal file
1
i18n/locales/ja.json
Normal file
@ -0,0 +1 @@
|
||||
{"messages":{"ConfigurePlanStep.editConfigurationLink":"","DeleteStackButton.deleteDeployment":"","DeploymentFailure.deleteDeployment":"","DeploymentSuccess.deleteDeployment":"","DeleteStackButton.deleteConfirmationQuestion":"","DeploymentConfiguration.deploymentConfiguration":"","DeploymentConfiguration.overallSettings":"","DeploymentConfiguration.parameters":"パラメーター","DeploymentConfigurationSummary.loadingCurrentConfiguration":"","DeploymentFailure.requestingDeletion":"","DeploymentProgress.requestingDeletion":"","DeploymentSuccess.requestingDeletion":"","DeploymentPlan.hardwareStepHeader":"","DeploymentPlan.configureRolesStepHeader":"","DeploymentPlan.deploymentConfigurationStepHeader":"","DeploymentPlan.deployStepHeader":"展開する","DeploymentProgress.cancelDeployment":"","DeploymentProgress.deploymentInProgress":"","DeploymentProgress.viewInformation":"","DeployStep.validateAndDeploy":"","HardwareStep.registerNodes":"","NodesAssignment.assignUnassignNodes":"","NodesAssignment.done":"","NoPlans.noPlansAvailable":"","NoPlans.noPlansAvailableMessage":"","NoPlans.createNewPlan":"","PlansDropdown.manageDeployments":"","PlansDropdown.selectDeployment":"","RoleCard.nodesAssigned":"","RoleCard.assignNodes":"","Roles.loadingDeploymentRoles":"","RolesStep.loadingNodes":""}}
|
@ -71,7 +71,9 @@
|
||||
"lint": "eslint --max-warnings 0 src",
|
||||
"start": "webpack-dev-server --progress",
|
||||
"test": "karma start --single-run",
|
||||
"test:watch": "karma start"
|
||||
"test:watch": "karma start",
|
||||
"json2pot": "rip json2pot ./i18n/extracted-messages/**/*.json -o ./messages.pot",
|
||||
"po2json": "rip po2json"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -1,15 +1,23 @@
|
||||
import React from 'react';
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import { Link } from 'react-router';
|
||||
import React from 'react';
|
||||
|
||||
import DeploymentConfigurationSummary from './DeploymentConfigurationSummary';
|
||||
|
||||
export const ConfigurePlanStep = (props) => {
|
||||
const messages = defineMessages({
|
||||
editConfigurationLink: {
|
||||
id: 'ConfigurePlanStep.editConfigurationLink',
|
||||
defaultMessage: 'Edit Configurations'
|
||||
}
|
||||
});
|
||||
|
||||
const ConfigurePlanStep = (props) => {
|
||||
return (
|
||||
<div>
|
||||
<DeploymentConfigurationSummary { ...props } />
|
||||
|
||||
<Link to="/deployment-plan/configuration">
|
||||
Edit Configuration
|
||||
<FormattedMessage { ...messages.editConfigurationLink} />
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
@ -21,3 +29,5 @@ ConfigurePlanStep.propTypes = {
|
||||
planName: React.PropTypes.string.isRequired,
|
||||
summary: React.PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default ConfigurePlanStep;
|
||||
|
@ -1,10 +1,22 @@
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import React from 'react';
|
||||
|
||||
import ConfirmationModal from '../ui/ConfirmationModal';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import Loader from '../ui/Loader';
|
||||
|
||||
export default class DeleteStackButton extends React.Component {
|
||||
const messages = defineMessages({
|
||||
deleteDeployment: {
|
||||
id: 'DeleteStackButton.deleteDeployment',
|
||||
defaultMessage: 'Delete Deployment'
|
||||
},
|
||||
deleteConfirmationQuestion: {
|
||||
id: 'DeleteStackButton.deleteConfirmationQuestion',
|
||||
defaultMessage: 'Are you sure you want to delete the stack?'
|
||||
}
|
||||
});
|
||||
|
||||
class DeleteStackButton extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
@ -18,6 +30,8 @@ export default class DeleteStackButton extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { formatMessage } = this.props.intl;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={() => this.setState({ showDeleteModal: true })}
|
||||
@ -33,8 +47,8 @@ export default class DeleteStackButton extends React.Component {
|
||||
</Loader>
|
||||
</button>
|
||||
<ConfirmationModal show={this.state.showDeleteModal}
|
||||
title="Delete Deployment"
|
||||
question="Are you sure you want to delete the stack?"
|
||||
title={formatMessage(messages.deleteDeployment)}
|
||||
question={formatMessage(messages.deleteConfirmationQuestion)}
|
||||
iconClass="pficon pficon-delete"
|
||||
confirmActionName="delete"
|
||||
onConfirm={this.confirmDelete.bind(this)}
|
||||
@ -49,6 +63,7 @@ DeleteStackButton.propTypes = {
|
||||
content: React.PropTypes.string.isRequired,
|
||||
deleteStack: React.PropTypes.func.isRequired,
|
||||
disabled: React.PropTypes.bool.isRequired,
|
||||
intl: React.PropTypes.object,
|
||||
loaded: React.PropTypes.bool.isRequired,
|
||||
loaderContent: React.PropTypes.string.isRequired,
|
||||
stack: ImmutablePropTypes.record.isRequired
|
||||
@ -56,3 +71,5 @@ DeleteStackButton.propTypes = {
|
||||
DeleteStackButton.defaultProps = {
|
||||
buttonIconClass: 'pficon pficon-delete'
|
||||
};
|
||||
|
||||
export default injectIntl(DeleteStackButton);
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import React from 'react';
|
||||
|
||||
import DeploymentSuccess from './DeploymentSuccess';
|
||||
import DeploymentFailure from './DeploymentFailure';
|
||||
@ -8,11 +9,18 @@ import Link from '../ui/Link';
|
||||
import Loader from '../ui/Loader';
|
||||
import { stackStates } from '../../constants/StacksConstants';
|
||||
|
||||
export const DeployStep = ({ currentPlan, currentStack, currentStackResources,
|
||||
currentStackResourcesLoaded, currentStackDeploymentProgress,
|
||||
deleteStack, deployPlan, fetchStackResource, fetchStackEnvironment,
|
||||
isRequestingStackDelete, runPostDeploymentValidations,
|
||||
const messages = defineMessages({
|
||||
validateAndDeploy: {
|
||||
id: 'DeployStep.validateAndDeploy',
|
||||
defaultMessage: 'Validate and Deploy'
|
||||
}
|
||||
});
|
||||
|
||||
const DeployStep = ({ currentPlan, currentStack, currentStackResources, currentStackResourcesLoaded,
|
||||
currentStackDeploymentProgress, deleteStack, deployPlan, fetchStackResource,
|
||||
fetchStackEnvironment, isRequestingStackDelete, runPostDeploymentValidations,
|
||||
stacksLoaded }) => {
|
||||
|
||||
if (!currentStack || currentStack.stack_status === stackStates.DELETE_COMPLETE) {
|
||||
return (
|
||||
<Loader loaded={stacksLoaded}>
|
||||
@ -23,7 +31,8 @@ export const DeployStep = ({ currentPlan, currentStack, currentStackResources,
|
||||
content="Requesting a deploy..."
|
||||
component="span"
|
||||
inline>
|
||||
<span className="fa fa-cloud-upload"/> Validate and Deploy
|
||||
<span className="fa fa-cloud-upload"/> <FormattedMessage
|
||||
{...messages.validateAndDeploy}/>
|
||||
</Loader>
|
||||
</Link>
|
||||
</Loader>
|
||||
@ -69,3 +78,5 @@ DeployStep.propTypes = {
|
||||
runPostDeploymentValidations: React.PropTypes.func.isRequired,
|
||||
stacksLoaded: React.PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
export default DeployStep;
|
||||
|
@ -1,9 +1,25 @@
|
||||
import React from 'react';
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import { Link } from 'react-router';
|
||||
import React from 'react';
|
||||
|
||||
import NavTab from '../ui/NavTab';
|
||||
import Modal from '../ui/Modal';
|
||||
|
||||
const messages = defineMessages({
|
||||
deploymentConfiguration: {
|
||||
id: 'DeploymentConfiguration.deploymentConfiguration',
|
||||
defaultMessage: 'Deployment Configuration'
|
||||
},
|
||||
overallSettings: {
|
||||
id: 'DeploymentConfiguration.overallSettings',
|
||||
defaultMessage: 'Overall Settings'
|
||||
},
|
||||
parameters: {
|
||||
id: 'DeploymentConfiguration.parameters',
|
||||
defaultMessage: 'Parameters'
|
||||
}
|
||||
});
|
||||
|
||||
export default class DeploymentConfiguration extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
@ -14,12 +30,18 @@ export default class DeploymentConfiguration extends React.Component {
|
||||
className="close">
|
||||
<span aria-hidden="true" className="pficon pficon-close"/>
|
||||
</Link>
|
||||
<h4 className="modal-title">Deployment Configuration</h4>
|
||||
<h4 className="modal-title">
|
||||
<FormattedMessage {...messages.deploymentConfiguration}/>
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
<ul className="nav nav-tabs">
|
||||
<NavTab to="/deployment-plan/configuration/environment">Overall Settings</NavTab>
|
||||
<NavTab to="/deployment-plan/configuration/parameters">Parameters</NavTab>
|
||||
<NavTab to="/deployment-plan/configuration/environment">
|
||||
<FormattedMessage {...messages.overallSettings}/>
|
||||
</NavTab>
|
||||
<NavTab to="/deployment-plan/configuration/parameters">
|
||||
<FormattedMessage {...messages.parameters}/>
|
||||
</NavTab>
|
||||
</ul>
|
||||
|
||||
{React.cloneElement(this.props.children,
|
||||
|
@ -1,8 +1,16 @@
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import React from 'react';
|
||||
|
||||
import Loader from '../ui/Loader';
|
||||
|
||||
export default class DeploymentConfigurationSummary extends React.Component {
|
||||
const messages = defineMessages({
|
||||
loadingCurrentConfiguration: {
|
||||
id: 'DeploymentConfigurationSummary.loadingCurrentConfiguration',
|
||||
defaultMessage: 'Loading current Deployment Configuration...'
|
||||
}
|
||||
});
|
||||
|
||||
class DeploymentConfigurationSummary extends React.Component {
|
||||
componentDidMount() {
|
||||
if(this.props.planName) {
|
||||
this.props.fetchEnvironmentConfiguration(this.props.planName);
|
||||
@ -18,7 +26,7 @@ export default class DeploymentConfigurationSummary extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Loader loaded={this.props.loaded}
|
||||
content="Loading current Deployment Configuration..."
|
||||
content={this.props.intl.formatMessage(messages.loadingCurrentConfiguration)}
|
||||
component="span"
|
||||
inline>
|
||||
<span>{this.props.summary}</span>
|
||||
@ -28,8 +36,11 @@ export default class DeploymentConfigurationSummary extends React.Component {
|
||||
}
|
||||
DeploymentConfigurationSummary.propTypes = {
|
||||
fetchEnvironmentConfiguration: React.PropTypes.func.isRequired,
|
||||
intl: React.PropTypes.object,
|
||||
isFetching: React.PropTypes.bool.isRequired,
|
||||
loaded: React.PropTypes.bool.isRequired,
|
||||
planName: React.PropTypes.string,
|
||||
summary: React.PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(DeploymentConfigurationSummary);
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { Link } from 'react-router';
|
||||
@ -6,8 +7,21 @@ import DeleteStackButton from './DeleteStackButton';
|
||||
import InlineNotification from '../ui/InlineNotification';
|
||||
import { deploymentStatusMessages } from '../../constants/StacksConstants';
|
||||
|
||||
export default class DeploymentFailure extends React.Component {
|
||||
const messages = defineMessages({
|
||||
deleteDeployment: {
|
||||
id: 'DeploymentFailure.deleteDeployment',
|
||||
defaultMessage: 'Delete Deployment'
|
||||
},
|
||||
requestingDeletion: {
|
||||
id: 'DeploymentFailure.requestingDeletion',
|
||||
defaultMessage: 'Requesting Deletion of Deployment'
|
||||
}
|
||||
});
|
||||
|
||||
class DeploymentFailure extends React.Component {
|
||||
render() {
|
||||
const { formatMessage } = this.props.intl;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<InlineNotification type="error"
|
||||
@ -17,11 +31,11 @@ export default class DeploymentFailure extends React.Component {
|
||||
More details</Link>
|
||||
</p>
|
||||
</InlineNotification>
|
||||
<DeleteStackButton content="Delete Deployment"
|
||||
<DeleteStackButton content={formatMessage(messages.deleteDeployment)}
|
||||
deleteStack={this.props.deleteStack}
|
||||
disabled={this.props.isRequestingStackDelete}
|
||||
loaded={!this.props.isRequestingStackDelete}
|
||||
loaderContent="Requesting Deletion of Deployment"
|
||||
loaderContent={formatMessage(messages.requestingDeletion)}
|
||||
stack={this.props.stack}/>
|
||||
</div>
|
||||
);
|
||||
@ -30,6 +44,9 @@ export default class DeploymentFailure extends React.Component {
|
||||
|
||||
DeploymentFailure.propTypes = {
|
||||
deleteStack: React.PropTypes.func.isRequired,
|
||||
intl: React.PropTypes.object,
|
||||
isRequestingStackDelete: React.PropTypes.bool,
|
||||
stack: ImmutablePropTypes.record.isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(DeploymentFailure);
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import React from 'react';
|
||||
|
||||
@ -9,22 +10,41 @@ import { getCurrentStack,
|
||||
import { getAvailableNodes, getUnassignedAvailableNodes } from '../../selectors/nodes';
|
||||
import { getEnvironmentConfigurationSummary } from '../../selectors/environmentConfiguration';
|
||||
import { getCurrentPlan } from '../../selectors/plans';
|
||||
import { ConfigurePlanStep } from './ConfigurePlanStep';
|
||||
import ConfigurePlanStep from './ConfigurePlanStep';
|
||||
import CurrentPlanActions from '../../actions/CurrentPlanActions';
|
||||
import { DeploymentPlanStep } from './DeploymentPlanStep';
|
||||
import { DeployStep } from './DeployStep';
|
||||
import DeployStep from './DeployStep';
|
||||
import EnvironmentConfigurationActions from '../../actions/EnvironmentConfigurationActions';
|
||||
import { HardwareStep } from './HardwareStep';
|
||||
import HardwareStep from './HardwareStep';
|
||||
import PlansDropdown from './PlansDropdown';
|
||||
import NodesActions from '../../actions/NodesActions';
|
||||
import NoPlans from './NoPlans';
|
||||
import NotificationActions from '../../actions/NotificationActions';
|
||||
import PlanActions from '../../actions/PlansActions';
|
||||
import StacksActions from '../../actions/StacksActions';
|
||||
import { RolesStep } from './RolesStep';
|
||||
import RolesStep from './RolesStep';
|
||||
import RolesActions from '../../actions/RolesActions';
|
||||
import ValidationsActions from '../../actions/ValidationsActions';
|
||||
|
||||
const messages = defineMessages({
|
||||
hardwareStepHeader: {
|
||||
id: 'DeploymentPlan.hardwareStepHeader',
|
||||
defaultMessage: 'Prepare Hardware'
|
||||
},
|
||||
configureRolesStepHeader: {
|
||||
id: 'DeploymentPlan.configureRolesStepHeader',
|
||||
defaultMessage: 'Configure Roles and Assign Nodes'
|
||||
},
|
||||
deploymentConfigurationStepHeader: {
|
||||
id: 'DeploymentPlan.deploymentConfigurationStepHeader',
|
||||
defaultMessage: 'Specify Deployment Configuration'
|
||||
},
|
||||
deployStepHeader: {
|
||||
id: 'DeploymentPlan.deployStepHeader',
|
||||
defaultMessage: 'Deploy'
|
||||
}
|
||||
});
|
||||
|
||||
class DeploymentPlan extends React.Component {
|
||||
componentDidMount() {
|
||||
this.props.fetchStacks();
|
||||
@ -52,6 +72,7 @@ class DeploymentPlan extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { formatMessage } = this.props.intl;
|
||||
let children;
|
||||
const currentPlanName = this.props.hasPlans ? this.props.currentPlan.name : undefined;
|
||||
|
||||
@ -75,11 +96,11 @@ class DeploymentPlan extends React.Component {
|
||||
</h1>
|
||||
</div>
|
||||
<ol className="deployment-step-list">
|
||||
<DeploymentPlanStep title="Prepare Hardware"
|
||||
<DeploymentPlanStep title={formatMessage(messages.hardwareStepHeader)}
|
||||
disabled={this.props.currentStackDeploymentInProgress}>
|
||||
<HardwareStep />
|
||||
</DeploymentPlanStep>
|
||||
<DeploymentPlanStep title="Specify Deployment Configuration"
|
||||
<DeploymentPlanStep title={formatMessage(messages.deploymentConfigurationStepHeader)}
|
||||
disabled={this.props.currentStackDeploymentInProgress}>
|
||||
<ConfigurePlanStep
|
||||
fetchEnvironmentConfiguration={this.props.fetchEnvironmentConfiguration}
|
||||
@ -88,7 +109,7 @@ class DeploymentPlan extends React.Component {
|
||||
isFetching={this.props.isFetchingEnvironmentConfiguration}
|
||||
loaded={this.props.environmentConfigurationLoaded}/>
|
||||
</DeploymentPlanStep>
|
||||
<DeploymentPlanStep title="Configure Roles and Assign Nodes"
|
||||
<DeploymentPlanStep title={formatMessage(messages.configureRolesStepHeader)}
|
||||
disabled={this.props.currentStackDeploymentInProgress}>
|
||||
<RolesStep availableNodes={this.props.availableNodes}
|
||||
fetchNodes={this.props.fetchNodes}
|
||||
@ -99,7 +120,7 @@ class DeploymentPlan extends React.Component {
|
||||
rolesLoaded={this.props.rolesLoaded}
|
||||
unassignedAvailableNodes={this.props.unassignedAvailableNodes}/>
|
||||
</DeploymentPlanStep>
|
||||
<DeploymentPlanStep title="Deploy">
|
||||
<DeploymentPlanStep title={formatMessage(messages.deployStepHeader)}>
|
||||
<DeployStep
|
||||
currentPlan={this.props.currentPlan}
|
||||
currentStack={this.props.currentStack}
|
||||
@ -151,6 +172,7 @@ DeploymentPlan.propTypes = {
|
||||
fetchStacks: React.PropTypes.func,
|
||||
hasPlans: React.PropTypes.bool,
|
||||
inactivePlans: ImmutablePropTypes.map,
|
||||
intl: React.PropTypes.object,
|
||||
isFetchingEnvironmentConfiguration: React.PropTypes.bool,
|
||||
isFetchingNodes: React.PropTypes.bool,
|
||||
isFetchingRoles: React.PropTypes.bool,
|
||||
@ -213,4 +235,4 @@ function mapDispatchToProps(dispatch) {
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(DeploymentPlan);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(DeploymentPlan));
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router';
|
||||
import React from 'react';
|
||||
|
||||
import { deploymentStatusMessages as statusMessages,
|
||||
stackStates } from '../../constants/StacksConstants';
|
||||
@ -8,7 +9,26 @@ import DeleteStackButton from './DeleteStackButton';
|
||||
import Loader from '../ui/Loader';
|
||||
import ProgressBar from '../ui/ProgressBar';
|
||||
|
||||
export default class DeploymentProgress extends React.Component {
|
||||
const messages = defineMessages({
|
||||
cancelDeployment: {
|
||||
id: 'DeploymentProgress.cancelDeployment',
|
||||
defaultMessage: 'Cancel Deployment'
|
||||
},
|
||||
requestingDeletion: {
|
||||
id: 'DeploymentProgress.requestingDeletion',
|
||||
defaultMessage: 'Requesting Deletion of Deployment'
|
||||
},
|
||||
deploymentInProgress: {
|
||||
id: 'DeploymentProgress.deploymentInProgress',
|
||||
defaultMessage: 'Deployment is currently in progress.'
|
||||
},
|
||||
viewInformation: {
|
||||
id: 'DeploymentProgress.viewInformation',
|
||||
defaultMessage: 'View detailed information'
|
||||
}
|
||||
});
|
||||
|
||||
class DeploymentProgress extends React.Component {
|
||||
renderProgressBar() {
|
||||
return (
|
||||
this.props.stack.stack_status === stackStates.CREATE_IN_PROGRESS ? (
|
||||
@ -20,24 +40,27 @@ export default class DeploymentProgress extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { formatMessage } = this.props.intl;
|
||||
|
||||
const statusMessage = (
|
||||
<strong>{statusMessages[this.props.stack.stack_status]}</strong>
|
||||
);
|
||||
|
||||
const deleteButton = this.props.stack.stack_status !== stackStates.DELETE_IN_PROGRESS
|
||||
? (<DeleteStackButton content="Cancel Deployment"
|
||||
? (<DeleteStackButton content={formatMessage(messages.cancelDeployment)}
|
||||
buttonIconClass="fa fa-ban"
|
||||
deleteStack={this.props.deleteStack}
|
||||
disabled={this.props.isRequestingStackDelete}
|
||||
loaded={!this.props.isRequestingStackDelete}
|
||||
loaderContent="Requesting Deletion of Deployment"
|
||||
loaderContent={formatMessage(messages.requestingDeletion)}
|
||||
stack={this.props.stack}/>) : null;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
Deployment is currently in progress. <Link to="/deployment-plan/deployment-detail">
|
||||
View detailed information
|
||||
<span><FormattedMessage {...messages.deploymentInProgress}/> </span>
|
||||
<Link to="/deployment-plan/deployment-detail">
|
||||
<FormattedMessage {...messages.viewInformation}/>
|
||||
</Link>
|
||||
</p>
|
||||
<div className="progress-description">
|
||||
@ -53,6 +76,9 @@ export default class DeploymentProgress extends React.Component {
|
||||
DeploymentProgress.propTypes = {
|
||||
deleteStack: React.PropTypes.func.isRequired,
|
||||
deploymentProgress: React.PropTypes.number.isRequired,
|
||||
intl: React.PropTypes.object,
|
||||
isRequestingStackDelete: React.PropTypes.bool.isRequired,
|
||||
stack: ImmutablePropTypes.record.isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(DeploymentProgress);
|
||||
|
@ -1,12 +1,24 @@
|
||||
import React from 'react';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import React from 'react';
|
||||
|
||||
import DeleteStackButton from './DeleteStackButton';
|
||||
import { deploymentStatusMessages } from '../../constants/StacksConstants';
|
||||
import InlineNotification from '../ui/InlineNotification';
|
||||
import OvercloudInfo from '../deployment/OvercloudInfo';
|
||||
|
||||
export default class DeploymentSuccess extends React.Component {
|
||||
const messages = defineMessages({
|
||||
deleteDeployment: {
|
||||
id: 'DeploymentSuccess.deleteDeployment',
|
||||
defaultMessage: 'Delete Deployment'
|
||||
},
|
||||
requestingDeletion: {
|
||||
id: 'DeploymentSuccess.requestingDeletion',
|
||||
defaultMessage: 'Requesting Deletion of Deployment'
|
||||
}
|
||||
});
|
||||
|
||||
class DeploymentSuccess extends React.Component {
|
||||
componentDidMount() {
|
||||
this.props.fetchStackResource(this.props.stack, 'PublicVirtualIP');
|
||||
this.props.fetchStackEnvironment(this.props.stack);
|
||||
@ -14,6 +26,8 @@ export default class DeploymentSuccess extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { formatMessage } = this.props.intl;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<InlineNotification type="success"
|
||||
@ -23,11 +37,11 @@ export default class DeploymentSuccess extends React.Component {
|
||||
<OvercloudInfo stackResourcesLoaded={this.props.stackResourcesLoaded}
|
||||
stack={this.props.stack}
|
||||
stackResources={this.props.stackResources}/>
|
||||
<DeleteStackButton content="Delete Deployment"
|
||||
<DeleteStackButton content={formatMessage(messages.deleteDeployment)}
|
||||
deleteStack={this.props.deleteStack}
|
||||
disabled={this.props.isRequestingStackDelete}
|
||||
loaded={!this.props.isRequestingStackDelete}
|
||||
loaderContent="Requesting Deletion of Deployment"
|
||||
loaderContent={formatMessage(messages.requestingDeletion)}
|
||||
stack={this.props.stack}/>
|
||||
</div>
|
||||
);
|
||||
@ -38,9 +52,12 @@ DeploymentSuccess.propTypes = {
|
||||
deleteStack: React.PropTypes.func.isRequired,
|
||||
fetchStackEnvironment: React.PropTypes.func.isRequired,
|
||||
fetchStackResource: React.PropTypes.func.isRequired,
|
||||
intl: React.PropTypes.object,
|
||||
isRequestingStackDelete: React.PropTypes.bool,
|
||||
runPostDeploymentValidations: React.PropTypes.func.isRequired,
|
||||
stack: ImmutablePropTypes.record.isRequired,
|
||||
stackResources: ImmutablePropTypes.map.isRequired,
|
||||
stackResourcesLoaded: React.PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(DeploymentSuccess);
|
||||
|
@ -1,10 +1,20 @@
|
||||
import React from 'react';
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import { Link } from 'react-router';
|
||||
import React from 'react';
|
||||
|
||||
export const HardwareStep = () => {
|
||||
const messages = defineMessages({
|
||||
registerNodes: {
|
||||
id: 'HardwareStep.registerNodes',
|
||||
defaultMessage: 'Register Nodes'
|
||||
}
|
||||
});
|
||||
|
||||
const HardwareStep = () => {
|
||||
return (
|
||||
<Link className="btn btn-default" to="/nodes/registered/register">
|
||||
<span className="fa fa-plus"/> Register Nodes
|
||||
<span className="fa fa-plus"/> <FormattedMessage {...messages.registerNodes}/>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export default HardwareStep;
|
||||
|
@ -1,6 +1,22 @@
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import { Link } from 'react-router';
|
||||
import React from 'react';
|
||||
|
||||
const messages = defineMessages({
|
||||
noPlansAvailable: {
|
||||
id: 'NoPlans.noPlansAvailable',
|
||||
defaultMessage: 'No Deployment Plans Available'
|
||||
},
|
||||
noPlansAvailableMessage: {
|
||||
id: 'NoPlans.noPlansAvailableMessage',
|
||||
defaultMessage: 'There are no Deployment Plans available. Please create one first.'
|
||||
},
|
||||
createNewPlan: {
|
||||
id: 'NoPlans.createNewPlan',
|
||||
defaultMessage: 'Create New Plan'
|
||||
}
|
||||
});
|
||||
|
||||
export default class NoPlans extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
@ -8,13 +24,13 @@ export default class NoPlans extends React.Component {
|
||||
<div className="blank-slate-pf-icon">
|
||||
<span className="fa fa-ban"></span>
|
||||
</div>
|
||||
<h1>No Deployment Plans Available</h1>
|
||||
<p>There are no Deployment Plans available. Please create one first.</p>
|
||||
<h1><FormattedMessage {...messages.noPlansAvailable}/></h1>
|
||||
<p><FormattedMessage {...messages.noPlansAvailableMessage}/></p>
|
||||
<div className="blank-slate-pf-main-action">
|
||||
<Link to="/plans/new"
|
||||
query={{tab: 'newPlan'}}
|
||||
className="btn btn-lg btn-primary">
|
||||
<span className="fa fa-plus"/> Create New Plan
|
||||
<span className="fa fa-plus"/> <FormattedMessage {...messages.createNewPlan}/>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import * as _ from 'lodash';
|
||||
import { connect } from 'react-redux';
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import Formsy from 'formsy-react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { Link } from 'react-router';
|
||||
@ -16,6 +17,17 @@ import Modal from '../ui/Modal';
|
||||
import NodesActions from '../../actions/NodesActions';
|
||||
import NodesTable from '../nodes/NodesTable';
|
||||
|
||||
const messages = defineMessages({
|
||||
assignUnassignNodes: {
|
||||
id: 'NodesAssignment.assignUnassignNodes',
|
||||
defaultMessage: 'Assign/Unassign Selected Nodes'
|
||||
},
|
||||
done: {
|
||||
id: 'NodesAssignment.done',
|
||||
defaultMessage: 'Done'
|
||||
}
|
||||
});
|
||||
|
||||
class NodesAssignment extends React.Component {
|
||||
|
||||
componentDidMount() {
|
||||
@ -36,7 +48,7 @@ class NodesAssignment extends React.Component {
|
||||
<button className="btn btn-primary"
|
||||
type="submit"
|
||||
disabled={this.props.nodesOperationInProgress}>
|
||||
Assign/Unassign Selected Nodes
|
||||
<FormattedMessage {...messages.assignUnassignNodes}/>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
@ -98,7 +110,7 @@ class NodesAssignment extends React.Component {
|
||||
|
||||
<div className="modal-footer">
|
||||
<Link to="/deployment-plan" type="button" className="btn btn-default" >
|
||||
Done
|
||||
<FormattedMessage {...messages.done}/>
|
||||
</Link>
|
||||
</div>
|
||||
</Formsy.Form>
|
||||
|
@ -1,11 +1,23 @@
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router';
|
||||
import React from 'react';
|
||||
|
||||
import DropdownItem from '../ui/dropdown/DropdownItem';
|
||||
import DropdownButton from '../ui/dropdown/DropdownButton';
|
||||
import Dropdown from '../ui/dropdown/Dropdown';
|
||||
|
||||
const messages = defineMessages({
|
||||
manageDeployments: {
|
||||
id: 'PlansDropdown.manageDeployments',
|
||||
defaultMessage: 'Manage Deployments'
|
||||
},
|
||||
selectDeployment: {
|
||||
id: 'PlansDropdown.selectDeployment',
|
||||
defaultMessage: 'Select Deployment'
|
||||
}
|
||||
});
|
||||
|
||||
export default class PlansDropdown extends React.Component {
|
||||
renderRecentPlans() {
|
||||
return this.props.plans.toList().map(plan => {
|
||||
@ -21,15 +33,21 @@ export default class PlansDropdown extends React.Component {
|
||||
render() {
|
||||
if(this.props.plans.isEmpty()) {
|
||||
return (
|
||||
<Link className="btn btn-link" to="/plans/list">Manage Deployments</Link>
|
||||
<Link className="btn btn-link" to="/plans/list">
|
||||
<FormattedMessage {...messages.manageDeployments}/>
|
||||
</Link>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Dropdown>
|
||||
<DropdownButton className="btn-link">Select Deployment</DropdownButton>
|
||||
<DropdownButton className="btn-link">
|
||||
<FormattedMessage {...messages.selectDeployment}/>
|
||||
</DropdownButton>
|
||||
{this.renderRecentPlans()}
|
||||
<DropdownItem key="divider" divider/>
|
||||
<DropdownItem key="plansLink" to="/plans/list">Manage Deployments</DropdownItem>
|
||||
<DropdownItem key="plansLink" to="/plans/list">
|
||||
<FormattedMessage {...messages.manageDeployments}/>
|
||||
</DropdownItem>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,19 @@
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import React from 'react';
|
||||
|
||||
import Link from '../ui/Link';
|
||||
|
||||
const messages = defineMessages({
|
||||
nodesAssigned: {
|
||||
id: 'RoleCard.nodesAssigned',
|
||||
defaultMessage: 'Nodes assigned'
|
||||
},
|
||||
assignNodes: {
|
||||
id: 'RoleCard.assignNodes',
|
||||
defaultMessage: 'Assign Nodes'
|
||||
}
|
||||
});
|
||||
|
||||
export default class RoleCard extends React.Component {
|
||||
render() {
|
||||
const disabled = !this.props.availableNodesCount && !this.props.assignedNodesCount;
|
||||
@ -22,7 +35,9 @@ export default class RoleCard extends React.Component {
|
||||
</span>
|
||||
<span className="card-pf-utilization-card-details-description">
|
||||
<span className="card-pf-utilization-card-details-line-1"> </span>
|
||||
<span className="card-pf-utilization-card-details-line-2">Nodes assigned</span>
|
||||
<span className="card-pf-utilization-card-details-line-2">
|
||||
<FormattedMessage {...messages.nodesAssigned}/>
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
@ -32,7 +47,8 @@ export default class RoleCard extends React.Component {
|
||||
disabled={disabled}
|
||||
to={`/deployment-plan/${this.props.identifier}/assign-nodes`}
|
||||
className="card-pf-link-with-icon">
|
||||
<span className="pficon pficon-add-circle-o" />Assign Nodes
|
||||
<span className="pficon pficon-add-circle-o" />
|
||||
<FormattedMessage {...messages.assignNodes}/>
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
|
@ -1,11 +1,19 @@
|
||||
import React from 'react';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import React from 'react';
|
||||
|
||||
import { getAssignedNodes } from '../../selectors/nodes';
|
||||
import Loader from '../ui/Loader';
|
||||
import RoleCard from './RoleCard';
|
||||
|
||||
export default class Roles extends React.Component {
|
||||
const messages = defineMessages({
|
||||
loadingDeploymentRoles: {
|
||||
id: 'Roles.loadingDeploymentRoles',
|
||||
defaultMessage: 'Loading Deployment Roles...'
|
||||
}
|
||||
});
|
||||
|
||||
class Roles extends React.Component {
|
||||
componentDidMount() {
|
||||
this.props.fetchRoles();
|
||||
this.props.fetchNodes();
|
||||
@ -45,7 +53,7 @@ export default class Roles extends React.Component {
|
||||
<div className="panel panel-default roles-panel">
|
||||
<div className="panel-body">
|
||||
<Loader loaded={this.props.loaded}
|
||||
content="Loading Deployment Roles..."
|
||||
content={this.props.intl.formatMessage(messages.loadingDeploymentRoles)}
|
||||
height={40}>
|
||||
<div className="row-cards-pf">
|
||||
{this.renderRoleCards()}
|
||||
@ -60,9 +68,12 @@ Roles.propTypes = {
|
||||
availableNodes: ImmutablePropTypes.map,
|
||||
fetchNodes: React.PropTypes.func.isRequired,
|
||||
fetchRoles: React.PropTypes.func.isRequired,
|
||||
intl: React.PropTypes.object,
|
||||
isFetchingNodes: React.PropTypes.bool,
|
||||
isFetchingRoles: React.PropTypes.bool,
|
||||
loaded: React.PropTypes.bool.isRequired,
|
||||
roles: React.PropTypes.array.isRequired,
|
||||
unassignedAvailableNodes: ImmutablePropTypes.map
|
||||
};
|
||||
|
||||
export default injectIntl(Roles);
|
||||
|
@ -1,22 +1,31 @@
|
||||
import React from 'react';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import React from 'react';
|
||||
|
||||
import Loader from '../ui/Loader';
|
||||
import Roles from './Roles';
|
||||
|
||||
export const RolesStep = ({ isFetchingNodes,
|
||||
const messages = defineMessages({
|
||||
loadingNodes: {
|
||||
id: 'RolesStep.loadingNodes',
|
||||
defaultMessage: 'Loading Nodes...'
|
||||
}
|
||||
});
|
||||
|
||||
const RolesStep = ({ isFetchingNodes,
|
||||
availableNodes,
|
||||
unassignedAvailableNodes,
|
||||
roles,
|
||||
fetchRoles,
|
||||
fetchNodes,
|
||||
intl,
|
||||
isFetchingRoles,
|
||||
rolesLoaded }) => {
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
<Loader loaded={!isFetchingNodes}
|
||||
content="Loading Nodes..."
|
||||
content={intl.formatMessage(messages.loadingNodes)}
|
||||
component="span"
|
||||
inline>
|
||||
<strong>{unassignedAvailableNodes.size}</strong> Nodes
|
||||
@ -38,9 +47,12 @@ RolesStep.propTypes = {
|
||||
availableNodes: ImmutablePropTypes.map.isRequired,
|
||||
fetchNodes: React.PropTypes.func.isRequired,
|
||||
fetchRoles: React.PropTypes.func.isRequired,
|
||||
intl: React.PropTypes.object,
|
||||
isFetchingNodes: React.PropTypes.bool.isRequired,
|
||||
isFetchingRoles: React.PropTypes.bool.isRequired,
|
||||
roles: ImmutablePropTypes.map.isRequired,
|
||||
rolesLoaded: React.PropTypes.bool.isRequired,
|
||||
unassignedAvailableNodes: ImmutablePropTypes.map.isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(RolesStep);
|
||||
|
46
src/js/components/i18n/I18nProvider.js
Normal file
46
src/js/components/i18n/I18nProvider.js
Normal file
@ -0,0 +1,46 @@
|
||||
import { addLocaleData, IntlProvider } from 'react-intl';
|
||||
import React from 'react';
|
||||
import ja from 'react-intl/locale-data/ja';
|
||||
|
||||
import jaMessages from '../../../../i18n/locales/ja.json';
|
||||
|
||||
|
||||
const MESSAGES = {
|
||||
ja: jaMessages.messages
|
||||
};
|
||||
|
||||
|
||||
class I18nProvider extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
addLocaleData([...ja]);
|
||||
this.state = {
|
||||
locale: 'en'
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
const locale = localStorage.getItem('language') ||
|
||||
(navigator.languages && navigator.languages[0]) ||
|
||||
navigator.language || navigator.userLanguage;
|
||||
// We only use the country part of the locale:
|
||||
const language = locale.substr(0, 2);
|
||||
if(MESSAGES[language]) {
|
||||
this.setState({ locale: language });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<IntlProvider locale={this.state.locale} messages={MESSAGES[this.state.locale]}>
|
||||
{this.props.children}
|
||||
</IntlProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
I18nProvider.propTypes = {
|
||||
children: React.PropTypes.node
|
||||
};
|
||||
|
||||
export default I18nProvider;
|
@ -3,7 +3,6 @@ import 'babel-polyfill';
|
||||
import { Provider } from 'react-redux';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { Router, Route, Redirect } from 'react-router';
|
||||
import { browserHistory } from 'react-router';
|
||||
|
||||
@ -18,6 +17,7 @@ import EditPlan from './components/plan/EditPlan';
|
||||
import EnvironmentConfiguration from
|
||||
'./components/environment_configuration/EnvironmentConfiguration.js';
|
||||
import { getCurrentStackDeploymentInProgress } from './selectors/stacks';
|
||||
import I18nProvider from './components/i18n/I18nProvider';
|
||||
import initFormsy from './components/utils/Formsy';
|
||||
import ListPlans from './components/plan/ListPlans';
|
||||
import Login from './components/Login';
|
||||
@ -41,6 +41,7 @@ import store from './store';
|
||||
|
||||
import '../less/base.less';
|
||||
|
||||
|
||||
TempStorage.initialized.then(() => {
|
||||
/**
|
||||
* @function checkAuth
|
||||
@ -134,9 +135,9 @@ TempStorage.initialized.then(() => {
|
||||
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<IntlProvider locale="en">
|
||||
<I18nProvider>
|
||||
<Router history={browserHistory}>{routes}</Router>
|
||||
</IntlProvider>
|
||||
</I18nProvider>
|
||||
</Provider>,
|
||||
document.getElementById('react-app-index')
|
||||
);
|
||||
|
@ -85,6 +85,12 @@ module.exports = {
|
||||
{
|
||||
loader: __dirname + '/src/js/loaders/version.js',
|
||||
test: /src\/js\/index.js$/
|
||||
},
|
||||
|
||||
// JSON
|
||||
{
|
||||
test: /\.json$/,
|
||||
loader: 'json'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user