feat(i18n): support Traditional Chinese (zh-tw) for console, logs and docs

This commit is contained in:
anomixer
2026-07-06 00:00:16 +08:00
parent d531e14fbf
commit 4b57971d33
8 changed files with 774 additions and 27 deletions

View File

@@ -1864,6 +1864,7 @@ class ConfigHandler:
json.dump(file_cfg, f, indent=4, ensure_ascii=False)
logger.info(f"[WebChannel] Config updated: {list(applied.keys())}")
restarted = False
# Apply a language change immediately so backend logs, agent
# replies and CLI output switch without a restart.
@@ -1874,6 +1875,26 @@ class ConfigHandler:
except Exception as lang_err:
logger.warning(f"[WebChannel] Failed to apply language: {lang_err}")
# Trigger a dynamic restart of the web channel if web_password is changed,
# so the socket re-binds to either 127.0.0.1 or 0.0.0.0 immediately.
if "web_password" in applied:
try:
import sys
app_module = sys.modules.get('__main__') or sys.modules.get('app')
mgr = getattr(app_module, '_channel_mgr', None) if app_module else None
if mgr:
def delay_restart():
time.sleep(0.5)
mgr.restart("web")
threading.Thread(
target=delay_restart,
daemon=True,
).start()
restarted = True
logger.info("[WebChannel] Web channel restart scheduled due to password change")
except Exception as restart_err:
logger.warning(f"[WebChannel] Failed to trigger web channel restart: {restart_err}")
# Reset Bridge so that bot routing reflects the new config.
# Without this, Bridge keeps its cached bot instance (e.g. LinkAIBot)
# even after the user switches bot_type / use_linkai / model in UI.
@@ -1886,7 +1907,7 @@ class ConfigHandler:
except Exception as reset_err:
logger.warning(f"[WebChannel] Failed to reset bridge: {reset_err}")
return json.dumps({"status": "success", "applied": applied}, ensure_ascii=False)
return json.dumps({"status": "success", "applied": applied, "restarted": restarted}, ensure_ascii=False)
except Exception as e:
logger.error(f"Error updating config: {e}")
return json.dumps({"status": "error", "message": str(e)})
@@ -3667,11 +3688,13 @@ class ChannelsHandler:
_require_auth()
web.header('Content-Type', 'application/json; charset=utf-8')
try:
from common import i18n
local_config = conf()
active_channels = self._active_channel_set()
# Desktop build ships without lark-oapi, so hide Feishu from the list.
desktop_mode = os.environ.get("COW_DESKTOP") == "1"
channels = []
is_tw = i18n.get_language() == i18n.ZH_TW
for ch_name, ch_def in self.CHANNEL_DEFS.items():
if desktop_mode and ch_name == "feishu":
continue
@@ -3682,16 +3705,32 @@ class ChannelsHandler:
display_val = self._mask_secret(str(raw_val))
else:
display_val = raw_val
label_val = f["label"]
if is_tw and isinstance(label_val, str):
label_val = i18n.to_traditional(label_val)
elif is_tw and isinstance(label_val, dict):
label_val = label_val.copy()
label_val["zh-tw"] = i18n.to_traditional(label_val.get("zh", ""))
fields_out.append({
"key": f["key"],
"label": f["label"],
"label": label_val,
"type": f["type"],
"value": display_val,
"default": f.get("default", ""),
})
label_val = ch_def["label"]
if is_tw and isinstance(label_val, str):
label_val = i18n.to_traditional(label_val)
elif is_tw and isinstance(label_val, dict):
label_val = label_val.copy()
label_val["zh-tw"] = i18n.to_traditional(label_val.get("zh", ""))
ch_info = {
"name": ch_name,
"label": ch_def["label"],
"label": label_val,
"icon": ch_def["icon"],
"color": ch_def["color"],
"active": ch_name in active_channels,
@@ -4248,16 +4287,26 @@ class ToolsHandler:
web.header('Content-Type', 'application/json; charset=utf-8')
try:
from agent.tools.tool_manager import ToolManager
from common import i18n
tm = ToolManager()
if not tm.tool_classes:
tm.load_tools()
tools = []
lang = i18n.get_language()
for name, cls in tm.tool_classes.items():
try:
instance = cls()
desc = instance.description
if lang == i18n.ZH_TW and desc:
desc = i18n.to_traditional(desc)
elif lang == "en" and name == "scheduler":
desc = (
"Create, query and manage scheduled tasks (reminders, periodic tasks, etc.).\n\n"
"⚠️ IMPORTANT: Only use this tool when delayed or periodic execution is needed."
)
tools.append({
"name": name,
"description": instance.description,
"description": desc,
})
except Exception:
tools.append({"name": name, "description": ""})
@@ -4274,10 +4323,17 @@ class SkillsHandler:
try:
from agent.skills.service import SkillService
from agent.skills.manager import SkillManager
from common import i18n
workspace_root = _get_workspace_root()
manager = SkillManager(custom_dir=os.path.join(workspace_root, "skills"))
service = SkillService(manager)
skills = service.query()
if i18n.get_language() == i18n.ZH_TW:
for skill in skills:
if isinstance(skill, dict):
for k, v in list(skill.items()):
if k in ("name", "description", "display_name") and isinstance(v, str):
skill[k] = i18n.to_traditional(v)
return json.dumps({"status": "success", "skills": skills}, ensure_ascii=False)
except Exception as e:
logger.error(f"[WebChannel] Skills API error: {e}")