mirror of
https://github.com/songquanpeng/one-api.git
synced 2025-09-17 09:16:36 +08:00
feat: initial i18n support
This commit is contained in:
parent
1521df6551
commit
bdf312e5dc
@ -5,10 +5,14 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
"history": "^5.3.0",
|
"history": "^5.3.0",
|
||||||
|
"i18next": "^24.2.2",
|
||||||
|
"i18next-browser-languagedetector": "^8.0.2",
|
||||||
|
"i18next-http-backend": "^3.0.2",
|
||||||
"marked": "^4.1.1",
|
"marked": "^4.1.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-dropzone": "^14.2.3",
|
"react-dropzone": "^14.2.3",
|
||||||
|
"react-i18next": "^15.4.0",
|
||||||
"react-router-dom": "^6.3.0",
|
"react-router-dom": "^6.3.0",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
"react-toastify": "^9.0.8",
|
"react-toastify": "^9.0.8",
|
||||||
|
38
web/default/public/locales/en/translation.json
Normal file
38
web/default/public/locales/en/translation.json
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"header": {
|
||||||
|
"home": "Home",
|
||||||
|
"channel": "Channel",
|
||||||
|
"token": "Token",
|
||||||
|
"redemption": "Redemption",
|
||||||
|
"topup": "Top Up",
|
||||||
|
"user": "User",
|
||||||
|
"dashboard": "Dashboard",
|
||||||
|
"log": "Log",
|
||||||
|
"setting": "Settings",
|
||||||
|
"about": "About",
|
||||||
|
"chat": "Chat",
|
||||||
|
"login": "Login",
|
||||||
|
"logout": "Logout",
|
||||||
|
"register": "Register"
|
||||||
|
},
|
||||||
|
"topup": {
|
||||||
|
"title": "Top Up Center",
|
||||||
|
"get_code": {
|
||||||
|
"title": "Get Redemption Code",
|
||||||
|
"current_quota": "Current Available Quota",
|
||||||
|
"button": "Get Code Now"
|
||||||
|
},
|
||||||
|
"redeem_code": {
|
||||||
|
"title": "Redeem Code",
|
||||||
|
"placeholder": "Please enter redemption code",
|
||||||
|
"paste": "Paste",
|
||||||
|
"paste_error": "Cannot access clipboard, please paste manually",
|
||||||
|
"submit": "Redeem Now",
|
||||||
|
"submitting": "Redeeming...",
|
||||||
|
"empty_code": "Please enter the redemption code!",
|
||||||
|
"success": "Top up successful!",
|
||||||
|
"request_failed": "Request failed",
|
||||||
|
"no_link": "Admin has not set up the top-up link!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
web/default/public/locales/zh/translation.json
Normal file
38
web/default/public/locales/zh/translation.json
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"header": {
|
||||||
|
"home": "首页",
|
||||||
|
"channel": "渠道",
|
||||||
|
"token": "令牌",
|
||||||
|
"redemption": "兑换",
|
||||||
|
"topup": "充值",
|
||||||
|
"user": "用户",
|
||||||
|
"dashboard": "总览",
|
||||||
|
"log": "日志",
|
||||||
|
"setting": "设置",
|
||||||
|
"about": "关于",
|
||||||
|
"chat": "聊天",
|
||||||
|
"login": "登录",
|
||||||
|
"logout": "注销",
|
||||||
|
"register": "注册"
|
||||||
|
},
|
||||||
|
"topup": {
|
||||||
|
"title": "充值中心",
|
||||||
|
"get_code": {
|
||||||
|
"title": "获取兑换码",
|
||||||
|
"current_quota": "当前可用额度",
|
||||||
|
"button": "立即获取兑换码"
|
||||||
|
},
|
||||||
|
"redeem_code": {
|
||||||
|
"title": "兑换码充值",
|
||||||
|
"placeholder": "请输入兑换码",
|
||||||
|
"paste": "粘贴",
|
||||||
|
"paste_error": "无法访问剪贴板,请手动粘贴",
|
||||||
|
"submit": "立即兑换",
|
||||||
|
"submitting": "兑换中...",
|
||||||
|
"empty_code": "请输入兑换码!",
|
||||||
|
"success": "充值成功!",
|
||||||
|
"request_failed": "请求失败",
|
||||||
|
"no_link": "超级管理员未设置充值链接!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import React, { useContext, useState } from 'react';
|
import React, { useContext, 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 { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@ -23,55 +24,55 @@ import '../index.css';
|
|||||||
// Header Buttons
|
// Header Buttons
|
||||||
let headerButtons = [
|
let headerButtons = [
|
||||||
{
|
{
|
||||||
name: '首页',
|
name: 'header.home',
|
||||||
to: '/',
|
to: '/',
|
||||||
icon: 'home',
|
icon: 'home',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '渠道',
|
name: 'header.channel',
|
||||||
to: '/channel',
|
to: '/channel',
|
||||||
icon: 'sitemap',
|
icon: 'sitemap',
|
||||||
admin: true,
|
admin: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '令牌',
|
name: 'header.token',
|
||||||
to: '/token',
|
to: '/token',
|
||||||
icon: 'key',
|
icon: 'key',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '兑换',
|
name: 'header.redemption',
|
||||||
to: '/redemption',
|
to: '/redemption',
|
||||||
icon: 'dollar sign',
|
icon: 'dollar sign',
|
||||||
admin: true,
|
admin: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '充值',
|
name: 'header.topup',
|
||||||
to: '/topup',
|
to: '/topup',
|
||||||
icon: 'cart',
|
icon: 'cart',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '用户',
|
name: 'header.user',
|
||||||
to: '/user',
|
to: '/user',
|
||||||
icon: 'user',
|
icon: 'user',
|
||||||
admin: true,
|
admin: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '总览',
|
name: 'header.dashboard',
|
||||||
to: '/dashboard',
|
to: '/dashboard',
|
||||||
icon: 'chart bar',
|
icon: 'chart bar',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '日志',
|
name: 'header.log',
|
||||||
to: '/log',
|
to: '/log',
|
||||||
icon: 'book',
|
icon: 'book',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '设置',
|
name: 'header.setting',
|
||||||
to: '/setting',
|
to: '/setting',
|
||||||
icon: 'setting',
|
icon: 'setting',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '关于',
|
name: 'header.about',
|
||||||
to: '/about',
|
to: '/about',
|
||||||
icon: 'info circle',
|
icon: 'info circle',
|
||||||
},
|
},
|
||||||
@ -79,13 +80,14 @@ let headerButtons = [
|
|||||||
|
|
||||||
if (localStorage.getItem('chat_link')) {
|
if (localStorage.getItem('chat_link')) {
|
||||||
headerButtons.splice(1, 0, {
|
headerButtons.splice(1, 0, {
|
||||||
name: '聊天',
|
name: 'header.chat',
|
||||||
to: '/chat',
|
to: '/chat',
|
||||||
icon: 'comments',
|
icon: 'comments',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
const [userState, userDispatch] = useContext(UserContext);
|
const [userState, userDispatch] = useContext(UserContext);
|
||||||
let navigate = useNavigate();
|
let navigate = useNavigate();
|
||||||
|
|
||||||
@ -112,13 +114,14 @@ const Header = () => {
|
|||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
return (
|
return (
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
|
key={button.name}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(button.to);
|
navigate(button.to);
|
||||||
setShowSidebar(false);
|
setShowSidebar(false);
|
||||||
}}
|
}}
|
||||||
style={{ fontSize: '15px' }}
|
style={{ fontSize: '15px' }}
|
||||||
>
|
>
|
||||||
{button.name}
|
{t(button.name)}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -134,12 +137,22 @@ const Header = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon name={button.icon} style={{ marginRight: '4px' }} />
|
<Icon name={button.icon} style={{ marginRight: '4px' }} />
|
||||||
{button.name}
|
{t(button.name)}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Add language switcher dropdown
|
||||||
|
const languageOptions = [
|
||||||
|
{ key: 'zh', text: '中文', value: 'zh' },
|
||||||
|
{ key: 'en', text: 'English', value: 'en' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const changeLanguage = (language) => {
|
||||||
|
i18n.changeLanguage(language);
|
||||||
|
};
|
||||||
|
|
||||||
if (isMobile()) {
|
if (isMobile()) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -175,10 +188,18 @@ const Header = () => {
|
|||||||
<Segment style={{ marginTop: 0, borderTop: '0' }}>
|
<Segment style={{ marginTop: 0, borderTop: '0' }}>
|
||||||
<Menu secondary vertical style={{ width: '100%', margin: 0 }}>
|
<Menu secondary vertical style={{ width: '100%', margin: 0 }}>
|
||||||
{renderButtons(true)}
|
{renderButtons(true)}
|
||||||
|
<Menu.Item>
|
||||||
|
<Dropdown
|
||||||
|
selection
|
||||||
|
options={languageOptions}
|
||||||
|
value={i18n.language}
|
||||||
|
onChange={(_, { value }) => changeLanguage(value)}
|
||||||
|
/>
|
||||||
|
</Menu.Item>
|
||||||
<Menu.Item>
|
<Menu.Item>
|
||||||
{userState.user ? (
|
{userState.user ? (
|
||||||
<Button onClick={logout} style={{ color: '#666666' }}>
|
<Button onClick={logout} style={{ color: '#666666' }}>
|
||||||
注销
|
{t('header.logout')}
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@ -188,7 +209,7 @@ const Header = () => {
|
|||||||
navigate('/login');
|
navigate('/login');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
登录
|
{t('header.login')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -196,7 +217,7 @@ const Header = () => {
|
|||||||
navigate('/register');
|
navigate('/register');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
注册
|
{t('header.register')}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -235,6 +256,17 @@ const Header = () => {
|
|||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
{renderButtons(false)}
|
{renderButtons(false)}
|
||||||
<Menu.Menu position='right'>
|
<Menu.Menu position='right'>
|
||||||
|
<Dropdown
|
||||||
|
item
|
||||||
|
options={languageOptions}
|
||||||
|
value={i18n.language}
|
||||||
|
onChange={(_, { value }) => changeLanguage(value)}
|
||||||
|
style={{
|
||||||
|
fontSize: '15px',
|
||||||
|
fontWeight: '400',
|
||||||
|
color: '#666',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
{userState.user ? (
|
{userState.user ? (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
text={userState.user.username}
|
text={userState.user.username}
|
||||||
@ -255,13 +287,13 @@ const Header = () => {
|
|||||||
color: '#666',
|
color: '#666',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
注销
|
{t('header.logout')}
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
</Dropdown.Menu>
|
</Dropdown.Menu>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
) : (
|
) : (
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
name='登录'
|
name={t('header.login')}
|
||||||
as={Link}
|
as={Link}
|
||||||
to='/login'
|
to='/login'
|
||||||
className='btn btn-link'
|
className='btn btn-link'
|
||||||
|
23
web/default/src/i18n.js
Normal file
23
web/default/src/i18n.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import i18n from 'i18next';
|
||||||
|
import { initReactI18next } from 'react-i18next';
|
||||||
|
import Backend from 'i18next-http-backend';
|
||||||
|
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||||
|
|
||||||
|
i18n
|
||||||
|
.use(Backend)
|
||||||
|
.use(LanguageDetector)
|
||||||
|
.use(initReactI18next)
|
||||||
|
.init({
|
||||||
|
fallbackLng: 'zh',
|
||||||
|
debug: process.env.NODE_ENV === 'development',
|
||||||
|
|
||||||
|
interpolation: {
|
||||||
|
escapeValue: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
backend: {
|
||||||
|
loadPath: '/locales/{{lng}}/{{ns}}.json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default i18n;
|
@ -11,6 +11,7 @@ import { UserProvider } from './context/User';
|
|||||||
import { ToastContainer } from 'react-toastify';
|
import { ToastContainer } from 'react-toastify';
|
||||||
import 'react-toastify/dist/ReactToastify.css';
|
import 'react-toastify/dist/ReactToastify.css';
|
||||||
import { StatusProvider } from './context/Status';
|
import { StatusProvider } from './context/Status';
|
||||||
|
import './i18n';
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||||
root.render(
|
root.render(
|
||||||
|
@ -10,8 +10,10 @@ import {
|
|||||||
} from 'semantic-ui-react';
|
} from 'semantic-ui-react';
|
||||||
import { API, showError, showInfo, showSuccess } from '../../helpers';
|
import { API, showError, showInfo, showSuccess } from '../../helpers';
|
||||||
import { renderQuota } from '../../helpers/render';
|
import { renderQuota } from '../../helpers/render';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const TopUp = () => {
|
const TopUp = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [redemptionCode, setRedemptionCode] = useState('');
|
const [redemptionCode, setRedemptionCode] = useState('');
|
||||||
const [topUpLink, setTopUpLink] = useState('');
|
const [topUpLink, setTopUpLink] = useState('');
|
||||||
const [userQuota, setUserQuota] = useState(0);
|
const [userQuota, setUserQuota] = useState(0);
|
||||||
@ -20,7 +22,7 @@ const TopUp = () => {
|
|||||||
|
|
||||||
const topUp = async () => {
|
const topUp = async () => {
|
||||||
if (redemptionCode === '') {
|
if (redemptionCode === '') {
|
||||||
showInfo('请输入兑换码!');
|
showInfo(t('topup.redeem_code.empty_code'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
@ -30,7 +32,7 @@ const TopUp = () => {
|
|||||||
});
|
});
|
||||||
const { success, message, data } = res.data;
|
const { success, message, data } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
showSuccess('充值成功!');
|
showSuccess(t('topup.redeem_code.success'));
|
||||||
setUserQuota((quota) => {
|
setUserQuota((quota) => {
|
||||||
return quota + data;
|
return quota + data;
|
||||||
});
|
});
|
||||||
@ -39,7 +41,7 @@ const TopUp = () => {
|
|||||||
showError(message);
|
showError(message);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showError('请求失败');
|
showError(t('topup.redeem_code.request_failed'));
|
||||||
} finally {
|
} finally {
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
}
|
}
|
||||||
@ -47,13 +49,12 @@ const TopUp = () => {
|
|||||||
|
|
||||||
const openTopUpLink = () => {
|
const openTopUpLink = () => {
|
||||||
if (!topUpLink) {
|
if (!topUpLink) {
|
||||||
showError('超级管理员未设置充值链接!');
|
showError(t('topup.redeem_code.no_link'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let url = new URL(topUpLink);
|
let url = new URL(topUpLink);
|
||||||
let username = user.username;
|
let username = user.username;
|
||||||
let user_id = user.id;
|
let user_id = user.id;
|
||||||
// add username and user_id to the topup link
|
|
||||||
url.searchParams.append('username', username);
|
url.searchParams.append('username', username);
|
||||||
url.searchParams.append('user_id', user_id);
|
url.searchParams.append('user_id', user_id);
|
||||||
url.searchParams.append('transaction_id', crypto.randomUUID());
|
url.searchParams.append('transaction_id', crypto.randomUUID());
|
||||||
@ -87,7 +88,7 @@ const TopUp = () => {
|
|||||||
<Card fluid className='chart-card'>
|
<Card fluid className='chart-card'>
|
||||||
<Card.Content>
|
<Card.Content>
|
||||||
<Card.Header>
|
<Card.Header>
|
||||||
<Header as='h2'>充值中心</Header>
|
<Header as='h2'>{t('topup.title')}</Header>
|
||||||
</Card.Header>
|
</Card.Header>
|
||||||
|
|
||||||
<Grid columns={2} stackable>
|
<Grid columns={2} stackable>
|
||||||
@ -109,7 +110,7 @@ const TopUp = () => {
|
|||||||
<Card.Header>
|
<Card.Header>
|
||||||
<Header as='h3' style={{ color: '#2185d0', margin: '1em' }}>
|
<Header as='h3' style={{ color: '#2185d0', margin: '1em' }}>
|
||||||
<i className='credit card icon'></i>
|
<i className='credit card icon'></i>
|
||||||
获取兑换码
|
{t('topup.get_code.title')}
|
||||||
</Header>
|
</Header>
|
||||||
</Card.Header>
|
</Card.Header>
|
||||||
<Card.Description
|
<Card.Description
|
||||||
@ -132,7 +133,9 @@ const TopUp = () => {
|
|||||||
<Statistic.Value style={{ color: '#2185d0' }}>
|
<Statistic.Value style={{ color: '#2185d0' }}>
|
||||||
{renderQuota(userQuota)}
|
{renderQuota(userQuota)}
|
||||||
</Statistic.Value>
|
</Statistic.Value>
|
||||||
<Statistic.Label>当前可用额度</Statistic.Label>
|
<Statistic.Label>
|
||||||
|
{t('topup.get_code.current_quota')}
|
||||||
|
</Statistic.Label>
|
||||||
</Statistic>
|
</Statistic>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -145,7 +148,7 @@ const TopUp = () => {
|
|||||||
onClick={openTopUpLink}
|
onClick={openTopUpLink}
|
||||||
style={{ width: '80%' }}
|
style={{ width: '80%' }}
|
||||||
>
|
>
|
||||||
立即获取兑换码
|
{t('topup.get_code.button')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -172,7 +175,7 @@ const TopUp = () => {
|
|||||||
<Card.Header>
|
<Card.Header>
|
||||||
<Header as='h3' style={{ color: '#21ba45', margin: '1em' }}>
|
<Header as='h3' style={{ color: '#21ba45', margin: '1em' }}>
|
||||||
<i className='ticket alternate icon'></i>
|
<i className='ticket alternate icon'></i>
|
||||||
兑换码充值
|
{t('topup.redeem_code.title')}
|
||||||
</Header>
|
</Header>
|
||||||
</Card.Header>
|
</Card.Header>
|
||||||
<Card.Description
|
<Card.Description
|
||||||
@ -194,7 +197,7 @@ const TopUp = () => {
|
|||||||
fluid
|
fluid
|
||||||
icon='key'
|
icon='key'
|
||||||
iconPosition='left'
|
iconPosition='left'
|
||||||
placeholder='请输入兑换码'
|
placeholder={t('topup.redeem_code.placeholder')}
|
||||||
value={redemptionCode}
|
value={redemptionCode}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setRedemptionCode(e.target.value);
|
setRedemptionCode(e.target.value);
|
||||||
@ -207,14 +210,14 @@ const TopUp = () => {
|
|||||||
action={
|
action={
|
||||||
<Button
|
<Button
|
||||||
icon='paste'
|
icon='paste'
|
||||||
content='粘贴'
|
content={t('topup.redeem_code.paste')}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
try {
|
try {
|
||||||
const text =
|
const text =
|
||||||
await navigator.clipboard.readText();
|
await navigator.clipboard.readText();
|
||||||
setRedemptionCode(text.trim());
|
setRedemptionCode(text.trim());
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showError('无法访问剪贴板,请手动粘贴');
|
showError(t('topup.redeem_code.paste_error'));
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -230,7 +233,9 @@ const TopUp = () => {
|
|||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
>
|
>
|
||||||
{isSubmitting ? '兑换中...' : '立即兑换'}
|
{isSubmitting
|
||||||
|
? t('topup.redeem_code.submitting')
|
||||||
|
: t('topup.redeem_code.submit')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user