feat: initial i18n support

This commit is contained in:
JustSong
2025-02-01 12:25:58 +08:00
parent bdf312e5dc
commit d9f2df2baf
5 changed files with 424 additions and 202 deletions

View File

@@ -1,4 +1,5 @@
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
Button,
Dropdown,
@@ -31,13 +32,17 @@ function renderTimestamp(timestamp) {
let type2label = undefined;
function renderType(type) {
function renderType(type, t) {
if (!type2label) {
type2label = new Map();
for (let i = 0; i < CHANNEL_OPTIONS.length; i++) {
type2label[CHANNEL_OPTIONS[i].value] = CHANNEL_OPTIONS[i];
}
type2label[0] = { value: 0, text: '未知类型', color: 'grey' };
type2label[0] = {
value: 0,
text: t('channel.table.status_unknown'),
color: 'grey',
};
}
return (
<Label basic color={type2label[type]?.color}>
@@ -46,7 +51,7 @@ function renderType(type) {
);
}
function renderBalance(type, balance) {
function renderBalance(type, balance, t) {
switch (type) {
case 1: // OpenAI
return <span>${balance.toFixed(2)}</span>;
@@ -67,7 +72,7 @@ function renderBalance(type, balance) {
case 44: // SiliconFlow
return <span>¥{balance.toFixed(2)}</span>;
default:
return <span>不支持</span>;
return <span>{t('channel.table.balance_not_supported')}</span>;
}
}
@@ -78,6 +83,7 @@ function isShowDetail() {
const promptID = 'detail';
const ChannelsTable = () => {
const { t } = useTranslation();
const [channels, setChannels] = useState([]);
const [loading, setLoading] = useState(true);
const [activePage, setActivePage] = useState(1);
@@ -207,12 +213,12 @@ const ChannelsTable = () => {
}
};
const renderStatus = (status) => {
const renderStatus = (status, t) => {
switch (status) {
case 1:
return (
<Label basic color='green'>
已启用
{t('channel.table.status_enabled')}
</Label>
);
case 2:
@@ -220,10 +226,10 @@ const ChannelsTable = () => {
<Popup
trigger={
<Label basic color='red'>
已禁用
{t('channel.table.status_disabled')}
</Label>
}
content='本渠道被手动禁用'
content={t('channel.table.status_disabled_tip')}
basic
/>
);
@@ -232,29 +238,29 @@ const ChannelsTable = () => {
<Popup
trigger={
<Label basic color='yellow'>
已禁用
{t('channel.table.status_auto_disabled')}
</Label>
}
content='本渠道被程序自动禁用'
content={t('channel.table.status_auto_disabled_tip')}
basic
/>
);
default:
return (
<Label basic color='grey'>
未知状态
{t('channel.table.status_unknown')}
</Label>
);
}
};
const renderResponseTime = (responseTime) => {
const renderResponseTime = (responseTime, t) => {
let time = responseTime / 1000;
time = time.toFixed(2) + '';
time = time.toFixed(2) + 's';
if (responseTime === 0) {
return (
<Label basic color='grey'>
未测试
{t('channel.table.not_tested')}
</Label>
);
} else if (responseTime <= 1000) {
@@ -320,9 +326,12 @@ const ChannelsTable = () => {
newChannels[realIdx].test_time = Date.now() / 1000;
setChannels(newChannels);
showInfo(
`渠道 ${name} 测试成功,模型 ${model},耗时 ${time.toFixed(
2
)} 秒,模型输出:${message}`
t('channel.messages.test_success', {
name: name,
model: model,
time: time.toFixed(2),
message: message,
})
);
} else {
showError(message);
@@ -338,7 +347,7 @@ const ChannelsTable = () => {
const res = await API.get(`/api/channel/test?scope=${scope}`);
const { success, message } = res.data;
if (success) {
showInfo('已成功开始测试渠道,请刷新页面查看结果。');
showInfo(t('channel.messages.test_all_started'));
} else {
showError(message);
}
@@ -348,7 +357,9 @@ const ChannelsTable = () => {
const res = await API.delete(`/api/channel/disabled`);
const { success, message, data } = res.data;
if (success) {
showSuccess(`已删除所有禁用渠道,共计 ${data}`);
showSuccess(
t('channel.messages.delete_disabled_success', { count: data })
);
await refresh();
} else {
showError(message);
@@ -364,7 +375,7 @@ const ChannelsTable = () => {
newChannels[realIdx].balance = balance;
newChannels[realIdx].balance_updated_time = Date.now() / 1000;
setChannels(newChannels);
showInfo(`渠道 ${name} 余额更新成功!`);
showInfo(t('channel.messages.balance_update_success', { name: name }));
} else {
showError(message);
}
@@ -375,7 +386,7 @@ const ChannelsTable = () => {
const res = await API.get(`/api/channel/update_balance`);
const { success, message } = res.data;
if (success) {
showInfo('已更新完毕所有已启用渠道余额!');
showInfo(t('channel.messages.all_balance_updated'));
} else {
showError(message);
}
@@ -413,7 +424,7 @@ const ChannelsTable = () => {
icon='search'
fluid
iconPosition='left'
placeholder='搜索渠道的 ID名称和密钥 ...'
placeholder={t('channel.search')}
value={searchKeyword}
loading={searching}
onChange={handleKeywordChange}
@@ -426,13 +437,11 @@ const ChannelsTable = () => {
setPromptShown(promptID);
}}
>
OpenAI 渠道已经不再支持通过 key 获取余额因此余额显示为
0对于支持的渠道类型请点击余额进行刷新
{t('channel.balance_notice')}
<br />
渠道测试仅支持 chat 模型优先使用
gpt-3.5-turbo如果该模型不可用则使用你所配置的模型列表中的第一个模型
{t('channel.test_notice')}
<br />
点击下方详情按钮可以显示余额以及设置额外的测试模型
{t('channel.detail_notice')}
</Message>
)}
<Table basic={'very'} compact size='small'>
@@ -444,7 +453,7 @@ const ChannelsTable = () => {
sortChannel('id');
}}
>
ID
{t('channel.table.id')}
</Table.HeaderCell>
<Table.HeaderCell
style={{ cursor: 'pointer' }}
@@ -452,7 +461,7 @@ const ChannelsTable = () => {
sortChannel('name');
}}
>
名称
{t('channel.table.name')}
</Table.HeaderCell>
<Table.HeaderCell
style={{ cursor: 'pointer' }}
@@ -460,7 +469,7 @@ const ChannelsTable = () => {
sortChannel('group');
}}
>
分组
{t('channel.table.group')}
</Table.HeaderCell>
<Table.HeaderCell
style={{ cursor: 'pointer' }}
@@ -468,7 +477,7 @@ const ChannelsTable = () => {
sortChannel('type');
}}
>
类型
{t('channel.table.type')}
</Table.HeaderCell>
<Table.HeaderCell
style={{ cursor: 'pointer' }}
@@ -476,7 +485,7 @@ const ChannelsTable = () => {
sortChannel('status');
}}
>
状态
{t('channel.table.status')}
</Table.HeaderCell>
<Table.HeaderCell
style={{ cursor: 'pointer' }}
@@ -484,7 +493,7 @@ const ChannelsTable = () => {
sortChannel('response_time');
}}
>
响应时间
{t('channel.table.response_time')}
</Table.HeaderCell>
<Table.HeaderCell
style={{ cursor: 'pointer' }}
@@ -493,7 +502,7 @@ const ChannelsTable = () => {
}}
hidden={!showDetail}
>
余额
{t('channel.table.balance')}
</Table.HeaderCell>
<Table.HeaderCell
style={{ cursor: 'pointer' }}
@@ -501,10 +510,12 @@ const ChannelsTable = () => {
sortChannel('priority');
}}
>
优先级
{t('channel.table.priority')}
</Table.HeaderCell>
<Table.HeaderCell hidden={!showDetail}>测试模型</Table.HeaderCell>
<Table.HeaderCell>操作</Table.HeaderCell>
<Table.HeaderCell hidden={!showDetail}>
{t('channel.table.test_model')}
</Table.HeaderCell>
<Table.HeaderCell>{t('channel.table.actions')}</Table.HeaderCell>
</Table.Row>
</Table.Header>
@@ -519,19 +530,21 @@ const ChannelsTable = () => {
return (
<Table.Row key={channel.id}>
<Table.Cell>{channel.id}</Table.Cell>
<Table.Cell>{channel.name ? channel.name : '无'}</Table.Cell>
<Table.Cell>
{channel.name ? channel.name : t('channel.table.no_name')}
</Table.Cell>
<Table.Cell>{renderGroup(channel.group)}</Table.Cell>
<Table.Cell>{renderType(channel.type)}</Table.Cell>
<Table.Cell>{renderStatus(channel.status)}</Table.Cell>
<Table.Cell>{renderType(channel.type, t)}</Table.Cell>
<Table.Cell>{renderStatus(channel.status, t)}</Table.Cell>
<Table.Cell>
<Popup
content={
channel.test_time
? renderTimestamp(channel.test_time)
: '未测试'
: t('channel.table.not_tested')
}
key={channel.id}
trigger={renderResponseTime(channel.response_time)}
trigger={renderResponseTime(channel.response_time, t)}
basic
/>
</Table.Cell>
@@ -544,10 +557,10 @@ const ChannelsTable = () => {
}}
style={{ cursor: 'pointer' }}
>
{renderBalance(channel.type, channel.balance)}
{renderBalance(channel.type, channel.balance, t)}
</span>
}
content='点击更新'
content={t('channel.table.click_to_update')}
basic
/>
</Table.Cell>
@@ -569,13 +582,13 @@ const ChannelsTable = () => {
<input style={{ maxWidth: '60px' }} />
</Input>
}
content='渠道选择优先级,越高越优先'
content={t('channel.table.priority_tip')}
basic
/>
</Table.Cell>
<Table.Cell hidden={!showDetail}>
<Dropdown
placeholder='请选择测试模型'
placeholder={t('channel.table.select_test_model')}
selection
options={channel.model_options}
defaultValue={channel.test_model}
@@ -598,22 +611,12 @@ const ChannelsTable = () => {
);
}}
>
测试
{t('channel.buttons.test')}
</Button>
{/*<Button*/}
{/* size={'small'}*/}
{/* positive*/}
{/* loading={updatingBalance}*/}
{/* onClick={() => {*/}
{/* updateChannelBalance(channel.id, channel.name, idx);*/}
{/* }}*/}
{/*>*/}
{/* 更新余额*/}
{/*</Button>*/}
<Popup
trigger={
<Button size='small' negative>
删除
{t('channel.buttons.delete')}
</Button>
}
on='click'
@@ -626,7 +629,7 @@ const ChannelsTable = () => {
manageChannel(channel.id, 'delete', idx);
}}
>
删除渠道 {channel.name}
{t('channel.buttons.confirm_delete')} {channel.name}
</Button>
</Popup>
<Button
@@ -639,14 +642,16 @@ const ChannelsTable = () => {
);
}}
>
{channel.status === 1 ? '禁用' : '启用'}
{channel.status === 1
? t('channel.buttons.disable')
: t('channel.buttons.enable')}
</Button>
<Button
size={'small'}
as={Link}
to={'/channel/edit/' + channel.id}
>
编辑
{t('channel.buttons.edit')}
</Button>
</div>
</Table.Cell>
@@ -664,7 +669,7 @@ const ChannelsTable = () => {
to='/channel/add'
loading={loading}
>
添加新的渠道
{t('channel.buttons.add')}
</Button>
<Button
size='small'
@@ -673,7 +678,7 @@ const ChannelsTable = () => {
testChannels('all');
}}
>
测试所有渠道
{t('channel.buttons.test_all')}
</Button>
<Button
size='small'
@@ -682,14 +687,12 @@ const ChannelsTable = () => {
testChannels('disabled');
}}
>
测试禁用渠道
{t('channel.buttons.test_disabled')}
</Button>
{/*<Button size='small' onClick={updateAllChannelsBalance}*/}
{/* loading={loading || updatingBalance}>更新已启用渠道余额</Button>*/}
<Popup
trigger={
<Button size='small' loading={loading}>
删除禁用渠道
{t('channel.buttons.delete_disabled')}
</Button>
}
on='click'
@@ -702,7 +705,7 @@ const ChannelsTable = () => {
negative
onClick={deleteAllDisabledChannels}
>
确认删除
{t('channel.buttons.confirm_delete_disabled')}
</Button>
</Popup>
<Pagination
@@ -717,10 +720,12 @@ const ChannelsTable = () => {
}
/>
<Button size='small' onClick={refresh} loading={loading}>
刷新
{t('channel.buttons.refresh')}
</Button>
<Button size='small' onClick={toggleShowDetail}>
{showDetail ? '隐藏详情' : '详情'}
{showDetail
? t('channel.buttons.hide_detail')
: t('channel.buttons.show_detail')}
</Button>
</Table.HeaderCell>
</Table.Row>