fix: localize API error messages
Build and publish Docker images / Build and publish images (push) Successful in 2m35s

This commit is contained in:
2026-09-14 14:40:49 +08:00
parent ac612b92cf
commit ca2a3c2dee
10 changed files with 249 additions and 63 deletions
@@ -71,6 +71,16 @@ export const deleteIndexedDBOnLogout = async () => {
localStorage.removeItem('cw-idb-names');
};
const getSafeLogoutRedirectLink = redirectLink => {
try {
const url = new URL(redirectLink || '/', window.location.origin);
if (url.origin !== window.location.origin) return '/';
return `${url.pathname}${url.search}${url.hash}`;
} catch {
return '/';
}
};
export const clearCookiesOnLogout = () => {
emitter.emit(CHATWOOT_RESET);
emitter.emit(ANALYTICS_RESET);
@@ -78,36 +88,163 @@ export const clearCookiesOnLogout = () => {
clearLocalStorageOnLogout();
clearSessionStorageOnLogout();
const globalConfig = window.globalConfig || {};
const logoutRedirectLink = globalConfig.LOGOUT_REDIRECT_LINK || '/';
window.location = logoutRedirectLink;
const logoutRedirectLink = getSafeLogoutRedirectLink(
globalConfig.LOGOUT_REDIRECT_LINK
);
window.location.assign(logoutRedirectLink);
};
const API_ERROR_MESSAGES = Object.freeze({
400: '请求参数有误,请检查后重试。',
401: '身份验证失败,请重新登录。',
403: '您没有权限执行此操作。',
404: '请求的资源不存在。',
408: '请求超时,请稍后再试。',
409: '操作冲突,请刷新后重试。',
422: '提交的信息有误,请检查后重试。',
429: '操作过于频繁,请稍后再试。',
500: '服务器发生错误,请稍后再试。',
502: '服务暂时不可用,请稍后再试。',
503: '服务暂时不可用,请稍后再试。',
504: '服务暂时不可用,请稍后再试。',
});
const extractStatusFromMessage = message => {
const match = message.match(
/(?:request failed with status code|status code|error)\s*[:=]?\s*([45]\d{2})/i
);
return match ? Number(match[1]) : null;
};
const getStatusMessage = status => {
const numericStatus = Number(status);
if (!Number.isInteger(numericStatus)) return '';
return (
API_ERROR_MESSAGES[numericStatus] ||
(numericStatus >= 500 ? API_ERROR_MESSAGES[500] : '')
);
};
const normalizeAPIErrorText = (message, status) => {
if (typeof message !== 'string') return message;
const text = message.trim();
if (!text) return text;
if (/^(network error|failed to fetch|network request failed)$/i.test(text)) {
return '网络连接失败,请检查网络后重试。';
}
if (/timeout|timed out|超时/i.test(text)) {
return '请求超时,请稍后再试。';
}
if (/^invalid email or password$/i.test(text)) {
return '邮箱或密码错误,请重试。';
}
if (/^email not confirmed\b/i.test(text)) {
return '邮箱尚未验证,请先完成邮箱验证。';
}
if (/^user account is inactive\b/i.test(text)) {
return '账户已停用,请联系管理员。';
}
if (/^this account uses .* authentication/i.test(text)) {
return '此账户使用第三方登录,请通过对应方式登录。';
}
if (
/^(invalid (token|credentials|headers)|unauthorized|unauthenticated|authentication failed)$/i.test(
text
)
) {
return '身份验证失败,请重新登录。';
}
if (/^account not identified$/i.test(text)) {
return '无法识别当前账户,请重新登录。';
}
if (/administrator role required/i.test(text)) {
return '需要管理员权限才能执行此操作。';
}
if (
/^(not authorized|authorization header required|bearer token required|widget_token required)$/i.test(
text
)
) {
return '缺少有效的登录凭证,请重新登录。';
}
if (/^account is suspended$/i.test(text)) {
return '账户已被停用,请联系管理员。';
}
if (/^sso session (expired or invalid|data corrupt)$/i.test(text)) {
return '单点登录会话无效,请重新登录。';
}
if (/^invalid (source )?url/i.test(text)) {
return '请求地址无效,请检查后重试。';
}
const messageStatus = extractStatusFromMessage(text);
if (messageStatus) {
return getStatusMessage(messageStatus) || text;
}
if (
status &&
/^(bad request|unauthorized|unauthenticated|forbidden|not found|request timeout|unprocessable entity|too many requests|internal server error|bad gateway|service unavailable|gateway timeout|invalid credentials|authentication failed)$/i.test(
text
)
) {
return getStatusMessage(status) || text;
}
if (
status >= 500 &&
/\b(error|failed|unavailable|gateway|server)\b/i.test(text)
) {
return getStatusMessage(status) || text;
}
if (/^(axioserror:\s*)?(error:\s*)?\[object object\]$/i.test(text)) {
return '请求失败,请稍后再试。';
}
if (
/^(failed|unable to|could not|cannot|can't|something went wrong|an error occurred)\b/i.test(
text
)
) {
return '请求失败,请稍后再试。';
}
return message;
};
const extractErrorText = value => {
if (typeof value === 'string') return value;
if (value?.message && typeof value.message === 'string') {
return value.message;
}
if (Array.isArray(value)) return extractErrorText(value[0]);
if (value && typeof value === 'object') return JSON.stringify(value);
return '';
};
export const parseAPIErrorResponse = error => {
if (error?.response?.data?.message) {
return error?.response?.data?.message;
if (typeof error === 'string') return normalizeAPIErrorText(error);
const status = error?.response?.status ?? error?.status;
if (error?.code === 'ERR_NETWORK') {
return '网络连接失败,请检查网络后重试。';
}
if (error?.response?.data?.error) {
// error could be a string or an object with a message property
if (typeof error.response.data.error === 'string') {
return error.response.data.error;
}
if (error.response.data.error?.message) {
return error.response.data.error.message;
}
return JSON.stringify(error.response.data.error);
if (error?.code === 'ECONNABORTED') {
return '请求超时,请稍后再试。';
}
if (error?.response?.data?.errors) {
const first = error.response.data.errors;
if (Array.isArray(first)) {
return first[0];
}
return first;
}
// Fallback: return a useful default message rather than the raw error object
if (error?.message && typeof error.message === 'string') {
return error.message;
}
return 'Request failed. Please try again.';
const data = error?.response?.data;
const message = extractErrorText(data?.message);
if (message) return normalizeAPIErrorText(message, status);
const apiError = extractErrorText(data?.error);
if (apiError) return normalizeAPIErrorText(apiError, status);
const errors = extractErrorText(data?.errors);
if (errors) return normalizeAPIErrorText(errors, status);
const errorMessage = extractErrorText(error?.message);
if (errorMessage) return normalizeAPIErrorText(errorMessage, status);
return getStatusMessage(status) || '请求失败,请稍后再试。';
};
export const throwErrorMessage = error => {