Merge pull request #14 from RobotHuang/master

feat: support i18n
This commit is contained in:
hsluoyz 2021-02-20 11:00:43 +08:00 committed by Yang Luo
commit 717d655fdb
17 changed files with 371 additions and 100 deletions

View File

@ -8,11 +8,13 @@
"@testing-library/react": "^9.3.2", "@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2", "@testing-library/user-event": "^7.1.2",
"antd": "^4.7.2", "antd": "^4.7.2",
"i18next": "^19.8.9",
"moment": "^2.29.1", "moment": "^2.29.1",
"react": "^16.14.0", "react": "^16.14.0",
"react-device-detect": "^1.14.0", "react-device-detect": "^1.14.0",
"react-dom": "^16.14.0", "react-dom": "^16.14.0",
"react-github-corner": "^2.5.0", "react-github-corner": "^2.5.0",
"react-i18next": "^11.8.7",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-scripts": "3.4.3" "react-scripts": "3.4.3"
}, },

View File

@ -35,6 +35,8 @@ import Face from "./auth/Face";
import LoginPage from "./auth/LoginPage"; import LoginPage from "./auth/LoginPage";
import * as AuthBackend from "./auth/AuthBackend"; import * as AuthBackend from "./auth/AuthBackend";
import AuthCallback from "./auth/AuthCallback"; import AuthCallback from "./auth/AuthCallback";
import SelectLanguageBox from './SelectLanguageBox';
import i18next from 'i18next';
const { Header, Footer } = Layout; const { Header, Footer } = Layout;
@ -56,6 +58,7 @@ class App extends Component {
} }
componentWillMount() { componentWillMount() {
Setting.setLanguage();
this.updateMenuKey(); this.updateMenuKey();
this.getAccount(); this.getAccount();
} }
@ -127,11 +130,11 @@ class App extends Component {
<Menu onClick={this.handleRightDropdownClick.bind(this)}> <Menu onClick={this.handleRightDropdownClick.bind(this)}>
<Menu.Item key="201"> <Menu.Item key="201">
<SettingOutlined /> <SettingOutlined />
My Account {i18next.t("account:My Account")}
</Menu.Item> </Menu.Item>
<Menu.Item key="202"> <Menu.Item key="202">
<LogoutOutlined /> <LogoutOutlined />
Logout {i18next.t("account:Logout")}
</Menu.Item> </Menu.Item>
</Menu> </Menu>
); );
@ -162,14 +165,14 @@ class App extends Component {
res.push( res.push(
<Menu.Item key="100" style={{float: 'right', marginRight: '20px'}}> <Menu.Item key="100" style={{float: 'right', marginRight: '20px'}}>
<Link to="/register"> <Link to="/register">
Register {i18next.t("account:Register")}
</Link> </Link>
</Menu.Item> </Menu.Item>
); );
res.push( res.push(
<Menu.Item key="101" style={{float: 'right'}}> <Menu.Item key="101" style={{float: 'right'}}>
<Link to="/login"> <Link to="/login">
Login {i18next.t("account:Login")}
</Link> </Link>
</Menu.Item> </Menu.Item>
); );
@ -190,7 +193,7 @@ class App extends Component {
res.push( res.push(
<Menu.Item key="0"> <Menu.Item key="0">
<Link to="/"> <Link to="/">
Home {i18next.t("general:Home")}
</Link> </Link>
</Menu.Item> </Menu.Item>
); );
@ -199,33 +202,32 @@ class App extends Component {
res.push( res.push(
<Menu.Item key="1"> <Menu.Item key="1">
<Link to="/organizations"> <Link to="/organizations">
Organizations {i18next.t("general:Organizations")}
</Link> </Link>
</Menu.Item> </Menu.Item>
); );
res.push( res.push(
<Menu.Item key="2"> <Menu.Item key="2">
<Link to="/users"> <Link to="/users">
Users {i18next.t("general:Users")}
</Link> </Link>
</Menu.Item> </Menu.Item>
); );
res.push( res.push(
<Menu.Item key="3"> <Menu.Item key="3">
<Link to="/providers"> <Link to="/providers">
Providers {i18next.t("general:Providers")}
</Link> </Link>
</Menu.Item> </Menu.Item>
); );
res.push( res.push(
<Menu.Item key="4"> <Menu.Item key="4">
<Link to="/applications"> <Link to="/applications">
Applications {i18next.t("general:Applications")}
</Link> </Link>
</Menu.Item> </Menu.Item>
); );
} }
return res; return res;
} }
@ -305,6 +307,7 @@ class App extends Component {
textAlign: 'center', textAlign: 'center',
} }
}> }>
<SelectLanguageBox/>
Made with <span style={{color: 'rgb(255, 255, 255)'}}></span> by <a style={{fontWeight: "bold", color: "black"}} target="_blank" href="https://casbin.org">Casbin</a> Made with <span style={{color: 'rgb(255, 255, 255)'}}></span> by <a style={{fontWeight: "bold", color: "black"}} target="_blank" href="https://casbin.org">Casbin</a>
</Footer> </Footer>
) )

View File

