Commit ed1a6bc0 by Calcium-Ion Committed by GitHub

feat: support i18n

feat: support i18n
parents 3f501f45 a771ecbe
...@@ -174,11 +174,11 @@ ...@@ -174,11 +174,11 @@
"\"验证码\"": "\"Verification code\"", "\"验证码\"": "\"Verification code\"",
"全部用户": "All users", "全部用户": "All users",
"当前用户": "Current user", "当前用户": "Current user",
"'全部'": "'All'", "全部'": "All'",
"'充值'": "'Recharge'", "充值'": "Recharge'",
"'消费'": "'Consumption'", "消费'": "Consumption'",
"'管理'": "'Management'", "管理'": "Management'",
"'系统'": "'System'", "系统'": "System'",
" 充值 ": " Recharge ", " 充值 ": " Recharge ",
" 消费 ": " Consumption ", " 消费 ": " Consumption ",
" 管理 ": " Management ", " 管理 ": " Management ",
...@@ -377,6 +377,7 @@ ...@@ -377,6 +377,7 @@
"添加新的用户": "Add New User", "添加新的用户": "Add New User",
"自定义": "Custom", "自定义": "Custom",
"等价金额": "Equivalent Amount", "等价金额": "Equivalent Amount",
"等价金额:{{quota}}:": "Equivalent amount: {{quota}}",
"未登录或登录已过期,请重新登录": "Not logged in or login has expired, please log in again", "未登录或登录已过期,请重新登录": "Not logged in or login has expired, please log in again",
"请求次数过多,请稍后再试": "Too many requests, please try again later", "请求次数过多,请稍后再试": "Too many requests, please try again later",
"服务器内部错误,请联系管理员": "Server internal error, please contact the administrator", "服务器内部错误,请联系管理员": "Server internal error, please contact the administrator",
...@@ -525,5 +526,13 @@ ...@@ -525,5 +526,13 @@
"模型版本": "Model version", "模型版本": "Model version",
"请输入星火大模型版本,注意是接口地址中的版本号,例如:v2.1": "Please enter the version of the Starfire model, note that it is the version number in the interface address, for example: v2.1", "请输入星火大模型版本,注意是接口地址中的版本号,例如:v2.1": "Please enter the version of the Starfire model, note that it is the version number in the interface address, for example: v2.1",
"点击查看": "click to view", "点击查看": "click to view",
"请确保已在 Azure 上创建了 gpt-35-turbo 模型,并且 apiVersion 已正确填写!": "Please make sure that the gpt-35-turbo model has been created on Azure, and the apiVersion has been filled in correctly!" "请确保已在 Azure 上创建了 gpt-35-turbo 模型,并且 apiVersion 已正确填写!": "Please make sure that the gpt-35-turbo model has been created on Azure, and the apiVersion has been filled in correctly!",
"第 {{start}} - {{end}} 条,共 {{total}} 条": "Items {{start}} - {{end}} of {{total}}",
"模型测试": "Model test",
"请选择最长响应时间": "Please select the longest response time",
"成功时自动启用通道": "Enable channel when successful",
"分钟": "minutes",
"设置过短会影响数据库性能": "Setting too short will affect database performance",
"仅修改展示粒度,统计精确到小时": "Only modify display granularity, statistics accurate to the hour",
"当运行通道全部测试时,超过此时间将自动禁用通道": "When running all channel tests, the channel will be automatically disabled when this time is exceeded"
} }
...@@ -23,7 +23,10 @@ ...@@ -23,7 +23,10 @@
"react-turnstile": "^1.0.5", "react-turnstile": "^1.0.5",
"semantic-ui-offline": "^2.5.0", "semantic-ui-offline": "^2.5.0",
"semantic-ui-react": "^2.1.3", "semantic-ui-react": "^2.1.3",
"sse": "github:mpetazzoni/sse.js" "sse": "github:mpetazzoni/sse.js",
"i18next": "^23.16.8",
"react-i18next": "^13.0.0",
"i18next-browser-languagedetector": "^7.2.0"
}, },
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
......
...@@ -32,6 +32,12 @@ importers: ...@@ -32,6 +32,12 @@ importers:
history: history:
specifier: ^5.3.0 specifier: ^5.3.0
version: 5.3.0 version: 5.3.0
i18next:
specifier: ^23.16.8
version: 23.16.8
i18next-browser-languagedetector:
specifier: ^7.2.0
version: 7.2.2
marked: marked:
specifier: ^4.1.1 specifier: ^4.1.1
version: 4.3.0 version: 4.3.0
...@@ -47,6 +53,9 @@ importers: ...@@ -47,6 +53,9 @@ importers:
react-fireworks: react-fireworks:
specifier: ^1.0.4 specifier: ^1.0.4
version: 1.0.4 version: 1.0.4
react-i18next:
specifier: ^13.0.0
version: 13.5.0(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-router-dom: react-router-dom:
specifier: ^6.3.0 specifier: ^6.3.0
version: 6.28.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 6.28.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
...@@ -1071,6 +1080,15 @@ packages: ...@@ -1071,6 +1080,15 @@ packages:
history@5.3.0: history@5.3.0:
resolution: {integrity: sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==} resolution: {integrity: sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==}
html-parse-stringify@3.0.1:
resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==}
i18next-browser-languagedetector@7.2.2:
resolution: {integrity: sha512-6b7r75uIJDWCcCflmbof+sJ94k9UQO4X0YR62oUfqGI/GjCLVzlCwu8TFdRZIqVLzWbzNcmkmhfqKEr4TLz4HQ==}
i18next@23.16.8:
resolution: {integrity: sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg==}
iconv-lite@0.4.24: iconv-lite@0.4.24:
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
...@@ -1487,6 +1505,19 @@ packages: ...@@ -1487,6 +1505,19 @@ packages:
react-fireworks@1.0.4: react-fireworks@1.0.4:
resolution: {integrity: sha512-jj1a+HTicB4pR6g2lqhVyAox0GTE0TOrZK2XaJFRYOwltgQWeYErZxnvU9+zH/blY+Hpmu9IKyb39OD3KcCMJw==} resolution: {integrity: sha512-jj1a+HTicB4pR6g2lqhVyAox0GTE0TOrZK2XaJFRYOwltgQWeYErZxnvU9+zH/blY+Hpmu9IKyb39OD3KcCMJw==}
react-i18next@13.5.0:
resolution: {integrity: sha512-CFJ5NDGJ2MUyBohEHxljOq/39NQ972rh1ajnadG9BjTk+UXbHLq4z5DKEbEQBDoIhUmmbuS/fIMJKo6VOax1HA==}
peerDependencies:
i18next: '>= 23.2.3'
react: '>= 16.8.0'
react-dom: '*'
react-native: '*'
peerDependenciesMeta:
react-dom:
optional: true
react-native:
optional: true
react-is@16.13.1: react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
...@@ -1812,6 +1843,10 @@ packages: ...@@ -1812,6 +1843,10 @@ packages:
terser: terser:
optional: true optional: true
void-elements@3.1.0:
resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
engines: {node: '>=0.10.0'}
warning@4.0.3: warning@4.0.3:
resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==} resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==}
...@@ -2935,6 +2970,18 @@ snapshots: ...@@ -2935,6 +2970,18 @@ snapshots:
dependencies: dependencies:
'@babel/runtime': 7.26.0 '@babel/runtime': 7.26.0
html-parse-stringify@3.0.1:
dependencies:
void-elements: 3.1.0
i18next-browser-languagedetector@7.2.2:
dependencies:
'@babel/runtime': 7.26.0
i18next@23.16.8:
dependencies:
'@babel/runtime': 7.26.0
iconv-lite@0.4.24: iconv-lite@0.4.24:
dependencies: dependencies:
safer-buffer: 2.1.2 safer-buffer: 2.1.2
...@@ -3611,6 +3658,15 @@ snapshots: ...@@ -3611,6 +3658,15 @@ snapshots:
react-fireworks@1.0.4: {} react-fireworks@1.0.4: {}
react-i18next@13.5.0(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@babel/runtime': 7.26.0
html-parse-stringify: 3.0.1
i18next: 23.16.8
react: 18.3.1
optionalDependencies:
react-dom: 18.3.1(react@18.3.1)
react-is@16.13.1: {} react-is@16.13.1: {}
react-is@18.3.1: {} react-is@18.3.1: {}
...@@ -3998,6 +4054,8 @@ snapshots: ...@@ -3998,6 +4054,8 @@ snapshots:
optionalDependencies: optionalDependencies:
fsevents: 2.3.3 fsevents: 2.3.3
void-elements@3.1.0: {}
warning@4.0.3: warning@4.0.3:
dependencies: dependencies:
loose-envify: 1.4.0 loose-envify: 1.4.0
......
...@@ -26,6 +26,7 @@ import Pricing from './pages/Pricing/index.js'; ...@@ -26,6 +26,7 @@ import Pricing from './pages/Pricing/index.js';
import Task from "./pages/Task/index.js"; import Task from "./pages/Task/index.js";
import Playground from './pages/Playground/Playground.js'; import Playground from './pages/Playground/Playground.js';
import OAuth2Callback from "./components/OAuth2Callback.js"; import OAuth2Callback from "./components/OAuth2Callback.js";
import { useTranslation } from 'react-i18next';
const Home = lazy(() => import('./pages/Home')); const Home = lazy(() => import('./pages/Home'));
const Detail = lazy(() => import('./pages/Detail')); const Detail = lazy(() => import('./pages/Detail'));
...@@ -34,6 +35,7 @@ const About = lazy(() => import('./pages/About')); ...@@ -34,6 +35,7 @@ const About = lazy(() => import('./pages/About'));
function App() { function App() {
const [userState, userDispatch] = useContext(UserContext); const [userState, userDispatch] = useContext(UserContext);
// const [statusState, statusDispatch] = useContext(StatusContext); // const [statusState, statusDispatch] = useContext(StatusContext);
const { i18n } = useTranslation();
const loadUser = () => { const loadUser = () => {
let user = localStorage.getItem('user'); let user = localStorage.getItem('user');
...@@ -56,7 +58,12 @@ function App() { ...@@ -56,7 +58,12 @@ function App() {
linkElement.href = logo; linkElement.href = logo;
} }
} }
}, []); // 从localStorage获取上次使用的语言
const savedLang = localStorage.getItem('i18nextLng');
if (savedLang) {
i18n.changeLanguage(savedLang);
}
}, [i18n]);
return ( return (
<> <>
......
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { getFooterHTML, getSystemName } from '../helpers'; import { getFooterHTML, getSystemName } from '../helpers';
import { Layout, Tooltip } from '@douyinfe/semi-ui'; import { Layout, Tooltip } from '@douyinfe/semi-ui';
const FooterBar = () => { const FooterBar = () => {
const { t } = useTranslation();
const systemName = getSystemName(); const systemName = getSystemName();
const [footer, setFooter] = useState(getFooterHTML()); const [footer, setFooter] = useState(getFooterHTML());
let remainCheckTimes = 5; let remainCheckTimes = 5;
...@@ -24,7 +25,7 @@ const FooterBar = () => { ...@@ -24,7 +25,7 @@ const FooterBar = () => {
> >
New API {import.meta.env.VITE_REACT_APP_VERSION}{' '} New API {import.meta.env.VITE_REACT_APP_VERSION}{' '}
</a> </a>
{' '} {t('由')}{' '}
<a <a
href='https://github.com/Calcium-Ion' href='https://github.com/Calcium-Ion'
target='_blank' target='_blank'
...@@ -32,7 +33,7 @@ const FooterBar = () => { ...@@ -32,7 +33,7 @@ const FooterBar = () => {
> >
Calcium-Ion Calcium-Ion
</a>{' '} </a>{' '}
开发,基于{' '} {t('开发,基于')}{' '}
<a <a
href='https://github.com/songquanpeng/one-api' href='https://github.com/songquanpeng/one-api'
target='_blank' target='_blank'
......
...@@ -2,6 +2,7 @@ import React, { useContext, useEffect, useState } from 'react'; ...@@ -2,6 +2,7 @@ import React, { useContext, useEffect, useState } from 'react';
import { Link, useNavigate } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import { UserContext } from '../context/User'; import { UserContext } from '../context/User';
import { useSetTheme, useTheme } from '../context/Theme'; import { useSetTheme, useTheme } from '../context/Theme';
import { useTranslation } from 'react-i18next';
import { API, getLogo, getSystemName, isMobile, showSuccess } from '../helpers'; import { API, getLogo, getSystemName, isMobile, showSuccess } from '../helpers';
import '../index.css'; import '../index.css';
...@@ -16,7 +17,8 @@ import { ...@@ -16,7 +17,8 @@ import {
IconKey, IconMenu, IconKey, IconMenu,
IconNoteMoneyStroked, IconNoteMoneyStroked,
IconPriceTag, IconPriceTag,
IconUser IconUser,
IconLanguage
} from '@douyinfe/semi-icons'; } from '@douyinfe/semi-icons';
import { Avatar, Button, Dropdown, Layout, Nav, Switch } from '@douyinfe/semi-ui'; import { Avatar, Button, Dropdown, Layout, Nav, Switch } from '@douyinfe/semi-ui';
import { stringToColor } from '../helpers/render'; import { stringToColor } from '../helpers/render';
...@@ -42,41 +44,45 @@ if (localStorage.getItem('chat_link')) { ...@@ -42,41 +44,45 @@ if (localStorage.getItem('chat_link')) {
} }
const HeaderBar = () => { const HeaderBar = () => {
const { t, i18n } = useTranslation();
const [userState, userDispatch] = useContext(UserContext); const [userState, userDispatch] = useContext(UserContext);
const [styleState, styleDispatch] = useContext(StyleContext); const [styleState, styleDispatch] = useContext(StyleContext);
let navigate = useNavigate(); let navigate = useNavigate();
const [currentLang, setCurrentLang] = useState(i18n.language);
const systemName = getSystemName(); const systemName = getSystemName();
const logo = getLogo(); const logo = getLogo();
const currentDate = new Date(); const currentDate = new Date();
// enable fireworks on new year(1.1 and 2.9-2.24) // enable fireworks on new year(1.1 and 2.9-2.24)
const isNewYear = const isNewYear =
(currentDate.getMonth() === 0 && currentDate.getDate() === 1) || (currentDate.getMonth() === 0 && currentDate.getDate() === 1);
(currentDate.getMonth() === 1 &&
currentDate.getDate() >= 9 &&
currentDate.getDate() <= 24);
let buttons = [ let buttons = [
{ {
text: '首页', text: t('首页'),
itemKey: 'home', itemKey: 'home',
to: '/', to: '/',
}, },
{ {
text: '控制台', text: t('控制台'),
itemKey: 'detail', itemKey: 'detail',
to: '/', to: '/',
}, },
{ {
text: '定价', text: t('定价'),
itemKey: 'pricing', itemKey: 'pricing',
to: '/pricing', to: '/pricing',
}, },
{
text: t('关于'),
itemKey: 'about',
to: '/about',
},
]; ];
async function logout() { async function logout() {
await API.get('/api/user/logout'); await API.get('/api/user/logout');
showSuccess('注销成功!'); showSuccess(t('注销成功!'));
userDispatch({ type: 'logout' }); userDispatch({ type: 'logout' });
localStorage.removeItem('user'); localStorage.removeItem('user');
navigate('/login'); navigate('/login');
...@@ -106,11 +112,28 @@ const HeaderBar = () => { ...@@ -106,11 +112,28 @@ const HeaderBar = () => {
} }
}, []); }, []);
useEffect(() => {
const handleLanguageChanged = (lng) => {
setCurrentLang(lng);
};
i18n.on('languageChanged', handleLanguageChanged);
return () => {
i18n.off('languageChanged', handleLanguageChanged);
};
}, [i18n]);
const handleLanguageChange = (lang) => {
i18n.changeLanguage(lang);
};
return ( return (
<> <>
<Layout> <Layout>
<div style={{ width: '100%' }}> <div style={{ width: '100%' }}>
<Nav <Nav
className={'topnav'}
mode={'horizontal'} mode={'horizontal'}
renderWrapper={({ itemElement, isSubNav, isInSubNav, props }) => { renderWrapper={({ itemElement, isSubNav, isInSubNav, props }) => {
const routerMap = { const routerMap = {
...@@ -125,10 +148,10 @@ const HeaderBar = () => { ...@@ -125,10 +148,10 @@ const HeaderBar = () => {
<div onClick={(e) => { <div onClick={(e) => {
if (props.itemKey === 'home') { if (props.itemKey === 'home') {
styleDispatch({ type: 'SET_INNER_PADDING', payload: false }); styleDispatch({ type: 'SET_INNER_PADDING', payload: false });
styleDispatch({ type: 'SET_SIDER', payload: false }); // styleDispatch({ type: 'SET_SIDER', payload: false });
} else { } else {
styleDispatch({ type: 'SET_INNER_PADDING', payload: true }); styleDispatch({ type: 'SET_INNER_PADDING', payload: true });
styleDispatch({ type: 'SET_SIDER', payload: true }); // styleDispatch({ type: 'SET_SIDER', payload: true });
} }
}}> }}>
<Link <Link
...@@ -149,10 +172,10 @@ const HeaderBar = () => { ...@@ -149,10 +172,10 @@ const HeaderBar = () => {
<> <>
{ {
!styleState.showSider ? !styleState.showSider ?
<Button icon={<IconMenu />} theme="light" aria-label="展开侧边栏" onClick={ <Button icon={<IconMenu />} theme="light" aria-label={t('展开侧边栏')} onClick={
() => styleDispatch({ type: 'SET_SIDER', payload: true }) () => styleDispatch({ type: 'SET_SIDER', payload: true })
} />: } />:
<Button icon={<IconIndentLeft />} theme="light" aria-label="关闭侧边栏" onClick={ <Button icon={<IconIndentLeft />} theme="light" aria-label={t('闭侧边栏')} onClick={
() => styleDispatch({ type: 'SET_SIDER', payload: false }) () => styleDispatch({ type: 'SET_SIDER', payload: false })
} /> } />
} }
...@@ -182,7 +205,7 @@ const HeaderBar = () => { ...@@ -182,7 +205,7 @@ const HeaderBar = () => {
<Nav.Item itemKey={'new-year'} text={'🏮'} /> <Nav.Item itemKey={'new-year'} text={'🏮'} />
</Dropdown> </Dropdown>
)} )}
<Nav.Item itemKey={'about'} icon={<IconHelpCircle />} /> {/* <Nav.Item itemKey={'about'} icon={<IconHelpCircle />} /> */}
<> <>
<Switch <Switch
checkedText='🌞' checkedText='🌞'
...@@ -194,13 +217,37 @@ const HeaderBar = () => { ...@@ -194,13 +217,37 @@ const HeaderBar = () => {
}} }}
/> />
</> </>
<Dropdown
position='bottomRight'
render={
<Dropdown.Menu>
<Dropdown.Item
onClick={() => handleLanguageChange('zh')}
type={currentLang === 'zh' ? 'primary' : 'tertiary'}
>
中文
</Dropdown.Item>
<Dropdown.Item
onClick={() => handleLanguageChange('en')}
type={currentLang === 'en' ? 'primary' : 'tertiary'}
>
English
</Dropdown.Item>
</Dropdown.Menu>
}
>
<Nav.Item
itemKey={'language'}
icon={<IconLanguage />}
/>
</Dropdown>
{userState.user ? ( {userState.user ? (
<> <>
<Dropdown <Dropdown
position='bottomRight' position='bottomRight'
render={ render={
<Dropdown.Menu> <Dropdown.Menu>
<Dropdown.Item onClick={logout}>退出</Dropdown.Item> <Dropdown.Item onClick={logout}>{t('退出')}</Dropdown.Item>
</Dropdown.Menu> </Dropdown.Menu>
} }
> >
...@@ -218,14 +265,18 @@ const HeaderBar = () => { ...@@ -218,14 +265,18 @@ const HeaderBar = () => {
<> <>
<Nav.Item <Nav.Item
itemKey={'login'} itemKey={'login'}
text={'登录'} text={!styleState.isMobile?t('登录'):null}
// icon={<IconKey />} icon={<IconUser />}
/> />
{
!styleState.isMobile && (
<Nav.Item <Nav.Item
itemKey={'register'} itemKey={'register'}
text={'注册'} text={t('注册')}
icon={<IconUser />} icon={<IconKey />}
/> />
)
}
</> </>
)} )}
</> </>
......
...@@ -28,6 +28,7 @@ import { IconGithubLogo, IconAlarm } from '@douyinfe/semi-icons'; ...@@ -28,6 +28,7 @@ import { IconGithubLogo, IconAlarm } from '@douyinfe/semi-icons';
import WeChatIcon from './WeChatIcon'; import WeChatIcon from './WeChatIcon';
import { setUserData } from '../helpers/data.js'; import { setUserData } from '../helpers/data.js';
import LinuxDoIcon from './LinuxDoIcon.js'; import LinuxDoIcon from './LinuxDoIcon.js';
import { useTranslation } from 'react-i18next';
const LoginForm = () => { const LoginForm = () => {
const [inputs, setInputs] = useState({ const [inputs, setInputs] = useState({
...@@ -45,6 +46,7 @@ const LoginForm = () => { ...@@ -45,6 +46,7 @@ const LoginForm = () => {
let navigate = useNavigate(); let navigate = useNavigate();
const [status, setStatus] = useState({}); const [status, setStatus] = useState({});
const [showWeChatLoginModal, setShowWeChatLoginModal] = useState(false); const [showWeChatLoginModal, setShowWeChatLoginModal] = useState(false);
const { t } = useTranslation();
const logo = getLogo(); const logo = getLogo();
...@@ -55,7 +57,7 @@ const LoginForm = () => { ...@@ -55,7 +57,7 @@ const LoginForm = () => {
useEffect(() => { useEffect(() => {
if (searchParams.get('expired')) { if (searchParams.get('expired')) {
showError('未登录或登录已过期,请重新登录!'); showError(t('未登录或登录已过期,请重新登录'));
} }
let status = localStorage.getItem('status'); let status = localStorage.getItem('status');
if (status) { if (status) {
...@@ -182,20 +184,20 @@ const LoginForm = () => { ...@@ -182,20 +184,20 @@ const LoginForm = () => {
<div style={{ width: 500 }}> <div style={{ width: 500 }}>
<Card> <Card>
<Title heading={2} style={{ textAlign: 'center' }}> <Title heading={2} style={{ textAlign: 'center' }}>
用户登录 {t('用户登录')}
</Title> </Title>
<Form> <Form>
<Form.Input <Form.Input
field={'username'} field={'username'}
label={'用户名'} label={t('用户名/邮箱')}
placeholder='用户名' placeholder={t('用户名/邮箱')}
name='username' name='username'
onChange={(value) => handleChange('username', value)} onChange={(value) => handleChange('username', value)}
/> />
<Form.Input <Form.Input
field={'password'} field={'password'}
label={'密码'} label={t('密码')}
placeholder='密码' placeholder={t('密码')}
name='password' name='password'
type='password' type='password'
onChange={(value) => handleChange('password', value)} onChange={(value) => handleChange('password', value)}
...@@ -209,7 +211,7 @@ const LoginForm = () => { ...@@ -209,7 +211,7 @@ const LoginForm = () => {
htmlType={'submit'} htmlType={'submit'}
onClick={handleSubmit} onClick={handleSubmit}
> >
登录 {t('登录')}
</Button> </Button>
</Form> </Form>
<div <div
...@@ -220,10 +222,10 @@ const LoginForm = () => { ...@@ -220,10 +222,10 @@ const LoginForm = () => {
}} }}
> >
<Text> <Text>
没有账号请先 <Link to='/register'>注册账号</Link> {t('没有账户?')} <Link to='/register'>{t('点击注册')}</Link>
</Text> </Text>
<Text> <Text>
忘记密码 <Link to='/reset'>点击重置</Link> {t('忘记密码?')} <Link to='/reset'>{t('点击重置')}</Link>
</Text> </Text>
</div> </div>
{status.github_oauth || {status.github_oauth ||
...@@ -232,7 +234,7 @@ const LoginForm = () => { ...@@ -232,7 +234,7 @@ const LoginForm = () => {
status.linuxdo_oauth ? ( status.linuxdo_oauth ? (
<> <>
<Divider margin='12px' align='center'> <Divider margin='12px' align='center'>
第三方登录 {t('第三方登录')}
</Divider> </Divider>
<div <div
style={{ style={{
...@@ -296,12 +298,12 @@ const LoginForm = () => { ...@@ -296,12 +298,12 @@ const LoginForm = () => {
<></> <></>
)} )}
<Modal <Modal
title='微信扫码登录' title={t('微信扫码登录')}
visible={showWeChatLoginModal} visible={showWeChatLoginModal}
maskClosable={true} maskClosable={true}
onOk={onSubmitWeChatVerificationCode} onOk={onSubmitWeChatVerificationCode}
onCancel={() => setShowWeChatLoginModal(false)} onCancel={() => setShowWeChatLoginModal(false)}
okText={'登录'} okText={t('登录')}
size={'small'} size={'small'}
centered={true} centered={true}
> >
...@@ -316,14 +318,14 @@ const LoginForm = () => { ...@@ -316,14 +318,14 @@ const LoginForm = () => {
</div> </div>
<div style={{ textAlign: 'center' }}> <div style={{ textAlign: 'center' }}>
<p> <p>
微信扫码关注公众号,输入「验证码」获取验证码(三分钟内有效) {t('微信扫码关注公众号,输入「验证码」获取验证码(三分钟内有效)')}
</p> </p>
</div> </div>
<Form size='large'> <Form size='large'>
<Form.Input <Form.Input
field={'wechat_verification_code'} field={'wechat_verification_code'}
placeholder='验证码' placeholder={t('验证码')}
label={'验证码'} label={t('验证码')}
value={inputs.wechat_verification_code} value={inputs.wechat_verification_code}
onChange={(value) => onChange={(value) =>
handleChange('wechat_verification_code', value) handleChange('wechat_verification_code', value)
......
...@@ -2,8 +2,10 @@ import React, { useEffect, useRef, useState } from 'react'; ...@@ -2,8 +2,10 @@ import React, { useEffect, useRef, useState } from 'react';
import { Banner, Button, Col, Form, Row } from '@douyinfe/semi-ui'; import { Banner, Button, Col, Form, Row } from '@douyinfe/semi-ui';
import { API, showError, showSuccess } from '../helpers'; import { API, showError, showSuccess } from '../helpers';
import { marked } from 'marked'; import { marked } from 'marked';
import { useTranslation } from 'react-i18next';
const OtherSetting = () => { const OtherSetting = () => {
const { t } = useTranslation();
let [inputs, setInputs] = useState({ let [inputs, setInputs] = useState({
Notice: '', Notice: '',
SystemName: '', SystemName: '',
...@@ -54,10 +56,10 @@ const OtherSetting = () => { ...@@ -54,10 +56,10 @@ const OtherSetting = () => {
try { try {
setLoadingInput((loadingInput) => ({ ...loadingInput, Notice: true })); setLoadingInput((loadingInput) => ({ ...loadingInput, Notice: true }));
await updateOption('Notice', inputs.Notice); await updateOption('Notice', inputs.Notice);
showSuccess('公告已更新'); showSuccess(t('公告已更新'));
} catch (error) { } catch (error) {
console.error('公告更新失败', error); console.error(t('公告更新失败'), error);
showError('公告更新失败'); showError(t('公告更新失败'));
} finally { } finally {
setLoadingInput((loadingInput) => ({ ...loadingInput, Notice: false })); setLoadingInput((loadingInput) => ({ ...loadingInput, Notice: false }));
} }
...@@ -72,10 +74,10 @@ const OtherSetting = () => { ...@@ -72,10 +74,10 @@ const OtherSetting = () => {
SystemName: true, SystemName: true,
})); }));
await updateOption('SystemName', inputs.SystemName); await updateOption('SystemName', inputs.SystemName);
showSuccess('系统名称已更新'); showSuccess(t('系统名称已更新'));
} catch (error) { } catch (error) {
console.error('系统名称更新失败', error); console.error(t('系统名称更新失败'), error);
showError('系统名称更新失败'); showError(t('系统名称更新失败'));
} finally { } finally {
setLoadingInput((loadingInput) => ({ setLoadingInput((loadingInput) => ({
...loadingInput, ...loadingInput,
...@@ -193,17 +195,17 @@ const OtherSetting = () => { ...@@ -193,17 +195,17 @@ const OtherSetting = () => {
getFormApi={(formAPI) => (formAPISettingGeneral.current = formAPI)} getFormApi={(formAPI) => (formAPISettingGeneral.current = formAPI)}
style={{ marginBottom: 15 }} style={{ marginBottom: 15 }}
> >
<Form.Section text={'通用设置'}> <Form.Section text={t('通用设置')}>
<Form.TextArea <Form.TextArea
label={'公告'} label={t('公告')}
placeholder={'在此输入新的公告内容,支持 Markdown & HTML 代码'} placeholder={t('在此输入新的公告内容,支持 Markdown & HTML 代码')}
field={'Notice'} field={'Notice'}
onChange={handleInputChange} onChange={handleInputChange}
style={{ fontFamily: 'JetBrains Mono, Consolas' }} style={{ fontFamily: 'JetBrains Mono, Consolas' }}
autosize={{ minRows: 6, maxRows: 12 }} autosize={{ minRows: 6, maxRows: 12 }}
/> />
<Button onClick={submitNotice} loading={loadingInput['Notice']}> <Button onClick={submitNotice} loading={loadingInput['Notice']}>
设置公告 {t('设置公告')}
</Button> </Button>
</Form.Section> </Form.Section>
</Form> </Form>
...@@ -213,10 +215,10 @@ const OtherSetting = () => { ...@@ -213,10 +215,10 @@ const OtherSetting = () => {
getFormApi={(formAPI) => (formAPIPersonalization.current = formAPI)} getFormApi={(formAPI) => (formAPIPersonalization.current = formAPI)}
style={{ marginBottom: 15 }} style={{ marginBottom: 15 }}
> >
<Form.Section text={'个性化设置'}> <Form.Section text={t('个性化设置')}>
<Form.Input <Form.Input
label={'系统名称'} label={t('系统名称')}
placeholder={'在此输入系统名称'} placeholder={t('在此输入系统名称')}
field={'SystemName'} field={'SystemName'}
onChange={handleInputChange} onChange={handleInputChange}
/> />
...@@ -224,22 +226,20 @@ const OtherSetting = () => { ...@@ -224,22 +226,20 @@ const OtherSetting = () => {
onClick={submitSystemName} onClick={submitSystemName}
loading={loadingInput['SystemName']} loading={loadingInput['SystemName']}
> >
设置系统名称 {t('设置系统名称')}
</Button> </Button>
<Form.Input <Form.Input
label={'Logo 图片地址'} label={t('Logo 图片地址')}
placeholder={'在此输入 Logo 图片地址'} placeholder={t('在此输入 Logo 图片地址')}
field={'Logo'} field={'Logo'}
onChange={handleInputChange} onChange={handleInputChange}
/> />
<Button onClick={submitLogo} loading={loadingInput['Logo']}> <Button onClick={submitLogo} loading={loadingInput['Logo']}>
设置 Logo {t('设置 Logo')}
</Button> </Button>
<Form.TextArea <Form.TextArea
label={'首页内容'} label={t('首页内容')}
placeholder={ placeholder={t('在此输入首页内容,支持 Markdown & HTML 代码,设置后首页的状态信息将不再显示。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为首页')}
'在此输入首页内容,支持 Markdown & HTML 代码,设置后首页的状态信息将不再显示。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为首页。'
}
field={'HomePageContent'} field={'HomePageContent'}
onChange={handleInputChange} onChange={handleInputChange}
style={{ fontFamily: 'JetBrains Mono, Consolas' }} style={{ fontFamily: 'JetBrains Mono, Consolas' }}
...@@ -249,39 +249,35 @@ const OtherSetting = () => { ...@@ -249,39 +249,35 @@ const OtherSetting = () => {
onClick={() => submitOption('HomePageContent')} onClick={() => submitOption('HomePageContent')}
loading={loadingInput['HomePageContent']} loading={loadingInput['HomePageContent']}
> >
设置首页内容 {t('设置首页内容')}
</Button> </Button>
<Form.TextArea <Form.TextArea
label={'关于'} label={t('关于')}
placeholder={ placeholder={t('在此输入新的关于内容,支持 Markdown & HTML 代码。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为关于页面')}
'在此输入新的关于内容,支持 Markdown & HTML 代码。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为关于页面。'
}
field={'About'} field={'About'}
onChange={handleInputChange} onChange={handleInputChange}
style={{ fontFamily: 'JetBrains Mono, Consolas' }} style={{ fontFamily: 'JetBrains Mono, Consolas' }}
autosize={{ minRows: 6, maxRows: 12 }} autosize={{ minRows: 6, maxRows: 12 }}
/> />
<Button onClick={submitAbout} loading={loadingInput['About']}> <Button onClick={submitAbout} loading={loadingInput['About']}>
设置关于 {t('设置关于')}
</Button> </Button>
{/* */} {/* */}
<Banner <Banner
fullMode={false} fullMode={false}
type='info' type='info'
description='移除 One API 的版权标识必须首先获得授权,项目维护需要花费大量精力,如果本项目对你有意义,请主动支持本项目。' description={t('移除 One API 的版权标识必须首先获得授权,项目维护需要花费大量精力,如果本项目对你有意义,请主动支持本项目')}
closeIcon={null} closeIcon={null}
style={{ marginTop: 15 }} style={{ marginTop: 15 }}
/> />
<Form.Input <Form.Input
label={'页脚'} label={t('页脚')}
placeholder={ placeholder={t('在此输入新的页脚,留空则使用默认页脚,支持 HTML 代码')}
'在此输入新的页脚,留空则使用默认页脚,支持 HTML 代码'
}
field={'Footer'} field={'Footer'}
onChange={handleInputChange} onChange={handleInputChange}
/> />
<Button onClick={submitFooter} loading={loadingInput['Footer']}> <Button onClick={submitFooter} loading={loadingInput['Footer']}>
设置页脚 {t('设置页脚')}
</Button> </Button>
</Form.Section> </Form.Section>
</Form> </Form>
......
...@@ -6,11 +6,13 @@ import FooterBar from './Footer.js'; ...@@ -6,11 +6,13 @@ import FooterBar from './Footer.js';
import { ToastContainer } from 'react-toastify'; import { ToastContainer } from 'react-toastify';
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { StyleContext } from '../context/Style/index.js'; import { StyleContext } from '../context/Style/index.js';
import { useTranslation } from 'react-i18next';
const { Sider, Content, Header, Footer } = Layout; const { Sider, Content, Header, Footer } = Layout;
const PageLayout = () => { const PageLayout = () => {
const [styleState, styleDispatch] = useContext(StyleContext); const [styleState, styleDispatch] = useContext(StyleContext);
const { t } = useTranslation();
return ( return (
<Layout style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}> <Layout style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}>
...@@ -28,7 +30,7 @@ const PageLayout = () => { ...@@ -28,7 +30,7 @@ const PageLayout = () => {
<App /> <App />
</Content> </Content>
<Layout.Footer> <Layout.Footer>
<FooterBar></FooterBar> <FooterBar />
</Layout.Footer> </Layout.Footer>
</Layout> </Layout>
</Layout> </Layout>
......
...@@ -19,55 +19,55 @@ import { ...@@ -19,55 +19,55 @@ import {
Tag, Tag,
} from '@douyinfe/semi-ui'; } from '@douyinfe/semi-ui';
import EditRedemption from '../pages/Redemption/EditRedemption'; import EditRedemption from '../pages/Redemption/EditRedemption';
import { useTranslation } from 'react-i18next';
function renderTimestamp(timestamp) { function renderTimestamp(timestamp) {
return <>{timestamp2string(timestamp)}</>; return <>{timestamp2string(timestamp)}</>;
} }
function renderStatus(status) { const RedemptionsTable = () => {
const { t } = useTranslation();
const renderStatus = (status) => {
switch (status) { switch (status) {
case 1: case 1:
return ( return (
<Tag color='green' size='large'> <Tag color='green' size='large'>
未使用 {t('未使用')}
</Tag> </Tag>
); );
case 2: case 2:
return ( return (
<Tag color='red' size='large'> <Tag color='red' size='large'>
{' '} {t('已禁用')}
已禁用{' '}
</Tag> </Tag>
); );
case 3: case 3:
return ( return (
<Tag color='grey' size='large'> <Tag color='grey' size='large'>
{' '} {t('已使用')}
已使用{' '}
</Tag> </Tag>
); );
default: default:
return ( return (
<Tag color='black' size='large'> <Tag color='black' size='large'>
{' '} {t('未知状态')}
未知状态{' '}
</Tag> </Tag>
); );
} }
} };
const RedemptionsTable = () => {
const columns = [ const columns = [
{ {
title: 'ID', title: t('ID'),
dataIndex: 'id', dataIndex: 'id',
}, },
{ {
title: '名称', title: t('名称'),
dataIndex: 'name', dataIndex: 'name',
}, },
{ {
title: '状态', title: t('状态'),
dataIndex: 'status', dataIndex: 'status',
key: 'status', key: 'status',
render: (text, record, index) => { render: (text, record, index) => {
...@@ -75,24 +75,24 @@ const RedemptionsTable = () => { ...@@ -75,24 +75,24 @@ const RedemptionsTable = () => {
}, },
}, },
{ {
title: '额度', title: t('额度'),
dataIndex: 'quota', dataIndex: 'quota',
render: (text, record, index) => { render: (text, record, index) => {
return <div>{renderQuota(parseInt(text))}</div>; return <div>{renderQuota(parseInt(text))}</div>;
}, },
}, },
{ {
title: '创建时间', title: t('创建时间'),
dataIndex: 'created_time', dataIndex: 'created_time',
render: (text, record, index) => { render: (text, record, index) => {
return <div>{renderTimestamp(text)}</div>; return <div>{renderTimestamp(text)}</div>;
}, },
}, },
{ {
title: '兑换人ID', title: t('兑换人ID'),
dataIndex: 'used_user_id', dataIndex: 'used_user_id',
render: (text, record, index) => { render: (text, record, index) => {
return <div>{text === 0 ? '无' : text}</div>; return <div>{text === 0 ? t('无') : text}</div>;
}, },
}, },
{ {
...@@ -102,7 +102,7 @@ const RedemptionsTable = () => { ...@@ -102,7 +102,7 @@ const RedemptionsTable = () => {
<div> <div>
<Popover content={record.key} style={{ padding: 20 }} position='top'> <Popover content={record.key} style={{ padding: 20 }} position='top'>
<Button theme='light' type='tertiary' style={{ marginRight: 1 }}> <Button theme='light' type='tertiary' style={{ marginRight: 1 }}>
查看 {t('查看')}
</Button> </Button>
</Popover> </Popover>
<Button <Button
...@@ -113,11 +113,11 @@ const RedemptionsTable = () => { ...@@ -113,11 +113,11 @@ const RedemptionsTable = () => {
await copyText(record.key); await copyText(record.key);
}} }}
> >
复制 {t('复制')}
</Button> </Button>
<Popconfirm <Popconfirm
title='确定是否要删除此兑换码?' title={t('确定是否要删除此兑换码?')}
content='此修改将不可逆' content={t('此修改将不可逆')}
okType={'danger'} okType={'danger'}
position={'left'} position={'left'}
onConfirm={() => { onConfirm={() => {
...@@ -127,7 +127,7 @@ const RedemptionsTable = () => { ...@@ -127,7 +127,7 @@ const RedemptionsTable = () => {
}} }}
> >
<Button theme='light' type='danger' style={{ marginRight: 1 }}> <Button theme='light' type='danger' style={{ marginRight: 1 }}>
删除 {t('删除')}
</Button> </Button>
</Popconfirm> </Popconfirm>
{record.status === 1 ? ( {record.status === 1 ? (
...@@ -139,7 +139,7 @@ const RedemptionsTable = () => { ...@@ -139,7 +139,7 @@ const RedemptionsTable = () => {
manageRedemption(record.id, 'disable', record); manageRedemption(record.id, 'disable', record);
}} }}
> >
禁用 {t('禁用')}
</Button> </Button>
) : ( ) : (
<Button <Button
...@@ -151,7 +151,7 @@ const RedemptionsTable = () => { ...@@ -151,7 +151,7 @@ const RedemptionsTable = () => {
}} }}
disabled={record.status === 3} disabled={record.status === 3}
> >
启用 {t('启用')}
</Button> </Button>
)} )}
<Button <Button
...@@ -164,7 +164,7 @@ const RedemptionsTable = () => { ...@@ -164,7 +164,7 @@ const RedemptionsTable = () => {
}} }}
disabled={record.status !== 1} disabled={record.status !== 1}
> >
编辑 {t('编辑')}
</Button> </Button>
</div> </div>
), ),
...@@ -239,10 +239,10 @@ const RedemptionsTable = () => { ...@@ -239,10 +239,10 @@ const RedemptionsTable = () => {
const copyText = async (text) => { const copyText = async (text) => {
if (await copy(text)) { if (await copy(text)) {
showSuccess('已复制到剪贴板!'); showSuccess(t('已复制到剪贴板!'));
} else { } else {
// setSearchKeyword(text); // setSearchKeyword(text);
Modal.error({ title: '无法复制到剪贴板,请手动复制', content: text }); Modal.error({ title: t('无法复制到剪贴板,请手动复制'), content: text });
} }
}; };
...@@ -286,7 +286,7 @@ const RedemptionsTable = () => { ...@@ -286,7 +286,7 @@ const RedemptionsTable = () => {
} }
const { success, message } = res.data; const { success, message } = res.data;
if (success) { if (success) {
showSuccess('操作成功完成!'); showSuccess(t('操作成功完成!'));
let redemption = res.data.data; let redemption = res.data.data;
let newRedemptions = [...redemptions]; let newRedemptions = [...redemptions];
// let realIdx = (activePage - 1) * ITEMS_PER_PAGE + idx; // let realIdx = (activePage - 1) * ITEMS_PER_PAGE + idx;
...@@ -381,11 +381,11 @@ const RedemptionsTable = () => { ...@@ -381,11 +381,11 @@ const RedemptionsTable = () => {
></EditRedemption> ></EditRedemption>
<Form onSubmit={searchRedemptions}> <Form onSubmit={searchRedemptions}>
<Form.Input <Form.Input
label='搜索关键字' label={t('搜索关键字')}
field='keyword' field='keyword'
icon='search' icon='search'
iconPosition='left' iconPosition='left'
placeholder='关键字(id或者名称)' placeholder={t('关键字(id或者名称)')}
value={searchKeyword} value={searchKeyword}
loading={searching} loading={searching}
onChange={handleKeywordChange} onChange={handleKeywordChange}
...@@ -404,14 +404,14 @@ const RedemptionsTable = () => { ...@@ -404,14 +404,14 @@ const RedemptionsTable = () => {
setShowEdit(true); setShowEdit(true);
}} }}
> >
添加兑换码 {t('添加兑换码')}
</Button> </Button>
<Button <Button
label='复制所选兑换码' label={t('复制所选兑换码')}
type='warning' type='warning'
onClick={async () => { onClick={async () => {
if (selectedKeys.length === 0) { if (selectedKeys.length === 0) {
showError('请至少选择一个兑换码!'); showError(t('请至少选择一个兑换码!'));
return; return;
} }
let keys = ''; let keys = '';
...@@ -421,7 +421,7 @@ const RedemptionsTable = () => { ...@@ -421,7 +421,7 @@ const RedemptionsTable = () => {
await copyText(keys); await copyText(keys);
}} }}
> >
复制所选兑换码到剪贴板 {t('复制所选兑换码到剪贴板')}
</Button> </Button>
</div> </div>
...@@ -436,7 +436,11 @@ const RedemptionsTable = () => { ...@@ -436,7 +436,11 @@ const RedemptionsTable = () => {
// showSizeChanger: true, // showSizeChanger: true,
// pageSizeOptions: [10, 20, 50, 100], // pageSizeOptions: [10, 20, 50, 100],
formatPageText: (page) => formatPageText: (page) =>
`第 ${page.currentStart} - ${page.currentEnd} 条,共 ${redemptions.length} 条`, t('第 {{start}} - {{end}} 条,共 {{total}} 条', {
start: page.currentStart,
end: page.currentEnd,
total: redemptions.length
}),
// onPageSizeChange: (size) => { // onPageSizeChange: (size) => {
// setPageSize(size); // setPageSize(size);
// setActivePage(1); // setActivePage(1);
......
...@@ -12,8 +12,10 @@ import WeChatIcon from './WeChatIcon.js'; ...@@ -12,8 +12,10 @@ import WeChatIcon from './WeChatIcon.js';
import TelegramLoginButton from 'react-telegram-login/src'; import TelegramLoginButton from 'react-telegram-login/src';
import { setUserData } from '../helpers/data.js'; import { setUserData } from '../helpers/data.js';
import { UserContext } from '../context/User/index.js'; import { UserContext } from '../context/User/index.js';
import { useTranslation } from 'react-i18next';
const RegisterForm = () => { const RegisterForm = () => {
const { t } = useTranslation();
const [inputs, setInputs] = useState({ const [inputs, setInputs] = useState({
username: '', username: '',
password: '', password: '',
...@@ -182,28 +184,28 @@ const RegisterForm = () => { ...@@ -182,28 +184,28 @@ const RegisterForm = () => {
<div style={{ width: 500 }}> <div style={{ width: 500 }}>
<Card> <Card>
<Title heading={2} style={{ textAlign: 'center' }}> <Title heading={2} style={{ textAlign: 'center' }}>
新用户注册 {t('新用户注册')}
</Title> </Title>
<Form size="large"> <Form size="large">
<Form.Input <Form.Input
field={'username'} field={'username'}
label={'用户名'} label={t('用户名')}
placeholder="用户名" placeholder={t('用户名')}
name="username" name="username"
onChange={(value) => handleChange('username', value)} onChange={(value) => handleChange('username', value)}
/> />
<Form.Input <Form.Input
field={'password'} field={'password'}
label={'密码'} label={t('密码')}
placeholder="密码,最短 8 位,最长 20 位" placeholder={t('输入密码,最短 8 位,最长 20 位')}
name="password" name="password"
type="password" type="password"
onChange={(value) => handleChange('password', value)} onChange={(value) => handleChange('password', value)}
/> />
<Form.Input <Form.Input
field={'password2'} field={'password2'}
label={'确认密码'} label={t('确认密码')}
placeholder="确认密码" placeholder={t('确认密码')}
name="password2" name="password2"
type="password" type="password"
onChange={(value) => handleChange('password2', value)} onChange={(value) => handleChange('password2', value)}
...@@ -212,21 +214,21 @@ const RegisterForm = () => { ...@@ -212,21 +214,21 @@ const RegisterForm = () => {
<> <>
<Form.Input <Form.Input
field={'email'} field={'email'}
label={'邮箱'} label={t('邮箱')}
placeholder="输入邮箱地址" placeholder={t('输入邮箱地址')}
onChange={(value) => handleChange('email', value)} onChange={(value) => handleChange('email', value)}
name="email" name="email"
type="email" type="email"
suffix={ suffix={
<Button onClick={sendVerificationCode} disabled={loading}> <Button onClick={sendVerificationCode} disabled={loading}>
获取验证码 {t('获取验证码')}
</Button> </Button>
} }
/> />
<Form.Input <Form.Input
field={'verification_code'} field={'verification_code'}
label={'验证码'} label={t('验证码')}
placeholder="输入验证码" placeholder={t('输入验证码')}
onChange={(value) => handleChange('verification_code', value)} onChange={(value) => handleChange('verification_code', value)}
name="verification_code" name="verification_code"
/> />
...@@ -242,7 +244,7 @@ const RegisterForm = () => { ...@@ -242,7 +244,7 @@ const RegisterForm = () => {
htmlType={'submit'} htmlType={'submit'}
onClick={handleSubmit} onClick={handleSubmit}
> >
注册 {t('注册')}
</Button> </Button>
</Form> </Form>
<div <div
...@@ -253,9 +255,9 @@ const RegisterForm = () => { ...@@ -253,9 +255,9 @@ const RegisterForm = () => {
}} }}
> >
<Text> <Text>
已有账户? {t('已有账户?')}
<Link to="/login"> <Link to="/login">
点击登录 {t('点击登录')}
</Link> </Link>
</Text> </Text>
</div> </div>
...@@ -265,7 +267,7 @@ const RegisterForm = () => { ...@@ -265,7 +267,7 @@ const RegisterForm = () => {
status.linuxdo_oauth ? ( status.linuxdo_oauth ? (
<> <>
<Divider margin='12px' align='center'> <Divider margin='12px' align='center'>
第三方登录 {t('第三方登录')}
</Divider> </Divider>
<div <div
style={{ style={{
...@@ -330,12 +332,12 @@ const RegisterForm = () => { ...@@ -330,12 +332,12 @@ const RegisterForm = () => {
)} )}
</Card> </Card>
<Modal <Modal
title='微信扫码登录' title={t('微信扫码登录')}
visible={showWeChatLoginModal} visible={showWeChatLoginModal}
maskClosable={true} maskClosable={true}
onOk={onSubmitWeChatVerificationCode} onOk={onSubmitWeChatVerificationCode}
onCancel={() => setShowWeChatLoginModal(false)} onCancel={() => setShowWeChatLoginModal(false)}
okText={'登录'} okText={t('登录')}
size={'small'} size={'small'}
centered={true} centered={true}
> >
...@@ -350,14 +352,14 @@ const RegisterForm = () => { ...@@ -350,14 +352,14 @@ const RegisterForm = () => {
</div> </div>
<div style={{ textAlign: 'center' }}> <div style={{ textAlign: 'center' }}>
<p> <p>
微信扫码关注公众号,输入「验证码」获取验证码(三分钟内有效) {t('微信扫码关注公众号,输入「验证码」获取验证码(三分钟内有效)')}
</p> </p>
</div> </div>
<Form size='large'> <Form size='large'>
<Form.Input <Form.Input
field={'wechat_verification_code'} field={'wechat_verification_code'}
placeholder='验证码' placeholder={t('验证码')}
label={'验证码'} label={t('验证码')}
value={inputs.wechat_verification_code} value={inputs.wechat_verification_code}
onChange={(value) => onChange={(value) =>
handleChange('wechat_verification_code', value) handleChange('wechat_verification_code', value)
......
...@@ -2,6 +2,7 @@ import React, { useContext, useEffect, useMemo, useState } from 'react'; ...@@ -2,6 +2,7 @@ import React, { useContext, useEffect, useMemo, useState } from 'react';
import { Link, useNavigate } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import { UserContext } from '../context/User'; import { UserContext } from '../context/User';
import { StatusContext } from '../context/Status'; import { StatusContext } from '../context/Status';
import { useTranslation } from 'react-i18next';
import { import {
API, API,
...@@ -36,6 +37,7 @@ import { StyleContext } from '../context/Style/index.js'; ...@@ -36,6 +37,7 @@ import { StyleContext } from '../context/Style/index.js';
// HeaderBar Buttons // HeaderBar Buttons
const SiderBar = () => { const SiderBar = () => {
const { t } = useTranslation();
const [styleState, styleDispatch] = useContext(StyleContext); const [styleState, styleDispatch] = useContext(StyleContext);
const [statusState, statusDispatch] = useContext(StatusContext); const [statusState, statusDispatch] = useContext(StatusContext);
const defaultIsCollapsed = const defaultIsCollapsed =
...@@ -74,30 +76,26 @@ const SiderBar = () => { ...@@ -74,30 +76,26 @@ const SiderBar = () => {
icon: <IconCommentStroked />, icon: <IconCommentStroked />,
}, },
{ {
text: '渠道', text: t('渠道'),
itemKey: 'channel', itemKey: 'channel',
to: '/channel', to: '/channel',
icon: <IconLayers />, icon: <IconLayers />,
className: isAdmin() ? 'semi-navigation-item-normal' : 'tableHiddle', className: isAdmin() ? 'semi-navigation-item-normal' : 'tableHiddle',
}, },
{ {
text: '聊天', text: t('聊天'),
itemKey: 'chat', itemKey: 'chat',
// to: '/chat',
items: chatItems, items: chatItems,
icon: <IconComment />, icon: <IconComment />,
// className: localStorage.getItem('chat_link')
// ? 'semi-navigation-item-normal'
// : 'tableHiddle',
}, },
{ {
text: '令牌', text: t('令牌'),
itemKey: 'token', itemKey: 'token',
to: '/token', to: '/token',
icon: <IconKey />, icon: <IconKey />,
}, },
{ {
text: '数据看板', text: t('数据看板'),
itemKey: 'detail', itemKey: 'detail',
to: '/detail', to: '/detail',
icon: <IconCalendarClock />, icon: <IconCalendarClock />,
...@@ -107,33 +105,33 @@ const SiderBar = () => { ...@@ -107,33 +105,33 @@ const SiderBar = () => {
: 'tableHiddle', : 'tableHiddle',
}, },
{ {
text: '兑换码', text: t('兑换码'),
itemKey: 'redemption', itemKey: 'redemption',
to: '/redemption', to: '/redemption',
icon: <IconGift />, icon: <IconGift />,
className: isAdmin() ? 'semi-navigation-item-normal' : 'tableHiddle', className: isAdmin() ? 'semi-navigation-item-normal' : 'tableHiddle',
}, },
{ {
text: '钱包', text: t('钱包'),
itemKey: 'topup', itemKey: 'topup',
to: '/topup', to: '/topup',
icon: <IconCreditCard />, icon: <IconCreditCard />,
}, },
{ {
text: '用户管理', text: t('用户管理'),
itemKey: 'user', itemKey: 'user',
to: '/user', to: '/user',
icon: <IconUser />, icon: <IconUser />,
className: isAdmin() ? 'semi-navigation-item-normal' : 'tableHiddle', className: isAdmin() ? 'semi-navigation-item-normal' : 'tableHiddle',
}, },
{ {
text: '日志', text: t('日志'),
itemKey: 'log', itemKey: 'log',
to: '/log', to: '/log',
icon: <IconHistogram />, icon: <IconHistogram />,
}, },
{ {
text: '绘图', text: t('绘图'),
itemKey: 'midjourney', itemKey: 'midjourney',
to: '/midjourney', to: '/midjourney',
icon: <IconImage />, icon: <IconImage />,
...@@ -143,7 +141,7 @@ const SiderBar = () => { ...@@ -143,7 +141,7 @@ const SiderBar = () => {
: 'tableHiddle', : 'tableHiddle',
}, },
{ {
text: '异步任务', text: t('异步任务'),
itemKey: 'task', itemKey: 'task',
to: '/task', to: '/task',
icon: <IconChecklistStroked />, icon: <IconChecklistStroked />,
...@@ -153,24 +151,20 @@ const SiderBar = () => { ...@@ -153,24 +151,20 @@ const SiderBar = () => {
: 'tableHiddle', : 'tableHiddle',
}, },
{ {
text: '设置', text: t('设置'),
itemKey: 'setting', itemKey: 'setting',
to: '/setting', to: '/setting',
icon: <IconSetting />, icon: <IconSetting />,
}, },
// {
// text: '关于',
// itemKey: 'about',
// to: '/about',
// icon: <IconAt/>
// }
], ],
[ [
localStorage.getItem('enable_data_export'), localStorage.getItem('enable_data_export'),
localStorage.getItem('enable_drawing'), localStorage.getItem('enable_drawing'),
localStorage.getItem('enable_task'), localStorage.getItem('enable_task'),
localStorage.getItem('chat_link'), chatItems, localStorage.getItem('chat_link'),
chatItems,
isAdmin(), isAdmin(),
t,
], ],
); );
......
...@@ -13,67 +13,69 @@ import { ITEMS_PER_PAGE } from '../constants'; ...@@ -13,67 +13,69 @@ import { ITEMS_PER_PAGE } from '../constants';
import { renderGroup, renderNumber, renderQuota } from '../helpers/render'; import { renderGroup, renderNumber, renderQuota } from '../helpers/render';
import AddUser from '../pages/User/AddUser'; import AddUser from '../pages/User/AddUser';
import EditUser from '../pages/User/EditUser'; import EditUser from '../pages/User/EditUser';
import { useTranslation } from 'react-i18next';
function renderRole(role) { const UsersTable = () => {
const { t } = useTranslation();
function renderRole(role) {
switch (role) { switch (role) {
case 1: case 1:
return <Tag size='large'>普通用户</Tag>; return <Tag size='large'>{t('普通用户')}</Tag>;
case 10: case 10:
return ( return (
<Tag color='yellow' size='large'> <Tag color='yellow' size='large'>
管理员 {t('管理员')}
</Tag> </Tag>
); );
case 100: case 100:
return ( return (
<Tag color='orange' size='large'> <Tag color='orange' size='large'>
超级管理员 {t('超级管理员')}
</Tag> </Tag>
); );
default: default:
return ( return (
<Tag color='red' size='large'> <Tag color='red' size='large'>
未知身份 {t('未知身份')}
</Tag> </Tag>
); );
} }
} }
const UsersTable = () => {
const columns = [ const columns = [
{ {
title: 'ID', title: 'ID',
dataIndex: 'id', dataIndex: 'id',
}, },
{ {
title: '用户名', title: t('用户名'),
dataIndex: 'username', dataIndex: 'username',
}, },
{ {
title: '分组', title: t('分组'),
dataIndex: 'group', dataIndex: 'group',
render: (text, record, index) => { render: (text, record, index) => {
return <div>{renderGroup(text)}</div>; return <div>{renderGroup(text)}</div>;
}, },
}, },
{ {
title: '统计信息', title: t('统计信息'),
dataIndex: 'info', dataIndex: 'info',
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
<Space spacing={1}> <Space spacing={1}>
<Tooltip content={'剩余额度'}> <Tooltip content={t('剩余额度')}>
<Tag color='white' size='large'> <Tag color='white' size='large'>
{renderQuota(record.quota)} {renderQuota(record.quota)}
</Tag> </Tag>
</Tooltip> </Tooltip>
<Tooltip content={'已用额度'}> <Tooltip content={t('已用额度')}>
<Tag color='white' size='large'> <Tag color='white' size='large'>
{renderQuota(record.used_quota)} {renderQuota(record.used_quota)}
</Tag> </Tag>
</Tooltip> </Tooltip>
<Tooltip content={'调用次数'}> <Tooltip content={t('调用次数')}>
<Tag color='white' size='large'> <Tag color='white' size='large'>
{renderNumber(record.request_count)} {renderNumber(record.request_count)}
</Tag> </Tag>
...@@ -84,26 +86,26 @@ const UsersTable = () => { ...@@ -84,26 +86,26 @@ const UsersTable = () => {
}, },
}, },
{ {
title: '邀请信息', title: t('邀请信息'),
dataIndex: 'invite', dataIndex: 'invite',
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
<Space spacing={1}> <Space spacing={1}>
<Tooltip content={'邀请人数'}> <Tooltip content={t('邀请人数')}>
<Tag color='white' size='large'> <Tag color='white' size='large'>
{renderNumber(record.aff_count)} {renderNumber(record.aff_count)}
</Tag> </Tag>
</Tooltip> </Tooltip>
<Tooltip content={'邀请总收益'}> <Tooltip content={t('邀请总收益')}>
<Tag color='white' size='large'> <Tag color='white' size='large'>
{renderQuota(record.aff_history_quota)} {renderQuota(record.aff_history_quota)}
</Tag> </Tag>
</Tooltip> </Tooltip>
<Tooltip content={'邀请人ID'}> <Tooltip content={t('邀请人ID')}>
{record.inviter_id === 0 ? ( {record.inviter_id === 0 ? (
<Tag color='white' size='large'> <Tag color='white' size='large'>
{t('无')}
</Tag> </Tag>
) : ( ) : (
<Tag color='white' size='large'> <Tag color='white' size='large'>
...@@ -117,20 +119,20 @@ const UsersTable = () => { ...@@ -117,20 +119,20 @@ const UsersTable = () => {
}, },
}, },
{ {
title: '角色', title: t('角色'),
dataIndex: 'role', dataIndex: 'role',
render: (text, record, index) => { render: (text, record, index) => {
return <div>{renderRole(text)}</div>; return <div>{renderRole(text)}</div>;
}, },
}, },
{ {
title: '状态', title: t('状态'),
dataIndex: 'status', dataIndex: 'status',
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
{record.DeletedAt !== null ? ( {record.DeletedAt !== null ? (
<Tag color='red'>已注销</Tag> <Tag color='red'>{t('已注销')}</Tag>
) : ( ) : (
renderStatus(text) renderStatus(text)
)} )}
...@@ -148,29 +150,25 @@ const UsersTable = () => { ...@@ -148,29 +150,25 @@ const UsersTable = () => {
) : ( ) : (
<> <>
<Popconfirm <Popconfirm
title='确定?' title={t('确定?')}
okType={'warning'} okType={'warning'}
onConfirm={() => { onConfirm={() => {
manageUser(record.id, 'promote', record); manageUser(record.id, 'promote', record);
}} }}
> >
<Button theme='light' type='warning' style={{ marginRight: 1 }}> <Button theme='light' type='warning' style={{ marginRight: 1 }}>
提升 {t('提升')}
</Button> </Button>
</Popconfirm> </Popconfirm>
<Popconfirm <Popconfirm
title='确定?' title={t('确定?')}
okType={'warning'} okType={'warning'}
onConfirm={() => { onConfirm={() => {
manageUser(record.id, 'demote', record); manageUser(record.id, 'demote', record);
}} }}
> >
<Button <Button theme='light' type='secondary' style={{ marginRight: 1 }}>
theme='light' {t('降级')}
type='secondary'
style={{ marginRight: 1 }}
>
降级
</Button> </Button>
</Popconfirm> </Popconfirm>
{record.status === 1 ? ( {record.status === 1 ? (
...@@ -182,7 +180,7 @@ const UsersTable = () => { ...@@ -182,7 +180,7 @@ const UsersTable = () => {
manageUser(record.id, 'disable', record); manageUser(record.id, 'disable', record);
}} }}
> >
禁用 {t('禁用')}
</Button> </Button>
) : ( ) : (
<Button <Button
...@@ -194,7 +192,7 @@ const UsersTable = () => { ...@@ -194,7 +192,7 @@ const UsersTable = () => {
}} }}
disabled={record.status === 3} disabled={record.status === 3}
> >
启用 {t('启用')}
</Button> </Button>
)} )}
<Button <Button
...@@ -206,11 +204,11 @@ const UsersTable = () => { ...@@ -206,11 +204,11 @@ const UsersTable = () => {
setShowEditUser(true); setShowEditUser(true);
}} }}
> >
编辑 {t('编辑')}
</Button> </Button>
<Popconfirm <Popconfirm
title='确定是否要注销此用户?' title={t('确定是否要注销此用户?')}
content='相当于删除用户,此修改将不可逆' content={t('相当于删除用户,此修改将不可逆')}
okType={'danger'} okType={'danger'}
position={'left'} position={'left'}
onConfirm={() => { onConfirm={() => {
...@@ -220,7 +218,7 @@ const UsersTable = () => { ...@@ -220,7 +218,7 @@ const UsersTable = () => {
}} }}
> >
<Button theme='light' type='danger' style={{ marginRight: 1 }}> <Button theme='light' type='danger' style={{ marginRight: 1 }}>
注销 {t('注销')}
</Button> </Button>
</Popconfirm> </Popconfirm>
</> </>
...@@ -327,17 +325,17 @@ const UsersTable = () => { ...@@ -327,17 +325,17 @@ const UsersTable = () => {
const renderStatus = (status) => { const renderStatus = (status) => {
switch (status) { switch (status) {
case 1: case 1:
return <Tag size='large'>已激活</Tag>; return <Tag size='large'>{t('已激活')}</Tag>;
case 2: case 2:
return ( return (
<Tag size='large' color='red'> <Tag size='large' color='red'>
已封禁 {t('已封禁')}
</Tag> </Tag>
); );
default: default:
return ( return (
<Tag size='large' color='grey'> <Tag size='large' color='grey'>
未知状态 {t('未知状态')}
</Tag> </Tag>
); );
} }
...@@ -453,18 +451,18 @@ const UsersTable = () => { ...@@ -453,18 +451,18 @@ const UsersTable = () => {
<div style={{ display: 'flex' }}> <div style={{ display: 'flex' }}>
<Space> <Space>
<Form.Input <Form.Input
label='搜索关键字' label={t('搜索关键字')}
icon='search' icon='search'
field='keyword' field='keyword'
iconPosition='left' iconPosition='left'
placeholder='搜索用户的 ID,用户名,显示名称,以及邮箱地址 ...' placeholder={t('搜索用户的 ID,用户名,显示名称,以及邮箱地址 ...')}
value={searchKeyword} value={searchKeyword}
loading={searching} loading={searching}
onChange={(value) => handleKeywordChange(value)} onChange={(value) => handleKeywordChange(value)}
/> />
<Form.Select <Form.Select
field='group' field='group'
label='分组' label={t('分组')}
optionList={groupOptions} optionList={groupOptions}
onChange={(value) => { onChange={(value) => {
setSearchGroup(value); setSearchGroup(value);
...@@ -472,12 +470,12 @@ const UsersTable = () => { ...@@ -472,12 +470,12 @@ const UsersTable = () => {
}} }}
/> />
<Button <Button
label='查询' label={t('查询')}
type='primary' type='primary'
htmlType='submit' htmlType='submit'
className='btn-margin-right' className='btn-margin-right'
> >
查询 {t('查询')}
</Button> </Button>
<Button <Button
theme='light' theme='light'
...@@ -486,7 +484,7 @@ const UsersTable = () => { ...@@ -486,7 +484,7 @@ const UsersTable = () => {
setShowAddUser(true); setShowAddUser(true);
}} }}
> >
添加用户 {t('添加用户')}
</Button> </Button>
</Space> </Space>
</div> </div>
...@@ -496,6 +494,12 @@ const UsersTable = () => { ...@@ -496,6 +494,12 @@ const UsersTable = () => {
columns={columns} columns={columns}
dataSource={pageData} dataSource={pageData}
pagination={{ pagination={{
formatPageText: (page) =>
t('第 {{start}} - {{end}} 条,共 {{total}} 条', {
start: page.currentStart,
end: page.currentEnd,
total: users.length
}),
currentPage: activePage, currentPage: activePage,
pageSize: ITEMS_PER_PAGE, pageSize: ITEMS_PER_PAGE,
total: userCount, total: userCount,
......
import i18next from 'i18next';
import { Tag } from '@douyinfe/semi-ui'; import { Tag } from '@douyinfe/semi-ui';
export function renderText(text, limit) { export function renderText(text, limit) {
...@@ -16,7 +17,7 @@ export function renderGroup(group) { ...@@ -16,7 +17,7 @@ export function renderGroup(group) {
if (group === '') { if (group === '') {
return ( return (
<Tag size='large' key='default' color='orange'> <Tag size='large' key='default' color='orange'>
用户分组 {i18next.t('用户分组')}
</Tag> </Tag>
); );
} }
...@@ -144,14 +145,16 @@ export function renderModelPrice( ...@@ -144,14 +145,16 @@ export function renderModelPrice(
completionRatio, completionRatio,
groupRatio, groupRatio,
) { ) {
// 1 ratio = $0.002 / 1K tokens
if (modelPrice !== -1) { if (modelPrice !== -1) {
return '模型价格:$' + modelPrice + ' * 分组倍率:' + groupRatio + ' = $' + modelPrice * groupRatio; return i18next.t('模型价格:${{price}} * 分组倍率:{{ratio}} = ${{total}}', {
price: modelPrice,
ratio: groupRatio,
total: modelPrice * groupRatio
});
} else { } else {
if (completionRatio === undefined) { if (completionRatio === undefined) {
completionRatio = 0; completionRatio = 0;
} }
// 这里的 *2 是因为 1倍率=0.002刀,请勿删除
let inputRatioPrice = modelRatio * 2.0; let inputRatioPrice = modelRatio * 2.0;
let completionRatioPrice = modelRatio * 2.0 * completionRatio; let completionRatioPrice = modelRatio * 2.0 * completionRatio;
let price = let price =
...@@ -160,15 +163,28 @@ export function renderModelPrice( ...@@ -160,15 +163,28 @@ export function renderModelPrice(
return ( return (
<> <>
<article> <article>
<p>提示:${inputRatioPrice} * {groupRatio} = ${inputRatioPrice * groupRatio} / 1M tokens</p> <p>{i18next.t('提示:${{price}} * {{ratio}} = ${{total}} / 1M tokens', {
<p>补全:${completionRatioPrice} * {groupRatio} = ${completionRatioPrice * groupRatio} / 1M tokens</p> price: inputRatioPrice,
ratio: groupRatio,
total: inputRatioPrice * groupRatio
})}</p>
<p>{i18next.t('补全:${{price}} * {{ratio}} = ${{total}} / 1M tokens', {
price: completionRatioPrice,
ratio: groupRatio,
total: completionRatioPrice * groupRatio
})}</p>
<p></p> <p></p>
<p> <p>
提示 {inputTokens} tokens / 1M tokens * ${inputRatioPrice} + 补全{' '} {i18next.t('提示 {{input}} tokens / 1M tokens * ${{price}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}', {
{completionTokens} tokens / 1M tokens * ${completionRatioPrice} * 分组 {groupRatio} = input: inputTokens,
${price.toFixed(6)} price: inputRatioPrice,
completion: completionTokens,
compPrice: completionRatioPrice,
ratio: groupRatio,
total: price.toFixed(6)
})}
</p> </p>
<p>仅供参考,以实际扣费为准</p> <p>{i18next.t('仅供参考,以实际扣费为准')}</p>
</article> </article>
</> </>
); );
...@@ -180,11 +196,16 @@ export function renderModelPriceSimple( ...@@ -180,11 +196,16 @@ export function renderModelPriceSimple(
modelPrice = -1, modelPrice = -1,
groupRatio, groupRatio,
) { ) {
// 1 ratio = $0.002 / 1K tokens
if (modelPrice !== -1) { if (modelPrice !== -1) {
return '价格:$' + modelPrice + ' * 分组:' + groupRatio; return i18next.t('价格:${{price}} * 分组:{{ratio}}', {
price: modelPrice,
ratio: groupRatio
});
} else { } else {
return '模型: ' + modelRatio + ' * 分组: ' + groupRatio; return i18next.t('模型: {{ratio}} * 分组: {{groupRatio}}', {
ratio: modelRatio,
groupRatio: groupRatio
});
} }
} }
...@@ -224,8 +245,12 @@ export function renderAudioModelPrice( ...@@ -224,8 +245,12 @@ export function renderAudioModelPrice(
<p>音频补全:${inputRatioPrice} * {groupRatio} * {audioRatio} * {audioCompletionRatio} = ${inputRatioPrice * audioRatio * audioCompletionRatio * groupRatio} / 1M tokens</p> <p>音频补全:${inputRatioPrice} * {groupRatio} * {audioRatio} * {audioCompletionRatio} = ${inputRatioPrice * audioRatio * audioCompletionRatio * groupRatio} / 1M tokens</p>
<p></p> <p></p>
<p> <p>
提示 {inputTokens} tokens / 1M tokens * ${inputRatioPrice} + 补全{' '} {i18next.t('提示 {{input}} tokens / 1M tokens * ${{price}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} +', {
{completionTokens} tokens / 1M tokens * ${completionRatioPrice} + input: inputTokens,
price: inputRatioPrice,
completion: completionTokens,
compPrice: completionRatioPrice
})}
</p> </p>
<p> <p>
音频提示 {audioInputTokens} tokens / 1M tokens * ${inputRatioPrice} * {audioRatio} + 音频补全 {audioCompletionTokens} tokens / 1M tokens * ${inputRatioPrice} * {audioRatio} * {audioCompletionRatio} 音频提示 {audioInputTokens} tokens / 1M tokens * ${inputRatioPrice} * {audioRatio} + 音频补全 {audioCompletionTokens} tokens / 1M tokens * ${inputRatioPrice} * {audioRatio} * {audioCompletionRatio}
...@@ -245,7 +270,7 @@ export function renderQuotaWithPrompt(quota, digits) { ...@@ -245,7 +270,7 @@ export function renderQuotaWithPrompt(quota, digits) {
let displayInCurrency = localStorage.getItem('display_in_currency'); let displayInCurrency = localStorage.getItem('display_in_currency');
displayInCurrency = displayInCurrency === 'true'; displayInCurrency = displayInCurrency === 'true';
if (displayInCurrency) { if (displayInCurrency) {
return `(等价金额:${renderQuota(quota, digits)})`; return '|' + i18next.t('等价金额') + ': ' + renderQuota(quota, digits) + '';
} }
return ''; return '';
} }
...@@ -315,7 +340,7 @@ export const modelColorMap = { ...@@ -315,7 +340,7 @@ export const modelColorMap = {
'gpt-3.5-turbo-0613': 'rgb(60,179,113)', // 海洋绿 'gpt-3.5-turbo-0613': 'rgb(60,179,113)', // 海洋绿
'gpt-3.5-turbo-1106': 'rgb(32,178,170)', // 浅海洋绿 'gpt-3.5-turbo-1106': 'rgb(32,178,170)', // 浅海洋绿
'gpt-3.5-turbo-16k': 'rgb(149,252,206)', // 淡橙色 'gpt-3.5-turbo-16k': 'rgb(149,252,206)', // 淡橙色
'gpt-3.5-turbo-16k-0613': 'rgb(119,255,214)', // 淡桃 'gpt-3.5-turbo-16k-0613': 'rgb(119,255,214)', // 淡桃���
'gpt-3.5-turbo-instruct': 'rgb(175,238,238)', // 粉蓝色 'gpt-3.5-turbo-instruct': 'rgb(175,238,238)', // 粉蓝色
'gpt-4': 'rgb(135,206,235)', // 天蓝色 'gpt-4': 'rgb(135,206,235)', // 天蓝色
// 'gpt-4-0314': 'rgb(70,130,180)', // 钢蓝色 // 'gpt-4-0314': 'rgb(70,130,180)', // 钢蓝色
...@@ -338,7 +363,7 @@ export const modelColorMap = { ...@@ -338,7 +363,7 @@ export const modelColorMap = {
'text-embedding-ada-002': 'rgb(255,182,193)', // 浅粉红 'text-embedding-ada-002': 'rgb(255,182,193)', // 浅粉红
'text-embedding-v1': 'rgb(255,174,185)', // 浅粉红色(略有区别) 'text-embedding-v1': 'rgb(255,174,185)', // 浅粉红色(略有区别)
'text-moderation-latest': 'rgb(255,130,171)', // 强粉色 'text-moderation-latest': 'rgb(255,130,171)', // 强粉色
'text-moderation-stable': 'rgb(255,160,122)', // 浅珊瑚色(Babbage相同,表示同一类功能) 'text-moderation-stable': 'rgb(255,160,122)', // 浅珊瑚色(���Babbage相同,表示同一类功能)
'tts-1': 'rgb(255,140,0)', // 深橙色 'tts-1': 'rgb(255,140,0)', // 深橙色
'tts-1-1106': 'rgb(255,165,0)', // 橙色 'tts-1-1106': 'rgb(255,165,0)', // 橙色
'tts-1-hd': 'rgb(255,215,0)', // 金色 'tts-1-hd': 'rgb(255,215,0)', // 金色
......
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import enTranslation from './locales/en.json';
import zhTranslation from './locales/zh.json';
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources: {
en: {
translation: enTranslation
},
zh: {
translation: zhTranslation
}
},
fallbackLng: 'zh',
interpolation: {
escapeValue: false
}
});
export default i18n;
\ No newline at end of file
{
"首页": "首页",
"控制台": "控制台",
"定价": "定价",
"关于": "关于",
"登录": "登录",
"注册": "注册",
"退出": "退出",
"语言": "语言",
"展开侧边栏": "展开侧边栏",
"关闭侧边栏": "关闭侧边栏",
"注销成功!": "注销成功!"
}
\ No newline at end of file
...@@ -25,17 +25,28 @@ body { ...@@ -25,17 +25,28 @@ body {
padding-right: 30px; padding-right: 30px;
} }
.panel-desc-card {
/*min-width: 320px;*/
}
@media only screen and (max-width: 767px) { @media only screen and (max-width: 767px) {
/*.semi-navigation-sub-wrap .semi-navigation-sub-title, .semi-navigation-item {*/
/* padding: 0 0;*/
/*}*/
.topnav .semi-navigation-list-wrapper {
max-width: calc(55vw - 20px);
overflow-x: auto;
scrollbar-width: none;
}
#root > section > header > section > div > div > div > div.semi-navigation-footer > div > a > li {
padding: 0 0;
}
#root > section > header > section > div > div > div > div.semi-navigation-header-list-outer > div.semi-navigation-list-wrapper > ul > div > a > li { #root > section > header > section > div > div > div > div.semi-navigation-header-list-outer > div.semi-navigation-list-wrapper > ul > div > a > li {
padding: 0 5px; padding: 0 5px;
} }
#root > section > header > section > div > div > div > div.semi-navigation-footer > div:nth-child(1) > a > li { #root > section > header > section > div > div > div > div.semi-navigation-footer > div:nth-child(1) > a > li {
padding: 0 5px; padding: 0 5px;
} }
.semi-navigation-footer {
padding-left: 0;
padding-right: 0;
}
.semi-table-tbody, .semi-table-tbody,
.semi-table-row, .semi-table-row,
.semi-table-row-cell { .semi-table-row-cell {
...@@ -105,11 +116,6 @@ code { ...@@ -105,11 +116,6 @@ code {
monospace; monospace;
} }
.semi-navigation-vertical {
/*display: flex;*/
/*flex-direction: column;*/
}
.semi-navigation-item { .semi-navigation-item {
margin-bottom: 0; margin-bottom: 0;
} }
......
...@@ -15,6 +15,7 @@ import { ThemeProvider } from './context/Theme'; ...@@ -15,6 +15,7 @@ import { ThemeProvider } from './context/Theme';
import FooterBar from './components/Footer'; import FooterBar from './components/Footer';
import { StyleProvider } from './context/Style/index.js'; import { StyleProvider } from './context/Style/index.js';
import PageLayout from './components/PageLayout.js'; import PageLayout from './components/PageLayout.js';
import './i18n/i18n.js';
// initialization // initialization
......
import React from 'react'; import React from 'react';
import ChannelsTable from '../../components/ChannelsTable'; import ChannelsTable from '../../components/ChannelsTable';
import { Layout } from '@douyinfe/semi-ui'; import { Layout } from '@douyinfe/semi-ui';
import { useTranslation } from 'react-i18next';
const File = () => ( const File = () => {
const { t } = useTranslation();
return (
<> <>
<Layout> <Layout>
<Layout.Header> <Layout.Header>
<h3>管理渠道</h3> <h3>{t('管理渠道')}</h3>
</Layout.Header> </Layout.Header>
<Layout.Content> <Layout.Content>
<ChannelsTable /> <ChannelsTable />
</Layout.Content> </Layout.Content>
</Layout> </Layout>
</> </>
); );
};
export default File; export default File;
...@@ -21,8 +21,10 @@ import { ...@@ -21,8 +21,10 @@ import {
} from '../../helpers/render'; } from '../../helpers/render';
import { UserContext } from '../../context/User/index.js'; import { UserContext } from '../../context/User/index.js';
import { StyleContext } from '../../context/Style/index.js'; import { StyleContext } from '../../context/Style/index.js';
import { useTranslation } from 'react-i18next';
const Detail = (props) => { const Detail = (props) => {
const { t } = useTranslation();
const formRef = useRef(); const formRef = useRef();
let now = new Date(); let now = new Date();
const [userState, userDispatch] = useContext(UserContext); const [userState, userDispatch] = useContext(UserContext);
...@@ -85,8 +87,8 @@ const Detail = (props) => { ...@@ -85,8 +87,8 @@ const Detail = (props) => {
}, },
title: { title: {
visible: true, visible: true,
text: '模型调用次数占比', text: t('模型调用次数占比'),
subtext: `总计${renderNumber(times)}`, subtext: `${t('总计')}${renderNumber(times)}`,
}, },
legends: { legends: {
visible: true, visible: true,
...@@ -125,11 +127,10 @@ const Detail = (props) => { ...@@ -125,11 +127,10 @@ const Detail = (props) => {
}, },
title: { title: {
visible: true, visible: true,
text: '模型消耗分布', text: t('模型消耗分布'),
subtext: `总计${renderQuota(consumeQuota, 2)}`, subtext: `${t('总计')}${renderQuota(consumeQuota, 2)}`,
}, },
bar: { bar: {
// The state style of bar
state: { state: {
hover: { hover: {
stroke: '#000', stroke: '#000',
...@@ -155,9 +156,7 @@ const Detail = (props) => { ...@@ -155,9 +156,7 @@ const Detail = (props) => {
}, },
], ],
updateContent: (array) => { updateContent: (array) => {
// sort by value
array.sort((a, b) => b.value - a.value); array.sort((a, b) => b.value - a.value);
// add $
let sum = 0; let sum = 0;
for (let i = 0; i < array.length; i++) { for (let i = 0; i < array.length; i++) {
sum += parseFloat(array[i].value); sum += parseFloat(array[i].value);
...@@ -166,9 +165,8 @@ const Detail = (props) => { ...@@ -166,9 +165,8 @@ const Detail = (props) => {
4, 4,
); );
} }
// add to first
array.unshift({ array.unshift({
key: '总计', key: t('总计'),
value: renderQuotaNumberWithDigit(sum, 4), value: renderQuotaNumberWithDigit(sum, 4),
}); });
return array; return array;
...@@ -331,7 +329,7 @@ const Detail = (props) => { ...@@ -331,7 +329,7 @@ const Detail = (props) => {
data: [{ id: 'id0', values: newPieData }], data: [{ id: 'id0', values: newPieData }],
title: { title: {
...prev.title, ...prev.title,
subtext: `总计${renderNumber(totalTimes)}` subtext: `${t('总计')}${renderNumber(totalTimes)}`
}, },
color: { color: {
specified: newModelColors specified: newModelColors
...@@ -343,7 +341,7 @@ const Detail = (props) => { ...@@ -343,7 +341,7 @@ const Detail = (props) => {
data: [{ id: 'barData', values: newLineData }], data: [{ id: 'barData', values: newLineData }],
title: { title: {
...prev.title, ...prev.title,
subtext: `总计${renderQuota(totalQuota, 2)}` subtext: `${t('总计')}${renderQuota(totalQuota, 2)}`
}, },
color: { color: {
specified: newModelColors specified: newModelColors
...@@ -382,14 +380,14 @@ const Detail = (props) => { ...@@ -382,14 +380,14 @@ const Detail = (props) => {
<> <>
<Layout> <Layout>
<Layout.Header> <Layout.Header>
<h3>数据看板</h3> <h3>{t('数据看板')}</h3>
</Layout.Header> </Layout.Header>
<Layout.Content> <Layout.Content>
<Form ref={formRef} layout='horizontal' style={{ marginTop: 10 }}> <Form ref={formRef} layout='horizontal' style={{ marginTop: 10 }}>
<> <>
<Form.DatePicker <Form.DatePicker
field='start_timestamp' field='start_timestamp'
label='起始时间' label={t('起始时间')}
style={{ width: 272 }} style={{ width: 272 }}
initValue={start_timestamp} initValue={start_timestamp}
value={start_timestamp} value={start_timestamp}
...@@ -402,7 +400,7 @@ const Detail = (props) => { ...@@ -402,7 +400,7 @@ const Detail = (props) => {
<Form.DatePicker <Form.DatePicker
field='end_timestamp' field='end_timestamp'
fluid fluid
label='结束时间' label={t('结束时间')}
style={{ width: 272 }} style={{ width: 272 }}
initValue={end_timestamp} initValue={end_timestamp}
value={end_timestamp} value={end_timestamp}
...@@ -412,15 +410,15 @@ const Detail = (props) => { ...@@ -412,15 +410,15 @@ const Detail = (props) => {
/> />
<Form.Select <Form.Select
field='data_export_default_time' field='data_export_default_time'
label='时间粒度' label={t('时间粒度')}
style={{ width: 176 }} style={{ width: 176 }}
initValue={dataExportDefaultTime} initValue={dataExportDefaultTime}
placeholder={'时间粒度'} placeholder={t('时间粒度')}
name='data_export_default_time' name='data_export_default_time'
optionList={[ optionList={[
{ label: '小时', value: 'hour' }, { label: t('小时'), value: 'hour' },
{ label: '天', value: 'day' }, { label: t('天'), value: 'day' },
{ label: '周', value: 'week' }, { label: t('周'), value: 'week' },
]} ]}
onChange={(value) => onChange={(value) =>
handleInputChange(value, 'data_export_default_time') handleInputChange(value, 'data_export_default_time')
...@@ -430,17 +428,17 @@ const Detail = (props) => { ...@@ -430,17 +428,17 @@ const Detail = (props) => {
<> <>
<Form.Input <Form.Input
field='username' field='username'
label='用户名称' label={t('用户名称')}
style={{ width: 176 }} style={{ width: 176 }}
value={username} value={username}
placeholder={'可选值'} placeholder={t('可选值')}
name='username' name='username'
onChange={(value) => handleInputChange(value, 'username')} onChange={(value) => handleInputChange(value, 'username')}
/> />
</> </>
)} )}
<Button <Button
label='查询' label={t('查询')}
type='primary' type='primary'
htmlType='submit' htmlType='submit'
className='btn-margin-right' className='btn-margin-right'
...@@ -448,7 +446,7 @@ const Detail = (props) => { ...@@ -448,7 +446,7 @@ const Detail = (props) => {
loading={loading} loading={loading}
style={{ marginTop: 24 }} style={{ marginTop: 24 }}
> >
查询 {t('查询')}
</Button> </Button>
<Form.Section> <Form.Section>
</Form.Section> </Form.Section>
...@@ -459,13 +457,13 @@ const Detail = (props) => { ...@@ -459,13 +457,13 @@ const Detail = (props) => {
<Col span={styleState.isMobile?24:8}> <Col span={styleState.isMobile?24:8}>
<Card className='panel-desc-card'> <Card className='panel-desc-card'>
<Descriptions row size="small"> <Descriptions row size="small">
<Descriptions.Item itemKey='当前余额'> <Descriptions.Item itemKey={t('当前余额')}>
{renderQuota(userState?.user?.quota)} {renderQuota(userState?.user?.quota)}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item itemKey='历史消耗'> <Descriptions.Item itemKey={t('历史消耗')}>
{renderQuota(userState?.user?.used_quota)} {renderQuota(userState?.user?.used_quota)}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item itemKey='请求次数'> <Descriptions.Item itemKey={t('请求次数')}>
{userState.user?.request_count} {userState.user?.request_count}
</Descriptions.Item> </Descriptions.Item>
</Descriptions> </Descriptions>
...@@ -474,13 +472,13 @@ const Detail = (props) => { ...@@ -474,13 +472,13 @@ const Detail = (props) => {
<Col span={styleState.isMobile?24:8}> <Col span={styleState.isMobile?24:8}>
<Card> <Card>
<Descriptions row size="small"> <Descriptions row size="small">
<Descriptions.Item itemKey='统计额度'> <Descriptions.Item itemKey={t('统计额度')}>
{renderQuota(consumeQuota)} {renderQuota(consumeQuota)}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item itemKey='统计Tokens'> <Descriptions.Item itemKey={t('统计Tokens')}>
{consumeTokens} {consumeTokens}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item itemKey='统计次数'> <Descriptions.Item itemKey={t('统计次数')}>
{times} {times}
</Descriptions.Item> </Descriptions.Item>
</Descriptions> </Descriptions>
...@@ -489,13 +487,13 @@ const Detail = (props) => { ...@@ -489,13 +487,13 @@ const Detail = (props) => {
<Col span={styleState.isMobile ? 24 : 8}> <Col span={styleState.isMobile ? 24 : 8}>
<Card> <Card>
<Descriptions row size='small'> <Descriptions row size='small'>
<Descriptions.Item itemKey='平均RPM'> <Descriptions.Item itemKey={t('平均RPM')}>
{(times / {(times /
((Date.parse(end_timestamp) - ((Date.parse(end_timestamp) -
Date.parse(start_timestamp)) / Date.parse(start_timestamp)) /
60000)).toFixed(3)} 60000)).toFixed(3)}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item itemKey='平均TPM'> <Descriptions.Item itemKey={t('平均TPM')}>
{(consumeTokens / {(consumeTokens /
((Date.parse(end_timestamp) - ((Date.parse(end_timestamp) -
Date.parse(start_timestamp)) / Date.parse(start_timestamp)) /
...@@ -507,7 +505,7 @@ const Detail = (props) => { ...@@ -507,7 +505,7 @@ const Detail = (props) => {
</Row> </Row>
<Card style={{marginTop: 20}}> <Card style={{marginTop: 20}}>
<Tabs type="line" defaultActiveKey="1"> <Tabs type="line" defaultActiveKey="1">
<Tabs.TabPane tab="消耗分布" itemKey="1"> <Tabs.TabPane tab={t('消耗分布')} itemKey="1">
<div style={{ height: 500 }}> <div style={{ height: 500 }}>
<VChart <VChart
spec={spec_line} spec={spec_line}
...@@ -515,7 +513,7 @@ const Detail = (props) => { ...@@ -515,7 +513,7 @@ const Detail = (props) => {
/> />
</div> </div>
</Tabs.TabPane> </Tabs.TabPane>
<Tabs.TabPane tab="调用次数分布" itemKey="2"> <Tabs.TabPane tab={t('调用次数分布')} itemKey="2">
<div style={{ height: 500 }}> <div style={{ height: 500 }}>
<VChart <VChart
spec={spec_pie} spec={spec_pie}
......
...@@ -6,21 +6,7 @@ import { Card, Chat, Input, Layout, Select, Slider, TextArea, Typography, Button ...@@ -6,21 +6,7 @@ import { Card, Chat, Input, Layout, Select, Slider, TextArea, Typography, Button
import { SSE } from 'sse'; import { SSE } from 'sse';
import { IconSetting } from '@douyinfe/semi-icons'; import { IconSetting } from '@douyinfe/semi-icons';
import { StyleContext } from '../../context/Style/index.js'; import { StyleContext } from '../../context/Style/index.js';
import { useTranslation } from 'react-i18next';
const defaultMessage = [
{
role: 'user',
id: '2',
createAt: 1715676751919,
content: "你好",
},
{
role: 'assistant',
id: '3',
createAt: 1715676751919,
content: "你好,请问有什么可以帮助您的吗?",
}
];
const roleInfo = { const roleInfo = {
user: { user: {
...@@ -43,6 +29,23 @@ function getId() { ...@@ -43,6 +29,23 @@ function getId() {
} }
const Playground = () => { const Playground = () => {
const { t } = useTranslation();
const defaultMessage = [
{
role: 'user',
id: '2',
createAt: 1715676751919,
content: t('你好'),
},
{
role: 'assistant',
id: '3',
createAt: 1715676751919,
content: t('你好,请问有什么可以帮助您的吗?'),
}
];
const [inputs, setInputs] = useState({ const [inputs, setInputs] = useState({
model: 'gpt-4o-mini', model: 'gpt-4o-mini',
group: '', group: '',
...@@ -65,7 +68,7 @@ const Playground = () => { ...@@ -65,7 +68,7 @@ const Playground = () => {
useEffect(() => { useEffect(() => {
if (searchParams.get('expired')) { if (searchParams.get('expired')) {
showError('未登录或登录已过期,请重新登录!'); showError(t('未登录或登录已过期,请重新登录!'));
} }
let status = localStorage.getItem('status'); let status = localStorage.getItem('status');
if (status) { if (status) {
...@@ -86,7 +89,7 @@ const Playground = () => { ...@@ -86,7 +89,7 @@ const Playground = () => {
})); }));
setModels(localModelOptions); setModels(localModelOptions);
} else { } else {
showError(message); showError(t(message));
} }
}; };
...@@ -115,7 +118,7 @@ const Playground = () => { ...@@ -115,7 +118,7 @@ const Playground = () => {
} }
} else { } else {
localGroupOptions = [{ localGroupOptions = [{
label: '用户分组', label: t('用户分组'),
value: '', value: '',
}]; }];
setGroups(localGroupOptions); setGroups(localGroupOptions);
...@@ -123,7 +126,7 @@ const Playground = () => { ...@@ -123,7 +126,7 @@ const Playground = () => {
setGroups(localGroupOptions); setGroups(localGroupOptions);
handleInputChange('group', localGroupOptions[0].value); handleInputChange('group', localGroupOptions[0].value);
} else { } else {
showError(message); showError(t(message));
} }
}; };
...@@ -314,10 +317,10 @@ const Playground = () => { ...@@ -314,10 +317,10 @@ const Playground = () => {
<Layout.Sider style={{ display: styleState.isMobile ? 'block' : 'initial' }}> <Layout.Sider style={{ display: styleState.isMobile ? 'block' : 'initial' }}>
<Card style={commonOuterStyle}> <Card style={commonOuterStyle}>
<div style={{ marginTop: 10 }}> <div style={{ marginTop: 10 }}>
<Typography.Text strong>分组</Typography.Text> <Typography.Text strong>{t('分组')}</Typography.Text>
</div> </div>
<Select <Select
placeholder={'请选择分组'} placeholder={t('请选择分组')}
name='group' name='group'
required required
selection selection
...@@ -334,10 +337,10 @@ const Playground = () => { ...@@ -334,10 +337,10 @@ const Playground = () => {
}))} }))}
/> />
<div style={{ marginTop: 10 }}> <div style={{ marginTop: 10 }}>
<Typography.Text strong>模型</Typography.Text> <Typography.Text strong>{t('模型')}</Typography.Text>
</div> </div>
<Select <Select
placeholder={'请选择模型'} placeholder={t('请选择模型')}
name='model' name='model'
required required
selection selection
......
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { import {
API, API,
downloadTextAsFile, downloadTextAsFile,
...@@ -22,6 +23,7 @@ import Title from '@douyinfe/semi-ui/lib/es/typography/title'; ...@@ -22,6 +23,7 @@ import Title from '@douyinfe/semi-ui/lib/es/typography/title';
import { Divider } from 'semantic-ui-react'; import { Divider } from 'semantic-ui-react';
const EditRedemption = (props) => { const EditRedemption = (props) => {
const { t } = useTranslation();
const isEdit = props.editingRedemption.id !== undefined; const isEdit = props.editingRedemption.id !== undefined;
const [loading, setLoading] = useState(isEdit); const [loading, setLoading] = useState(isEdit);
...@@ -69,7 +71,7 @@ const EditRedemption = (props) => { ...@@ -69,7 +71,7 @@ const EditRedemption = (props) => {
let name = inputs.name; let name = inputs.name;
if (!isEdit && inputs.name === '') { if (!isEdit && inputs.name === '') {
// set default name // set default name
name = '兑换码-' + renderQuota(quota); name = t('新建兑换码') + ' ' + renderQuota(quota);
} }
setLoading(true); setLoading(true);
let localInputs = inputs; let localInputs = inputs;
...@@ -90,11 +92,11 @@ const EditRedemption = (props) => { ...@@ -90,11 +92,11 @@ const EditRedemption = (props) => {
const { success, message, data } = res.data; const { success, message, data } = res.data;
if (success) { if (success) {
if (isEdit) { if (isEdit) {
showSuccess('兑换码更新成功!'); showSuccess(t('兑换码更新成功!'));
props.refresh(); props.refresh();
props.handleClose(); props.handleClose();
} else { } else {
showSuccess('兑换码创建成功!'); showSuccess(t('兑换码创建成功!'));
setInputs(originInputs); setInputs(originInputs);
props.refresh(); props.refresh();
props.handleClose(); props.handleClose();
...@@ -107,13 +109,12 @@ const EditRedemption = (props) => { ...@@ -107,13 +109,12 @@ const EditRedemption = (props) => {
for (let i = 0; i < data.length; i++) { for (let i = 0; i < data.length; i++) {
text += data[i] + '\n'; text += data[i] + '\n';
} }
// downloadTextAsFile(text, `${inputs.name}.txt`);
Modal.confirm({ Modal.confirm({
title: '兑换码创建成功', title: t('兑换码创建成功'),
content: ( content: (
<div> <div>
<p>兑换码创建成功,是否下载兑换码?</p> <p>{t('兑换码创建成功,是否下载兑换码?')}</p>
<p>兑换码将以文本文件的形式下载,文件名为兑换码的名称。</p> <p>{t('兑换码将以文本文件的形式下载,文件名为兑换码的名称。')}</p>
</div> </div>
), ),
onOk: () => { onOk: () => {
...@@ -130,7 +131,7 @@ const EditRedemption = (props) => { ...@@ -130,7 +131,7 @@ const EditRedemption = (props) => {
placement={isEdit ? 'right' : 'left'} placement={isEdit ? 'right' : 'left'}
title={ title={
<Title level={3}> <Title level={3}>
{isEdit ? '更新兑换码信息' : '创建新的兑换码'} {isEdit ? t('更新兑换码信息') : t('创建新的兑换码')}
</Title> </Title>
} }
headerStyle={{ borderBottom: '1px solid var(--semi-color-border)' }} headerStyle={{ borderBottom: '1px solid var(--semi-color-border)' }}
...@@ -140,7 +141,7 @@ const EditRedemption = (props) => { ...@@ -140,7 +141,7 @@ const EditRedemption = (props) => {
<div style={{ display: 'flex', justifyContent: 'flex-end' }}> <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Space> <Space>
<Button theme='solid' size={'large'} onClick={submit}> <Button theme='solid' size={'large'} onClick={submit}>
提交 {t('提交')}
</Button> </Button>
<Button <Button
theme='solid' theme='solid'
...@@ -148,7 +149,7 @@ const EditRedemption = (props) => { ...@@ -148,7 +149,7 @@ const EditRedemption = (props) => {
type={'tertiary'} type={'tertiary'}
onClick={handleCancel} onClick={handleCancel}
> >
取消 {t('取消')}
</Button> </Button>
</Space> </Space>
</div> </div>
...@@ -160,9 +161,9 @@ const EditRedemption = (props) => { ...@@ -160,9 +161,9 @@ const EditRedemption = (props) => {
<Spin spinning={loading}> <Spin spinning={loading}>
<Input <Input
style={{ marginTop: 20 }} style={{ marginTop: 20 }}
label='名称' label={t('名称')}
name='name' name='name'
placeholder={'请输入名称'} placeholder={t('请输入名称')}
onChange={(value) => handleInputChange('name', value)} onChange={(value) => handleInputChange('name', value)}
value={name} value={name}
autoComplete='new-password' autoComplete='new-password'
...@@ -170,12 +171,12 @@ const EditRedemption = (props) => { ...@@ -170,12 +171,12 @@ const EditRedemption = (props) => {
/> />
<Divider /> <Divider />
<div style={{ marginTop: 20 }}> <div style={{ marginTop: 20 }}>
<Typography.Text>{`额度${renderQuotaWithPrompt(quota)}`}</Typography.Text> <Typography.Text>{t('额度') + renderQuotaWithPrompt(quota)}</Typography.Text>
</div> </div>
<AutoComplete <AutoComplete
style={{ marginTop: 8 }} style={{ marginTop: 8 }}
name='quota' name='quota'
placeholder={'请输入额度'} placeholder={t('请输入额度')}
onChange={(value) => handleInputChange('quota', value)} onChange={(value) => handleInputChange('quota', value)}
value={quota} value={quota}
autoComplete='new-password' autoComplete='new-password'
...@@ -193,12 +194,12 @@ const EditRedemption = (props) => { ...@@ -193,12 +194,12 @@ const EditRedemption = (props) => {
{!isEdit && ( {!isEdit && (
<> <>
<Divider /> <Divider />
<Typography.Text>生成数量</Typography.Text> <Typography.Text>{t('生成数量')}</Typography.Text>
<Input <Input
style={{ marginTop: 8 }} style={{ marginTop: 8 }}
label='生成数量' label={t('生成数量')}
name='count' name='count'
placeholder={'请输入生成数量'} placeholder={t('请输入生成数量')}
onChange={(value) => handleInputChange('count', value)} onChange={(value) => handleInputChange('count', value)}
value={count} value={count}
autoComplete='new-password' autoComplete='new-password'
......
import React from 'react'; import React from 'react';
import RedemptionsTable from '../../components/RedemptionsTable'; import RedemptionsTable from '../../components/RedemptionsTable';
import { Layout } from '@douyinfe/semi-ui'; import { Layout } from '@douyinfe/semi-ui';
import { useTranslation } from 'react-i18next';
const Redemption = () => ( const Redemption = () => {
const { t } = useTranslation();
return (
<> <>
<Layout> <Layout>
<Layout.Header> <Layout.Header>
<h3>管理兑换码</h3> <h3>{t('管理兑换码')}</h3>
</Layout.Header> </Layout.Header>
<Layout.Content> <Layout.Content>
<RedemptionsTable /> <RedemptionsTable />
...@@ -14,5 +17,6 @@ const Redemption = () => ( ...@@ -14,5 +17,6 @@ const Redemption = () => (
</Layout> </Layout>
</> </>
); );
}
export default Redemption; export default Redemption;
...@@ -9,8 +9,10 @@ import { ...@@ -9,8 +9,10 @@ import {
verifyJSON, verifyJSON,
verifyJSONPromise verifyJSONPromise
} from '../../../helpers'; } from '../../../helpers';
import { useTranslation } from 'react-i18next';
export default function SettingsChats(props) { export default function SettingsChats(props) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [inputs, setInputs] = useState({ const [inputs, setInputs] = useState({
Chats: "[]", Chats: "[]",
...@@ -24,7 +26,7 @@ export default function SettingsChats(props) { ...@@ -24,7 +26,7 @@ export default function SettingsChats(props) {
await refForm.current.validate().then(() => { await refForm.current.validate().then(() => {
console.log('Validation passed'); console.log('Validation passed');
const updateArray = compareObjects(inputs, inputsRow); const updateArray = compareObjects(inputs, inputsRow);
if (!updateArray.length) return showWarning('你似乎并没有修改什么'); if (!updateArray.length) return showWarning(t('你似乎并没有修改什么'));
const requestQueue = updateArray.map((item) => { const requestQueue = updateArray.map((item) => {
let value = ''; let value = '';
if (typeof inputs[item.key] === 'boolean') { if (typeof inputs[item.key] === 'boolean') {
...@@ -44,23 +46,23 @@ export default function SettingsChats(props) { ...@@ -44,23 +46,23 @@ export default function SettingsChats(props) {
if (res.includes(undefined)) return; if (res.includes(undefined)) return;
} else if (requestQueue.length > 1) { } else if (requestQueue.length > 1) {
if (res.includes(undefined)) if (res.includes(undefined))
return showError('部分保存失败,请重试'); return showError(t('部分保存失败,请重试'));
} }
showSuccess('保存成功'); showSuccess(t('保存成功'));
props.refresh(); props.refresh();
}) })
.catch(() => { .catch(() => {
showError('保存失败,请重试'); showError(t('保存失败,请重试'));
}) })
.finally(() => { .finally(() => {
setLoading(false); setLoading(false);
}); });
}).catch((error) => { }).catch((error) => {
console.error('Validation failed:', error); console.error('Validation failed:', error);
showError('请检查输入'); showError(t('请检查输入'));
}); });
} catch (error) { } catch (error) {
showError('请检查输入'); showError(t('请检查输入'));
console.error(error); console.error(error);
} }
} }
...@@ -104,19 +106,19 @@ export default function SettingsChats(props) { ...@@ -104,19 +106,19 @@ export default function SettingsChats(props) {
getFormApi={(formAPI) => (refForm.current = formAPI)} getFormApi={(formAPI) => (refForm.current = formAPI)}
style={{ marginBottom: 15 }} style={{ marginBottom: 15 }}
> >
<Form.Section text={'令牌聊天设置'}> <Form.Section text={t('令牌聊天设置')}>
<Banner <Banner
type='warning' type='warning'
description={'必须将上方聊天链接全部设置为空,才能使用下方聊天设置功能'} description={t('必须将上方聊天链接全部设置为空,才能使用下方聊天设置功能')}
/> />
<Banner <Banner
type='info' type='info'
description={'链接中的{key}将自动替换为sk-xxxx,{address}将自动替换为系统设置的服务器地址,末尾不带/和/v1'} description={t('链接中的{key}将自动替换为sk-xxxx,{address}将自动替换为系统设置的服务器地址,末尾不带/和/v1')}
/> />
<Form.TextArea <Form.TextArea
label={'聊天配置'} label={t('聊天配置')}
extraText={''} extraText={''}
placeholder={'为一个 JSON 文本'} placeholder={t('为一个 JSON 文本')}
field={'Chats'} field={'Chats'}
autosize={{ minRows: 6, maxRows: 12 }} autosize={{ minRows: 6, maxRows: 12 }}
trigger='blur' trigger='blur'
...@@ -126,7 +128,7 @@ export default function SettingsChats(props) { ...@@ -126,7 +128,7 @@ export default function SettingsChats(props) {
validator: (rule, value) => { validator: (rule, value) => {
return verifyJSON(value); return verifyJSON(value);
}, },
message: '不是合法的 JSON 字符串' message: t('不是合法的 JSON 字符串')
} }
]} ]}
onChange={(value) => onChange={(value) =>
...@@ -140,7 +142,7 @@ export default function SettingsChats(props) { ...@@ -140,7 +142,7 @@ export default function SettingsChats(props) {
</Form> </Form>
<Space> <Space>
<Button onClick={onSubmit}> <Button onClick={onSubmit}>
保存聊天设置 {t('保存聊天设置')}
</Button> </Button>
</Space> </Space>
</Spin> </Spin>
......
import React, { useEffect, useState, useRef } from 'react'; import React, { useEffect, useState, useRef } from 'react';
import { Button, Col, Form, Row, Spin } from '@douyinfe/semi-ui'; import { Button, Col, Form, Row, Spin } from '@douyinfe/semi-ui';
import { useTranslation } from 'react-i18next';
import { import {
compareObjects, compareObjects,
API, API,
...@@ -9,6 +10,7 @@ import { ...@@ -9,6 +10,7 @@ import {
} from '../../../helpers'; } from '../../../helpers';
export default function SettingsCreditLimit(props) { export default function SettingsCreditLimit(props) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [inputs, setInputs] = useState({ const [inputs, setInputs] = useState({
QuotaForNewUser: '', QuotaForNewUser: '',
...@@ -21,7 +23,7 @@ export default function SettingsCreditLimit(props) { ...@@ -21,7 +23,7 @@ export default function SettingsCreditLimit(props) {
function onSubmit() { function onSubmit() {
const updateArray = compareObjects(inputs, inputsRow); const updateArray = compareObjects(inputs, inputsRow);
if (!updateArray.length) return showWarning('你似乎并没有修改什么'); if (!updateArray.length) return showWarning(t('你似乎并没有修改什么'));
const requestQueue = updateArray.map((item) => { const requestQueue = updateArray.map((item) => {
let value = ''; let value = '';
if (typeof inputs[item.key] === 'boolean') { if (typeof inputs[item.key] === 'boolean') {
...@@ -40,13 +42,13 @@ export default function SettingsCreditLimit(props) { ...@@ -40,13 +42,13 @@ export default function SettingsCreditLimit(props) {
if (requestQueue.length === 1) { if (requestQueue.length === 1) {
if (res.includes(undefined)) return; if (res.includes(undefined)) return;
} else if (requestQueue.length > 1) { } else if (requestQueue.length > 1) {
if (res.includes(undefined)) return showError('部分保存失败,请重试'); if (res.includes(undefined)) return showError(t('部分保存失败,请重试'));
} }
showSuccess('保存成功'); showSuccess(t('保存成功'));
props.refresh(); props.refresh();
}) })
.catch(() => { .catch(() => {
showError('保存失败,请重试'); showError(t('保存失败,请重试'));
}) })
.finally(() => { .finally(() => {
setLoading(false); setLoading(false);
...@@ -72,11 +74,11 @@ export default function SettingsCreditLimit(props) { ...@@ -72,11 +74,11 @@ export default function SettingsCreditLimit(props) {
getFormApi={(formAPI) => (refForm.current = formAPI)} getFormApi={(formAPI) => (refForm.current = formAPI)}
style={{ marginBottom: 15 }} style={{ marginBottom: 15 }}
> >
<Form.Section text={'额度设置'}> <Form.Section text={t('额度设置')}>
<Row gutter={16}> <Row gutter={16}>
<Col span={6}> <Col span={6}>
<Form.InputNumber <Form.InputNumber
label={'新用户初始额度'} label={t('新用户初始额度')}
field={'QuotaForNewUser'} field={'QuotaForNewUser'}
step={1} step={1}
min={0} min={0}
...@@ -92,12 +94,12 @@ export default function SettingsCreditLimit(props) { ...@@ -92,12 +94,12 @@ export default function SettingsCreditLimit(props) {
</Col> </Col>
<Col span={6}> <Col span={6}>
<Form.InputNumber <Form.InputNumber
label={'请求预扣费额度'} label={t('请求预扣费额度')}
field={'PreConsumedQuota'} field={'PreConsumedQuota'}
step={1} step={1}
min={0} min={0}
suffix={'Token'} suffix={'Token'}
extraText={'请求结束后多退少补'} extraText={t('请求结束后多退少补')}
placeholder={''} placeholder={''}
onChange={(value) => onChange={(value) =>
setInputs({ setInputs({
...@@ -109,13 +111,13 @@ export default function SettingsCreditLimit(props) { ...@@ -109,13 +111,13 @@ export default function SettingsCreditLimit(props) {
</Col> </Col>
<Col span={6}> <Col span={6}>
<Form.InputNumber <Form.InputNumber
label={'邀请新用户奖励额度'} label={t('邀请新用户奖励额度')}
field={'QuotaForInviter'} field={'QuotaForInviter'}
step={1} step={1}
min={0} min={0}
suffix={'Token'} suffix={'Token'}
extraText={''} extraText={''}
placeholder={'例如:2000'} placeholder={t('例如:2000')}
onChange={(value) => onChange={(value) =>
setInputs({ setInputs({
...inputs, ...inputs,
...@@ -126,13 +128,13 @@ export default function SettingsCreditLimit(props) { ...@@ -126,13 +128,13 @@ export default function SettingsCreditLimit(props) {
</Col> </Col>
<Col span={6}> <Col span={6}>
<Form.InputNumber <Form.InputNumber
label={'新用户使用邀请码奖励额度'} label={t('新用户使用邀请码奖励额度')}
field={'QuotaForInvitee'} field={'QuotaForInvitee'}
step={1} step={1}
min={0} min={0}
suffix={'Token'} suffix={'Token'}
extraText={''} extraText={''}
placeholder={'例如:1000'} placeholder={t('例如:1000')}
onChange={(value) => onChange={(value) =>
setInputs({ setInputs({
...inputs, ...inputs,
...@@ -145,7 +147,7 @@ export default function SettingsCreditLimit(props) { ...@@ -145,7 +147,7 @@ export default function SettingsCreditLimit(props) {
<Row> <Row>
<Button size='default' onClick={onSubmit}> <Button size='default' onClick={onSubmit}>
保存额度设置 {t('保存额度设置')}
</Button> </Button>
</Row> </Row>
</Form.Section> </Form.Section>
......
import React, { useEffect, useState, useRef } from 'react'; import React, { useEffect, useState, useRef } from 'react';
import { Button, Col, Form, Row, Spin, Tag } from '@douyinfe/semi-ui'; import { Button, Col, Form, Row, Spin } from '@douyinfe/semi-ui';
import { import {
compareObjects, compareObjects,
API, API,
...@@ -7,12 +7,15 @@ import { ...@@ -7,12 +7,15 @@ import {
showSuccess, showSuccess,
showWarning, showWarning,
} from '../../../helpers'; } from '../../../helpers';
import { useTranslation } from 'react-i18next';
export default function DataDashboard(props) { export default function DataDashboard(props) {
const { t } = useTranslation();
const optionsDataExportDefaultTime = [ const optionsDataExportDefaultTime = [
{ key: 'hour', label: '小时', value: 'hour' }, { key: 'hour', label: t('小时'), value: 'hour' },
{ key: 'day', label: '天', value: 'day' }, { key: 'day', label: t('天'), value: 'day' },
{ key: 'week', label: '周', value: 'week' }, { key: 'week', label: t('周'), value: 'week' },
]; ];
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [inputs, setInputs] = useState({ const [inputs, setInputs] = useState({
...@@ -25,7 +28,7 @@ export default function DataDashboard(props) { ...@@ -25,7 +28,7 @@ export default function DataDashboard(props) {
function onSubmit() { function onSubmit() {
const updateArray = compareObjects(inputs, inputsRow); const updateArray = compareObjects(inputs, inputsRow);
if (!updateArray.length) return showWarning('你似乎并没有修改什么'); if (!updateArray.length) return showWarning(t('你似乎并没有修改什么'));
const requestQueue = updateArray.map((item) => { const requestQueue = updateArray.map((item) => {
let value = ''; let value = '';
if (typeof inputs[item.key] === 'boolean') { if (typeof inputs[item.key] === 'boolean') {
...@@ -44,13 +47,13 @@ export default function DataDashboard(props) { ...@@ -44,13 +47,13 @@ export default function DataDashboard(props) {
if (requestQueue.length === 1) { if (requestQueue.length === 1) {
if (res.includes(undefined)) return; if (res.includes(undefined)) return;
} else if (requestQueue.length > 1) { } else if (requestQueue.length > 1) {
if (res.includes(undefined)) return showError('部分保存失败,请重试'); if (res.includes(undefined)) return showError(t('部分保存失败,请重试'));
} }
showSuccess('保存成功'); showSuccess(t('保存成功'));
props.refresh(); props.refresh();
}) })
.catch(() => { .catch(() => {
showError('保存失败,请重试'); showError(t('保存失败,请重试'));
}) })
.finally(() => { .finally(() => {
setLoading(false); setLoading(false);
...@@ -81,12 +84,12 @@ export default function DataDashboard(props) { ...@@ -81,12 +84,12 @@ export default function DataDashboard(props) {
getFormApi={(formAPI) => (refForm.current = formAPI)} getFormApi={(formAPI) => (refForm.current = formAPI)}
style={{ marginBottom: 15 }} style={{ marginBottom: 15 }}
> >
<Form.Section text={'数据看板设置'}> <Form.Section text={t('数据看板设置')}>
<Row gutter={16}> <Row gutter={16}>
<Col span={8}> <Col span={8}>
<Form.Switch <Form.Switch
field={'DataExportEnabled'} field={'DataExportEnabled'}
label={'启用数据看板(实验性)'} label={t('启用数据看板(实验性)')}
size='default' size='default'
checkedText='|' checkedText='|'
uncheckedText='〇' uncheckedText='〇'
...@@ -102,12 +105,12 @@ export default function DataDashboard(props) { ...@@ -102,12 +105,12 @@ export default function DataDashboard(props) {
<Row> <Row>
<Col span={8}> <Col span={8}>
<Form.InputNumber <Form.InputNumber
label={'数据看板更新间隔 '} label={t('数据看板更新间隔')}
step={1} step={1}
min={1} min={1}
suffix={'分钟'} suffix={t('分钟')}
extraText={'设置过短会影响数据库性能'} extraText={t('设置过短会影响数据库性能')}
placeholder={'数据看板更新间隔'} placeholder={t('数据看板更新间隔')}
field={'DataExportInterval'} field={'DataExportInterval'}
onChange={(value) => onChange={(value) =>
setInputs({ setInputs({
...@@ -119,11 +122,11 @@ export default function DataDashboard(props) { ...@@ -119,11 +122,11 @@ export default function DataDashboard(props) {
</Col> </Col>
<Col span={8}> <Col span={8}>
<Form.Select <Form.Select
label='数据看板默认时间粒度' label={t('数据看板默认时间粒度')}
optionList={optionsDataExportDefaultTime} optionList={optionsDataExportDefaultTime}
field={'DataExportDefaultTime'} field={'DataExportDefaultTime'}
extraText={'仅修改展示粒度,统计精确到小时'} extraText={t('仅修改展示粒度,统计精确到小时')}
placeholder={'数据看板默认时间粒度'} placeholder={t('数据看板默认时间粒度')}
style={{ width: 180 }} style={{ width: 180 }}
onChange={(value) => onChange={(value) =>
setInputs({ setInputs({
...@@ -136,7 +139,7 @@ export default function DataDashboard(props) { ...@@ -136,7 +139,7 @@ export default function DataDashboard(props) {
</Row> </Row>
<Row> <Row>
<Button size='default' onClick={onSubmit}> <Button size='default' onClick={onSubmit}>
保存数据看板设置 {t('保存数据看板设置')}
</Button> </Button>
</Row> </Row>
</Form.Section> </Form.Section>
......
...@@ -7,8 +7,10 @@ import { ...@@ -7,8 +7,10 @@ import {
showSuccess, showSuccess,
showWarning, showWarning,
} from '../../../helpers'; } from '../../../helpers';
import { useTranslation } from 'react-i18next';
export default function SettingsDrawing(props) { export default function SettingsDrawing(props) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [inputs, setInputs] = useState({ const [inputs, setInputs] = useState({
DrawingEnabled: false, DrawingEnabled: false,
...@@ -23,7 +25,7 @@ export default function SettingsDrawing(props) { ...@@ -23,7 +25,7 @@ export default function SettingsDrawing(props) {
function onSubmit() { function onSubmit() {
const updateArray = compareObjects(inputs, inputsRow); const updateArray = compareObjects(inputs, inputsRow);
if (!updateArray.length) return showWarning('你似乎并没有修改什么'); if (!updateArray.length) return showWarning(t('你似乎并没有修改什么'));
const requestQueue = updateArray.map((item) => { const requestQueue = updateArray.map((item) => {
let value = ''; let value = '';
if (typeof inputs[item.key] === 'boolean') { if (typeof inputs[item.key] === 'boolean') {
...@@ -42,13 +44,13 @@ export default function SettingsDrawing(props) { ...@@ -42,13 +44,13 @@ export default function SettingsDrawing(props) {
if (requestQueue.length === 1) { if (requestQueue.length === 1) {
if (res.includes(undefined)) return; if (res.includes(undefined)) return;
} else if (requestQueue.length > 1) { } else if (requestQueue.length > 1) {
if (res.includes(undefined)) return showError('部分保存失败,请重试'); if (res.includes(undefined)) return showError(t('部分保存失败,请重试'));
} }
showSuccess('保存成功'); showSuccess(t('保存成功'));
props.refresh(); props.refresh();
}) })
.catch(() => { .catch(() => {
showError('保存失败,请重试'); showError(t('保存失败,请重试'));
}) })
.finally(() => { .finally(() => {
setLoading(false); setLoading(false);
...@@ -67,6 +69,7 @@ export default function SettingsDrawing(props) { ...@@ -67,6 +69,7 @@ export default function SettingsDrawing(props) {
refForm.current.setValues(currentInputs); refForm.current.setValues(currentInputs);
localStorage.setItem('mj_notify_enabled', String(inputs.MjNotifyEnabled)); localStorage.setItem('mj_notify_enabled', String(inputs.MjNotifyEnabled));
}, [props.options]); }, [props.options]);
return ( return (
<> <>
<Spin spinning={loading}> <Spin spinning={loading}>
...@@ -75,12 +78,12 @@ export default function SettingsDrawing(props) { ...@@ -75,12 +78,12 @@ export default function SettingsDrawing(props) {
getFormApi={(formAPI) => (refForm.current = formAPI)} getFormApi={(formAPI) => (refForm.current = formAPI)}
style={{ marginBottom: 15 }} style={{ marginBottom: 15 }}
> >
<Form.Section text={'绘图设置'}> <Form.Section text={t('绘图设置')}>
<Row gutter={16}> <Row gutter={16}>
<Col span={8}> <Col span={8}>
<Form.Switch <Form.Switch
field={'DrawingEnabled'} field={'DrawingEnabled'}
label={'启用绘图功能'} label={t('启用绘图功能')}
size='default' size='default'
checkedText='|' checkedText='|'
uncheckedText='〇' uncheckedText='〇'
...@@ -95,7 +98,7 @@ export default function SettingsDrawing(props) { ...@@ -95,7 +98,7 @@ export default function SettingsDrawing(props) {
<Col span={8}> <Col span={8}>
<Form.Switch <Form.Switch
field={'MjNotifyEnabled'} field={'MjNotifyEnabled'}
label={'允许回调(会泄露服务器 IP 地址)'} label={t('允许回调(会泄露服务器 IP 地址)')}
size='default' size='default'
checkedText='|' checkedText='|'
uncheckedText='〇' uncheckedText='〇'
...@@ -110,7 +113,7 @@ export default function SettingsDrawing(props) { ...@@ -110,7 +113,7 @@ export default function SettingsDrawing(props) {
<Col span={8}> <Col span={8}>
<Form.Switch <Form.Switch
field={'MjAccountFilterEnabled'} field={'MjAccountFilterEnabled'}
label={'允许 AccountFilter 参数'} label={t('允许 AccountFilter 参数')}
size='default' size='default'
checkedText='|' checkedText='|'
uncheckedText='〇' uncheckedText='〇'
...@@ -125,7 +128,7 @@ export default function SettingsDrawing(props) { ...@@ -125,7 +128,7 @@ export default function SettingsDrawing(props) {
<Col span={8}> <Col span={8}>
<Form.Switch <Form.Switch
field={'MjForwardUrlEnabled'} field={'MjForwardUrlEnabled'}
label={'开启之后将上游地址替换为服务器地址'} label={t('开启之后将上游地址替换为服务器地址')}
size='default' size='default'
checkedText='|' checkedText='|'
uncheckedText='〇' uncheckedText='〇'
...@@ -142,8 +145,8 @@ export default function SettingsDrawing(props) { ...@@ -142,8 +145,8 @@ export default function SettingsDrawing(props) {
field={'MjModeClearEnabled'} field={'MjModeClearEnabled'}
label={ label={
<> <>
开启之后会清除用户提示词中的 <Tag>--fast</Tag> {t('开启之后会清除用户提示词中的')} <Tag>--fast</Tag>
<Tag>--relax</Tag> 以及 <Tag>--turbo</Tag> 参数 <Tag>--relax</Tag> {t('以及')} <Tag>--turbo</Tag> {t('参数')}
</> </>
} }
size='default' size='default'
...@@ -160,11 +163,7 @@ export default function SettingsDrawing(props) { ...@@ -160,11 +163,7 @@ export default function SettingsDrawing(props) {
<Col span={8}> <Col span={8}>
<Form.Switch <Form.Switch
field={'MjActionCheckSuccessEnabled'} field={'MjActionCheckSuccessEnabled'}
label={ label={t('检测必须等待绘图成功才能进行放大等操作')}
<>
检测必须等待绘图成功才能进行放大等操作
</>
}
size='default' size='default'
checkedText='|' checkedText='|'
uncheckedText='〇' uncheckedText='〇'
...@@ -179,7 +178,7 @@ export default function SettingsDrawing(props) { ...@@ -179,7 +178,7 @@ export default function SettingsDrawing(props) {
</Row> </Row>
<Row> <Row>
<Button size='default' onClick={onSubmit}> <Button size='default' onClick={onSubmit}>
保存绘图设置 {t('保存绘图设置')}
</Button> </Button>
</Row> </Row>
</Form.Section> </Form.Section>
......
...@@ -7,8 +7,10 @@ import { ...@@ -7,8 +7,10 @@ import {
showSuccess, showSuccess,
showWarning, showWarning,
} from '../../../helpers'; } from '../../../helpers';
import { useTranslation } from 'react-i18next';
export default function GeneralSettings(props) { export default function GeneralSettings(props) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [inputs, setInputs] = useState({ const [inputs, setInputs] = useState({
TopUpLink: '', TopUpLink: '',
...@@ -22,13 +24,15 @@ export default function GeneralSettings(props) { ...@@ -22,13 +24,15 @@ export default function GeneralSettings(props) {
}); });
const refForm = useRef(); const refForm = useRef();
const [inputsRow, setInputsRow] = useState(inputs); const [inputsRow, setInputsRow] = useState(inputs);
function onChange(value, e) { function onChange(value, e) {
const name = e.target.id; const name = e.target.id;
setInputs((inputs) => ({ ...inputs, [name]: value })); setInputs((inputs) => ({ ...inputs, [name]: value }));
} }
function onSubmit() { function onSubmit() {
const updateArray = compareObjects(inputs, inputsRow); const updateArray = compareObjects(inputs, inputsRow);
if (!updateArray.length) return showWarning('你似乎并没有修改什么'); if (!updateArray.length) return showWarning(t('你似乎并没有修改什么'));
const requestQueue = updateArray.map((item) => { const requestQueue = updateArray.map((item) => {
let value = ''; let value = '';
if (typeof inputs[item.key] === 'boolean') { if (typeof inputs[item.key] === 'boolean') {
...@@ -47,13 +51,13 @@ export default function GeneralSettings(props) { ...@@ -47,13 +51,13 @@ export default function GeneralSettings(props) {
if (requestQueue.length === 1) { if (requestQueue.length === 1) {
if (res.includes(undefined)) return; if (res.includes(undefined)) return;
} else if (requestQueue.length > 1) { } else if (requestQueue.length > 1) {
if (res.includes(undefined)) return showError('部分保存失败,请重试'); if (res.includes(undefined)) return showError(t('部分保存失败,请重试'));
} }
showSuccess('保存成功'); showSuccess(t('保存成功'));
props.refresh(); props.refresh();
}) })
.catch(() => { .catch(() => {
showError('保存失败,请重试'); showError(t('保存失败,请重试'));
}) })
.finally(() => { .finally(() => {
setLoading(false); setLoading(false);
...@@ -71,26 +75,27 @@ export default function GeneralSettings(props) { ...@@ -71,26 +75,27 @@ export default function GeneralSettings(props) {
setInputsRow(structuredClone(currentInputs)); setInputsRow(structuredClone(currentInputs));
refForm.current.setValues(currentInputs); refForm.current.setValues(currentInputs);
}, [props.options]); }, [props.options]);
return ( return (
<> <>
<Spin spinning={loading}> <Spin spinning={loading}>
<Banner <Banner
type='warning' type='warning'
description={'聊天链接功能已经弃用,请使用下方聊天设置功能'} description={t('聊天链接功能已经弃用,请使用下方聊天设置功能')}
/> />
<Form <Form
values={inputs} values={inputs}
getFormApi={(formAPI) => (refForm.current = formAPI)} getFormApi={(formAPI) => (refForm.current = formAPI)}
style={{ marginBottom: 15 }} style={{ marginBottom: 15 }}
> >
<Form.Section text={'通用设置'}> <Form.Section text={t('通用设置')}>
<Row gutter={16}> <Row gutter={16}>
<Col span={8}> <Col span={8}>
<Form.Input <Form.Input
field={'TopUpLink'} field={'TopUpLink'}
label={'充值链接'} label={t('充值链接')}
initValue={''} initValue={''}
placeholder={'例如发卡网站的购买链接'} placeholder={t('例如发卡网站的购买链接')}
onChange={onChange} onChange={onChange}
showClear showClear
/> />
...@@ -98,9 +103,9 @@ export default function GeneralSettings(props) { ...@@ -98,9 +103,9 @@ export default function GeneralSettings(props) {
<Col span={8}> <Col span={8}>
<Form.Input <Form.Input
field={'ChatLink'} field={'ChatLink'}
label={'默认聊天页面链接'} label={t('默认聊天页面链接')}
initValue={''} initValue={''}
placeholder='例如 ChatGPT Next Web 的部署地址' placeholder={t('例如 ChatGPT Next Web 的部署地址')}
onChange={onChange} onChange={onChange}
showClear showClear
/> />
...@@ -108,9 +113,9 @@ export default function GeneralSettings(props) { ...@@ -108,9 +113,9 @@ export default function GeneralSettings(props) {
<Col span={8}> <Col span={8}>
<Form.Input <Form.Input
field={'ChatLink2'} field={'ChatLink2'}
label={'聊天页面 2 链接'} label={t('聊天页面 2 链接')}
initValue={''} initValue={''}
placeholder='例如 ChatGPT Next Web 的部署地址' placeholder={t('例如 ChatGPT Next Web 的部署地址')}
onChange={onChange} onChange={onChange}
showClear showClear
/> />
...@@ -118,9 +123,9 @@ export default function GeneralSettings(props) { ...@@ -118,9 +123,9 @@ export default function GeneralSettings(props) {
<Col span={8}> <Col span={8}>
<Form.Input <Form.Input
field={'QuotaPerUnit'} field={'QuotaPerUnit'}
label={'单位美元额度'} label={t('单位美元额度')}
initValue={''} initValue={''}
placeholder='一单位货币能兑换的额度' placeholder={t('一单位货币能兑换的额度')}
onChange={onChange} onChange={onChange}
showClear showClear
/> />
...@@ -128,9 +133,9 @@ export default function GeneralSettings(props) { ...@@ -128,9 +133,9 @@ export default function GeneralSettings(props) {
<Col span={8}> <Col span={8}>
<Form.Input <Form.Input
field={'RetryTimes'} field={'RetryTimes'}
label={'失败重试次数'} label={t('失败重试次数')}
initValue={''} initValue={''}
placeholder='失败重试次数' placeholder={t('失败重试次数')}
onChange={onChange} onChange={onChange}
showClear showClear
/> />
...@@ -140,7 +145,7 @@ export default function GeneralSettings(props) { ...@@ -140,7 +145,7 @@ export default function GeneralSettings(props) {
<Col span={8}> <Col span={8}>
<Form.Switch <Form.Switch
field={'DisplayInCurrencyEnabled'} field={'DisplayInCurrencyEnabled'}
label={'以货币形式显示额度'} label={t('以货币形式显示额度')}
size='default' size='default'
checkedText='|' checkedText='|'
uncheckedText='〇' uncheckedText='〇'
...@@ -155,7 +160,7 @@ export default function GeneralSettings(props) { ...@@ -155,7 +160,7 @@ export default function GeneralSettings(props) {
<Col span={8}> <Col span={8}>
<Form.Switch <Form.Switch
field={'DisplayTokenStatEnabled'} field={'DisplayTokenStatEnabled'}
label={'Billing 相关 API 显示令牌额度而非用户额度'} label={t('额度查询接口返回令牌额度而非用户额度')}
size='default' size='default'
checkedText='|' checkedText='|'
uncheckedText='〇' uncheckedText='〇'
...@@ -170,7 +175,7 @@ export default function GeneralSettings(props) { ...@@ -170,7 +175,7 @@ export default function GeneralSettings(props) {
<Col span={8}> <Col span={8}>
<Form.Switch <Form.Switch
field={'DefaultCollapseSidebar'} field={'DefaultCollapseSidebar'}
label={'默认折叠侧边栏'} label={t('默认折叠侧边栏')}
size='default' size='default'
checkedText='|' checkedText='|'
uncheckedText='〇' uncheckedText='〇'
...@@ -185,7 +190,7 @@ export default function GeneralSettings(props) { ...@@ -185,7 +190,7 @@ export default function GeneralSettings(props) {
</Row> </Row>
<Row> <Row>
<Button size='default' onClick={onSubmit}> <Button size='default' onClick={onSubmit}>
保存通用设置 {t('保存通用设置')}
</Button> </Button>
</Row> </Row>
</Form.Section> </Form.Section>
......
import React, { useEffect, useState, useRef } from 'react'; import React, { useEffect, useState, useRef } from 'react';
import { Button, Col, Form, Row, Spin, DatePicker } from '@douyinfe/semi-ui'; import { Button, Col, Form, Row, Spin, DatePicker } from '@douyinfe/semi-ui';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useTranslation } from 'react-i18next';
import { import {
compareObjects, compareObjects,
API, API,
...@@ -10,6 +11,7 @@ import { ...@@ -10,6 +11,7 @@ import {
} from '../../../helpers'; } from '../../../helpers';
export default function SettingsLog(props) { export default function SettingsLog(props) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [loadingCleanHistoryLog, setLoadingCleanHistoryLog] = useState(false); const [loadingCleanHistoryLog, setLoadingCleanHistoryLog] = useState(false);
const [inputs, setInputs] = useState({ const [inputs, setInputs] = useState({
...@@ -24,7 +26,7 @@ export default function SettingsLog(props) { ...@@ -24,7 +26,7 @@ export default function SettingsLog(props) {
(item) => item.key !== 'historyTimestamp', (item) => item.key !== 'historyTimestamp',
); );
if (!updateArray.length) return showWarning('你似乎并没有修改什么'); if (!updateArray.length) return showWarning(t('你似乎并没有修改什么'));
const requestQueue = updateArray.map((item) => { const requestQueue = updateArray.map((item) => {
let value = ''; let value = '';
if (typeof inputs[item.key] === 'boolean') { if (typeof inputs[item.key] === 'boolean') {
...@@ -43,13 +45,13 @@ export default function SettingsLog(props) { ...@@ -43,13 +45,13 @@ export default function SettingsLog(props) {
if (requestQueue.length === 1) { if (requestQueue.length === 1) {
if (res.includes(undefined)) return; if (res.includes(undefined)) return;
} else if (requestQueue.length > 1) { } else if (requestQueue.length > 1) {
if (res.includes(undefined)) return showError('部分保存失败,请重试'); if (res.includes(undefined)) return showError(t('部分保存失败,请重试'));
} }
showSuccess('保存成功'); showSuccess(t('保存成功'));
props.refresh(); props.refresh();
}) })
.catch(() => { .catch(() => {
showError('保存失败,请重试'); showError(t('保存失败,请重试'));
}) })
.finally(() => { .finally(() => {
setLoading(false); setLoading(false);
...@@ -58,16 +60,16 @@ export default function SettingsLog(props) { ...@@ -58,16 +60,16 @@ export default function SettingsLog(props) {
async function onCleanHistoryLog() { async function onCleanHistoryLog() {
try { try {
setLoadingCleanHistoryLog(true); setLoadingCleanHistoryLog(true);
if (!inputs.historyTimestamp) throw new Error('请选择日志记录时间'); if (!inputs.historyTimestamp) throw new Error(t('请选择日志记录时间'));
const res = await API.delete( const res = await API.delete(
`/api/log/?target_timestamp=${Date.parse(inputs.historyTimestamp) / 1000}`, `/api/log/?target_timestamp=${Date.parse(inputs.historyTimestamp) / 1000}`,
); );
const { success, message, data } = res.data; const { success, message, data } = res.data;
if (success) { if (success) {
showSuccess(`${data} 条日志已清理!`); showSuccess(`${data} ${t('条日志已清理!')}`);
return; return;
} else { } else {
throw new Error('日志清理失败:' + message); throw new Error(t('日志清理失败:') + message);
} }
} catch (error) { } catch (error) {
showError(error.message); showError(error.message);
...@@ -96,12 +98,12 @@ export default function SettingsLog(props) { ...@@ -96,12 +98,12 @@ export default function SettingsLog(props) {
getFormApi={(formAPI) => (refForm.current = formAPI)} getFormApi={(formAPI) => (refForm.current = formAPI)}
style={{ marginBottom: 15 }} style={{ marginBottom: 15 }}
> >
<Form.Section text={'日志设置'}> <Form.Section text={t('日志设置')}>
<Row gutter={16}> <Row gutter={16}>
<Col span={8}> <Col span={8}>
<Form.Switch <Form.Switch
field={'LogConsumeEnabled'} field={'LogConsumeEnabled'}
label={'启用额度消费日志记录'} label={t('启用额度消费日志记录')}
size='default' size='default'
checkedText='|' checkedText='|'
uncheckedText='〇' uncheckedText='〇'
...@@ -116,7 +118,7 @@ export default function SettingsLog(props) { ...@@ -116,7 +118,7 @@ export default function SettingsLog(props) {
<Col span={8}> <Col span={8}>
<Spin spinning={loadingCleanHistoryLog}> <Spin spinning={loadingCleanHistoryLog}>
<Form.DatePicker <Form.DatePicker
label='日志记录时间' label={t('日志记录时间')}
field={'historyTimestamp'} field={'historyTimestamp'}
type='dateTime' type='dateTime'
inputReadOnly={true} inputReadOnly={true}
...@@ -128,7 +130,7 @@ export default function SettingsLog(props) { ...@@ -128,7 +130,7 @@ export default function SettingsLog(props) {
}} }}
/> />
<Button size='default' onClick={onCleanHistoryLog}> <Button size='default' onClick={onCleanHistoryLog}>
清除历史日志 {t('清除历史日志')}
</Button> </Button>
</Spin> </Spin>
</Col> </Col>
...@@ -136,7 +138,7 @@ export default function SettingsLog(props) { ...@@ -136,7 +138,7 @@ export default function SettingsLog(props) {
<Row> <Row>
<Button size='default' onClick={onSubmit}> <Button size='default' onClick={onSubmit}>
保存日志设置 {t('保存日志设置')}
</Button> </Button>
</Row> </Row>
</Form.Section> </Form.Section>
......
...@@ -7,8 +7,10 @@ import { ...@@ -7,8 +7,10 @@ import {
showSuccess, showSuccess,
showWarning, showWarning,
} from '../../../helpers'; } from '../../../helpers';
import { useTranslation } from 'react-i18next';
export default function SettingsMonitoring(props) { export default function SettingsMonitoring(props) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [inputs, setInputs] = useState({ const [inputs, setInputs] = useState({
ChannelDisableThreshold: '', ChannelDisableThreshold: '',
...@@ -21,7 +23,7 @@ export default function SettingsMonitoring(props) { ...@@ -21,7 +23,7 @@ export default function SettingsMonitoring(props) {
function onSubmit() { function onSubmit() {
const updateArray = compareObjects(inputs, inputsRow); const updateArray = compareObjects(inputs, inputsRow);
if (!updateArray.length) return showWarning('你似乎并没有修改什么'); if (!updateArray.length) return showWarning(t('你似乎并没有修改什么'));
const requestQueue = updateArray.map((item) => { const requestQueue = updateArray.map((item) => {
let value = ''; let value = '';
if (typeof inputs[item.key] === 'boolean') { if (typeof inputs[item.key] === 'boolean') {
...@@ -40,13 +42,13 @@ export default function SettingsMonitoring(props) { ...@@ -40,13 +42,13 @@ export default function SettingsMonitoring(props) {
if (requestQueue.length === 1) { if (requestQueue.length === 1) {
if (res.includes(undefined)) return; if (res.includes(undefined)) return;
} else if (requestQueue.length > 1) { } else if (requestQueue.length > 1) {
if (res.includes(undefined)) return showError('部分保存失败,请重试'); if (res.includes(undefined)) return showError(t('部分保存失败,请重试'));
} }
showSuccess('保存成功'); showSuccess(t('保存成功'));
props.refresh(); props.refresh();
}) })
.catch(() => { .catch(() => {
showError('保存失败,请重试'); showError(t('保存失败,请重试'));
}) })
.finally(() => { .finally(() => {
setLoading(false); setLoading(false);
...@@ -64,6 +66,7 @@ export default function SettingsMonitoring(props) { ...@@ -64,6 +66,7 @@ export default function SettingsMonitoring(props) {
setInputsRow(structuredClone(currentInputs)); setInputsRow(structuredClone(currentInputs));
refForm.current.setValues(currentInputs); refForm.current.setValues(currentInputs);
}, [props.options]); }, [props.options]);
return ( return (
<> <>
<Spin spinning={loading}> <Spin spinning={loading}>
...@@ -72,15 +75,15 @@ export default function SettingsMonitoring(props) { ...@@ -72,15 +75,15 @@ export default function SettingsMonitoring(props) {
getFormApi={(formAPI) => (refForm.current = formAPI)} getFormApi={(formAPI) => (refForm.current = formAPI)}
style={{ marginBottom: 15 }} style={{ marginBottom: 15 }}
> >
<Form.Section text={'监控设置'}> <Form.Section text={t('监控设置')}>
<Row gutter={16}> <Row gutter={16}>
<Col span={8}> <Col span={8}>
<Form.InputNumber <Form.InputNumber
label={'最长响应时间'} label={t('最长响应时间')}
step={1} step={1}
min={0} min={0}
suffix={'秒'} suffix={t('秒')}
extraText={'当运行通道全部测试时,超过此时间将自动禁用通道'} extraText={t('当运行通道全部测试时,超过此时间将自动禁用通道')}
placeholder={''} placeholder={''}
field={'ChannelDisableThreshold'} field={'ChannelDisableThreshold'}
onChange={(value) => onChange={(value) =>
...@@ -93,11 +96,11 @@ export default function SettingsMonitoring(props) { ...@@ -93,11 +96,11 @@ export default function SettingsMonitoring(props) {
</Col> </Col>
<Col span={8}> <Col span={8}>
<Form.InputNumber <Form.InputNumber
label={'额度提醒阈值'} label={t('额度提醒阈值')}
step={1} step={1}
min={0} min={0}
suffix={'Token'} suffix={'Token'}
extraText={'低于此额度时将发送邮件提醒用户'} extraText={t('低于此额度时将发送邮件提醒用户')}
placeholder={''} placeholder={''}
field={'QuotaRemindThreshold'} field={'QuotaRemindThreshold'}
onChange={(value) => onChange={(value) =>
...@@ -113,7 +116,7 @@ export default function SettingsMonitoring(props) { ...@@ -113,7 +116,7 @@ export default function SettingsMonitoring(props) {
<Col span={8}> <Col span={8}>
<Form.Switch <Form.Switch
field={'AutomaticDisableChannelEnabled'} field={'AutomaticDisableChannelEnabled'}
label={'失败时自动禁用通道'} label={t('失败时自动禁用通道')}
size='default' size='default'
checkedText='|' checkedText='|'
uncheckedText='〇' uncheckedText='〇'
...@@ -128,7 +131,7 @@ export default function SettingsMonitoring(props) { ...@@ -128,7 +131,7 @@ export default function SettingsMonitoring(props) {
<Col span={8}> <Col span={8}>
<Form.Switch <Form.Switch
field={'AutomaticEnableChannelEnabled'} field={'AutomaticEnableChannelEnabled'}
label={'成功时自动启用通道'} label={t('成功时自动启用通道')}
size='default' size='default'
checkedText='|' checkedText='|'
uncheckedText='〇' uncheckedText='〇'
...@@ -143,7 +146,7 @@ export default function SettingsMonitoring(props) { ...@@ -143,7 +146,7 @@ export default function SettingsMonitoring(props) {
</Row> </Row>
<Row> <Row>
<Button size='default' onClick={onSubmit}> <Button size='default' onClick={onSubmit}>
保存监控设置 {t('保存监控设置')}
</Button> </Button>
</Row> </Row>
</Form.Section> </Form.Section>
......
...@@ -7,8 +7,10 @@ import { ...@@ -7,8 +7,10 @@ import {
showSuccess, showSuccess,
showWarning, showWarning,
} from '../../../helpers'; } from '../../../helpers';
import { useTranslation } from 'react-i18next';
export default function SettingsSensitiveWords(props) { export default function SettingsSensitiveWords(props) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [inputs, setInputs] = useState({ const [inputs, setInputs] = useState({
CheckSensitiveEnabled: false, CheckSensitiveEnabled: false,
...@@ -20,7 +22,7 @@ export default function SettingsSensitiveWords(props) { ...@@ -20,7 +22,7 @@ export default function SettingsSensitiveWords(props) {
function onSubmit() { function onSubmit() {
const updateArray = compareObjects(inputs, inputsRow); const updateArray = compareObjects(inputs, inputsRow);
if (!updateArray.length) return showWarning('你似乎并没有修改什么'); if (!updateArray.length) return showWarning(t('你似乎并没有修改什么'));
const requestQueue = updateArray.map((item) => { const requestQueue = updateArray.map((item) => {
let value = ''; let value = '';
if (typeof inputs[item.key] === 'boolean') { if (typeof inputs[item.key] === 'boolean') {
...@@ -39,13 +41,13 @@ export default function SettingsSensitiveWords(props) { ...@@ -39,13 +41,13 @@ export default function SettingsSensitiveWords(props) {
if (requestQueue.length === 1) { if (requestQueue.length === 1) {
if (res.includes(undefined)) return; if (res.includes(undefined)) return;
} else if (requestQueue.length > 1) { } else if (requestQueue.length > 1) {
if (res.includes(undefined)) return showError('部分保存失败,请重试'); if (res.includes(undefined)) return showError(t('部分保存失败,请重试'));
} }
showSuccess('保存成功'); showSuccess(t('保存成功'));
props.refresh(); props.refresh();
}) })
.catch(() => { .catch(() => {
showError('保存失败,请重试'); showError(t('保存失败,请重试'));
}) })
.finally(() => { .finally(() => {
setLoading(false); setLoading(false);
...@@ -71,12 +73,12 @@ export default function SettingsSensitiveWords(props) { ...@@ -71,12 +73,12 @@ export default function SettingsSensitiveWords(props) {
getFormApi={(formAPI) => (refForm.current = formAPI)} getFormApi={(formAPI) => (refForm.current = formAPI)}
style={{ marginBottom: 15 }} style={{ marginBottom: 15 }}
> >
<Form.Section text={'屏蔽词过滤设置'}> <Form.Section text={t('屏蔽词过滤设置')}>
<Row gutter={16}> <Row gutter={16}>
<Col span={8}> <Col span={8}>
<Form.Switch <Form.Switch
field={'CheckSensitiveEnabled'} field={'CheckSensitiveEnabled'}
label={'启用屏蔽词过滤功能'} label={t('启用屏蔽词过滤功能')}
size='default' size='default'
checkedText='|' checkedText='|'
uncheckedText='〇' uncheckedText='〇'
...@@ -91,7 +93,7 @@ export default function SettingsSensitiveWords(props) { ...@@ -91,7 +93,7 @@ export default function SettingsSensitiveWords(props) {
<Col span={8}> <Col span={8}>
<Form.Switch <Form.Switch
field={'CheckSensitiveOnPromptEnabled'} field={'CheckSensitiveOnPromptEnabled'}
label={'启用 Prompt 检查'} label={t('启用 Prompt 检查')}
size='default' size='default'
checkedText='|' checkedText='|'
uncheckedText='〇' uncheckedText='〇'
...@@ -107,9 +109,9 @@ export default function SettingsSensitiveWords(props) { ...@@ -107,9 +109,9 @@ export default function SettingsSensitiveWords(props) {
<Row> <Row>
<Col span={16}> <Col span={16}>
<Form.TextArea <Form.TextArea
label={'屏蔽词列表'} label={t('屏蔽词列表')}
extraText={'一行一个屏蔽词,不需要符号分割'} extraText={t('一行一个屏蔽词,不需要符号分割')}
placeholder={'一行一个屏蔽词,不需要符号分割'} placeholder={t('一行一个屏蔽词,不需要符号分割')}
field={'SensitiveWords'} field={'SensitiveWords'}
onChange={(value) => onChange={(value) =>
setInputs({ setInputs({
...@@ -124,7 +126,7 @@ export default function SettingsSensitiveWords(props) { ...@@ -124,7 +126,7 @@ export default function SettingsSensitiveWords(props) {
</Row> </Row>
<Row> <Row>
<Button size='default' onClick={onSubmit}> <Button size='default' onClick={onSubmit}>
保存屏蔽词过滤设置 {t('保存屏蔽词过滤设置')}
</Button> </Button>
</Row> </Row>
</Form.Section> </Form.Section>
......
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Layout, TabPane, Tabs } from '@douyinfe/semi-ui'; import { Layout, TabPane, Tabs } from '@douyinfe/semi-ui';
import { useNavigate, useLocation } from 'react-router-dom'; import { useNavigate, useLocation } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import SystemSetting from '../../components/SystemSetting'; import SystemSetting from '../../components/SystemSetting';
import { isRoot } from '../../helpers'; import { isRoot } from '../../helpers';
import OtherSetting from '../../components/OtherSetting'; import OtherSetting from '../../components/OtherSetting';
import PersonalSetting from '../../components/PersonalSetting'; import PersonalSetting from '../../components/PersonalSetting';
import OperationSetting from '../../components/OperationSetting'; import OperationSetting from '../../components/OperationSetting';
const Setting = () => { const Setting = () => {
const { t } = useTranslation();
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
const [tabActiveKey, setTabActiveKey] = useState('1'); const [tabActiveKey, setTabActiveKey] = useState('1');
let panes = [ let panes = [
{ {
tab: '个人设置', tab: t('个人设置'),
content: <PersonalSetting />, content: <PersonalSetting />,
itemKey: 'personal', itemKey: 'personal',
}, },
...@@ -21,17 +24,17 @@ const Setting = () => { ...@@ -21,17 +24,17 @@ const Setting = () => {
if (isRoot()) { if (isRoot()) {
panes.push({ panes.push({
tab: '运营设置', tab: t('运营设置'),
content: <OperationSetting />, content: <OperationSetting />,
itemKey: 'operation', itemKey: 'operation',
}); });
panes.push({ panes.push({
tab: '系统设置', tab: t('系统设置'),
content: <SystemSetting />, content: <SystemSetting />,
itemKey: 'system', itemKey: 'system',
}); });
panes.push({ panes.push({
tab: '其他设置', tab: t('其他设置'),
content: <OtherSetting />, content: <OtherSetting />,
itemKey: 'other', itemKey: 'other',
}); });
......
import React from 'react'; import React from 'react';
import TokensTable from '../../components/TokensTable'; import TokensTable from '../../components/TokensTable';
import { Banner, Layout } from '@douyinfe/semi-ui'; import { Banner, Layout } from '@douyinfe/semi-ui';
const Token = () => ( import { useTranslation } from 'react-i18next';
const Token = () => {
const { t } = useTranslation();
return (
<> <>
<Layout> <Layout>
<Layout.Header> <Layout.Header>
<Banner <Banner
type='warning' type='warning'
description='令牌无法精确控制使用额度,请勿直接将令牌分发给用户。' description={t('令牌无法精确控制使用额度,只允许自用,请勿直接将令牌分发给他人。')}
/> />
</Layout.Header> </Layout.Header>
<Layout.Content> <Layout.Content>
...@@ -15,6 +18,7 @@ const Token = () => ( ...@@ -15,6 +18,7 @@ const Token = () => (
</Layout.Content> </Layout.Content>
</Layout> </Layout>
</> </>
); );
};
export default Token; export default Token;
...@@ -21,8 +21,10 @@ import { ...@@ -21,8 +21,10 @@ import {
import Title from '@douyinfe/semi-ui/lib/es/typography/title'; import Title from '@douyinfe/semi-ui/lib/es/typography/title';
import Text from '@douyinfe/semi-ui/lib/es/typography/text'; import Text from '@douyinfe/semi-ui/lib/es/typography/text';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
const TopUp = () => { const TopUp = () => {
const { t } = useTranslation();
const [redemptionCode, setRedemptionCode] = useState(''); const [redemptionCode, setRedemptionCode] = useState('');
const [topUpCode, setTopUpCode] = useState(''); const [topUpCode, setTopUpCode] = useState('');
const [topUpCount, setTopUpCount] = useState(0); const [topUpCount, setTopUpCount] = useState(0);
...@@ -38,7 +40,7 @@ const TopUp = () => { ...@@ -38,7 +40,7 @@ const TopUp = () => {
const topUp = async () => { const topUp = async () => {
if (redemptionCode === '') { if (redemptionCode === '') {
showInfo('请输入兑换码!'); showInfo(t('请输入兑换码!'));
return; return;
} }
setIsSubmitting(true); setIsSubmitting(true);
...@@ -48,10 +50,10 @@ const TopUp = () => { ...@@ -48,10 +50,10 @@ const TopUp = () => {
}); });
const { success, message, data } = res.data; const { success, message, data } = res.data;
if (success) { if (success) {
showSuccess('兑换成功!'); showSuccess(t('兑换成功!'));
Modal.success({ Modal.success({
title: '兑换成功!', title: t('兑换成功!'),
content: '成功兑换额度:' + renderQuota(data), content: t('成功兑换额度:') + renderQuota(data),
centered: true, centered: true,
}); });
setUserQuota((quota) => { setUserQuota((quota) => {
...@@ -62,7 +64,7 @@ const TopUp = () => { ...@@ -62,7 +64,7 @@ const TopUp = () => {
showError(message); showError(message);
} }
} catch (err) { } catch (err) {
showError('请求失败'); showError(t('请求失败'));
} finally { } finally {
setIsSubmitting(false); setIsSubmitting(false);
} }
...@@ -70,7 +72,7 @@ const TopUp = () => { ...@@ -70,7 +72,7 @@ const TopUp = () => {
const openTopUpLink = () => { const openTopUpLink = () => {
if (!topUpLink) { if (!topUpLink) {
showError('超级管理员未设置充值链接!'); showError(t('超级管理员未设置充值链接!'));
return; return;
} }
window.open(topUpLink, '_blank'); window.open(topUpLink, '_blank');
...@@ -78,12 +80,12 @@ const TopUp = () => { ...@@ -78,12 +80,12 @@ const TopUp = () => {
const preTopUp = async (payment) => { const preTopUp = async (payment) => {
if (!enableOnlineTopUp) { if (!enableOnlineTopUp) {
showError('管理员未开启在线充值!'); showError(t('管理员未开启在线充值!'));
return; return;
} }
await getAmount(); await getAmount();
if (topUpCount < minTopUp) { if (topUpCount < minTopUp) {
showError('充值数量不能小于' + minTopUp); showError(t('充值数量不能小于') + minTopUp);
return; return;
} }
setPayWay(payment); setPayWay(payment);
...@@ -174,7 +176,7 @@ const TopUp = () => { ...@@ -174,7 +176,7 @@ const TopUp = () => {
const renderAmount = () => { const renderAmount = () => {
// console.log(amount); // console.log(amount);
return amount + '元'; return amount + ' ' + t('元');
}; };
const getAmount = async (value) => { const getAmount = async (value) => {
...@@ -214,11 +216,11 @@ const TopUp = () => { ...@@ -214,11 +216,11 @@ const TopUp = () => {
<div> <div>
<Layout> <Layout>
<Layout.Header> <Layout.Header>
<h3>我的钱包</h3> <h3>{t('我的钱包')}</h3>
</Layout.Header> </Layout.Header>
<Layout.Content> <Layout.Content>
<Modal <Modal
title='确定要充值吗' title={t('确定要充值吗')}
visible={open} visible={open}
onOk={onlineTopUp} onOk={onlineTopUp}
onCancel={handleCancel} onCancel={handleCancel}
...@@ -226,24 +228,24 @@ const TopUp = () => { ...@@ -226,24 +228,24 @@ const TopUp = () => {
size={'small'} size={'small'}
centered={true} centered={true}
> >
<p>充值数量{topUpCount}</p> <p>{t('充值数量')}{topUpCount}</p>
<p>实付金额{renderAmount()}</p> <p>{t('实付金额')}{renderAmount()}</p>
<p>是否确认充值?</p> <p>{t('是否确认充值?')}</p>
</Modal> </Modal>
<div <div
style={{ marginTop: 20, display: 'flex', justifyContent: 'center' }} style={{ marginTop: 20, display: 'flex', justifyContent: 'center' }}
> >
<Card style={{ width: '500px', padding: '20px' }}> <Card style={{ width: '500px', padding: '20px' }}>
<Title level={3} style={{ textAlign: 'center' }}> <Title level={3} style={{ textAlign: 'center' }}>
余额 {renderQuota(userQuota)} {t('余额')} {renderQuota(userQuota)}
</Title> </Title>
<div style={{ marginTop: 20 }}> <div style={{ marginTop: 20 }}>
<Divider>兑换余额</Divider> <Divider>{t('兑换余额')}</Divider>
<Form> <Form>
<Form.Input <Form.Input
field={'redemptionCode'} field={'redemptionCode'}
label={'兑换码'} label={t('兑换码')}
placeholder='兑换码' placeholder={t('兑换码')}
name='redemptionCode' name='redemptionCode'
value={redemptionCode} value={redemptionCode}
onChange={(value) => { onChange={(value) => {
...@@ -257,7 +259,7 @@ const TopUp = () => { ...@@ -257,7 +259,7 @@ const TopUp = () => {
theme={'solid'} theme={'solid'}
onClick={openTopUpLink} onClick={openTopUpLink}
> >
获取兑换码 {t('获取兑换码')}
</Button> </Button>
) : null} ) : null}
<Button <Button
...@@ -266,21 +268,19 @@ const TopUp = () => { ...@@ -266,21 +268,19 @@ const TopUp = () => {
onClick={topUp} onClick={topUp}
disabled={isSubmitting} disabled={isSubmitting}
> >
{isSubmitting ? '兑换中...' : '兑换'} {isSubmitting ? t('兑换中...') : t('兑换')}
</Button> </Button>
</Space> </Space>
</Form> </Form>
</div> </div>
<div style={{ marginTop: 20 }}> <div style={{ marginTop: 20 }}>
<Divider>在线充值</Divider> <Divider>{t('在线充值')}</Divider>
<Form> <Form>
<Form.Input <Form.Input
disabled={!enableOnlineTopUp} disabled={!enableOnlineTopUp}
field={'redemptionCount'} field={'redemptionCount'}
label={'实付金额:' + renderAmount()} label={t('实付金额:') + ' ' + renderAmount()}
placeholder={ placeholder={t('充值数量,最低 ') + renderQuotaWithAmount(minTopUp)}
'充值数量,最低 ' + renderQuotaWithAmount(minTopUp)
}
name='redemptionCount' name='redemptionCount'
type={'number'} type={'number'}
value={topUpCount} value={topUpCount}
...@@ -300,7 +300,7 @@ const TopUp = () => { ...@@ -300,7 +300,7 @@ const TopUp = () => {
preTopUp('zfb'); preTopUp('zfb');
}} }}
> >
支付宝 {t('支付宝')}
</Button> </Button>
<Button <Button
style={{ style={{
...@@ -312,7 +312,7 @@ const TopUp = () => { ...@@ -312,7 +312,7 @@ const TopUp = () => {
preTopUp('wx'); preTopUp('wx');
}} }}
> >
微信 {t('微信')}
</Button> </Button>
</Space> </Space>
</Form> </Form>
......
...@@ -14,6 +14,7 @@ import { ...@@ -14,6 +14,7 @@ import {
Spin, Spin,
Typography, Typography,
} from '@douyinfe/semi-ui'; } from '@douyinfe/semi-ui';
import { useTranslation } from 'react-i18next';
const EditUser = (props) => { const EditUser = (props) => {
const userId = props.editingUser.id; const userId = props.editingUser.id;
...@@ -120,11 +121,13 @@ const EditUser = (props) => { ...@@ -120,11 +121,13 @@ const EditUser = (props) => {
setIsModalOpen(true); setIsModalOpen(true);
}; };
const { t } = useTranslation();
return ( return (
<> <>
<SideSheet <SideSheet
placement={'right'} placement={'right'}
title={<Title level={3}>{'编辑用户'}</Title>} title={<Title level={3}>{t('编辑用户')}</Title>}
headerStyle={{ borderBottom: '1px solid var(--semi-color-border)' }} headerStyle={{ borderBottom: '1px solid var(--semi-color-border)' }}
bodyStyle={{ borderBottom: '1px solid var(--semi-color-border)' }} bodyStyle={{ borderBottom: '1px solid var(--semi-color-border)' }}
visible={props.visible} visible={props.visible}
...@@ -132,7 +135,7 @@ const EditUser = (props) => { ...@@ -132,7 +135,7 @@ const EditUser = (props) => {
<div style={{ display: 'flex', justifyContent: 'flex-end' }}> <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Space> <Space>
<Button theme='solid' size={'large'} onClick={submit}> <Button theme='solid' size={'large'} onClick={submit}>
提交 {t('提交')}
</Button> </Button>
<Button <Button
theme='solid' theme='solid'
...@@ -140,7 +143,7 @@ const EditUser = (props) => { ...@@ -140,7 +143,7 @@ const EditUser = (props) => {
type={'tertiary'} type={'tertiary'}
onClick={handleCancel} onClick={handleCancel}
> >
取消 {t('取消')}
</Button> </Button>
</Space> </Space>
</div> </div>
...@@ -151,35 +154,35 @@ const EditUser = (props) => { ...@@ -151,35 +154,35 @@ const EditUser = (props) => {
> >
<Spin spinning={loading}> <Spin spinning={loading}>
<div style={{ marginTop: 20 }}> <div style={{ marginTop: 20 }}>
<Typography.Text>用户名</Typography.Text> <Typography.Text>{t('用户名')}</Typography.Text>
</div> </div>
<Input <Input
label='用户名' label={t('用户名')}
name='username' name='username'
placeholder={'请输入新的用户名'} placeholder={t('请输入新的用户名')}
onChange={(value) => handleInputChange('username', value)} onChange={(value) => handleInputChange('username', value)}
value={username} value={username}
autoComplete='new-password' autoComplete='new-password'
/> />
<div style={{ marginTop: 20 }}> <div style={{ marginTop: 20 }}>
<Typography.Text>密码</Typography.Text> <Typography.Text>{t('密码')}</Typography.Text>
</div> </div>
<Input <Input
label='密码' label={t('密码')}
name='password' name='password'
type={'password'} type={'password'}
placeholder={'请输入新的密码,最短 8 位'} placeholder={t('请输入新的密码,最短 8 位')}
onChange={(value) => handleInputChange('password', value)} onChange={(value) => handleInputChange('password', value)}
value={password} value={password}
autoComplete='new-password' autoComplete='new-password'
/> />
<div style={{ marginTop: 20 }}> <div style={{ marginTop: 20 }}>
<Typography.Text>显示名称</Typography.Text> <Typography.Text>{t('显示名称')}</Typography.Text>
</div> </div>
<Input <Input
label='显示名称' label={t('显示名称')}
name='display_name' name='display_name'
placeholder={'请输入新的显示名称'} placeholder={t('请输入新的显示名称')}
onChange={(value) => handleInputChange('display_name', value)} onChange={(value) => handleInputChange('display_name', value)}
value={display_name} value={display_name}
autoComplete='new-password' autoComplete='new-password'
...@@ -187,76 +190,76 @@ const EditUser = (props) => { ...@@ -187,76 +190,76 @@ const EditUser = (props) => {
{userId && ( {userId && (
<> <>
<div style={{ marginTop: 20 }}> <div style={{ marginTop: 20 }}>
<Typography.Text>分组</Typography.Text> <Typography.Text>{t('分组')}</Typography.Text>
</div> </div>
<Select <Select
placeholder={'请选择分组'} placeholder={t('请选择分组')}
name='group' name='group'
fluid fluid
search search
selection selection
allowAdditions allowAdditions
additionLabel={'请在系统设置页面编辑分组倍率以添加新的分组:'} additionLabel={t('请在系统设置页面编辑分组倍率以添加新的分组:')}
onChange={(value) => handleInputChange('group', value)} onChange={(value) => handleInputChange('group', value)}
value={inputs.group} value={inputs.group}
autoComplete='new-password' autoComplete='new-password'
optionList={groupOptions} optionList={groupOptions}
/> />
<div style={{ marginTop: 20 }}> <div style={{ marginTop: 20 }}>
<Typography.Text>{`剩余额度${renderQuotaWithPrompt(quota)}`}</Typography.Text> <Typography.Text>{`${t('剩余额度')}${renderQuotaWithPrompt(quota)}`}</Typography.Text>
</div> </div>
<Space> <Space>
<Input <Input
name='quota' name='quota'
placeholder={'请输入新的剩余额度'} placeholder={t('请输入新的剩余额度')}
onChange={(value) => handleInputChange('quota', value)} onChange={(value) => handleInputChange('quota', value)}
value={quota} value={quota}
type={'number'} type={'number'}
autoComplete='new-password' autoComplete='new-password'
/> />
<Button onClick={openAddQuotaModal}>添加额度</Button> <Button onClick={openAddQuotaModal}>{t('添加额度')}</Button>
</Space> </Space>
</> </>
)} )}
<Divider style={{ marginTop: 20 }}>以下信息不可修改</Divider> <Divider style={{ marginTop: 20 }}>{t('以下信息不可修改')}</Divider>
<div style={{ marginTop: 20 }}> <div style={{ marginTop: 20 }}>
<Typography.Text>已绑定的 GitHub 账户</Typography.Text> <Typography.Text>{t('已绑定的 GitHub 账户')}</Typography.Text>
</div> </div>
<Input <Input
name='github_id' name='github_id'
value={github_id} value={github_id}
autoComplete='new-password' autoComplete='new-password'
placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改' placeholder={t('此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改')}
readonly readonly
/> />
<div style={{ marginTop: 20 }}> <div style={{ marginTop: 20 }}>
<Typography.Text>已绑定的微信账户</Typography.Text> <Typography.Text>{t('已绑定的微信账户')}</Typography.Text>
</div> </div>
<Input <Input
name='wechat_id' name='wechat_id'
value={wechat_id} value={wechat_id}
autoComplete='new-password' autoComplete='new-password'
placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改' placeholder={t('此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改')}
readonly readonly
/> />
<div style={{ marginTop: 20 }}> <div style={{ marginTop: 20 }}>
<Typography.Text>已绑定的邮箱账户</Typography.Text> <Typography.Text>{t('已绑定的邮箱账户')}</Typography.Text>
</div> </div>
<Input <Input
name='email' name='email'
value={email} value={email}
autoComplete='new-password' autoComplete='new-password'
placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改' placeholder={t('此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改')}
readonly readonly
/> />
<div style={{ marginTop: 20 }}> <div style={{ marginTop: 20 }}>
<Typography.Text>已绑定的Telegram账户</Typography.Text> <Typography.Text>{t('已绑定的Telegram账户')}</Typography.Text>
</div> </div>
<Input <Input
name='telegram_id' name='telegram_id'
value={telegram_id} value={telegram_id}
autoComplete='new-password' autoComplete='new-password'
placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改' placeholder={t('此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改')}
readonly readonly
/> />
</Spin> </Spin>
...@@ -272,11 +275,11 @@ const EditUser = (props) => { ...@@ -272,11 +275,11 @@ const EditUser = (props) => {
closable={null} closable={null}
> >
<div style={{ marginTop: 20 }}> <div style={{ marginTop: 20 }}>
<Typography.Text>{`新额度${renderQuota(quota)} + ${renderQuota(addQuotaLocal)} = ${renderQuota(quota + parseInt(addQuotaLocal))}`}</Typography.Text> <Typography.Text>{`${t('新额度')}${renderQuota(quota)} + ${renderQuota(addQuotaLocal)} = ${renderQuota(quota + parseInt(addQuotaLocal))}`}</Typography.Text>
</div> </div>
<Input <Input
name='addQuotaLocal' name='addQuotaLocal'
placeholder={'需要添加的额度(支持负数)'} placeholder={t('需要添加的额度(支持负数)')}
onChange={(value) => { onChange={(value) => {
setAddQuotaLocal(value); setAddQuotaLocal(value);
}} }}
......
import React from 'react'; import React from 'react';
import UsersTable from '../../components/UsersTable'; import UsersTable from '../../components/UsersTable';
import { Layout } from '@douyinfe/semi-ui'; import { Layout } from '@douyinfe/semi-ui';
import { useTranslation } from 'react-i18next';
const User = () => ( const User = () => {
const { t } = useTranslation();
return (
<> <>
<Layout> <Layout>
<Layout.Header> <Layout.Header>
<h3>管理用户</h3> <h3>{t('管理用户')}</h3>
</Layout.Header> </Layout.Header>
<Layout.Content> <Layout.Content>
<UsersTable /> <UsersTable />
</Layout.Content> </Layout.Content>
</Layout> </Layout>
</> </>
); );
};
export default User; export default User;
...@@ -26,6 +26,7 @@ export default defineConfig({ ...@@ -26,6 +26,7 @@ export default defineConfig({
esbuildOptions: { esbuildOptions: {
loader: { loader: {
'.js': 'jsx', '.js': 'jsx',
'.json': 'json',
}, },
}, },
}, },
...@@ -45,6 +46,7 @@ export default defineConfig({ ...@@ -45,6 +46,7 @@ export default defineConfig({
'react-toastify', 'react-toastify',
'react-turnstile', 'react-turnstile',
], ],
'i18n': ['i18next', 'react-i18next', 'i18next-browser-languagedetector'],
}, },
}, },
}, },
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment