From 9eb58c684eec09a0cf844ca93c3e4bdd5d3c2e2f Mon Sep 17 00:00:00 2001 From: fengsilin Date: Fri, 17 Apr 2026 13:54:29 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=90=8E=E5=8F=B0=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E8=AF=AD=E8=A8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 管理员可在系统设置页面配置全局默认语言,未登录用户和未设置 语言偏好的已登录用户将强制使用该语言,忽略浏览器语言检测。 - common/constants.go: 新增 DefaultLanguage 变量 - model/option.go: 新增 DefaultLanguage option handler - controller/misc.go: /api/status 暴露 default_language - SystemSetting.jsx: 添加语言下拉框到通用设置 - PageLayout.jsx: 未登录用户应用默认语言 - UserContext.jsx: 无偏好用户回退到默认语言 Co-Authored-By: Claude Opus 4.6 --- common/constants.go | 1 + controller/misc.go | 1 + model/option.go | 2 ++ web/src/components/layout/PageLayout.jsx | 4 +++ web/src/components/settings/SystemSetting.jsx | 30 ++++++++++++++++++- web/src/context/User/index.jsx | 7 ++++- 6 files changed, 43 insertions(+), 2 deletions(-) diff --git a/common/constants.go b/common/constants.go index 6823b2c..40d77ce 100644 --- a/common/constants.go +++ b/common/constants.go @@ -16,6 +16,7 @@ var SystemName = "New API" var Footer = "" var Logo = "" var TopUpLink = "" +var DefaultLanguage = "" // admin-configured default language; empty = follow browser detection // var ChatLink = "" // var ChatLink2 = "" diff --git a/controller/misc.go b/controller/misc.go index b24a74a..28e767f 100644 --- a/controller/misc.go +++ b/controller/misc.go @@ -87,6 +87,7 @@ func GetStatus(c *gin.Context) { "demo_site_enabled": operation_setting.DemoSiteEnabled, "self_use_mode_enabled": operation_setting.SelfUseModeEnabled, "default_use_auto_group": setting.DefaultUseAutoGroup, + "default_language": common.DefaultLanguage, "usd_exchange_rate": operation_setting.USDExchangeRate, "price": operation_setting.Price, diff --git a/model/option.go b/model/option.go index e8aee77..5bbe0ad 100644 --- a/model/option.go +++ b/model/option.go @@ -456,6 +456,8 @@ func updateOptionMap(key string, value string) (err error) { common.SystemName = value case "Logo": common.Logo = value + case "DefaultLanguage": + common.DefaultLanguage = value case "WeChatServerAddress": common.WeChatServerAddress = value case "WeChatServerToken": diff --git a/web/src/components/layout/PageLayout.jsx b/web/src/components/layout/PageLayout.jsx index 49c47e6..ebba8d5 100644 --- a/web/src/components/layout/PageLayout.jsx +++ b/web/src/components/layout/PageLayout.jsx @@ -91,6 +91,10 @@ const PageLayout = () => { if (success) { statusDispatch({ type: 'set', payload: data }); setStatusData(data); + // Apply admin-configured default language for unauthenticated users + if (data.default_language && !localStorage.getItem('user')) { + i18n.changeLanguage(data.default_language); + } } else { showError('Unable to connect to server'); } diff --git a/web/src/components/settings/SystemSetting.jsx b/web/src/components/settings/SystemSetting.jsx index 8334c01..fbc2f43 100644 --- a/web/src/components/settings/SystemSetting.jsx +++ b/web/src/components/settings/SystemSetting.jsx @@ -100,6 +100,7 @@ const SystemSetting = () => { LinuxDOClientSecret: '', LinuxDOMinimumTrustLevel: '', ServerAddress: '', + DefaultLanguage: '', // SSRF防护配置 'fetch_setting.enable_ssrf_protection': true, 'fetch_setting.allow_private_ip': '', @@ -317,6 +318,10 @@ const SystemSetting = () => { await updateOptions([{ key: 'ServerAddress', value: ServerAddress }]); }; + const submitDefaultLanguage = async () => { + await updateOptions([{ key: 'DefaultLanguage', value: inputs.DefaultLanguage || '' }]); + }; + const submitSMTP = async () => { const options = []; @@ -716,7 +721,7 @@ const SystemSetting = () => { - + { )} /> + + + + diff --git a/web/src/context/User/index.jsx b/web/src/context/User/index.jsx index 2dd9100..7ecdebf 100644 --- a/web/src/context/User/index.jsx +++ b/web/src/context/User/index.jsx @@ -20,6 +20,7 @@ For commercial licensing, please contact support@quantumnous.com import React, { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { reducer, initialState } from './reducer'; +import { StatusContext } from '../Status'; export const UserContext = React.createContext({ state: initialState, @@ -29,6 +30,7 @@ export const UserContext = React.createContext({ export const UserProvider = ({ children }) => { const [state, dispatch] = React.useReducer(reducer, initialState); const { i18n } = useTranslation(); + const [statusState] = React.useContext(StatusContext); // Sync language preference when user data is loaded useEffect(() => { @@ -37,12 +39,15 @@ export const UserProvider = ({ children }) => { const settings = JSON.parse(state.user.setting); if (settings.language && settings.language !== i18n.language) { i18n.changeLanguage(settings.language); + } else if (!settings.language && statusState.status?.default_language) { + // No personal preference — fall back to admin default + i18n.changeLanguage(statusState.status.default_language); } } catch (e) { // Ignore parse errors } } - }, [state.user?.setting, i18n]); + }, [state.user?.setting, statusState.status?.default_language, i18n]); return (