From fad5dbbc308b3873113ed0fe108d2e476c7d1e96 Mon Sep 17 00:00:00 2001 From: junes <673638712@qq.com> Date: Wed, 6 May 2026 09:13:02 +0800 Subject: [PATCH] fix(web): localize toast messages and keep full session counts (#573) Normalize hub toast text in the web client for i18n coverage (including Ready for input notifications) and stop deduplicating session rows by agentSessionId so outline/group counts reflect user-visible sessions. Co-authored-by: Cursor --- web/src/App.tsx | 51 ++++++++++++++++++++++++++++-- web/src/components/SessionList.tsx | 4 +-- web/src/lib/locales/en.ts | 6 ++++ web/src/lib/locales/zh-CN.ts | 6 ++++ web/src/router.tsx | 4 +-- 5 files changed, 64 insertions(+), 7 deletions(-) diff --git a/web/src/App.tsx b/web/src/App.tsx index 0045124e..140f8c42 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -239,14 +239,59 @@ function AppInner() { clearMessageWindow(event.sessionId) void fetchLatestMessages(api, event.sessionId) }, [api, selectedSessionId]) + const translateIncomingToast = useCallback((title: string, body: string): { title: string; body: string } => { + const normalizedTitle = title.trim() + const normalizedBody = body.trim() + + if (normalizedTitle === 'Ready for input') { + const waitingMatch = normalizedBody.match(/^(.+)\s+is waiting in\s+(.+)$/i) + if (waitingMatch) { + const agent = waitingMatch[1]?.trim() ?? '' + const sessionName = waitingMatch[2]?.trim() ?? '' + return { + title: t('toast.ready.title'), + body: t('toast.ready.body', { agent, session: sessionName }) + } + } + return { + title: t('toast.ready.title'), + body: normalizedBody + } + } + + if (normalizedTitle === 'Permission Request') { + return { + title: t('toast.permission.title'), + body: normalizedBody + } + } + + if (normalizedTitle === 'Task completed') { + return { + title: t('toast.task.completed'), + body: normalizedBody + } + } + + if (normalizedTitle === 'Task failed') { + return { + title: t('toast.task.failed'), + body: normalizedBody + } + } + + return { title, body } + }, [t]) + const handleToast = useCallback((event: ToastEvent) => { + const localized = translateIncomingToast(event.data.title, event.data.body) addToast({ - title: event.data.title, - body: event.data.body, + title: localized.title, + body: localized.body, sessionId: event.data.sessionId, url: event.data.url }) - }, [addToast]) + }, [addToast, translateIncomingToast]) const eventSubscription = useMemo(() => { if (selectedSessionId) { diff --git a/web/src/components/SessionList.tsx b/web/src/components/SessionList.tsx index 8d3ad16f..35857aed 100644 --- a/web/src/components/SessionList.tsx +++ b/web/src/components/SessionList.tsx @@ -711,8 +711,8 @@ export function SessionList(props: { } const allSessions = useMemo( - () => deduplicateSessionsByAgentId(props.sessions, selectedSessionId), - [props.sessions, selectedSessionId] + () => props.sessions, + [props.sessions] ) const visibleSessions = useMemo( () => isSearching diff --git a/web/src/lib/locales/en.ts b/web/src/lib/locales/en.ts index 148a1dcc..e4b79bd8 100644 --- a/web/src/lib/locales/en.ts +++ b/web/src/lib/locales/en.ts @@ -274,6 +274,12 @@ export default { // Send blocked 'send.blocked.title': 'Cannot send message', 'send.blocked.noConnection': 'Not connected to server', + 'resume.failed.title': 'Resume failed', + 'toast.ready.title': 'Ready for input', + 'toast.ready.body': '{agent} is waiting in {session}', + 'toast.permission.title': 'Permission Request', + 'toast.task.completed': 'Task completed', + 'toast.task.failed': 'Task failed', // Install prompt 'install.title': 'Install HAPI', diff --git a/web/src/lib/locales/zh-CN.ts b/web/src/lib/locales/zh-CN.ts index c01f97b9..a5e44aa7 100644 --- a/web/src/lib/locales/zh-CN.ts +++ b/web/src/lib/locales/zh-CN.ts @@ -276,6 +276,12 @@ export default { // Send blocked 'send.blocked.title': '无法发送消息', 'send.blocked.noConnection': '未连接到服务器', + 'resume.failed.title': '恢复会话失败', + 'toast.ready.title': '等待输入', + 'toast.ready.body': '{agent} 正在 {session} 等待你的输入', + 'toast.permission.title': '权限请求', + 'toast.task.completed': '任务完成', + 'toast.task.failed': '任务失败', // Install prompt 'install.title': '安装 HAPI', diff --git a/web/src/router.tsx b/web/src/router.tsx index d96c5f88..58e68e57 100644 --- a/web/src/router.tsx +++ b/web/src/router.tsx @@ -276,9 +276,9 @@ function SessionPage() { try { return await api.resumeSession(currentSessionId, { permissionMode: session.permissionMode ?? undefined }) } catch (error) { - const message = error instanceof Error ? error.message : 'Resume failed' + const message = error instanceof Error ? error.message : t('dialog.error.default') addToast({ - title: 'Resume failed', + title: t('resume.failed.title'), body: message, sessionId: currentSessionId, url: ''