Переглянути джерело

feat: 法律文档双语配置(中/英)+ 精简语言支持至中英双语

- LegalSettings 字段拆分为 _zh/_en 双语对
- API 支持 ?lang= 参数返回对应语言内容
- 后台设置页每个文档配语言切换按钮(中文/English)
- 前端页面根据 UI 语言自动请求对应内容
- 移除 fr/ru/ja/vi/zh-TW 语言支持

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
master
fengsilin 2 тижднів тому
джерело
коміт
934b4b10a7
16 змінених файлів з 165 додано та 15499 видалено
  1. +23
    -8
      controller/misc.go
  2. +16
    -8
      setting/system_setting/legal.go
  3. +0
    -30
      web/src/components/layout/headerbar/LanguageSelector.jsx
  4. +102
    -108
      web/src/components/settings/OtherSetting.jsx
  5. +0
    -5
      web/src/components/settings/personal/cards/PreferencesSettings.jsx
  6. +0
    -6
      web/src/components/table/models/modals/SyncWizardModal.jsx
  7. +0
    -10
      web/src/i18n/i18n.js
  8. +0
    -2930
      web/src/i18n/locales/fr.json
  9. +0
    -2913
      web/src/i18n/locales/ja.json
  10. +0
    -2943
      web/src/i18n/locales/ru.json
  11. +0
    -3482
      web/src/i18n/locales/vi.json
  12. +0
    -2998
      web/src/i18n/locales/zh-TW.json
  13. +6
    -24
      web/src/pages/PrivacyPolicy/index.jsx
  14. +6
    -5
      web/src/pages/Terms/index.jsx
  15. +6
    -5
      web/src/pages/UsagePolicy/index.jsx
  16. +6
    -24
      web/src/pages/UserAgreement/index.jsx

+ 23
- 8
controller/misc.go Переглянути файл

@@ -114,10 +114,10 @@ func GetStatus(c *gin.Context) {
"passkey_user_verification": passkeySetting.UserVerification,
"passkey_attachment": passkeySetting.AttachmentPreference,
"setup": constant.Setup,
"user_agreement_enabled": legalSetting.UserAgreement != "",
"privacy_policy_enabled": legalSetting.PrivacyPolicy != "",
"terms_enabled": legalSetting.TermsOfService != "",
"usage_policy_enabled": legalSetting.UsagePolicy != "",
"user_agreement_enabled": legalSetting.UserAgreementZh != "" || legalSetting.UserAgreementEn != "",
"privacy_policy_enabled": legalSetting.PrivacyPolicyZh != "" || legalSetting.PrivacyPolicyEn != "",
"terms_enabled": legalSetting.TermsOfServiceZh != "" || legalSetting.TermsOfServiceEn != "",
"usage_policy_enabled": legalSetting.UsagePolicyZh != "" || legalSetting.UsagePolicyEn != "",
"checkin_enabled": operation_setting.GetCheckinSetting().Enabled,
"_qn": "new-api",
}
@@ -191,38 +191,53 @@ func GetAbout(c *gin.Context) {
return
}

func getLegalContent(zh, en string, lang string) string {
if lang == "en" {
return en
}
return zh
}

func GetUserAgreement(c *gin.Context) {
ls := system_setting.GetLegalSettings()
lang := c.Query("lang")
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "",
"data": system_setting.GetLegalSettings().UserAgreement,
"data": getLegalContent(ls.UserAgreementZh, ls.UserAgreementEn, lang),
})
return
}

func GetPrivacyPolicy(c *gin.Context) {
ls := system_setting.GetLegalSettings()
lang := c.Query("lang")
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "",
"data": system_setting.GetLegalSettings().PrivacyPolicy,
"data": getLegalContent(ls.PrivacyPolicyZh, ls.PrivacyPolicyEn, lang),
})
return
}

