Просмотр исходного кода

Merge pull request #622 from Calcium-Ion/i18n-fix

feat: Enhance i18n support
Calcium-Ion 1 год назад
Родитель
Сommit
8e2b6d0aad

+ 3 - 32
web/src/App.js

@@ -27,44 +27,15 @@ 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';
 import { useTranslation } from 'react-i18next';
+import { StatusContext } from './context/Status';
+import { setStatusData } from './helpers/data.js';
+import { API, showError } from './helpers';
 
 
 const Home = lazy(() => import('./pages/Home'));
 const Home = lazy(() => import('./pages/Home'));
 const Detail = lazy(() => import('./pages/Detail'));
 const Detail = lazy(() => import('./pages/Detail'));
 const About = lazy(() => import('./pages/About'));
 const About = lazy(() => import('./pages/About'));
 
 
 function App() {
 function App() {
-  const [userState, userDispatch] = useContext(UserContext);
-  // const [statusState, statusDispatch] = useContext(StatusContext);
-  const { i18n } = useTranslation();
-
-  const loadUser = () => {
-    let user = localStorage.getItem('user');
-    if (user) {
-      let data = JSON.parse(user);
-      userDispatch({ type: 'login', payload: data });
-    }
-  };
-
-  useEffect(() => {
-    loadUser();
-    let systemName = getSystemName();
-    if (systemName) {
-      document.title = systemName;
-    }
-    let logo = getLogo();
-    if (logo) {
-      let linkElement = document.querySelector("link[rel~='icon']");
-      if (linkElement) {
-        linkElement.href = logo;
-      }
-    }
-    // 从localStorage获取上次使用的语言
-    const savedLang = localStorage.getItem('i18nextLng');
-    if (savedLang) {
-      i18n.changeLanguage(savedLang);
-    }
-  }, [i18n]);
-
   return (
   return (
     <>
     <>
       <Routes>
       <Routes>

+ 11 - 11
web/src/components/ChannelsTable.js

@@ -249,7 +249,7 @@ const ChannelsTable = () => {
       }
       }
     },
     },
     {
     {
-      title: '优先级',
+      title: t('优先级'),
       dataIndex: 'priority',
       dataIndex: 'priority',
       render: (text, record, index) => {
       render: (text, record, index) => {
         if (record.children === undefined) {
         if (record.children === undefined) {
@@ -276,8 +276,8 @@ const ChannelsTable = () => {
               keepFocus={true}
               keepFocus={true}
               onBlur={(e) => {
               onBlur={(e) => {
                 Modal.warning({
                 Modal.warning({
-                  title: '修改子渠道优先级',
-                  content: '确定要修改所有子渠道优先级为 ' + e.target.value + ' 吗?',
+                  title: t('修改子渠道优先级'),
+                  content: t('确定要修改所有子渠道优先级为 ') + e.target.value + t(' 吗?'),
                   onOk: () => {
                   onOk: () => {
                     if (e.target.value === '') {
                     if (e.target.value === '') {
                       return;
                       return;
@@ -298,7 +298,7 @@ const ChannelsTable = () => {
       }
       }
     },
     },
     {
     {
-      title: '权重',
+      title: t('权重'),
       dataIndex: 'weight',
       dataIndex: 'weight',
       render: (text, record, index) => {
       render: (text, record, index) => {
         if (record.children === undefined) {
         if (record.children === undefined) {
@@ -325,8 +325,8 @@ const ChannelsTable = () => {
               keepFocus={true}
               keepFocus={true}
               onBlur={(e) => {
               onBlur={(e) => {
                 Modal.warning({
                 Modal.warning({
-                  title: '修改子渠道权重',
-                  content: '确定要修改所有子渠道权重为 ' + e.target.value + ' 吗?',
+                  title: t('修改子渠道权重'),
+                  content: t('确定要修改所有子渠道权重为 ') + e.target.value + t(' 吗?'),
                   onOk: () => {
                   onOk: () => {
                     if (e.target.value === '') {
                     if (e.target.value === '') {
                       return;
                       return;
@@ -646,25 +646,25 @@ const ChannelsTable = () => {
 
 
   const copySelectedChannel = async (record) => {
   const copySelectedChannel = async (record) => {
     const channelToCopy = record
     const channelToCopy = record
-    channelToCopy.name += '_复制';
+    channelToCopy.name += t('_复制');
     channelToCopy.created_time = null;
     channelToCopy.created_time = null;
     channelToCopy.balance = 0;
     channelToCopy.balance = 0;
     channelToCopy.used_quota = 0;
     channelToCopy.used_quota = 0;
     if (!channelToCopy) {
     if (!channelToCopy) {
-      showError('渠道未找到,请刷新页面后重试。');
+      showError(t('渠道未找到,请刷新页面后重试。'));
       return;
       return;
     }
     }
     try {
     try {
       const newChannel = { ...channelToCopy, id: undefined };
       const newChannel = { ...channelToCopy, id: undefined };
       const response = await API.post('/api/channel/', newChannel);
       const response = await API.post('/api/channel/', newChannel);
       if (response.data.success) {
       if (response.data.success) {
-        showSuccess('渠道复制成功');
+        showSuccess(t('渠道复制成功'));
         await refresh();
         await refresh();
       } else {
       } else {
         showError(response.data.message);
         showError(response.data.message);
       }
       }
     } catch (error) {
     } catch (error) {
-      showError('渠道复制失败: ' + error.message);
+      showError(t('渠道复制失败: ') + error.message);
     }
     }
   };
   };
 
 
@@ -723,7 +723,7 @@ const ChannelsTable = () => {
     }
     }
     const { success, message } = res.data;
     const { success, message } = res.data;
     if (success) {
     if (success) {
-      showSuccess('操作成功完成!');
+      showSuccess(t('操作成功完成!'));
       let channel = res.data.data;
       let channel = res.data.data;
       let newChannels = [...channels];
       let newChannels = [...channels];
       if (action === 'delete') {
       if (action === 'delete') {

+ 0 - 18
web/src/components/HeaderBar.js

@@ -25,24 +25,6 @@ import { stringToColor } from '../helpers/render';
 import Text from '@douyinfe/semi-ui/lib/es/typography/text';
 import Text from '@douyinfe/semi-ui/lib/es/typography/text';
 import { StyleContext } from '../context/Style/index.js';
 import { StyleContext } from '../context/Style/index.js';
 
 
-// HeaderBar Buttons
-let headerButtons = [
-  {
-    text: '关于',
-    itemKey: 'about',
-    to: '/about',
-    icon: <IconHelpCircle />,
-  },
-];
-
-if (localStorage.getItem('chat_link')) {
-  headerButtons.splice(1, 0, {
-    name: '聊天',
-    to: '/chat',
-    icon: 'comments',
-  });
-}
-
 const HeaderBar = () => {
 const HeaderBar = () => {
   const { t, i18n } = useTranslation();
   const { t, i18n } = useTranslation();
   const [userState, userDispatch] = useContext(UserContext);
   const [userState, userDispatch] = useContext(UserContext);

+ 52 - 2
web/src/components/PageLayout.js

@@ -4,15 +4,65 @@ import SiderBar from './SiderBar.js';
 import App from '../App.js';
 import App from '../App.js';
 import FooterBar from './Footer.js';
 import FooterBar from './Footer.js';
 import { ToastContainer } from 'react-toastify';
 import { ToastContainer } from 'react-toastify';
-import React, { useContext } from 'react';
+import React, { useContext, useEffect } from 'react';
 import { StyleContext } from '../context/Style/index.js';
 import { StyleContext } from '../context/Style/index.js';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
+import { API, getLogo, getSystemName, showError } from '../helpers/index.js';
+import { setStatusData } from '../helpers/data.js';
+import { UserContext } from '../context/User/index.js';
+import { StatusContext } from '../context/Status/index.js';
 const { Sider, Content, Header, Footer } = Layout;
 const { Sider, Content, Header, Footer } = Layout;
 
 
 
 
 const PageLayout = () => {
 const PageLayout = () => {
+  const [userState, userDispatch] = useContext(UserContext);
+  const [statusState, statusDispatch] = useContext(StatusContext);
   const [styleState, styleDispatch] = useContext(StyleContext);
   const [styleState, styleDispatch] = useContext(StyleContext);
-  const { t } = useTranslation();
+  const { i18n } = useTranslation();
+
+  const loadUser = () => {
+    let user = localStorage.getItem('user');
+    if (user) {
+      let data = JSON.parse(user);
+      userDispatch({ type: 'login', payload: data });
+    }
+  };
+
+  const loadStatus = async () => {
+    try {
+      const res = await API.get('/api/status');
+      const { success, data } = res.data;
+      if (success) {
+        statusDispatch({ type: 'set', payload: data });
+        setStatusData(data);
+      } else {
+        showError('Unable to connect to server');
+      }
+    } catch (error) {
+      showError('Failed to load status');
+    }
+  };
+
+  useEffect(() => {
+    loadUser();
+    loadStatus().catch(console.error);
+    let systemName = getSystemName();
+    if (systemName) {
+      document.title = systemName;
+    }
+    let logo = getLogo();
+    if (logo) {
+      let linkElement = document.querySelector("link[rel~='icon']");
+      if (linkElement) {
+        linkElement.href = logo;
+      }
+    }
+    // 从localStorage获取上次使用的语言
+    const savedLang = localStorage.getItem('i18nextLng');
+    if (savedLang) {
+      i18n.changeLanguage(savedLang);
+    }
+  }, [i18n]);
 
 
   return (
   return (
     <Layout style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}>
     <Layout style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}>

+ 3 - 19
web/src/components/SiderBar.js

@@ -168,31 +168,13 @@ const SiderBar = () => {
     ],
     ],
   );
   );
 
 
-  const loadStatus = async () => {
-    const res = await API.get('/api/status');
-    if (res === undefined) {
-      return;
-    }
-    const { success, data } = res.data;
-    if (success) {
-      statusDispatch({ type: 'set', payload: data });
-      setStatusData(data);
-    } else {
-      showError('无法正常连接至服务器!');
-    }
-  };
-
   useEffect(() => {
   useEffect(() => {
-    loadStatus().then(() => {
-      setIsCollapsed(
-          localStorage.getItem('default_collapse_sidebar') === 'true',
-      );
-    });
     let localKey = window.location.pathname.split('/')[1];
     let localKey = window.location.pathname.split('/')[1];
     if (localKey === '') {
     if (localKey === '') {
       localKey = 'home';
       localKey = 'home';
     }
     }
     setSelectedKeys([localKey]);
     setSelectedKeys([localKey]);
+    
     let chatLink = localStorage.getItem('chat_link');
     let chatLink = localStorage.getItem('chat_link');
     if (!chatLink) {
     if (!chatLink) {
         let chats = localStorage.getItem('chats');
         let chats = localStorage.getItem('chats');
@@ -220,6 +202,8 @@ const SiderBar = () => {
             }
             }
         }
         }
     }
     }
+    
+    setIsCollapsed(localStorage.getItem('default_collapse_sidebar') === 'true');
   }, []);
   }, []);
 
 
   return (
   return (

+ 12 - 4
web/src/i18n/locales/en.json

@@ -709,7 +709,7 @@
   "密码修改成功!": "Password changed successfully!",
   "密码修改成功!": "Password changed successfully!",
   "划转金额最低为": "The minimum transfer amount is",
   "划转金额最低为": "The minimum transfer amount is",
   "请输入邮箱!": "Please enter your email!",
   "请输入邮箱!": "Please enter your email!",
-  "验证码发送成功,请检查���箱!": "The verification code was sent successfully, please check your email!",
+  "验证码发送成功,请检查箱!": "The verification code was sent successfully, please check your email!",
   "请输入邮箱验证码!": "Please enter the email verification code!",
   "请输入邮箱验证码!": "Please enter the email verification code!",
   "请输入要划转的数量": "Please enter the amount to be transferred",
   "请输入要划转的数量": "Please enter the amount to be transferred",
   "当前余额": "Current balance",
   "当前余额": "Current balance",
@@ -827,8 +827,8 @@
   "模型消耗分布": "Model consumption distribution",
   "模型消耗分布": "Model consumption distribution",
   "模型调用次数占比": "Proportion of model calls",
   "模型调用次数占比": "Proportion of model calls",
   "用户消耗分布": "User consumption distribution",
   "用户消耗分布": "User consumption distribution",
-  "时间粒度": "time granularity",
-  "天": "sky",
+  "时间粒度": "Time granularity",
+  "天": "day",
   "模型概览": "Model overview",
   "模型概览": "Model overview",
   "用户概览": "User overview",
   "用户概览": "User overview",
   "正在策划中": "Under planning",
   "正在策划中": "Under planning",
@@ -1209,5 +1209,13 @@
   "首页内容已更新": "Home page content updated",
   "首页内容已更新": "Home page content updated",
   "关于已更新": "About updated",
   "关于已更新": "About updated",
   "模型测试": "model test",
   "模型测试": "model test",
-  "当前未开启Midjourney回调,部分项目可能无法获得绘图结果,可在运营设置中开启。": "Current Midjourney callback is not enabled, some projects may not be able to obtain drawing results, which can be enabled in the operation settings."
+  "当前未开启Midjourney回调,部分项目可能无法获得绘图结果,可在运营设置中开启。": "Current Midjourney callback is not enabled, some projects may not be able to obtain drawing results, which can be enabled in the operation settings.",
+  "Telegram 身份验证": "Telegram authentication",
+  "Linux DO 身份验证": "Linux DO authentication",
+  "协议": "License",
+  "修改子渠道权重": "Modify sub-channel weight",
+  "确定要修改所有子渠道权重为 ": "Confirm to modify all sub-channel weights to ",
+  " 吗?": "?",
+  "修改子渠道优先级": "Modify sub-channel priority",
+  "确定要修改所有子渠道优先级为 ": "Confirm to modify all sub-channel priorities to "
 }
 }

+ 32 - 29
web/src/pages/Home/index.js

@@ -4,8 +4,10 @@ import { API, showError, showNotice, timestamp2string } from '../../helpers';
 import { StatusContext } from '../../context/Status';
 import { StatusContext } from '../../context/Status';
 import { marked } from 'marked';
 import { marked } from 'marked';
 import { StyleContext } from '../../context/Style/index.js';
 import { StyleContext } from '../../context/Style/index.js';
+import { useTranslation } from 'react-i18next';
 
 
 const Home = () => {
 const Home = () => {
+  const { t } = useTranslation();
   const [statusState] = useContext(StatusContext);
   const [statusState] = useContext(StatusContext);
   const [homePageContentLoaded, setHomePageContentLoaded] = useState(false);
   const [homePageContentLoaded, setHomePageContentLoaded] = useState(false);
   const [homePageContent, setHomePageContent] = useState('');
   const [homePageContent, setHomePageContent] = useState('');
@@ -52,7 +54,8 @@ const Home = () => {
   useEffect(() => {
   useEffect(() => {
     displayNotice().then();
     displayNotice().then();
     displayHomePageContent().then();
     displayHomePageContent().then();
-  }, []);
+  });
+
   return (
   return (
     <>
     <>
       {homePageContentLoaded && homePageContent === '' ? (
       {homePageContentLoaded && homePageContent === '' ? (
@@ -60,13 +63,13 @@ const Home = () => {
           <Card
           <Card
             bordered={false}
             bordered={false}
             headerLine={false}
             headerLine={false}
-            title='系统状况'
+            title={t('系统状况')}
             bodyStyle={{ padding: '10px 20px' }}
             bodyStyle={{ padding: '10px 20px' }}
           >
           >
             <Row gutter={16}>
             <Row gutter={16}>
               <Col span={12}>
               <Col span={12}>
                 <Card
                 <Card
-                  title='系统信息'
+                  title={t('系统信息')}
                   headerExtraContent={
                   headerExtraContent={
                     <span
                     <span
                       style={{
                       style={{
@@ -74,19 +77,19 @@ const Home = () => {
                         color: 'var(--semi-color-text-1)',
                         color: 'var(--semi-color-text-1)',
                       }}
                       }}
                     >
                     >
-                      系统信息总览
+                      {t('系统信息总览')}
                     </span>
                     </span>
                   }
                   }
                 >
                 >
-                  <p>名称:{statusState?.status?.system_name}</p>
+                  <p>{t('名称')}:{statusState?.status?.system_name}</p>
                   <p>
                   <p>
-                    版本:
+                    {t('版本')}
                     {statusState?.status?.version
                     {statusState?.status?.version
                       ? statusState?.status?.version
                       ? statusState?.status?.version
                       : 'unknown'}
                       : 'unknown'}
                   </p>
                   </p>
                   <p>
                   <p>
-                    源码:
+                    {t('源码')}
                     <a
                     <a
                       href='https://github.com/Calcium-Ion/new-api'
                       href='https://github.com/Calcium-Ion/new-api'
                       target='_blank'
                       target='_blank'
@@ -96,7 +99,7 @@ const Home = () => {
                     </a>
                     </a>
                   </p>
                   </p>
                   <p>
                   <p>
-                    协议:
+                    {t('协议')}
                     <a
                     <a
                       href='https://www.apache.org/licenses/LICENSE-2.0'
                       href='https://www.apache.org/licenses/LICENSE-2.0'
                       target='_blank'
                       target='_blank'
@@ -105,12 +108,12 @@ const Home = () => {
                       Apache-2.0 License
                       Apache-2.0 License
                     </a>
                     </a>
                   </p>
                   </p>
-                  <p>启动时间:{getStartTimeString()}</p>
+                  <p>{t('启动时间')}:{getStartTimeString()}</p>
                 </Card>
                 </Card>
               </Col>
               </Col>
               <Col span={12}>
               <Col span={12}>
                 <Card
                 <Card
-                  title='系统配置'
+                  title={t('系统配置')}
                   headerExtraContent={
                   headerExtraContent={
                     <span
                     <span
                       style={{
                       style={{
@@ -118,45 +121,45 @@ const Home = () => {
                         color: 'var(--semi-color-text-1)',
                         color: 'var(--semi-color-text-1)',
                       }}
                       }}
                     >
                     >
-                      系统配置总览
+                      {t('系统配置总览')}
                     </span>
                     </span>
                   }
                   }
                 >
                 >
                   <p>
                   <p>
-                    邮箱验证:
+                    {t('邮箱验证')}
                     {statusState?.status?.email_verification === true
                     {statusState?.status?.email_verification === true
-                      ? '已启用'
-                      : '未启用'}
+                      ? t('已启用')
+                      : t('未启用')}
                   </p>
                   </p>
                   <p>
                   <p>
-                    GitHub 身份验证:
+                    {t('GitHub 身份验证')}
                     {statusState?.status?.github_oauth === true
                     {statusState?.status?.github_oauth === true
-                      ? '已启用'
-                      : '未启用'}
+                      ? t('已启用')
+                      : t('未启用')}
                   </p>
                   </p>
                   <p>
                   <p>
-                    微信身份验证:
+                    {t('微信身份验证')}
                     {statusState?.status?.wechat_login === true
                     {statusState?.status?.wechat_login === true
-                      ? '已启用'
-                      : '未启用'}
+                      ? t('已启用')
+                      : t('未启用')}
                   </p>
                   </p>
                   <p>
                   <p>
-                    Turnstile 用户校验:
+                    {t('Turnstile 用户校验')}
                     {statusState?.status?.turnstile_check === true
                     {statusState?.status?.turnstile_check === true
-                      ? '已启用'
-                      : '未启用'}
+                      ? t('已启用')
+                      : t('未启用')}
                   </p>
                   </p>
                   <p>
                   <p>
-                    Telegram 身份验证:
+                    {t('Telegram 身份验证')}
                     {statusState?.status?.telegram_oauth === true
                     {statusState?.status?.telegram_oauth === true
-                      ? '已启用'
-                      : '未启用'}
+                      ? t('已启用')
+                      : t('未启用')}
                   </p>
                   </p>
                   <p>
                   <p>
-                    Linux DO 身份验证:
+                    {t('Linux DO 身份验证')}
                     {statusState?.status?.linuxdo_oauth === true
                     {statusState?.status?.linuxdo_oauth === true
-                      ? '已启用'
-                      : '未启用'}
+                      ? t('已启用')
+                      : t('未启用')}
                   </p>
                   </p>
                 </Card>
                 </Card>
               </Col>
               </Col>

+ 11 - 14
web/src/pages/Playground/Playground.js

@@ -97,32 +97,29 @@ const Playground = () => {
     let res = await API.get(`/api/user/self/groups`);
     let res = await API.get(`/api/user/self/groups`);
     const { success, message, data } = res.data;
     const { success, message, data } = res.data;
     if (success) {
     if (success) {
-      // return data is a map, key is group name, value is group description
-      // label is group description, value is group name
       let localGroupOptions = Object.keys(data).map((group) => ({
       let localGroupOptions = Object.keys(data).map((group) => ({
         label: data[group],
         label: data[group],
         value: group,
         value: group,
       }));
       }));
-      // handleInputChange('group', localGroupOptions[0].value);
 
 
-      if (localGroupOptions.length > 0) {
-        // set user group at first
-        if (userState.user && userState.user.group) {
-          let userGroup = userState.user.group;
-          // Find and move user's group to the front
+      if (localGroupOptions.length === 0) {
+        localGroupOptions = [{
+          label: t('用户分组'),
+          value: '',
+        }];
+      } else {
+        const localUser = JSON.parse(localStorage.getItem('user'));
+        const userGroup = (userState.user && userState.user.group) || (localUser && localUser.group);
+        
+        if (userGroup) {
           const userGroupIndex = localGroupOptions.findIndex(g => g.value === userGroup);
           const userGroupIndex = localGroupOptions.findIndex(g => g.value === userGroup);
           if (userGroupIndex > -1) {
           if (userGroupIndex > -1) {
             const userGroupOption = localGroupOptions.splice(userGroupIndex, 1)[0];
             const userGroupOption = localGroupOptions.splice(userGroupIndex, 1)[0];
             localGroupOptions.unshift(userGroupOption);
             localGroupOptions.unshift(userGroupOption);
           }
           }
         }
         }
-      } else {
-        localGroupOptions = [{
-          label: t('用户分组'),
-          value: '',
-        }];
-        setGroups(localGroupOptions);
       }
       }
+
       setGroups(localGroupOptions);
       setGroups(localGroupOptions);
       handleInputChange('group', localGroupOptions[0].value);
       handleInputChange('group', localGroupOptions[0].value);
     } else {
     } else {