mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-07-17 03:03:19 +08:00
refactor(i18n): reply to upstream's #2935 feedback
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
[English] | [<a href="docs/zh/README.md">中文</a>] | [<a href="docs/zh/README-tw.md">繁體中文</a>] | [<a href="docs/ja/README.md">日本語</a>]
|
||||
[English] | [<a href="docs/zh/README.md">中文</a>] | [<a href="docs/zh/README-Hant.md">繁體中文</a>] | [<a href="docs/ja/README.md">日本語</a>]
|
||||
</p>
|
||||
|
||||
**CowAgent** is an open-source super AI assistant that proactively plans tasks, controls your computer and external services, creates and runs Skills, builds a personal knowledge base and long-term memory, and grows alongside you through self-evolution — a reference implementation of Agent Harness engineering.
|
||||
|
||||
@@ -47,13 +47,15 @@
|
||||
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' / 'zh-tw' / 'en',
|
||||
// Map an arbitrary locale string (zh-CN, en-US, fr ...) to 'zh' / 'zh-Hant' / '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().replace('_', '-');
|
||||
if (v === 'auto') return '';
|
||||
if (v.indexOf('zh-tw') === 0 || v.indexOf('zh-hk') === 0 || v.indexOf('zh-hant') >= 0) return 'zh-tw';
|
||||
// Handle Traditional Chinese variants first (more specific)
|
||||
if (v === 'zh-hant' || v.indexOf('zh-hant-') === 0 || v === 'zh-tw' || v === 'zh-hk') return 'zh-Hant';
|
||||
// Then Simplified Chinese
|
||||
if (v.indexOf('zh') === 0) return 'zh';
|
||||
if (v.indexOf('en') === 0) return 'en';
|
||||
return '';
|
||||
|
||||
@@ -147,8 +147,9 @@ const I18N = {
|
||||
config_custom_tip: '接口需遵循 OpenAI API 协议',
|
||||
config_security: '安全设置', config_password: '访问密码',
|
||||
config_password_hint: '留空则不启用密码保护',
|
||||
config_password_changed: '密码已更新,请重新登录',
|
||||
config_password_changed: '密码已更新',
|
||||
config_password_cleared: '密码已清除',
|
||||
config_password_security_warning: '⚠️ 警告:目前密码为空且对外连接埠开放,建议重启服务,或检查是否调整监听位址绑定。',
|
||||
skills_title: '技能管理', skills_desc: '查看、启用或禁用 Agent 工具和技能', skills_hub_btn: '探索技能广场',
|
||||
skills_loading: '加载技能中...', skills_loading_desc: '技能加载后将显示在此处',
|
||||
tools_section_title: '内置工具', tools_loading: '加载工具中...',
|
||||
@@ -256,7 +257,7 @@ const I18N = {
|
||||
edit_cancel: '取消',
|
||||
logout: '退出',
|
||||
},
|
||||
'zh-tw': {
|
||||
'zh-Hant': {
|
||||
|
||||
console: '控制台',
|
||||
nav_chat: '對話', nav_manage: '管理', nav_monitor: '監控',
|
||||
@@ -393,8 +394,9 @@ const I18N = {
|
||||
config_custom_tip: '介面需遵循 OpenAI API 協議',
|
||||
config_security: '安全設定', config_password: '訪問密碼',
|
||||
config_password_hint: '留空則不啟用密碼保護',
|
||||
config_password_changed: '密碼已更新,請重新登入',
|
||||
config_password_changed: '密碼已更新',
|
||||
config_password_cleared: '密碼已清除',
|
||||
config_password_security_warning: '⚠️ 警告:目前密碼為空且對外連接埠開放,建議重啟服務,或檢查是否調整監聽位址綁定。',
|
||||
skills_title: '技能管理', skills_desc: '檢視、啟用或禁用 Agent 工具和技能', skills_hub_btn: '探索技能廣場',
|
||||
skills_loading: '載入技能中...', skills_loading_desc: '技能載入後將顯示在此處',
|
||||
tools_section_title: '內建工具', tools_loading: '載入工具中...',
|
||||
@@ -638,8 +640,9 @@ const I18N = {
|
||||
config_custom_tip: 'API must follow OpenAI protocol.',
|
||||
config_security: 'Security', config_password: 'Password',
|
||||
config_password_hint: 'Leave empty to disable password protection',
|
||||
config_password_changed: 'Password updated, please re-login',
|
||||
config_password_changed: 'Password updated',
|
||||
config_password_cleared: 'Password cleared',
|
||||
config_password_security_warning: '⚠️ Warning: Password is now empty and the port is exposed. Consider restarting the service or adjusting the listening address binding.',
|
||||
skills_title: 'Skills', skills_desc: 'View, enable, or disable agent tools and skills', skills_hub_btn: 'Skill Hub',
|
||||
skills_loading: 'Loading skills...', skills_loading_desc: 'Skills will be displayed here after loading',
|
||||
tools_section_title: 'Built-in Tools', tools_loading: 'Loading tools...',
|
||||
@@ -759,6 +762,9 @@ let currentLang = (typeof window.__cowResolveLang__ === 'function')
|
||||
if (!raw) return '';
|
||||
const v = String(raw).trim().toLowerCase();
|
||||
if (v === 'auto') return '';
|
||||
// Handle Traditional Chinese variants first (more specific)
|
||||
if (v === 'zh-hant' || v.startsWith('zh-hant-') || v === 'zh-tw' || v === 'zh-hk') return 'zh-Hant';
|
||||
// Then Simplified Chinese
|
||||
if (v.indexOf('zh') === 0) return 'zh';
|
||||
if (v.indexOf('en') === 0) return 'en';
|
||||
return '';
|
||||
@@ -799,9 +805,15 @@ function applyI18n() {
|
||||
el.setAttribute('data-tooltip', t(el.dataset.tipKey));
|
||||
});
|
||||
installCfgTipPortal();
|
||||
|
||||
// Clear any status messages when language changes
|
||||
document.querySelectorAll('[id$="-status"]').forEach(el => {
|
||||
el.classList.add('opacity-0');
|
||||
});
|
||||
|
||||
const langLabel = document.getElementById('lang-label');
|
||||
if (langLabel) {
|
||||
if (currentLang === 'zh-tw') langLabel.textContent = '繁中';
|
||||
if (currentLang === 'zh-Hant') langLabel.textContent = '繁中';
|
||||
else if (currentLang === 'zh') langLabel.textContent = '簡中';
|
||||
else langLabel.textContent = 'EN';
|
||||
}
|
||||
@@ -814,7 +826,7 @@ function applyI18n() {
|
||||
// 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' || lang === 'zh' || lang === 'zh-tw') ? lang : 'zh';
|
||||
const next = (lang === 'en' || lang === 'zh' || lang === 'zh-Hant') ? lang : 'zh';
|
||||
if (next === currentLang) {
|
||||
// Still persist + sync in case storage/backend drifted from the UI.
|
||||
syncLanguageToBackend(next);
|
||||
@@ -855,7 +867,7 @@ function syncLanguageToBackend(lang, callback) {
|
||||
function updateLangControls() {
|
||||
const langLabel = document.getElementById('lang-label');
|
||||
if (langLabel) {
|
||||
if (currentLang === 'zh-tw') langLabel.textContent = '繁體';
|
||||
if (currentLang === 'zh-Hant') langLabel.textContent = '繁體';
|
||||
else if (currentLang === 'zh') langLabel.textContent = '簡體';
|
||||
else langLabel.textContent = 'EN';
|
||||
}
|
||||
@@ -866,7 +878,7 @@ function updateLangControls() {
|
||||
sel._ddValue = currentLang;
|
||||
const textEl = sel.querySelector('.cfg-dropdown-text');
|
||||
if (textEl) {
|
||||
if (currentLang === 'zh-tw') textEl.textContent = '繁體中文';
|
||||
if (currentLang === 'zh-Hant') textEl.textContent = '繁體中文';
|
||||
else if (currentLang === 'zh') textEl.textContent = '简体中文';
|
||||
else textEl.textContent = 'English';
|
||||
}
|
||||
@@ -878,8 +890,8 @@ function updateLangControls() {
|
||||
|
||||
function toggleLanguage() {
|
||||
if (currentLang === 'zh') {
|
||||
setLanguage('zh-tw');
|
||||
} else if (currentLang === 'zh-tw') {
|
||||
setLanguage('zh-Hant');
|
||||
} else if (currentLang === 'zh-Hant') {
|
||||
setLanguage('en');
|
||||
} else {
|
||||
setLanguage('zh');
|
||||
@@ -1018,6 +1030,12 @@ function navigateTo(viewId) {
|
||||
document.getElementById('breadcrumb-page').textContent = t(meta.page);
|
||||
document.getElementById('breadcrumb-page').dataset.i18n = meta.page;
|
||||
currentView = viewId;
|
||||
|
||||
// Clear status messages when navigating away
|
||||
document.querySelectorAll('[id$="-status"]').forEach(el => {
|
||||
el.classList.add('opacity-0');
|
||||
});
|
||||
|
||||
if (window.innerWidth < 1024) closeSidebar();
|
||||
}
|
||||
|
||||
@@ -4554,7 +4572,7 @@ function initConfigView(data) {
|
||||
if (langSel) {
|
||||
initDropdown(
|
||||
langSel,
|
||||
[{ value: 'zh', label: '简体中文' }, { value: 'zh-tw', label: '繁體中文' }, { value: 'en', label: 'English' }],
|
||||
[{ value: 'zh', label: '简体中文' }, { value: 'zh-Hant', label: '繁體中文' }, { value: 'en', label: 'English' }],
|
||||
currentLang,
|
||||
(val) => setLanguage(val)
|
||||
);
|
||||
@@ -4727,7 +4745,10 @@ function showStatus(elId, msgKey, isError) {
|
||||
el.classList.toggle('text-red-500', !!isError);
|
||||
el.classList.toggle('text-primary-500', !isError);
|
||||
el.classList.remove('opacity-0');
|
||||
setTimeout(() => el.classList.add('opacity-0'), 2500);
|
||||
// Warning messages (errors) should stay visible, success messages auto-hide
|
||||
if (!isError) {
|
||||
setTimeout(() => el.classList.add('opacity-0'), 2500);
|
||||
}
|
||||
}
|
||||
|
||||
function saveModelConfig() {
|
||||
@@ -4840,18 +4861,33 @@ function savePasswordConfig() {
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
console.log('[Password Config] Response:', data); // Debug
|
||||
if (data.status === 'success') {
|
||||
if (newPwd) {
|
||||
showStatus('cfg-password-status', 'config_password_changed', false);
|
||||
setTimeout(() => { window.location.reload(); }, 1500);
|
||||
// Mark as masked so user needs to re-enter to change again
|
||||
input.dataset.masked = '1';
|
||||
input.dataset.maskedVal = newPwd;
|
||||
input.value = '••••••••';
|
||||
input.classList.add('cfg-key-masked');
|
||||
|
||||
// Show logout button since password is now enabled
|
||||
const logoutBtn = document.getElementById('logout-btn-header');
|
||||
if (logoutBtn) logoutBtn.classList.remove('hidden');
|
||||
} else {
|
||||
input.dataset.masked = '';
|
||||
input.dataset.maskedVal = '';
|
||||
input.classList.remove('cfg-key-masked');
|
||||
showStatus('cfg-password-status', 'config_password_cleared', false);
|
||||
|
||||
// Show security warning if password was cleared with public host
|
||||
if (data.warning === 'password_cleared_with_public_host') {
|
||||
showStatus('cfg-password-status', 'config_password_security_warning', true);
|
||||
} else {
|
||||
showStatus('cfg-password-status', 'config_password_cleared', false);
|
||||
}
|
||||
|
||||
const logoutBtn = document.getElementById('logout-btn-header');
|
||||
if (logoutBtn) logoutBtn.classList.add('hidden');
|
||||
setTimeout(() => { window.location.reload(); }, 1500);
|
||||
}
|
||||
} else {
|
||||
showStatus('cfg-password-status', 'config_save_error', true);
|
||||
@@ -9019,7 +9055,7 @@ function showLoginScreen() {
|
||||
if (currentLang === 'en') {
|
||||
subtitle.textContent = 'Enter password to access the console';
|
||||
loginBtn.textContent = 'Login';
|
||||
} else if (currentLang === 'zh-tw') {
|
||||
} else if (currentLang === 'zh-Hant') {
|
||||
subtitle.textContent = '請輸入密碼以存取控制台';
|
||||
loginBtn.textContent = '登入';
|
||||
} else {
|
||||
@@ -9052,7 +9088,7 @@ function showLoginScreen() {
|
||||
if (logoutBtn) logoutBtn.classList.remove('hidden');
|
||||
initApp();
|
||||
} else {
|
||||
if (currentLang === 'zh-tw') {
|
||||
if (currentLang === 'zh-Hant') {
|
||||
errEl.textContent = '密碼錯誤';
|
||||
} else if (currentLang === 'zh') {
|
||||
errEl.textContent = '密码错误';
|
||||
@@ -9065,7 +9101,7 @@ function showLoginScreen() {
|
||||
}
|
||||
btn.disabled = false;
|
||||
}).catch(() => {
|
||||
if (currentLang === 'zh-tw') {
|
||||
if (currentLang === 'zh-Hant') {
|
||||
errEl.textContent = '網路錯誤,請重試';
|
||||
} else if (currentLang === 'zh') {
|
||||
errEl.textContent = '网络错误,请重试';
|
||||
|
||||
@@ -1854,9 +1854,13 @@ class ConfigHandler:
|
||||
return json.dumps({"status": "error", "message": "no valid keys to update"})
|
||||
|
||||
config_path = os.path.join(get_data_root(), "config.json")
|
||||
old_password = "" # Store old password before update
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
file_cfg = json.load(f)
|
||||
# Capture old password before updating
|
||||
if "web_password" in applied:
|
||||
old_password = file_cfg.get("web_password", "")
|
||||
else:
|
||||
file_cfg = {}
|
||||
file_cfg.update(applied)
|
||||
@@ -1864,7 +1868,6 @@ 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.
|
||||
@@ -1875,25 +1878,25 @@ 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.
|
||||
# Check if password was cleared: if there was a password before clearing,
|
||||
# the service is likely bound to 0.0.0.0 (public), so warn the user.
|
||||
password_warning = None
|
||||
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}")
|
||||
new_password = applied["web_password"]
|
||||
configured_host = file_cfg.get("web_host", "")
|
||||
|
||||
# If password was cleared and there was a password before
|
||||
if not new_password and old_password:
|
||||
# If web_host is not explicitly set, the service auto-binds based on password
|
||||
# With password → 0.0.0.0 (public), without password → 127.0.0.1 (local)
|
||||
# So clearing password when it was previously set means going from public to local
|
||||
if not configured_host or configured_host == "0.0.0.0":
|
||||
password_warning = "password_cleared_with_public_host"
|
||||
logger.warning(
|
||||
"[WebChannel] Password cleared while service is likely bound to 0.0.0.0. "
|
||||
"Consider restarting the service to rebind to 127.0.0.1 "
|
||||
"or explicitly set web_host in config to prevent unauthorized access."
|
||||
)
|
||||
|
||||
# Reset Bridge so that bot routing reflects the new config.
|
||||
# Without this, Bridge keeps its cached bot instance (e.g. LinkAIBot)
|
||||
@@ -1907,7 +1910,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, "restarted": restarted}, ensure_ascii=False)
|
||||
return json.dumps({"status": "success", "applied": applied, "warning": password_warning}, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating config: {e}")
|
||||
return json.dumps({"status": "error", "message": str(e)})
|
||||
@@ -3694,7 +3697,7 @@ class ChannelsHandler:
|
||||
# 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
|
||||
is_hant = i18n.get_language() == i18n.ZH_HANT
|
||||
for ch_name, ch_def in self.CHANNEL_DEFS.items():
|
||||
if desktop_mode and ch_name == "feishu":
|
||||
continue
|
||||
@@ -3707,11 +3710,11 @@ class ChannelsHandler:
|
||||
display_val = raw_val
|
||||
|
||||
label_val = f["label"]
|
||||
if is_tw and isinstance(label_val, str):
|
||||
if is_hant and isinstance(label_val, str):
|
||||
label_val = i18n.to_traditional(label_val)
|
||||
elif is_tw and isinstance(label_val, dict):
|
||||
elif is_hant and isinstance(label_val, dict):
|
||||
label_val = label_val.copy()
|
||||
label_val["zh-tw"] = i18n.to_traditional(label_val.get("zh", ""))
|
||||
label_val["zh-Hant"] = i18n.to_traditional(label_val.get("zh", ""))
|
||||
|
||||
fields_out.append({
|
||||
"key": f["key"],
|
||||
@@ -3722,11 +3725,11 @@ class ChannelsHandler:
|
||||
})
|
||||
|
||||
label_val = ch_def["label"]
|
||||
if is_tw and isinstance(label_val, str):
|
||||
if is_hant and isinstance(label_val, str):
|
||||
label_val = i18n.to_traditional(label_val)
|
||||
elif is_tw and isinstance(label_val, dict):
|
||||
elif is_hant and isinstance(label_val, dict):
|
||||
label_val = label_val.copy()
|
||||
label_val["zh-tw"] = i18n.to_traditional(label_val.get("zh", ""))
|
||||
label_val["zh-Hant"] = i18n.to_traditional(label_val.get("zh", ""))
|
||||
|
||||
ch_info = {
|
||||
"name": ch_name,
|
||||
@@ -4297,7 +4300,7 @@ class ToolsHandler:
|
||||
try:
|
||||
instance = cls()
|
||||
desc = instance.description
|
||||
if lang == i18n.ZH_TW and desc:
|
||||
if lang == i18n.ZH_HANT and desc:
|
||||
desc = i18n.to_traditional(desc)
|
||||
elif lang == "en" and name == "scheduler":
|
||||
desc = (
|
||||
@@ -4328,7 +4331,7 @@ class SkillsHandler:
|
||||
manager = SkillManager(custom_dir=os.path.join(workspace_root, "skills"))
|
||||
service = SkillService(manager)
|
||||
skills = service.query()
|
||||
if i18n.get_language() == i18n.ZH_TW:
|
||||
if i18n.get_language() == i18n.ZH_HANT:
|
||||
for skill in skills:
|
||||
if isinstance(skill, dict):
|
||||
for k, v in list(skill.items()):
|
||||
|
||||
@@ -7,6 +7,11 @@ across the CLI, startup logs, error messages, agent prompts and channel
|
||||
replies. It must NOT import project config (to avoid circular imports) and
|
||||
must stay dependency-free so it can run at the earliest startup phase.
|
||||
|
||||
Supported language codes (BCP 47 compliant):
|
||||
- "zh" (Simplified Chinese)
|
||||
- "zh-Hant" (Traditional Chinese, script-based tag per Unicode CLDR)
|
||||
- "en" (English)
|
||||
|
||||
Resolution priority (highest first):
|
||||
1. Explicit `cow_lang` from config.json — also covers Docker/CI, since any
|
||||
config key is overridable via its uppercase env var (e.g. COW_LANG=zh),
|
||||
@@ -19,7 +24,10 @@ Resolution priority (highest first):
|
||||
5. Default -> English
|
||||
|
||||
A value of "auto" (the default) triggers detection (steps 2-5). Explicitly
|
||||
setting "zh" or "en" locks the language and skips detection.
|
||||
setting "zh", "zh-Hant", or "en" locks the language and skips detection.
|
||||
|
||||
Note: For backwards compatibility, zh-tw, zh-hk, and other regional variants
|
||||
are automatically normalized to zh-Hant during detection.
|
||||
"""
|
||||
|
||||
import os
|
||||
@@ -28,9 +36,9 @@ import sys
|
||||
|
||||
# Supported language codes
|
||||
ZH = "zh"
|
||||
ZH_TW = "zh-tw"
|
||||
ZH_HANT = "zh-Hant"
|
||||
EN = "en"
|
||||
SUPPORTED = (ZH, ZH_TW, EN)
|
||||
SUPPORTED = (ZH, ZH_HANT, EN)
|
||||
DEFAULT_LANG = EN
|
||||
|
||||
# Mapped Simplified to Traditional characters in this codebase
|
||||
@@ -71,6 +79,20 @@ _PHRASE_MAP = {
|
||||
|
||||
|
||||
def to_traditional(text):
|
||||
"""Convert Simplified Chinese text to Traditional Chinese.
|
||||
|
||||
Uses a two-tier approach:
|
||||
1. Phrase-level mapping for project-specific terms (e.g., "配置" → "設定")
|
||||
2. OpenCC library (opencc-python-reimplemented) if available for high-quality
|
||||
context-aware conversion, with fallback to built-in character mapping
|
||||
|
||||
This function is designed to work without external dependencies. If OpenCC
|
||||
is not installed, it falls back to a curated 450-character mapping table
|
||||
plus 30+ technical term mappings that cover common project vocabulary.
|
||||
|
||||
For production use with zh-Hant language, installing OpenCC is recommended:
|
||||
pip install opencc-python-reimplemented
|
||||
"""
|
||||
if not text:
|
||||
return text
|
||||
|
||||
@@ -111,7 +133,7 @@ def _normalize(raw):
|
||||
return None
|
||||
# Traditional Chinese variants: zh-tw, zh-hk, zh-hant, zh-hant-tw, zh-hant-hk...
|
||||
if value.startswith("zh-tw") or value.startswith("zh-hk") or "hant" in value:
|
||||
return ZH_TW
|
||||
return ZH_HANT
|
||||
# General or Simplified Chinese variants: zh, zh-cn, zh-hans...
|
||||
if value.startswith("zh") or value.startswith("chinese"):
|
||||
return ZH
|
||||
@@ -231,7 +253,7 @@ def get_language():
|
||||
|
||||
|
||||
def is_zh():
|
||||
return get_language() in (ZH, ZH_TW)
|
||||
return get_language() in (ZH, ZH_HANT)
|
||||
|
||||
|
||||
def t(zh_text, en_text):
|
||||
@@ -241,6 +263,6 @@ def t(zh_text, en_text):
|
||||
t("已中止", "Cancelled")
|
||||
"""
|
||||
lang = get_language()
|
||||
if lang == ZH_TW:
|
||||
if lang == ZH_HANT:
|
||||
return to_traditional(zh_text)
|
||||
return zh_text if lang == ZH else en_text
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
[<a href="../../README.md">English</a>] | [中文] | [<a href="README-tw.md">繁體中文</a>] | [<a href="../ja/README.md">日本語</a>]
|
||||
[<a href="../../README.md">English</a>] | [中文] | [<a href="README-Hant.md">繁體中文</a>] | [<a href="../ja/README.md">日本語</a>]
|
||||
</p>
|
||||
|
||||
**CowAgent** 是一个开源的超级 AI 助理,能够主动思考和规划任务、操作计算机和外部资源、创造和执行 Skills、构建知识库与长期记忆、通过自主进化与你一同成长,是 Agent Harness 工程的最佳实践之一。
|
||||
|
||||
@@ -20,3 +20,7 @@ pypdf
|
||||
python-docx
|
||||
openpyxl
|
||||
python-pptx
|
||||
|
||||
# i18n: Traditional Chinese (zh-Hant) high-quality conversion
|
||||
# Built-in fallback provides basic conversion without this dependency
|
||||
opencc-python-reimplemented
|
||||
|
||||
@@ -36,7 +36,3 @@ discord.py
|
||||
web.py; python_version < "3.13"
|
||||
web.py @ git+https://github.com/webpy/webpy.git ; python_version >= "3.13"
|
||||
legacy-cgi; python_version >= "3.13"
|
||||
|
||||
# i18n translation mapping
|
||||
opencc-python-reimplemented
|
||||
|
||||
|
||||
Reference in New Issue
Block a user