diff --git a/packages/client/src/components/layout/LanguageSwitch.vue b/packages/client/src/components/layout/LanguageSwitch.vue index 94f10bc8..06cf5493 100644 --- a/packages/client/src/components/layout/LanguageSwitch.vue +++ b/packages/client/src/components/layout/LanguageSwitch.vue @@ -5,7 +5,8 @@ import { NSelect } from 'naive-ui' const { locale } = useI18n() const options = [ - { label: '中文', value: 'zh' }, + { label: '简体中文', value: 'zh' }, + { label: '繁體中文', value: 'zh-TW' }, { label: 'English', value: 'en' }, { label: '日本語', value: 'ja' }, { label: '한국어', value: 'ko' }, diff --git a/packages/client/src/i18n/index.ts b/packages/client/src/i18n/index.ts index bb359eb2..ab9c1643 100644 --- a/packages/client/src/i18n/index.ts +++ b/packages/client/src/i18n/index.ts @@ -2,24 +2,45 @@ import { createI18n } from 'vue-i18n' import { messages } from './messages' const saved = localStorage.getItem('hermes_locale') -const detected = navigator.language.slice(0, 2) -const supportedLocales = ['en', 'zh', 'ja', 'ko', 'fr', 'es', 'de', 'pt'] as const +const supportedLocales = ['en', 'zh', 'zh-TW', 'ja', 'ko', 'fr', 'es', 'de', 'pt'] as const type SupportedLocale = (typeof supportedLocales)[number] -function resolveLocale(saved: string | null, detected: string): SupportedLocale { +function resolveLocale(saved: string | null): SupportedLocale { if (saved && (supportedLocales as readonly string[]).includes(saved)) { return saved as SupportedLocale } - if ((supportedLocales as readonly string[]).includes(detected)) { - return detected as SupportedLocale + + // Normalize a single BCP-47 tag to a supported locale key. + // Covers zh-Hant-TW, zh-TW, zh-HK, zh-MO, zh-Hant → zh-TW + // zh-Hans-*, zh-CN, zh-SG, zh → zh + function normalize(tag: string): SupportedLocale | null { + const lower = tag.toLowerCase() + if (lower.startsWith('zh')) { + const isTraditional = + lower.includes('hant') || + lower.includes('-tw') || + lower.includes('-hk') || + lower.includes('-mo') + return isTraditional ? 'zh-TW' : 'zh' + } + const short = tag.slice(0, 2) + if ((supportedLocales as readonly string[]).includes(tag)) return tag as SupportedLocale + if ((supportedLocales as readonly string[]).includes(short)) return short as SupportedLocale + return null + } + + for (const lang of navigator.languages) { + const resolved = normalize(lang) + if (resolved) return resolved } + return 'en' } export const i18n = createI18n({ legacy: false, - locale: resolveLocale(saved, detected), + locale: resolveLocale(saved), fallbackLocale: 'en', messages, }) diff --git a/packages/client/src/i18n/locales/zh-TW.ts b/packages/client/src/i18n/locales/zh-TW.ts new file mode 100644 index 00000000..6375ca5a --- /dev/null +++ b/packages/client/src/i18n/locales/zh-TW.ts @@ -0,0 +1,1145 @@ +export default { + // 登入 + login: { + title: 'Hermes Web UI', + description: '輸入存取權杖以繼續。權杖可在伺服器啟動日誌中查看。', + placeholder: '存取權杖', + submit: '登入', + tokenRequired: '請輸入存取權杖', + invalidToken: '權杖無效', + connectionFailed: '無法連線至伺服器', + passwordLogin: '密碼登入', + tokenLogin: '權杖登入', + usernamePlaceholder: '使用者名稱', + passwordPlaceholder: '密碼', + credentialsRequired: '請輸入使用者名稱和密碼', + invalidCredentials: '使用者名稱或密碼錯誤', + tooManyAttempts: '登入失敗次數過多,請稍後再試', + passwordMismatch: '兩次密碼不一致', + passwordTooShort: '密碼長度至少 6 個字元', + setupSuccess: '密碼登入設定成功', + passwordChanged: '密碼修改成功', + passwordRemoved: '密碼登入已移除', + setupPassword: '設定密碼登入', + changePassword: '修改密碼', + changeUsername: '修改使用者名稱', + removePasswordLogin: '移除', + username: '使用者名稱', + currentPassword: '目前密碼', + newPassword: '新密碼', + confirmPassword: '確認密碼', + newUsername: '新使用者名稱', + usernameChanged: '使用者名稱修改成功', + usernameTooShort: '使用者名稱至少 2 個字元', + setupDescription: '設定使用者名稱和密碼以便快速登入。存取權杖仍可繼續使用。', + removeConfirm: '確定要移除密碼登入嗎?移除後需使用存取權杖登入。', + passwordLoginNotConfigured: '密碼登入未設定', + passwordLoginConfigured: '密碼登入已啟用({username})', + }, + + // 通用 + common: { + loading: '載入中...', + cancel: '取消', + delete: '刪除', + retry: '重試', + edit: '編輯', + save: '儲存', + saved: '已儲存', + saveFailed: '儲存失敗', + deleteFailed: '刪除失敗', + ok: '確定', + copied: '已複製', + copy: '複製', + update: '更新', + create: '建立', + noData: '目前無資料', + expired: '已過期', + fetch: '取得', + add: '新增', + enable: '啟用', + disable: '停用', + configured: '已設定', + notConfigured: '未設定', + confirm: '確定', + expand: '展開', + collapse: '收起', + start: '啟動', + stop: '停止', + }, + + // 側邊欄 + sidebar: { + chat: '對話', + search: '搜尋', + apiRelay: '中轉站', + history: '歷史', + jobs: '任務', + kanban: '看板', + models: '模型', + profiles: '使用者', + plugins: '插件', + skills: '技能', + memory: '記憶', + logs: '日誌', + usage: '用量', + channels: '頻道', + gateways: '閘道', + terminal: '終端機', + groupChat: '群聊', + files: '檔案', + groupConversation: '對話', + groupPlatform: '平台', + groupAgent: '代理', + groupSystem: '系統', + groupMonitoring: '監控', + groupTools: '工具', + settings: '設定', + connected: '已連線', + disconnected: '未連線', + collapse: '收起選單', + expand: '展開選單', + updateTip: '在終端機執行 "hermes-web-ui update" 即可更新', + updateVersion: '升級版本 v{version}', + updating: '正在更新...', + updateSuccess: '更新完成,請重新啟動服務', + updateFailed: '更新失敗', + logout: '登出', + nodeVersionWarning: '偵測到 Node.js v{version},請升級至 23 以上版本。', + changelog: '更新日誌', + noChangelog: '目前無更新日誌', + }, + + // 抽屜 + drawer: { + terminal: '終端機', + files: '工作區', + }, + + // 對話 + chat: { + contextRemaining: '剩餘', + contextClickToEdit: '點擊編輯上下文長度', + contextEditTitle: '編輯上下文長度', + contextEditDesc: '設定目前模型的上下文長度限制(token 數量)', + contextEditPlaceholder: '請輸入上下文長度', + contextEditHint: '常見值:200k (Claude), 128k (GPT-4), 32k (GPT-3.5)', + contextEditSave: '儲存', + contextEditCancel: '取消', + contextEditInvalid: '請輸入有效的上下文長度', + contextEditSuccess: '上下文長度已更新', + contextEditFailed: '更新失敗', + emptyState: '開始與 Hermes Agent 對話', + inputPlaceholder: '輸入訊息... (Enter 發送,Shift+Enter 換行)', + attachFiles: '新增附件', + autoPlaySpeech: '自動播放語音', + messageQueue: '訊息佇列', + removeQueuedMessage: '移除佇列訊息', + stop: '停止', + start: '啟動', + stopGateway: '停止閘道', + send: '發送', + contextUsed: '上下文已用:', + sessions: '工作階段', + webUiSessions: '工作階段', + sessionScopeHint: '這裡只顯示目前工作階段;CLI、Telegram、Discord、Cron 等頻道工作階段在歷史中以唯讀方式查看。', + openHistory: '開啟歷史', + hermesHistory: 'Hermes 歷史', + historyScopeHint: '這裡按來源以唯讀方式查看 Hermes 歷史工作階段。', + noSessions: '目前無工作階段', + searchTitle: '搜尋工作階段', + searchSubtitle: '依標題或訊息內容搜尋', + searchHint: 'Cmd/Ctrl+K', + searchPlaceholder: '搜尋工作階段...', + searchEmpty: '最近工作階段', + searchRecent: '最近工作階段', + searchNoResults: '沒有符合的工作階段', + searchNoSnippet: '沒有可顯示的摘要', + searchEnterHint: 'Enter 開啟 · Esc 關閉', + searchFailed: '搜尋工作階段失敗', + newChat: '新增對話', + deleteSession: '確定刪除此工作階段?', + sessionDeleted: '工作階段已刪除', + toggleBatchMode: '批次選取', + selectAll: '全選', + confirmBatchDelete: '確定刪除選取的 {count} 個工作階段?', + batchDeleteSuccess: '已刪除 {count} 個工作階段', + batchDeletePartial: '{failed} 個工作階段刪除失敗', + batchDeleteFailed: '批次刪除失敗', + rename: '重新命名', + pin: '釘選', + unpin: '取消釘選', + pinned: '已釘選', + chatMode: '聊天', + liveMode: '即時', + liveSessions: '即時工作階段', + recentBadge: '最近', + linkedSessions: '關聯 {count} 個工作階段', + noVisibleMessages: '沒有人類可見訊息。', + monitorRoleUser: '使用者', + monitorRoleAssistant: '助手', + copySessionId: '複製工作階段 ID', + export: '匯出', + exportFull: '完整匯出 (JSON)', + exportCompressed: '壓縮匯出 (TXT)', + exportCompressing: '正在壓縮上下文,請稍候...', + exportSuccess: '工作階段已匯出', + exportFailed: '匯出失敗', + renamed: '已重新命名', + renameFailed: '重新命名失敗', + renameSession: '重新命名工作階段', + sessionNotFound: '找不到工作階段', + enterNewTitle: '輸入新標題', + workspace: '工作區', + setWorkspace: '設定工作區', + setWorkspaceTitle: '設定工作階段工作區', + workspacePlaceholder: '輸入專案路徑,例如 /home/user/project', + workspaceSet: '工作區已設定', + workspaceSetFailed: '設定工作區失敗', + other: '其他', + runFailed: '執行失敗', + error: '錯誤', + tool: '工具', + arguments: '參數', + result: '結果', + truncated: '... (已截斷)', + executionDuration: '執行時長', + thinkingLabel: '思考過程', + thinkingInProgress: '思考中…', + thinkingShow: '展開思考過程', + thinkingHide: '收起思考過程', + thinkingDuration: '已觀察 {duration}', + thinkingChars: '{count} 字', + copyBubble: '複製訊息', + copiedBubble: '已複製', + copyFailed: '複製失敗', + playSpeech: '播放語音', + pauseSpeech: '暫停', + resumeSpeech: '繼續', + stopSpeech: '停止', + speechNotSupported: '此瀏覽器不支援語音播放', + }, + + // 看板 + kanban: { + title: '看板', + createTask: '新增任務', + noTasks: '目前無任務', + allStatuses: '所有狀態', + allAssignees: '所有負責人', + board: { + create: '新增看板', + archive: '封存看板', + slugPlaceholder: '看板識別碼,例如 project-a', + namePlaceholder: '顯示名稱(選填)', + slugRequired: '看板識別碼不能為空', + created: '看板已建立', + archived: '看板已封存', + archiveConfirm: '確定封存目前看板?', + }, + columns: { + triage: '待分類', + todo: '待辦', + ready: '就緒', + running: '進行中', + blocked: '阻塞', + done: '已完成', + archived: '已封存', + }, + form: { + title: '標題', + titlePlaceholder: '任務標題', + titleRequired: '標題不能為空', + body: '描述', + bodyPlaceholder: '任務描述(選填)', + assignee: '負責人', + selectAssignee: '選擇負責人...', + priority: '優先級', + selectPriority: '選擇優先級...', + }, + card: { + assigneeTooltip: '負責人', + priority: { + low: '低', + medium: '中', + high: '高', + }, + timeAgo: { + justNow: '剛剛', + minutes: '{count} 分鐘前', + hours: '{count} 小時前', + days: '{count} 天前', + }, + }, + detail: { + status: '狀態', + assignee: '負責人', + priority: '優先級', + tenant: '租戶', + createdAt: '建立時間', + startedAt: '開始時間', + completedAt: '完成時間', + comments: '評論', + events: '事件', + runs: '執行記錄', + result: '完成結果', + sessions: '關聯工作階段', + sessionMessages: '工作階段記錄', + noSessions: '找不到關聯工作階段。', + artifacts: '產出檔案', + sources: '資料來源', + highlights: '關鍵資訊', + }, + action: { + title: '操作', + complete: '完成', + completeSummary: '完成摘要(選填)', + block: '阻塞', + blockReason: '阻塞原因', + unblock: '解除阻塞', + assign: '指派', + assignTo: '指派給...', + }, + message: { + taskCreated: '任務已建立', + taskCompleted: '任務已完成', + taskBlocked: '任務已阻塞', + taskUnblocked: '任務已解除阻塞', + taskAssigned: '任務已指派', + loadFailed: '載入任務失敗', + }, + stats: { + total: '總計', + tasks: '任務數', + }, + }, + + // 排程任務 + jobs: { + title: '排程任務', + createJob: '建立任務', + editJob: '編輯任務', + noJobs: '目前無排程任務,建立一個開始吧。', + name: '名稱', + namePlaceholder: '任務名稱', + schedule: '排程運算式 (Cron)', + schedulePlaceholder: '例如 0 9 * * *', + quickPresets: '快速預設', + selectPreset: '選擇預設...', + presetEveryMinute: '每分鐘', + presetEvery5Min: '每 5 分鐘', + presetEveryHour: '每小時', + presetEveryDay: '每天 00:00', + presetEveryDay9: '每天 09:00', + presetEveryMonday: '每週一 09:00', + presetEveryMonth: '每月 1 日 09:00', + prompt: '提示詞', + promptPlaceholder: '要執行的內容', + deliverTarget: '投遞目標', + origin: '來源', + local: '本地', + repeatCount: '重複次數(選填)', + modelPlaceholder: '預設模型', + repeatPlaceholder: '留空表示無限重複', + jobCreated: '任務已建立', + jobUpdated: '任務已更新', + nameRequired: '名稱為必填項', + scheduleRequired: '排程運算式為必填項', + loadFailed: '載入任務失敗', + jobPaused: '任務已暫停', + jobResumed: '任務已恢復', + jobTriggered: '任務已觸發', + modelUpdated: '模型已更新', + jobDeleted: '任務已刪除', + status: { + running: '執行中', + paused: '已暫停', + disabled: '已停用', + scheduled: '已排程', + }, + info: { + model: '模型', + schedule: '排程', + lastRun: '上次執行', + nextRun: '下次執行', + deliver: '投遞', + repeat: '重複', + }, + action: { + pause: '暫停', + pauseJob: '暫停任務', + resume: '恢復', + resumeJob: '恢復任務', + runNow: '立即執行', + triggerImmediately: '立即觸發', + }, + runHistory: { + title: '執行歷史', + runs: '次執行', + noRuns: '目前無執行歷史。', + }, + }, + + // 技能 + skills: { + title: '技能', + searchPlaceholder: '搜尋技能...', + noMatch: '沒有符合的技能', + noSkills: '目前無技能', + backTo: '返回', + attachedFiles: '附件檔案', + loadFailed: '載入技能失敗', + fileLoadFailed: '載入檔案失敗', + modified: '使用者已修改', + archived: '已封存', + pinned: '已釘選', + pin: '釘選技能', + unpin: '取消釘選', + pinFailed: '變更釘選狀態失敗', + toggleFailed: '切換技能狀態失敗', + source: { + builtin: '內建', + hub: 'Hub 安裝', + local: '本地安裝', + }, + }, + + // 插件 + plugins: { + title: '插件', + refresh: '重新整理', + notice: '唯讀顯示可發現的 Hermes 插件 manifest。發現元資料讀取不會載入插件程式碼。v1 管理動作仍保留在 CLI,新 Hermes 工作階段生效。', + loadFailed: '載入插件失敗', + commandCopied: '指令已複製', + searchPlaceholder: '搜尋 key、名稱、描述、路徑...', + source: '來源', + kind: '類型', + statusTitle: '狀態', + configStatus: '設定:{status}', + notAvailable: '無', + copyCommand: '複製指令', + managedElsewhere: '由其他位置管理', + noMatch: '沒有符合目前篩選條件的插件', + enabled: '已啟用', + disabled: '已停用', + summary: { + total: '總數', + active: '已啟用 / 自動', + inactive: '未啟用', + disabled: '已停用', + providerManaged: 'Provider 管理', + }, + status: { + enabled: '已啟用', + 'auto-active': '自動啟用', + inactive: '未啟用', + disabled: '已停用', + 'provider-managed': 'Provider 管理', + }, + statusLabel: { + enabled: '設定啟用', + 'auto-active': '自動啟用', + inactive: '未啟用', + disabled: '已停用', + 'provider-managed': 'Provider 管理', + }, + configStatuses: { + enabled: '已啟用', + disabled: '已停用', + 'not-enabled': '未啟用', + auto: '自動', + 'provider-managed': 'Provider 管理', + }, + table: { + plugin: '插件', + status: '狀態', + source: '來源', + kind: '類型', + capabilities: '能力', + path: '路徑 / 入口', + cli: 'CLI', + }, + capabilities: { + tools: '{count} 個工具', + hooks: '{count} 個 hook', + env: '{count} 個環境變數', + }, + metadata: { + agentRoot: 'Agent 根目錄', + python: 'Python', + scanCwd: '掃描 cwd', + projectPlugins: '專案插件', + }, + }, + + // 記憶 + memory: { + title: '記憶', + refresh: '重新整理', + loadFailed: '載入記憶失敗', + myNotes: '我的筆記', + noNotes: '目前無筆記。', + notesPlaceholder: '輸入筆記內容...', + userProfile: '使用者畫像', + noProfile: '目前無畫像。', + profilePlaceholder: '輸入使用者畫像...', + soul: '靈魂', + noSoul: '目前無靈魂設定。', + soulPlaceholder: '輸入靈魂設定...', + }, + + // 模型 + models: { + title: '模型', + searchPlaceholder: '搜尋模型...', + addProvider: '新增 Provider', + providerType: 'Provider 類型', + preset: '預設', + custom: '自訂', + selectProvider: '選擇 Provider', + chooseProvider: '選擇一個 provider...', + getApiKey: '取得 API Key', + name: '名稱', + autoGeneratedName: '依 Base URL 自動產生', + baseUrl: 'Base URL', + region: '區域', + regionIntl: '國際版', + regionCn: '中國大陸', + baseUrlPlaceholder: '例如 https://api.example.com/v1', + apiKey: 'API Key', + apiKeyPlaceholder: 'sk-...', + defaultModel: '預設模型', + selectOrInput: '選擇或輸入模型名稱...', + selectModel: '選擇模型...', + providerAdded: 'Provider 已新增', + providerDeleted: 'Provider 已刪除', + deleteProvider: '刪除 Provider', + deleteConfirm: '確定刪除「{name}」嗎?', + codexLoginTitle: 'OpenAI Codex 登入', + codexWaiting: '在授權頁面輸入以下代碼完成登入:', + codexCopyCode: '代碼已複製', + codexOpenLink: '開啟授權頁面', + codexApproved: '登入成功', + codexExpired: '授權已過期,請重試。', + nousLoginTitle: 'Nous Portal 登入', + nousWaiting: '在授權頁面輸入此代碼完成登入:', + nousCopyCode: '代碼已複製', + nousOpenLink: '開啟授權頁面', + nousApproved: '登入成功', + nousDenied: '授權被拒絕,請重試。', + nousExpired: '授權已過期,請重試。', + copilotLoginTitle: 'GitHub Copilot 登入', + copilotWaiting: '請前往 GitHub 輸入下方裝置代碼完成授權。授權完成後視窗會自動關閉。', + copilotCopyCode: '代碼已複製', + copilotOpenLink: '開啟 GitHub 授權頁', + copilotApproved: '登入成功!', + copilotDenied: '授權被拒絕。', + copilotExpired: '授權連結已過期,請重試。', + copilotAddDetectedTitle: '偵測到 GitHub Copilot', + copilotAddDetected: '已在本機偵測到 GitHub Copilot OAuth 憑證,點擊「新增」即可在 Hermes 中啟用 Copilot。', + copilotAddSourceEnv: '來源:~/.hermes/.env(COPILOT_GITHUB_TOKEN)', + copilotAddSourceGhCli: '來源:gh CLI(gh auth token)', + copilotAddSourceAppsJson: '來源:VS Code Copilot 插件(apps.json)', + copilotDeleteHintEnv: '此操作會清除 ~/.hermes/.env 中的 COPILOT_GITHUB_TOKEN,不影響其他工具。', + copilotDeleteHintGhCli: 'Copilot 將從 Hermes 清單移除。不會影響 gh CLI —— `gh auth status` 仍顯示已登入。', + copilotDeleteHintAppsJson: 'Copilot 將從 Hermes 清單移除。不會影響 VS Code Copilot 插件的登入。', + customBadge: '自訂', + previewBadge: '預覽', + disabledBadge: '不可用', + disabledTooltip: '此模型目前帳號不可用', + customModelPlaceholder: '自訂模型名稱', + customModelHint: '按 Enter 載入', + noProviders: '目前無 Provider,新增一個開始吧。', + models: '模型清單', + count: '個模型', + more: '個更多', + builtIn: '內建', + customType: '自訂', + provider: 'Provider', + contextLength: '上下文長度', + contextLengthPlaceholder: '例如 200000(選填)', + local: '本地 ({host})', + selectProviderRequired: '請選擇 Provider', + baseUrlRequired: 'Base URL 為必填項', + apiKeyRequired: 'API Key 為必填項', + modelRequired: '預設模型為必填項', + enterBaseUrl: '請先輸入 Base URL', + unexpectedFormat: '回應格式異常', + foundModels: '找到 {count} 個模型', + fetchFailed: '取得模型失敗', + }, + + // 設定檔 + profiles: { + title: '設定檔', + create: '建立設定檔', + import: '匯入', + export: '匯出', + rename: '重新命名', + delete: '刪除', + switchTo: '切換至', + switchConfirm: '切換至設定檔「{name}」將重新啟動閘道,是否繼續?', + switchSuccess: '已切換至設定檔「{name}」', + switchFailed: '切換設定檔失敗,閘道可能需要手動重新啟動', + createSuccess: '設定檔「{name}」已建立', + createFailed: '建立設定檔失敗', + renameSuccess: '設定檔已重新命名', + renameFailed: '重新命名設定檔失敗', + deleteConfirm: '確定刪除設定檔「{name}」嗎?', + deleteSuccess: '設定檔已刪除', + deleteFailed: '刪除設定檔失敗', + exportSuccess: '設定檔已匯出', + exportFailed: '匯出設定檔失敗', + importSuccess: '設定檔已匯入', + importFailed: '匯入設定檔失敗', + importSelectFile: '選擇封存檔案', + importInvalidFile: '請選擇有效的封存檔案 (.tar.gz, .tgz, .gz, .zip)', + name: '設定檔名稱', + namePlaceholder: '僅限小寫字母、數字、連字號', + nameValidation: '設定檔名稱只能包含小寫字母、數字、底線和連字號', + newName: '新名稱', + newNamePlaceholder: '小寫字母、數字、連字號', + cloneFromCurrent: '從目前設定檔複製', + cloneCleanupNotice: '複製時會自動略過獨占型平台憑證(Weixin / Telegram / Slack 等),避免與來源設定檔衝突', + cloneStrippedCredentials: '已清理 {count} 項獨占憑證:{list}', + cloneDisabledPlatforms: '已停用 {count} 個平台:{list}', + cloneStrippedConfigCredentials: '已清理 config.yaml 中 {count} 項嵌入憑證:{list}', + archivePath: '封存路徑', + archivePathPlaceholder: '封存檔案的伺服器路徑', + importName: '設定檔名稱(選填)', + importNamePlaceholder: '留空則使用封存名稱', + active: '使用中', + model: '模型', + gateway: '閘道', + alias: '別名', + provider: 'Provider', + path: '路徑', + skills: '技能', + hasEnv: '有 .env', + hasSoulMd: '有 soul.md', + noProfiles: '目前無設定檔,建立一個開始吧。', + }, + + // 日誌 + logs: { + title: '日誌', + all: '全部', + searchPlaceholder: '搜尋...', + refresh: '重新整理', + noEntries: '目前無日誌', + }, + + // 設定 + settings: { + title: '設定', + saved: '已儲存', + saveFailed: '儲存失敗', + tabs: { + display: '顯示', + account: '帳號', + agent: '代理', + memory: '記憶', + session: '工作階段', + privacy: '隱私', + apiServer: 'API 伺服器', + models: '模型', + voice: '語音', + }, + models: { + apiKey: 'API Key', + apiKeyPlaceholder: '輸入 API Key', + save: '儲存', + saved: '已儲存', + saveFailed: '儲存失敗', + noProviders: '目前無已設定的模型', + }, + display: { + streaming: '串流回應', + streamingHint: '即時顯示 AI 回覆', + compact: '緊湊模式', + compactHint: '減少訊息間距', + showReasoning: '顯示推理過程', + showReasoningHint: '展示模型思考過程', + showCost: '顯示費用', + showCostHint: '在回覆中顯示 token 使用量', + inlineDiffs: '內嵌差異', + inlineDiffsHint: '程式碼變更以內嵌方式顯示', + bellOnComplete: '完成提示音', + bellOnCompleteHint: 'AI 回覆完成時播放提示音', + busyInputMode: '忙碌輸入模式', + busyInputModeHint: 'AI 處理中仍可輸入', + theme: '主題', + themeHint: '選擇淺色、暗色或跟隨系統', + themeLight: '淺色', + themeDark: '暗色', + themeSystem: '跟隨系統', + }, + agent: { + maxTurns: '最大輪次', + maxTurnsHint: '單次對話最大互動輪數', + gatewayTimeout: '閘道逾時', + gatewayTimeoutHint: '單次請求逾時時間(秒)', + restartDrainTimeout: '重啟排空逾時', + restartDrainTimeoutHint: '重啟前排空請求的逾時時間(秒)', + toolEnforcement: '工具執行策略', + toolEnforcementHint: '控制工具呼叫的執行模式', + auto: '自動', + always: '始終', + never: '從不', + }, + memory: { + enabled: '啟用記憶', + enabledHint: '允許 AI 記住對話上下文', + userProfile: '使用者畫像', + userProfileHint: '允許 AI 記住使用者偏好資訊', + charLimit: '記憶字元上限', + charLimitHint: 'MEMORY.md 最大字元數', + userCharLimit: '使用者畫像字元上限', + userCharLimitHint: 'USER.md 最大字元數', + }, + session: { + mode: '重設模式', + modeHint: '工作階段重設的觸發條件', + modeBoth: '閒置 + 定時', + modeIdle: '僅閒置', + modeHourly: '僅定時', + idleMinutes: '閒置逾時', + idleMinutesHint: '無操作後自動重設的等待時間(分鐘)', + atHour: '定時重設時間', + humanOnly: '僅顯示人類工作階段', + humanOnlyHint: '預設隱藏子代理和工作階段監看雜訊', + liveMonitorHumanOnly: '即時監看:僅顯示人類工作階段', + liveMonitorHumanOnlyHint: '在即時監看中預設隱藏子代理和工作階段監看雜訊', + atHourHint: '每天在指定小時重設工作階段', + requireAuth: '工作階段授權', + requireAuthHint: '修改工作階段操作是否授權', + }, + privacy: { + redactPii: '遮蔽 PII', + redactPiiHint: '自動偵測並隱藏敏感資訊(密碼、金鑰等)', + }, + apiServer: { + enable: '啟用', + enableHint: '啟用 API 伺服器', + host: '主機', + hostHint: '監聽位址', + port: '連接埠', + portHint: '監聽連接埠', + key: '金鑰', + keyHint: 'API 存取金鑰', + cors: 'CORS 來源', + corsHint: '允許的跨域來源', + }, + lockedIps: { + title: '鎖定 IP 管理', + count: '{count} 個 IP 被鎖定', + empty: '目前無鎖定 IP', + unlock: '解鎖', + unlockAll: '全部解鎖', + unlockAllConfirm: '確認解鎖所有鎖定的 IP?', + unlocked: 'IP 已解鎖', + allUnlocked: '已解鎖 {count} 個 IP', + }, + voice: { + ttsProvider: 'TTS 提供者', + ttsProviderHint: '選擇訊息朗讀使用的語音引擎', + providerWebSpeech: 'WebSpeech API(瀏覽器內建)', + providerOpenai: 'OpenAI TTS', + providerCustom: '自訂端點(相容 OpenAI)', + providerEdge: 'Edge TTS(免費,無需 API Key)', + + // WebSpeech + webspeechVoice: '音色', + webspeechVoiceHint: '從瀏覽器或系統提供的語音中選擇', + webspeechVoicePlaceholder: '自動(預設語音)', + + // OpenAI + openaiKey: 'API 金鑰', + openaiKeyHint: '具有 TTS 權限的 OpenAI API Key', + openaiUrl: 'API 基礎位址', + openaiUrlHint: '例如 https://api.openai.com/v1/audio/speech', + openaiModel: '模型', + openaiModelHint: 'tts-1(快速)/ tts-1-hd(高音質)', + openaiVoice: '音色', + openaiVoiceHint: '用於語音合成的音色', + + // 自訂端點 + customHint: '支援任何 OpenAI 相容的 TTS 服務——可用於 GPT-SoVITS、CosyVoice 等自部署服務。', + customUrl: 'API 位址', + customUrlHint: 'TTS 服務的完整基礎位址', + customUrlPlaceholder: '本地適配器中設定的位址,如:http://127.0.0.1:9880', + customApiKey: 'API 金鑰(選填)', + customApiKeyHint: '部分自部署服務需要身份驗證', + customApiKeyPlaceholder: '不需要則留空', + // Edge TTS + edgeHint: '由 Microsoft Edge TTS 驅動(node-edge-tts)。', + edgeUrl: '適配器位址', + edgeUrlHint: 'Edge TTS 適配器位址,例如 http://127.0.0.1:9882', + edgeUrlPlaceholder: 'http://127.0.0.1:9882', + edgeVoice: '音色', + edgeVoiceHint: '選擇用於語音合成的音色', + + // 試聽 + testTitle: '試聽測試', + testText: '測試文字', + testTextPlaceholder: '輸入測試文字...', + testTextDefault: '你好,這是一個語音測試。', + testButton: '試聽', + testButtonPlaying: '播放中...', + testFailed: '測試失敗:{error}', + }, + }, + + // 平台頻道設定 + platform: { + requireMention: "需要 {'@'}提及", + requireMentionGroup: "群組中需要 {'@'}機器人 才會回應", + requireMentionChannel: "頻道中需要 {'@'}機器人 才會回應", + requireMentionRoom: "房間中需要 {'@'}機器人 才會回應", + reactions: '表情回應', + reactionsHint: '對訊息新增表情回應', + freeResponseChats: '自由回應聊天', + freeResponseChatsHint: "不需要 {'@'}提及即回應的聊天 ID(逗號分隔)", + freeResponseChannels: '自由回應頻道', + freeResponseChannelsHint: "不需要 {'@'}提及即回應的頻道 ID(逗號分隔)", + freeResponseRooms: '自由回應房間', + freeResponseRoomsHint: "不需要 {'@'}提及即回應的房間 ID(逗號分隔)", + mentionPatterns: '自訂提及模式', + mentionPatternsHint: '額外的觸發模式清單', + autoThread: '自動建立討論串', + autoThreadHint: "{'@'}提及後自動建立回覆討論串", + autoThreadHintRoom: '在房間中自動建立回覆討論串', + dmMentionThreads: 'DM 提及討論串', + dmMentionThreadsHint: '在私聊中也使用討論串回覆提及', + allowBots: '允許機器人訊息', + allowBotsHint: '回應其他機器人傳送的訊息', + allowedChannels: '允許的頻道', + allowedChannelsHint: '白名單頻道 ID(逗號分隔)', + ignoredChannels: '忽略的頻道', + ignoredChannelsHint: '不回應的頻道 ID(逗號分隔)', + noThreadChannels: '無討論串頻道', + noThreadChannelsHint: '不建立討論串的頻道 ID(逗號分隔)', + exclusiveTokenWarning: '此平台使用獨占 token 鎖。每個 profile 必須使用不同的身份 token,否則會與其他 profile 衝突導致 gateway 啟動失敗。', + botToken: 'Bot Token', + botTokenHint: '開發者入口網站取得的 Bot Token', + accessToken: 'Access Token', + accessTokenHint: 'Matrix Access Token', + homeserver: 'Homeserver URL', + homeserverHint: 'Matrix 伺服器位址', + appId: 'App ID', + appIdHint: '飛書 App ID', + appSecret: 'App Secret', + appSecretHint: '飛書 App Secret', + clientId: 'Client ID', + clientIdHint: '釘釘 Client ID', + clientSecret: 'Client Secret', + clientSecretHint: '釘釘 Client Secret', + botId: 'Bot ID', + botIdHint: '企業微信 Bot ID', + wecomSecretHint: '企業微信 Bot Secret', + waEnabled: '啟用 WhatsApp', + waEnabledHint: '透過 QR Code 配對啟用 WhatsApp', + weixinToken: '微信 Token', + weixinTokenHint: '透過 weixin CLI 掃碼登入取得 (hermes weixin)', + accountId: 'Account ID', + accountIdHint: '微信 Account ID', + qrLogin: '掃碼登入', + qrRelogin: '重新登入', + qrFetching: '正在取得 QR Code...', + qrScanHint: '使用微信掃描 QR Code 登入', + qrScanedHint: '已掃描,請在手機上確認...', + // QQ + qqAppId: 'App ID', + qqAppIdHint: 'QQ 開放平台機器人 App ID', + qqAppSecret: 'App Secret', + qqAppSecretHint: 'QQ 開放平台機器人 App Secret', + qqMarkdown: 'Markdown 支援', + qqMarkdownHint: '啟用 Markdown 格式訊息(部分客戶端可能不支援)', + qqSandbox: '沙箱模式', + qqSandboxHint: '啟用沙箱環境(測試用)', + qqQrScanHint: '使用 QQ 掃描上方 QR Code,或在手機上開啟連結完成綁定', + }, + + // 閘道 + gateways: { + title: '閘道', + running: '執行中', + stopped: '已停止', + started: '已啟動', + startFailed: '啟動失敗', + stopFailed: '停止失敗', + }, + + // 語言 + language: { + label: '語言', + zh: '中文(簡體)', + 'zh-TW': '繁體中文', + en: 'English', + }, + + // 終端機 + terminal: { + sessions: '工作階段', + newTab: '新增終端機', + closeSession: '關閉此工作階段?', + sessionExited: '已退出', + processExited: '程序已退出,代碼 {code}', + noSessions: '目前無終端機工作階段', + connectionFailed: '終端機服務連線失敗', + connectionClosed: '終端機連線已關閉', + connectionError: '終端機連線錯誤', + }, + + // 群聊 + groupChat: { + title: '群聊', + createRoom: '建立房間', + joinByCode: '透過邀請碼加入', + roomName: '房間名稱', + roomNamePlaceholder: '輸入房間名稱', + inviteCode: '邀請碼', + autoGenerate: '自動產生', + noRooms: '目前無房間', + selectOrCreate: '選擇或建立一個房間開始聊天', + agents: '智慧代理', + addAgent: '新增智慧代理', + selectProfile: '選擇一個設定檔', + agentAdded: '智慧代理已新增', + agentAlreadyInRoom: '該智慧代理已在房間中', + noAgents: '目前房間無智慧代理', + members: '成員', + roomCreated: '房間已建立', + roomDeleted: '房間已刪除', + deleteRoomConfirm: '確定刪除這個房間嗎?', + you: '你', + joined: '已加入房間', + joinFailed: '加入房間失敗', + inputPlaceholder: '輸入訊息... (Enter 發送)', + enterCode: '輸入邀請碼', + yourName: '你的名稱', + yourNamePlaceholder: '輸入你的群聊暱稱', + yourDescription: '自我介紹(選填)', + yourDescriptionPlaceholder: '介紹一下你自己...', + agentName: 'Agent 名稱', + agentNamePlaceholder: '自訂名稱(留空則使用 profile 名稱)', + agentDesc: 'Agent 描述', + agentDescPlaceholder: '描述這個 agent 的作用...', + agentReplying: '正在回覆...', + agentCompressing: '正在壓縮上下文...', + compressionSettings: '壓縮設定', + triggerTokens: '觸發壓縮 Token 數', + triggerTokensDesc: '訊息 token 數超過此值時觸發上下文壓縮', + maxHistoryTokens: '最大歷史 Token 數', + maxHistoryTokensDesc: '壓縮後傳送給 LLM 的最大 token 數', + tailMessageCount: '保留最近訊息數', + tailMessageCountDesc: '壓縮後保留最近的原始訊息條數', + compressionConfig: '壓縮設定', + compressionSaved: '壓縮設定已儲存', + compressNow: '立即壓縮', + compressingInProgress: '正在壓縮中,請稍後', + }, + + // 用量統計 + usage: { + title: '用量統計', + refresh: '重新整理', + totalTokens: '總 Token 數', + inputTokens: '輸入', + outputTokens: '輸出', + totalSessions: '總工作階段數', + avgPerDay: '日均 ~{n}', + estimatedCost: '預估費用', + cacheHitRate: '快取命中率', + modelBreakdown: '模型分布', + dailyTrend: '每日用量(近 30 天)', + date: '日期', + tokens: 'Token', + cache: '快取', + cacheRead: '快取讀取', + cacheWrite: '快取寫入', + sessions: '工作階段', + cost: '費用', + noData: '目前無用量資料', + }, + + // 檔案管理 + files: { + title: '檔案', + fileTree: '檔案樹', + tree: '目錄樹', + list: '檔案清單', + breadcrumbRoot: '根目錄', + newFile: '新增檔案', + newFolder: '新增資料夾', + upload: '上傳', + refresh: '重新整理', + open: '開啟', + edit: '編輯', + preview: '預覽', + download: '下載', + copyPath: '複製路徑', + rename: '重新命名', + delete: '刪除', + name: '名稱', + size: '大小', + modified: '修改時間', + actions: '操作', + emptyDir: '空目錄', + loading: '載入中...', + confirmDelete: '確定要刪除「{name}」嗎?', + confirmDeleteDir: '確定要刪除目錄「{name}」及其所有內容嗎?', + deleteFailed: '刪除失敗', + deleted: '已刪除', + renameTo: '重新命名為', + newFileName: '檔案名稱', + newFolderName: '資料夾名稱', + created: '已建立', + createFailed: '建立失敗', + renamed: '已重新命名', + renameFailed: '重新命名失敗', + uploadSuccess: '已上傳 {count} 個檔案', + uploadFailed: '上傳失敗', + saveFailed: '儲存失敗', + saved: '已儲存', + unsavedChanges: '有未儲存的變更,是否捨棄?', + pathCopied: '路徑已複製', + fileTooLarge: '檔案過大(最大 10MB)', + permissionDenied: '無法修改受保護的檔案', + notFound: '檔案或目錄不存在', + backendError: '檔案操作失敗', + dragDropHint: '拖曳檔案至此處上傳', + closeEditor: '關閉編輯器', + closePreview: '關閉', + saveFile: '儲存', + }, + + // 下載 + download: { + downloading: '正在下載...', + downloadFailed: '下載失敗', + fileNotFound: '檔案不存在或已被刪除', + fileTooLarge: '檔案過大(超過限制)', + backendError: '檔案讀取失敗,遠端環境可能不可用', + backendTimeout: '檔案讀取逾時', + unsupportedBackend: '目前 terminal backend 暫不支援檔案下載', + invalidPath: '無效的檔案路徑', + download: '下載', + downloadFile: '下載檔案', + }, + + // 更新日誌 + changelog: { + new_0_5_14_1: '新增工作階段匯出功能:支援完整和壓縮模式,匯出為 JSON 或純文字', + new_0_5_14_2: '修復終端機面板啟動即連線導致 PTY 資源耗盡的問題,改為延遲載入連線', + new_0_5_14_3: '修復 WSL2 環境下 IPv6 雙堆疊綁定導致健康檢查失敗、連接埠無法存取的問題', + new_0_5_14_4: '修復服務關閉時 SQLite 連線未釋放導致重啟後資料庫被鎖的問題', + new_0_5_14_5: '更新 FUN-Codex/FUN-Claude 模型清單,新增內建標識、API 中轉站入口', + new_0_5_15_1: '新增看板面板,視覺化任務與工作階段管理', + new_0_5_15_2: '新增官網首頁與文件站', + new_0_5_15_3: '修復群聊 Agent 客戶端使用動態連接埠而非硬式編碼 8648', + new_0_5_15_4: '新增 node-edge-tts 語音模組', + new_0_5_15_5: '修復 WSL 預設監聽位址綁定問題', + new_0_5_15_6: '新增官網首頁 SEO 元資料', + new_0_5_15_7: '新增基於 IP 的登入暴力破解防護', + new_0_5_15_8: '修復 MarkdownRenderer 中下載連結重複包裝問題', + new_0_5_15_9: '修復 Hermes Markdown 媒體渲染與同步重試', + new_0_5_15_10: '重構移除上游環境變數依賴', + new_0_5_15_11: '如果看板功能無法使用,請升級 hermes-agent', + new_0_5_16_1: '聊天串流介面從 /v1/runs 遷移至 /v1/responses,降低延遲', + new_0_5_16_2: '持久化真實 API 用量(token、快取、推理)到用量統計表', + new_0_5_16_3: '官網導覽列新增 QQ 群 QR Code', + new_0_5_16_4: '移除訊息 schema 中未使用的 codex_reasoning_items 欄位', + new_0_5_13_1: '新增訊息佇列,順序處理執行請求,避免並發衝突', + new_0_5_13_2: '支援二級 Skills 目錄結構,扁平化 Skill 歸入「雜項」分類', + new_0_5_13_3: '啟動同步時過濾暫時工作階段(eph_*),避免匯入內部工作階段', + new_0_5_13_4: '新增 Termux/proot 環境相容,支援行動端和嵌入式部署', + new_0_5_13_5: '完善聊天執行中止生命週期:使用者取消時正確清理狀態', + new_0_5_13_6: '移除歷史訊息清單中的串流指示器,顯示更清爽', + new_0_5_13_7: '修復自訂 Provider 的上下文解析,改進模型匹配', + new_0_5_13_8: '修復 IPv6 預設監聽位址綁定問題', + new_0_5_13_9: '最佳化聊天訊息顯示和上下文壓縮邏輯', + new_0_5_13_10: '修復聊天完成提示音開關不生效及 cron 靜默執行歷史為空的問題', + new_0_5_12_1: '新增工作階段批次刪除功能:支援 checkbox 多選、全選、批次刪除,提升工作階段管理效率', + new_0_5_12_2: '新增模型上下文長度視覺化編輯:點擊上下文長度即可開啟編輯對話框,支援自訂 token 限制', + new_0_5_12_3: '修復群聊提及功能鍵盤選擇問題:使用自訂下拉選單替換 NDropdown,支援鍵盤導覽和捲動跟隨', + new_0_5_12_4: '新增聊天完成提示音:支援自動播放開關,提升互動體驗', + new_0_5_12_5: '最佳化聊天訊息顯示:過濾空內容助手訊息,改進歷史記錄和壓縮邏輯', + new_0_5_12_6: '改進升級機制:使用 npm prefix -g 動態解析路徑,支援 Homebrew 等非標準 Node.js 安裝', + new_0_5_6_1: '新增語音播放功能:使用 Web Speech API,支援手動播放按鈕、自動播放開關、彩虹邊框動畫和行動端最佳化', + new_0_5_6_2: '新增強健的 LLM JSON 解析器,相容 Python 格式並從串流事件中擷取文字', + new_0_5_6_3: 'Skills 功能增強:使用統計、來源過濾、封存技能、來源追溯和釘選切換', + new_0_5_6_4: '擴展每日使用統計,包含詳細的 token 細分,分離快取讀/寫統計', + new_0_5_6_5: '最佳化工作階段歷史範圍說明,改進聊天和歷史視圖的描述', + new_0_5_6_6: '重新設計附件處理,採用 Anthropic 風格的 ContentBlock 陣列格式,支援類型區分(文字、圖片、檔案)', + new_0_5_6_7: '新增前端檔案下載功能,支援 ContentBlock 和 Markdown 兩種格式,附身份驗證', + new_0_5_10_1: 'GitHub Release 時自動建置 Docker 映像檔並新增版本標籤(如 :v0.5.10)', + new_0_5_10_2: '新增工作階段授權模式設定:approvals.mode(關閉/手動)', + new_0_5_10_3: '新增自動 OpenAPI 文件產生(94 個端點,24 個標籤)', + new_0_5_10_4: '增強媒體渲染:支援 Markdown 中的圖片、影片和檔案', + new_0_5_10_5: '最佳化群聊提示並修復媒體處理', + new_0_5_6_8: '修復多程序衝突導致的 SQLite 資料庫重設問題,清理冗餘 nodemon 程序', + new_0_5_9_1: '統一應用程式中的 profile 管理,提供一致的 API 和狀態管理', + new_0_5_9_2: '新增 GitHub issue 和 pull request 範本以改進貢獻工作流程', + new_0_5_8_1: '新增抽屜面板支援行動端側邊欄,可自訂彩虹邊框按鈕', + new_0_5_8_2: '修復 profile 切換狀態同步問題,立即更新 UI 並驗證後端狀態', + new_0_5_8_3: '過濾語音播放中的特殊字元和表情符號,改善語音合成效果', + new_0_5_8_4: '新增缺失的 i18n 鍵並統一工作階段資料來源,優先使用資料庫', + new_0_5_8_5: '最佳化 Vite 建置設定加快 Docker 建置,使用 esbuild 和程式碼分割', + new_0_5_7_1: '最佳化上下文壓縮以支援豐富內容(圖片、檔案),改進工具訊息處理', + new_0_5_7_2: '改進工作階段同步,使用批次插入和交易保護確保資料一致性', + new_0_5_7_3: '修復 usage.updated 事件接收,確保跨執行準確追蹤 token', + new_0_5_5_1: '🎉 勞動節快樂!祝大家勞動愉快', + new_0_5_5_2: '新增歷史頁面,用於瀏覽 Hermes 工作階段歷史記錄', + new_0_5_5_3: '歷史頁面獨立管理工作階段狀態,不影響目前聊天頁面的使用中工作階段', + new_0_5_5_4: '歷史頁面預設自動載入並選取第一個 CLI 類型的工作階段', + new_0_5_5_5: '新增 HistoryMessageList 元件,支援透過 props 注入工作階段資料', + new_0_5_5_6: '過濾空內容訊息和無 toolName 的 tool 訊息,提升歷史記錄顯示品質', + new_0_5_5_7: '移除 localStorage 工作階段快取,所有工作階段資料改為直接從後端取得', + new_0_5_5_8: '最佳化 profile 切換邏輯,移除已棄用的快取清理呼叫', + new_0_5_4_1: '修復並發聊天工作階段事件串擾問題,重構 WebSocket 事件路由機制', + new_0_5_4_2: '修復 cron job 編輯 payload,支援長提示詞的僅名稱編輯', + new_0_5_4_3: '修復 Docker 部署後 Web 終端機無法使用 Hermes CLI 的問題', + new_0_5_4_4: '新增工作區對話框標題 i18n 翻譯,改進工作階段持久化', + new_0_5_4_5: '支援程式碼區塊複製回饋,顯示使用者通知', + new_0_5_4_6: '對齊使用分析與 Hermes 狀態資料庫架構', + new_0_4_8_2: '修復巢狀 Markdown 程式碼區塊導致渲染截斷', + new_0_4_8_3: '修復壓縮續接工作階段投影和搜尋問題', + new_0_4_8_4: '最佳化工作階段清單 N+1 查詢,修復非 CJK 搜尋 500 錯誤', + new_0_4_8_5: '修復切換標籤頁返回時強制捲動到底部', + new_0_4_8_6: '切換工作階段時新增載入過渡動畫', + new_0_4_8_7: '修復登入 Token 驗證使用正確的工作階段端點', + new_0_4_8_8: '修復重新整理頁面後圖片附件失效問題', + new_0_4_8_9: '點擊圖片附件可全螢幕預覽', + new_0_4_8_10: '上傳目錄從暫存目錄遷移到 ~/.hermes-web-ui/upload', + new_0_4_7_1: '即時串流顯示思考/推理過程', + new_0_4_7_2: 'Docker 建置時跳過 prepare 指令碼', + new_0_4_7_3: '群聊行動端體驗改進和 UI 最佳化', + new_0_4_7_4: '將剩餘上下文 Token 數限制為 0 而非負數', + new_0_4_7_5: '新增阿里雲編碼計畫內建 Provider,支援 .env base_url 覆蓋', + new_0_4_7_6: '啟動時跳過遠端設定檔以防止卡住', + new_0_4_7_7: '偵測並展示被靜默吞沒的執行錯誤', + new_0_4_7_8: 'Provider 感知的上下文長度查詢', + new_0_4_7_9: '切換時重設 config.model 並解析 CLI 自訂 Provider', + new_0_4_7_10: '刪除內建 Provider 時清除 .env 中的 base_url_env', + new_0_4_7_11: '對齊群聊房間側邊欄背景與工作階段清單', + new_0_4_4_1: '新增檔案瀏覽器,支援多後端(本地/Docker/SSH/Singularity)', + new_0_4_4_2: '新增聊天訊息附件檔案下載', + new_0_4_4_3: '使用中工作階段顯示即時狀態標籤', + new_0_4_4_4: '新增 StepFun 和 Nous Portal Provider 支援', + new_0_4_4_5: '修復特殊字元搜尋導致 500 錯誤', + new_0_4_5_1: '新增群聊功能,支援多 Agent 房間、提及路由和輸入狀態恢復', + new_0_4_5_2: '使用 YAML 重寫模型上下文設定,新增 context_length 設定', + new_0_4_5_3: '新增 gpt-5.5 到 OpenAI Codex 模型清單', + new_0_4_5_4: '用本地控制器替換任務代理,最佳化模型載入', + new_0_4_5_5: 'ModelSelector 自訂模型功能新增 i18n 支援', + new_0_4_5_6: '修復側邊欄 i18n 缺失 key 警告', + new_0_4_5_7: '登出時清除所有 localStorage', + new_0_4_5_8: '新增日誌定期輪替,防止日誌無限增長', + new_0_4_2_1: '新增 Token 用量追蹤、上下文顯示和動態上下文長度', + new_0_4_2_2: '新增工作階段搜尋彈窗', + new_0_4_2_3: '恢復群聊系統(Socket.IO + SQLite 持久化)', + new_0_4_2_4: 'Chat 頁面新增釘選工作階段和即時監控', + new_0_4_2_5: '修復內建 Provider 偵測和模型匹配問題', + }, +} diff --git a/packages/client/src/i18n/messages.ts b/packages/client/src/i18n/messages.ts index 3ef73172..260a48d1 100644 --- a/packages/client/src/i18n/messages.ts +++ b/packages/client/src/i18n/messages.ts @@ -6,18 +6,20 @@ import ja from './locales/ja' import ko from './locales/ko' import pt from './locales/pt' import zh from './locales/zh' +import zhTW from './locales/zh-TW' export type LocaleMessages = Record export const rawMessages = { - en, - zh, - ja, - ko, - fr, - es, - de, - pt, + 'en': en, + 'zh': zh, + 'zh-TW': zhTW, + 'ja': ja, + 'ko': ko, + 'fr': fr, + 'es': es, + 'de': de, + 'pt': pt, } satisfies Record function isPlainObject(value: unknown): value is LocaleMessages {