117 lines
4.6 KiB
Python
117 lines
4.6 KiB
Python
|
|
"""Translate HTTPException detail strings to Chinese for admin-facing API responses."""
|
|||
|
|
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
import re
|
|||
|
|
from typing import Any
|
|||
|
|
|
|||
|
|
# Exact English literals from server/api (and shared services)
|
|||
|
|
_DETAIL_EXACT: dict[str, str] = {
|
|||
|
|
"Not found": "不存在",
|
|||
|
|
"Server not found": "服务器不存在",
|
|||
|
|
"Platform not found": "平台不存在",
|
|||
|
|
"Node not found": "节点不存在",
|
|||
|
|
"Admin not found": "管理员不存在",
|
|||
|
|
"Setting not found": "设置项不存在",
|
|||
|
|
"API_KEY not found": "API_KEY 不存在",
|
|||
|
|
"Schedule not found": "调度任务不存在",
|
|||
|
|
"Preset not found": "凭据预设不存在",
|
|||
|
|
"SSH key preset not found": "SSH 密钥预设不存在",
|
|||
|
|
"telegram_bot_token not found": "telegram_bot_token 不存在",
|
|||
|
|
"Retry job not found": "重试任务不存在",
|
|||
|
|
"Credential not found": "凭据不存在",
|
|||
|
|
"Execution not found": "执行记录不存在",
|
|||
|
|
"Script not found": "脚本不存在",
|
|||
|
|
"Login failed": "登录失败",
|
|||
|
|
"Missing refresh token": "缺少刷新令牌",
|
|||
|
|
"Invalid refresh token": "刷新令牌无效",
|
|||
|
|
"Missing Authorization header": "缺少 Authorization 请求头",
|
|||
|
|
"Invalid or expired token": "令牌无效或已过期",
|
|||
|
|
"Can only setup TOTP for yourself": "只能为自己设置 TOTP",
|
|||
|
|
"Can only enable TOTP for yourself": "只能为自己启用 TOTP",
|
|||
|
|
"Can only disable TOTP for yourself": "只能为自己禁用 TOTP",
|
|||
|
|
"Setup failed": "设置失败",
|
|||
|
|
"Enable failed": "启用失败",
|
|||
|
|
"Disable failed": "禁用失败",
|
|||
|
|
"Server has no domain configured": "服务器未配置域名",
|
|||
|
|
"Missing API key": "缺少 API Key",
|
|||
|
|
"Invalid API key for this server": "该服务器的 API Key 无效",
|
|||
|
|
"Invalid or expired job": "任务无效或已过期",
|
|||
|
|
"new_path required for rename": "重命名需要指定 new_path",
|
|||
|
|
"Invalid operation": "无效操作",
|
|||
|
|
"server_id required": "server_id 必填",
|
|||
|
|
"Too many script callback requests": "脚本回调请求过于频繁",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# AuthService ``reason`` codes → user-facing Chinese (when ``message`` absent)
|
|||
|
|
_AUTH_REASON_ZH: dict[str, str] = {
|
|||
|
|
"admin_not_found": "管理员不存在",
|
|||
|
|
"no_secret": "请先完成 TOTP 设置",
|
|||
|
|
"invalid_totp": "TOTP 验证码错误",
|
|||
|
|
"invalid_password": "当前密码错误",
|
|||
|
|
"totp_required": "需要 TOTP 验证码",
|
|||
|
|
"invalid_token": "令牌无效或已过期",
|
|||
|
|
"invalid_credentials": "用户名或密码错误",
|
|||
|
|
"account_locked": "登录尝试过多,请 15 分钟后重试",
|
|||
|
|
"account_disabled": "账户已被禁用",
|
|||
|
|
"ip_blocked": "拒绝访问",
|
|||
|
|
"service_unavailable": "认证服务暂不可用,请稍后重试",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_DETAIL_PATTERNS: list[tuple[re.Pattern[str], str]] = [
|
|||
|
|
(
|
|||
|
|
re.compile(r"^Command failed \(exit (\d+)\)$"),
|
|||
|
|
r"命令失败(退出码 \1)",
|
|||
|
|
),
|
|||
|
|
(
|
|||
|
|
re.compile(
|
|||
|
|
r"^Setting '([^']+)' is immutable and cannot be modified via API$"
|
|||
|
|
),
|
|||
|
|
r"设置项「\1」不可通过 API 修改",
|
|||
|
|
),
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _looks_chinese(text: str) -> bool:
|
|||
|
|
return any("\u4e00" <= ch <= "\u9fff" for ch in text)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def translate_http_detail_str(msg: str) -> str:
|
|||
|
|
"""Return Chinese detail when a known translation exists."""
|
|||
|
|
if not msg or _looks_chinese(msg):
|
|||
|
|
return msg
|
|||
|
|
if msg in _DETAIL_EXACT:
|
|||
|
|
return _DETAIL_EXACT[msg]
|
|||
|
|
for pattern, repl in _DETAIL_PATTERNS:
|
|||
|
|
if pattern.fullmatch(msg):
|
|||
|
|
return pattern.sub(repl, msg)
|
|||
|
|
return msg
|
|||
|
|
|
|||
|
|
|
|||
|
|
def auth_failure_detail(result: dict[str, Any], *, fallback: str = "操作失败") -> str:
|
|||
|
|
"""Map AuthService failure dict to Chinese ``HTTPException.detail``."""
|
|||
|
|
msg = result.get("message")
|
|||
|
|
if isinstance(msg, str) and msg.strip():
|
|||
|
|
return translate_http_detail_str(msg)
|
|||
|
|
reason = str(result.get("reason") or "")
|
|||
|
|
if reason in _AUTH_REASON_ZH:
|
|||
|
|
return _AUTH_REASON_ZH[reason]
|
|||
|
|
return fallback if _looks_chinese(fallback) else translate_http_detail_str(fallback)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def translate_http_detail(detail: Any) -> Any:
|
|||
|
|
"""Translate ``HTTPException.detail`` (str | list | dict) for JSON responses."""
|
|||
|
|
if isinstance(detail, str):
|
|||
|
|
return translate_http_detail_str(detail)
|
|||
|
|
if isinstance(detail, list):
|
|||
|
|
return [translate_http_detail(item) for item in detail]
|
|||
|
|
if isinstance(detail, dict):
|
|||
|
|
out: dict[Any, Any] = {}
|
|||
|
|
for key, value in detail.items():
|
|||
|
|
if key in ("msg", "message") and isinstance(value, str):
|
|||
|
|
out[key] = translate_http_detail_str(value)
|
|||
|
|
else:
|
|||
|
|
out[key] = value
|
|||
|
|
return out
|
|||
|
|
return detail
|