diff --git a/package-lock.json b/package-lock.json
index 6b8915d..5991dd7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7042,6 +7042,14 @@
}
}
},
+ "html-parse-stringify2": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/html-parse-stringify2/-/html-parse-stringify2-2.0.1.tgz",
+ "integrity": "sha1-3FZwtyksoVi3vJFsmmc1rIhyg0o=",
+ "requires": {
+ "void-elements": "^2.0.1"
+ }
+ },
"html-webpack-plugin": {
"version": "4.0.0-alpha.2",
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.0.0-alpha.2.tgz",
@@ -7409,6 +7417,14 @@
"resolved": "http://registry.npm.taobao.org/https-browserify/download/https-browserify-1.0.0.tgz",
"integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM="
},
+ "i18next": {
+ "version": "15.0.5",
+ "resolved": "https://registry.npmjs.org/i18next/-/i18next-15.0.5.tgz",
+ "integrity": "sha512-JqcoIM20BuYq0iQm8VLlq2pqhCNoG60+QuOxLE9PCDGZZc+in9kWofw1qLUazo4B53jJDca1ARd2YLmhXcx2uw==",
+ "requires": {
+ "@babel/runtime": "^7.3.1"
+ }
+ },
"iconv-lite": {
"version": "0.4.24",
"resolved": "http://registry.npm.taobao.org/iconv-lite/download/iconv-lite-0.4.24.tgz",
@@ -13455,6 +13471,15 @@
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-5.1.3.tgz",
"integrity": "sha512-GoqeM3Xadie7XUApXOjkY3Qhs8RkwB/Za4WMedBGrOKH1eTuKGyoAECff7jiVonJchOx6KZ9i8ILO5XIoHB+Tg=="
},
+ "react-i18next": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-10.2.0.tgz",
+ "integrity": "sha512-b+AryBB2QiLCEmk8PaNGMkowEZVSY032crKuQToa+W1lBgrNiWVKstmi0z4bZqqcK9GL4cJr8Mevw4XpPFvpeQ==",
+ "requires": {
+ "@babel/runtime": "^7.3.1",
+ "html-parse-stringify2": "2.0.1"
+ }
+ },
"react-is": {
"version": "16.6.3",
"resolved": "http://registry.npm.taobao.org/react-is/download/react-is-16.6.3.tgz",
@@ -16595,6 +16620,11 @@
"indexof": "0.0.1"
}
},
+ "void-elements": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz",
+ "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w="
+ },
"w3c-hr-time": {
"version": "1.0.1",
"resolved": "http://registry.npm.taobao.org/w3c-hr-time/download/w3c-hr-time-1.0.1.tgz",
diff --git a/package.json b/package.json
index 1595036..769d291 100644
--- a/package.json
+++ b/package.json
@@ -10,9 +10,11 @@
"antd": "^3.13.6",
"axios": "^0.18.0",
"classnames": "^2.2.6",
+ "i18next": "^15.0.5",
"node-sass": "^4.11.0",
"react": "^16.8.3",
"react-dom": "^16.8.3",
+ "react-i18next": "^10.2.0",
"react-loading-screen": "0.0.17",
"react-scripts": "2.1.5",
"typed.js": "^2.0.10"
diff --git a/src/App.js b/src/App.js
index 100e646..6cd421c 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,4 +1,6 @@
import React, { Component } from "react";
+import { withTranslation } from 'react-i18next';
+
import "./App.scss";
import Typed from "typed.js";
import { getOSList, removeContainerById } from "./util/api";
@@ -8,10 +10,13 @@ import { getItem, rmItem } from "./util/util";
import { Button, Tooltip, Divider, Card, Modal } from "antd";
import SelectSystemConfig from "./components/SelectSystemConfig";
import SystemConfiguration from "./components/SystemConfiguration";
+import LanguageSwitcher from "./components/LanguageSwitcher";
class App extends Component {
constructor(props) {
super(props);
+ this.t = props.t;
+
const { isExistContainer, container } = this.isExistContainer();
this.state = {
@@ -22,8 +27,8 @@ class App extends Component {
timeout: 24,
cpu: 1,
memory: 512,
- port: 80, // 内部端口(填写表单填写的端口号)
- externalPort: 0, // 外部端口(后端返回的端口号)
+ port: 80, // Internal port (entered by user)
+ externalPort: 0, // External port (assigned by api)
container,
isExistContainer,
screenLoading: false,
@@ -38,7 +43,7 @@ class App extends Component {
if (document.getElementsByClassName('app__desc-content').length > 0) {
this.typed = new Typed(".app__desc-content", {
strings: [
- `Want to experiment with something on a Linux distribution? Let's start!`
+ this.t('site.typed')
],
typeSpeed: 50
});
@@ -52,7 +57,7 @@ class App extends Component {
if (containerInfo) {
containerInfo = JSON.parse(containerInfo);
const curTime = Math.floor(new Date().getTime() / 1000);
- // 检查是否过期
+ // Check if it's still valid
if (curTime < containerInfo.timeout) {
return { isExistContainer: true, container: containerInfo };
} else {
@@ -79,7 +84,6 @@ class App extends Component {
this.setState({ osList });
};
-
handleOSSelect = selectedOS => {
const osList = [...this.state.osList];
@@ -103,7 +107,7 @@ class App extends Component {
};
handleSelectAgain = async () => {
- this.setState({ screenLoading: true, screenText: "删除中..." });
+ this.setState({ screenLoading: true, screenText: this.t('prompt.purging') });
const { container } = this.state;
const timestamp = Math.floor(new Date().getTime() / 1000);
this.p3 = removeContainerById(
@@ -152,13 +156,13 @@ class App extends Component {
- instantbox
+ {this.t('site.heading')}
- Ubuntu / CentOS / Arch Linux / Debian / Fedora / Alpine
+ {this.state.osList.map(os => os.label).join(' / ') || this.t('site.heading')}
$
@@ -166,9 +170,10 @@ class App extends Component {
+
- {isExistContainer ? "您已创建系统" : "选择系统配置"}
+ {isExistContainer ? this.t('prompt.created-os') : this.t('prompt.select-os')}
{isExistContainer && (
@@ -192,7 +197,7 @@ class App extends Component {
{isExistContainer ? (
-
+
) : (
@@ -225,21 +231,21 @@ class App extends Component {
{
window.open(this.state.container.shareUrl.replace('http://:', `http://${window.location.hostname}:`));
this.setState({ skipModalVisible: false });
}}
- okText="确定"
- cancelText="取消"
+ okText={this.t('keyword.ok')}
+ cancelText={this.t('keyword.cancel')}
onCancel={() => this.setState({ skipModalVisible: false })}
>
- 系统已创建,是否跳转到系统页面?
+ {this.t('sentence.open-webshell')}
);
}
}
-export default App;
+export default withTranslation()(App);
diff --git a/src/App.scss b/src/App.scss
index 77075b2..21b6d11 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -18,6 +18,11 @@
&__start {
margin: 80px 0;
}
+ &__lang-switcher {
+ position: absolute;
+ top: 10px;
+ right: 12px;
+ }
&__text-field {
margin-top: 8px !important;
input {
diff --git a/src/App.test.js b/src/App.test.js
index a754b20..5e3105b 100644
--- a/src/App.test.js
+++ b/src/App.test.js
@@ -1,9 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
+import i18n from './i18n';
it('renders without crashing', () => {
const div = document.createElement('div');
- ReactDOM.render(, div);
+ ReactDOM.render(, div);
ReactDOM.unmountComponentAtNode(div);
});
diff --git a/src/components/LanguageSwitcher/LanguageSwitcher.js b/src/components/LanguageSwitcher/LanguageSwitcher.js
new file mode 100644
index 0000000..3dd59d5
--- /dev/null
+++ b/src/components/LanguageSwitcher/LanguageSwitcher.js
@@ -0,0 +1,34 @@
+import React from "react";
+import { withTranslation } from 'react-i18next';
+
+/**
+ * Switch between languages
+ */
+class LanguageSwitcher extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.t = props.t;
+ }
+
+ changeLanguage(e) {
+ e.preventDefault();
+
+ this.props.i18n.changeLanguage(e.target.dataset.locale)
+ }
+
+ render() {
+ return
+
+ {this.t('language')} |
+ {
+ this.t('locale') === 'en'
+ ? 中文
+ : English
+ }
+
+
+ }
+}
+
+export default withTranslation()(LanguageSwitcher);
diff --git a/src/components/LanguageSwitcher/index.js b/src/components/LanguageSwitcher/index.js
new file mode 100644
index 0000000..e8e5b57
--- /dev/null
+++ b/src/components/LanguageSwitcher/index.js
@@ -0,0 +1,3 @@
+import LanguageSwitcher from './LanguageSwitcher';
+
+export default LanguageSwitcher;
diff --git a/src/components/SelectSystemConfig/SelectForm.js b/src/components/SelectSystemConfig/SelectForm.js
index 29b914b..f521218 100644
--- a/src/components/SelectSystemConfig/SelectForm.js
+++ b/src/components/SelectSystemConfig/SelectForm.js
@@ -1,4 +1,6 @@
import React from "react";
+import { withTranslation } from 'react-i18next';
+
import "./SelectSystemConfig.scss";
import {
Input,
@@ -7,83 +9,8 @@ import {
const FormItem = Form.Item;
-const rules = {
- port: [
- {
- required: true,
- message: "请输入端口号"
- },
- {
- validator: (rule, value, callback) => {
- if (
- (/^\d+$/g.test(value) && value >= 1 && value <= 65535) ||
- value === ""
- ) {
- return callback();
- }
- callback("端口号格式或范围有误");
- },
- message: "端口号范围: 1 ~ 65535"
- }
- ],
- cpu: [
- {
- required: true,
- message: "请输入 CPU 核数"
- },
- {
- validator: (rule, value, callback) => {
- if (
- (/^\d+$/g.test(value) && value >= 1 && value <= 4) ||
- value === ""
- ) {
- return callback();
- }
- callback("CPU 格式或范围有误");
- },
- message: "CPU 范围:1 ~ 4"
- }
- ],
- mem: [
- {
- required: true,
- message: "请输入空间大小"
- },
- {
- validator: (rule, value, callback) => {
- if (
- (/^\d+$/g.test(value) && value >= 1 && value <= 3584) ||
- value === ""
- ) {
- return callback();
- }
- callback("空间大小格式或范围有误");
- },
- message: "空间大小范围:1 ~ 3584"
- }
- ],
- timeout: [
- {
- required: true,
- message: "请输入使用时长"
- },
- {
- validator: (rule, value, callback) => {
- if (
- (/^\d+$/g.test(value) && value >= 1 && value <= 24) ||
- value === ""
- ) {
- return callback();
- }
- callback("使用时长格式或范围有误");
- },
- message: "使用时长范围:1 ~ 24"
- }
- ]
-};
-
/**
- * 填写系统性能参数的表单
+ * A form for instantbox requests
*/
export class SelectForm extends React.Component {
static propTypes = {};
@@ -91,13 +18,91 @@ export class SelectForm extends React.Component {
constructor(props) {
super(props);
-
+ this.t = props.t;
+
this.state = {};
}
componentDidMount = () => {
this.props.getForm && this.props.getForm(this.props.form);
- };
+ }
+
+ getRules = () => {
+ return {
+ port: [
+ {
+ required: true,
+ message: this.t('prompt.enter-port')
+ },
+ {
+ validator: (rule, value, callback) => {
+ if (
+ (/^\d+$/g.test(value) && value >= 1 && value <= 65535) ||
+ value === ""
+ ) {
+ return callback();
+ }
+ callback(this.t('sentence.err-port'));
+ },
+ message: this.t('sentence.msg-port')
+ }
+ ],
+ cpu: [
+ {
+ required: true,
+ message: this.t('prompt.enter-cpu-core-count')
+ },
+ {
+ validator: (rule, value, callback) => {
+ if (
+ (/^\d+$/g.test(value) && value >= 1 && value <= 4) ||
+ value === ""
+ ) {
+ return callback();
+ }
+ callback(this.t('sentence.err-cpu-core-count'));
+ },
+ message: this.t('sentence.msg-cpu-core-count')
+ }
+ ],
+ mem: [
+ {
+ required: true,
+ message: this.t('prompt.enter-memory-in-mb')
+ },
+ {
+ validator: (rule, value, callback) => {
+ if (
+ (/^\d+$/g.test(value) && value >= 1 && value <= 3584) ||
+ value === ""
+ ) {
+ return callback();
+ }
+ callback(this.t('sentence.err-memory-in-mb'));
+ },
+ message: this.t('sentence.msg-memory-in-mb')
+ }
+ ],
+ timeout: [
+ {
+ required: true,
+ message: this.t('prompt.enter-ttl-in-hours')
+ },
+ {
+ validator: (rule, value, callback) => {
+ if (
+ (/^\d+$/g.test(value) && value >= 1 && value <= 24) ||
+ value === ""
+ ) {
+ return callback();
+ }
+ callback(this.t('sentence.err-ttl-in-hours'));
+ },
+ message: this.t('sentence.msg-ttl-in-hours')
+ }
+ ]
+ }
+ }
render() {
const { getFieldDecorator } = this.props.form;
@@ -105,35 +110,36 @@ export class SelectForm extends React.Component {
labelCol: { span: 10 },
wrapperCol: { span: 14 }
};
+ const rules = this.getRules()
return (
);
}
}
-export default Form.create()(SelectForm);
+export default Form.create()(withTranslation()(SelectForm));
diff --git a/src/components/SelectSystemConfig/SelectSystemConfig.js b/src/components/SelectSystemConfig/SelectSystemConfig.js
index 3915bb8..df202fb 100644
--- a/src/components/SelectSystemConfig/SelectSystemConfig.js
+++ b/src/components/SelectSystemConfig/SelectSystemConfig.js
@@ -1,6 +1,9 @@
import React, { Fragment } from "react";
-import "./SelectSystemConfig.scss";
import classNames from "classnames";
+import { withTranslation } from 'react-i18next';
+
+import "./SelectSystemConfig.scss";
+
import {
Spin,
Steps,
@@ -20,14 +23,15 @@ const Step = Steps.Step;
const Option = Select.Option;
/**
- * 选择系统配置
+ * Select system configuration
*/
-export class SelectSystemConfig extends React.Component {
+class SelectSystemConfig extends React.Component {
static propTypes = {};
static defaultProps = {};
constructor(props) {
super(props);
+ this.t = props.t;
const currentStep = this.getCurrentStep();
const { isExistContainer, container } = this.isExistContainer();
@@ -43,19 +47,6 @@ export class SelectSystemConfig extends React.Component {
container,
skipModalVisible: false
};
-
- this.steps = [
- {
- title: "系统",
- desc: "选择系统",
- content: ""
- },
- {
- title: "性能参数",
- desc: "填写系统性能参数",
- content: ""
- }
- ];
}
getCurrentStep = () => {
@@ -69,7 +60,7 @@ export class SelectSystemConfig extends React.Component {
}
containerInfo = JSON.parse(containerInfo);
const curTime = Math.floor(new Date().getTime() / 1000);
- // 未过期
+ // Not expired
if (curTime < containerInfo.timeout) {
return { isExistContainer: true, container: containerInfo };
}
@@ -89,14 +80,14 @@ export class SelectSystemConfig extends React.Component {
const { currentStep, selectsObj, port } = this.state;
if (currentStep === 0) {
if (!selectsObj.length) {
- return message.error("请先选择系统和系统版本");
+ return message.error(this.t('sentence.err-empty-os'));
}
}
if (currentStep === 1) {
const result = !/\d./.test(port);
if (!port || result) {
- return message.error("请输入正确格式的端口号");
+ return message.error(this.t('sentence.err-port'));
}
}
this.setState({ currentStep: this.state.currentStep + 1 });
@@ -119,7 +110,7 @@ export class SelectSystemConfig extends React.Component {
const { validateFields } = this._form;
validateFields(async (err, values) => {
if (err) {
- return message.error("性能参数有误,请重新填写");
+ return message.error(this.t('sentence.err-resources'));
}
this.setState({ modalVisible: true });
this._values = values;
@@ -175,56 +166,65 @@ export class SelectSystemConfig extends React.Component {
this._shareUrl = res.shareUrl;
} else {
this.setState({ okLoading: false });
- message.error("创建失败,请重试");
+ message.error(this.t('sentence.err-creation'));
}
}
};
generateStepsContent = () => {
- this.steps[0].content = (
-
- {this.props.osList.map((item, index) => {
- const { selectsObj } = this.state;
- let osCode;
- if (selectsObj[index]) {
- osCode = selectsObj[index].osCode;
- }
- const classes = classNames({
- isSelect: !!selectsObj[index]
- });
- return (
-
- {item.label}
-
-
-
- );
- })}
-
- );
-
- this.steps[1].content = ;
+ return [
+ {
+ title: this.t('keyword.os'),
+ desc: this.t('prompt.choose-os'),
+ content: (
+
+ {this.props.osList.map((item, index) => {
+ const { selectsObj } = this.state;
+ let osCode;
+ if (selectsObj[index]) {
+ osCode = selectsObj[index].osCode;
+ }
+ const classes = classNames({
+ isSelect: !!selectsObj[index]
+ });
+ return (
+
+ {item.label}
+
+
+
+ );
+ })}
+
+ )
+ },
+ {
+ title: this.t('keyword.resources'),
+ desc: this.t('prompt.choose-resources'),
+ content:
+ }
+ ];
};
getSystemVersion = () => {
@@ -250,13 +250,13 @@ export class SelectSystemConfig extends React.Component {
} = this.state;
const { osList } = this.props;
const { system, version } = this.getSystemVersion();
- this.generateStepsContent();
+ const steps = this.generateStepsContent();
return (
- {this.steps.map(step => (
+ {steps.map(step => (
- {this.steps[currentStep].content}
+ {steps[currentStep].content}
this.setState({ modalVisible: false })}
- okText="确定"
- cancelText="取消"
+ okText={this.t('keyword.confirm')}
+ cancelText={this.t('keyword.cancel')}
>
{
return (
-
+
{title}
);
};
/**
- * 显示用户配置表单
+ * Show current system configuration
*/
class SystemConfiguration extends React.Component {
static propTypes = {
/**
- * 系统名称
+ * OS name
*/
system: PropTypes.string,
/**
- * 系统版本号
+ * OS version
*/
version: PropTypes.string,
/**
- * cpu 核数
+ * CPU
*/
cpu: PropTypes.string,
/**
- * 空间大小
+ * Memory
*/
mem: PropTypes.string,
/**
- * 使用时长
+ * Time-to-live
*/
timeout: PropTypes.any,
/**
- * 内部端口号
+ * Port exposed inside container
*/
innerPort: PropTypes.string,
/**
- * 外部端口号
+ * Port that is publically accessible
*/
externalPort: PropTypes.any,
/**
- * 是否显示外部字段
- * 默认:false
+ * Should show innerPort
+ * Default: false
*/
showInnerPort: PropTypes.bool,
/**
- * 是否显示内部字段
- * 默认:false
+ * Should show external port
+ * Default: false
*/
showExternalPort: PropTypes.bool
};
+
static defaultProps = {
showInnerPort: false,
showExternalPort: false
};
+ constructor(props) {
+ super(props);
+ this.t = props.t;
+ }
+
getSystemVersion = () => {
const { system, version } = this.props;
return (
@@ -98,29 +104,28 @@ class SystemConfiguration extends React.Component {
return (