feat(i18n): add English support for host-side chat messages

Wrap all user-facing chat text in src/channels with a host-side t()
translator (Chinese is the identity default, so untranslated text is
always sent verbatim in Chinese). English is selected via the host
plugin config `language: en` or the DSH_IM_LANGUAGE environment
variable, and the settings UI keeps following the DSH web locale.

- add src/channels/shared/i18n.mjs with setImHostLanguage/t() and a
  Chinese-key -> English dictionary split by area (shared + channels)
- complete the English dictionary for every t() key in src/channels,
  including the feishu channel (welcome/help, repair flow, interactive
  cards, watch flow) and the shared command/approval/stream text
- wrap previously untranslated chat-facing strings in feishu bridge,
  feishu runtime, editable-message-stream, harness-client,
  connection-test and image-prompt
- dedupe dictionary entries that existed in multiple channel files and
  canonicalize conflicting translations into the shared dictionaries
- extend the i18n unit test to decode string-literal escapes so keys
  containing \n are checked correctly
- document the language switch in README.md, README.en.md and
  CHANGELOG.md, and rebuild lib/index.js

Added English support for bot chat messages across all nine IM
channels and AI Office without changing any Chinese output.
This commit is contained in:
Zaakir 2026-08-23 22:34:11 +05:00
parent 3ff8c2a519
commit dcd7cd64d8
78 changed files with 2283 additions and 992 deletions

View file

