From e301121c214664cb7bbbb3fdd2e90cb3cdbd2b20 Mon Sep 17 00:00:00 2001 From: RobotHuang <1183598761@qq.com> Date: Fri, 19 Feb 2021 23:23:59 +0800 Subject: [PATCH] feat: support i18n Signed-off-by: RobotHuang <1183598761@qq.com> --- conf/app.conf | 2 +- web/package.json | 4 +- web/src/App.js | 23 +++++++----- web/src/ApplicationEditPage.js | 21 ++++++----- web/src/ApplicationListPage.js | 21 ++++++----- web/src/OrganizationEditPage.js | 13 ++++--- web/src/OrganizationListPage.js | 19 +++++----- web/src/ProviderEditPage.js | 19 +++++----- web/src/ProviderListPage.js | 23 ++++++------ web/src/SelectLanguageBox.js | 38 +++++++++++++++++++ web/src/Setting.js | 16 ++++++++ web/src/UserEditPage.js | 33 +++++++++-------- web/src/UserListPage.js | 31 ++++++++-------- web/src/basic/HomePage.js | 9 +++-- web/src/i18n.js | 39 +++++++++++++++++++ web/src/locales/en.json | 64 ++++++++++++++++++++++++++++++++ web/src/locales/zh.json | 66 +++++++++++++++++++++++++++++++++ web/yarn.lock | 34 +++++++++++++++++ 18 files changed, 373 insertions(+), 102 deletions(-) create mode 100644 web/src/SelectLanguageBox.js create mode 100644 web/src/i18n.js create mode 100644 web/src/locales/en.json create mode 100644 web/src/locales/zh.json diff --git a/conf/app.conf b/conf/app.conf index 999319d8..f54b7136 100644 --- a/conf/app.conf +++ b/conf/app.conf @@ -3,7 +3,7 @@ httpport = 8000 runmode = dev SessionOn = true copyrequestbody = true -dataSourceName = root:123@tcp(localhost:3306)/ +dataSourceName = root:123456@tcp(localhost:3306)/ dbName = casdoor AuthState = "casdoor" UseProxy = false \ No newline at end of file diff --git a/web/package.json b/web/package.json index 6144d780..252798a8 100644 --- a/web/package.json +++ b/web/package.json @@ -8,16 +8,18 @@ "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", "antd": "^4.7.2", + "i18next": "^19.8.9", "moment": "^2.29.1", "react": "^16.14.0", "react-device-detect": "^1.14.0", "react-dom": "^16.14.0", "react-github-corner": "^2.5.0", + "react-i18next": "^11.8.7", "react-router-dom": "^5.2.0", "react-scripts": "3.4.3" }, "scripts": { - "start": "set PORT=7001 && react-scripts start", + "start": "PORT=7001 && export PORT=7001 && react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" diff --git a/web/src/App.js b/web/src/App.js index 2f84d9e5..78e29452 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -35,6 +35,8 @@ import Face from "./auth/Face"; import LoginPage from "./auth/LoginPage"; import * as AuthBackend from "./auth/AuthBackend"; import AuthCallback from "./auth/AuthCallback"; +import SelectLanguageBox from './SelectLanguageBox'; +import i18next from 'i18next'; const { Header, Footer } = Layout; @@ -56,6 +58,7 @@ class App extends Component { } componentWillMount() { + Setting.setLanguage(); this.updateMenuKey(); this.getAccount(); } @@ -127,11 +130,11 @@ class App extends Component { - My Account + {i18next.t("account:My Account")} - Logout + {i18next.t("account:Logout")} ); @@ -162,14 +165,14 @@ class App extends Component { res.push( - Register + {i18next.t("account:Register")} ); res.push( - Login + {i18next.t("account:Login")} ); @@ -190,7 +193,7 @@ class App extends Component { res.push( - Home + {i18next.t("general:Home")} ); @@ -199,33 +202,32 @@ class App extends Component { res.push( - Organizations + {i18next.t("general:Organizations")} ); res.push( - Users + {i18next.t("general:Users")} ); res.push( - Providers + {i18next.t("general:Providers")} ); res.push( - Applications + {i18next.t("general:Applications")} ); } - return res; } @@ -305,6 +307,7 @@ class App extends Component { textAlign: 'center', } }> + Made with ❤️ by Casbin ) diff --git a/web/src/ApplicationEditPage.js b/web/src/ApplicationEditPage.js index 9e3dd6ec..b154a46a 100644 --- a/web/src/ApplicationEditPage.js +++ b/web/src/ApplicationEditPage.js @@ -20,6 +20,7 @@ import * as Setting from "./Setting"; import * as ProviderBackend from "./backend/ProviderBackend"; import * as OrganizationBackend from "./backend/OrganizationBackend"; import Face from "./auth/Face"; +import i18next from "i18next"; const { Option } = Select; @@ -89,13 +90,13 @@ class ApplicationEditPage extends React.Component { return ( - Edit Application     - + {i18next.t("application:Edit Application")}     + } style={{marginLeft: '5px'}} type="inner"> - Name: + {i18next.t("general:Name")}: { @@ -105,7 +106,7 @@ class ApplicationEditPage extends React.Component { - Display Name: + {i18next.t("general:Display Name")}: { @@ -130,7 +131,7 @@ class ApplicationEditPage extends React.Component { - Preview: + {i18next.t("general:Preview")}: @@ -142,7 +143,7 @@ class ApplicationEditPage extends React.Component { - Organization: + {i18next.t("general:Organization")}: - Face Preview: + {i18next.t("application:Face Preview")}: @@ -237,7 +238,7 @@ class ApplicationEditPage extends React.Component { - + diff --git a/web/src/ApplicationListPage.js b/web/src/ApplicationListPage.js index 05b69783..fb0a04ed 100644 --- a/web/src/ApplicationListPage.js +++ b/web/src/ApplicationListPage.js @@ -18,6 +18,7 @@ import {Button, Col, Popconfirm, Row, Table} from 'antd'; import moment from "moment"; import * as Setting from "./Setting"; import * as ApplicationBackend from "./backend/ApplicationBackend"; +import i18next from "i18next"; class ApplicationListPage extends React.Component { constructor(props) { @@ -85,7 +86,7 @@ class ApplicationListPage extends React.Component { renderTable(applications) { const columns = [ { - title: 'Name', + title: i18next.t("general:Name"), dataIndex: 'name', key: 'name', width: '150px', @@ -99,7 +100,7 @@ class ApplicationListPage extends React.Component { } }, { - title: 'Created Time', + title: i18next.t("general:Created Time"), dataIndex: 'createdTime', key: 'createdTime', width: '160px', @@ -109,7 +110,7 @@ class ApplicationListPage extends React.Component { } }, { - title: 'Display Name', + title: i18next.t("general:Display Name"), dataIndex: 'displayName', key: 'displayName', // width: '100px', @@ -129,7 +130,7 @@ class ApplicationListPage extends React.Component { } }, { - title: 'Organization', + title: i18next.t("general:Organization"), dataIndex: 'organization', key: 'organization', width: '200px', @@ -143,7 +144,7 @@ class ApplicationListPage extends React.Component { } }, { - title: 'Providers', + title: i18next.t("general:Providers"), dataIndex: 'providers', key: 'providers', width: '200px', @@ -157,19 +158,19 @@ class ApplicationListPage extends React.Component { } }, { - title: 'Action', + title: i18next.t("general:Action"), dataIndex: '', key: 'op', width: '170px', render: (text, record, index) => { return (
- + this.deleteApplication(index)} > - +
) @@ -182,8 +183,8 @@ class ApplicationListPage extends React.Component { (
- Applications     - + {i18next.t("general:Applications")}     +
)} loading={applications === null} diff --git a/web/src/OrganizationEditPage.js b/web/src/OrganizationEditPage.js index 70226e80..28be09e4 100644 --- a/web/src/OrganizationEditPage.js +++ b/web/src/OrganizationEditPage.js @@ -16,6 +16,7 @@ import React from "react"; import {Button, Card, Col, Input, Row} from 'antd'; import * as OrganizationBackend from "./backend/OrganizationBackend"; import * as Setting from "./Setting"; +import i18next from "i18next"; class OrganizationEditPage extends React.Component { constructor(props) { @@ -61,13 +62,13 @@ class OrganizationEditPage extends React.Component { return ( - Edit Organization     - + {i18next.t("organization:Edit Organization")}     + } style={{marginLeft: '5px'}} type="inner"> - Name: + {i18next.t("general:Name")}: { @@ -77,7 +78,7 @@ class OrganizationEditPage extends React.Component { - Display Name: + {i18next.t("general:Display Name")}: { @@ -87,7 +88,7 @@ class OrganizationEditPage extends React.Component { - Website URL: + {i18next.t("organization:Website URL")}: { @@ -137,7 +138,7 @@ class OrganizationEditPage extends React.Component { - + diff --git a/web/src/OrganizationListPage.js b/web/src/OrganizationListPage.js index 3bd8f804..67a7a432 100644 --- a/web/src/OrganizationListPage.js +++ b/web/src/OrganizationListPage.js @@ -18,6 +18,7 @@ import {Button, Col, Popconfirm, Row, Table} from 'antd'; import moment from "moment"; import * as Setting from "./Setting"; import * as OrganizationBackend from "./backend/OrganizationBackend"; +import i18next from "i18next"; class OrganizationListPage extends React.Component { constructor(props) { @@ -83,7 +84,7 @@ class OrganizationListPage extends React.Component { renderTable(organizations) { const columns = [ { - title: 'Name', + title: i18next.t("general:Name"), dataIndex: 'name', key: 'name', width: '120px', @@ -97,7 +98,7 @@ class OrganizationListPage extends React.Component { } }, { - title: 'Created Time', + title: i18next.t("general:Created Time"), dataIndex: 'createdTime', key: 'createdTime', width: '160px', @@ -107,14 +108,14 @@ class OrganizationListPage extends React.Component { } }, { - title: 'Display Name', + title: i18next.t("general:Display Name"), dataIndex: 'displayName', key: 'displayName', // width: '100px', sorter: (a, b) => a.displayName.localeCompare(b.displayName), }, { - title: 'Website URL', + title: i18next.t("organization:Website URL"), dataIndex: 'websiteUrl', key: 'websiteUrl', width: '300px', @@ -128,19 +129,19 @@ class OrganizationListPage extends React.Component { } }, { - title: 'Action', + title: i18next.t("general:Action"), dataIndex: '', key: 'op', width: '170px', render: (text, record, index) => { return (
- + this.deleteOrganization(index)} > - +
) @@ -153,8 +154,8 @@ class OrganizationListPage extends React.Component {
(
- Organizations     - + {i18next.t("general:Organizations")}     +
)} loading={organizations === null} diff --git a/web/src/ProviderEditPage.js b/web/src/ProviderEditPage.js index f0a5e043..682674c3 100644 --- a/web/src/ProviderEditPage.js +++ b/web/src/ProviderEditPage.js @@ -16,6 +16,7 @@ import React from "react"; import {Button, Card, Col, Input, Row, Select} from 'antd'; import * as ProviderBackend from "./backend/ProviderBackend"; import * as Setting from "./Setting"; +import i18next from "i18next"; const { Option } = Select; @@ -63,13 +64,13 @@ class ProviderEditPage extends React.Component { return ( - Edit Provider     - + {i18next.t("provider:Edit Provider")}     + } style={{marginLeft: '5px'}} type="inner"> - Name: + {i18next.t("general:Name")}: { @@ -79,7 +80,7 @@ class ProviderEditPage extends React.Component { - Display Name: + {i18next.t("general:Display Name")}: { @@ -89,7 +90,7 @@ class ProviderEditPage extends React.Component { - Type: + {i18next.t("provider:Type")}: { @@ -116,7 +117,7 @@ class ProviderEditPage extends React.Component { - Client Secret: + {i18next.t("provider:Client Secret")}: { @@ -126,7 +127,7 @@ class ProviderEditPage extends React.Component { - Provider URL: + {i18next.t("provider:Provider URL")}: { @@ -176,7 +177,7 @@ class ProviderEditPage extends React.Component { - + diff --git a/web/src/ProviderListPage.js b/web/src/ProviderListPage.js index 7fae08e4..f622f219 100644 --- a/web/src/ProviderListPage.js +++ b/web/src/ProviderListPage.js @@ -19,6 +19,7 @@ import moment from "moment"; import * as Setting from "./Setting"; import * as ProviderBackend from "./backend/ProviderBackend"; import * as Provider from "./auth/Provider"; +import i18next from "i18next"; class ProviderListPage extends React.Component { constructor(props) { @@ -87,7 +88,7 @@ class ProviderListPage extends React.Component { renderTable(providers) { const columns = [ { - title: 'Name', + title: i18next.t("general:Name"), dataIndex: 'name', key: 'name', width: '120px', @@ -101,7 +102,7 @@ class ProviderListPage extends React.Component { } }, { - title: 'Created Time', + title: i18next.t("general:Created Time"), dataIndex: 'createdTime', key: 'createdTime', width: '160px', @@ -111,14 +112,14 @@ class ProviderListPage extends React.Component { } }, { - title: 'Display Name', + title: i18next.t("general:Display Name"), dataIndex: 'displayName', key: 'displayName', // width: '100px', sorter: (a, b) => a.displayName.localeCompare(b.displayName), }, { - title: 'Type', + title: i18next.t("provider:Type"), dataIndex: 'type', key: 'type', width: '80px', @@ -130,7 +131,7 @@ class ProviderListPage extends React.Component { } }, { - title: 'Client Id', + title: i18next.t("provider:Client ID"), dataIndex: 'clientId', key: 'clientId', width: '150px', @@ -144,7 +145,7 @@ class ProviderListPage extends React.Component { // sorter: (a, b) => a.clientSecret.localeCompare(b.clientSecret), // }, { - title: 'Provider Url', + title: i18next.t("provider:Provider URL"), dataIndex: 'providerUrl', key: 'providerUrl', width: '150px', @@ -160,19 +161,19 @@ class ProviderListPage extends React.Component { } }, { - title: 'Action', + title: i18next.t("general:Action"), dataIndex: '', key: 'op', width: '170px', render: (text, record, index) => { return (
- + this.deleteProvider(index)} > - +
) @@ -185,8 +186,8 @@ class ProviderListPage extends React.Component {
(
- Providers     - + {i18next.t("general:Providers")}     +
)} loading={providers === null} diff --git a/web/src/SelectLanguageBox.js b/web/src/SelectLanguageBox.js new file mode 100644 index 00000000..69e97ea0 --- /dev/null +++ b/web/src/SelectLanguageBox.js @@ -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 ( +
+ +
+ ) + } +} + +export default SelectLanguageBox; diff --git a/web/src/Setting.js b/web/src/Setting.js index bcda65c6..bbda2333 100644 --- a/web/src/Setting.js +++ b/web/src/Setting.js @@ -15,6 +15,8 @@ import {message} from "antd"; import React from "react"; import {isMobile as isMobileDevice} from "react-device-detect"; +import "./i18n"; +import i18next from "i18next"; export let ServerUrl = ""; @@ -135,3 +137,17 @@ export function getAvatarColor(s) { } 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); +} diff --git a/web/src/UserEditPage.js b/web/src/UserEditPage.js index 0d39cd19..57b0699f 100644 --- a/web/src/UserEditPage.js +++ b/web/src/UserEditPage.js @@ -18,6 +18,7 @@ import * as UserBackend from "./backend/UserBackend"; import * as OrganizationBackend from "./backend/OrganizationBackend"; import * as Setting from "./Setting"; import {LinkOutlined} from "@ant-design/icons"; +import i18next from "i18next"; const { Option } = Select; @@ -77,13 +78,13 @@ class UserEditPage extends React.Component { return ( - Edit User     - + {i18next.t("user:Edit User")}     + } style={{marginLeft: '5px'}} type="inner"> - Organization: + {i18next.t("general:Organization")}: { @@ -113,7 +114,7 @@ class UserEditPage extends React.Component { - Display Name: + {i18next.t("general:Display Name")}: { @@ -123,7 +124,7 @@ class UserEditPage extends React.Component { - Avatar: + {i18next.t("general:Avatar")}: @@ -138,7 +139,7 @@ class UserEditPage extends React.Component { - Preview: + {i18next.t("general:Preview")}: @@ -150,7 +151,7 @@ class UserEditPage extends React.Component { - Password Type: + {i18next.t("general:Password Type")}: { @@ -160,7 +161,7 @@ class UserEditPage extends React.Component { - Password: + {i18next.t("general:Password")}: { @@ -170,7 +171,7 @@ class UserEditPage extends React.Component { - Email: + {i18next.t("general:Email")}: { @@ -180,7 +181,7 @@ class UserEditPage extends React.Component { - Phone: + {i18next.t("general:Phone")}: { @@ -190,7 +191,7 @@ class UserEditPage extends React.Component { - Affiliation: + {i18next.t("user:Affiliation")}: { @@ -200,7 +201,7 @@ class UserEditPage extends React.Component { - Tag: + {i18next.t("user:Tag")}: { @@ -221,7 +222,7 @@ class UserEditPage extends React.Component { - Is Admin: + {i18next.t("user:Is Admin")}: { @@ -231,7 +232,7 @@ class UserEditPage extends React.Component { - Is Global Admin: + {i18next.t("user:Is Global Admin")}: { @@ -289,7 +290,7 @@ class UserEditPage extends React.Component { - + diff --git a/web/src/UserListPage.js b/web/src/UserListPage.js index f39b660d..661a14a3 100644 --- a/web/src/UserListPage.js +++ b/web/src/UserListPage.js @@ -18,6 +18,7 @@ import {Button, Col, Popconfirm, Row, Switch, Table} from 'antd'; import moment from "moment"; import * as Setting from "./Setting"; import * as UserBackend from "./backend/UserBackend"; +import i18next from "i18next"; class UserListPage extends React.Component { constructor(props) { @@ -90,7 +91,7 @@ class UserListPage extends React.Component { renderTable(users) { const columns = [ { - title: 'Organization', + title: i18next.t("general:Organization"), dataIndex: 'owner', key: 'owner', width: '120px', @@ -104,7 +105,7 @@ class UserListPage extends React.Component { } }, { - title: 'Name', + title: i18next.t("general:Name"), dataIndex: 'name', key: 'name', width: '100px', @@ -118,7 +119,7 @@ class UserListPage extends React.Component { } }, { - title: 'Created Time', + title: i18next.t("general:Created Time"), dataIndex: 'createdTime', key: 'createdTime', width: '160px', @@ -142,14 +143,14 @@ class UserListPage extends React.Component { // sorter: (a, b) => a.password.localeCompare(b.password), // }, { - title: 'Display Name', + title: i18next.t("general:Display Name"), dataIndex: 'displayName', key: 'displayName', // width: '100px', sorter: (a, b) => a.displayName.localeCompare(b.displayName), }, { - title: 'Avatar', + title: i18next.t("general:Avatar"), dataIndex: 'avatar', key: 'avatar', width: '100px', @@ -162,7 +163,7 @@ class UserListPage extends React.Component { } }, { - title: 'Email', + title: i18next.t("general:Email"), dataIndex: 'email', key: 'email', width: '160px', @@ -183,21 +184,21 @@ class UserListPage extends React.Component { // sorter: (a, b) => a.phone.localeCompare(b.phone), // }, { - title: 'Affiliation', + title: i18next.t("user:Affiliation"), dataIndex: 'affiliation', key: 'affiliation', width: '120px', sorter: (a, b) => a.affiliation.localeCompare(b.affiliation), }, { - title: 'Tag', + title: i18next.t("user:Tag"), dataIndex: 'tag', key: 'tag', width: '100px', sorter: (a, b) => a.tag.localeCompare(b.tag), }, { - title: 'Is Admin', + title: i18next.t("user:Is Admin"), dataIndex: 'isAdmin', key: 'isAdmin', width: '120px', @@ -209,7 +210,7 @@ class UserListPage extends React.Component { } }, { - title: 'Is Global Admin', + title: i18next.t("user:Is Global Admin"), dataIndex: 'isGlobalAdmin', key: 'isGlobalAdmin', width: '120px', @@ -221,19 +222,19 @@ class UserListPage extends React.Component { } }, { - title: 'Action', + title: i18next.t("general:Action"), dataIndex: '', key: 'op', width: '170px', render: (text, record, index) => { return (
- + this.deleteUser(index)} > - +
) @@ -246,8 +247,8 @@ class UserListPage extends React.Component {
(
- Users     - + {i18next.t("general:Users")}     +
)} loading={users === null} diff --git a/web/src/basic/HomePage.js b/web/src/basic/HomePage.js index aa9e0001..073246f0 100644 --- a/web/src/basic/HomePage.js +++ b/web/src/basic/HomePage.js @@ -16,6 +16,7 @@ import React from "react"; import {Card, Col, Row} from "antd"; import * as Setting from "../Setting"; import SingleCard from "./SingleCard"; +import i18next from "i18next"; class HomePage extends React.Component { constructor(props) { @@ -29,10 +30,10 @@ class HomePage extends React.Component { let items = []; if (Setting.isAdminUser(this.props.account)) { items = [ - {link: "/organizations", name: "Organizations", organizer: "User containers"}, - {link: "/users", name: "Users", organizer: "Users under all organizations"}, - {link: "/providers", name: "Providers", organizer: "OAuth providers"}, - {link: "/applications", name: "Applications", organizer: "Applications that requires authentication"}, + {link: "/organizations", name: i18next.t("general:Organizations"), organizer: i18next.t("general:User containers")}, + {link: "/users", name: i18next.t("general:Users"), organizer: i18next.t("general:Users under all organizations")}, + {link: "/providers", name: i18next.t("general:Providers"), organizer: i18next.t("general:OAuth providers")}, + {link: "/applications", name: i18next.t("general:Applications"), organizer: i18next.t("general:Applications that requires authentication")}, ]; } diff --git a/web/src/i18n.js b/web/src/i18n.js new file mode 100644 index 00000000..f81562ee --- /dev/null +++ b/web/src/i18n.js @@ -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; \ No newline at end of file diff --git a/web/src/locales/en.json b/web/src/locales/en.json new file mode 100644 index 00000000..fc2c4240 --- /dev/null +++ b/web/src/locales/en.json @@ -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" + } +} \ No newline at end of file diff --git a/web/src/locales/zh.json b/web/src/locales/zh.json new file mode 100644 index 00000000..1edb36bf --- /dev/null +++ b/web/src/locales/zh.json @@ -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": "图标预览" + } +} \ No newline at end of file diff --git a/web/yarn.lock b/web/yarn.lock index d1a8488d..a733ec2b 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -1156,6 +1156,13 @@ dependencies: 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": version "7.10.4" 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" 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: 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" @@ -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" 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: 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" @@ -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" 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: 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" @@ -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" 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: version "1.0.2" resolved "https://registry.npm.taobao.org/w3c-hr-time/download/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"