支持多语言英文

This commit is contained in:
whyour
2023-07-29 18:26:30 +08:00
parent 39bfd39559
commit e7d023a7e0
68 changed files with 2186 additions and 982 deletions
+13 -14
View File
@@ -1,3 +1,4 @@
import intl from 'react-intl-universal';
import React, { useEffect, useState } from 'react';
import { Typography, Input, Form, Button, message, Descriptions } from 'antd';
import styles from './index.less';
@@ -20,27 +21,25 @@ const About = ({ systemInfo }: { systemInfo: SharedContext['systemInfo'] }) => {
src="https://qn.whyour.cn/logo.png"
/>
<div className={styles.right}>
<span className={styles.title}></span>
<span className={styles.title}>{intl.get('青龙')}</span>
<span className={styles.desc}>
python3javaScriptshelltypescript A timed
task management panel that supports typescript, javaScript, python3,
and shell.
{intl.get(
'支持python3、javascript、shell、typescript 的定时任务管理面板',
)}
</span>
<Descriptions>
<Descriptions.Item label="版本" span={3}>
{TVersion[systemInfo.branch]} v{systemInfo.version}
<Descriptions.Item label={intl.get('版本')} span={3}>
{intl.get(TVersion[systemInfo.branch])} v{systemInfo.version}
</Descriptions.Item>
<Descriptions.Item label="更新时间" span={3}>
{dayjs(systemInfo.publishTime * 1000).format(
'YYYY-MM-DD HH:mm',
)}
<Descriptions.Item label={intl.get('更新时间')} span={3}>
{dayjs(systemInfo.publishTime * 1000).format('YYYY-MM-DD HH:mm')}
</Descriptions.Item>
<Descriptions.Item label="更新日志" span={3}>
<Descriptions.Item label={intl.get('更新日志')} span={3}>
<Link
href={`https://qn.whyour.cn/version.yaml?t=${Date.now()}`}
target="_blank"
>
{intl.get('查看')}
</Link>
</Descriptions.Item>
</Descriptions>
@@ -57,13 +56,13 @@ const About = ({ systemInfo }: { systemInfo: SharedContext['systemInfo'] }) => {
target="_blank"
style={{ marginRight: 15 }}
>
Telegram频道
{intl.get('Telegram频道')}
</Link>
<Link
href="https://github.com/whyour/qinglong/issues"
target="_blank"
>
BUG
{intl.get('提交BUG')}
</Link>
</div>
</div>
+14 -7
View File
@@ -1,3 +1,4 @@
import intl from 'react-intl-universal';
import React, { useEffect, useState } from 'react';
import { Modal, message, Input, Form, Select } from 'antd';
import { request } from '@/utils/http';
@@ -29,7 +30,9 @@ const AppModal = ({
);
if (code === 200) {
message.success(app ? '更新应用成功' : '新建应用成功');
message.success(
app ? intl.get('更新应用成功') : intl.get('创建应用成功'),
);
handleCancel(data);
}
setLoading(false);
@@ -44,7 +47,7 @@ const AppModal = ({
return (
<Modal
title={app ? '编辑应用' : '建应用'}
title={app ? intl.get('编辑应用') : intl.get('建应用')}
open={visible}
forceRender
centered
@@ -70,22 +73,26 @@ const AppModal = ({
>
<Form.Item
name="name"
label="名称"
label={intl.get('名称')}
rules={[
{
validator: (_, value) =>
['system'].includes(value)
? Promise.reject(new Error('名称不能为保留关键字'))
? Promise.reject(new Error(intl.get('名称不能为保留关键字')))
: Promise.resolve(),
},
]}
>
<Input placeholder="请输入应用名称" />
<Input placeholder={intl.get('请输入应用名称')} />
</Form.Item>
<Form.Item name="scopes" label="权限" rules={[{ required: true }]}>
<Form.Item
name="scopes"
label={intl.get('权限')}
rules={[{ required: true }]}
>
<Select
mode="multiple"
placeholder="请选择模块权限"
placeholder={intl.get('请选择模块权限')}
allowClear
style={{ width: '100%' }}
>
+18 -15
View File
@@ -1,3 +1,4 @@
import intl from 'react-intl-universal';
import React, { useEffect, useState, useRef } from 'react';
import { Statistic, Modal, Tag, Button, Spin, message } from 'antd';
import { request } from '@/utils/http';
@@ -38,16 +39,17 @@ const CheckUpdate = ({ socketMessage, systemInfo }: any) => {
const showForceUpdateModal = (data: any) => {
Modal.confirm({
width: 500,
title: '更新',
title: intl.get('更新'),
content: (
<>
<div></div>
<div>{intl.get('已经是最新版了!')}</div>
<div style={{ fontSize: 12, fontWeight: 400, marginTop: 5 }}>
{data.lastVersion}
{intl.get('青龙')} {data.lastVersion}{' '}
{intl.get('是目前检测到的最新可用版本了。')}
</div>
</>
),
okText: '重新下载',
okText: intl.get('重新下载'),
onOk() {
showUpdatingModal();
request
@@ -66,9 +68,10 @@ const CheckUpdate = ({ socketMessage, systemInfo }: any) => {
width: 500,
title: (
<>
<div></div>
<div>{intl.get('更新可用')}</div>
<div style={{ fontSize: 12, fontWeight: 400, marginTop: 5 }}>
{lastVersion} 使 {systemInfo.version}
{intl.get('新版本')} {lastVersion}{' '}
{intl.get('可用,你使用的版本为')} {systemInfo.version}
</div>
</>
),
@@ -82,8 +85,8 @@ const CheckUpdate = ({ socketMessage, systemInfo }: any) => {
{lastLog}
</pre>
),
okText: '下载更新',
cancelText: '以后再说',
okText: intl.get('下载更新'),
cancelText: intl.get('以后再说'),
onOk() {
showUpdatingModal();
request
@@ -104,7 +107,7 @@ const CheckUpdate = ({ socketMessage, systemInfo }: any) => {
closable: false,
keyboard: false,
okButtonProps: { disabled: true },
title: '下载更新中...',
title: intl.get('下载更新中...'),
centered: true,
content: (
<pre
@@ -123,10 +126,10 @@ const CheckUpdate = ({ socketMessage, systemInfo }: any) => {
Modal.confirm({
width: 600,
maskClosable: false,
title: '确认重启',
title: intl.get('确认重启'),
centered: true,
content: '系统安装包下载成功,确认重启',
okText: '重启',
content: intl.get('系统安装包下载成功,确认重启'),
okText: intl.get('重启'),
onOk() {
request
.put(`${config.apiPrefix}system/reload`, { type: 'system' })
@@ -134,13 +137,13 @@ const CheckUpdate = ({ socketMessage, systemInfo }: any) => {
message.success({
content: (
<span>
{intl.get('系统将在')}
<Countdown
className="inline-countdown"
format="ss"
value={Date.now() + 1000 * 30}
/>
{intl.get('秒后自动刷新')}
</span>
),
duration: 30,
@@ -216,7 +219,7 @@ const CheckUpdate = ({ socketMessage, systemInfo }: any) => {
return (
<>
<Button type="primary" onClick={checkUpgrade}>
{intl.get('检查更新')}
</Button>
</>
);
+24 -21
View File
@@ -1,3 +1,4 @@
import intl from 'react-intl-universal';
import React, { useState, useEffect } from 'react';
import {
Button,
@@ -47,7 +48,7 @@ const Setting = () => {
} = useOutletContext<SharedContext>();
const columns = [
{
title: '名称',
title: intl.get('名称'),
dataIndex: 'name',
key: 'name',
},
@@ -68,7 +69,7 @@ const Setting = () => {
},
},
{
title: '权限',
title: intl.get('权限'),
dataIndex: 'scopes',
key: 'scopes',
width: '40%',
@@ -79,23 +80,23 @@ const Setting = () => {
},
},
{
title: '操作',
title: intl.get('操作'),
key: 'action',
render: (text: string, record: any, index: number) => {
const isPc = !isPhone;
return (
<Space size="middle" style={{ paddingLeft: 8 }}>
<Tooltip title={isPc ? '编辑' : ''}>
<Tooltip title={isPc ? intl.get('编辑') : ''}>
<a onClick={() => editApp(record, index)}>
<EditOutlined />
</a>
</Tooltip>
<Tooltip title={isPc ? '重置secret' : ''}>
<Tooltip title={isPc ? intl.get('重置secret') : ''}>
<a onClick={() => resetSecret(record, index)}>
<ReloadOutlined />
</a>
</Tooltip>
<Tooltip title={isPc ? '删除' : ''}>
<Tooltip title={isPc ? intl.get('删除') : ''}>
<a onClick={() => deleteApp(record, index)}>
<DeleteOutlined />
</a>
@@ -138,14 +139,14 @@ const Setting = () => {
const deleteApp = (record: any, index: number) => {
Modal.confirm({
title: '确认删除',
title: intl.get('确认删除'),
content: (
<>
{' '}
{intl.get('确认删除应用')}{' '}
<Text style={{ wordBreak: 'break-all' }} type="warning">
{record.name}
</Text>{' '}
{intl.get('吗')}
</>
),
onOk() {
@@ -168,16 +169,18 @@ const Setting = () => {
const resetSecret = (record: any, index: number) => {
Modal.confirm({
title: '确认重置',
title: intl.get('确认重置'),
content: (
<>
{' '}
{intl.get('确认重置应用')}{' '}
<Text style={{ wordBreak: 'break-all' }} type="warning">
{record.name}
</Text>{' '}
Secret吗
{intl.get('的Secret吗')}
<br />
<Text type="secondary">Secret会让当前应用所有token失效</Text>
<Text type="secondary">
{intl.get('重置Secret会让当前应用所有token失效')}
</Text>
</>
),
onOk() {
@@ -262,7 +265,7 @@ const Setting = () => {
return (
<PageContainer
className="ql-container-wrapper ql-container-wrapper-has-tab ql-setting-container"
title="系统设置"
title={intl.get('系统设置')}
header={{
style: headerStyle,
}}
@@ -270,7 +273,7 @@ const Setting = () => {
tabActiveKey === 'app'
? [
<Button key="2" type="primary" onClick={() => addApp()}>
{intl.get('创建应用')}
</Button>,
]
: []
@@ -286,7 +289,7 @@ const Setting = () => {
? [
{
key: 'security',
label: '安全设置',
label: intl.get('安全设置'),
children: (
<SecuritySettings user={user} userChange={reloadUser} />
),
@@ -295,7 +298,7 @@ const Setting = () => {
: []),
{
key: 'app',
label: '应用设置',
label: intl.get('应用设置'),
children: (
<Table
columns={columns}
@@ -310,17 +313,17 @@ const Setting = () => {
},
{
key: 'notification',
label: '通知设置',
label: intl.get('通知设置'),
children: <NotificationSetting data={notificationInfo} />,
},
{
key: 'login',
label: '登录日志',
label: intl.get('登录日志'),
children: <LoginLog data={loginLogData} />,
},
{
key: 'other',
label: '其他设置',
label: intl.get('其他设置'),
children: (
<Other
reloadTheme={reloadTheme}
@@ -331,7 +334,7 @@ const Setting = () => {
},
{
key: 'about',
label: '关于',
label: intl.get('关于'),
children: <About systemInfo={systemInfo} />,
},
]}
+12 -8
View File
@@ -1,3 +1,4 @@
import intl from 'react-intl-universal';
import React, { useEffect, useState } from 'react';
import { Typography, Table, Tag, Button, Spin, message } from 'antd';
import { request } from '@/utils/http';
@@ -17,45 +18,48 @@ enum LoginStatusColor {
const columns = [
{
title: '序号',
width: 50,
title: intl.get('序号'),
width: 40,
render: (text: string, record: any, index: number) => {
return index + 1;
},
},
{
title: '登录时间',
title: intl.get('登录时间'),
dataIndex: 'timestamp',
key: 'timestamp',
width: 120,
render: (text: string, record: any) => {
return new Date(record.timestamp).toLocaleString();
},
},
{
title: '登录地址',
title: intl.get('登录地址'),
dataIndex: 'address',
width: 120,
key: 'address',
},
{
title: '登录IP',
title: intl.get('登录IP'),
dataIndex: 'ip',
width: 100,
key: 'ip',
},
{
title: '登录设备',
title: intl.get('登录设备'),
dataIndex: 'platform',
key: 'platform',
width: 80,
},
{
title: '登录状态',
title: intl.get('登录状态'),
dataIndex: 'status',
key: 'status',
width: 80,
render: (text: string, record: any) => {
return (
<Tag color={LoginStatusColor[record.status]} style={{ marginRight: 0 }}>
{LoginStatus[record.status]}
{intl.get(LoginStatus[record.status])}
</Tag>
);
},
+6 -3
View File
@@ -1,3 +1,4 @@
import intl from 'react-intl-universal';
import React, { useEffect, useState } from 'react';
import { Typography, Input, Form, Button, Select, message } from 'antd';
import { request } from '@/utils/http';
@@ -22,7 +23,9 @@ const NotificationSetting = ({ data }: any) => {
.put(`${config.apiPrefix}user/notification`, values)
.then(({ code, data }) => {
if (code === 200) {
message.success(values.type ? '通知发送成功' : '通知关闭成功');
message.success(
values.type ? intl.get('通知发送成功') : intl.get('通知关闭成功'),
);
}
})
.catch((error: any) => {
@@ -48,7 +51,7 @@ const NotificationSetting = ({ data }: any) => {
<div>
<Form onFinish={handleOk} form={form} layout="vertical">
<Form.Item
label="通知方式"
label={intl.get('通知方式')}
name="type"
rules={[{ required: true }]}
style={{ maxWidth: 400 }}
@@ -92,7 +95,7 @@ const NotificationSetting = ({ data }: any) => {
</Form.Item>
))}
<Button type="primary" htmlType="submit" disabled={loading}>
{loading ? '测试中...' : '保存'}
{loading ? intl.get('测试中...') : intl.get('保存')}
</Button>
</Form>
</div>
+27 -22
View File
@@ -1,3 +1,4 @@
import intl from 'react-intl-universal';
import React, { useState, useEffect, useRef } from 'react';
import {
Button,
@@ -22,9 +23,9 @@ import Countdown from 'antd/lib/statistic/Countdown';
import useProgress from './progress';
const optionsWithDisabled = [
{ label: '亮色', value: 'light' },
{ label: '暗色', value: 'dark' },
{ label: '跟随系统', value: 'auto' },
{ label: intl.get('亮色'), value: 'light' },
{ label: intl.get('暗色'), value: 'dark' },
{ label: intl.get('跟随系统'), value: 'auto' },
];
const Other = ({
@@ -121,10 +122,10 @@ const Other = ({
Modal.confirm({
width: 600,
maskClosable: false,
title: '确认重启',
title: intl.get('确认重启'),
centered: true,
content: '备份数据上传成功,确认覆盖数据',
okText: '重启',
content: intl.get('备份数据上传成功,确认覆盖数据'),
okText: intl.get('重启'),
onOk() {
request
.put(`${config.apiPrefix}system/reload`, { type: 'data' })
@@ -132,13 +133,13 @@ const Other = ({
message.success({
content: (
<span>
{intl.get('系统将在')}
<Countdown
className="inline-countdown"
format="ss"
value={Date.now() + 1000 * 30}
/>
{intl.get('秒后自动刷新')}
</span>
),
duration: 30,
@@ -160,7 +161,11 @@ const Other = ({
return (
<Form layout="vertical" form={form}>
<Form.Item label="主题设置" name="theme" initialValue={defaultTheme}>
<Form.Item
label={intl.get('主题设置')}
name="theme"
initialValue={defaultTheme}
>
<Radio.Group
options={optionsWithDisabled}
onChange={themeChange}
@@ -170,15 +175,15 @@ const Other = ({
/>
</Form.Item>
<Form.Item
label="日志删除频率"
label={intl.get('日志删除频率')}
name="frequency"
tooltip="每x天自动删除x天以前的日志"
tooltip={intl.get('每x天自动删除x天以前的日志')}
>
<Input.Group compact>
<InputNumber
addonBefore="每"
addonAfter="天"
style={{ width: 142 }}
addonBefore={intl.get('每')}
addonAfter={intl.get('天')}
style={{ width: 180 }}
min={0}
value={systemConfig?.logRemoveFrequency}
onChange={(value) => {
@@ -186,14 +191,14 @@ const Other = ({
}}
/>
<Button type="primary" onClick={updateSystemConfig}>
{intl.get('确认')}
</Button>
</Input.Group>
</Form.Item>
<Form.Item label="定时任务并发数" name="frequency">
<Form.Item label={intl.get('定时任务并发数')} name="frequency">
<Input.Group compact>
<InputNumber
style={{ width: 142 }}
style={{ width: 150 }}
min={1}
value={systemConfig?.cronConcurrency}
onChange={(value) => {
@@ -201,13 +206,13 @@ const Other = ({
}}
/>
<Button type="primary" onClick={updateSystemConfig}>
{intl.get('确认')}
</Button>
</Input.Group>
</Form.Item>
<Form.Item label="数据备份还原" name="frequency">
<Form.Item label={intl.get('数据备份还原')} name="frequency">
<Button type="primary" onClick={exportData} loading={exportLoading}>
{exportLoading ? '生成数据中...' : '备份'}
{exportLoading ? intl.get('生成数据中...') : intl.get('备份')}
</Button>
<Upload
method="put"
@@ -228,11 +233,11 @@ const Other = ({
}}
>
<Button icon={<UploadOutlined />} style={{ marginLeft: 8 }}>
{intl.get('还原数据')}
</Button>
</Upload>
</Form.Item>
<Form.Item label="检查更新" name="update">
<Form.Item label={intl.get('检查更新')} name="update">
<CheckUpdate systemInfo={systemInfo} socketMessage={socketMessage} />
</Form.Item>
</Form>
+20 -18
View File
@@ -1,3 +1,4 @@
import intl from 'react-intl-universal';
import React, { useEffect, useState } from 'react';
import { Typography, Input, Form, Button, message, Avatar, Upload } from 'antd';
import { request } from '@/utils/http';
@@ -110,8 +111,8 @@ const SecuritySettings = ({ user, userChange }: any) => {
<>
{twoFactorInfo ? (
<div>
<Title level={5}></Title>
Google Authenticator
<Title level={5}>{intl.get('第一步')}</Title>
{intl.get('下载两步验证手机应用,比如 Google Authenticator 、')}
<Link
href="https://www.microsoft.com/en-us/security/mobile-authenticator-app"
target="_blank"
@@ -137,9 +138,10 @@ const SecuritySettings = ({ user, userChange }: any) => {
LastPass Authenticator
</Link>
<Title style={{ marginTop: 5 }} level={5}>
{intl.get('第二步')}
</Title>
使 {twoFactorInfo?.secret}
{intl.get('使用手机应用扫描二维码,或者输入秘钥')}{' '}
{twoFactorInfo?.secret}
<div style={{ marginTop: 10 }}>
<QRCode
style={{ border: '1px solid #21262d', borderRadius: 6 }}
@@ -149,9 +151,9 @@ const SecuritySettings = ({ user, userChange }: any) => {
/>
</div>
<Title style={{ marginTop: 5 }} level={5}>
{intl.get('第三步')}
</Title>
6
{intl.get('输入手机应用上的6位数字')}
<Input
style={{ margin: '10px 0 10px 0', display: 'block', maxWidth: 200 }}
value={code}
@@ -159,7 +161,7 @@ const SecuritySettings = ({ user, userChange }: any) => {
placeholder="123456"
/>
<Button type="primary" loading={loading} onClick={completeTowFactor}>
{intl.get('完成设置')}
</Button>
</div>
) : (
@@ -176,35 +178,35 @@ const SecuritySettings = ({ user, userChange }: any) => {
paddingBottom: 4,
}}
>
{intl.get('修改用户名密码')}
</div>
<Form onFinish={handleOk} layout="vertical">
<Form.Item
label="用户名"
label={intl.get('用户名')}
name="username"
rules={[{ required: true }]}
hasFeedback
style={{ maxWidth: 300 }}
>
<Input placeholder="用户名" />
<Input placeholder={intl.get('用户名')} />
</Form.Item>
<Form.Item
label="密码"
label={intl.get('密码')}
name="password"
rules={[
{ required: true },
{
pattern: /^(?!admin$).*$/,
message: '密码不能为admin',
message: intl.get('密码不能为admin'),
},
]}
hasFeedback
style={{ maxWidth: 300 }}
>
<Input type="password" placeholder="密码" />
<Input type="password" placeholder={intl.get('密码')} />
</Form.Item>
<Button type="primary" htmlType="submit">
{intl.get('保存')}
</Button>
</Form>
@@ -217,14 +219,14 @@ const SecuritySettings = ({ user, userChange }: any) => {
marginTop: 16,
}}
>
{intl.get('两步验证')}
</div>
<Button
type="primary"
danger={twoFactorActivated}
onClick={activeOrDeactiveTwoFactor}
>
{twoFactorActivated ? '禁用' : '启用'}
{twoFactorActivated ? intl.get('禁用') : intl.get('启用')}
</Button>
<div
@@ -236,7 +238,7 @@ const SecuritySettings = ({ user, userChange }: any) => {
marginTop: 16,
}}
>
{intl.get('头像')}
</div>
<Avatar size={128} shape="square" icon={<UserOutlined />} src={avatar} />
<ImgCrop rotationSlider>
@@ -252,7 +254,7 @@ const SecuritySettings = ({ user, userChange }: any) => {
}}
>
<Button icon={<UploadOutlined />} style={{ marginLeft: 8 }}>
{intl.get('更换头像')}
</Button>
</Upload>
</ImgCrop>