feat: i18n support

This commit is contained in:
JustSong 2025-02-01 23:48:05 +08:00
parent 958f2f4ea8
commit ee3ed65356
5 changed files with 151 additions and 45 deletions

View File

@ -7,6 +7,7 @@ import (
"time"
"github.com/gin-gonic/gin"
"github.com/songquanpeng/one-api/common"
"github.com/songquanpeng/one-api/common/config"
)
@ -71,7 +72,7 @@ func memoryRateLimiter(c *gin.Context, maxRequestNum int, duration int64, mark s
}
func rateLimitFactory(maxRequestNum int, duration int64, mark string) func(c *gin.Context) {
if maxRequestNum == 0 {
if maxRequestNum == 0 || config.DebugEnabled {
return func(c *gin.Context) {
c.Next()
}

View File

@ -305,6 +305,7 @@
}
},
"user": {
"title": "User Management",
"edit": {
"title": "Update User Information",
"username": "Username",
@ -337,6 +338,50 @@
},
"messages": {
"create_success": "User account created successfully!"
},
"search": "Search users...",
"table": {
"id": "ID",
"username": "Username",
"group": "Group",
"quota": "Quota",
"role_text": "Role",
"status_text": "Status",
"actions": "Actions",
"remaining_quota": "Remaining Quota",
"used_quota": "Used Quota",
"request_count": "Request Count",
"role_types": {
"normal": "Normal User",
"admin": "Admin",
"super_admin": "Super Admin",
"unknown": "Unknown Role"
},
"status_types": {
"activated": "Activated",
"banned": "Banned",
"unknown": "Unknown Status"
},
"sort": {
"default": "Default Order",
"by_quota": "Sort by Remaining Quota",
"by_used_quota": "Sort by Used Quota",
"by_request_count": "Sort by Request Count"
},
"sort_by": "Sort By"
},
"buttons": {
"add": "Add New User",
"delete": "Delete",
"delete_user": "Delete User",
"enable": "Enable",
"disable": "Disable",
"edit": "Edit",
"promote": "Promote",
"demote": "Demote"
},
"messages": {
"operation_success": "Operation completed successfully!"
}
}
}

View File

@ -305,6 +305,7 @@
}
},
"user": {
"title": "用户管理",
"edit": {
"title": "更新用户信息",
"username": "用户名",
@ -337,6 +338,50 @@
},
"messages": {
"create_success": "用户账户创建成功!"
},
"search": "搜索用户...",
"table": {
"id": "ID",
"username": "用户名",
"group": "分组",
"quota": "额度",
"role_text": "角色",
"status_text": "状态",
"actions": "操作",
"remaining_quota": "剩余额度",
"used_quota": "已用额度",
"request_count": "请求次数",
"role_types": {
"normal": "普通用户",
"admin": "管理员",
"super_admin": "超级管理员",
"unknown": "未知身份"
},
"status_types": {
"activated": "已激活",
"banned": "已封禁",
"unknown": "未知状态"
},
"sort": {
"default": "默认排序",
"by_quota": "按剩余额度排序",
"by_used_quota": "按已用额度排序",
"by_request_count": "按请求次数排序"
},
"sort_by": "排序方式"
},
"buttons": {
"add": "添加新的用户",
"delete": "删除",
"delete_user": "删除用户",
"enable": "启用",
"disable": "禁用",
"edit": "编辑",
"promote": "提升",
"demote": "降级"
},
"messages": {
"operation_success": "操作成功完成!"
}
}
}

View File

