// errors.ts — 错误码 → 多语文案映射表(阶段 B 落地) import { ApiError } from './types'; import type { Lang } from '../i18n'; /** * 后端统一返回 {code, message_zh, message_en};前端优先用本表(按 code 命中当前语言), * 再回退后端文案(en 优先),最后 unknown。codes 与 #1 openapi 对齐。 */ export const ERROR_TEXT: Record = { invalid_credentials: { zh: '邮箱或密码错误', en: 'Wrong email or password', ja: 'メールアドレスまたはパスワードが違います', ko: '이메일 또는 비밀번호가 올바르지 않습니다', ru: 'Неверная почта или пароль', es: 'Correo o contraseña incorrectos' }, rate_limited: { zh: '操作过于频繁,请稍后再试', en: 'Too many requests, slow down', ja: 'リクエストが多すぎます。少し時間をおいてください', ko: '요청이 너무 많습니다. 잠시 후 다시 시도하세요', ru: 'Слишком много запросов, помедленнее', es: 'Demasiadas solicitudes, ve más despacio' }, account_locked: { zh: '失败次数过多,账户已临时锁定', en: 'Too many attempts — account temporarily locked', ja: '試行回数が多すぎます — アカウントを一時的にロックしました', ko: '시도 횟수가 너무 많습니다 — 계정이 일시적으로 잠겼습니다', ru: 'Слишком много попыток — аккаунт временно заблокирован', es: 'Demasiados intentos: la cuenta está bloqueada temporalmente' }, totp_required: { zh: '需要双重认证动态码', en: 'Two-factor code required', ja: '二要素認証コードが必要です', ko: '2단계 인증 코드가 필요합니다', ru: 'Требуется код двухфакторной аутентификации', es: 'Se requiere el código de verificación en dos pasos' }, totp_invalid: { zh: '动态码不正确,请重试', en: 'Invalid code, try again', ja: 'コードが正しくありません。もう一度お試しください', ko: '코드가 올바르지 않습니다. 다시 시도하세요', ru: 'Неверный код, попробуйте снова', es: 'Código no válido, inténtalo de nuevo' }, code_invalid: { zh: '激活码无效', en: 'Invalid activation code', ja: 'アクティベーションコードが無効です', ko: '인증 코드가 유효하지 않습니다', ru: 'Недействительный код активации', es: 'Código de activación no válido' }, code_used: { zh: '激活码已被使用', en: 'Activation code already used', ja: 'アクティベーションコードは使用済みです', ko: '이미 사용된 인증 코드입니다', ru: 'Код активации уже использован', es: 'El código de activación ya se ha usado' }, code_expired: { zh: '激活码已过期', en: 'Activation code expired', ja: 'アクティベーションコードの有効期限が切れています', ko: '인증 코드가 만료되었습니다', ru: 'Срок действия кода активации истёк', es: 'El código de activación ha caducado' }, device_limit: { zh: '设备数量已达上限', en: 'Device limit reached', ja: 'デバイス数が上限に達しました', ko: '기기 수가 한도에 도달했습니다', ru: 'Достигнут лимит устройств', es: 'Se alcanzó el límite de dispositivos' }, device_not_found: { zh: '设备不存在', en: 'Device not found', ja: 'デバイスが見つかりません', ko: '기기를 찾을 수 없습니다', ru: 'Устройство не найдено', es: 'Dispositivo no encontrado' }, unauthorized: { zh: '登录已失效,请重新登录', en: 'Session expired, please log in again', ja: 'セッションの有効期限が切れました。再度ログインしてください', ko: '세션이 만료되었습니다. 다시 로그인하세요', ru: 'Сеанс истёк, войдите снова', es: 'La sesión ha caducado, inicia sesión de nuevo' }, 'auth.ticket_invalid': { zh: '登录票据无效或已过期,请重新从 App 打开', en: 'Login ticket is invalid or expired, please reopen from the app', ja: 'ログインチケットが無効か期限切れです。アプリから開き直してください', ko: '로그인 티켓이 유효하지 않거나 만료되었습니다. 앱에서 다시 여세요', ru: 'Токен входа недействителен или истёк, откройте снова из приложения', es: 'El ticket de inicio de sesión no es válido o ha caducado, vuelve a abrirlo desde la app' }, network: { zh: '网络异常,请稍后重试', en: 'Network error, please retry', ja: 'ネットワークエラーです。後でもう一度お試しください', ko: '네트워크 오류입니다. 잠시 후 다시 시도하세요', ru: 'Ошибка сети, повторите попытку', es: 'Error de red, vuelve a intentarlo' }, unknown: { zh: '操作失败,请稍后重试', en: 'Something went wrong, please retry', ja: '問題が発生しました。後でもう一度お試しください', ko: '문제가 발생했습니다. 잠시 후 다시 시도하세요', ru: 'Что-то пошло не так, повторите попытку', es: 'Algo salió mal, vuelve a intentarlo' }, }; /** 把任意错误转成当前语言文案(按 code 查表→后端文案 en 优先→通用兜底) */ export function bilingual(err: unknown, lang: Lang): string { if (err instanceof ApiError) { const m = ERROR_TEXT[err.code]; return m?.[lang] ?? m?.en ?? err.message_en ?? err.message_zh ?? ERROR_TEXT.unknown[lang]; } return ERROR_TEXT.unknown[lang]; }