feat: i18n for token related pages

This commit is contained in:
JustSong
2025-02-01 15:11:07 +08:00
parent 93ce6c4cd7
commit 60f2776795
7 changed files with 385 additions and 185 deletions

View File

@@ -1,4 +1,5 @@
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
Button,
Dropdown,
@@ -21,64 +22,63 @@ import {
import { ITEMS_PER_PAGE } from '../constants';
import { renderQuota } from '../helpers/render';
const COPY_OPTIONS = [
{ key: 'next', text: 'ChatGPT Next Web', value: 'next' },
{ key: 'ama', text: 'BotGem', value: 'ama' },
{ key: 'opencat', text: 'OpenCat', value: 'opencat' },
{ key: 'lobechat', text: 'LobeChat', value: 'lobechat' },
];
const OPEN_LINK_OPTIONS = [
{ key: 'next', text: 'ChatGPT Next Web', value: 'next' },
{ key: 'ama', text: 'BotGem', value: 'ama' },
{ key: 'opencat', text: 'OpenCat', value: 'opencat' },
{ key: 'lobechat', text: 'LobeChat', value: 'lobechat' },
];
function renderTimestamp(timestamp) {
return <>{timestamp2string(timestamp)}</>;
}
function renderStatus(status) {
function renderStatus(status, t) {
switch (status) {
case 1:
return (
<Label basic color='green'>
已启用
{t('token.table.status_enabled')}
</Label>
);
case 2:
return (
<Label basic color='red'>
{' '}
已禁用{' '}
{t('token.table.status_disabled')}
</Label>
);
case 3:
return (
<Label basic color='yellow'>
{' '}
已过期{' '}
{t('token.table.status_expired')}
</Label>
);
case 4:
return (
<Label basic color='grey'>
{' '}
已耗尽{' '}
{t('token.table.status_depleted')}
</Label>
);
default:
return (
<Label basic color='black'>
{' '}
未知状态{' '}
{t('token.table.status_unknown')}
</Label>
);
}
}
const TokensTable = () => {
const { t } = useTranslation();
const COPY_OPTIONS = [
{ key: 'raw', text: t('token.copy_options.raw'), value: '' },
{ key: 'next', text: t('token.copy_options.next'), value: 'next' },
{ key: 'ama', text: t('token.copy_options.ama'), value: 'ama' },
{ key: 'opencat', text: t('token.copy_options.opencat'), value: 'opencat' },
{ key: 'lobe', text: t('token.copy_options.lobe'), value: 'lobechat' }
];
const OPEN_LINK_OPTIONS = [
{ key: 'next', text: t('token.copy_options.next'), value: 'next' },
{ key: 'ama', text: t('token.copy_options.ama'), value: 'ama' },
{ key: 'opencat', text: t('token.copy_options.opencat'), value: 'opencat' },
{ key: 'lobe', text: t('token.copy_options.lobe'), value: 'lobechat' }
];
const [tokens, setTokens] = useState([]);
const [loading, setLoading] = useState(true);
const [activePage, setActivePage] = useState(1);
@@ -135,8 +135,7 @@ const TokensTable = () => {
let nextUrl;
if (nextLink) {
nextUrl =
nextLink + `/#/?settings={"key":"sk-${key}","url":"${serverAddress}"}`;
nextUrl = nextLink + `/#/?settings={"key":"sk-${key}","url":"${serverAddress}"}`;
} else {
nextUrl = `https://app.nextchat.dev/#/?settings={"key":"sk-${key}","url":"${serverAddress}"}`;
}
@@ -153,17 +152,15 @@ const TokensTable = () => {
url = nextUrl;
break;
case 'lobechat':
url =
nextLink +
`/?settings={"keyVaults":{"openai":{"apiKey":"sk-${key}","baseURL":"${serverAddress}/v1"}}}`;
url = nextLink + `/?settings={"keyVaults":{"openai":{"apiKey":"sk-${key}","baseURL":"${serverAddress}/v1"}}}`;
break;
default:
url = `sk-${key}`;
}
if (await copy(url)) {
showSuccess('已复制到剪贴板!');
showSuccess(t('token.messages.copy_success'));
} else {
showWarning('无法复制到剪贴板,请手动复制,已将令牌填入搜索框。');
showWarning(t('token.messages.copy_failed'));
setSearchKeyword(url);
}
};
@@ -237,7 +234,7 @@ const TokensTable = () => {
}
const { success, message } = res.data;
if (success) {
showSuccess('操作成功完成!');
showSuccess(t('token.messages.operation_success'));
let token = res.data.data;
let newTokens = [...tokens];
let realIdx = (activePage - 1) * ITEMS_PER_PAGE + idx;
@@ -308,7 +305,7 @@ const TokensTable = () => {
icon='search'
fluid
iconPosition='left'
placeholder='搜索令牌的名称 ...'
placeholder={t('token.search')}
value={searchKeyword}
loading={searching}
onChange={handleKeywordChange}
@@ -324,7 +321,7 @@ const TokensTable = () => {
sortToken('name');
}}
>
名称
{t('token.table.name')}
</Table.HeaderCell>
<Table.HeaderCell
style={{ cursor: 'pointer' }}
@@ -332,7 +329,7 @@ const TokensTable = () => {
sortToken('status');
}}
>
状态
{t('token.table.status')}
</Table.HeaderCell>
<Table.HeaderCell
style={{ cursor: 'pointer' }}
@@ -340,7 +337,7 @@ const TokensTable = () => {
sortToken('used_quota');
}}
>
已用额度
{t('token.table.used_quota')}
</Table.HeaderCell>
<Table.HeaderCell
style={{ cursor: 'pointer' }}
@@ -348,7 +345,7 @@ const TokensTable = () => {
sortToken('remain_quota');
}}
>
剩余额度
{t('token.table.remain_quota')}
</Table.HeaderCell>
<Table.HeaderCell
style={{ cursor: 'pointer' }}
@@ -356,7 +353,7 @@ const TokensTable = () => {
sortToken('created_time');
}}
>
创建时间
{t('token.table.created_time')}
</Table.HeaderCell>
<Table.HeaderCell
style={{ cursor: 'pointer' }}
@@ -364,9 +361,9 @@ const TokensTable = () => {
sortToken('expired_time');
}}
>
过期时间
{t('token.table.expired_time')}
</Table.HeaderCell>
<Table.HeaderCell>操作</Table.HeaderCell>
<Table.HeaderCell>{t('token.table.actions')}</Table.HeaderCell>
</Table.Row>
</Table.Header>
@@ -378,20 +375,37 @@ const TokensTable = () => {
)
.map((token, idx) => {
if (token.deleted) return <></>;
const copyOptionsWithHandlers = COPY_OPTIONS.map(option => ({
...option,
onClick: async () => {
await onCopy(option.value, token.key);
}
}));
const openLinkOptionsWithHandlers = OPEN_LINK_OPTIONS.map(option => ({
...option,
onClick: async () => {
await onOpenLink(option.value, token.key);
}
}));
return (
<Table.Row key={token.id}>
<Table.Cell>{token.name ? token.name : '无'}</Table.Cell>
<Table.Cell>{renderStatus(token.status)}</Table.Cell>
<Table.Cell>{renderQuota(token.used_quota)}</Table.Cell>
<Table.Cell>
{token.name ? token.name : t('token.table.no_name')}
</Table.Cell>
<Table.Cell>{renderStatus(token.status, t)}</Table.Cell>
<Table.Cell>{renderQuota(token.used_quota, t)}</Table.Cell>
<Table.Cell>
{token.unlimited_quota
? '无限制'
: renderQuota(token.remain_quota, 2)}
? t('token.table.unlimited')
: renderQuota(token.remain_quota, t, 2)}
</Table.Cell>
<Table.Cell>{renderTimestamp(token.created_time)}</Table.Cell>
<Table.Cell>
{token.expired_time === -1
? '永不过期'
? t('token.table.never_expire')
: renderTimestamp(token.expired_time)}
</Table.Cell>
<Table.Cell>
@@ -400,21 +414,14 @@ const TokensTable = () => {
<Button
size={'small'}
positive
onClick={async () => {
await onCopy('', token.key);
}}
onClick={async () => await onCopy('', token.key)}
>
复制
{t('token.buttons.copy')}
</Button>
<Dropdown
className='button icon'
floating
options={COPY_OPTIONS.map((option) => ({
...option,
onClick: async () => {
await onCopy(option.value, token.key);
},
}))}
options={copyOptionsWithHandlers}
trigger={<></>}
/>
</Button.Group>{' '}
@@ -422,28 +429,21 @@ const TokensTable = () => {
<Button
size={'small'}
positive
onClick={() => {
onOpenLink('', token.key);
}}
onClick={() => onOpenLink('', token.key)}
>
聊天
{t('token.buttons.chat')}
</Button>
<Dropdown
className='button icon'
floating
options={OPEN_LINK_OPTIONS.map((option) => ({
...option,
onClick: async () => {
await onOpenLink(option.value, token.key);
},
}))}
options={openLinkOptionsWithHandlers}
trigger={<></>}
/>
</Button.Group>{' '}
<Popup
trigger={
<Button size='small' negative>
删除
{t('token.buttons.delete')}
</Button>
}
on='click'
@@ -456,7 +456,7 @@ const TokensTable = () => {
manageToken(token.id, 'delete', idx);
}}
>
删除令牌 {token.name}
{t('token.buttons.confirm_delete')} {token.name}
</Button>
</Popup>
<Button
@@ -469,14 +469,12 @@ const TokensTable = () => {
);
}}
>
{token.status === 1 ? '禁用' : '启用'}
{token.status === 1
? t('token.buttons.disable')
: t('token.buttons.enable')}
</Button>
<Button
size={'small'}
as={Link}
to={'/token/edit/' + token.id}
>
编辑
<Button size={'small'} as={Link} to={'/token/edit/' + token.id}>
{t('token.buttons.edit')}
</Button>
</div>
</Table.Cell>
@@ -489,24 +487,24 @@ const TokensTable = () => {
<Table.Row>
<Table.HeaderCell colSpan='7'>
<Button size='small' as={Link} to='/token/add' loading={loading}>
添加新的令牌
{t('token.buttons.add')}
</Button>
<Button size='small' onClick={refresh} loading={loading}>
刷新
{t('token.buttons.refresh')}
</Button>
<Dropdown
placeholder='排序方式'
placeholder={t('token.sort.placeholder')}
selection
options={[
{ key: '', text: '默认排序', value: '' },
{ key: '', text: t('token.sort.default'), value: '' },
{
key: 'remain_quota',
text: '按剩余额度排序',
text: t('token.sort.by_remain'),
value: 'remain_quota',
},
{
key: 'used_quota',
text: '按已用额度排序',
text: t('token.sort.by_used'),
value: 'used_quota',
},
]}