- 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_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 | |||
| } | |||
| @@ -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() { | |||
| @@ -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> | |||
| } | |||
| > | |||
| @@ -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> | |||
| @@ -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 }) => { | |||
| @@ -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> | |||
| @@ -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, | |||
| @@ -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...' : '加载隐私政策内容失败...'} | |||
| /> | |||
| ); | |||
| }; | |||
| @@ -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...' : '加载服务条款内容失败...'} | |||
| /> | |||
| ); | |||
| }; | |||
| @@ -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...' : '加载使用政策内容失败...'} | |||
| /> | |||
| ); | |||
| }; | |||
| @@ -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...' : '加载用户协议内容失败...'} | |||
| /> | |||
| ); | |||
| }; | |||