mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-06 05:04:22 +00:00
fix(tgbot): resolve client creation race conditions and localization bugs
- Refactored Telegram bot client creation state to use a concurrent-safe map (\clientStates map[int64]*ClientState\), replacing package-level global variables. This prevents data races when multiple administrators interact with the bot simultaneously. - Fixed hardcoded English strings in \BuildInboundClientDataMessage\ by utilizing the \ .I18nBot()\ localization wrapper. - Implemented \UpdateBotLocalizer\ to dynamically refresh the bot's language whenever the \ gLang\ setting is updated in the web panel, eliminating the need for a service restart. - Synchronized missing translation keys for \Sub ID\ and \Flow\ across all non-English/Russian localization files to prevent missing interface elements.
This commit is contained in:
parent
63a7931862
commit
29fa28bf75
13 changed files with 7566 additions and 6920 deletions
|
|
@ -6,6 +6,7 @@ import (
|
||||||
|
|
||||||
"github.com/mhsanaei/3x-ui/v3/util/crypto"
|
"github.com/mhsanaei/3x-ui/v3/util/crypto"
|
||||||
"github.com/mhsanaei/3x-ui/v3/web/entity"
|
"github.com/mhsanaei/3x-ui/v3/web/entity"
|
||||||
|
"github.com/mhsanaei/3x-ui/v3/web/locale"
|
||||||
"github.com/mhsanaei/3x-ui/v3/web/service"
|
"github.com/mhsanaei/3x-ui/v3/web/service"
|
||||||
"github.com/mhsanaei/3x-ui/v3/web/session"
|
"github.com/mhsanaei/3x-ui/v3/web/session"
|
||||||
|
|
||||||
|
|
@ -77,6 +78,9 @@ func (a *SettingController) updateSetting(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = a.settingService.UpdateAllSetting(allSetting)
|
err = a.settingService.UpdateAllSetting(allSetting)
|
||||||
|
if err == nil {
|
||||||
|
locale.UpdateBotLocalizer(allSetting.TgLang)
|
||||||
|
}
|
||||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -111,11 +111,20 @@ func initTGBotLocalizer(settingService SettingService) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
logger.Infof("Initializing TG Bot localizer with language: %s", botLang)
|
||||||
|
|
||||||
LocalizerBot = i18n.NewLocalizer(i18nBundle, botLang)
|
LocalizerBot = i18n.NewLocalizer(i18nBundle, botLang)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateBotLocalizer dynamically updates the bot localizer language.
|
||||||
|
func UpdateBotLocalizer(botLang string) {
|
||||||
|
if i18nBundle != nil {
|
||||||
|
logger.Infof("Updating TG Bot localizer with language: %s", botLang)
|
||||||
|
LocalizerBot = i18n.NewLocalizer(i18nBundle, botLang)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// LocalizerMiddleware returns a Gin middleware that sets up localization for web requests.
|
// LocalizerMiddleware returns a Gin middleware that sets up localization for web requests.
|
||||||
// It determines the user's language from cookies or Accept-Language header,
|
// It determines the user's language from cookies or Accept-Language header,
|
||||||
// creates a localizer instance, and stores it in the Gin context for use in handlers.
|
// creates a localizer instance, and stores it in the Gin context for use in handlers.
|
||||||
|
|
@ -161,7 +170,9 @@ func loadTranslationsFromDisk(bundle *i18n.Bundle) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = bundle.ParseMessageFileBytes(data, path)
|
filename := d.Name()
|
||||||
|
logger.Infof("Parsing translation file from disk: %s", filename)
|
||||||
|
_, err = bundle.ParseMessageFileBytes(data, filename)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -183,7 +194,9 @@ func parseTranslationFiles(i18nFS embed.FS, i18nBundle *i18n.Bundle) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = i18nBundle.ParseMessageFileBytes(data, path)
|
filename := d.Name()
|
||||||
|
logger.Infof("Parsing translation file from embed: %s (path: %s)", filename, path)
|
||||||
|
_, err = i18nBundle.ParseMessageFileBytes(data, filename)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
1124
web/service/tgbot.go
1124
web/service/tgbot.go
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -873,6 +873,11 @@
|
||||||
"exhaustedMsg": "🚨 Exhausted {{ .Type }}:\r\n",
|
"exhaustedMsg": "🚨 Exhausted {{ .Type }}:\r\n",
|
||||||
"exhaustedCount": "🚨 Exhausted {{ .Type }} count:\r\n",
|
"exhaustedCount": "🚨 Exhausted {{ .Type }} count:\r\n",
|
||||||
"onlinesCount": "🌐 Online Clients: {{ .Count }}\r\n",
|
"onlinesCount": "🌐 Online Clients: {{ .Count }}\r\n",
|
||||||
|
"cpu": "CPU: {{ .Usage }}%\r\n",
|
||||||
|
"mem": "RAM: {{ .Usage }}/{{ .Total }}\r\n",
|
||||||
|
"swap": "Swap: {{ .Usage }}/{{ .Total }}\r\n",
|
||||||
|
"disk": "Disk: {{ .Usage }}/{{ .Total }}\r\n",
|
||||||
|
"uptime": "Uptime: {{ .Time }}\r\n",
|
||||||
"disabled": "🛑 Disabled: {{ .Disabled }}\r\n",
|
"disabled": "🛑 Disabled: {{ .Disabled }}\r\n",
|
||||||
"depleteSoon": "🔜 Deplete Soon: {{ .Deplete }}\r\n\r\n",
|
"depleteSoon": "🔜 Deplete Soon: {{ .Deplete }}\r\n\r\n",
|
||||||
"backupTime": "🗄 Backup Time: {{ .Time }}\r\n",
|
"backupTime": "🗄 Backup Time: {{ .Time }}\r\n",
|
||||||
|
|
@ -947,6 +952,8 @@
|
||||||
"change_subid": "📝 Sub ID",
|
"change_subid": "📝 Sub ID",
|
||||||
"change_flow": "🌊 Flow",
|
"change_flow": "🌊 Flow",
|
||||||
"flow_none": "None",
|
"flow_none": "None",
|
||||||
|
"qrCode": "QR Code",
|
||||||
|
"selectTGUser": "Select Telegram User",
|
||||||
"ResetAllTraffics": "Reset All Traffics",
|
"ResetAllTraffics": "Reset All Traffics",
|
||||||
"SortedTrafficUsageReport": "Sorted Traffic Usage Report"
|
"SortedTrafficUsageReport": "Sorted Traffic Usage Report"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -873,6 +873,11 @@
|
||||||
"exhaustedMsg": "🚨 Исчерпаны {{ .Type }}:\r\n",
|
"exhaustedMsg": "🚨 Исчерпаны {{ .Type }}:\r\n",
|
||||||
"exhaustedCount": "🚨 Количество исчерпанных {{ .Type }}:\r\n",
|
"exhaustedCount": "🚨 Количество исчерпанных {{ .Type }}:\r\n",
|
||||||
"onlinesCount": "🌐 Клиентов онлайн: {{ .Count }}\r\n",
|
"onlinesCount": "🌐 Клиентов онлайн: {{ .Count }}\r\n",
|
||||||
|
"cpu": "ЦП: {{ .Usage }}%\r\n",
|
||||||
|
"mem": "ОЗУ: {{ .Usage }}/{{ .Total }}\r\n",
|
||||||
|
"swap": "Swap: {{ .Usage }}/{{ .Total }}\r\n",
|
||||||
|
"disk": "Диск: {{ .Usage }}/{{ .Total }}\r\n",
|
||||||
|
"uptime": "Время работы: {{ .Time }}\r\n",
|
||||||
"disabled": "🛑 Отключено: {{ .Disabled }}\r\n",
|
"disabled": "🛑 Отключено: {{ .Disabled }}\r\n",
|
||||||
"depleteSoon": "🔜 Клиенты, у которых скоро исчерпание: {{ .Deplete }}\r\n\r\n",
|
"depleteSoon": "🔜 Клиенты, у которых скоро исчерпание: {{ .Deplete }}\r\n\r\n",
|
||||||
"backupTime": "🗄 Время резервного копирования: {{ .Time }}\r\n",
|
"backupTime": "🗄 Время резервного копирования: {{ .Time }}\r\n",
|
||||||
|
|
@ -947,6 +952,7 @@
|
||||||
"change_subid": "📝 Sub ID",
|
"change_subid": "📝 Sub ID",
|
||||||
"change_flow": "🌊 Flow",
|
"change_flow": "🌊 Flow",
|
||||||
"flow_none": "Отсутствует",
|
"flow_none": "Отсутствует",
|
||||||
|
"qrCode": "QR-код",
|
||||||
"ResetAllTraffics": "Сбросить весь трафик",
|
"ResetAllTraffics": "Сбросить весь трафик",
|
||||||
"SortedTrafficUsageReport": "Отсортированный отчет об использовании трафика"
|
"SortedTrafficUsageReport": "Отсортированный отчет об использовании трафика"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue