Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add I18n support #9

Merged
merged 2 commits into from Mar 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
30 changes: 30 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -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"
Expand Down
40 changes: 23 additions & 17 deletions 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";
Expand All @@ -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 = {
Expand All @@ -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,
Expand All @@ -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
});
Expand All @@ -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 {
Expand All @@ -79,7 +84,6 @@ class App extends Component {
this.setState({ osList });
};


handleOSSelect = selectedOS => {
const osList = [...this.state.osList];

Expand All @@ -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(
Expand Down Expand Up @@ -152,23 +156,24 @@ class App extends Component {
<div className="app">
<h1 className="app__title">
<span className="app__title-span">
instantbox
{this.t('site.heading')}
</span>
</h1>
<div className="app__desc">
<div className="app__text-editor-wrap">
<div className="app__title-bar">
Ubuntu / CentOS / Arch Linux / Debian / Fedora / Alpine
{this.state.osList.map(os => os.label).join(' / ') || this.t('site.heading')}
</div>
<div className="app__text-body">
<span style={{ marginRight: 10 }}>$</span>
<span className="app__desc-content" />
</div>
</div>
</div>
<LanguageSwitcher className="app__lang-switcher" i18n={this.props.i18n} />

<Divider style={{ marginTop: 100 }}>
{isExistContainer ? "您已创建系统" : "选择系统配置"}
{isExistContainer ? this.t('prompt.created-os') : this.t('prompt.select-os')}
</Divider>

{isExistContainer && (
Expand All @@ -192,7 +197,7 @@ class App extends Component {
<div className="app__os-list">
{isExistContainer ? (
<div style={{ marginTop: 20, textAlign: "center" }}>
<Tooltip title="若打开的页面报错,请重新点击">
<Tooltip title={this.t('sentence.open-webshell-try-again')}>
<Button
size="large"
color="primary"
Expand All @@ -205,18 +210,19 @@ class App extends Component {
}}
style={{ margin: 10 }}
>
打开已创建的系统
{this.t('prompt.open-os')}
</Button>
</Tooltip>

<Button
size="large"
type="danger"
color="secondary"
variant="outlined"
onClick={this.handleSelectAgain}
style={{ margin: 10 }}
>
重新选择
{this.t('prompt.purge-os')}
</Button>
</div>
) : (
Expand All @@ -225,21 +231,21 @@ class App extends Component {
</div>
</div>
<Modal
title="提示"
title={this.t('keyword.notice')}
visible={this.state.skipModalVisible}
onOk={() => {
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 })}
>
<p>系统已创建,是否跳转到系统页面?</p>
<p>{this.t('sentence.open-webshell')}</p>
</Modal>
</LoadingScreen>
);
}
}

export default App;
export default withTranslation()(App);
5 changes: 5 additions & 0 deletions src/App.scss
Expand Up @@ -18,6 +18,11 @@
&__start {
margin: 80px 0;
}
&__lang-switcher {
position: absolute;
top: 10px;
right: 12px;
}
&__text-field {
margin-top: 8px !important;
input {
Expand Down
3 changes: 2 additions & 1 deletion 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(<App />, div);
ReactDOM.render(<App i18n={i18n} />, div);
ReactDOM.unmountComponentAtNode(div);
});
34 changes: 34 additions & 0 deletions 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 <div className={this.props.className}>
<p>
<span>{this.t('language')} | </span>
{
this.t('locale') === 'en'
? <a href="?lng=zh" data-locale="zh" onClick={this.changeLanguage.bind(this)}>中文</a>
: <a href="?lng=en" data-locale="en" onClick={this.changeLanguage.bind(this)}>English</a>
}
</p>
</div>
}
}

export default withTranslation()(LanguageSwitcher);
3 changes: 3 additions & 0 deletions src/components/LanguageSwitcher/index.js
@@ -0,0 +1,3 @@
import LanguageSwitcher from './LanguageSwitcher';

export default LanguageSwitcher;