From 57c98c657d63b882345769accfc7bd523b7a489f Mon Sep 17 00:00:00 2001
From: Florian Fuchs
Date: Fri, 18 Nov 2016 15:44:26 +0100
Subject: [PATCH] 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
---
.babelrc | 8 ++-
.gitignore | 2 +
README.md | 44 ++++++++++++++++
i18n/locales/ja.json | 1 +
package.json | 4 +-
.../deployment_plan/ConfigurePlanStep.js | 16 ++++--
.../deployment_plan/DeleteStackButton.js | 23 ++++++--
.../components/deployment_plan/DeployStep.js | 25 ++++++---
.../DeploymentConfiguration.js | 30 +++++++++--
.../DeploymentConfigurationSummary.js | 15 +++++-
.../deployment_plan/DeploymentFailure.js | 23 ++++++--
.../deployment_plan/DeploymentPlan.js | 52 +++++++++++++------
.../deployment_plan/DeploymentProgress.js | 38 +++++++++++---
.../deployment_plan/DeploymentSuccess.js | 25 +++++++--
.../deployment_plan/HardwareStep.js | 16 ++++--
src/js/components/deployment_plan/NoPlans.js | 22 ++++++--
.../deployment_plan/NodesAssignment.js | 16 +++++-
.../deployment_plan/PlansDropdown.js | 26 ++++++++--
src/js/components/deployment_plan/RoleCard.js | 20 ++++++-
src/js/components/deployment_plan/Roles.js | 17 ++++--
.../components/deployment_plan/RolesStep.js | 18 +++++--
src/js/components/i18n/I18nProvider.js | 46 ++++++++++++++++
src/js/index.js | 7 +--
webpack.config.js | 6 +++
24 files changed, 428 insertions(+), 72 deletions(-)
create mode 100644 i18n/locales/ja.json
create mode 100644 src/js/components/i18n/I18nProvider.js
diff --git a/.babelrc b/.babelrc
index 9b7d435a..33677322 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,3 +1,9 @@
{
- "presets": ["es2015", "stage-0", "react"]
+ "presets": ["es2015", "stage-0", "react"],
+ "plugins": [
+ ["react-intl", {
+ "messagesDir": "./i18n/extracted-messages/",
+ "enforceDescriptions": false
+ }]
+ ]
}
diff --git a/.gitignore b/.gitignore
index 729bec59..3224c928 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,5 @@ tripleo-ui-*.tar.gz
.DS_Store
.idea
app.conf
+messages.pot
+i18n/extracted-messages*
diff --git a/README.md b/README.md
index ec80da05..71d22638 100644
--- a/README.md
+++ b/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
+};
+```
diff --git a/i18n/locales/ja.json b/i18n/locales/ja.json
new file mode 100644
index 00000000..21cd9ea6
--- /dev/null
+++ b/i18n/locales/ja.json
@@ -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":""}}
diff --git a/package.json b/package.json
index 182e9d2e..03fa1886 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/js/components/deployment_plan/ConfigurePlanStep.js b/src/js/components/deployment_plan/ConfigurePlanStep.js
index 263e6512..09ccf91a 100644
--- a/src/js/components/deployment_plan/ConfigurePlanStep.js
+++ b/src/js/components/deployment_plan/ConfigurePlanStep.js
@@ -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 (
- Edit Configuration
+
);
@@ -21,3 +29,5 @@ ConfigurePlanStep.propTypes = {
planName: React.PropTypes.string.isRequired,
summary: React.PropTypes.string.isRequired
};
+
+export default ConfigurePlanStep;
diff --git a/src/js/components/deployment_plan/DeleteStackButton.js b/src/js/components/deployment_plan/DeleteStackButton.js
index 3344eac7..79f0501f 100644
--- a/src/js/components/deployment_plan/DeleteStackButton.js
+++ b/src/js/components/deployment_plan/DeleteStackButton.js
@@ -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 (
{
+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 (
@@ -23,7 +31,8 @@ export const DeployStep = ({ currentPlan, currentStack, currentStackResources,
content="Requesting a deploy..."
component="span"
inline>
- Validate and Deploy
+
@@ -69,3 +78,5 @@ DeployStep.propTypes = {
runPostDeploymentValidations: React.PropTypes.func.isRequired,
stacksLoaded: React.PropTypes.bool.isRequired
};
+
+export default DeployStep;
diff --git a/src/js/components/deployment_plan/DeploymentConfiguration.js b/src/js/components/deployment_plan/DeploymentConfiguration.js
index 97b94d62..10810c40 100644
--- a/src/js/components/deployment_plan/DeploymentConfiguration.js
+++ b/src/js/components/deployment_plan/DeploymentConfiguration.js
@@ -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">
- Deployment Configuration
+
+
+
- Overall Settings
- Parameters
+
+
+
+
+
+
{React.cloneElement(this.props.children,
diff --git a/src/js/components/deployment_plan/DeploymentConfigurationSummary.js b/src/js/components/deployment_plan/DeploymentConfigurationSummary.js
index d8876d2b..e20bbe3b 100644
--- a/src/js/components/deployment_plan/DeploymentConfigurationSummary.js
+++ b/src/js/components/deployment_plan/DeploymentConfigurationSummary.js
@@ -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 (
{this.props.summary}
@@ -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);
diff --git a/src/js/components/deployment_plan/DeploymentFailure.js b/src/js/components/deployment_plan/DeploymentFailure.js
index b0ff5eed..fb7d60ff 100644
--- a/src/js/components/deployment_plan/DeploymentFailure.js
+++ b/src/js/components/deployment_plan/DeploymentFailure.js
@@ -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 (
-
);
@@ -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);
diff --git a/src/js/components/deployment_plan/DeploymentPlan.js b/src/js/components/deployment_plan/DeploymentPlan.js
index 7b2e6126..fb02d59b 100644
--- a/src/js/components/deployment_plan/DeploymentPlan.js
+++ b/src/js/components/deployment_plan/DeploymentPlan.js
@@ -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,21 +96,21 @@ class DeploymentPlan extends React.Component {
-
-
-
-
+
+
+
+
-
-
+
+
-
+
{statusMessages[this.props.stack.stack_status]}
);
const deleteButton = this.props.stack.stack_status !== stackStates.DELETE_IN_PROGRESS
- ? () : null;
return (
- Deployment is currently in progress.
- View detailed information
+
+
+
@@ -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);
diff --git a/src/js/components/deployment_plan/DeploymentSuccess.js b/src/js/components/deployment_plan/DeploymentSuccess.js
index f91334ec..8e2e5e98 100644
--- a/src/js/components/deployment_plan/DeploymentSuccess.js
+++ b/src/js/components/deployment_plan/DeploymentSuccess.js
@@ -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 (
-
);
@@ -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);
diff --git a/src/js/components/deployment_plan/HardwareStep.js b/src/js/components/deployment_plan/HardwareStep.js
index 14de2fe0..366efc04 100644
--- a/src/js/components/deployment_plan/HardwareStep.js
+++ b/src/js/components/deployment_plan/HardwareStep.js
@@ -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 (
-
Register Nodes
+
);
};
+
+export default HardwareStep;
diff --git a/src/js/components/deployment_plan/NoPlans.js b/src/js/components/deployment_plan/NoPlans.js
index 60497a94..29de6f77 100644
--- a/src/js/components/deployment_plan/NoPlans.js
+++ b/src/js/components/deployment_plan/NoPlans.js
@@ -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 {
-
No Deployment Plans Available
-
There are no Deployment Plans available. Please create one first.
+
+
- Create New Plan
+
diff --git a/src/js/components/deployment_plan/NodesAssignment.js b/src/js/components/deployment_plan/NodesAssignment.js
index e981c355..b16c78f6 100644
--- a/src/js/components/deployment_plan/NodesAssignment.js
+++ b/src/js/components/deployment_plan/NodesAssignment.js
@@ -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 {
);
@@ -98,7 +110,7 @@ class NodesAssignment extends React.Component {
- Done
+
diff --git a/src/js/components/deployment_plan/PlansDropdown.js b/src/js/components/deployment_plan/PlansDropdown.js
index c32e9daa..7110585b 100644
--- a/src/js/components/deployment_plan/PlansDropdown.js
+++ b/src/js/components/deployment_plan/PlansDropdown.js
@@ -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 (
- Manage Deployments
+
+
+
);
} else {
return (
- Select Deployment
+
+
+
{this.renderRecentPlans()}
- Manage Deployments
+
+
+
);
}
diff --git a/src/js/components/deployment_plan/RoleCard.js b/src/js/components/deployment_plan/RoleCard.js
index b80a1ac8..203d994f 100644
--- a/src/js/components/deployment_plan/RoleCard.js
+++ b/src/js/components/deployment_plan/RoleCard.js
@@ -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 {
- Nodes assigned
+
+
+
@@ -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">
- Assign Nodes
+
+
diff --git a/src/js/components/deployment_plan/Roles.js b/src/js/components/deployment_plan/Roles.js
index b7135a64..5e2f9ea2 100644
--- a/src/js/components/deployment_plan/Roles.js
+++ b/src/js/components/deployment_plan/Roles.js
@@ -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 {
{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);
diff --git a/src/js/components/deployment_plan/RolesStep.js b/src/js/components/deployment_plan/RolesStep.js
index 1955a7b6..b8d51f55 100644
--- a/src/js/components/deployment_plan/RolesStep.js
+++ b/src/js/components/deployment_plan/RolesStep.js
@@ -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 (
{unassignedAvailableNodes.size} 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);
diff --git a/src/js/components/i18n/I18nProvider.js b/src/js/components/i18n/I18nProvider.js
new file mode 100644
index 00000000..7d33415f
--- /dev/null
+++ b/src/js/components/i18n/I18nProvider.js
@@ -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 (
+
+ {this.props.children}
+
+ );
+ }
+}
+
+I18nProvider.propTypes = {
+ children: React.PropTypes.node
+};
+
+export default I18nProvider;
diff --git a/src/js/index.js b/src/js/index.js
index 7ce15b53..365d6798 100644
--- a/src/js/index.js
+++ b/src/js/index.js
@@ -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(
-
+
{routes}
-
+
,
document.getElementById('react-app-index')
);
diff --git a/webpack.config.js b/webpack.config.js
index 3a39aeb0..4ccf8abf 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -85,6 +85,12 @@ module.exports = {
{
loader: __dirname + '/src/js/loaders/version.js',
test: /src\/js\/index.js$/
+ },
+
+ // JSON
+ {
+ test: /\.json$/,
+ loader: 'json'
}
]
},