func GetTermsOfService(c *gin.Context) {
ls := system_setting.GetLegalSettings()
lang := c.Query("lang")
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "",
"data": system_setting.GetLegalSettings().TermsOfService,
"data": getLegalContent(ls.TermsOfServiceZh, ls.TermsOfServiceEn, lang),
})
return
}

func GetUsagePolicy(c *gin.Context) {
ls := system_setting.GetLegalSettings()
lang := c.Query("lang")
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "",
"data": system_setting.GetLegalSettings().UsagePolicy,
"data": getLegalContent(ls.UsagePolicyZh, ls.UsagePolicyEn, lang),
})
return
}


+ 16
- 8
setting/system_setting/legal.go Переглянути файл

@@ -3,17 +3,25 @@ package system_setting
import "github.com/QuantumNous/new-api/setting/config"

type LegalSettings struct {
UserAgreement string `json:"user_agreement"`
PrivacyPolicy string `json:"privacy_policy"`
TermsOfService string `json:"terms_of_service"`
UsagePolicy string `json:"usage_policy"`
UserAgreementZh string `json:"user_agreement_zh"`
UserAgreementEn string `json:"user_agreement_en"`
PrivacyPolicyZh string `json:"privacy_policy_zh"`
PrivacyPolicyEn string `json:"privacy_policy_en"`
TermsOfServiceZh string `json:"terms_of_service_zh"`
TermsOfServiceEn string `json:"terms_of_service_en"`
UsagePolicyZh string `json:"usage_policy_zh"`
UsagePolicyEn string `json:"usage_policy_en"`
}

var defaultLegalSettings = LegalSettings{
UserAgreement: "",
PrivacyPolicy: "",
TermsOfService: "",
UsagePolicy: "",
UserAgreementZh: "",
UserAgreementEn: "",
PrivacyPolicyZh: "",
PrivacyPolicyEn: "",
TermsOfServiceZh: "",
TermsOfServiceEn: "",
UsagePolicyZh: "",
UsagePolicyEn: "",
}

