Browse Source

fix(tgbot): localize QR caption via I18nBot (#6564)

* fix(panel): accept 2FA codes from adjacent TOTP windows

CheckUser compared only gotp.Now(), so a code submitted at the end of
its 30s window (or with slight client/server clock drift) failed with
'invalid 2fa code', while the immediate retry in the next window
succeeded. Accept current +/-1 window, the standard TOTP skew
tolerance.

Fixes MHSanaei/3x-ui#6535

* fix(panel): share TOTP skew tolerance with VerifyTwoFactorCode

Move the +/-1 window helper to internal/util/totp so both 2FA
acceptance points use it: login (CheckUser) and disable/rebind plus
username/password changes (VerifyTwoFactorCode). Also shrink comments
to the 2-line house rule and anchor the unit test mid-window to avoid
a step-boundary flake.

Addresses review on #6546 (MEDIUM + 2 LOWs).

* fix(tgbot): localize QR caption via I18nBot

sendClientQRLinks hardcoded English 'QRCode for client <email>:',
bypassing I18nBot, so non-English bot languages (e.g. ru-RU) still
got English. Add tgbot.answers.qrCodeForClient key with Email param
in all 13 locales and route the caption through I18nBot.

Fixes MHSanaei/3x-ui#6562

* fix(tgbot): repair locale JSON syntax, harden QR i18n test

- Add missing separators so all 13 locale files parse again.
- Rewrite the regression test to read the real shipped files
  (fails on malformed JSON or missing key).
- Add TestTgbotLocalesQrKeyValid covering every locale file.

* chore(tgbot): drop QR caption tests that cannot catch the bug

TestQRCodeForClientLocalizes never calls sendClientQRLinks: it registers
two messages in a synthetic bundle and asserts on I18nBot, a passthrough
to go-i18n. With the tgbot_client.go line reverted to the hardcoded
English caption, both it and TestTgbotLocalesQrKeyValid still pass, so
neither certifies the fix.

The malformed-locale class they were added for is already pinned twice:
the discord package's TestMain loads every translation file through
locale.InitLocalizer and panics on invalid JSON, and
frontend/src/test/i18n-dead-keys.test.ts parses all 13 locales and
checks each carries the en-US key set. Both go red on the #6564 syntax
error this PR first shipped.

---------

Co-authored-by: sdhfsl <[email protected]>
Co-authored-by: Sanaei <[email protected]>
sdhfsl 12 hours ago
parent
commit
536f9a6338

+ 1 - 1
internal/web/service/tgbot/tgbot_client.go

@@ -348,7 +348,7 @@ func (t *Tgbot) sendClientQRLinks(chatId int64, email string) {
 	}
 
 	// Inform user
-	t.SendMsgToTgbot(chatId, "QRCode for client "+email+":")
+	t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.qrCodeForClient", "Email=="+email))
 
 	// Send sub URL QR (filename: sub.png)
 	if png, err := createQR(subURL, 320); err == nil {

+ 2 - 1
internal/web/translation/ar-EG.json

@@ -2433,7 +2433,8 @@
       "disableSuccess": "✅ {{ .Email }}: اتعطل بنجاح.",
       "askToAddUserId": "مافيش إعدادات ليك!\r\nاطلب من الأدمن يضيف الـ Telegram ChatID الخاص بيك في إعداداتك.\r\n\r\nالـ ChatID بتاعك: <code>{{ .TgUserID }}</code>",
       "chooseClient": "اختار عميل للإدخال {{ .Inbound }}",
-      "chooseInbound": "اختار الإدخال"
+      "chooseInbound": "اختار الإدخال",
+      "qrCodeForClient": "رمز QR للعميل {{ .Email }}:"
     }
   },
   "discord": {

+ 2 - 1
internal/web/translation/en-US.json

@@ -2433,7 +2433,8 @@
       "disableSuccess": "✅ {{ .Email }}: Disabled successfully.",
       "askToAddUserId": "Your configuration is not found!\r\nPlease ask your admin to use your Telegram ChatID in your configuration(s).\r\n\r\nYour ChatID: <code>{{ .TgUserID }}</code>",
       "chooseClient": "Choose a Client for Inbound {{ .Inbound }}",
-      "chooseInbound": "Choose an Inbound"
+      "chooseInbound": "Choose an Inbound",
+      "qrCodeForClient": "QRCode for client {{ .Email }}:"
     }
   },
   "discord": {

+ 2 - 1
internal/web/translation/es-ES.json

@@ -2433,7 +2433,8 @@
       "disableSuccess": "✅ {{ .Email }} : Deshabilitado exitosamente.",
       "askToAddUserId": "¡No se encuentra su configuración!\r\nPor favor, pídale a su administrador que use su ChatID de usuario de Telegram en su(s) configuración(es).\r\n\r\nSu ChatID de usuario: <code>{{ .TgUserID }}</code>",
       "chooseClient": "Elige un Cliente para Inbound {{ .Inbound }}",
-      "chooseInbound": "Elige un Inbound"
+      "chooseInbound": "Elige un Inbound",
+      "qrCodeForClient": "Código QR para el cliente {{ .Email }}:"
     }
   },
   "discord": {

+ 2 - 1
internal/web/translation/fa-IR.json

@@ -2433,7 +2433,8 @@
       "disableSuccess": "✅ {{ .Email }} : با موفقیت غیرفعال شد.",
       "askToAddUserId": "پیکربندی شما یافت نشد!\r\nلطفاً از مدیر خود بخواهید که شناسه کاربر تلگرام خود را در پیکربندی (های) خود استفاده کند.\r\n\r\nشناسه کاربری شما: <code>{{ .TgUserID }}</code>",
       "chooseClient": "یک مشتری برای ورودی {{ .Inbound }} انتخاب کنید",
-      "chooseInbound": "یک ورودی انتخاب کنید"
+      "chooseInbound": "یک ورودی انتخاب کنید",
+      "qrCodeForClient": "کد QR برای کاربر {{ .Email }}:"
     }
   },
   "discord": {

+ 2 - 1
internal/web/translation/id-ID.json

@@ -2433,7 +2433,8 @@
       "disableSuccess": "✅ {{ .Email }}: Dinonaktifkan dengan berhasil.",
       "askToAddUserId": "Konfigurasi Anda tidak ditemukan!\r\nSilakan minta admin Anda untuk menggunakan ChatID Telegram Anda dalam konfigurasi Anda.\r\n\r\nChatID Pengguna Anda: <code>{{ .TgUserID }}</code>",
       "chooseClient": "Pilih Klien untuk Inbound {{ .Inbound }}",
-      "chooseInbound": "Pilih Inbound"
+      "chooseInbound": "Pilih Inbound",
+      "qrCodeForClient": "Kode QR untuk klien {{ .Email }}:"
     }
   },
   "discord": {

+ 2 - 1
internal/web/translation/ja-JP.json

@@ -2433,7 +2433,8 @@
       "disableSuccess": "✅ {{ .Email }}:正常に無効化されました。",
       "askToAddUserId": "設定が見つかりませんでした!\r\n管理者に問い合わせて、設定にTelegramユーザーのChatIDを使用してください。\r\n\r\nあなたのユーザーChatID:<code>{{ .TgUserID }}</code>",
       "chooseClient": "インバウンド {{ .Inbound }} のクライアントを選択",
-      "chooseInbound": "インバウンドを選択"
+      "chooseInbound": "インバウンドを選択",
+      "qrCodeForClient": "クライアント {{ .Email }} のQRコード:"
     }
   },
   "discord": {

+ 2 - 1
internal/web/translation/pt-BR.json

@@ -2433,7 +2433,8 @@
       "disableSuccess": "✅ {{ .Email }}: Desativado com sucesso.",
       "askToAddUserId": "Sua configuração não foi encontrada!\r\nPeça ao seu administrador para usar seu Telegram ChatID em suas configurações.\r\n\r\nSeu ChatID: <code>{{ .TgUserID }}</code>",
       "chooseClient": "Escolha um cliente para Inbound {{ .Inbound }}",
-      "chooseInbound": "Escolha um Inbound"
+      "chooseInbound": "Escolha um Inbound",
+      "qrCodeForClient": "QR Code para o cliente {{ .Email }}:"
     }
   },
   "discord": {

+ 2 - 1
internal/web/translation/ru-RU.json

@@ -2433,7 +2433,8 @@
       "disableSuccess": "✅ {{ .Email }}: Отключено успешно.",
       "askToAddUserId": "❌ Ваша конфигурация не найдена!\r\n💭 Пожалуйста, попросите администратора использовать ваш Telegram User ID в конфигурации.\r\n\r\n🆔 Ваш User ID: <code>{{ .TgUserID }}</code>",
       "chooseClient": "Выберите клиента для входящего подключения {{ .Inbound }}",
-      "chooseInbound": "Выберите входящее подключение"
+      "chooseInbound": "Выберите входящее подключение",
+      "qrCodeForClient": "QR-код для клиента {{ .Email }}:"
     }
   },
   "discord": {

+ 2 - 1
internal/web/translation/tr-TR.json

@@ -2433,7 +2433,8 @@
       "disableSuccess": "✅ {{ .Email }}: Başarıyla devre dışı bırakıldı.",
       "askToAddUserId": "Yapılandırmanız bulunamadı!\r\nLütfen yöneticinizden Telegram Chat ID'nizi yapılandırmanıza eklemesini isteyin.\r\n\r\nSizin Chat ID'niz: <code>{{ .TgUserID }}</code>",
       "chooseClient": "Gelen Bağlantı {{ .Inbound }} için bir Kullanıcı Seçin",
-      "chooseInbound": "Bir Gelen Bağlantı Seçin"
+      "chooseInbound": "Bir Gelen Bağlantı Seçin",
+      "qrCodeForClient": "{{ .Email }} istemcisi için QR Kodu:"
     }
   },
   "discord": {

+ 2 - 1
internal/web/translation/uk-UA.json

@@ -2433,7 +2433,8 @@
       "disableSuccess": "✅ {{ .Email }}: Успішно вимкнено.",
       "askToAddUserId": "Вашу конфігурацію не знайдено!\r\nБудь ласка, попросіть свого адміністратора використовувати ваш ідентифікатор Telegram у вашій конфігурації.\r\n\r\nВаш ідентифікатор користувача: <code>{{ .TgUserID }}</code>",
       "chooseClient": "Виберіть клієнта для Вхідного {{ .Inbound }}",
-      "chooseInbound": "Виберіть Вхідний"
+      "chooseInbound": "Виберіть Вхідний",
+      "qrCodeForClient": "QR-код для клієнта {{ .Email }}:"
     }
   },
   "discord": {

+ 2 - 1
internal/web/translation/vi-VN.json

@@ -2433,7 +2433,8 @@
       "disableSuccess": "✅ {{ .Email }} : Đã Tắt Thành Công.",
       "askToAddUserId": "Cấu hình của bạn không được tìm thấy!\r\nVui lòng yêu cầu Quản trị viên sử dụng ID người dùng telegram của bạn trong cấu hình của bạn.\r\n\r\nID người dùng của bạn: <code>{{ .TgUserID }}</code>",
       "chooseClient": "Chọn một Khách hàng cho Inbound {{ .Inbound }}",
-      "chooseInbound": "Chọn một Inbound"
+      "chooseInbound": "Chọn một Inbound",
+      "qrCodeForClient": "Mã QR cho khách hàng {{ .Email }}:"
     }
   },
   "discord": {

+ 2 - 1
internal/web/translation/zh-CN.json

@@ -2433,7 +2433,8 @@
       "disableSuccess": "✅ {{ .Email }}:已成功禁用。",
       "askToAddUserId": "未找到您的配置!\r\n请向管理员询问,在您的配置中使用您的 Telegram 用户 ChatID。\r\n\r\n您的用户 ChatID:<code>{{ .TgUserID }}</code>",
       "chooseClient": "为入站 {{ .Inbound }} 选择一个客户",
-      "chooseInbound": "选择一个入站"
+      "chooseInbound": "选择一个入站",
+      "qrCodeForClient": "客户端 {{ .Email }} 的二维码:"
     }
   },
   "discord": {

+ 2 - 1
internal/web/translation/zh-TW.json

@@ -2433,7 +2433,8 @@
       "disableSuccess": "✅ {{ .Email }}:已成功禁用。",
       "askToAddUserId": "未找到您的配置!\r\n請向管理員詢問,在您的配置中使用您的 Telegram 使用者 ChatID。\r\n\r\n您的使用者 ChatID:<code>{{ .TgUserID }}</code>",
       "chooseClient": "為入站 {{ .Inbound }} 選擇一個客戶",
-      "chooseInbound": "選擇一個入站"
+      "chooseInbound": "選擇一個入站",
+      "qrCodeForClient": "客戶端 {{ .Email }} 的二維碼:"
     }
   },
   "discord": {