From 3790392dff27ed9d64f3f30935634befcdfbe546 Mon Sep 17 00:00:00 2001 From: dolphin Date: Thu, 23 Jul 2026 22:37:51 +0800 Subject: [PATCH] fix(frontend): generic fallback at every error-copy exit (ledger #11) - new api_errors `fallback` key (3 languages); naked i18n keys can no longer reach users when a code has no translation (gateway codes, pre-CI legacy) - client (6 exits): SSE/WS/chat-input/route/request-layer fall back to generic Chinese; t(key, vars) kept so {{xxx}} templates still interpolate - platform (5 exits): fall back status_message -> generic copy (admin keeps backend diagnostics); chatErrorMessage's literal "error" fallback removed --- src/frontend/client/src/api/request.ts | 9 ++++++++- src/frontend/client/src/hooks/SSE/useSSE.ts | 2 +- src/frontend/client/src/locales/en/api_errors.gen.json | 3 ++- src/frontend/client/src/locales/ja/api_errors.gen.json | 3 ++- .../client/src/locales/zh-Hans/api_errors.gen.json | 3 ++- src/frontend/client/src/pages/appChat/ChatInput.tsx | 2 +- src/frontend/client/src/pages/appChat/useWebsocket.ts | 2 +- src/frontend/client/src/routes/ChatRoute.tsx | 2 +- src/frontend/packages/locales/src/api_errors/en.json | 3 ++- src/frontend/packages/locales/src/api_errors/ja.json | 3 ++- .../packages/locales/src/api_errors/zh-Hans.json | 3 ++- .../platform/public/locales/en-US/api_errors.json | 3 ++- src/frontend/platform/public/locales/ja/api_errors.json | 3 ++- .../platform/public/locales/zh-Hans/api_errors.json | 3 ++- src/frontend/platform/src/App.tsx | 2 +- .../components/bs-comp/chatComponent/chatErrorMessage.ts | 2 +- .../src/pages/BuildPage/flow/FlowChat/ChatInput.tsx | 2 +- src/frontend/platform/src/pages/BuildPage/hook.ts | 2 +- .../src/pages/KnowledgePage/components/Files.tsx | 2 +- 19 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/frontend/client/src/api/request.ts b/src/frontend/client/src/api/request.ts index e04d638ce..0cf4c24be 100644 --- a/src/frontend/client/src/api/request.ts +++ b/src/frontend/client/src/api/request.ts @@ -121,7 +121,14 @@ export const translateApiErrorMessage = (data: any) => { if (statusMessageKey && i18next.exists(statusMessageKey)) { return i18next.t(statusMessageKey, data?.data); } - return statusMessage || (statusCodeKey ? i18next.t(statusCodeKey, data?.data) : ""); + // Last resort keeps t(key, vars) so {{xxx}} templates still interpolate from + // data.data; defaultValue guards the truly-untranslated case — never the raw key. + return ( + statusMessage || + (statusCodeKey + ? String(i18next.t(statusCodeKey, { ...(data?.data || {}), defaultValue: String(i18next.t("api_errors.fallback")) })) + : "") + ); }; // License degradation (gateway returns 11001): throttle the toast so a burst of diff --git a/src/frontend/client/src/hooks/SSE/useSSE.ts b/src/frontend/client/src/hooks/SSE/useSSE.ts index 922a0c3e3..aecb1e080 100644 --- a/src/frontend/client/src/hooks/SSE/useSSE.ts +++ b/src/frontend/client/src/hooks/SSE/useSSE.ts @@ -227,7 +227,7 @@ export default function useSSE( // localize const _data = { - text: localize(`api_errors.${data?.status_code}`, data?.data), + text: localize(`api_errors.${data?.status_code}`, { ...(data?.data || {}), defaultValue: localize('api_errors.fallback') }), } errorHandler({ data: _data, submission: { ...submission, userMessage } as EventSubmission }); }); diff --git a/src/frontend/client/src/locales/en/api_errors.gen.json b/src/frontend/client/src/locales/en/api_errors.gen.json index acffce142..e2b338e82 100644 --- a/src/frontend/client/src/locales/en/api_errors.gen.json +++ b/src/frontend/client/src/locales/en/api_errors.gen.json @@ -392,5 +392,6 @@ "24005": "Invalid quota_config or department_id value", "90001": "You do not have permission to access the admin backend. Please contact the administrator to request access if needed.", "90002": "Your current role does not have permission to access the workbench. Please contact the administrator if needed.", - "personIdAlreadyExists": "Person ID already exists" + "personIdAlreadyExists": "Person ID already exists", + "fallback": "The operation failed. Please try again later." } diff --git a/src/frontend/client/src/locales/ja/api_errors.gen.json b/src/frontend/client/src/locales/ja/api_errors.gen.json index ecb1161c5..55b561a26 100644 --- a/src/frontend/client/src/locales/ja/api_errors.gen.json +++ b/src/frontend/client/src/locales/ja/api_errors.gen.json @@ -392,5 +392,6 @@ "24005": "quota_configまたはdepartment_idの値が無効です。", "90001": "現在のアカウントには管理バックエンドへのアクセス権限がありません。必要な場合は、管理者に連絡して権限を開通してください。", "90002": "現在のロールにはワークベンチへのアクセス権限がありません。必要な場合は管理者に連絡してください。", - "personIdAlreadyExists": "Person ID は既に存在します" + "personIdAlreadyExists": "Person ID は既に存在します", + "fallback": "操作に失敗しました。しばらくしてから再試行してください。" } diff --git a/src/frontend/client/src/locales/zh-Hans/api_errors.gen.json b/src/frontend/client/src/locales/zh-Hans/api_errors.gen.json index 531038730..2f7f6b92d 100644 --- a/src/frontend/client/src/locales/zh-Hans/api_errors.gen.json +++ b/src/frontend/client/src/locales/zh-Hans/api_errors.gen.json @@ -392,5 +392,6 @@ "24005": "quota_config 或 department_id 配置无效。", "90001": "您当前角色没有访问管理后台的权限。如有需要,请联系管理员开通。", "90002": "您当前角色没有访问工作台的权限。如有需要,请联系管理员开通。", - "personIdAlreadyExists": "人员 ID 已存在" + "personIdAlreadyExists": "人员 ID 已存在", + "fallback": "操作失败,请稍后重试" } diff --git a/src/frontend/client/src/pages/appChat/ChatInput.tsx b/src/frontend/client/src/pages/appChat/ChatInput.tsx index 2b98bec25..10eeb852f 100644 --- a/src/frontend/client/src/pages/appChat/ChatInput.tsx +++ b/src/frontend/client/src/pages/appChat/ChatInput.tsx @@ -37,7 +37,7 @@ export default function ChatInput({ readOnly, v }) { // ... Placeholder 和 AutoFocus 逻辑保持不变 ... const placholder = useMemo(() => { return inputDisabled ? - (inputMsg.code ? localize(`api_errors.${inputMsg.code}`, inputMsg.data) : ' ') + (inputMsg.code ? localize(`api_errors.${inputMsg.code}`, { ...(inputMsg.data || {}), defaultValue: localize('api_errors.fallback') }) : ' ') : localize('com_ui_please_enter_question') }, [inputDisabled, inputMsg, localize]); diff --git a/src/frontend/client/src/pages/appChat/useWebsocket.ts b/src/frontend/client/src/pages/appChat/useWebsocket.ts index bf0f47acf..1fbdc4550 100644 --- a/src/frontend/client/src/pages/appChat/useWebsocket.ts +++ b/src/frontend/client/src/pages/appChat/useWebsocket.ts @@ -204,7 +204,7 @@ export const useWebSocket = (helpers) => { } if (![10421, 13002].includes(code)) { showToast({ - message: code === 500 ? message : localize(`api_errors.${String(code)}`, data.message?.data), + message: code === 500 ? message : localize(`api_errors.${String(code)}`, { ...(data.message?.data || {}), defaultValue: localize('api_errors.fallback') }), severity: NotificationSeverity.ERROR, }) } else { diff --git a/src/frontend/client/src/routes/ChatRoute.tsx b/src/frontend/client/src/routes/ChatRoute.tsx index 090de9dc6..a20c3ace2 100644 --- a/src/frontend/client/src/routes/ChatRoute.tsx +++ b/src/frontend/client/src/routes/ChatRoute.tsx @@ -55,7 +55,7 @@ const useErrorPrompt = () => { useEffect(() => { if (error) { - showToast({ message: localize(`api_errors.${error}`), status: 'error' }); + showToast({ message: localize(`api_errors.${error}`, { defaultValue: localize('api_errors.fallback') }), status: 'error' }); } }, []); }; \ No newline at end of file diff --git a/src/frontend/packages/locales/src/api_errors/en.json b/src/frontend/packages/locales/src/api_errors/en.json index acffce142..e2b338e82 100644 --- a/src/frontend/packages/locales/src/api_errors/en.json +++ b/src/frontend/packages/locales/src/api_errors/en.json @@ -392,5 +392,6 @@ "24005": "Invalid quota_config or department_id value", "90001": "You do not have permission to access the admin backend. Please contact the administrator to request access if needed.", "90002": "Your current role does not have permission to access the workbench. Please contact the administrator if needed.", - "personIdAlreadyExists": "Person ID already exists" + "personIdAlreadyExists": "Person ID already exists", + "fallback": "The operation failed. Please try again later." } diff --git a/src/frontend/packages/locales/src/api_errors/ja.json b/src/frontend/packages/locales/src/api_errors/ja.json index ecb1161c5..55b561a26 100644 --- a/src/frontend/packages/locales/src/api_errors/ja.json +++ b/src/frontend/packages/locales/src/api_errors/ja.json @@ -392,5 +392,6 @@ "24005": "quota_configまたはdepartment_idの値が無効です。", "90001": "現在のアカウントには管理バックエンドへのアクセス権限がありません。必要な場合は、管理者に連絡して権限を開通してください。", "90002": "現在のロールにはワークベンチへのアクセス権限がありません。必要な場合は管理者に連絡してください。", - "personIdAlreadyExists": "Person ID は既に存在します" + "personIdAlreadyExists": "Person ID は既に存在します", + "fallback": "操作に失敗しました。しばらくしてから再試行してください。" } diff --git a/src/frontend/packages/locales/src/api_errors/zh-Hans.json b/src/frontend/packages/locales/src/api_errors/zh-Hans.json index 531038730..2f7f6b92d 100644 --- a/src/frontend/packages/locales/src/api_errors/zh-Hans.json +++ b/src/frontend/packages/locales/src/api_errors/zh-Hans.json @@ -392,5 +392,6 @@ "24005": "quota_config 或 department_id 配置无效。", "90001": "您当前角色没有访问管理后台的权限。如有需要,请联系管理员开通。", "90002": "您当前角色没有访问工作台的权限。如有需要,请联系管理员开通。", - "personIdAlreadyExists": "人员 ID 已存在" + "personIdAlreadyExists": "人员 ID 已存在", + "fallback": "操作失败,请稍后重试" } diff --git a/src/frontend/platform/public/locales/en-US/api_errors.json b/src/frontend/platform/public/locales/en-US/api_errors.json index acffce142..e2b338e82 100644 --- a/src/frontend/platform/public/locales/en-US/api_errors.json +++ b/src/frontend/platform/public/locales/en-US/api_errors.json @@ -392,5 +392,6 @@ "24005": "Invalid quota_config or department_id value", "90001": "You do not have permission to access the admin backend. Please contact the administrator to request access if needed.", "90002": "Your current role does not have permission to access the workbench. Please contact the administrator if needed.", - "personIdAlreadyExists": "Person ID already exists" + "personIdAlreadyExists": "Person ID already exists", + "fallback": "The operation failed. Please try again later." } diff --git a/src/frontend/platform/public/locales/ja/api_errors.json b/src/frontend/platform/public/locales/ja/api_errors.json index ecb1161c5..55b561a26 100644 --- a/src/frontend/platform/public/locales/ja/api_errors.json +++ b/src/frontend/platform/public/locales/ja/api_errors.json @@ -392,5 +392,6 @@ "24005": "quota_configまたはdepartment_idの値が無効です。", "90001": "現在のアカウントには管理バックエンドへのアクセス権限がありません。必要な場合は、管理者に連絡して権限を開通してください。", "90002": "現在のロールにはワークベンチへのアクセス権限がありません。必要な場合は管理者に連絡してください。", - "personIdAlreadyExists": "Person ID は既に存在します" + "personIdAlreadyExists": "Person ID は既に存在します", + "fallback": "操作に失敗しました。しばらくしてから再試行してください。" } diff --git a/src/frontend/platform/public/locales/zh-Hans/api_errors.json b/src/frontend/platform/public/locales/zh-Hans/api_errors.json index 531038730..2f7f6b92d 100644 --- a/src/frontend/platform/public/locales/zh-Hans/api_errors.json +++ b/src/frontend/platform/public/locales/zh-Hans/api_errors.json @@ -392,5 +392,6 @@ "24005": "quota_config 或 department_id 配置无效。", "90001": "您当前角色没有访问管理后台的权限。如有需要,请联系管理员开通。", "90002": "您当前角色没有访问工作台的权限。如有需要,请联系管理员开通。", - "personIdAlreadyExists": "人员 ID 已存在" + "personIdAlreadyExists": "人员 ID 已存在", + "fallback": "操作失败,请稍后重试" } diff --git a/src/frontend/platform/src/App.tsx b/src/frontend/platform/src/App.tsx index e53bfd8fb..9f850e1d5 100644 --- a/src/frontend/platform/src/App.tsx +++ b/src/frontend/platform/src/App.tsx @@ -185,7 +185,7 @@ export default function App() { useEffect(() => { if (window.url_error) { toast({ - description: t(`api_errors:${window.url_error}`, { defaultValue: String(window.url_error) }), + description: t(`api_errors:${window.url_error}`, { defaultValue: t('api_errors:fallback') }), variant: 'error', }); delete window.url_error diff --git a/src/frontend/platform/src/components/bs-comp/chatComponent/chatErrorMessage.ts b/src/frontend/platform/src/components/bs-comp/chatComponent/chatErrorMessage.ts index d71515bc7..9edf98517 100644 --- a/src/frontend/platform/src/components/bs-comp/chatComponent/chatErrorMessage.ts +++ b/src/frontend/platform/src/components/bs-comp/chatComponent/chatErrorMessage.ts @@ -33,7 +33,7 @@ export function resolveChatErrorMessage( payload: ChatErrorPayload | null | undefined, translate: Translate, ): string { - const fallbackMessage = payload?.status_message || "error"; + const fallbackMessage = payload?.status_message || String(translate("api_errors:fallback") ?? ""); const statusCode = payload?.status_code; const interpolationParams = diff --git a/src/frontend/platform/src/pages/BuildPage/flow/FlowChat/ChatInput.tsx b/src/frontend/platform/src/pages/BuildPage/flow/FlowChat/ChatInput.tsx index c39cc442d..db0ccff5b 100644 --- a/src/frontend/platform/src/pages/BuildPage/flow/FlowChat/ChatInput.tsx +++ b/src/frontend/platform/src/pages/BuildPage/flow/FlowChat/ChatInput.tsx @@ -283,7 +283,7 @@ export default function ChatInput({ autoRun, version, clear, form, wsUrl, onBefo } // 记录 - const errorMsg = status_code == 500 ? status_message || data.message.message : t(`api_errors:${status_code}`, params) + const errorMsg = status_code == 500 ? status_message || data.message.message : t(`api_errors:${status_code}`, { ...(params || {}), defaultValue: status_message || t('api_errors:fallback') }) addNotification({ type: 'error', title: 'runtime error', diff --git a/src/frontend/platform/src/pages/BuildPage/hook.ts b/src/frontend/platform/src/pages/BuildPage/hook.ts index 5c8869b92..74bda2fbd 100644 --- a/src/frontend/platform/src/pages/BuildPage/hook.ts +++ b/src/frontend/platform/src/pages/BuildPage/hook.ts @@ -64,7 +64,7 @@ export const useErrorPrompt = () => { useEffect(() => { if (error) { - toast({ description: t(`api_errors:${error}`), variant: 'error' }); + toast({ description: t(`api_errors:${error}`, { defaultValue: t('api_errors:fallback') }), variant: 'error' }); // Clear the 'error' parameter from the URL const newUrl = window.location.origin + window.location.pathname; diff --git a/src/frontend/platform/src/pages/KnowledgePage/components/Files.tsx b/src/frontend/platform/src/pages/KnowledgePage/components/Files.tsx index 997e43cb6..0f39762ad 100644 --- a/src/frontend/platform/src/pages/KnowledgePage/components/Files.tsx +++ b/src/frontend/platform/src/pages/KnowledgePage/components/Files.tsx @@ -93,7 +93,7 @@ export const StatusIndicator: React.FC = ({ status, remark } const mediaMessage = formatMediaParseFailureMessage(obj, t); if (mediaMessage) return mediaMessage; - return t(`api_errors:${obj.status_code}`, obj.data) + return t(`api_errors:${obj.status_code}`, { ...(obj.data || {}), defaultValue: obj.status_message || t('api_errors:fallback') }) } catch (error) { return trimmedRemark }