@ -20,16 +20,18 @@ import {
renderText,
} from '../helpers/render';
function renderRole(role) {
function renderRole(role, t) {
switch (role) {
case 1:
return <Label>普通用户</Label>;
return <Label>{t('user.table.role_types.normal')}</Label>;
case 10:
return <Label color='yellow'>管理员</Label>;
return <Label color='yellow'>{t('user.table.role_types.admin')}</Label>;
case 100:
return <Label color='orange'>超级管理员</Label>;
return (
<Label color='orange'>{t('user.table.role_types.super_admin')}</Label>
);
default:
return <Label color='red'>未知身份</Label>;
return <Label color='red'>{t('user.table.role_types.unknown')}</Label>;
}
}
@ -85,7 +87,7 @@ const UsersTable = () => {
});
const { success, message } = res.data;
if (success) {
showSuccess('操作成功完成!');
showSuccess(t('user.messages.operation_success'));
let user = res.data.data;
let newUsers = [...users];
let realIdx = (activePage - 1) * ITEMS_PER_PAGE + idx;
@ -105,17 +107,17 @@ const UsersTable = () => {
const renderStatus = (status) => {
switch (status) {
case 1:
return <Label basic>已激活</Label>;
return <Label basic>{t('user.table.status_types.activated')}</Label>;
case 2:
return (
<Label basic color='red'>
已封禁
{t('user.table.status_types.banned')}
</Label>
);
default:
return (
<Label basic color='grey'>
未知状态
{t('user.table.status_types.unknown')}
</Label>
);
}
@ -177,7 +179,7 @@ const UsersTable = () => {
icon='search'
fluid
iconPosition='left'
placeholder='搜索用户的 ID用户名显示名称以及邮箱地址 ...'
placeholder={t('user.search')}
value={searchKeyword}
loading={searching}
onChange={handleKeywordChange}
@ -193,7 +195,7 @@ const UsersTable = () => {
sortUser('id');
}}
>
ID
{t('user.table.id')}
</Table.HeaderCell>
<Table.HeaderCell
style={{ cursor: 'pointer' }}
@ -201,7 +203,7 @@ const UsersTable = () => {
sortUser('username');
}}
>
用户名
{t('user.table.username')}
</Table.HeaderCell>
<Table.HeaderCell
style={{ cursor: 'pointer' }}
@ -209,7 +211,7 @@ const UsersTable = () => {
sortUser('group');
}}
>
分组
{t('user.table.group')}
</Table.HeaderCell>
<Table.HeaderCell
style={{ cursor: 'pointer' }}
@ -217,7 +219,7 @@ const UsersTable = () => {
sortUser('quota');
}}
>
统计信息
{t('user.table.quota')}
</Table.HeaderCell>
<Table.HeaderCell
style={{ cursor: 'pointer' }}
@ -225,7 +227,7 @@ const UsersTable = () => {
sortUser('role');
}}
>
用户角色
{t('user.table.role_text')}
</Table.HeaderCell>
<Table.HeaderCell
style={{ cursor: 'pointer' }}
@ -233,9 +235,9 @@ const UsersTable = () => {
sortUser('status');
}}
>
状态
{t('user.table.status_text')}
</Table.HeaderCell>
<Table.HeaderCell>操作</Table.HeaderCell>
<Table.HeaderCell>{t('user.table.actions')}</Table.HeaderCell>
</Table.Row>
</Table.Header>
@ -267,23 +269,25 @@ const UsersTable = () => {
{/*</Table.Cell>*/}
<Table.Cell>
<Popup
content='剩余额度'
trigger={<Label basic>{renderQuota(user.quota, t)}</Label>}
content={t('user.table.remaining_quota')}
trigger={
<Label basic>{renderQuota(user.quota, t)}</Label>
}
/>
<Popup
content='已用额度'
content={t('user.table.used_quota')}
trigger={
<Label basic>{renderQuota(user.used_quota, t)}</Label>
}
/>
<Popup
content='请求次数'
content={t('user.table.request_count')}
trigger={
<Label basic>{renderNumber(user.request_count)}</Label>
}
/>
</Table.Cell>
<Table.Cell>{renderRole(user.role)}</Table.Cell>
<Table.Cell>{renderRole(user.role, t)}</Table.Cell>
<Table.Cell>{renderStatus(user.status)}</Table.Cell>
<Table.Cell>
<div>
@ -295,7 +299,7 @@ const UsersTable = () => {
}}
disabled={user.role === 100}
>
提升
{t('user.buttons.promote')}
</Button>
<Button
size={'small'}
@ -305,7 +309,7 @@ const UsersTable = () => {
}}
disabled={user.role === 100}
>
降级
{t('user.buttons.demote')}
</Button>
<Popup
trigger={
@ -314,7 +318,7 @@ const UsersTable = () => {
negative
disabled={user.role === 100}
>
删除
{t('user.buttons.delete')}
</Button>
}
on='click'
@ -327,7 +331,7 @@ const UsersTable = () => {
manageUser(user.username, 'delete', idx);
}}
>
删除用户 {user.username}
{t('user.buttons.delete_user')} {user.username}
</Button>
</Popup>
<Button
@ -341,14 +345,16 @@ const UsersTable = () => {
}}
disabled={user.role === 100}
>
{user.status === 1 ? '禁用' : '启用'}
{user.status === 1
? t('user.buttons.disable')
: t('user.buttons.enable')}
</Button>
<Button
size={'small'}
as={Link}
to={'/user/edit/' + user.id}
>
编辑
{t('user.buttons.edit')}
</Button>
</div>
</Table.Cell>
@ -361,22 +367,26 @@ const UsersTable = () => {
<Table.Row>
<Table.HeaderCell colSpan='7'>
<Button size='small' as={Link} to='/user/add' loading={loading}>
添加新的用户
{t('user.buttons.add')}
</Button>
<Dropdown
placeholder='排序方式'
placeholder={t('user.table.sort_by')}
selection
options={[
{ key: '', text: '默认排序', value: '' },
{ key: 'quota', text: '按剩余额度排序', value: 'quota' },
{ key: '', text: t('user.table.sort.default'), value: '' },
{
key: 'quota',
text: t('user.table.sort.by_quota'),
value: 'quota',
},
{
key: 'used_quota',
text: '按已用额度排序',
text: t('user.table.sort.by_used_quota'),
value: 'used_quota',
},
{
key: 'request_count',
text: '按请求次数排序',
text: t('user.table.sort.by_request_count'),
value: 'request_count',
},
]}

View File

@ -1,16 +1,21 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Card } from 'semantic-ui-react';
import UsersTable from '../../components/UsersTable';
const User = () => (
const User = () => {
const { t } = useTranslation();
return (
<div className='dashboard-container'>
<Card fluid className='chart-card'>
<Card.Content>
<Card.Header className='header'>用户管理</Card.Header>
<Card.Header className='header'>{t('user.title')}</Card.Header>
<UsersTable />
</Card.Content>
</Card>
</div>
);
};
export default User;