mirror of
https://github.com/hansjone/dsh-im-ops.git
synced 2026-10-14 10:50:44 +08:00
Merge pull request #46 from zaakirio/feat/host-i18n-en
feat(i18n): add English support for host-side chat messages
This commit is contained in:
commit
3741dd8fd9
78 changed files with 2290 additions and 1000 deletions
|
|
@ -1,3 +1,4 @@
|
|||
import { t } from './i18n.mjs';
|
||||
import { runWorkspaceCommand } from './workspace-command.mjs';
|
||||
import { runCompactCommand } from './compact-command.mjs';
|
||||
import {
|
||||
|
|
@ -55,38 +56,38 @@ function canClaimInteractionReply(message, pending, senderId) {
|
|||
}
|
||||
|
||||
function artifactFailureText(fileName, error, descriptor) {
|
||||
const name = String(fileName ?? '结果文件')
|
||||
const name = String(fileName ?? t('结果文件'))
|
||||
.replace(/[\r\n]+/g, ' ')
|
||||
.trim()
|
||||
.slice(0, 255) || '结果文件';
|
||||
.slice(0, 255) || t('结果文件');
|
||||
switch (error?.code) {
|
||||
case 'artifact-delivery-uncertain':
|
||||
return `结果文件「${name}」的发送结果未能确认,请先检查聊天内是否已收到,不要立即重试。`;
|
||||
return t('结果文件「{name}」的发送结果未能确认,请先检查聊天内是否已收到,不要立即重试。', { name });
|
||||
case 'artifact-permission-required':
|
||||
if (descriptor?.key === 'slack') {
|
||||
return `结果文件「${name}」已生成,但 Slack 应用缺少 files:write 权限。请更新 Manifest、重新安装应用并重新连接机器人后重试。`;
|
||||
return t('结果文件「{name}」已生成,但 Slack 应用缺少 files:write 权限。请更新 Manifest、重新安装应用并重新连接机器人后重试。', { name });
|
||||
}
|
||||
if (descriptor?.key === 'discord') {
|
||||
return `结果文件「${name}」已生成,但机器人缺少 Discord 的 Send Messages、Attach Files 或 Read Message History 权限。`;
|
||||
return t('结果文件「{name}」已生成,但机器人缺少 Discord 的 Send Messages、Attach Files 或 Read Message History 权限。', { name });
|
||||
}
|
||||
if (descriptor?.key === 'telegram') {
|
||||
return `结果文件「${name}」已生成,但 Telegram 不允许机器人在当前聊天发送文档,请检查聊天权限。`;
|
||||
return t('结果文件「{name}」已生成,但 Telegram 不允许机器人在当前聊天发送文档,请检查聊天权限。', { name });
|
||||
}
|
||||
return `结果文件「${name}」已生成,但当前机器人没有文件发送权限,请检查渠道权限。`;
|
||||
return t('结果文件「{name}」已生成,但当前机器人没有文件发送权限,请检查渠道权限。', { name });
|
||||
case 'artifact-too-large':
|
||||
return `结果文件「${name}」超过当前渠道大小上限,未发送。`;
|
||||
return t('结果文件「{name}」超过当前渠道大小上限,未发送。', { name });
|
||||
case 'artifact-empty':
|
||||
return `结果文件「${name}」为空,未发送。`;
|
||||
return t('结果文件「{name}」为空,未发送。', { name });
|
||||
case 'artifact-invalid':
|
||||
case 'artifact-changed':
|
||||
case 'artifact-unavailable':
|
||||
return `结果文件「${name}」暂时无法读取或准备发送,请确认文件仍可访问后重试。`;
|
||||
return t('结果文件「{name}」暂时无法读取或准备发送,请确认文件仍可访问后重试。', { name });
|
||||
case 'artifact-rate-limited':
|
||||
return `结果文件「${name}」暂时被当前渠道限流,未能发送,请稍后重试。`;
|
||||
return t('结果文件「{name}」暂时被当前渠道限流,未能发送,请稍后重试。', { name });
|
||||
case 'artifact-provider-rejected':
|
||||
return `结果文件「${name}」已生成,但当前渠道拒绝了该文件或文件消息。`;
|
||||
return t('结果文件「{name}」已生成,但当前渠道拒绝了该文件或文件消息。', { name });
|
||||
default:
|
||||
return `结果文件「${name}」已生成,但当前渠道暂时未能发送,请稍后重试。`;
|
||||
return t('结果文件「{name}」已生成,但当前渠道暂时未能发送,请稍后重试。', { name });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -321,7 +322,7 @@ export class TextHarnessBridge {
|
|||
if (error?.code === 'turn-stopped' || this.#signal?.aborted) return;
|
||||
this.#status.lastError = error?.message ?? String(error);
|
||||
this.#logger.error?.(`[dsh-im:${this.#descriptor.key}] failed to process a command:`, error);
|
||||
await this.#bot.sendText(target, '消息处理失败,请稍后重试。').catch(() => undefined);
|
||||
await this.#bot.sendText(target, t('消息处理失败,请稍后重试。')).catch(() => undefined);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -329,7 +330,7 @@ export class TextHarnessBridge {
|
|||
return sendRememberedConnectionTest({
|
||||
state: this.#state,
|
||||
text,
|
||||
channelLabel: `${this.#descriptor.label}机器人`,
|
||||
channelLabel: t('{label}机器人', { label: this.#descriptor.label }),
|
||||
send: (target, message) => this.#bot.sendText(target, message),
|
||||
});
|
||||
}
|
||||
|
|
@ -384,38 +385,38 @@ export class TextHarnessBridge {
|
|||
const hasImages = hasInboundImages(message);
|
||||
const hasFiles = hasInboundFiles(message);
|
||||
if (!text && !hasImages && !hasFiles) {
|
||||
await this.#bot.sendText(target, '目前支持文字、图片和文件消息。');
|
||||
await this.#bot.sendText(target, t('目前支持文字、图片和文件消息。'));
|
||||
return;
|
||||
}
|
||||
const command = text.toLowerCase();
|
||||
if (!hasImages && !hasFiles && command === '/help') {
|
||||
await this.#bot.sendText(target, [
|
||||
`${this.#descriptor.label}机器人已连接 DeepSeek Harness。`,
|
||||
t('{label}机器人已连接 DeepSeek Harness。', { label: this.#descriptor.label }),
|
||||
'',
|
||||
'直接发送文字、图片或文件即可继续当前会话。',
|
||||
'/new 开启一个全新会话',
|
||||
'/compact 压缩当前会话的较早上下文',
|
||||
'/workspace 工作区绝对路径 切换工作区',
|
||||
'/workspacelist 列出工作区绝对路径',
|
||||
'/sessionlist [工作区序号或绝对路径] 列出会话 ID 和标题',
|
||||
'/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话',
|
||||
'/models 按序号列出所有可用模型',
|
||||
'/model [序号或完整模型ID] 查看或切换当前会话模型',
|
||||
'示例:先发 /models,再发 /model 2',
|
||||
'/presetlist 按序号列出可用 Agent Preset',
|
||||
'/preset [序号或完整ID] 查看或设置当前机器人 Agent Preset',
|
||||
'纯数字 ID:/preset id:<ID>',
|
||||
'/preset --default 跟随 Host 默认',
|
||||
'/stop 停止当前任务',
|
||||
'/steer 补充指令 纠偏当前任务',
|
||||
'/status 检查连接状态',
|
||||
'/help 显示本帮助',
|
||||
t('直接发送文字、图片或文件即可继续当前会话。'),
|
||||
t('/new 开启一个全新会话'),
|
||||
t('/compact 压缩当前会话的较早上下文'),
|
||||
t('/workspace 工作区绝对路径 切换工作区'),
|
||||
t('/workspacelist 列出工作区绝对路径'),
|
||||
t('/sessionlist [工作区序号或绝对路径] 列出会话 ID 和标题'),
|
||||
t('/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话'),
|
||||
t('/models 按序号列出所有可用模型'),
|
||||
t('/model [序号或完整模型ID] 查看或切换当前会话模型'),
|
||||
t('示例:先发 /models,再发 /model 2'),
|
||||
t('/presetlist 按序号列出可用 Agent Preset'),
|
||||
t('/preset [序号或完整ID] 查看或设置当前机器人 Agent Preset'),
|
||||
t('纯数字 ID:/preset id:<ID>'),
|
||||
t('/preset --default 跟随 Host 默认'),
|
||||
t('/stop 停止当前任务'),
|
||||
t('/steer 补充指令 纠偏当前任务'),
|
||||
t('/status 检查连接状态'),
|
||||
t('/help 显示本帮助'),
|
||||
].join('\n'));
|
||||
return;
|
||||
}
|
||||
if (!hasImages && !hasFiles && command === '/status') {
|
||||
await this.#harness.ensureRunning({ signal: this.#signal });
|
||||
await this.#bot.sendText(target, `${this.#descriptor.label}机器人与 DeepSeek Harness 连接正常。`);
|
||||
await this.#bot.sendText(target, t('{label}机器人与 DeepSeek Harness 连接正常。', { label: this.#descriptor.label }));
|
||||
return;
|
||||
}
|
||||
const workspaceCommand = !hasImages && !hasFiles
|
||||
|
|
@ -429,7 +430,7 @@ export class TextHarnessBridge {
|
|||
}
|
||||
if (!hasImages && !hasFiles && command === '/new') {
|
||||
await this.#state.clearSession(conversationKey);
|
||||
await this.#bot.sendText(target, '已开启新会话。请发送你的问题。');
|
||||
await this.#bot.sendText(target, t('已开启新会话。请发送你的问题。'));
|
||||
return;
|
||||
}
|
||||
const compactCommand = !hasImages && !hasFiles
|
||||
|
|
@ -487,7 +488,7 @@ export class TextHarnessBridge {
|
|||
control: { owner: this, key: conversationKey },
|
||||
onUpdate: stream ? async (update) => {
|
||||
const progress = update.type === 'text' ? update.text
|
||||
: update.type === 'tool' ? `正在使用${update.name}…` : update.text;
|
||||
: update.type === 'tool' ? t('正在使用{name}…', { name: update.name }) : update.text;
|
||||
if (progress) {
|
||||
const format = update.type === 'text' ? 'markdown' : 'plain';
|
||||
await stream.update(semanticStream
|
||||
|
|
@ -505,12 +506,11 @@ export class TextHarnessBridge {
|
|||
files: message.files,
|
||||
},
|
||||
});
|
||||
const visibleAnswer = !cleanText(answer) && artifacts.length > 0
|
||||
? FILE_ONLY_COMPLETION_TEXT
|
||||
const fileOnlyCompletion = !cleanText(answer) && artifacts.length > 0;
|
||||
const visibleAnswer = fileOnlyCompletion
|
||||
? t(FILE_ONLY_COMPLETION_TEXT)
|
||||
: answer;
|
||||
const answerFormat = visibleAnswer === FILE_ONLY_COMPLETION_TEXT && !cleanText(answer)
|
||||
? 'plain'
|
||||
: 'markdown';
|
||||
const answerFormat = fileOnlyCompletion ? 'plain' : 'markdown';
|
||||
let textDeliveryError = null;
|
||||
let textReceipt = null;
|
||||
if (stream) {
|
||||
|
|
@ -580,7 +580,7 @@ export class TextHarnessBridge {
|
|||
if (error?.code === 'turn-stopped') {
|
||||
if (stream) {
|
||||
try {
|
||||
await stream.finish('已停止。');
|
||||
await stream.finish(t('已停止。'));
|
||||
} catch {
|
||||
stream.cancel?.();
|
||||
}
|
||||
|
|
@ -639,7 +639,7 @@ export class TextHarnessBridge {
|
|||
}
|
||||
stream?.cancel?.();
|
||||
try {
|
||||
await this.#bot.sendText(target, '消息处理失败,请稍后重试。');
|
||||
await this.#bot.sendText(target, t('消息处理失败,请稍后重试。'));
|
||||
} catch (sendError) {
|
||||
this.#logger.error?.(
|
||||
`[dsh-im:${this.#descriptor.key}] failed to send the safe error reply:`,
|
||||
|
|
@ -682,7 +682,7 @@ export class TextHarnessBridge {
|
|||
const text = cleanText(message.content);
|
||||
if (!text || hasInboundImages(message) || hasInboundFiles(message)) {
|
||||
try {
|
||||
await this.#bot.sendText(target, '请用文字回答当前问题。');
|
||||
await this.#bot.sendText(target, t('请用文字回答当前问题。'));
|
||||
} catch (error) {
|
||||
this.#logger.error?.(
|
||||
`[dsh-im:${this.#descriptor.key}] failed to reject a non-text interaction reply:`,
|
||||
|
|
@ -710,7 +710,7 @@ export class TextHarnessBridge {
|
|||
try {
|
||||
await this.#presentInteraction(pending);
|
||||
} catch (error) {
|
||||
this.#status.lastError = `${this.#descriptor.label}交互问题发送失败。`;
|
||||
this.#status.lastError = t('{label}交互问题发送失败。', { label: this.#descriptor.label });
|
||||
this.#logger.error?.(
|
||||
`[dsh-im:${this.#descriptor.key}] failed to retry an interaction question:`,
|
||||
error,
|
||||
|
|
@ -748,7 +748,7 @@ export class TextHarnessBridge {
|
|||
try {
|
||||
await this.#presentInteraction(pending);
|
||||
} catch (error) {
|
||||
this.#status.lastError = `${this.#descriptor.label}交互问题发送失败。`;
|
||||
this.#status.lastError = t('{label}交互问题发送失败。', { label: this.#descriptor.label });
|
||||
this.#logger.error?.(
|
||||
`[dsh-im:${this.#descriptor.key}] failed to send the next interaction question:`,
|
||||
error,
|
||||
|
|
@ -774,7 +774,7 @@ export class TextHarnessBridge {
|
|||
this.#clearPendingInteraction(key, pending.interactionId);
|
||||
if (this.#signal?.aborted) return;
|
||||
try {
|
||||
await this.#bot.sendText(target, INTERACTION_RESOLVED_TEXT);
|
||||
await this.#bot.sendText(target, t(INTERACTION_RESOLVED_TEXT));
|
||||
} catch (sendError) {
|
||||
this.#logger.error?.(
|
||||
`[dsh-im:${this.#descriptor.key}] failed to send an expired interaction notice:`,
|
||||
|
|
@ -787,13 +787,13 @@ export class TextHarnessBridge {
|
|||
pending.submitting = false;
|
||||
pending.answers.pop();
|
||||
pending.index -= 1;
|
||||
this.#status.lastError = '回答提交失败。';
|
||||
this.#status.lastError = t('回答提交失败。');
|
||||
this.#logger.error?.(
|
||||
`[dsh-im:${this.#descriptor.key}] failed to answer a Harness interaction:`,
|
||||
error,
|
||||
);
|
||||
try {
|
||||
await this.#bot.sendText(target, '回答提交失败,请重新发送当前问题的答案。');
|
||||
await this.#bot.sendText(target, t('回答提交失败,请重新发送当前问题的答案。'));
|
||||
} catch (sendError) {
|
||||
this.#logger.error?.(
|
||||
`[dsh-im:${this.#descriptor.key}] failed to send an interaction retry notice:`,
|
||||
|
|
@ -840,7 +840,7 @@ export class TextHarnessBridge {
|
|||
try {
|
||||
await this.#bot.sendText(
|
||||
target,
|
||||
'检测到这个 Session 中遗留的待回答问题,已安全取消并继续处理你刚才的消息。',
|
||||
t('检测到这个 Session 中遗留的待回答问题,已安全取消并继续处理你刚才的消息。'),
|
||||
);
|
||||
} catch (error) {
|
||||
this.#logger.error?.(
|
||||
|
|
@ -941,7 +941,7 @@ export class TextHarnessBridge {
|
|||
this.#status.lastMessageAt = new Date().toISOString();
|
||||
}
|
||||
try {
|
||||
await this.#bot.sendText(message.replyTarget, INTERACTION_RESOLVED_TEXT);
|
||||
await this.#bot.sendText(message.replyTarget, t(INTERACTION_RESOLVED_TEXT));
|
||||
} catch (error) {
|
||||
this.#logger.error?.(
|
||||
`[dsh-im:${this.#descriptor.key}] failed to send an expired interaction notice:`,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue