mirror of
https://github.com/hansjone/dsh-im-ops.git
synced 2026-10-11 15:53:17 +08:00
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:
parent
3ff8c2a519
commit
dcd7cd64d8
78 changed files with 2283 additions and 992 deletions
|
|
@ -2,6 +2,7 @@ import {
|
|||
normalizeAgentPresetCatalog,
|
||||
normalizeAgentPresetId,
|
||||
} from './agent-preset.mjs';
|
||||
import { t } from './i18n.mjs';
|
||||
import { withSessionBindingLock } from './session-binding-lock.mjs';
|
||||
import { splitWorkspaceCommandMessage } from './workspace-command.mjs';
|
||||
import { WORKSPACE_SESSION_STALE } from './workspace-session.mjs';
|
||||
|
|
@ -59,7 +60,7 @@ function normalizeSettings(value) {
|
|||
|
||||
function presetItemText(item) {
|
||||
const label = safeDisplayText(item.label) || item.id;
|
||||
return `${label}(${item.id})`;
|
||||
return t('{label}({id})', { label, id: item.id });
|
||||
}
|
||||
|
||||
function itemFor(catalog, id) {
|
||||
|
|
@ -67,11 +68,11 @@ function itemFor(catalog, id) {
|
|||
}
|
||||
|
||||
function defaultDescription(catalog) {
|
||||
if (!catalog.defaultId) return '未设置或当前不可用';
|
||||
if (!catalog.defaultId) return t('未设置或当前不可用');
|
||||
const item = itemFor(catalog, catalog.defaultId);
|
||||
return item
|
||||
? presetItemText(item)
|
||||
: `${catalog.defaultId}(当前不可用)`;
|
||||
: t('{id}(当前不可用)', { id: catalog.defaultId });
|
||||
}
|
||||
|
||||
function currentDescription(settings) {
|
||||
|
|
@ -79,63 +80,63 @@ function currentDescription(settings) {
|
|||
if (agentPreset === null) {
|
||||
const item = itemFor(catalog, catalog.defaultId);
|
||||
return item
|
||||
? `跟随 Host 默认:${presetItemText(item)}`
|
||||
: '跟随 Host 默认(Host 默认当前不可用)';
|
||||
? t('跟随 Host 默认:{preset}', { preset: presetItemText(item) })
|
||||
: t('跟随 Host 默认(Host 默认当前不可用)');
|
||||
}
|
||||
const item = itemFor(catalog, agentPreset);
|
||||
return item
|
||||
? presetItemText(item)
|
||||
: `${agentPreset}(已不可用)`;
|
||||
: t('{id}(已不可用)', { id: agentPreset });
|
||||
}
|
||||
|
||||
function formatCurrent(settings) {
|
||||
return [
|
||||
'当前机器人用于新会话的 Agent Preset:',
|
||||
t('当前机器人用于新会话的 Agent Preset:'),
|
||||
currentDescription(settings),
|
||||
'',
|
||||
'已有会话不会受此设置影响。',
|
||||
'查看可用项:/presetlist',
|
||||
'恢复跟随 Host 默认:/preset --default',
|
||||
t('已有会话不会受此设置影响。'),
|
||||
t('查看可用项:/presetlist'),
|
||||
t('恢复跟随 Host 默认:/preset --default'),
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
function formatList(settings) {
|
||||
const { agentPreset, agentPresetCatalog: catalog } = settings;
|
||||
const lines = [
|
||||
'当前机器人用于新会话的 Agent Preset:',
|
||||
t('当前机器人用于新会话的 Agent Preset:'),
|
||||
currentDescription(settings),
|
||||
'',
|
||||
`Host 默认:${defaultDescription(catalog)}`,
|
||||
t('Host 默认:{preset}', { preset: defaultDescription(catalog) }),
|
||||
'',
|
||||
`可用 Agent Preset(${catalog.items.length}):`,
|
||||
t('可用 Agent Preset({count}):', { count: catalog.items.length }),
|
||||
];
|
||||
if (catalog.items.length === 0) {
|
||||
lines.push('当前没有可用 Agent Preset。');
|
||||
lines.push(t('当前没有可用 Agent Preset。'));
|
||||
} else {
|
||||
catalog.items.forEach((item, index) => {
|
||||
const markers = [];
|
||||
if (item.id === catalog.defaultId) markers.push('Host 默认');
|
||||
if (item.id === agentPreset) markers.push('当前选择');
|
||||
if (agentPreset === null && item.id === catalog.defaultId) markers.push('当前生效');
|
||||
if (item.id === catalog.defaultId) markers.push(t('Host 默认'));
|
||||
if (item.id === agentPreset) markers.push(t('当前选择'));
|
||||
if (agentPreset === null && item.id === catalog.defaultId) markers.push(t('当前生效'));
|
||||
const annotation = markers.length > 0 ? `(${markers.join(',')})` : '';
|
||||
lines.push(`${index + 1}. ${presetItemText(item)}${annotation}`);
|
||||
});
|
||||
}
|
||||
lines.push(
|
||||
'',
|
||||
'选择:/preset <序号或 ID>',
|
||||
'纯数字 ID:/preset id:<ID>',
|
||||
'恢复跟随 Host 默认:/preset --default',
|
||||
t('选择:/preset <序号或 ID>'),
|
||||
t('纯数字 ID:/preset id:<ID>'),
|
||||
t('恢复跟随 Host 默认:/preset --default'),
|
||||
);
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function formatUpdated(settings) {
|
||||
return [
|
||||
'当前机器人用于新会话的 Agent Preset 已设置为:',
|
||||
t('当前机器人用于新会话的 Agent Preset 已设置为:'),
|
||||
currentDescription(settings),
|
||||
'',
|
||||
'已有会话不变。若当前聊天已有会话,请先发送 /new,再发送普通消息,才会使用新设置创建会话。',
|
||||
t('已有会话不变。若当前聊天已有会话,请先发送 /new,再发送普通消息,才会使用新设置创建会话。'),
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
|
|
@ -189,16 +190,16 @@ function presetFromSnapshot(state, key, requested) {
|
|||
if (!/^\d+$/u.test(requested)) return { numeric: false, id: null };
|
||||
const index = Number(requested);
|
||||
if (!Number.isSafeInteger(index) || index < 1) {
|
||||
return { numeric: true, error: 'Agent Preset 序号无效,请先执行 /presetlist。' };
|
||||
return { numeric: true, error: t('Agent Preset 序号无效,请先执行 /presetlist。') };
|
||||
}
|
||||
const snapshot = loadSnapshot(state, key);
|
||||
if (!snapshot) {
|
||||
return { numeric: true, error: '请先执行 /presetlist,再按列表序号选择 Agent Preset。' };
|
||||
return { numeric: true, error: t('请先执行 /presetlist,再按列表序号选择 Agent Preset。') };
|
||||
}
|
||||
const id = snapshot[index - 1];
|
||||
return id
|
||||
? { numeric: true, id }
|
||||
: { numeric: true, error: 'Agent Preset 序号不存在,请重新执行 /presetlist。' };
|
||||
: { numeric: true, error: t('Agent Preset 序号不存在,请重新执行 /presetlist。') };
|
||||
}
|
||||
|
||||
function errorCode(error) {
|
||||
|
|
@ -208,22 +209,23 @@ function errorCode(error) {
|
|||
function presetErrorMessage(error, action) {
|
||||
const code = errorCode(error);
|
||||
if (code === 'agent-preset-invalid') {
|
||||
return `Agent Preset ID 格式无效。\n${PRESET_USAGE}`;
|
||||
return t(`Agent Preset ID 格式无效。
|
||||
{usage}`, { usage: t(PRESET_USAGE) });
|
||||
}
|
||||
if (code === 'agent-preset-unavailable') {
|
||||
return 'Agent Preset 不存在或当前不可用,请重新执行 /presetlist。';
|
||||
return t('Agent Preset 不存在或当前不可用,请重新执行 /presetlist。');
|
||||
}
|
||||
if (code === WORKSPACE_SESSION_STALE || code === 'workspace-bot-not-found') {
|
||||
return '工作区或机器人状态已发生变化,请重试。';
|
||||
return t('工作区或机器人状态已发生变化,请重试。');
|
||||
}
|
||||
if (code === 'cancelled' || error?.name === 'AbortError') {
|
||||
if (action === 'list') return '获取 Agent Preset 列表已取消。';
|
||||
if (action === 'current') return '获取 Agent Preset 设置已取消。';
|
||||
return 'Agent Preset 修改已取消。';
|
||||
if (action === 'list') return t('获取 Agent Preset 列表已取消。');
|
||||
if (action === 'current') return t('获取 Agent Preset 设置已取消。');
|
||||
return t('Agent Preset 修改已取消。');
|
||||
}
|
||||
if (action === 'list') return '暂时无法获取 Agent Preset 列表,请稍后重试。';
|
||||
if (action === 'current') return '暂时无法获取 Agent Preset 设置,请稍后重试。';
|
||||
return 'Agent Preset 修改失败,请稍后重试。';
|
||||
if (action === 'list') return t('暂时无法获取 Agent Preset 列表,请稍后重试。');
|
||||
if (action === 'current') return t('暂时无法获取 Agent Preset 设置,请稍后重试。');
|
||||
return t('Agent Preset 修改失败,请稍后重试。');
|
||||
}
|
||||
|
||||
async function settings(harness, options) {
|
||||
|
|
@ -250,12 +252,12 @@ export async function runPresetCommand(text, harness, state, key, options = {})
|
|||
if (!isPresetCommand(text)) return null;
|
||||
const command = text.trim();
|
||||
if (options.hasImages) {
|
||||
return commandResult('Agent Preset 命令仅支持纯文字,请移除图片后重试。');
|
||||
return commandResult(t('Agent Preset 命令仅支持纯文字,请移除图片后重试。'));
|
||||
}
|
||||
const requestOptions = rpcOptions(options.signal);
|
||||
|
||||
if (PRESET_LIST_COMMAND.test(command)) {
|
||||
if (!/^\/presetlist[ \t]*$/iu.test(command)) return commandResult(PRESET_LIST_USAGE);
|
||||
if (!/^\/presetlist[ \t]*$/iu.test(command)) return commandResult(t(PRESET_LIST_USAGE));
|
||||
try {
|
||||
const current = await settings(harness, requestOptions);
|
||||
saveSnapshot(state, key, current.agentPresetCatalog.items);
|
||||
|
|
@ -266,7 +268,7 @@ export async function runPresetCommand(text, harness, state, key, options = {})
|
|||
}
|
||||
|
||||
const match = /^\/preset(?:[ \t]+([^\s]+))?[ \t]*$/iu.exec(command);
|
||||
if (!match) return commandResult(PRESET_USAGE);
|
||||
if (!match) return commandResult(t(PRESET_USAGE));
|
||||
const requested = match[1];
|
||||
if (!requested) {
|
||||
try {
|
||||
|
|
@ -290,7 +292,8 @@ export async function runPresetCommand(text, harness, state, key, options = {})
|
|||
selected = fromSnapshot.id;
|
||||
} else {
|
||||
selected = normalizeAgentPresetId(requested);
|
||||
if (!selected) return commandResult(`Agent Preset ID 格式无效。\n${PRESET_USAGE}`);
|
||||
if (!selected) return commandResult(t(`Agent Preset ID 格式无效。
|
||||
{usage}`, { usage: t(PRESET_USAGE) }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue