mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-06-02 00:57:41 +08:00
feat(i18n): add global language resolution and localize user-facing text
This commit is contained in:
@@ -47,11 +47,30 @@
|
||||
This runs synchronously in <head> so the correct class is on <html>
|
||||
before any CSS or body rendering occurs. -->
|
||||
<script>
|
||||
// Map an arbitrary locale string (zh-CN, en-US, fr ...) to 'zh' / 'en',
|
||||
// or '' when unrecognized so callers can fall through to the next source.
|
||||
window.__cowNormalizeLang__ = function(raw) {
|
||||
if (!raw) return '';
|
||||
var v = String(raw).trim().toLowerCase();
|
||||
if (v === 'auto') return '';
|
||||
if (v.indexOf('zh') === 0) return 'zh';
|
||||
if (v.indexOf('en') === 0) return 'en';
|
||||
return '';
|
||||
};
|
||||
// Resolve the console language by priority:
|
||||
// user choice (localStorage) -> backend-detected -> browser -> 'zh'.
|
||||
window.__cowResolveLang__ = function() {
|
||||
return window.__cowNormalizeLang__(localStorage.getItem('cow_lang'))
|
||||
|| window.__cowNormalizeLang__(window.__COW_DEFAULT_LANG__)
|
||||
|| window.__cowNormalizeLang__(navigator.language || (navigator.languages && navigator.languages[0]))
|
||||
|| 'zh';
|
||||
};
|
||||
(function() {
|
||||
// Backend-resolved default language (from cow_lang config / auto-detect).
|
||||
window.__COW_DEFAULT_LANG__ = '{{COW_DEFAULT_LANG}}';
|
||||
var theme = localStorage.getItem('cow_theme') || 'dark';
|
||||
if (theme === 'dark') document.documentElement.classList.add('dark');
|
||||
var lang = localStorage.getItem('cow_lang') || 'zh';
|
||||
document.documentElement.setAttribute('lang', lang);
|
||||
document.documentElement.setAttribute('lang', window.__cowResolveLang__());
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
|
||||
@@ -91,6 +91,27 @@ const I18N = {
|
||||
example_knowledge_title: '知识库', example_knowledge_text: '查看知识库当前文档情况',
|
||||
example_skill_title: '技能系统', example_skill_text: '查看所有支持的工具和技能',
|
||||
example_web_title: '指令中心', example_web_text: '查看全部命令',
|
||||
slash_help: '显示命令帮助',
|
||||
slash_status: '查看运行状态',
|
||||
slash_context: '查看对话上下文',
|
||||
slash_context_clear: '清除对话上下文',
|
||||
slash_skill_list: '查看已安装技能',
|
||||
slash_skill_list_remote: '浏览技能广场',
|
||||
slash_skill_search: '搜索技能',
|
||||
slash_skill_install: '安装技能 (名称或 GitHub URL)',
|
||||
slash_skill_uninstall: '卸载技能',
|
||||
slash_skill_info: '查看技能详情',
|
||||
slash_skill_enable: '启用技能',
|
||||
slash_skill_disable: '禁用技能',
|
||||
slash_memory_dream: '手动触发记忆蒸馏 (可指定天数, 默认3)',
|
||||
slash_knowledge: '查看知识库统计',
|
||||
slash_knowledge_list: '查看知识库文件树',
|
||||
slash_knowledge_on: '开启知识库',
|
||||
slash_knowledge_off: '关闭知识库',
|
||||
slash_config: '查看当前配置',
|
||||
slash_cancel: '中止当前正在运行的 Agent 任务',
|
||||
slash_logs: '查看最近日志',
|
||||
slash_version: '查看版本',
|
||||
input_placeholder: '输入消息,或输入 / 使用指令',
|
||||
config_title: '配置管理', config_desc: '管理模型和 Agent 配置',
|
||||
config_model: '模型配置', config_agent: 'Agent 配置',
|
||||
@@ -265,6 +286,27 @@ const I18N = {
|
||||
example_knowledge_title: 'Knowledge', example_knowledge_text: 'Show me the current knowledge base',
|
||||
example_skill_title: 'Skills', example_skill_text: 'Show current tools and skills',
|
||||
example_web_title: 'Commands', example_web_text: 'Show all commands',
|
||||
slash_help: 'Show this help',
|
||||
slash_status: 'Show running status',
|
||||
slash_context: 'Show conversation context',
|
||||
slash_context_clear: 'Clear conversation context',
|
||||
slash_skill_list: 'List installed skills',
|
||||
slash_skill_list_remote: 'Browse Skill Hub',
|
||||
slash_skill_search: 'Search skills',
|
||||
slash_skill_install: 'Install a skill (name or GitHub URL)',
|
||||
slash_skill_uninstall: 'Uninstall a skill',
|
||||
slash_skill_info: 'Show skill details',
|
||||
slash_skill_enable: 'Enable a skill',
|
||||
slash_skill_disable: 'Disable a skill',
|
||||
slash_memory_dream: 'Trigger memory distillation (optional days, default 3)',
|
||||
slash_knowledge: 'Show knowledge base stats',
|
||||
slash_knowledge_list: 'Show knowledge base file tree',
|
||||
slash_knowledge_on: 'Enable knowledge base',
|
||||
slash_knowledge_off: 'Disable knowledge base',
|
||||
slash_config: 'Show current config',
|
||||
slash_cancel: 'Abort the running Agent task',
|
||||
slash_logs: 'Show recent logs',
|
||||
slash_version: 'Show version',
|
||||
input_placeholder: 'Type a message, or press / for commands',
|
||||
config_title: 'Configuration', config_desc: 'Manage model and agent settings',
|
||||
config_model: 'Model Configuration', config_agent: 'Agent Configuration',
|
||||
@@ -361,7 +403,25 @@ const I18N = {
|
||||
}
|
||||
};
|
||||
|
||||
let currentLang = localStorage.getItem('cow_lang') || 'zh';
|
||||
// Resolve language by priority: user choice (localStorage) -> backend-detected
|
||||
// (cow_lang) -> browser language -> 'zh'. Shares __cowResolveLang__ defined in
|
||||
// chat.html; falls back to a local resolver if loaded standalone.
|
||||
let currentLang = (typeof window.__cowResolveLang__ === 'function')
|
||||
? window.__cowResolveLang__()
|
||||
: (function () {
|
||||
const norm = (raw) => {
|
||||
if (!raw) return '';
|
||||
const v = String(raw).trim().toLowerCase();
|
||||
if (v === 'auto') return '';
|
||||
if (v.indexOf('zh') === 0) return 'zh';
|
||||
if (v.indexOf('en') === 0) return 'en';
|
||||
return '';
|
||||
};
|
||||
return norm(localStorage.getItem('cow_lang'))
|
||||
|| norm(window.__COW_DEFAULT_LANG__)
|
||||
|| norm(navigator.language)
|
||||
|| 'zh';
|
||||
})();
|
||||
|
||||
function t(key) {
|
||||
return (I18N[currentLang] && I18N[currentLang][key]) || (I18N.en[key]) || key;
|
||||
@@ -1298,28 +1358,30 @@ chatInput.addEventListener('compositionstart', () => { isComposing = true; });
|
||||
chatInput.addEventListener('compositionend', () => { setTimeout(() => { isComposing = false; }, 100); });
|
||||
|
||||
// ── Slash Command Menu ───────────────────────────────────────
|
||||
// desc holds an i18n key, resolved via t() at render time so the menu follows
|
||||
// the current UI language.
|
||||
const SLASH_COMMANDS = [
|
||||
{ cmd: '/help', desc: '显示命令帮助' },
|
||||
{ cmd: '/status', desc: '查看运行状态' },
|
||||
{ cmd: '/context', desc: '查看对话上下文' },
|
||||
{ cmd: '/context clear', desc: '清除对话上下文' },
|
||||
{ cmd: '/skill list', desc: '查看已安装技能' },
|
||||
{ cmd: '/skill list --remote', desc: '浏览技能广场' },
|
||||
{ cmd: '/skill search ', desc: '搜索技能' },
|
||||
{ cmd: '/skill install ', desc: '安装技能 (名称或 GitHub URL)' },
|
||||
{ cmd: '/skill uninstall ', desc: '卸载技能' },
|
||||
{ cmd: '/skill info ', desc: '查看技能详情' },
|
||||
{ cmd: '/skill enable ', desc: '启用技能' },
|
||||
{ cmd: '/skill disable ', desc: '禁用技能' },
|
||||
{ cmd: '/memory dream ', desc: '手动触发记忆蒸馏 (可指定天数, 默认3)' },
|
||||
{ cmd: '/knowledge', desc: '查看知识库统计' },
|
||||
{ cmd: '/knowledge list', desc: '查看知识库文件树' },
|
||||
{ cmd: '/knowledge on', desc: '开启知识库' },
|
||||
{ cmd: '/knowledge off', desc: '关闭知识库' },
|
||||
{ cmd: '/config', desc: '查看当前配置' },
|
||||
{ cmd: '/cancel', desc: '中止当前正在运行的 Agent 任务' },
|
||||
{ cmd: '/logs', desc: '查看最近日志' },
|
||||
{ cmd: '/version', desc: '查看版本' },
|
||||
{ cmd: '/help', desc: 'slash_help' },
|
||||
{ cmd: '/status', desc: 'slash_status' },
|
||||
{ cmd: '/context', desc: 'slash_context' },
|
||||
{ cmd: '/context clear', desc: 'slash_context_clear' },
|
||||
{ cmd: '/skill list', desc: 'slash_skill_list' },
|
||||
{ cmd: '/skill list --remote', desc: 'slash_skill_list_remote' },
|
||||
{ cmd: '/skill search ', desc: 'slash_skill_search' },
|
||||
{ cmd: '/skill install ', desc: 'slash_skill_install' },
|
||||
{ cmd: '/skill uninstall ', desc: 'slash_skill_uninstall' },
|
||||
{ cmd: '/skill info ', desc: 'slash_skill_info' },
|
||||
{ cmd: '/skill enable ', desc: 'slash_skill_enable' },
|
||||
{ cmd: '/skill disable ', desc: 'slash_skill_disable' },
|
||||
{ cmd: '/memory dream ', desc: 'slash_memory_dream' },
|
||||
{ cmd: '/knowledge', desc: 'slash_knowledge' },
|
||||
{ cmd: '/knowledge list', desc: 'slash_knowledge_list' },
|
||||
{ cmd: '/knowledge on', desc: 'slash_knowledge_on' },
|
||||
{ cmd: '/knowledge off', desc: 'slash_knowledge_off' },
|
||||
{ cmd: '/config', desc: 'slash_config' },
|
||||
{ cmd: '/cancel', desc: 'slash_cancel' },
|
||||
{ cmd: '/logs', desc: 'slash_logs' },
|
||||
{ cmd: '/version', desc: 'slash_version' },
|
||||
];
|
||||
|
||||
const slashMenu = document.getElementById('slash-menu');
|
||||
@@ -1373,7 +1435,7 @@ function renderSlashItems() {
|
||||
slashFiltered.map((c, i) =>
|
||||
`<div class="slash-menu-item${i === slashActiveIdx ? ' active' : ''}" data-idx="${i}">` +
|
||||
`<span class="cmd">${escapeHtml(c.cmd)}</span>` +
|
||||
`<span class="desc">${escapeHtml(c.desc)}</span></div>`
|
||||
`<span class="desc">${escapeHtml(t(c.desc))}</span></div>`
|
||||
).join('');
|
||||
|
||||
const activeEl = slashMenu.querySelector('.slash-menu-item.active');
|
||||
|
||||
@@ -21,6 +21,7 @@ from channel.chat_channel import ChatChannel, check_prefix
|
||||
from channel.chat_message import ChatMessage
|
||||
from collections import OrderedDict
|
||||
from common import const
|
||||
from common import i18n
|
||||
from common.log import logger
|
||||
from common.singleton import singleton
|
||||
from config import conf
|
||||
@@ -98,7 +99,7 @@ def _require_auth():
|
||||
def _cancel_reply_text(cancelled: int, lang: str) -> str:
|
||||
en = lang.startswith("en")
|
||||
if cancelled > 0:
|
||||
return "🛑 Cancelled." if en else "🛑 已中止"
|
||||
return "🛑 Cancelled" if en else "🛑 已中止"
|
||||
return "Nothing to cancel." if en else "当前没有可中止的任务。"
|
||||
|
||||
|
||||
@@ -477,7 +478,10 @@ class WebChannel(ChatChannel):
|
||||
)
|
||||
q.put({
|
||||
"type": "done",
|
||||
"content": "(模型未返回任何内容,请重试或换一种方式描述你的需求)",
|
||||
"content": i18n.t(
|
||||
"(模型未返回任何内容,请重试或换一种方式描述你的需求)",
|
||||
"(The model returned no content. Please retry or rephrase your request.)",
|
||||
),
|
||||
"request_id": request_id,
|
||||
"timestamp": time.time(),
|
||||
})
|
||||
@@ -805,13 +809,13 @@ class WebChannel(ChatChannel):
|
||||
if not fpath:
|
||||
continue
|
||||
if ftype == "image":
|
||||
file_refs.append(f"[图片: {fpath}]")
|
||||
file_refs.append(f"[{i18n.t('图片', 'Image')}: {fpath}]")
|
||||
elif ftype == "video":
|
||||
file_refs.append(f"[视频: {fpath}]")
|
||||
file_refs.append(f"[{i18n.t('视频', 'Video')}: {fpath}]")
|
||||
elif ftype == "directory":
|
||||
file_refs.append(f"[目录: {fpath}]")
|
||||
file_refs.append(f"[{i18n.t('目录', 'Directory')}: {fpath}]")
|
||||
else:
|
||||
file_refs.append(f"[文件: {fpath}]")
|
||||
file_refs.append(f"[{i18n.t('文件', 'File')}: {fpath}]")
|
||||
if file_refs:
|
||||
prompt = prompt + "\n" + "\n".join(file_refs)
|
||||
logger.info(f"[WebChannel] Attached {len(file_refs)} file(s) to message")
|
||||
@@ -952,7 +956,7 @@ class WebChannel(ChatChannel):
|
||||
if request_id and request_id in self.sse_queues:
|
||||
self.sse_queues[request_id].put({
|
||||
"type": "cancelled",
|
||||
"content": "Cancelled" if lang.startswith("en") else "已中止",
|
||||
"content": "🛑 Cancelled" if lang.startswith("en") else "🛑 已中止",
|
||||
"request_id": request_id,
|
||||
"timestamp": time.time(),
|
||||
})
|
||||
@@ -1008,7 +1012,10 @@ class WebChannel(ChatChannel):
|
||||
"""Serve the chat HTML page."""
|
||||
file_path = os.path.join(os.path.dirname(__file__), 'chat.html') # 使用绝对路径
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return f.read()
|
||||
html = f.read()
|
||||
# Inject the backend-resolved default language so the console can use
|
||||
# it on first load (when the user has no saved cow_lang preference).
|
||||
return html.replace("{{COW_DEFAULT_LANG}}", i18n.get_language())
|
||||
|
||||
def startup(self):
|
||||
configured_host = conf().get("web_host", "")
|
||||
@@ -1388,6 +1395,8 @@ class ChatHandler:
|
||||
cache_bust = str(int(time.time()))
|
||||
html = html.replace('assets/js/console.js', f'assets/js/console.js?v={cache_bust}')
|
||||
html = html.replace('assets/css/console.css', f'assets/css/console.css?v={cache_bust}')
|
||||
# Inject the backend-resolved default language for first-load fallback.
|
||||
html = html.replace("{{COW_DEFAULT_LANG}}", i18n.get_language())
|
||||
return html
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user