@ -20,6 +20,7 @@ import * as Setting from "./Setting";
import * as ProviderBackend from "./backend/ProviderBackend"; import * as ProviderBackend from "./backend/ProviderBackend";
import * as OrganizationBackend from "./backend/OrganizationBackend"; import * as OrganizationBackend from "./backend/OrganizationBackend";
import Face from "./auth/Face"; import Face from "./auth/Face";
import i18next from "i18next";
const { Option } = Select; const { Option } = Select;
@ -89,13 +90,13 @@ class ApplicationEditPage extends React.Component {
return ( return (
<Card size="small" title={ <Card size="small" title={
<div> <div>
Edit Application&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("application:Edit Application")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" onClick={this.submitApplicationEdit.bind(this)}>Save</Button> <Button type="primary" onClick={this.submitApplicationEdit.bind(this)}>{i18next.t("general:Save")}</Button>
</div> </div>
} style={{marginLeft: '5px'}} type="inner"> } style={{marginLeft: '5px'}} type="inner">
<Row style={{marginTop: '10px'}} > <Row style={{marginTop: '10px'}} >
<Col style={{marginTop: '5px'}} span={2}> <Col style={{marginTop: '5px'}} span={2}>
Name: {i18next.t("general:Name")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.application.name} onChange={e => { <Input value={this.state.application.name} onChange={e => {
@ -105,7 +106,7 @@ class ApplicationEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}> <Col style={{marginTop: '5px'}} span={2}>
Display Name: {i18next.t("general:Display Name")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.application.displayName} onChange={e => { <Input value={this.state.application.displayName} onChange={e => {
@ -130,7 +131,7 @@ class ApplicationEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={1}> <Col style={{marginTop: '5px'}} span={1}>
Preview: {i18next.t("general:Preview")}:
</Col> </Col>
<Col span={23} > <Col span={23} >
<a target="_blank" href={this.state.application.logo}> <a target="_blank" href={this.state.application.logo}>
@ -142,7 +143,7 @@ class ApplicationEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}> <Col style={{marginTop: '5px'}} span={2}>
Organization: {i18next.t("general:Organization")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.application.organization} onChange={(value => {this.updateApplicationField('organization', value);})}> <Select virtual={false} style={{width: '100%'}} value={this.state.application.organization} onChange={(value => {this.updateApplicationField('organization', value);})}>
@ -154,7 +155,7 @@ class ApplicationEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}> <Col style={{marginTop: '5px'}} span={2}>
Enable Password: {i18next.t("application:Enable Password")}:
</Col> </Col>
<Col span={1} > <Col span={1} >
<Switch checked={this.state.application.enablePassword} onChange={checked => { <Switch checked={this.state.application.enablePassword} onChange={checked => {
@ -164,7 +165,7 @@ class ApplicationEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}> <Col style={{marginTop: '5px'}} span={2}>
Providers: {i18next.t("general:Providers")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select mode="tags" style={{width: '100%'}} <Select mode="tags" style={{width: '100%'}}
@ -180,7 +181,7 @@ class ApplicationEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}> <Col style={{marginTop: '5px'}} span={2}>
Face Preview: {i18next.t("application:Face Preview")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<a style={{marginBottom: '10px'}} target="_blank" href={`/doors/${this.state.application.name}`}> <a style={{marginBottom: '10px'}} target="_blank" href={`/doors/${this.state.application.name}`}>
@ -237,7 +238,7 @@ class ApplicationEditPage extends React.Component {
<Col span={2}> <Col span={2}>
</Col> </Col>
<Col span={18}> <Col span={18}>
<Button type="primary" size="large" onClick={this.submitApplicationEdit.bind(this)}>Save</Button> <Button type="primary" size="large" onClick={this.submitApplicationEdit.bind(this)}>{i18next.t("general:Save")}</Button>
</Col> </Col>
</Row> </Row>
</div> </div>

View File

@ -18,6 +18,7 @@ import {Button, Col, Popconfirm, Row, Table} from 'antd';
import moment from "moment"; import moment from "moment";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import * as ApplicationBackend from "./backend/ApplicationBackend"; import * as ApplicationBackend from "./backend/ApplicationBackend";
import i18next from "i18next";
class ApplicationListPage extends React.Component { class ApplicationListPage extends React.Component {
constructor(props) { constructor(props) {
@ -85,7 +86,7 @@ class ApplicationListPage extends React.Component {
renderTable(applications) { renderTable(applications) {
const columns = [ const columns = [
{ {
title: 'Name', title: i18next.t("general:Name"),
dataIndex: 'name', dataIndex: 'name',
key: 'name', key: 'name',
width: '150px', width: '150px',
@ -99,7 +100,7 @@ class ApplicationListPage extends React.Component {
} }
}, },
{ {
title: 'Created Time', title: i18next.t("general:Created Time"),
dataIndex: 'createdTime', dataIndex: 'createdTime',
key: 'createdTime', key: 'createdTime',
width: '160px', width: '160px',
@ -109,7 +110,7 @@ class ApplicationListPage extends React.Component {
} }
}, },
{ {
title: 'Display Name', title: i18next.t("general:Display Name"),
dataIndex: 'displayName', dataIndex: 'displayName',
key: 'displayName', key: 'displayName',
// width: '100px', // width: '100px',
@ -129,7 +130,7 @@ class ApplicationListPage extends React.Component {
} }
}, },
{ {
title: 'Organization', title: i18next.t("general:Organization"),
dataIndex: 'organization', dataIndex: 'organization',
key: 'organization', key: 'organization',
width: '200px', width: '200px',
@ -143,7 +144,7 @@ class ApplicationListPage extends React.Component {
} }
}, },
{ {
title: 'Providers', title: i18next.t("general:Providers"),
dataIndex: 'providers', dataIndex: 'providers',
key: 'providers', key: 'providers',
width: '200px', width: '200px',
@ -157,19 +158,19 @@ class ApplicationListPage extends React.Component {
} }
}, },
{ {
title: 'Action', title: i18next.t("general:Action"),
dataIndex: '', dataIndex: '',
key: 'op', key: 'op',
width: '170px', width: '170px',
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/applications/${record.name}`)}>Edit</Button> <Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/applications/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm <Popconfirm
title={`Sure to delete application: ${record.name} ?`} title={`Sure to delete application: ${record.name} ?`}
onConfirm={() => this.deleteApplication(index)} onConfirm={() => this.deleteApplication(index)}
> >
<Button style={{marginBottom: '10px'}} type="danger">Delete</Button> <Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm> </Popconfirm>
</div> </div>
) )
@ -182,8 +183,8 @@ class ApplicationListPage extends React.Component {
<Table columns={columns} dataSource={applications} rowKey="name" size="middle" bordered pagination={{pageSize: 100}} <Table columns={columns} dataSource={applications} rowKey="name" size="middle" bordered pagination={{pageSize: 100}}
title={() => ( title={() => (
<div> <div>
Applications&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("general:Applications")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addApplication.bind(this)}>Add</Button> <Button type="primary" size="small" onClick={this.addApplication.bind(this)}>{i18next.t("general:Add")}</Button>
</div> </div>
)} )}
loading={applications === null} loading={applications === null}

View File

@ -16,6 +16,7 @@ import React from "react";
import {Button, Card, Col, Input, Row} from 'antd'; import {Button, Card, Col, Input, Row} from 'antd';
import * as OrganizationBackend from "./backend/OrganizationBackend"; import * as OrganizationBackend from "./backend/OrganizationBackend";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import i18next from "i18next";
class OrganizationEditPage extends React.Component { class OrganizationEditPage extends React.Component {
constructor(props) { constructor(props) {
@ -61,13 +62,13 @@ class OrganizationEditPage extends React.Component {
return ( return (
<Card size="small" title={ <Card size="small" title={
<div> <div>
Edit Organization&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("organization:Edit Organization")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" onClick={this.submitOrganizationEdit.bind(this)}>Save</Button> <Button type="primary" onClick={this.submitOrganizationEdit.bind(this)}>{i18next.t("general:Save")}</Button>
</div> </div>
} style={{marginLeft: '5px'}} type="inner"> } style={{marginLeft: '5px'}} type="inner">
<Row style={{marginTop: '10px'}} > <Row style={{marginTop: '10px'}} >
<Col style={{marginTop: '5px'}} span={2}> <Col style={{marginTop: '5px'}} span={2}>
Name: {i18next.t("general:Name")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.organization.name} onChange={e => { <Input value={this.state.organization.name} onChange={e => {
@ -77,7 +78,7 @@ class OrganizationEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}> <Col style={{marginTop: '5px'}} span={2}>
Display Name: {i18next.t("general:Display Name")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.organization.displayName} onChange={e => { <Input value={this.state.organization.displayName} onChange={e => {
@ -87,7 +88,7 @@ class OrganizationEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}> <Col style={{marginTop: '5px'}} span={2}>
Website URL: {i18next.t("organization:Website URL")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.organization.websiteUrl} onChange={e => { <Input value={this.state.organization.websiteUrl} onChange={e => {
@ -137,7 +138,7 @@ class OrganizationEditPage extends React.Component {
<Col span={2}> <Col span={2}>
</Col> </Col>
<Col span={18}> <Col span={18}>
<Button type="primary" size="large" onClick={this.submitOrganizationEdit.bind(this)}>Save</Button> <Button type="primary" size="large" onClick={this.submitOrganizationEdit.bind(this)}>{i18next.t("general:Save")}</Button>
</Col> </Col>
</Row> </Row>
</div> </div>

View File

@ -18,6 +18,7 @@ import {Button, Col, Popconfirm, Row, Table} from 'antd';
import moment from "moment"; import moment from "moment";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import * as OrganizationBackend from "./backend/OrganizationBackend"; import * as OrganizationBackend from "./backend/OrganizationBackend";
import i18next from "i18next";
class OrganizationListPage extends React.Component { class OrganizationListPage extends React.Component {
constructor(props) { constructor(props) {
@ -83,7 +84,7 @@ class OrganizationListPage extends React.Component {
renderTable(organizations) { renderTable(organizations) {
const columns = [ const columns = [
{ {
title: 'Name', title: i18next.t("general:Name"),
dataIndex: 'name', dataIndex: 'name',
key: 'name', key: 'name',
width: '120px', width: '120px',
@ -97,7 +98,7 @@ class OrganizationListPage extends React.Component {
} }
}, },
{ {
title: 'Created Time', title: i18next.t("general:Created Time"),
dataIndex: 'createdTime', dataIndex: 'createdTime',
key: 'createdTime', key: 'createdTime',
width: '160px', width: '160px',
@ -107,14 +108,14 @@ class OrganizationListPage extends React.Component {
} }
}, },
{ {
title: 'Display Name', title: i18next.t("general:Display Name"),
dataIndex: 'displayName', dataIndex: 'displayName',
key: 'displayName', key: 'displayName',
// width: '100px', // width: '100px',
sorter: (a, b) => a.displayName.localeCompare(b.displayName), sorter: (a, b) => a.displayName.localeCompare(b.displayName),
}, },
{ {
title: 'Website URL', title: i18next.t("organization:Website URL"),
dataIndex: 'websiteUrl', dataIndex: 'websiteUrl',
key: 'websiteUrl', key: 'websiteUrl',
width: '300px', width: '300px',
@ -128,19 +129,19 @@ class OrganizationListPage extends React.Component {
} }
}, },
{ {
title: 'Action', title: i18next.t("general:Action"),
dataIndex: '', dataIndex: '',
key: 'op', key: 'op',
width: '170px', width: '170px',
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/organizations/${record.name}`)}>Edit</Button> <Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/organizations/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm <Popconfirm
title={`Sure to delete organization: ${record.name} ?`} title={`Sure to delete organization: ${record.name} ?`}
onConfirm={() => this.deleteOrganization(index)} onConfirm={() => this.deleteOrganization(index)}
> >
<Button style={{marginBottom: '10px'}} type="danger">Delete</Button> <Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm> </Popconfirm>
</div> </div>
) )
@ -153,8 +154,8 @@ class OrganizationListPage extends React.Component {
<Table columns={columns} dataSource={organizations} rowKey="name" size="middle" bordered pagination={{pageSize: 100}} <Table columns={columns} dataSource={organizations} rowKey="name" size="middle" bordered pagination={{pageSize: 100}}
title={() => ( title={() => (
<div> <div>
Organizations&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("general:Organizations")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addOrganization.bind(this)}>Add</Button> <Button type="primary" size="small" onClick={this.addOrganization.bind(this)}>{i18next.t("general:Add")}</Button>
</div> </div>
)} )}
loading={organizations === null} loading={organizations === null}

View File

@ -16,6 +16,7 @@ import React from "react";
import {Button, Card, Col, Input, Row, Select} from 'antd'; import {Button, Card, Col, Input, Row, Select} from 'antd';
import * as ProviderBackend from "./backend/ProviderBackend"; import * as ProviderBackend from "./backend/ProviderBackend";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import i18next from "i18next";
const { Option } = Select; const { Option } = Select;
@ -63,13 +64,13 @@ class ProviderEditPage extends React.Component {
return ( return (
<Card size="small" title={ <Card size="small" title={
<div> <div>
Edit Provider&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("provider:Edit Provider")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" onClick={this.submitProviderEdit.bind(this)}>Save</Button> <Button type="primary" onClick={this.submitProviderEdit.bind(this)}>{i18next.t("general:Save")}</Button>
</div> </div>
} style={{marginLeft: '5px'}} type="inner"> } style={{marginLeft: '5px'}} type="inner">
<Row style={{marginTop: '10px'}} > <Row style={{marginTop: '10px'}} >
<Col style={{marginTop: '5px'}} span={2}> <Col style={{marginTop: '5px'}} span={2}>
Name: {i18next.t("general:Name")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.provider.name} onChange={e => { <Input value={this.state.provider.name} onChange={e => {
@ -79,7 +80,7 @@ class ProviderEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}> <Col style={{marginTop: '5px'}} span={2}>
Display Name: {i18next.t("general:Display Name")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.provider.displayName} onChange={e => { <Input value={this.state.provider.displayName} onChange={e => {
@ -89,7 +90,7 @@ class ProviderEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}> <Col style={{marginTop: '5px'}} span={2}>
Type: {i18next.t("provider:Type")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.provider.type} onChange={(value => {this.updateProviderField('type', value);})}> <Select virtual={false} style={{width: '100%'}} value={this.state.provider.type} onChange={(value => {this.updateProviderField('type', value);})}>
@ -106,7 +107,7 @@ class ProviderEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}> <Col style={{marginTop: '5px'}} span={2}>
Client ID: {i18next.t("provider:Client ID")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.provider.clientId} onChange={e => { <Input value={this.state.provider.clientId} onChange={e => {
@ -116,7 +117,7 @@ class ProviderEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}> <Col style={{marginTop: '5px'}} span={2}>
Client Secret: {i18next.t("provider:Client Secret")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.provider.clientSecret} onChange={e => { <Input value={this.state.provider.clientSecret} onChange={e => {
@ -126,7 +127,7 @@ class ProviderEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}> <Col style={{marginTop: '5px'}} span={2}>
Provider URL: {i18next.t("provider:Provider URL")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.provider.providerUrl} onChange={e => { <Input value={this.state.provider.providerUrl} onChange={e => {
@ -176,7 +177,7 @@ class ProviderEditPage extends React.Component {
<Col span={2}> <Col span={2}>
</Col> </Col>
<Col span={18}> <Col span={18}>
<Button type="primary" size="large" onClick={this.submitProviderEdit.bind(this)}>Save</Button> <Button type="primary" size="large" onClick={this.submitProviderEdit.bind(this)}>{i18next.t("general:Save")}</Button>
</Col> </Col>
</Row> </Row>
</div> </div>

View File

@ -19,6 +19,7 @@ import moment from "moment";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import * as ProviderBackend from "./backend/ProviderBackend"; import * as ProviderBackend from "./backend/ProviderBackend";
import * as Provider from "./auth/Provider"; import * as Provider from "./auth/Provider";
import i18next from "i18next";
class ProviderListPage extends React.Component { class ProviderListPage extends React.Component {
constructor(props) { constructor(props) {
@ -87,7 +88,7 @@ class ProviderListPage extends React.Component {
renderTable(providers) { renderTable(providers) {
const columns = [ const columns = [
{ {
title: 'Name', title: i18next.t("general:Name"),
dataIndex: 'name', dataIndex: 'name',
key: 'name', key: 'name',
width: '120px', width: '120px',
@ -101,7 +102,7 @@ class ProviderListPage extends React.Component {
} }
}, },
{ {
title: 'Created Time', title: i18next.t("general:Created Time"),
dataIndex: 'createdTime', dataIndex: 'createdTime',
key: 'createdTime', key: 'createdTime',
width: '160px', width: '160px',
@ -111,14 +112,14 @@ class ProviderListPage extends React.Component {
} }
}, },
{ {
title: 'Display Name', title: i18next.t("general:Display Name"),
dataIndex: 'displayName', dataIndex: 'displayName',
key: 'displayName', key: 'displayName',
// width: '100px', // width: '100px',
sorter: (a, b) => a.displayName.localeCompare(b.displayName), sorter: (a, b) => a.displayName.localeCompare(b.displayName),
}, },
{ {
title: 'Type', title: i18next.t("provider:Type"),
dataIndex: 'type', dataIndex: 'type',
key: 'type', key: 'type',
width: '80px', width: '80px',
@ -130,7 +131,7 @@ class ProviderListPage extends React.Component {
} }
}, },
{ {
title: 'Client Id', title: i18next.t("provider:Client ID"),
dataIndex: 'clientId', dataIndex: 'clientId',
key: 'clientId', key: 'clientId',
width: '150px', width: '150px',
@ -144,7 +145,7 @@ class ProviderListPage extends React.Component {
// sorter: (a, b) => a.clientSecret.localeCompare(b.clientSecret), // sorter: (a, b) => a.clientSecret.localeCompare(b.clientSecret),
// }, // },
{ {
title: 'Provider Url', title: i18next.t("provider:Provider URL"),
dataIndex: 'providerUrl', dataIndex: 'providerUrl',
key: 'providerUrl', key: 'providerUrl',
width: '150px', width: '150px',
@ -160,19 +161,19 @@ class ProviderListPage extends React.Component {
} }
}, },
{ {
title: 'Action', title: i18next.t("general:Action"),
dataIndex: '', dataIndex: '',
key: 'op', key: 'op',
width: '170px', width: '170px',
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/providers/${record.name}`)}>Edit</Button> <Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/providers/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm <Popconfirm
title={`Sure to delete provider: ${record.name} ?`} title={`Sure to delete provider: ${record.name} ?`}
onConfirm={() => this.deleteProvider(index)} onConfirm={() => this.deleteProvider(index)}
> >
<Button style={{marginBottom: '10px'}} type="danger">Delete</Button> <Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm> </Popconfirm>
</div> </div>
) )
@ -185,8 +186,8 @@ class ProviderListPage extends React.Component {
<Table columns={columns} dataSource={providers} rowKey="name" size="middle" bordered pagination={{pageSize: 100}} <Table columns={columns} dataSource={providers} rowKey="name" size="middle" bordered pagination={{pageSize: 100}}
title={() => ( title={() => (
<div> <div>
Providers&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("general:Providers")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addProvider.bind(this)}>Add</Button> <Button type="primary" size="small" onClick={this.addProvider.bind(this)}>{i18next.t("general:Add")}</Button>
</div> </div>
)} )}
loading={providers === null} loading={providers === null}

View File

@ -0,0 +1,38 @@
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from "react";
import * as Setting from "./Setting";
class SelectLanguageBox extends React.Component {
constructor(props) {
super(props);
this.state = {
classes: props,
};
}
render() {
return (
<div align="center">
<div class="box" style={{width: "600px"}}>
<a href="javascript:void(0)" onClick={() => Setting.changeLanguage("en")} class="lang-selector">English</a>/
<a href="javascript:void(0)" onClick={() => Setting.changeLanguage("zh")} class="lang-selector">简体中文</a>
</div>
</div>
)
}
}
export default SelectLanguageBox;

View File

@ -15,6 +15,8 @@
import {message} from "antd"; import {message} from "antd";
import React from "react"; import React from "react";
import {isMobile as isMobileDevice} from "react-device-detect"; import {isMobile as isMobileDevice} from "react-device-detect";
import "./i18n";
import i18next from "i18next";
export let ServerUrl = ""; export let ServerUrl = "";
@ -135,3 +137,17 @@ export function getAvatarColor(s) {
} }
return colorList[random % 4]; return colorList[random % 4];
} }
export function setLanguage() {
let language = localStorage.getItem('language');
if (language === undefined) {
language = "en"
}
i18next.changeLanguage(language)
}
export function changeLanguage(language) {
localStorage.setItem("language", language)
i18next.changeLanguage(language)
window.location.reload(true);
}

View File

@ -18,6 +18,7 @@ import * as UserBackend from "./backend/UserBackend";
import * as OrganizationBackend from "./backend/OrganizationBackend"; import * as OrganizationBackend from "./backend/OrganizationBackend";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import {LinkOutlined} from "@ant-design/icons"; import {LinkOutlined} from "@ant-design/icons";
import i18next from "i18next";
const { Option } = Select; const { Option } = Select;
@ -77,13 +78,13 @@ class UserEditPage extends React.Component {
return ( return (
<Card size="small" title={ <Card size="small" title={
<div> <div>
Edit User&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("user:Edit User")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" onClick={this.submitUserEdit.bind(this)}>Save</Button> <Button type="primary" onClick={this.submitUserEdit.bind(this)}>{i18next.t("general:Save")}</Button>
</div> </div>
} style={{marginLeft: '5px'}} type="inner"> } style={{marginLeft: '5px'}} type="inner">
<Row style={{marginTop: '10px'}} > <Row style={{marginTop: '10px'}} >
<Col style={{marginTop: '5px'}} span={2}> <Col style={{marginTop: '5px'}} span={2}>
Organization: {i18next.t("general:Organization")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.user.owner} onChange={(value => {this.updateUserField('owner', value);})}> <Select virtual={false} style={{width: '100%'}} value={this.state.user.owner} onChange={(value => {this.updateUserField('owner', value);})}>
@ -103,7 +104,7 @@ class UserEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}> <Col style={{marginTop: '5px'}} span={2}>
Name: {i18next.t("general:Name")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.user.name} onChange={e => { <Input value={this.state.user.name} onChange={e => {
@ -113,7 +114,7 @@ class UserEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}> <Col style={{marginTop: '5px'}} span={2}>
Display Name: {i18next.t("general:Display Name")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.user.displayName} onChange={e => { <Input value={this.state.user.displayName} onChange={e => {
@ -123,7 +124,7 @@ class UserEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}> <Col style={{marginTop: '5px'}} span={2}>
Avatar: {i18next.t("general:Avatar")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: '20px'}} >
@ -138,7 +139,7 @@ class UserEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={1}> <Col style={{marginTop: '5px'}} span={1}>
Preview: {i18next.t("general:Preview")}:
</Col> </Col>
<Col span={23} > <Col span={23} >
<a target="_blank" href={this.state.user.avatar}> <a target="_blank" href={this.state.user.avatar}>
@ -150,7 +151,7 @@ class UserEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}> <Col style={{marginTop: '5px'}} span={2}>
Password Type: {i18next.t("general:Password Type")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.user.passwordType} onChange={e => { <Input value={this.state.user.passwordType} onChange={e => {
@ -160,7 +161,7 @@ class UserEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}> <Col style={{marginTop: '5px'}} span={2}>
Password: {i18next.t("general:Password")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.user.password} onChange={e => { <Input value={this.state.user.password} onChange={e => {
@ -170,7 +171,7 @@ class UserEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}> <Col style={{marginTop: '5px'}} span={2}>
Email: {i18next.t("general:Email")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.user.email} onChange={e => { <Input value={this.state.user.email} onChange={e => {
@ -180,7 +181,7 @@ class UserEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}> <Col style={{marginTop: '5px'}} span={2}>
Phone: {i18next.t("general:Phone")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.user.phone} onChange={e => { <Input value={this.state.user.phone} onChange={e => {
@ -190,7 +191,7 @@ class UserEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}> <Col style={{marginTop: '5px'}} span={2}>
Affiliation: {i18next.t("user:Affiliation")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.user.affiliation} onChange={e => { <Input value={this.state.user.affiliation} onChange={e => {
@ -200,7 +201,7 @@ class UserEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}> <Col style={{marginTop: '5px'}} span={2}>
Tag: {i18next.t("user:Tag")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.user.tag} onChange={e => { <Input value={this.state.user.tag} onChange={e => {
@ -221,7 +222,7 @@ class UserEditPage extends React.Component {
<React.Fragment> <React.Fragment>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}> <Col style={{marginTop: '5px'}} span={2}>
Is Admin: {i18next.t("user:Is Admin")}:
</Col> </Col>
<Col span={1} > <Col span={1} >
<Switch checked={this.state.user.isAdmin} onChange={checked => { <Switch checked={this.state.user.isAdmin} onChange={checked => {
@ -231,7 +232,7 @@ class UserEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}> <Col style={{marginTop: '5px'}} span={2}>
Is Global Admin: {i18next.t("user:Is Global Admin")}:
</Col> </Col>
<Col span={1} > <Col span={1} >
<Switch checked={this.state.user.isGlobalAdmin} onChange={checked => { <Switch checked={this.state.user.isGlobalAdmin} onChange={checked => {
@ -289,7 +290,7 @@ class UserEditPage extends React.Component {
<Col span={2}> <Col span={2}>
</Col> </Col>
<Col span={18}> <Col span={18}>
<Button type="primary" size="large" onClick={this.submitUserEdit.bind(this)}>Save</Button> <Button type="primary" size="large" onClick={this.submitUserEdit.bind(this)}>{i18next.t("general:Save")}</Button>
</Col> </Col>
</Row> </Row>
</div> </div>

View File

@ -18,6 +18,7 @@ import {Button, Col, Popconfirm, Row, Switch, Table} from 'antd';
import moment from "moment"; import moment from "moment";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import * as UserBackend from "./backend/UserBackend"; import * as UserBackend from "./backend/UserBackend";
import i18next from "i18next";
class UserListPage extends React.Component { class UserListPage extends React.Component {
constructor(props) { constructor(props) {
@ -90,7 +91,7 @@ class UserListPage extends React.Component {
renderTable(users) { renderTable(users) {
const columns = [ const columns = [
{ {
title: 'Organization', title: i18next.t("general:Organization"),
dataIndex: 'owner', dataIndex: 'owner',
key: 'owner', key: 'owner',
width: '120px', width: '120px',
@ -104,7 +105,7 @@ class UserListPage extends React.Component {
} }
}, },
{ {
title: 'Name', title: i18next.t("general:Name"),
dataIndex: 'name', dataIndex: 'name',
key: 'name', key: 'name',
width: '100px', width: '100px',
@ -118,7 +119,7 @@ class UserListPage extends React.Component {
} }
}, },
{ {
title: 'Created Time', title: i18next.t("general:Created Time"),
dataIndex: 'createdTime', dataIndex: 'createdTime',
key: 'createdTime', key: 'createdTime',
width: '160px', width: '160px',
@ -142,14 +143,14 @@ class UserListPage extends React.Component {
// sorter: (a, b) => a.password.localeCompare(b.password), // sorter: (a, b) => a.password.localeCompare(b.password),
// }, // },
{ {
title: 'Display Name', title: i18next.t("general:Display Name"),
dataIndex: 'displayName', dataIndex: 'displayName',
key: 'displayName', key: 'displayName',
// width: '100px', // width: '100px',
sorter: (a, b) => a.displayName.localeCompare(b.displayName), sorter: (a, b) => a.displayName.localeCompare(b.displayName),
}, },
{ {
title: 'Avatar', title: i18next.t("general:Avatar"),
dataIndex: 'avatar', dataIndex: 'avatar',
key: 'avatar', key: 'avatar',
width: '100px', width: '100px',
@ -162,7 +163,7 @@ class UserListPage extends React.Component {
} }
}, },
{ {
title: 'Email', title: i18next.t("general:Email"),
dataIndex: 'email', dataIndex: 'email',
key: 'email', key: 'email',
width: '160px', width: '160px',
@ -183,21 +184,21 @@ class UserListPage extends React.Component {
// sorter: (a, b) => a.phone.localeCompare(b.phone), // sorter: (a, b) => a.phone.localeCompare(b.phone),
// }, // },
{ {
title: 'Affiliation', title: i18next.t("user:Affiliation"),
dataIndex: 'affiliation', dataIndex: 'affiliation',
key: 'affiliation', key: 'affiliation',
width: '120px', width: '120px',
sorter: (a, b) => a.affiliation.localeCompare(b.affiliation), sorter: (a, b) => a.affiliation.localeCompare(b.affiliation),
}, },
{ {
title: 'Tag', title: i18next.t("user:Tag"),
dataIndex: 'tag', dataIndex: 'tag',
key: 'tag', key: 'tag',
width: '100px', width: '100px',
sorter: (a, b) => a.tag.localeCompare(b.tag), sorter: (a, b) => a.tag.localeCompare(b.tag),
}, },
{ {
title: 'Is Admin', title: i18next.t("user:Is Admin"),
dataIndex: 'isAdmin', dataIndex: 'isAdmin',
key: 'isAdmin', key: 'isAdmin',
width: '120px', width: '120px',
@ -209,7 +210,7 @@ class UserListPage extends React.Component {
} }
}, },
{ {
title: 'Is Global Admin', title: i18next.t("user:Is Global Admin"),
dataIndex: 'isGlobalAdmin', dataIndex: 'isGlobalAdmin',
key: 'isGlobalAdmin', key: 'isGlobalAdmin',
width: '120px', width: '120px',
@ -221,19 +222,19 @@ class UserListPage extends React.Component {
} }
}, },
{ {
title: 'Action', title: i18next.t("general:Action"),
dataIndex: '', dataIndex: '',
key: 'op', key: 'op',
width: '170px', width: '170px',
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/users/${record.owner}/${record.name}`)}>Edit</Button> <Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/users/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm <Popconfirm
title={`Sure to delete user: ${record.name} ?`} title={`Sure to delete user: ${record.name} ?`}
onConfirm={() => this.deleteUser(index)} onConfirm={() => this.deleteUser(index)}
> >
<Button style={{marginBottom: '10px'}} type="danger">Delete</Button> <Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm> </Popconfirm>
</div> </div>
) )
@ -246,8 +247,8 @@ class UserListPage extends React.Component {
<Table columns={columns} dataSource={users} rowKey="name" size="middle" bordered pagination={{pageSize: 100}} <Table columns={columns} dataSource={users} rowKey="name" size="middle" bordered pagination={{pageSize: 100}}
title={() => ( title={() => (
<div> <div>
Users&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("general:Users")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addUser.bind(this)}>Add</Button> <Button type="primary" size="small" onClick={this.addUser.bind(this)}>{i18next.t("general:Add")}</Button>
</div> </div>
)} )}
loading={users === null} loading={users === null}

View File

@ -16,6 +16,7 @@ import React from "react";
import {Card, Col, Row} from "antd"; import {Card, Col, Row} from "antd";
import * as Setting from "../Setting"; import * as Setting from "../Setting";
import SingleCard from "./SingleCard"; import SingleCard from "./SingleCard";
import i18next from "i18next";
class HomePage extends React.Component { class HomePage extends React.Component {
constructor(props) { constructor(props) {
@ -29,10 +30,10 @@ class HomePage extends React.Component {
let items = []; let items = [];
if (Setting.isAdminUser(this.props.account)) { if (Setting.isAdminUser(this.props.account)) {
items = [ items = [
{link: "/organizations", name: "Organizations", organizer: "User containers"}, {link: "/organizations", name: i18next.t("general:Organizations"), organizer: i18next.t("general:User containers")},
{link: "/users", name: "Users", organizer: "Users under all organizations"}, {link: "/users", name: i18next.t("general:Users"), organizer: i18next.t("general:Users under all organizations")},
{link: "/providers", name: "Providers", organizer: "OAuth providers"}, {link: "/providers", name: i18next.t("general:Providers"), organizer: i18next.t("general:OAuth providers")},
{link: "/applications", name: "Applications", organizer: "Applications that requires authentication"}, {link: "/applications", name: i18next.t("general:Applications"), organizer: i18next.t("general:Applications that requires authentication")},
]; ];
} }

39
web/src/i18n.js Normal file
View File

@ -0,0 +1,39 @@
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import i18n from 'i18next'
import zh from './locales/zh.json'
import en from './locales/en.json'
const resources = {
en: en,
zh: zh
};
i18n
.init({
lng: "en",
resources: resources,
keySeparator: false,
interpolation: {
escapeValue: false
},
saveMissing: true,
})
export default i18n;

64
web/src/locales/en.json Normal file
View File

@ -0,0 +1,64 @@
{
"general":
{
"Home": "Home",
"Organizations": "Organizations",
"Providers": "Providers",
"Users": "Users",
"Applications": "Applications",
"Save": "Save",
"Add": "Add",
"Action": "Action",
"Edit": "Edit",
"Delete": "Delete",
"Organization": "Organization",
"Created Time": "Created Time",
"Name": "Name",
"Display Name": "Display Name",
"Avatar": "Avatar",
"Preview": "Preview",
"Password Type": "Password Type",
"Password": "Password",
"Email": "Email",
"Phone": "Phone",
"Logo": "Logo",
"User containers": "User containers",
"Users under all organizations": "Users under all organizations",
"OAuth providers": "OAuth providers",
"Applications that requires authentication": "Applications that requires authentication"
},
"account":
{
"My Account": "My Account",
"Login": "Login",
"Logout": "Logout",
"Register": "Register"
},
"organization":
{
"Edit Organization": "Edit Organization",
"Website URL": "Website URL"
},
"provider":
{
"Type": "Type",
"Client ID": "Client ID",
"Client Secret": "Client Secret",
"Provider URL": "Provider URL",
"Edit Provider": "Edit Provider"
},
"user":
{
"Edit User": "Edit User",
"Affiliation": "Affiliation",
"Tag": "Tag",
"Is Admin": "Is Admin",
"Is Global Admin": "Is Global Admin"
},
"application":
{
"Edit Application": "Edit Application",
"Enable Password": "Enable Password",
"Face Preview": "Face Preview"
}
}

66
web/src/locales/zh.json Normal file
View File

@ -0,0 +1,66 @@
{
"general":
{
"Home": "主页",
"Organizations": "组织",
"Providers": "第三方",
"Users": "用户",
"Applications": "应用",
"Save": "保存",
"Add": "添加",
"Action": "行为",
"Edit": "修改",
"Delete": "删除",
"Organization": "组织",
"Created Time": "创建时间",
"Name": "名字",
"Display Name": "展示的名字",
"Avatar": "头像",
"Preview": "预览",
"Password Type": "密码类型",
"Password": "密码",
"Email": "电子邮件",
"Phone": "手机",
"Logo": "Logo",
"User containers": "用户容器",
"Users under all organizations": "所有组织里的用户",
"OAuth providers": "OAuth提供方",
"Applications that requires authentication": "需要鉴权的应用"
},
"account":
{
"My Account": "我的账户",
"Login": "登录",
"Logout": "登出",
"Register": "注册"
},
"organization":
{
"Edit Organization": "修改组织",
"Website URL": "网页地址"
},
"provider":
{
"Name": "名字",
"Display Name": "展示的名字",
"Type": "类型",
"Client ID": "Client ID",
"Client Secret": "Client Secret",
"Provider URL": "第三方URL",
"Edit Provider": "修改第三方"
},
"user":
{
"Edit User": "修改用户",
"Affiliation": "联盟",
"Tag": "标签",
"Is Admin": "是管理员",
"Is Global Admin": "是全局管理员"
},
"application":
{
"Edit Application": "修改应用",
"Enable Password": "开启密码",
"Face Preview": "图标预览"
}
}

View File

@ -1156,6 +1156,13 @@
dependencies: dependencies:
regenerator-runtime "^0.13.4" regenerator-runtime "^0.13.4"
"@babel/runtime@^7.12.0", "@babel/runtime@^7.3.1":
version "7.12.13"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.13.tgz#0a21452352b02542db0ffb928ac2d3ca7cb6d66d"
integrity sha512-8+3UMPBrjFa/6TtKi/7sehPKqfAm4g6K+YQjyyFOLUTxzOngcRZTlAVY8sc2CORJYqdHQY8gRPHmn+qo15rCBw==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/template@^7.10.4", "@babel/template@^7.4.0", "@babel/template@^7.8.6": "@babel/template@^7.10.4", "@babel/template@^7.4.0", "@babel/template@^7.8.6":
version "7.10.4" version "7.10.4"
resolved "https://registry.npm.taobao.org/@babel/template/download/@babel/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" resolved "https://registry.npm.taobao.org/@babel/template/download/@babel/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278"
@ -5360,6 +5367,13 @@ html-minifier-terser@^5.0.1:
relateurl "^0.2.7" relateurl "^0.2.7"
terser "^4.6.3" terser "^4.6.3"
html-parse-stringify2@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/html-parse-stringify2/-/html-parse-stringify2-2.0.1.tgz#dc5670b7292ca158b7bc916c9a6735ac8872834a"
integrity sha1-3FZwtyksoVi3vJFsmmc1rIhyg0o=
dependencies:
void-elements "^2.0.1"
html-webpack-plugin@4.0.0-beta.11: html-webpack-plugin@4.0.0-beta.11:
version "4.0.0-beta.11" version "4.0.0-beta.11"
resolved "https://registry.npm.taobao.org/html-webpack-plugin/download/html-webpack-plugin-4.0.0-beta.11.tgz?cache=0&sync_timestamp=1602970416532&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhtml-webpack-plugin%2Fdownload%2Fhtml-webpack-plugin-4.0.0-beta.11.tgz#3059a69144b5aecef97708196ca32f9e68677715" resolved "https://registry.npm.taobao.org/html-webpack-plugin/download/html-webpack-plugin-4.0.0-beta.11.tgz?cache=0&sync_timestamp=1602970416532&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhtml-webpack-plugin%2Fdownload%2Fhtml-webpack-plugin-4.0.0-beta.11.tgz#3059a69144b5aecef97708196ca32f9e68677715"
@ -5459,6 +5473,13 @@ https-browserify@^1.0.0:
resolved "https://registry.npm.taobao.org/https-browserify/download/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" resolved "https://registry.npm.taobao.org/https-browserify/download/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
i18next@^19.8.9:
version "19.8.9"
resolved "https://registry.yarnpkg.com/i18next/-/i18next-19.8.9.tgz#ee3175cf1edcd142b45eca49d23079edaa1e6fcc"
integrity sha512-cyLeJv7tg3MP+bRNbNe9CleiBGfkCeEKr0DKY2MBPYE1vIQGu4mQ3qlTCmW1tcZi9fMqSI4G2mGBO+JwoIQSbQ==
dependencies:
"@babel/runtime" "^7.12.0"
iconv-lite@0.4.24, iconv-lite@^0.4.24: iconv-lite@0.4.24, iconv-lite@^0.4.24:
version "0.4.24" version "0.4.24"
resolved "https://registry.npm.taobao.org/iconv-lite/download/iconv-lite-0.4.24.tgz?cache=0&sync_timestamp=1594184325364&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ficonv-lite%2Fdownload%2Ficonv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" resolved "https://registry.npm.taobao.org/iconv-lite/download/iconv-lite-0.4.24.tgz?cache=0&sync_timestamp=1594184325364&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ficonv-lite%2Fdownload%2Ficonv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@ -9290,6 +9311,14 @@ react-github-corner@^2.5.0:
resolved "https://registry.npm.taobao.org/react-github-corner/download/react-github-corner-2.5.0.tgz#e350d0c69f69c075bc0f1d2a6f1df6ee91da31f2" resolved "https://registry.npm.taobao.org/react-github-corner/download/react-github-corner-2.5.0.tgz#e350d0c69f69c075bc0f1d2a6f1df6ee91da31f2"
integrity sha1-41DQxp9pwHW8Dx0qbx327pHaMfI= integrity sha1-41DQxp9pwHW8Dx0qbx327pHaMfI=
react-i18next@^11.8.7:
version "11.8.7"
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.8.7.tgz#845f31e01e2aa92a3954c374ebcc4bd54df1cace"
integrity sha512-IDK/a73cJN4J2C6yTqmDS/r8iikwKaN283hltqps8UiRaFVTvlSxE85HkamEFD6lULG4hFZExecXJ/hA27DW3Q==
dependencies:
"@babel/runtime" "^7.3.1"
html-parse-stringify2 "2.0.1"
react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4: react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4:
version "16.13.1" version "16.13.1"
resolved "https://registry.npm.taobao.org/react-is/download/react-is-16.13.1.tgz?cache=0&sync_timestamp=1602081887213&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-is%2Fdownload%2Freact-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" resolved "https://registry.npm.taobao.org/react-is/download/react-is-16.13.1.tgz?cache=0&sync_timestamp=1602081887213&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-is%2Fdownload%2Freact-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@ -11117,6 +11146,11 @@ vm-browserify@^1.0.1:
resolved "https://registry.npm.taobao.org/vm-browserify/download/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" resolved "https://registry.npm.taobao.org/vm-browserify/download/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
integrity sha1-eGQcSIuObKkadfUR56OzKobl3aA= integrity sha1-eGQcSIuObKkadfUR56OzKobl3aA=
void-elements@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=
w3c-hr-time@^1.0.1: w3c-hr-time@^1.0.1:
version "1.0.2" version "1.0.2"
resolved "https://registry.npm.taobao.org/w3c-hr-time/download/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" resolved "https://registry.npm.taobao.org/w3c-hr-time/download/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"