Add i18n strings to plan components

Change-Id: I8a72b7354fba3fa69014aaab0fe0f8a8e6b9d9c2
Closes-Bug: #1650564
Implements: blueprint tripleo-ui-i18n-support-for-js
This commit is contained in:
Florian Fuchs 2017-01-03 15:32:34 +01:00
parent 1a737d0794
commit dd06ea1ea8
9 changed files with 257 additions and 68 deletions

View File

@ -1,10 +1,30 @@
import { connect } from 'react-redux';
import { defineMessages, FormattedMessage } from 'react-intl';
import React from 'react';
import { Link } from 'react-router';
import PlansActions from '../../actions/PlansActions';
import Modal from '../ui/Modal';
const messages = defineMessages({
deletePlan: {
id: 'DeletePlan.deletePlan',
defaultMessage: 'Delete Plan'
},
deletePlanName: {
id: 'DeletePlan.deletePlanName',
defaultMessage: 'Delete {planName}'
},
deletePlanConfirmation: {
id: 'DeletePlan.deletePlanConfirmation',
defaultMessage: 'Are you sure you want to delete plan {planName}?'
},
cancel: {
id: 'DeletePlan.cancel',
defaultMessage: 'Cancel'
}
});
class DeletePlan extends React.Component {
getNameFromUrl() {
let planName = this.props.params.planName || '';
@ -28,21 +48,27 @@ class DeletePlan extends React.Component {
<span aria-hidden="true" className="pficon pficon-close"/>
</Link>
<h4 className="modal-title">
<span className="pficon pficon-delete"></span> Delete {this.getNameFromUrl()}
<span className="pficon pficon-delete"></span> <FormattedMessage
{...messages.deletePlanName}
values={{planName: this.getNameFromUrl()}}
/>
</h4>
</div>
<div className="modal-body">
<p>
Are you sure you want to delete plan <strong>{this.getNameFromUrl()}</strong>?
<FormattedMessage {...messages.deletePlanConfirmation}
values={{ planName: <strong>{this.getNameFromUrl()}</strong>}}/>
</p>
</div>
<div className="modal-footer">
<button className="btn btn-danger"
onClick={this.onDeleteClick.bind(this)}
type="submit">
Delete Plan
<FormattedMessage {...messages.deletePlan}/>
</button>
<Link to="/plans/list" type="button" className="btn btn-default" >Cancel</Link>
<Link to="/plans/list" type="button" className="btn btn-default" >
<FormattedMessage {...messages.cancel}/>
</Link>
</div>
</Modal>
);

View File

@ -1,4 +1,5 @@
import { connect } from 'react-redux';
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
import Formsy from 'formsy-react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { Link } from 'react-router';
@ -10,6 +11,25 @@ import PlansActions from '../../actions/PlansActions';
import Modal from '../ui/Modal';
import Loader from '../ui/Loader';
const messages = defineMessages({
cancel: {
id: 'EditPlan.cancel',
defaultMessage: 'Cancel'
},
updatePlanNameFiles: {
id: 'EditPlan.updatePlanNameFiles',
defaultMessage: 'Update {planName} Files'
},
updatingPlanLoader: {
id: 'EditPlan.updatingPlanLoader',
defaultMessage: 'Updating plan...'
},
uploadAndUpdate: {
id: 'EditPlan.uploadAndUpdate',
defaultMessage: 'Upload Files and Update Plan'
}
});
class EditPlan extends React.Component {
constructor() {
@ -82,12 +102,15 @@ class EditPlan extends React.Component {
className="close">
<span aria-hidden="true" className="pficon pficon-close"/>
</Link>
<h4>Update {this.getNameFromUrl()} Files</h4>
<h4>
<FormattedMessage {...messages.updatePlanNameFiles}
values={{ planName: this.getNameFromUrl() }}/>
</h4>
</div>
<Loader loaded={!this.props.isTransitioningPlan}
size="lg"
height={60}
content="Updating plan...">
content={this.props.intl.formatMessage(messages.updatingPlanLoader)}>
<ModalFormErrorList errors={this.props.planFormErrors.toJS()}/>
<div className="modal-body">
<PlanEditFormTabs currentTab={this.props.location.query.tab || 'editPlan'}
@ -102,11 +125,13 @@ class EditPlan extends React.Component {
<button disabled={!this.state.canSubmit}
className="btn btn-primary"
type="submit">
Upload Files and Update Plan
<FormattedMessage {...messages.uploadAndUpdate}/>
</button>
<Link to="/plans/list"
type="button"
className="btn btn-default">Cancel</Link>
className="btn btn-default">
<FormattedMessage {...messages.cancel}/>
</Link>
</div>
</Formsy.Form>
</Modal>
@ -117,6 +142,7 @@ class EditPlan extends React.Component {
EditPlan.propTypes = {
fetchPlan: React.PropTypes.func,
history: React.PropTypes.object,
intl: React.PropTypes.object,
isTransitioningPlan: React.PropTypes.bool,
location: React.PropTypes.object,
params: React.PropTypes.object,
@ -148,4 +174,4 @@ function mapDispatchToProps(dispatch) {
};
}
export default connect(mapStateToProps, mapDispatchToProps)(EditPlan);
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(EditPlan));

View File

@ -1,9 +1,17 @@
import ClassNames from 'classnames';
import { defineMessages, FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { Map } from 'immutable';
import { PlanFile } from '../../immutableRecords/plans';
import React from 'react';
const messages = defineMessages({
planFiles: {
id: 'FileList.planFiles',
defaultMessage: 'Plan Files'
}
});
export default class FileList extends React.Component {
getMergedFiles(planFiles, selectedFiles) {
@ -55,7 +63,7 @@ export default class FileList extends React.Component {
return (
<div className="panel panel-default">
<div className="panel-heading" role="tab" id="plan-files-list-panel">
<h4 className="panel-title">Plan Files</h4>
<h4 className="panel-title"><FormattedMessage {...messages.planFiles}/></h4>
</div>
<table className="table upload-files">
<tbody>

View File

@ -1,4 +1,5 @@
import { connect } from 'react-redux';
import { defineMessages, FormattedMessage } from 'react-intl';
import { Link } from 'react-router';
import ImmutablePropTypes from 'react-immutable-proptypes';
import React from 'react';
@ -10,6 +11,41 @@ import DataTableColumn from '../ui/tables/DataTableColumn';
import { PageHeader } from '../ui/PageHeader';
import PlansActions from '../../actions/PlansActions';
const messages = defineMessages({
actions: {
id: 'ListPlans.actions',
defaultMessage: 'Actions'
},
edit: {
id: 'ListPlans.edit',
defaultMessage: 'Edit'
},
delete: {
id: 'ListPlans.delete',
defaultMessage: 'Delete'
},
deletingPlanName: {
id: 'ListPlans.deletingPlanName',
defaultMessage: 'Deleting {planName}...'
},
plans: {
id: 'ListPlans.plans',
defaultMessage: 'Plans'
},
name: {
id: 'ListPlans.name',
defaultMessage: 'Name'
},
noPlans: {
id: 'ListPlans.noPlans',
defaultMessage: 'There are currently no plans'
},
createNewPlan: {
id: 'ListPlans.createNewPlan',
defaultMessage: 'Create New Plan'
}
});
class ListPlans extends React.Component {
constructor() {
super();
@ -25,12 +61,14 @@ class ListPlans extends React.Component {
<td colSpan="2">
<p></p>
<p className="text-center">
There are currently no Plans
<FormattedMessage {...messages.noPlans}/>
</p>
<p className="text-center">
<Link to="/plans/new"
query={{tab: 'newPlan'}}
className="btn btn-success">Create New Plan</Link>
className="btn btn-success">
<FormattedMessage {...messages.createNewPlan}/>
</Link>
</p>
</td>
</tr>
@ -42,7 +80,7 @@ class ListPlans extends React.Component {
<Link to="/plans/new"
query={{tab: 'newPlan'}}
className="btn btn-primary">
<span className="fa fa-plus"/> Create New Plan
<span className="fa fa-plus"/> <FormattedMessage {...messages.createNewPlan}/>
</Link>
);
}
@ -51,17 +89,23 @@ class ListPlans extends React.Component {
let plans = this.props.plans.sortBy(plan => plan.name).toArray();
return (
<div>
<PageHeader>Plans</PageHeader>
<PageHeader>
<FormattedMessage {...messages.plans}/>
</PageHeader>
<DataTable data={plans}
rowsCount={plans.length}
noRowsRenderer={this.renderNoPlans.bind(this)}
tableActions={this.renderTableActions}>
<DataTableColumn header={<DataTableHeaderCell key="name">Name</DataTableHeaderCell>}
<DataTableColumn header={<DataTableHeaderCell key="name">
<FormattedMessage {...messages.name}/>
</DataTableHeaderCell>}
cell={<PlanNameCell
data={plans}
currentPlanName={this.props.currentPlanName}
choosePlan={this.props.choosePlan}/>}/>
<DataTableColumn header={<DataTableHeaderCell key="actions">Actions</DataTableHeaderCell>}
<DataTableColumn header={<DataTableHeaderCell key="actions">
<FormattedMessage {...messages.actions}/>
</DataTableHeaderCell>}
cell={<RowActionsCell className="actions text-right"
data={plans}/>}/>
</DataTable>
@ -114,11 +158,15 @@ class RowActionsCell extends React.Component {
<Link key="edit"
to={`/plans/${plan.name}/edit`}
query={{tab: 'editPlan'}}
className="btn btn-xs btn-default">Edit</Link>
className="btn btn-xs btn-default">
<FormattedMessage {...messages.edit}/>
</Link>
&nbsp;
<Link key="delete"
to={`/plans/${plan.name}/delete`}
className="btn btn-xs btn-danger">Delete</Link>
className="btn btn-xs btn-danger">
<FormattedMessage {...messages.delete}/>
</Link>
</DataTableCell>
);
}
@ -150,7 +198,10 @@ export class PlanNameCell extends React.Component {
if(plan.transition === 'deleting') {
return (
<DataTableCell {...this.props} colSpan="2" className={plan.transition}>
<em>Deleting <strong>{plan.name}</strong>&hellip;</em>
<em>
<FormattedMessage {...messages.deletingPlanName}
values={{ planName: <strong>{plan.name}</strong>}}/>
</em>
</DataTableCell>
);
} else {

View File

@ -1,4 +1,5 @@
import { connect } from 'react-redux';
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
import Formsy from 'formsy-react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { Link } from 'react-router';
@ -10,6 +11,25 @@ import PlanFormTabs from './PlanFormTabs';
import Modal from '../ui/Modal';
import Loader from '../ui/Loader';
const messages = defineMessages({
cancel: {
id: 'NewPlan.cancel',
defaultMessage: 'Cancel'
},
createNewPlan: {
id: 'NewPlan.createNewPlan',
defaultMessage: 'Create New Plan'
},
creatingPlanLoader: {
id: 'NewPlan.creatingPlanLoader',
defaultMessage: 'Creating plan...'
},
uploadAndCreate: {
id: 'NewPlan.uploadAndCreate',
defaultMessage: 'Upload Files and Create Plan'
}
});
class NewPlan extends React.Component {
constructor() {
@ -73,11 +93,13 @@ class NewPlan extends React.Component {
className="close">
<span aria-hidden="true" className="pficon pficon-close"/>
</Link>
<h4 className="modal-title">Create New Plan</h4>
<h4 className="modal-title">
<FormattedMessage {...messages.createNewPlan}/>
</h4>
</div>
<Loader loaded={!this.props.isTransitioningPlan}
size="lg"
content="Creating plan...">
content={this.props.intl.formatMessage(messages.creatingPlanLoader)}>
<ModalFormErrorList errors={this.props.planFormErrors.toJS()}/>
<div className="modal-body">
<PlanFormTabs currentTab={this.props.location.query.tab || 'newPlan'}
@ -87,17 +109,18 @@ class NewPlan extends React.Component {
</div>
</Loader>
<div className="modal-footer">
<button disabled={!this.state.canSubmit}
className="btn btn-primary"
type="submit">
Upload Files and Create Plan
<FormattedMessage {...messages.uploadAndCreate}/>
</button>
<Link to="/plans/list"
type="button"
onClick={() => this.props.cancelCreatePlan()}
className="btn btn-default">Cancel</Link>
className="btn btn-default">
<FormattedMessage {...messages.cancel}/>
</Link>
</div>
</Formsy.Form>
</Modal>
@ -108,6 +131,7 @@ NewPlan.propTypes = {
cancelCreatePlan: React.PropTypes.func,
createPlan: React.PropTypes.func,
createPlanFromTarball: React.PropTypes.func,
intl: React.PropTypes.object,
isTransitioningPlan: React.PropTypes.bool,
location: React.PropTypes.object,
planFormErrors: ImmutablePropTypes.list
@ -134,4 +158,4 @@ function mapDispatchToProps(dispatch) {
};
}
export default connect(mapStateToProps, mapDispatchToProps)(NewPlan);
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(NewPlan));

View File

@ -1,23 +0,0 @@
import { Link } from 'react-router';
import React from 'react';
export default class NoPlans extends React.Component {
render() {
return (
<div className="blank-slate-pf">
<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>
<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
</Link>
</div>
</div>
);
}
}

View File

@ -1,3 +1,4 @@
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { List } from 'immutable';
import React from 'react';
@ -8,6 +9,29 @@ import PlanFileInput from './PlanFileInput';
import PlanFilesTab from './PlanFilesTab';
import PlanUploadTypeRadios from './PlanUploadTypeRadios';
const messages = defineMessages({
files: {
id: 'PlanEditFormTabs.files',
defaultMessage: 'Files'
},
planName: {
id: 'PlanEditFormTabs.planName',
defaultMessage: 'Plan Name'
},
updatePlan: {
id: 'PlanEditFormTabs.updatePlan',
defaultMessage: 'Update Plan'
},
uploadFiles: {
id: 'PlanEditFormTabs.uploadFiles',
defaultMessage: 'Upload Files'
},
uploadType: {
id: 'PlanEditFormTabs.uploadType',
defaultMessage: 'Upload Type'
}
});
export default class PlanEditFormTabs extends React.Component {
setActiveTab(tabName) {
return this.props.currentTab === tabName ? 'active' : '';
@ -26,10 +50,11 @@ export default class PlanEditFormTabs extends React.Component {
<div>
<ul className="nav nav-tabs">
<NavTab to={`/plans/${this.props.planName}/edit`}
query={{tab: 'editPlan'}}>Update Plan</NavTab>
query={{tab: 'editPlan'}}><FormattedMessage {...messages.updatePlan}/></NavTab>
<NavTab to={`/plans/${this.props.planName}/edit`}
query={{tab: 'planFiles'}}>
Files <span className="badge">{this.getFileCount.bind(this)()}</span>
<FormattedMessage {...messages.files}/> <span className="badge">
{this.getFileCount.bind(this)()}</span>
</NavTab>
</ul>
<div className="tab-content">
@ -57,22 +82,23 @@ PlanEditFormTabs.defaultProps = {
currentTtab: 'editPlan'
};
class PlanFormTab extends React.Component {
class _PlanFormTab extends React.Component {
render() {
const { formatMessage } = this.props.intl;
return (
<div className={`tab-pane ${this.props.active}`}>
<HorizontalStaticText title="Plan Name"
<HorizontalStaticText title={formatMessage(messages.planName)}
text={this.props.planName}
valueColumnClasses="col-sm-7"
labelColumnClasses="col-sm-3"/>
<PlanUploadTypeRadios title="Upload Type"
<PlanUploadTypeRadios title={formatMessage(messages.uploadType)}
inputColumnClasses="col-sm-7"
labelColumnClasses="col-sm-3"
setUploadType={this.props.setUploadType}
uploadType={this.props.uploadType}/>
<PlanFileInput name="planFiles"
title="Upload Files"
title={this.props.intl.formatMessage(messages.uploadFiles)}
inputColumnClasses="col-sm-7"
labelColumnClasses="col-sm-3"
uploadType={this.props.uploadType}
@ -82,9 +108,11 @@ class PlanFormTab extends React.Component {
);
}
}
PlanFormTab.propTypes = {
_PlanFormTab.propTypes = {
active: React.PropTypes.string,
intl: React.PropTypes.object,
planName: React.PropTypes.string,
setUploadType: React.PropTypes.func.isRequired,
uploadType: React.PropTypes.string.isRequired
};
const PlanFormTab = injectIntl(_PlanFormTab);

View File

@ -1,3 +1,4 @@
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
import React from 'react';
import HorizontalInput from '../ui/forms/HorizontalInput';
@ -6,6 +7,37 @@ import PlanFileInput from './PlanFileInput';
import PlanFilesTab from './PlanFilesTab';
import PlanUploadTypeRadios from './PlanUploadTypeRadios';
const messages = defineMessages({
addPlanName: {
id: 'PlanFormTabs.addPlanName',
defaultMessage: 'Add a Plan Name'
},
newPlan: {
id: 'PlanFormTabs.newPlan',
defaultMessage: 'New Plan'
},
files: {
id: 'PlanFormTabs.files',
defaultMessage: 'Files'
},
planFiles: {
id: 'PlanFormTabs.planFiles',
defaultMessage: 'Plan Files'
},
planName: {
id: 'PlanFormTabs.planName',
defaultMessage: 'Plan Name'
},
planNameValidationError: {
id: 'PlanFormTabs.planNameValidationError',
defaultMessage: 'Please use only alphanumeric characters and -'
},
uploadType: {
id: 'PlanFormTabs.uploadType',
defaultMessage: 'Upload Type'
}
});
export default class PlanFormTabs extends React.Component {
setActiveTab(tabName) {
return this.props.currentTab === tabName ? 'active' : '';
@ -15,9 +47,12 @@ export default class PlanFormTabs extends React.Component {
return (
<div>
<ul className="nav nav-tabs">
<NavTab to="/plans/new" query={{tab: 'newPlan'}}>New Plan</NavTab>
<NavTab to="/plans/new" query={{tab: 'newPlan'}}>
<FormattedMessage {...messages.newPlan}/>
</NavTab>
<NavTab to="/plans/new" query={{tab: 'planFiles'}}>
Files <span className="badge">{this.props.selectedFiles.length}</span>
<FormattedMessage {...messages.files}/> <span className="badge">
{this.props.selectedFiles.length}</span>
</NavTab>
</ul>
<div className="tab-content">
@ -42,27 +77,26 @@ PlanFormTabs.defaultProps = {
selectedFiles: []
};
class PlanFormTab extends React.Component {
class _PlanFormTab extends React.Component {
render() {
const { formatMessage } = this.props.intl;
return (
<div className={`tab-pane ${this.props.active}`}>
<HorizontalInput name="planName"
title="Plan Name"
title={formatMessage(messages.planName)}
inputColumnClasses="col-sm-7"
labelColumnClasses="col-sm-3"
placeholder="Add a Plan Name"
placeholder={formatMessage(messages.addPlanName)}
validations={{matchRegexp: /^[A-Za-z0-9-]+$/}}
validationError="Please use only alphanumeric characters and
-"
validationError={formatMessage(messages.planNameValidationError)}
required />
<PlanUploadTypeRadios title="Upload Type"
<PlanUploadTypeRadios title={formatMessage(messages.uploadType)}
inputColumnClasses="col-sm-7"
labelColumnClasses="col-sm-3"
setUploadType={this.props.setUploadType}
uploadType={this.props.uploadType}/>
<PlanFileInput name="planFiles"
title="Plan Files"
title={formatMessage(messages.planFiles)}
inputColumnClasses="col-sm-7"
labelColumnClasses="col-sm-3"
uploadType={this.props.uploadType}
@ -72,8 +106,11 @@ class PlanFormTab extends React.Component {
);
}
}
PlanFormTab.propTypes = {
_PlanFormTab.propTypes = {
active: React.PropTypes.string,
intl: React.PropTypes.object,
setUploadType: React.PropTypes.func.isRequired,
uploadType: React.PropTypes.string.isRequired
};
const PlanFormTab = injectIntl(_PlanFormTab);

View File

@ -1,5 +1,17 @@
import { defineMessages, FormattedMessage } from 'react-intl';
import React from 'react';
const messages = defineMessages({
localFolder: {
id: 'PlanUploadTypeRadios.localFolder',
defaultMessage: 'Local Folder'
},
tarArchive: {
id: 'PlanUploadTypeRadios.tarArchive',
defaultMessage: 'Tar Archive (tar.gz)'
}
});
export default class PlanUploadTypeRadios extends React.Component {
render() {
@ -16,7 +28,7 @@ export default class PlanUploadTypeRadios extends React.Component {
name="uploadType"
value="tarball"
onChange={this.props.setUploadType}
defaultChecked/> Tar Archive (tar.gz)
defaultChecked/> <FormattedMessage {...messages.tarArchive}/>
</label>
<label className="radio-inline" htmlFor="checkbox-folder">
<input ref="checkbox-folder"
@ -24,7 +36,7 @@ export default class PlanUploadTypeRadios extends React.Component {
id="checkbox-folder"
name="uploadType"
onChange={this.props.setUploadType}
value="folder"/> Local Folder
value="folder"/> <FormattedMessage {...messages.localFolder}/>
</label>
</div>
</div>