diff --git a/internal/web/service/email/email.go b/internal/web/service/email/email.go index 5bcbdab7f..7031183fa 100644 --- a/internal/web/service/email/email.go +++ b/internal/web/service/email/email.go @@ -321,28 +321,30 @@ func (s *EmailService) SendTest() error { ) } -// classifySMTPError maps raw SMTP errors to human-readable messages. +// classifySMTPError maps a raw SMTP error to an i18n key. The key is returned +// unprefixed: the caller renders it under "pages.settings.", as does every +// other Message this file produces. func classifySMTPError(err error) string { msg := err.Error() msgLower := strings.ToLower(msg) switch { case strings.Contains(msg, "535") || strings.Contains(msgLower, "authentication"): - return "pages.settings.smtpErrorAuth" + return "smtpErrorAuth" case strings.Contains(msg, "534") || strings.Contains(msgLower, "starttls"): - return "pages.settings.smtpErrorStarttls" + return "smtpErrorStarttls" case strings.Contains(msg, "465") || strings.Contains(msgLower, "tls"): - return "pages.settings.smtpErrorTls" + return "smtpErrorTls" case strings.Contains(msgLower, "connection refused") || strings.Contains(msgLower, "dial"): - return "pages.settings.smtpErrorRefused" + return "smtpErrorRefused" case strings.Contains(msgLower, "timeout"): - return "pages.settings.smtpErrorTimeout" + return "smtpErrorTimeout" case strings.Contains(msg, "550") || strings.Contains(msgLower, "relay"): - return "pages.settings.smtpErrorRelay" + return "smtpErrorRelay" case strings.Contains(msgLower, "eof"): - return "pages.settings.smtpErrorEof" + return "smtpErrorEof" default: - return fmt.Sprintf("pages.settings.smtpErrorUnknown: %s", msg) + return "smtpErrorUnknown" } } diff --git a/internal/web/service/email/email_test.go b/internal/web/service/email/email_test.go index d32ad3fff..3c56498a5 100644 --- a/internal/web/service/email/email_test.go +++ b/internal/web/service/email/email_test.go @@ -2,6 +2,7 @@ package email import ( "bufio" + "errors" "fmt" "io" "mime" @@ -222,3 +223,33 @@ func TestBuildMessageStripsHeaderInjection(t *testing.T) { t.Errorf("injected X-Evil header leaked: %q", got) } } + +// Every Message this file emits is rendered by the frontend under a single +// "pages.settings." prefix, so a key that carries its own resolves to nothing +// and the alert shows the raw key instead of the classified reason. +func TestClassifySMTPErrorReturnsUnprefixedKeys(t *testing.T) { + cases := []struct { + name string + err error + want string + }{ + {"auth", errors.New("535 5.7.8 Authentication credentials invalid"), "smtpErrorAuth"}, + {"starttls", errors.New("534 must issue a STARTTLS command first"), "smtpErrorStarttls"}, + {"refused", errors.New("dial tcp 127.0.0.1:25: connection refused"), "smtpErrorRefused"}, + {"timeout", errors.New("i/o timeout"), "smtpErrorTimeout"}, + {"relay", errors.New("550 relay not permitted"), "smtpErrorRelay"}, + {"eof", errors.New("unexpected EOF"), "smtpErrorEof"}, + {"unknown", errors.New("something nobody classified"), "smtpErrorUnknown"}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got := classifySMTPError(tc.err) + if got != tc.want { + t.Fatalf("classifySMTPError = %q, want %q", got, tc.want) + } + if strings.HasPrefix(got, "pages.settings.") { + t.Fatalf("key %q carries the prefix the frontend adds, so it resolves to nothing", got) + } + }) + } +} diff --git a/internal/web/translation/ar-EG.json b/internal/web/translation/ar-EG.json index 541b3515a..165952e98 100644 --- a/internal/web/translation/ar-EG.json +++ b/internal/web/translation/ar-EG.json @@ -1394,7 +1394,7 @@ "smtpErrorTimeout": "انتهت مهلة الاتصال — تعذّر الوصول إلى الخادم", "smtpErrorRelay": "الخادم يرفض الإرسال من هذا العنوان", "smtpErrorEof": "تم إغلاق الاتصال من قبل الخادم", - "smtpErrorUnknown": "خطأ SMTP: {{ .Error }}", + "smtpErrorUnknown": "خطأ SMTP — راجع سجل اللوحة للتفاصيل", "eventMemoryHigh": "ارتفاع استخدام الذاكرة (%)", "remarkTemplate": "قالب الملاحظة", "remarkTemplateDesc": "عند تعيينه، يحل هذا محل نموذج الملاحظة لكل رابط اشتراك — اكتب صيغتك الخاصة باستخدام رموز المتغيرات (استخدم الزر لإدراجها). اتركه فارغاً لاستخدام النموذج أعلاه.", diff --git a/internal/web/translation/en-US.json b/internal/web/translation/en-US.json index d8db76dff..ca4dd7094 100644 --- a/internal/web/translation/en-US.json +++ b/internal/web/translation/en-US.json @@ -1516,7 +1516,7 @@ "smtpErrorTimeout": "Connection timeout — host unreachable", "smtpErrorRelay": "Server rejects sending from this address", "smtpErrorEof": "Connection closed by server", - "smtpErrorUnknown": "SMTP error: {{ .Error }}", + "smtpErrorUnknown": "SMTP error — see the panel log for details", "eventMemoryHigh": "Memory high (%)", "validation": { "pathLeadingSlash": "Path must start with /" diff --git a/internal/web/translation/es-ES.json b/internal/web/translation/es-ES.json index a2f5c68bc..c03320a50 100644 --- a/internal/web/translation/es-ES.json +++ b/internal/web/translation/es-ES.json @@ -1394,7 +1394,7 @@ "smtpErrorTimeout": "Tiempo de conexión agotado: servidor inaccesible", "smtpErrorRelay": "El servidor rechaza el envío desde esta dirección", "smtpErrorEof": "Conexión cerrada por el servidor", - "smtpErrorUnknown": "Error de SMTP: {{ .Error }}", + "smtpErrorUnknown": "Error de SMTP — consulta el registro del panel", "eventMemoryHigh": "Uso de memoria alto (%)", "remarkTemplate": "Plantilla de notas", "remarkTemplateDesc": "Cuando se define, esto reemplaza el modelo de notas para cada enlace de suscripción — escribe tu propio formato con los tokens de variable (usa el botón para insertarlos). Déjalo vacío para usar el modelo anterior.", diff --git a/internal/web/translation/fa-IR.json b/internal/web/translation/fa-IR.json index 6486237e3..0be51fcfe 100644 --- a/internal/web/translation/fa-IR.json +++ b/internal/web/translation/fa-IR.json @@ -1398,7 +1398,7 @@ "smtpErrorTimeout": "مهلت اتصال به پایان رسید — میزبان در دسترس نیست", "smtpErrorRelay": "سرور ارسال از این آدرس را رد می‌کند", "smtpErrorEof": "اتصال توسط سرور بسته شد", - "smtpErrorUnknown": "خطای SMTP: {{ .Error }}", + "smtpErrorUnknown": "خطای SMTP — جزئیات در لاگ پنل", "eventMemoryHigh": "مصرف حافظه بالا (%)", "validation": { "pathLeadingSlash": "مسیر باید با / شروع شود" diff --git a/internal/web/translation/id-ID.json b/internal/web/translation/id-ID.json index 3392af6cb..65fb78aa9 100644 --- a/internal/web/translation/id-ID.json +++ b/internal/web/translation/id-ID.json @@ -1394,7 +1394,7 @@ "smtpErrorTimeout": "Koneksi waktu habis — host tidak dapat dijangkau", "smtpErrorRelay": "Server menolak pengiriman dari alamat ini", "smtpErrorEof": "Koneksi ditutup oleh server", - "smtpErrorUnknown": "Kesalahan SMTP: {{ .Error }}", + "smtpErrorUnknown": "Kesalahan SMTP — lihat log panel", "eventMemoryHigh": "Penggunaan memori tinggi (%)", "remarkTemplate": "Templat Catatan", "remarkTemplateDesc": "Jika diatur, ini menggantikan model catatan untuk setiap tautan langganan — tulis format Anda sendiri dengan token variabel (gunakan tombol untuk menyisipkannya). Biarkan kosong untuk memakai model di atas.", diff --git a/internal/web/translation/ja-JP.json b/internal/web/translation/ja-JP.json index bd37238f6..e1920a63e 100644 --- a/internal/web/translation/ja-JP.json +++ b/internal/web/translation/ja-JP.json @@ -1394,7 +1394,7 @@ "smtpErrorTimeout": "接続がタイムアウトしました — ホストに到達できません", "smtpErrorRelay": "サーバーはこのアドレスからの送信を拒否しています", "smtpErrorEof": "サーバーによって接続が閉じられました", - "smtpErrorUnknown": "SMTPエラー: {{ .Error }}", + "smtpErrorUnknown": "SMTPエラー — 詳細はパネルのログを参照", "eventMemoryHigh": "メモリ使用率が高い (%)", "remarkTemplate": "備考テンプレート", "remarkTemplateDesc": "設定すると、すべてのサブスクリプションリンクの備考モデルを置き換えます — 変数トークンを使って独自の形式を記述してください(ボタンで挿入できます)。空欄にすると上記のモデルが使用されます。", diff --git a/internal/web/translation/pt-BR.json b/internal/web/translation/pt-BR.json index 54c820167..9a2945a1e 100644 --- a/internal/web/translation/pt-BR.json +++ b/internal/web/translation/pt-BR.json @@ -1394,7 +1394,7 @@ "smtpErrorTimeout": "Tempo de conexão esgotado — host inacessível", "smtpErrorRelay": "O servidor rejeita o envio a partir deste endereço", "smtpErrorEof": "Conexão encerrada pelo servidor", - "smtpErrorUnknown": "Erro de SMTP: {{ .Error }}", + "smtpErrorUnknown": "Erro de SMTP — consulte o log do painel", "eventMemoryHigh": "Uso de memória alto (%)", "remarkTemplate": "Modelo de Observação", "remarkTemplateDesc": "Quando definido, isto substitui o modelo de observação de cada link de assinatura — escreva seu próprio formato com os tokens de variáveis (use o botão para inseri-los). Deixe vazio para usar o modelo acima.", diff --git a/internal/web/translation/ru-RU.json b/internal/web/translation/ru-RU.json index 0463b3180..b7dd4adea 100644 --- a/internal/web/translation/ru-RU.json +++ b/internal/web/translation/ru-RU.json @@ -1394,7 +1394,7 @@ "smtpErrorTimeout": "Таймаут соединения — хост недоступен", "smtpErrorRelay": "Сервер отклоняет отправку с этого адреса", "smtpErrorEof": "Соединение закрыто сервером", - "smtpErrorUnknown": "Ошибка SMTP: {{ .Error }}", + "smtpErrorUnknown": "Ошибка SMTP — подробности в журнале панели", "eventMemoryHigh": "Превышение порога памяти (%)", "remarkTemplate": "Шаблон примечания", "remarkTemplateDesc": "Если задан, заменяет модель примечания для каждой ссылки подписки — задайте собственный формат с помощью токенов переменных (используйте кнопку для их вставки). Оставьте пустым, чтобы использовать модель выше.", diff --git a/internal/web/translation/tr-TR.json b/internal/web/translation/tr-TR.json index 5e5f6a89f..a55efbfd1 100644 --- a/internal/web/translation/tr-TR.json +++ b/internal/web/translation/tr-TR.json @@ -1394,7 +1394,7 @@ "smtpErrorTimeout": "Bağlantı zaman aşımına uğradı — sunucuya ulaşılamıyor", "smtpErrorRelay": "Sunucu bu adresten gönderimi reddediyor", "smtpErrorEof": "Bağlantı sunucu tarafından kapatıldı", - "smtpErrorUnknown": "SMTP hatası: {{ .Error }}", + "smtpErrorUnknown": "SMTP hatası — ayrıntılar için panel günlüğüne bakın", "eventMemoryHigh": "Bellek kullanımı yüksek (%)", "remarkTemplate": "Açıklama Şablonu", "remarkTemplateDesc": "Ayarlandığında, her abonelik bağlantısının açıklama modelinin yerini alır — değişken belirteçleriyle kendi formatınızı yazın (eklemek için düğmeyi kullanın). Yukarıdaki modeli kullanmak için boş bırakın.", diff --git a/internal/web/translation/uk-UA.json b/internal/web/translation/uk-UA.json index e0172f345..5fa27eeb9 100644 --- a/internal/web/translation/uk-UA.json +++ b/internal/web/translation/uk-UA.json @@ -1394,7 +1394,7 @@ "smtpErrorTimeout": "Час очікування з'єднання вичерпано — хост недоступний", "smtpErrorRelay": "Сервер відхиляє надсилання з цієї адреси", "smtpErrorEof": "З'єднання закрито сервером", - "smtpErrorUnknown": "Помилка SMTP: {{ .Error }}", + "smtpErrorUnknown": "Помилка SMTP — подробиці в журналі панелі", "eventMemoryHigh": "Високе використання пам'яті (%)", "remarkTemplate": "Шаблон примітки", "remarkTemplateDesc": "Якщо задано, це замінює модель примітки для кожного посилання підписки — напишіть власний формат із токенами змінних (використовуйте кнопку для їх вставлення). Залиште порожнім, щоб використовувати модель вище.", diff --git a/internal/web/translation/vi-VN.json b/internal/web/translation/vi-VN.json index 2510d3cdd..7e0329467 100644 --- a/internal/web/translation/vi-VN.json +++ b/internal/web/translation/vi-VN.json @@ -1394,7 +1394,7 @@ "smtpErrorTimeout": "Hết thời gian kết nối — không thể truy cập máy chủ", "smtpErrorRelay": "Máy chủ từ chối gửi từ địa chỉ này", "smtpErrorEof": "Kết nối đã bị máy chủ đóng", - "smtpErrorUnknown": "Lỗi SMTP: {{ .Error }}", + "smtpErrorUnknown": "Lỗi SMTP — xem nhật ký bảng điều khiển", "eventMemoryHigh": "Sử dụng bộ nhớ cao (%)", "remarkTemplate": "Mẫu ghi chú", "remarkTemplateDesc": "Khi được đặt, mục này thay thế mô hình ghi chú cho mọi liên kết đăng ký — hãy viết định dạng riêng của bạn bằng các token biến (dùng nút để chèn chúng). Để trống để dùng mô hình ở trên.", diff --git a/internal/web/translation/zh-CN.json b/internal/web/translation/zh-CN.json index fa9fb9297..0c25c4eef 100644 --- a/internal/web/translation/zh-CN.json +++ b/internal/web/translation/zh-CN.json @@ -1394,7 +1394,7 @@ "smtpErrorTimeout": "连接超时——主机无法访问", "smtpErrorRelay": "服务器拒绝从此地址发送", "smtpErrorEof": "连接被服务器关闭", - "smtpErrorUnknown": "SMTP 错误:{{ .Error }}", + "smtpErrorUnknown": "SMTP 错误 — 详情见面板日志", "eventMemoryHigh": "内存使用率高 (%)", "remarkTemplate": "备注模板", "remarkTemplateDesc": "设置后,将替换每个订阅链接的备注模型 — 使用变量标记编写您自己的格式(用按钮插入它们)。留空则使用上方的模型。", diff --git a/internal/web/translation/zh-TW.json b/internal/web/translation/zh-TW.json index 01efdd1a5..810aff228 100644 --- a/internal/web/translation/zh-TW.json +++ b/internal/web/translation/zh-TW.json @@ -1394,7 +1394,7 @@ "smtpErrorTimeout": "連線逾時 — 無法連線至主機", "smtpErrorRelay": "伺服器拒絕從此地址傳送", "smtpErrorEof": "連線已被伺服器關閉", - "smtpErrorUnknown": "SMTP 錯誤:{{ .Error }}", + "smtpErrorUnknown": "SMTP 錯誤 — 詳情見面板日誌", "eventMemoryHigh": "記憶體使用率高 (%)", "remarkTemplate": "備註範本", "remarkTemplateDesc": "設定後,這將取代每個訂閱連結的備註模型——使用變數標記撰寫您自己的格式(使用按鈕來插入)。留空則使用上方的模型。",