@ -0,0 +1,216 @@
// English translations (shared-b area). Keys are exact Chinese literals passed to t().
export default {
// workspace-command.mjs
'用法:/session Session ID 或当前工作区序号(/session N)':
'Usage: /session Session ID or the session index in the current Workspace (/session N)',
'用法:\n/sessionlist 列出当前工作区会话\n/sessionlist 工作区序号 按 /workspacelist 序号列出会话\n/sessionlist 工作区绝对路径 列出指定工作区会话':
'Usage:\n/sessionlist List Sessions in the current Workspace\n/sessionlist Workspace index List Sessions by /workspacelist index\n/sessionlist Workspace absolute path List Sessions in the given Workspace',
'工作区必须是绝对路径。\n{usage}': 'The Workspace must be an absolute path.\n{usage}',
'工作区路径包含不支持的字符或长度超过限制。\n{usage}':
'The Workspace path contains unsupported characters or exceeds the length limit.\n{usage}',
'工作区路径不存在。\n{usage}': 'The Workspace path does not exist.\n{usage}',
'工作区路径必须指向一个目录。\n{usage}': 'The Workspace path must point to a directory.\n{usage}',
'用法:/workspacelist': 'Usage: /workspacelist',
'当前机器人暂不支持列出工作区。': 'This bot does not support listing Workspaces yet.',
'当前 Harness Host 上没有仍然存在的已登记工作区。':
'No registered Workspaces still exist on the current Harness Host.',
'当前 Harness Host 上存在的工作区({count}):':
'Workspaces on the current Harness Host ({count}):',
'(当前)': '(current)',
'切换用法:/workspace 工作区绝对路径': 'To switch: /workspace Workspace absolute path',
'查看会话:/sessionlist 工作区序号或绝对路径':
'To view Sessions: /sessionlist Workspace index or absolute path',
'机器人正在移除或已重新接入,无法列出原会话的工作区。':
'The bot is being removed or has been reconnected; cannot list Workspaces of the original Session.',
'暂时无法获取工作区列表,请稍后重试。':
'Unable to get the Workspace list right now; please try again later.',
'当前机器人没有可用的工作区。': 'This bot has no available Workspace.',
'当前机器人暂不支持按序号选择工作区。':
'This bot does not support selecting a Workspace by index yet.',
'工作区序号不存在,请先执行 /workspacelist。':
'The Workspace index does not exist; please run /workspacelist first.',
'今天 {time}': 'Today {time}',
'昨天 {time}': 'Yesterday {time}',
'前天 {time}': 'The day before yesterday {time}',
'{month}月{day}日 {time}': '{month}/{day} {time}',
'{year}年{month}月{day}日': '{year}-{month}-{day}',
'标题暂不可用': 'Title unavailable',
'暂无标题': 'No title',
'(已归档)': '(archived)',
'工作区:{workspace}\n该工作区暂无会话。': 'Workspace: {workspace}\nThis Workspace has no Sessions yet.',
'工作区:{workspace}': 'Workspace: {workspace}',
'会话({count}):': 'Sessions ({count}):',
'绑定用法:/session Session ID 或当前工作区序号(/session N)':
'To bind: /session Session ID or the Session index in the current Workspace (/session N)',
'绑定用法:/session Session ID\n提示:/session N 只按机器人当前工作区的序号绑定。':
"To bind: /session Session ID\nNote: /session N binds by index within the bot's current Workspace only.",
'当前机器人暂不支持列出工作区会话。':
'This bot does not support listing Workspace Sessions yet.',
'机器人正在移除或已重新接入,无法列出原会话的工作区会话。':
'The bot is being removed or has been reconnected; cannot list Workspace Sessions of the original Session.',
'暂时无法获取工作区会话列表,请稍后重试。':
'Unable to get the Workspace Session list right now; please try again later.',
'Session ID 格式无效。\n{usage}': 'Invalid Session ID format.\n{usage}',
'未找到该会话,请先执行 /sessionlist 确认 Session ID。':
'Session not found; please run /sessionlist to confirm the Session ID.',
'子代理会话不能绑定到机器人对话,请选择普通会话。':
'Sub-agent Sessions cannot be bound to a bot conversation; please choose a regular Session.',
'该会话的工作区归属不明确,暂时无法绑定。':
"The Session's Workspace ownership is ambiguous; cannot bind it right now.",
'暂时无法读取该会话的信息,请稍后重试。':
'Unable to read the Session info right now; please try again later.',
'机器人正在移除或已重新接入,无法绑定原对话的会话。':
'The bot is being removed or has been reconnected; cannot bind the Session of the original conversation.',
'工作区或会话状态已发生变化,请重试。':
'The Workspace or Session state has changed; please try again.',
'暂时无法绑定会话,请稍后重试。': 'Unable to bind the Session right now; please try again later.',
'当前机器人暂不支持按序号绑定,请使用 /session Session ID。':
'This bot does not support binding by index yet; please use /session Session ID.',
'会话序号不存在,请先执行 /sessionlist 查看序号。':
'The Session index does not exist; please run /sessionlist to see the indexes.',
'暂时无法获取会话列表,请稍后重试。':
'Unable to get the Session list right now; please try again later.',
'当前机器人暂不支持绑定已有会话。':
'This bot does not support binding existing Sessions yet.',
'当前消息缺少可绑定的会话上下文。':
'The current message lacks a conversation context to bind to.',
'当前聊天已绑定会话:': 'This chat is now bound to the Session:',
'标题:{title}': 'Title: {title}',
'归档:{archived}': 'Archived: {archived}',
'是': 'Yes',
'否': 'No',
'用法:/workspace 工作区绝对路径': 'Usage: /workspace Workspace absolute path',
'当前机器人暂不支持切换工作区。': 'This bot does not support switching Workspaces yet.',
'工作区已切换为:{workspace}': 'Workspace switched to: {workspace}',
'{message}\n用法:/workspace 工作区绝对路径': '{message}\nUsage: /workspace Workspace absolute path',
'机器人正在移除或已重新接入,无法切换原会话的工作区。':
'The bot is being removed or has been reconnected; cannot switch the Workspace of the original Session.',
// preset-command.mjs
'用法:/presetlist(不带参数)': 'Usage: /presetlist (no arguments)',
'用法:\n/preset 查看当前设置\n/preset <序号> 按最近一次 /presetlist 的序号选择\n/preset <ID> 按 Agent Preset ID 选择\n/preset id:<纯数字 ID> 选择纯数字 ID\n/preset --default 跟随 Host 默认':
'Usage:\n/preset Show current settings\n/preset <index> Select by index from the latest /presetlist\n/preset <ID> Select by Agent Preset ID\n/preset id:<numeric ID> Select a purely numeric ID\n/preset --default Follow the Host default',
'未设置或当前不可用': 'Not set or currently unavailable',
'{id}(当前不可用)': '{id} (currently unavailable)',
'跟随 Host 默认:{preset}': 'Following Host default: {preset}',
'跟随 Host 默认(Host 默认当前不可用)': 'Following Host default (Host default currently unavailable)',
'{id}(已不可用)': '{id} (no longer available)',
'当前机器人用于新会话的 Agent Preset:': 'Agent Preset this bot uses for new Sessions:',
'已有会话不会受此设置影响。': 'Existing Sessions are not affected by this setting.',
'查看可用项:/presetlist': 'See available options: /presetlist',
'恢复跟随 Host 默认:/preset --default': 'Restore following Host default: /preset --default',
'Host 默认:{preset}': 'Host default: {preset}',
'可用 Agent Preset({count}):': 'Available Agent Presets ({count}):',
'当前没有可用 Agent Preset。': 'No Agent Presets are currently available.',
'Host 默认': 'Host default',
'当前选择': 'current selection',
'当前生效': 'currently in effect',
'选择:/preset <序号或 ID>': 'To select: /preset <index or ID>',
'当前机器人用于新会话的 Agent Preset 已设置为:':
'The Agent Preset this bot uses for new Sessions is now set to:',
'已有会话不变。若当前聊天已有会话,请先发送 /new,再发送普通消息,才会使用新设置创建会话。':
'Existing Sessions are unchanged. If this chat already has a Session, send /new first, then a regular message, to create a Session with the new setting.',
'Agent Preset 序号无效,请先执行 /presetlist。':
'Invalid Agent Preset index; please run /presetlist first.',
'请先执行 /presetlist,再按列表序号选择 Agent Preset。':
'Please run /presetlist first, then select an Agent Preset by list index.',
'Agent Preset 序号不存在,请重新执行 /presetlist。':
'The Agent Preset index does not exist; please run /presetlist again.',
'Agent Preset ID 格式无效。\n{usage}': 'Invalid Agent Preset ID format.\n{usage}',
'Agent Preset 不存在或当前不可用,请重新执行 /presetlist。':
'The Agent Preset does not exist or is currently unavailable; please run /presetlist again.',
'工作区或机器人状态已发生变化,请重试。':
'The Workspace or bot state has changed; please try again.',
'获取 Agent Preset 列表已取消。': 'Fetching the Agent Preset list was cancelled.',
'获取 Agent Preset 设置已取消。': 'Fetching the Agent Preset settings was cancelled.',
'Agent Preset 修改已取消。': 'The Agent Preset change was cancelled.',
'暂时无法获取 Agent Preset 列表,请稍后重试。':
'Unable to get the Agent Preset list right now; please try again later.',
'暂时无法获取 Agent Preset 设置,请稍后重试。':
'Unable to get the Agent Preset settings right now; please try again later.',
'Agent Preset 修改失败,请稍后重试。': 'Failed to change the Agent Preset; please try again later.',
'Agent Preset 命令仅支持纯文字,请移除图片后重试。':
'Agent Preset commands support text only; please remove images and try again.',
// model-command.mjs
'用法:/model <序号> 或 /model <provider>/<model>': 'Usage: /model <index> or /model <provider>/<model>',
'用法:/models(不带参数)': 'Usage: /models (no arguments)',
'模型序号无效:{input}': 'Invalid model index: {input}',
'请发送 /models 查看并输入有效的正整数序号。':
'Please send /models to view the list and enter a valid positive integer index.',
'可用模型:': 'Available models:',
'当前没有可用模型。': 'No models are currently available.',
'以下模型提供方暂时不可用:': 'The following model providers are temporarily unavailable:',
'切换模型:/model <序号>': 'To switch models: /model <index>',
'当前模型:': 'Current model:',
'查看全部模型:/models': 'See all models: /models',
'当前聊天还没有会话。': 'This chat has no Session yet.',
'查看模型:/models': 'View models: /models',
'选择模型:/model <序号>': 'Select a model: /model <index>',
'当前任务正在运行,请等待完成或先发送 /stop。':
'A task is currently running; please wait for it to finish or send /stop first.',
'当前聊天绑定的会话已不存在,请重试。':
'The Session bound to this chat no longer exists; please try again.',
'无法切换到该模型。模型当前不可用,或不支持当前会话中的图片。':
'Cannot switch to that model. It is currently unavailable, or does not support the images in this Session.',
'当前聊天绑定的会话已发生变化,请重试。':
'The Session bound to this chat has changed; please try again.',
'获取模型列表已取消。': 'Fetching the model list was cancelled.',
'模型切换已取消。': 'The model switch was cancelled.',
'暂时无法获取模型列表,请稍后重试。':
'Unable to get the model list right now; please try again later.',
'模型切换失败,请稍后重试。': 'Failed to switch the model; please try again later.',
'模型命令仅支持纯文字,请移除图片后重试。':
'Model commands support text only; please remove images and try again.',
'当前任务正在等待你的回答或审批。': 'The current task is waiting for your answer or approval.',
'请先处理当前请求,或者发送 /stop 停止任务。':
'Please handle the current request first, or send /stop to stop the task.',
'没有找到模型:{model}': 'Model not found: {model}',
'请发送 /models 查看可用模型。': 'Please send /models to view available models.',
'模型已切换为:\n{model}\n\n后续消息将使用该模型。':
'Model switched to:\n{model}\n\nSubsequent messages will use this model.',
// compact-command.mjs
'用法:/compact(不带参数)': 'Usage: /compact (no arguments)',
'暂无可压缩的历史记录。': 'No compactable history yet.',
'当前会话正在生成回复或执行压缩,请稍后重试。':
'The current Session is generating a reply or compacting; please try again later.',
'上下文压缩已取消。': 'Context compaction was cancelled.',
'压缩期间会话历史发生变化,本次未修改会话,请重试。':
'The Session history changed during compaction; the Session was not modified this time, please try again.',
'未能生成有效的压缩摘要,本次未修改会话。':
'Could not produce a useful compaction summary; the Session was not modified this time.',
'上下文压缩未正常完成,部分会话历史可能已变化,请检查会话后再重试。':
'Context compaction did not finish cleanly; some Session history may have changed. Please inspect the Session before retrying.',
'上下文已压缩,但会话保存失败。': 'Context was compacted, but saving the Session failed.',
'已压缩 {count} 条历史记录(约 {tokens} 个 token)。':
'Compacted {count} history items (~{tokens} tokens).',
'上下文压缩完成。': 'Context compaction completed.',
'上下文压缩失败。': 'Context compaction failed.',
'当前聊天绑定的会话已不存在,请发送新消息开启会话。':
'The Session bound to this chat no longer exists; please send a new message to start a Session.',
'当前会话正在生成回复,请稍后重试。':
'The current Session is generating a reply; please try again later.',
'当前 Harness 暂不支持从机器人执行上下文压缩。':
'The current Harness does not support running context compaction from a bot yet.',
'上下文压缩失败,请稍后重试。': 'Context compaction failed; please try again later.',
'当前机器人没有可用的会话状态。': 'This bot has no available Session state.',
'当前聊天还没有可压缩的会话,请先发送一条消息。':
'This chat has no Session to compact yet; please send a message first.',
'当前机器人暂不支持上下文压缩。': 'This bot does not support context compaction yet.',
'当前 Harness 未注册 /compact 命令,请确认上下文压缩组件已启用。':
'The current Harness has not registered the /compact command; please make sure the context compaction component is enabled.',
// control-command.mjs
'用法:/stop(不带参数)': 'Usage: /stop (no arguments)',
'用法:/steer <补充指令>': 'Usage: /steer <additional instruction>',
'控制命令仅支持纯文字,请移除图片后重试。':
'Control commands support text only; please remove images and try again.',
'当前聊天没有正在运行的任务。': 'This chat has no running task.',
'已请求停止当前任务。': 'Stop requested for the current task.',
'当前聊天没有正在运行的任务,请直接发送普通消息。':
'This chat has no running task; just send a regular message.',
'已提交补充指令,Agent 会在下一步读取。':
'Additional instruction submitted; the Agent will read it at the next step.',
};