- LegalSettings 字段拆分为 _zh/_en 双语对 - API 支持 ?lang= 参数返回对应语言内容 - 后台设置页每个文档配语言切换按钮(中文/English) - 前端页面根据 UI 语言自动请求对应内容 - 移除 fr/ru/ja/vi/zh-TW 语言支持 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>master
| @@ -114,10 +114,10 @@ func GetStatus(c *gin.Context) { | |||||
| "passkey_user_verification": passkeySetting.UserVerification, | "passkey_user_verification": passkeySetting.UserVerification, | ||||
| "passkey_attachment": passkeySetting.AttachmentPreference, | "passkey_attachment": passkeySetting.AttachmentPreference, | ||||
| "setup": constant.Setup, | "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, | "checkin_enabled": operation_setting.GetCheckinSetting().Enabled, | ||||
| "_qn": "new-api", | "_qn": "new-api", | ||||
| } | } | ||||
| @@ -191,38 +191,53 @@ func GetAbout(c *gin.Context) { | |||||
| return | return | ||||
| } | } | ||||
| func getLegalContent(zh, en string, lang string) string { | |||||
| if lang == "en" { | |||||
| return en | |||||
| } | |||||
| return zh | |||||
| } | |||||
| func GetUserAgreement(c *gin.Context) { | func GetUserAgreement(c *gin.Context) { | ||||
| ls := system_setting.GetLegalSettings() | |||||
| lang := c.Query("lang") | |||||
| c.JSON(http.StatusOK, gin.H{ | c.JSON(http.StatusOK, gin.H{ | ||||
| "success": true, | "success": true, | ||||
| "message": "", | "message": "", | ||||
| "data": system_setting.GetLegalSettings().UserAgreement, | |||||
| "data": getLegalContent(ls.UserAgreementZh, ls.UserAgreementEn, lang), | |||||
| }) | }) | ||||
| return | return | ||||
| } | } | ||||
| func GetPrivacyPolicy(c *gin.Context) { | func GetPrivacyPolicy(c *gin.Context) { | ||||
| ls := system_setting.GetLegalSettings() | |||||
| lang := c.Query("lang") | |||||
| c.JSON(http.StatusOK, gin.H{ | c.JSON(http.StatusOK, gin.H{ | ||||
| "success": true, | "success": true, | ||||
| "message": "", | "message": "", | ||||
| "data": system_setting.GetLegalSettings().PrivacyPolicy, | |||||
| "data": getLegalContent(ls.PrivacyPolicyZh, ls.PrivacyPolicyEn, lang), | |||||
| }) | }) | ||||
| return | return | ||||
| } | } | ||||
| func GetTermsOfService(c *gin.Context) { | func GetTermsOfService(c *gin.Context) { | ||||
| ls := system_setting.GetLegalSettings() | |||||
| lang := c.Query("lang") | |||||
| c.JSON(http.StatusOK, gin.H{ | c.JSON(http.StatusOK, gin.H{ | ||||
| "success": true, | "success": true, | ||||
| "message": "", | "message": "", | ||||
| "data": system_setting.GetLegalSettings().TermsOfService, | |||||
| "data": getLegalContent(ls.TermsOfServiceZh, ls.TermsOfServiceEn, lang), | |||||
| }) | }) | ||||
| return | return | ||||
| } | } | ||||
| func GetUsagePolicy(c *gin.Context) { | func GetUsagePolicy(c *gin.Context) { | ||||
| ls := system_setting.GetLegalSettings() | |||||
| lang := c.Query("lang") | |||||
| c.JSON(http.StatusOK, gin.H{ | c.JSON(http.StatusOK, gin.H{ | ||||
| "success": true, | "success": true, | ||||
| "message": "", | "message": "", | ||||
| "data": system_setting.GetLegalSettings().UsagePolicy, | |||||
| "data": getLegalContent(ls.UsagePolicyZh, ls.UsagePolicyEn, lang), | |||||
| }) | }) | ||||
| return | return | ||||
| } | } | ||||
| @@ -3,17 +3,25 @@ package system_setting | |||||
| import "github.com/QuantumNous/new-api/setting/config" | import "github.com/QuantumNous/new-api/setting/config" | ||||
| type LegalSettings struct { | 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{ | var defaultLegalSettings = LegalSettings{ | ||||
| UserAgreement: "", | |||||
| PrivacyPolicy: "", | |||||
| TermsOfService: "", | |||||
| UsagePolicy: "", | |||||
| UserAgreementZh: "", | |||||
| UserAgreementEn: "", | |||||
| PrivacyPolicyZh: "", | |||||
| PrivacyPolicyEn: "", | |||||
| TermsOfServiceZh: "", | |||||
| TermsOfServiceEn: "", | |||||
| UsagePolicyZh: "", | |||||
| UsagePolicyEn: "", | |||||
| } | } | ||||
| func init() { | func init() { | ||||
| @@ -27,7 +27,6 @@ const LanguageSelector = ({ currentLang, onLanguageChange, t }) => { | |||||
| position='bottomRight' | position='bottomRight' | ||||
| render={ | 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'> | <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 | <Dropdown.Item | ||||
| onClick={() => onLanguageChange('zh-CN')} | 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'}`} | 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> | ||||
| <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')} | 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'}`} | 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 | English | ||||
| </Dropdown.Item> | </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> | </Dropdown.Menu> | ||||
| } | } | ||||
| > | > | ||||
| @@ -21,6 +21,7 @@ import React, { useContext, useEffect, useRef, useState } from 'react'; | |||||
| import { | import { | ||||
| Banner, | Banner, | ||||
| Button, | Button, | ||||
| ButtonGroup, | |||||
| Col, | Col, | ||||
| Form, | Form, | ||||
| Row, | Row, | ||||
| @@ -34,19 +35,26 @@ import { useTranslation } from 'react-i18next'; | |||||
| import { StatusContext } from '../../context/Status'; | import { StatusContext } from '../../context/Status'; | ||||
| import Text from '@douyinfe/semi-ui/lib/es/typography/text'; | 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 OtherSetting = () => { | ||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const [editingLang, setEditingLang] = useState('zh'); | |||||
| let [inputs, setInputs] = useState({ | let [inputs, setInputs] = useState({ | ||||
| Notice: '', | 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: '', | SystemName: '', | ||||
| Logo: '', | Logo: '', | ||||
| Footer: '', | Footer: '', | ||||
| @@ -78,10 +86,10 @@ const OtherSetting = () => { | |||||
| const [loadingInput, setLoadingInput] = useState({ | const [loadingInput, setLoadingInput] = useState({ | ||||
| Notice: false, | 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, | SystemName: false, | ||||
| Logo: false, | Logo: false, | ||||
| HomePageContent: false, | HomePageContent: false, | ||||
| @@ -109,89 +117,19 @@ const OtherSetting = () => { | |||||
| setLoadingInput((loadingInput) => ({ ...loadingInput, Notice: false })); | 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 { | 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) { | } catch (error) { | ||||
| console.error(t('使用政策更新失败'), error); | |||||
| showError(t('使用政策更新失败')); | |||||
| console.error(t(errorMsg), error); | |||||
| showError(t(errorMsg)); | |||||
| } finally { | } finally { | ||||
| setLoadingInput((loadingInput) => ({ | |||||
| ...loadingInput, | |||||
| [LEGAL_USAGE_POLICY_KEY]: false, | |||||
| })); | |||||
| setLoadingInput((prev) => ({ ...prev, [docKey]: false })); | |||||
| } | } | ||||
| }; | }; | ||||
| // 个性化设置 | // 个性化设置 | ||||
| @@ -423,11 +361,25 @@ const OtherSetting = () => { | |||||
| {t('设置公告')} | {t('设置公告')} | ||||
| </Button> | </Button> | ||||
| <Form.TextArea | <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( | placeholder={t( | ||||
| '在此输入用户协议内容,支持 Markdown & HTML 代码', | '在此输入用户协议内容,支持 Markdown & HTML 代码', | ||||
| )} | )} | ||||
| field={LEGAL_USER_AGREEMENT_KEY} | |||||
| field={LEGAL_KEYS.userAgreement[editingLang]} | |||||
| onChange={handleInputChange} | onChange={handleInputChange} | ||||
| style={{ fontFamily: 'JetBrains Mono, Consolas' }} | style={{ fontFamily: 'JetBrains Mono, Consolas' }} | ||||
| autosize={{ minRows: 6, maxRows: 12 }} | autosize={{ minRows: 6, maxRows: 12 }} | ||||
| @@ -436,17 +388,31 @@ const OtherSetting = () => { | |||||
| )} | )} | ||||
| /> | /> | ||||
| <Button | <Button | ||||
| onClick={submitUserAgreement} | |||||
| loading={loadingInput[LEGAL_USER_AGREEMENT_KEY]} | |||||
| onClick={() => submitLegalDoc('userAgreement', '用户协议已更新', '用户协议更新失败')} | |||||
| loading={loadingInput['userAgreement']} | |||||
| > | > | ||||
| {t('设置用户协议')} | {t('设置用户协议')} | ||||
| </Button> | </Button> | ||||
| <Form.TextArea | <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( | placeholder={t( | ||||
| '在此输入隐私政策内容,支持 Markdown & HTML 代码', | '在此输入隐私政策内容,支持 Markdown & HTML 代码', | ||||
| )} | )} | ||||
| field={LEGAL_PRIVACY_POLICY_KEY} | |||||
| field={LEGAL_KEYS.privacyPolicy[editingLang]} | |||||
| onChange={handleInputChange} | onChange={handleInputChange} | ||||
| style={{ fontFamily: 'JetBrains Mono, Consolas' }} | style={{ fontFamily: 'JetBrains Mono, Consolas' }} | ||||
| autosize={{ minRows: 6, maxRows: 12 }} | autosize={{ minRows: 6, maxRows: 12 }} | ||||
| @@ -455,40 +421,68 @@ const OtherSetting = () => { | |||||
| )} | )} | ||||
| /> | /> | ||||
| <Button | <Button | ||||
| onClick={submitPrivacyPolicy} | |||||
| loading={loadingInput[LEGAL_PRIVACY_POLICY_KEY]} | |||||
| onClick={() => submitLegalDoc('privacyPolicy', '隐私政策已更新', '隐私政策更新失败')} | |||||
| loading={loadingInput['privacyPolicy']} | |||||
| > | > | ||||
| {t('设置隐私政策')} | {t('设置隐私政策')} | ||||
| </Button> | </Button> | ||||
| <Form.TextArea | <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( | placeholder={t( | ||||
| '在此输入服务条款内容,支持 Markdown & HTML 代码', | '在此输入服务条款内容,支持 Markdown & HTML 代码', | ||||
| )} | )} | ||||
| field={LEGAL_TERMS_KEY} | |||||
| field={LEGAL_KEYS.termsOfService[editingLang]} | |||||
| 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 | <Button | ||||
| onClick={submitTermsOfService} | |||||
| loading={loadingInput[LEGAL_TERMS_KEY]} | |||||
| onClick={() => submitLegalDoc('termsOfService', '服务条款已更新', '服务条款更新失败')} | |||||
| loading={loadingInput['termsOfService']} | |||||
| > | > | ||||
| {t('设置服务条款')} | {t('设置服务条款')} | ||||
| </Button> | </Button> | ||||
| <Form.TextArea | <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( | placeholder={t( | ||||
| '在此输入使用政策内容,支持 Markdown & HTML 代码', | '在此输入使用政策内容,支持 Markdown & HTML 代码', | ||||
| )} | )} | ||||
| field={LEGAL_USAGE_POLICY_KEY} | |||||
| field={LEGAL_KEYS.usagePolicy[editingLang]} | |||||
| 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 | <Button | ||||
| onClick={submitUsagePolicy} | |||||
| loading={loadingInput[LEGAL_USAGE_POLICY_KEY]} | |||||
| onClick={() => submitLegalDoc('usagePolicy', '使用政策已更新', '使用政策更新失败')} | |||||
| loading={loadingInput['usagePolicy']} | |||||
| > | > | ||||
| {t('设置使用政策')} | {t('设置使用政策')} | ||||
| </Button> | </Button> | ||||
| @@ -27,12 +27,7 @@ import { UserContext } from "../../../../context/User"; | |||||
| // Language options with native names | // Language options with native names | ||||
| const languageOptions = [ | const languageOptions = [ | ||||
| { value: "zh-CN", label: "简体中文" }, | { value: "zh-CN", label: "简体中文" }, | ||||
| { value: "zh-TW", label: "繁體中文" }, | |||||
| { value: "en", label: "English" }, | { 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 }) => { | const PreferencesSettings = ({ t }) => { | ||||
| @@ -118,12 +118,6 @@ const SyncWizardModal = ({ visible, onClose, onConfirm, loading, t }) => { | |||||
| <Radio value='zh-CN' extra='简体中文'> | <Radio value='zh-CN' extra='简体中文'> | ||||
| zh-CN | zh-CN | ||||
| </Radio> | </Radio> | ||||
| <Radio value='zh-TW' extra='繁體中文'> | |||||
| zh-TW | |||||
| </Radio> | |||||
| <Radio value='ja' extra='日本語'> | |||||
| ja | |||||
| </Radio> | |||||
| </RadioGroup> | </RadioGroup> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| @@ -22,12 +22,7 @@ import { initReactI18next } from 'react-i18next'; | |||||
| import LanguageDetector from 'i18next-browser-languagedetector'; | import LanguageDetector from 'i18next-browser-languagedetector'; | ||||
| import enTranslation from './locales/en.json'; | import enTranslation from './locales/en.json'; | ||||
| import frTranslation from './locales/fr.json'; | |||||
| import zhCNTranslation from './locales/zh-CN.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 | i18n | ||||
| .use(LanguageDetector) | .use(LanguageDetector) | ||||
| @@ -37,11 +32,6 @@ i18n | |||||
| resources: { | resources: { | ||||
| en: enTranslation, | en: enTranslation, | ||||
| 'zh-CN': zhCNTranslation, | 'zh-CN': zhCNTranslation, | ||||
| 'zh-TW': zhTWTranslation, | |||||
| fr: frTranslation, | |||||
| ru: ruTranslation, | |||||
| ja: jaTranslation, | |||||
| vi: viTranslation, | |||||
| }, | }, | ||||
| fallbackLng: 'zh-CN', | fallbackLng: 'zh-CN', | ||||
| nsSeparator: false, | nsSeparator: false, | ||||
| @@ -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 React from 'react'; | ||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import DocumentRenderer from '../../components/common/DocumentRenderer'; | import DocumentRenderer from '../../components/common/DocumentRenderer'; | ||||
| const PrivacyPolicy = () => { | const PrivacyPolicy = () => { | ||||
| const { t } = useTranslation(); | |||||
| const { i18n } = useTranslation(); | |||||
| const lang = i18n.language.startsWith('en') ? 'en' : 'zh'; | |||||
| return ( | return ( | ||||
| <DocumentRenderer | <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...' : '加载隐私政策内容失败...'} | |||||
| /> | /> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -3,14 +3,15 @@ import { useTranslation } from 'react-i18next'; | |||||
| import DocumentRenderer from '../../components/common/DocumentRenderer'; | import DocumentRenderer from '../../components/common/DocumentRenderer'; | ||||
| const Terms = () => { | const Terms = () => { | ||||
| const { t } = useTranslation(); | |||||
| const { i18n } = useTranslation(); | |||||
| const lang = i18n.language.startsWith('en') ? 'en' : 'zh'; | |||||
| return ( | return ( | ||||
| <DocumentRenderer | <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...' : '加载服务条款内容失败...'} | |||||
| /> | /> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -3,14 +3,15 @@ import { useTranslation } from 'react-i18next'; | |||||
| import DocumentRenderer from '../../components/common/DocumentRenderer'; | import DocumentRenderer from '../../components/common/DocumentRenderer'; | ||||
| const UsagePolicy = () => { | const UsagePolicy = () => { | ||||
| const { t } = useTranslation(); | |||||
| const { i18n } = useTranslation(); | |||||
| const lang = i18n.language.startsWith('en') ? 'en' : 'zh'; | |||||
| return ( | return ( | ||||
| <DocumentRenderer | <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...' : '加载使用政策内容失败...'} | |||||
| /> | /> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -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 React from 'react'; | ||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import DocumentRenderer from '../../components/common/DocumentRenderer'; | import DocumentRenderer from '../../components/common/DocumentRenderer'; | ||||
| const UserAgreement = () => { | const UserAgreement = () => { | ||||
| const { t } = useTranslation(); | |||||
| const { i18n } = useTranslation(); | |||||
| const lang = i18n.language.startsWith('en') ? 'en' : 'zh'; | |||||
| return ( | return ( | ||||
| <DocumentRenderer | <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...' : '加载用户协议内容失败...'} | |||||
| /> | /> | ||||
| ); | ); | ||||
| }; | }; | ||||