func init() {


+ 0
- 30
web/src/components/layout/headerbar/LanguageSelector.jsx Переглянути файл

@@ -27,7 +27,6 @@ const LanguageSelector = ({ currentLang, onLanguageChange, t }) => {
position='bottomRight'
render={
<Dropdown.Menu className='!bg-semi-color-bg-overlay !border-semi-color-border !shadow-lg !rounded-lg dark:!bg-gray-700 dark:!border-gray-600'>
{/* Language sorting: Order by English name (Chinese, English, French, Japanese, Russian) */}
<Dropdown.Item
onClick={() => onLanguageChange('zh-CN')}
className={`!px-3 !py-1.5 !text-sm !text-semi-color-text-0 dark:!text-gray-200 ${currentLang === 'zh-CN' ? '!bg-semi-color-primary-light-default dark:!bg-blue-600 !font-semibold' : 'hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-600'}`}
@@ -35,40 +34,11 @@ const LanguageSelector = ({ currentLang, onLanguageChange, t }) => {
简体中文
</Dropdown.Item>
<Dropdown.Item
onClick={() => onLanguageChange('zh-TW')}
className={`!px-3 !py-1.5 !text-sm !text-semi-color-text-0 dark:!text-gray-200 ${currentLang === 'zh-TW' ? '!bg-semi-color-primary-light-default dark:!bg-blue-600 !font-semibold' : 'hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-600'}`}
>
繁體中文
</Dropdown.Item> <Dropdown.Item
onClick={() => onLanguageChange('en')}
className={`!px-3 !py-1.5 !text-sm !text-semi-color-text-0 dark:!text-gray-200 ${currentLang === 'en' ? '!bg-semi-color-primary-light-default dark:!bg-blue-600 !font-semibold' : 'hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-600'}`}
>
English
</Dropdown.Item>
<Dropdown.Item
onClick={() => onLanguageChange('fr')}
className={`!px-3 !py-1.5 !text-sm !text-semi-color-text-0 dark:!text-gray-200 ${currentLang === 'fr' ? '!bg-semi-color-primary-light-default dark:!bg-blue-600 !font-semibold' : 'hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-600'}`}
>
Français
</Dropdown.Item>
<Dropdown.Item
onClick={() => onLanguageChange('ja')}
className={`!px-3 !py-1.5 !text-sm !text-semi-color-text-0 dark:!text-gray-200 ${currentLang === 'ja' ? '!bg-semi-color-primary-light-default dark:!bg-blue-600 !font-semibold' : 'hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-600'}`}
>
日本語
</Dropdown.Item>
<Dropdown.Item
onClick={() => onLanguageChange('ru')}
className={`!px-3 !py-1.5 !text-sm !text-semi-color-text-0 dark:!text-gray-200 ${currentLang === 'ru' ? '!bg-semi-color-primary-light-default dark:!bg-blue-600 !font-semibold' : 'hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-600'}`}
>
Русский
</Dropdown.Item>
<Dropdown.Item
onClick={() => onLanguageChange('vi')}
className={`!px-3 !py-1.5 !text-sm !text-semi-color-text-0 dark:!text-gray-200 ${currentLang === 'vi' ? '!bg-semi-color-primary-light-default dark:!bg-blue-600 !font-semibold' : 'hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-600'}`}
>
Tiếng Việt
</Dropdown.Item>
</Dropdown.Menu>
}
>


+ 102
- 108
web/src/components/settings/OtherSetting.jsx Переглянути файл

@@ -21,6 +21,7 @@ import React, { useContext, useEffect, useRef, useState } from 'react';
import {
Banner,
Button,
ButtonGroup,
Col,
Form,
Row,
@@ -34,19 +35,26 @@ import { useTranslation } from 'react-i18next';
import { StatusContext } from '../../context/Status';
import Text from '@douyinfe/semi-ui/lib/es/typography/text';

const LEGAL_USER_AGREEMENT_KEY = 'legal.user_agreement';
const LEGAL_PRIVACY_POLICY_KEY = 'legal.privacy_policy';
const LEGAL_TERMS_KEY = 'legal.terms_of_service';
const LEGAL_USAGE_POLICY_KEY = 'legal.usage_policy';
const LEGAL_KEYS = {
userAgreement: { zh: 'legal.user_agreement_zh', en: 'legal.user_agreement_en' },
privacyPolicy: { zh: 'legal.privacy_policy_zh', en: 'legal.privacy_policy_en' },
termsOfService: { zh: 'legal.terms_of_service_zh', en: 'legal.terms_of_service_en' },
usagePolicy: { zh: 'legal.usage_policy_zh', en: 'legal.usage_policy_en' },
};

const OtherSetting = () => {
const { t } = useTranslation();
const [editingLang, setEditingLang] = useState('zh');
let [inputs, setInputs] = useState({
Notice: '',
[LEGAL_USER_AGREEMENT_KEY]: '',
[LEGAL_PRIVACY_POLICY_KEY]: '',
[LEGAL_TERMS_KEY]: '',
[LEGAL_USAGE_POLICY_KEY]: '',
[LEGAL_KEYS.userAgreement.zh]: '',
[LEGAL_KEYS.userAgreement.en]: '',
[LEGAL_KEYS.privacyPolicy.zh]: '',
[LEGAL_KEYS.privacyPolicy.en]: '',
[LEGAL_KEYS.termsOfService.zh]: '',
[LEGAL_KEYS.termsOfService.en]: '',
[LEGAL_KEYS.usagePolicy.zh]: '',
[LEGAL_KEYS.usagePolicy.en]: '',
SystemName: '',
Logo: '',
Footer: '',
@@ -78,10 +86,10 @@ const OtherSetting = () => {

const [loadingInput, setLoadingInput] = useState({
Notice: false,
[LEGAL_USER_AGREEMENT_KEY]: false,
[LEGAL_PRIVACY_POLICY_KEY]: false,
[LEGAL_TERMS_KEY]: false,
[LEGAL_USAGE_POLICY_KEY]: false,
userAgreement: false,
privacyPolicy: false,
termsOfService: false,
usagePolicy: false,
SystemName: false,
Logo: false,
HomePageContent: false,
@@ -109,89 +117,19 @@ const OtherSetting = () => {
setLoadingInput((loadingInput) => ({ ...loadingInput, Notice: false }));
}
};
// 通用设置 - UserAgreement
const submitUserAgreement = async () => {
try {
setLoadingInput((loadingInput) => ({
...loadingInput,
[LEGAL_USER_AGREEMENT_KEY]: true,
}));
await updateOption(
LEGAL_USER_AGREEMENT_KEY,
inputs[LEGAL_USER_AGREEMENT_KEY],
);
showSuccess(t('用户协议已更新'));
} catch (error) {
console.error(t('用户协议更新失败'), error);
showError(t('用户协议更新失败'));
} finally {
setLoadingInput((loadingInput) => ({
...loadingInput,
[LEGAL_USER_AGREEMENT_KEY]: false,
}));
}
};
// 通用设置 - PrivacyPolicy
const submitPrivacyPolicy = async () => {
try {
setLoadingInput((loadingInput) => ({
...loadingInput,
[LEGAL_PRIVACY_POLICY_KEY]: true,
}));
await updateOption(
LEGAL_PRIVACY_POLICY_KEY,
inputs[LEGAL_PRIVACY_POLICY_KEY],
);
showSuccess(t('隐私政策已更新'));
} catch (error) {
console.error(t('隐私政策更新失败'), error);
showError(t('隐私政策更新失败'));
} finally {
setLoadingInput((loadingInput) => ({
...loadingInput,
[LEGAL_PRIVACY_POLICY_KEY]: false,
}));
}
};
// 通用设置 - TermsOfService
const submitTermsOfService = async () => {
try {
setLoadingInput((loadingInput) => ({
...loadingInput,
[LEGAL_TERMS_KEY]: true,
}));
await updateOption(LEGAL_TERMS_KEY, inputs[LEGAL_TERMS_KEY]);
showSuccess(t('服务条款已更新'));
} catch (error) {
console.error(t('服务条款更新失败'), error);
showError(t('服务条款更新失败'));
} finally {
setLoadingInput((loadingInput) => ({
...loadingInput,
[LEGAL_TERMS_KEY]: false,
}));
}
};
// 通用设置 - UsagePolicy
const submitUsagePolicy = async () => {
// 通用法律文档保存(同时保存中英文)
const submitLegalDoc = async (docKey, successMsg, errorMsg) => {
const keys = LEGAL_KEYS[docKey];
try {
setLoadingInput((loadingInput) => ({
...loadingInput,
[LEGAL_USAGE_POLICY_KEY]: true,
}));
await updateOption(
LEGAL_USAGE_POLICY_KEY,
inputs[LEGAL_USAGE_POLICY_KEY],
);
showSuccess(t('使用政策已更新'));
setLoadingInput((prev) => ({ ...prev, [docKey]: true }));
await updateOption(keys.zh, inputs[keys.zh]);
await updateOption(keys.en, inputs[keys.en]);
showSuccess(t(successMsg));
} catch (error) {
console.error(t('使用政策更新失败'), error);
showError(t('使用政策更新失败'));
console.error(t(errorMsg), error);
showError(t(errorMsg));
} finally {
setLoadingInput((loadingInput) => ({
...loadingInput,
[LEGAL_USAGE_POLICY_KEY]: false,
}));
setLoadingInput((prev) => ({ ...prev, [docKey]: false }));
}
};
// 个性化设置
@@ -423,11 +361,25 @@ const OtherSetting = () => {
{t('设置公告')}
</Button>
<Form.TextArea
label={t('用户协议')}
label={
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span>{t('用户协议')}</span>
<ButtonGroup size='small'>
<Button
type={editingLang === 'zh' ? 'primary' : 'tertiary'}
onClick={() => setEditingLang('zh')}
>中文</Button>
<Button
type={editingLang === 'en' ? 'primary' : 'tertiary'}
onClick={() => setEditingLang('en')}
>English</Button>
</ButtonGroup>
</div>
}
placeholder={t(
'在此输入用户协议内容,支持 Markdown & HTML 代码',
)}
field={LEGAL_USER_AGREEMENT_KEY}
field={LEGAL_KEYS.userAgreement[editingLang]}
onChange={handleInputChange}
style={{ fontFamily: 'JetBrains Mono, Consolas' }}
autosize={{ minRows: 6, maxRows: 12 }}
@@ -436,17 +388,31 @@ const OtherSetting = () => {
)}
/>
<Button
onClick={submitUserAgreement}
loading={loadingInput[LEGAL_USER_AGREEMENT_KEY]}
onClick={() => submitLegalDoc('userAgreement', '用户协议已更新', '用户协议更新失败')}
loading={loadingInput['userAgreement']}
>
{t('设置用户协议')}
</Button>
<Form.TextArea
label={t('隐私政策')}
label={
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span>{t('隐私政策')}</span>
<ButtonGroup size='small'>
<Button
type={editingLang === 'zh' ? 'primary' : 'tertiary'}
onClick={() => setEditingLang('zh')}
>中文</Button>
<Button
type={editingLang === 'en' ? 'primary' : 'tertiary'}
onClick={() => setEditingLang('en')}
>English</Button>
</ButtonGroup>
</div>
}
placeholder={t(
'在此输入隐私政策内容,支持 Markdown & HTML 代码',
)}
field={LEGAL_PRIVACY_POLICY_KEY}
field={LEGAL_KEYS.privacyPolicy[editingLang]}
onChange={handleInputChange}
style={{ fontFamily: 'JetBrains Mono, Consolas' }}
autosize={{ minRows: 6, maxRows: 12 }}
@@ -455,40 +421,68 @@ const OtherSetting = () => {
)}
/>
<Button
onClick={submitPrivacyPolicy}
loading={loadingInput[LEGAL_PRIVACY_POLICY_KEY]}
onClick={() => submitLegalDoc('privacyPolicy', '隐私政策已更新', '隐私政策更新失败')}
loading={loadingInput['privacyPolicy']}
>
{t('设置隐私政策')}
</Button>
<Form.TextArea
label={t('服务条款')}
label={
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span>{t('服务条款')}</span>
<ButtonGroup size='small'>
<Button
type={editingLang === 'zh' ? 'primary' : 'tertiary'}
onClick={() => setEditingLang('zh')}
>中文</Button>
<Button
type={editingLang === 'en' ? 'primary' : 'tertiary'}
onClick={() => setEditingLang('en')}
>English</Button>
</ButtonGroup>
</div>
}
placeholder={t(
'在此输入服务条款内容,支持 Markdown & HTML 代码',
)}
field={LEGAL_TERMS_KEY}
field={LEGAL_KEYS.termsOfService[editingLang]}
onChange={handleInputChange}
style={{ fontFamily: 'JetBrains Mono, Consolas' }}
autosize={{ minRows: 6, maxRows: 12 }}
/>
<Button
onClick={submitTermsOfService}
loading={loadingInput[LEGAL_TERMS_KEY]}
onClick={() => submitLegalDoc('termsOfService', '服务条款已更新', '服务条款更新失败')}
loading={loadingInput['termsOfService']}
>
{t('设置服务条款')}
</Button>
<Form.TextArea
label={t('使用政策')}
label={
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span>{t('使用政策')}</span>
<ButtonGroup size='small'>
<Button
type={editingLang === 'zh' ? 'primary' : 'tertiary'}
onClick={() => setEditingLang('zh')}
>中文</Button>
<Button
type={editingLang === 'en' ? 'primary' : 'tertiary'}
onClick={() => setEditingLang('en')}
>English</Button>
</ButtonGroup>
</div>
}
placeholder={t(
'在此输入使用政策内容,支持 Markdown & HTML 代码',
)}
field={LEGAL_USAGE_POLICY_KEY}
field={LEGAL_KEYS.usagePolicy[editingLang]}
onChange={handleInputChange}
style={{ fontFamily: 'JetBrains Mono, Consolas' }}
autosize={{ minRows: 6, maxRows: 12 }}
/>
<Button
onClick={submitUsagePolicy}
loading={loadingInput[LEGAL_USAGE_POLICY_KEY]}
onClick={() => submitLegalDoc('usagePolicy', '使用政策已更新', '使用政策更新失败')}
loading={loadingInput['usagePolicy']}
>
{t('设置使用政策')}
</Button>


+ 0
- 5
web/src/components/settings/personal/cards/PreferencesSettings.jsx Переглянути файл

@@ -27,12 +27,7 @@ import { UserContext } from "../../../../context/User";
// Language options with native names
const languageOptions = [
{ value: "zh-CN", label: "简体中文" },
{ value: "zh-TW", label: "繁體中文" },
{ value: "en", label: "English" },
{ value: 'fr', label: 'Français'},
{ value: 'ru', label: 'Русский'},
{ value: 'ja', label: '日本語'},
{ value: "vi", label: "Tiếng Việt" },
];

const PreferencesSettings = ({ t }) => {


+ 0
- 6
web/src/components/table/models/modals/SyncWizardModal.jsx Переглянути файл

@@ -118,12 +118,6 @@ const SyncWizardModal = ({ visible, onClose, onConfirm, loading, t }) => {
<Radio value='zh-CN' extra='简体中文'>
zh-CN
</Radio>
<Radio value='zh-TW' extra='繁體中文'>
zh-TW
</Radio>
<Radio value='ja' extra='日本語'>
ja
</Radio>
</RadioGroup>
</div>
</div>


+ 0
- 10
web/src/i18n/i18n.js Переглянути файл

@@ -22,12 +22,7 @@ import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';

import enTranslation from './locales/en.json';
import frTranslation from './locales/fr.json';
import zhCNTranslation from './locales/zh-CN.json';
import zhTWTranslation from './locales/zh-TW.json';
import ruTranslation from './locales/ru.json';
import jaTranslation from './locales/ja.json';
import viTranslation from './locales/vi.json';

i18n
.use(LanguageDetector)
@@ -37,11 +32,6 @@ i18n
resources: {
en: enTranslation,
'zh-CN': zhCNTranslation,
'zh-TW': zhTWTranslation,
fr: frTranslation,
ru: ruTranslation,
ja: jaTranslation,
vi: viTranslation,
},
fallbackLng: 'zh-CN',
nsSeparator: false,


+ 0
- 2930
web/src/i18n/locales/fr.json
Різницю між файлами не показано, бо вона завелика
Переглянути файл


+ 0
- 2913
web/src/i18n/locales/ja.json
Різницю між файлами не показано, бо вона завелика
Переглянути файл


+ 0
- 2943
web/src/i18n/locales/ru.json
Різницю між файлами не показано, бо вона завелика
Переглянути файл


+ 0
- 3482
web/src/i18n/locales/vi.json
Різницю між файлами не показано, бо вона завелика
Переглянути файл


+ 0
- 2998
web/src/i18n/locales/zh-TW.json
Різницю між файлами не показано, бо вона завелика
Переглянути файл


+ 6
- 24
web/src/pages/PrivacyPolicy/index.jsx Переглянути файл

@@ -1,35 +1,17 @@
/*
Copyright (C) 2025 QuantumNous

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.

For commercial licensing, please contact support@quantumnous.com
*/

import React from 'react';
import { useTranslation } from 'react-i18next';
import DocumentRenderer from '../../components/common/DocumentRenderer';

const PrivacyPolicy = () => {
const { t } = useTranslation();
const { i18n } = useTranslation();
const lang = i18n.language.startsWith('en') ? 'en' : 'zh';

return (
<DocumentRenderer
apiEndpoint='/api/privacy-policy'
title={t('隐私政策')}
cacheKey='privacy_policy'
emptyMessage={t('加载隐私政策内容失败...')}
apiEndpoint={`/api/privacy-policy?lang=${lang}`}
title={i18n.language.startsWith('en') ? 'Privacy Policy' : '隐私政策'}
cacheKey={`privacy_policy_${lang}`}
emptyMessage={i18n.language.startsWith('en') ? 'Failed to load privacy policy...' : '加载隐私政策内容失败...'}
/>
);
};


+ 6
- 5
web/src/pages/Terms/index.jsx Переглянути файл

@@ -3,14 +3,15 @@ import { useTranslation } from 'react-i18next';
import DocumentRenderer from '../../components/common/DocumentRenderer';

const Terms = () => {
const { t } = useTranslation();
const { i18n } = useTranslation();
const lang = i18n.language.startsWith('en') ? 'en' : 'zh';

return (
<DocumentRenderer
apiEndpoint='/api/terms'
title={t('服务条款')}
cacheKey='terms_of_service'
emptyMessage={t('加载服务条款内容失败...')}
apiEndpoint={`/api/terms?lang=${lang}`}
title={i18n.language.startsWith('en') ? 'Terms of Service' : '服务条款'}
cacheKey={`terms_of_service_${lang}`}
emptyMessage={i18n.language.startsWith('en') ? 'Failed to load terms of service...' : '加载服务条款内容失败...'}
/>
);
};


+ 6
- 5
web/src/pages/UsagePolicy/index.jsx Переглянути файл

@@ -3,14 +3,15 @@ import { useTranslation } from 'react-i18next';
import DocumentRenderer from '../../components/common/DocumentRenderer';

const UsagePolicy = () => {
const { t } = useTranslation();
const { i18n } = useTranslation();
const lang = i18n.language.startsWith('en') ? 'en' : 'zh';

return (
<DocumentRenderer
apiEndpoint='/api/usage-policy'
title={t('使用政策')}
cacheKey='usage_policy'
emptyMessage={t('加载使用政策内容失败...')}
apiEndpoint={`/api/usage-policy?lang=${lang}`}
title={i18n.language.startsWith('en') ? 'Usage Policy' : '使用政策'}
cacheKey={`usage_policy_${lang}`}
emptyMessage={i18n.language.startsWith('en') ? 'Failed to load usage policy...' : '加载使用政策内容失败...'}
/>
);
};


+ 6
- 24
web/src/pages/UserAgreement/index.jsx Переглянути файл

@@ -1,35 +1,17 @@
/*
Copyright (C) 2025 QuantumNous

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.

For commercial licensing, please contact support@quantumnous.com
*/

import React from 'react';
import { useTranslation } from 'react-i18next';
import DocumentRenderer from '../../components/common/DocumentRenderer';

const UserAgreement = () => {
const { t } = useTranslation();
const { i18n } = useTranslation();
const lang = i18n.language.startsWith('en') ? 'en' : 'zh';

return (
<DocumentRenderer
apiEndpoint='/api/user-agreement'
title={t('用户协议')}
cacheKey='user_agreement'
emptyMessage={t('加载用户协议内容失败...')}
apiEndpoint={`/api/user-agreement?lang=${lang}`}
title={i18n.language.startsWith('en') ? 'User Agreement' : '用户协议'}
cacheKey={`user_agreement_${lang}`}
emptyMessage={i18n.language.startsWith('en') ? 'Failed to load user agreement...' : '加载用户协议内容失败...'}
/>
);
};


Завантаження…
Відмінити
Зберегти