a14fbb3df3
凭据轮询在 SSH_STRICT 默认 true 时因 HostKeyNotVerifiable 在认证前失败; 探测始终 known_hosts=None,默认 strict 改为 false,错误信息统一中文。 Co-authored-by: Cursor <cursoragent@cursor.com>
86 lines
4.4 KiB
Python
86 lines
4.4 KiB
Python
"""Translate rsync/SSH push error text to Chinese for sync_logs and API responses."""
|
||
|
||
from __future__ import annotations
|
||
|
||
import re
|
||
|
||
# Order matters: more specific patterns first.
|
||
_PATTERNS: list[tuple[re.Pattern[str], str]] = [
|
||
(re.compile(r"rsync:\s*connection unexpectedly closed", re.I), "rsync:连接意外关闭"),
|
||
(re.compile(r"rsync error:\s*some files/attrs were not transferred", re.I), "rsync 错误:部分文件或属性未能传输"),
|
||
(re.compile(r"rsync error:\s*error in rsync protocol data stream", re.I), "rsync 错误:协议数据流异常"),
|
||
(re.compile(r"rsync error:\s*", re.I), "rsync 错误:"),
|
||
(re.compile(r"rsync:\s*", re.I), "rsync:"),
|
||
(re.compile(r"Host key is not trusted for host", re.I), "SSH 主机密钥未信任(未知主机)"),
|
||
(re.compile(r"Host key verification failed", re.I), "SSH 主机密钥验证失败"),
|
||
(re.compile(r"Permission denied for user .+ on host", re.I), "SSH 认证失败:用户名或密码/密钥不正确"),
|
||
(re.compile(r"Login failed for user", re.I), "SSH 登录失败:用户名或密码/密钥不正确"),
|
||
(re.compile(r"Key exchange failed", re.I), "SSH 密钥交换失败"),
|
||
(re.compile(r"Disconnect reason:", re.I), "SSH 连接断开:"),
|
||
(re.compile(r"OpenSSH protocol error", re.I), "SSH 协议错误"),
|
||
(re.compile(r"Connection lost", re.I), "SSH 连接丢失"),
|
||
(re.compile(r"Connection closed", re.I), "SSH 连接已关闭"),
|
||
(re.compile(r"Could not resolve hostname", re.I), "无法解析主机名"),
|
||
(re.compile(r"Permission denied,\s*please try again", re.I), "权限被拒绝,请重试"),
|
||
(re.compile(r"Permission denied\s*\(publickey", re.I), "公钥认证失败,权限被拒绝"),
|
||
(re.compile(r"Permission denied", re.I), "权限被拒绝"),
|
||
(re.compile(r"No such file or directory", re.I), "文件或目录不存在"),
|
||
(re.compile(r"Connection refused", re.I), "连接被拒绝"),
|
||
(re.compile(r"Connection timed out", re.I), "连接超时"),
|
||
(re.compile(r"Connection reset by peer", re.I), "连接被对端重置"),
|
||
(re.compile(r"Network is unreachable", re.I), "网络不可达"),
|
||
(re.compile(r"No route to host", re.I), "无法路由到主机"),
|
||
(re.compile(r"No space left on device", re.I), "磁盘空间不足"),
|
||
(re.compile(r"Read-only file system", re.I), "文件系统为只读"),
|
||
(re.compile(r"Operation timed out", re.I), "操作超时"),
|
||
(re.compile(r"Operation not permitted", re.I), "操作不允许"),
|
||
(re.compile(r"Authentication failed", re.I), "认证失败"),
|
||
(re.compile(r"Broken pipe", re.I), "连接中断(管道破裂)"),
|
||
(re.compile(r"Protocol error", re.I), "协议错误"),
|
||
(re.compile(r"Too many open files", re.I), "打开的文件过多"),
|
||
(re.compile(r"Name or service not known", re.I), "无法解析主机名或服务名"),
|
||
(re.compile(r"ssh:\s*connect to host", re.I), "ssh:无法连接到主机"),
|
||
(re.compile(r"ssh:\s*", re.I), "ssh:"),
|
||
(re.compile(r"Command failed\s*\(exit\s*(\d+)\)", re.I), r"命令失败(退出码 \1)"),
|
||
(re.compile(r"rsync exited with code\s*(\d+)", re.I), r"rsync 退出码 \1"),
|
||
(re.compile(r"\bexit code\s*(\d+)", re.I), r"退出码 \1"),
|
||
(re.compile(r"Server not found", re.I), "服务器不存在"),
|
||
(re.compile(r"failed to set times on", re.I), "无法设置文件时间戳:"),
|
||
(re.compile(r"sender", re.I), "发送端"),
|
||
(re.compile(r"receiver", re.I), "接收端"),
|
||
]
|
||
|
||
_CJK_RE = re.compile(r"[\u4e00-\u9fff\u3400-\u4dbf]")
|
||
|
||
|
||
def _is_primarily_chinese(text: str) -> bool:
|
||
cjk = len(_CJK_RE.findall(text))
|
||
if cjk == 0:
|
||
return False
|
||
# Heuristic: any CJK and CJK chars >= 20% of non-whitespace length
|
||
compact = re.sub(r"\s+", "", text)
|
||
if not compact:
|
||
return False
|
||
return cjk / len(compact) >= 0.2
|
||
|
||
|
||
def translate_ssh_error_message(message: str | None) -> str | None:
|
||
"""Return a Chinese-localized SSH/rsync error string, or the original if already Chinese."""
|
||
return translate_sync_error_message(message)
|
||
|
||
|
||
def translate_sync_error_message(message: str | None) -> str | None:
|
||
"""Return a Chinese-localized push/sync error string, or the original if already Chinese."""
|
||
if message is None:
|
||
return None
|
||
text = message.strip()
|
||
if not text:
|
||
return message
|
||
if _is_primarily_chinese(text):
|
||
return message
|
||
|
||
result = text
|
||
for pattern, repl in _PATTERNS:
|
||
result = pattern.sub(repl, result)
|
||
return result
|