mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-06-02 00:57:41 +08:00
feat(i18n): bind web language switch to cow_lang config
This commit is contained in:
@@ -659,6 +659,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Language Config Card -->
|
||||||
|
<div class="bg-white dark:bg-[#1A1A1A] rounded-xl border border-slate-200 dark:border-white/10 p-6">
|
||||||
|
<div class="flex items-center gap-3 mb-5">
|
||||||
|
<div class="w-9 h-9 rounded-lg bg-sky-50 dark:bg-sky-900/30 flex items-center justify-center">
|
||||||
|
<i class="fas fa-language text-sky-500 text-sm"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="font-semibold text-slate-800 dark:text-slate-100" data-i18n="config_language">语言</h3>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label class="flex items-center gap-1.5 text-sm font-medium text-slate-600 dark:text-slate-400 mb-1.5">
|
||||||
|
<span data-i18n="config_language">语言</span>
|
||||||
|
<span class="cfg-tip" data-tip-key="config_language_hint"><i class="fas fa-circle-question"></i></span>
|
||||||
|
</label>
|
||||||
|
<div id="cfg-lang-select" class="cfg-dropdown" tabindex="0">
|
||||||
|
<div class="cfg-dropdown-selected">
|
||||||
|
<span class="cfg-dropdown-text">--</span>
|
||||||
|
<i class="fas fa-chevron-down cfg-dropdown-arrow"></i>
|
||||||
|
</div>
|
||||||
|
<div class="cfg-dropdown-menu"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ const I18N = {
|
|||||||
input_placeholder: '输入消息,或输入 / 使用指令',
|
input_placeholder: '输入消息,或输入 / 使用指令',
|
||||||
config_title: '配置管理', config_desc: '管理模型和 Agent 配置',
|
config_title: '配置管理', config_desc: '管理模型和 Agent 配置',
|
||||||
config_model: '模型配置', config_agent: 'Agent 配置',
|
config_model: '模型配置', config_agent: 'Agent 配置',
|
||||||
|
config_language: '语言', config_language_hint: '界面展示、命令文案、系统报错等使用的语言(与右上角切换同步)',
|
||||||
config_model_advanced: '高级配置',
|
config_model_advanced: '高级配置',
|
||||||
config_channel: '通道配置',
|
config_channel: '通道配置',
|
||||||
config_agent_enabled: 'Agent 模式',
|
config_agent_enabled: 'Agent 模式',
|
||||||
@@ -310,6 +311,7 @@ const I18N = {
|
|||||||
input_placeholder: 'Type a message, or press / for commands',
|
input_placeholder: 'Type a message, or press / for commands',
|
||||||
config_title: 'Configuration', config_desc: 'Manage model and agent settings',
|
config_title: 'Configuration', config_desc: 'Manage model and agent settings',
|
||||||
config_model: 'Model Configuration', config_agent: 'Agent Configuration',
|
config_model: 'Model Configuration', config_agent: 'Agent Configuration',
|
||||||
|
config_language: 'Language', config_language_hint: 'Language for the UI, command text, system messages and more (synced with the top-right switch)',
|
||||||
config_model_advanced: 'Advanced',
|
config_model_advanced: 'Advanced',
|
||||||
config_channel: 'Channel Configuration',
|
config_channel: 'Channel Configuration',
|
||||||
config_agent_enabled: 'Agent Mode',
|
config_agent_enabled: 'Agent Mode',
|
||||||
@@ -454,14 +456,60 @@ function applyI18n() {
|
|||||||
if (langLabel) langLabel.textContent = currentLang === 'zh' ? '中文' : 'EN';
|
if (langLabel) langLabel.textContent = currentLang === 'zh' ? '中文' : 'EN';
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleLanguage() {
|
// Single entry point for switching language. Updates the in-memory language,
|
||||||
currentLang = currentLang === 'zh' ? 'en' : 'zh';
|
// persists the user choice locally, re-renders the UI, and binds the choice to
|
||||||
|
// the backend `cow_lang` config so logs / agent replies / CLI follow suit.
|
||||||
|
function setLanguage(lang) {
|
||||||
|
const next = (lang === 'en') ? 'en' : 'zh';
|
||||||
|
if (next === currentLang) {
|
||||||
|
// Still persist + sync in case storage/backend drifted from the UI.
|
||||||
|
syncLanguageToBackend(next);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentLang = next;
|
||||||
localStorage.setItem('cow_lang', currentLang);
|
localStorage.setItem('cow_lang', currentLang);
|
||||||
applyI18n();
|
applyI18n();
|
||||||
_applyInputTooltips();
|
_applyInputTooltips();
|
||||||
// Re-render views whose DOM is built in JS (data-i18n alone does not
|
// Re-render views whose DOM is built in JS (data-i18n alone does not
|
||||||
// cover strings interpolated via t() into innerHTML).
|
// cover strings interpolated via t() into innerHTML).
|
||||||
try { rerenderDynamicViews(); } catch (e) {}
|
try { rerenderDynamicViews(); } catch (e) {}
|
||||||
|
// Keep the language switch button and config selector visually in sync.
|
||||||
|
try { updateLangControls(); } catch (e) {}
|
||||||
|
syncLanguageToBackend(currentLang);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Persist the language to the backend `cow_lang` config (best-effort; the UI
|
||||||
|
// has already switched locally, so a network failure is non-blocking).
|
||||||
|
function syncLanguageToBackend(lang) {
|
||||||
|
try {
|
||||||
|
fetch('/config', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ updates: { cow_lang: lang } })
|
||||||
|
}).catch(() => {});
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reflect the current language on both the top-right toggle and the config
|
||||||
|
// selector (if present), so the two entry points stay synchronized.
|
||||||
|
function updateLangControls() {
|
||||||
|
const langLabel = document.getElementById('lang-label');
|
||||||
|
if (langLabel) langLabel.textContent = currentLang === 'zh' ? '中文' : 'EN';
|
||||||
|
// The config language picker is the custom .cfg-dropdown component. Only
|
||||||
|
// sync it once it has been initialized (i.e. the config panel was opened).
|
||||||
|
const sel = document.getElementById('cfg-lang-select');
|
||||||
|
if (sel && sel._ddValue !== undefined && sel._ddValue !== currentLang) {
|
||||||
|
sel._ddValue = currentLang;
|
||||||
|
const textEl = sel.querySelector('.cfg-dropdown-text');
|
||||||
|
if (textEl) textEl.textContent = currentLang === 'zh' ? '中文' : 'English';
|
||||||
|
sel.querySelectorAll('.cfg-dropdown-item').forEach(i => {
|
||||||
|
i.classList.toggle('active', i.dataset.value === currentLang);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleLanguage() {
|
||||||
|
setLanguage(currentLang === 'zh' ? 'en' : 'zh');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh JS-rendered views after a language switch. Each branch uses the
|
// Refresh JS-rendered views after a language switch. Each branch uses the
|
||||||
@@ -3358,6 +3406,18 @@ function initConfigView(data) {
|
|||||||
document.getElementById('cfg-max-steps').value = data.agent_max_steps || 20;
|
document.getElementById('cfg-max-steps').value = data.agent_max_steps || 20;
|
||||||
document.getElementById('cfg-enable-thinking').checked = data.enable_thinking === true;
|
document.getElementById('cfg-enable-thinking').checked = data.enable_thinking === true;
|
||||||
|
|
||||||
|
// Reflect the current UI language (already resolved, may include the user's
|
||||||
|
// local choice) on the selector so it stays in sync with the top-right toggle.
|
||||||
|
const langSel = document.getElementById('cfg-lang-select');
|
||||||
|
if (langSel) {
|
||||||
|
initDropdown(
|
||||||
|
langSel,
|
||||||
|
[{ value: 'zh', label: '中文' }, { value: 'en', label: 'English' }],
|
||||||
|
currentLang,
|
||||||
|
(val) => setLanguage(val)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const pwdInput = document.getElementById('cfg-password');
|
const pwdInput = document.getElementById('cfg-password');
|
||||||
const maskedPwd = data.web_password_masked || '';
|
const maskedPwd = data.web_password_masked || '';
|
||||||
pwdInput.value = maskedPwd;
|
pwdInput.value = maskedPwd;
|
||||||
|
|||||||
@@ -1535,6 +1535,7 @@ class ConfigHandler:
|
|||||||
])
|
])
|
||||||
|
|
||||||
EDITABLE_KEYS = {
|
EDITABLE_KEYS = {
|
||||||
|
"cow_lang",
|
||||||
"model", "bot_type", "use_linkai",
|
"model", "bot_type", "use_linkai",
|
||||||
"open_ai_api_base", "deepseek_api_base", "qianfan_api_base", "claude_api_base", "gemini_api_base",
|
"open_ai_api_base", "deepseek_api_base", "qianfan_api_base", "claude_api_base", "gemini_api_base",
|
||||||
"zhipu_ai_api_base", "moonshot_base_url", "ark_base_url", "custom_api_base", "mimo_api_base",
|
"zhipu_ai_api_base", "moonshot_base_url", "ark_base_url", "custom_api_base", "mimo_api_base",
|
||||||
@@ -1643,6 +1644,15 @@ class ConfigHandler:
|
|||||||
|
|
||||||
logger.info(f"[WebChannel] Config updated: {list(applied.keys())}")
|
logger.info(f"[WebChannel] Config updated: {list(applied.keys())}")
|
||||||
|
|
||||||
|
# Apply a language change immediately so backend logs, agent
|
||||||
|
# replies and CLI output switch without a restart.
|
||||||
|
if "cow_lang" in applied:
|
||||||
|
try:
|
||||||
|
i18n.resolve_language(applied["cow_lang"])
|
||||||
|
logger.info(f"[WebChannel] Language switched to: {i18n.get_language()}")
|
||||||
|
except Exception as lang_err:
|
||||||
|
logger.warning(f"[WebChannel] Failed to apply language: {lang_err}")
|
||||||
|
|
||||||
# Reset Bridge so that bot routing reflects the new config.
|
# Reset Bridge so that bot routing reflects the new config.
|
||||||
# Without this, Bridge keeps its cached bot instance (e.g. LinkAIBot)
|
# Without this, Bridge keeps its cached bot instance (e.g. LinkAIBot)
|
||||||
# even after the user switches bot_type / use_linkai / model in UI.
|
# even after the user switches bot_type / use_linkai / model in UI.
|
||||||
|
|||||||
@@ -298,6 +298,8 @@ def status():
|
|||||||
click.echo(_t(f" 模型: {cfg.get('model', 'unknown')}", f" Model: {cfg.get('model', 'unknown')}"))
|
click.echo(_t(f" 模型: {cfg.get('model', 'unknown')}", f" Model: {cfg.get('model', 'unknown')}"))
|
||||||
mode = "Chat" if cfg.get("agent") is False else "Agent"
|
mode = "Chat" if cfg.get("agent") is False else "Agent"
|
||||||
click.echo(_t(f" 模式: {mode}", f" Mode: {mode}"))
|
click.echo(_t(f" 模式: {mode}", f" Mode: {mode}"))
|
||||||
|
lang_label = "中文" if i18n.get_language() == "zh" else "English"
|
||||||
|
click.echo(_t(f" 语言: {lang_label}", f" Language: {lang_label}"))
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
|
|||||||
@@ -465,6 +465,10 @@ class CowCliPlugin(Plugin):
|
|||||||
mode = "Chat" if cfg.get("agent") is False else "Agent"
|
mode = "Chat" if cfg.get("agent") is False else "Agent"
|
||||||
lines.append(_t(f" 模式: {mode}", f" Mode: {mode}"))
|
lines.append(_t(f" 模式: {mode}", f" Mode: {mode}"))
|
||||||
|
|
||||||
|
from common import i18n
|
||||||
|
lang_label = "中文" if i18n.get_language() == "zh" else "English"
|
||||||
|
lines.append(_t(f" 语言: {lang_label}", f" Language: {lang_label}"))
|
||||||
|
|
||||||
session_id = self._get_session_id(e_context, fallback=session_id)
|
session_id = self._get_session_id(e_context, fallback=session_id)
|
||||||
agent = self._get_agent(session_id)
|
agent = self._get_agent(session_id)
|
||||||
if agent:
|
if agent:
|
||||||
|
|||||||
Reference in New Issue
Block a user