mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-02-04 04:30:05 +00:00
feat: add i18n support
This commit is contained in:
@@ -6,6 +6,8 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/naiba/nezha/service/singleton"
|
||||
)
|
||||
|
||||
type ioStreamContext struct {
|
||||
@@ -117,13 +119,13 @@ LOOP:
|
||||
}
|
||||
|
||||
if stream.userIo == nil && stream.agentIo == nil {
|
||||
return errors.New("timeout: no connection established")
|
||||
return singleton.Localizer.ErrorT("timeout: no connection established")
|
||||
}
|
||||
if stream.userIo == nil {
|
||||
return errors.New("timeout: user connection not established")
|
||||
return singleton.Localizer.ErrorT("timeout: user connection not established")
|
||||
}
|
||||
if stream.agentIo == nil {
|
||||
return errors.New("timeout: agent connection not established")
|
||||
return singleton.Localizer.ErrorT("timeout: agent connection not established")
|
||||
}
|
||||
|
||||
isDone := new(atomic.Bool)
|
||||
|
||||
@@ -54,11 +54,11 @@ func (s *NezhaHandler) ReportTask(c context.Context, r *pb.TaskResult) (*pb.Rece
|
||||
curServer := model.Server{}
|
||||
copier.Copy(&curServer, singleton.ServerList[clientID])
|
||||
if cr.PushSuccessful && r.GetSuccessful() {
|
||||
singleton.SendNotification(cr.NotificationGroupID, fmt.Sprintf("[%s] %s, %s\n%s", "Scheduled Task Executed Successfully",
|
||||
singleton.SendNotification(cr.NotificationGroupID, fmt.Sprintf("[%s] %s, %s\n%s", singleton.Localizer.T("Scheduled Task Executed Successfully"),
|
||||
cr.Name, singleton.ServerList[clientID].Name, r.GetData()), nil, &curServer)
|
||||
}
|
||||
if !r.GetSuccessful() {
|
||||
singleton.SendNotification(cr.NotificationGroupID, fmt.Sprintf("[%s] %s, %s\n%s", "Scheduled Task Executed Failed",
|
||||
singleton.SendNotification(cr.NotificationGroupID, fmt.Sprintf("[%s] %s, %s\n%s", singleton.Localizer.T("Scheduled Task Executed Failed"),
|
||||
cr.Name, singleton.ServerList[clientID].Name, r.GetData()), nil, &curServer)
|
||||
}
|
||||
singleton.DB.Model(cr).Updates(model.Cron{
|
||||
@@ -153,7 +153,7 @@ func (s *NezhaHandler) ReportSystemInfo(c context.Context, r *pb.Host) (*pb.Rece
|
||||
singleton.SendNotification(singleton.Conf.IPChangeNotificationGroupID,
|
||||
fmt.Sprintf(
|
||||
"[%s] %s, %s => %s",
|
||||
"IPChanged",
|
||||
singleton.Localizer.T("IP Changed"),
|
||||
singleton.ServerList[clientID].Name, singleton.IPDesensitize(singleton.ServerList[clientID].Host.IP),
|
||||
singleton.IPDesensitize(host.IP),
|
||||
),
|
||||
|
||||
@@ -156,7 +156,7 @@ func checkStatus() {
|
||||
// 始终触发模式或上次检查不为失败时触发报警(跳过单次触发+上次失败的情况)
|
||||
if alert.TriggerMode == model.ModeAlwaysTrigger || alertsPrevState[alert.ID][server.ID] != _RuleCheckFail {
|
||||
alertsPrevState[alert.ID][server.ID] = _RuleCheckFail
|
||||
message := fmt.Sprintf("[%s] %s(%s) %s", "Incident",
|
||||
message := fmt.Sprintf("[%s] %s(%s) %s", Localizer.T("Incident"),
|
||||
server.Name, IPDesensitize(server.Host.IP), alert.Name)
|
||||
go SendTriggerTasks(alert.FailTriggerTasks, curServer.ID)
|
||||
go SendNotification(alert.NotificationGroupID, message, NotificationMuteLabel.ServerIncident(server.ID, alert.ID), &curServer)
|
||||
@@ -166,7 +166,7 @@ func checkStatus() {
|
||||
} else {
|
||||
// 本次通过检查但上一次的状态为失败,则发送恢复通知
|
||||
if alertsPrevState[alert.ID][server.ID] == _RuleCheckFail {
|
||||
message := fmt.Sprintf("[%s] %s(%s) %s", "Resolved",
|
||||
message := fmt.Sprintf("[%s] %s(%s) %s", Localizer.T("Resolved"),
|
||||
server.Name, IPDesensitize(server.Host.IP), alert.Name)
|
||||
go SendTriggerTasks(alert.RecoverTriggerTasks, curServer.ID)
|
||||
go SendNotification(alert.NotificationGroupID, message, NotificationMuteLabel.ServerIncidentResolved(server.ID, alert.ID), &curServer)
|
||||
|
||||
@@ -34,29 +34,29 @@ func loadCronTasks() {
|
||||
var err error
|
||||
var notificationGroupList []uint64
|
||||
notificationMsgMap := make(map[uint64]*bytes.Buffer)
|
||||
for i := 0; i < len(CronList); i++ {
|
||||
for _, cron := range CronList {
|
||||
// 触发任务类型无需注册
|
||||
if CronList[i].TaskType == model.CronTypeTriggerTask {
|
||||
Crons[CronList[i].ID] = CronList[i]
|
||||
if cron.TaskType == model.CronTypeTriggerTask {
|
||||
Crons[cron.ID] = cron
|
||||
continue
|
||||
}
|
||||
// 注册计划任务
|
||||
CronList[i].CronJobID, err = Cron.AddFunc(CronList[i].Scheduler, CronTrigger(CronList[i]))
|
||||
cron.CronJobID, err = Cron.AddFunc(cron.Scheduler, CronTrigger(cron))
|
||||
if err == nil {
|
||||
Crons[CronList[i].ID] = CronList[i]
|
||||
Crons[cron.ID] = cron
|
||||
} else {
|
||||
// 当前通知组首次出现 将其加入通知组列表并初始化通知组消息缓存
|
||||
if _, ok := notificationMsgMap[CronList[i].NotificationGroupID]; !ok {
|
||||
notificationGroupList = append(notificationGroupList, CronList[i].NotificationGroupID)
|
||||
notificationMsgMap[CronList[i].NotificationGroupID] = bytes.NewBufferString("")
|
||||
notificationMsgMap[CronList[i].NotificationGroupID].WriteString("调度失败的计划任务:[")
|
||||
if _, ok := notificationMsgMap[cron.NotificationGroupID]; !ok {
|
||||
notificationGroupList = append(notificationGroupList, cron.NotificationGroupID)
|
||||
notificationMsgMap[cron.NotificationGroupID] = bytes.NewBufferString("")
|
||||
notificationMsgMap[cron.NotificationGroupID].WriteString(Localizer.T("Tasks failed to register: ["))
|
||||
}
|
||||
notificationMsgMap[CronList[i].NotificationGroupID].WriteString(fmt.Sprintf("%d,", CronList[i].ID))
|
||||
notificationMsgMap[cron.NotificationGroupID].WriteString(fmt.Sprintf("%d,", cron.ID))
|
||||
}
|
||||
}
|
||||
// 向注册错误的计划任务所在通知组发送通知
|
||||
for _, gid := range notificationGroupList {
|
||||
notificationMsgMap[gid].WriteString("] 这些任务将无法正常执行,请进入后点重新修改保存。")
|
||||
notificationMsgMap[gid].WriteString(Localizer.T("] These tasks will not execute properly. Fix them in the admin dashboard."))
|
||||
SendNotification(gid, notificationMsgMap[gid].String(), nil)
|
||||
}
|
||||
Cron.Start()
|
||||
@@ -147,7 +147,7 @@ func CronTrigger(cr *model.Cron, triggerServer ...uint64) func() {
|
||||
// 保存当前服务器状态信息
|
||||
curServer := model.Server{}
|
||||
copier.Copy(&curServer, s)
|
||||
SendNotification(cr.NotificationGroupID, fmt.Sprintf("[任务失败] %s,服务器 %s 离线,无法执行。", cr.Name, s.Name), nil, &curServer)
|
||||
SendNotification(cr.NotificationGroupID, Localizer.Tf("[Task failed] %s: server %s is offline and cannot execute the task", cr.Name, s.Name), nil, &curServer)
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -172,7 +172,7 @@ func CronTrigger(cr *model.Cron, triggerServer ...uint64) func() {
|
||||
// 保存当前服务器状态信息
|
||||
curServer := model.Server{}
|
||||
copier.Copy(&curServer, s)
|
||||
SendNotification(cr.NotificationGroupID, fmt.Sprintf("[任务失败] %s,服务器 %s 离线,无法执行。", cr.Name, s.Name), nil, &curServer)
|
||||
SendNotification(cr.NotificationGroupID, Localizer.Tf("[Task failed] %s: server %s is offline and cannot execute the task", cr.Name, s.Name), nil, &curServer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
80
service/singleton/i18n.go
Normal file
80
service/singleton/i18n.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package singleton
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/naiba/nezha/pkg/i18n"
|
||||
)
|
||||
|
||||
const domain = "nezha"
|
||||
|
||||
var Localizer *i18n.Localizer
|
||||
|
||||
func initI18n() {
|
||||
if err := loadTranslation(); err != nil {
|
||||
log.Printf("NEZHA>> init i18n failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func loadTranslation() error {
|
||||
lang := Conf.Language
|
||||
if lang == "" {
|
||||
lang = "zh_CN"
|
||||
}
|
||||
|
||||
data, err := getTranslationArchive(lang)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Localizer = i18n.NewLocalizer(lang, domain, domain+".zip", data)
|
||||
return nil
|
||||
}
|
||||
|
||||
func OnUpdateLang(lang string) error {
|
||||
if Localizer.Exists(lang) {
|
||||
Localizer.SetLanguage(lang)
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := getTranslationArchive(lang)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Localizer.AppendIntl(lang, domain, domain+".zip", data)
|
||||
Localizer.SetLanguage(lang)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getTranslationArchive(lang string) ([]byte, error) {
|
||||
files := [...]string{
|
||||
fmt.Sprintf("translations/%s/LC_MESSAGES/%s.po", lang, domain),
|
||||
fmt.Sprintf("translations/%s/LC_MESSAGES/%s.mo", lang, domain),
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
w := zip.NewWriter(buf)
|
||||
|
||||
for _, file := range files {
|
||||
f, err := w.Create(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := i18n.Translations.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := f.Write(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := w.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
@@ -436,14 +436,14 @@ func (ss *ServiceSentinel) worker() {
|
||||
// 延迟超过最大值
|
||||
ServerLock.RLock()
|
||||
reporterServer := ServerList[r.Reporter]
|
||||
msg := fmt.Sprintf("[Latency] %s %2f > %2f, Reporter: %s", ss.Services[mh.GetId()].Name, mh.Delay, ss.Services[mh.GetId()].MaxLatency, reporterServer.Name)
|
||||
msg := Localizer.Tf("[Latency] %s %2f > %2f, Reporter: %s", ss.Services[mh.GetId()].Name, mh.Delay, ss.Services[mh.GetId()].MaxLatency, reporterServer.Name)
|
||||
go SendNotification(notificationGroupID, msg, minMuteLabel)
|
||||
ServerLock.RUnlock()
|
||||
} else if mh.Delay < ss.Services[mh.GetId()].MinLatency {
|
||||
// 延迟低于最小值
|
||||
ServerLock.RLock()
|
||||
reporterServer := ServerList[r.Reporter]
|
||||
msg := fmt.Sprintf("[Latency] %s %2f < %2f, Reporter: %s", ss.Services[mh.GetId()].Name, mh.Delay, ss.Services[mh.GetId()].MinLatency, reporterServer.Name)
|
||||
msg := Localizer.Tf("[Latency] %s %2f < %2f, Reporter: %s", ss.Services[mh.GetId()].Name, mh.Delay, ss.Services[mh.GetId()].MinLatency, reporterServer.Name)
|
||||
go SendNotification(notificationGroupID, msg, maxMuteLabel)
|
||||
ServerLock.RUnlock()
|
||||
} else {
|
||||
@@ -469,7 +469,7 @@ func (ss *ServiceSentinel) worker() {
|
||||
|
||||
reporterServer := ServerList[r.Reporter]
|
||||
notificationGroupID := ss.Services[mh.GetId()].NotificationGroupID
|
||||
notificationMsg := fmt.Sprintf("[%s] %s Reporter: %s, Error: %s", StatusCodeToString(stateCode), ss.Services[mh.GetId()].Name, reporterServer.Name, mh.Data)
|
||||
notificationMsg := Localizer.Tf("[%s] %s Reporter: %s, Error: %s", StatusCodeToString(stateCode), ss.Services[mh.GetId()].Name, reporterServer.Name, mh.Data)
|
||||
muteLabel := NotificationMuteLabel.ServiceStateChanged(mh.GetId())
|
||||
|
||||
// 状态变更时,清除静音缓存
|
||||
@@ -512,7 +512,7 @@ func (ss *ServiceSentinel) worker() {
|
||||
ss.ServicesLock.RLock()
|
||||
if ss.Services[mh.GetId()].Notify {
|
||||
muteLabel := NotificationMuteLabel.ServiceSSL(mh.GetId(), "network")
|
||||
go SendNotification(ss.Services[mh.GetId()].NotificationGroupID, fmt.Sprintf("[SSL] Fetch cert info failed, %s %s", ss.Services[mh.GetId()].Name, errMsg), muteLabel)
|
||||
go SendNotification(ss.Services[mh.GetId()].NotificationGroupID, Localizer.Tf("[SSL] Fetch cert info failed, %s %s", ss.Services[mh.GetId()].Name, errMsg), muteLabel)
|
||||
}
|
||||
ss.ServicesLock.RUnlock()
|
||||
|
||||
@@ -551,7 +551,7 @@ func (ss *ServiceSentinel) worker() {
|
||||
// 证书过期提醒
|
||||
if expiresNew.Before(time.Now().AddDate(0, 0, 7)) {
|
||||
expiresTimeStr := expiresNew.Format("2006-01-02 15:04:05")
|
||||
errMsg = fmt.Sprintf(
|
||||
errMsg = Localizer.Tf(
|
||||
"The SSL certificate will expire within seven days. Expiration time: %s",
|
||||
expiresTimeStr,
|
||||
)
|
||||
@@ -564,8 +564,8 @@ func (ss *ServiceSentinel) worker() {
|
||||
|
||||
// 证书变更提醒
|
||||
if isCertChanged {
|
||||
errMsg = fmt.Sprintf(
|
||||
"SSL certificate changed, old: %s, %s expired; new: %s, %s expired.",
|
||||
errMsg = Localizer.Tf(
|
||||
"SSL certificate changed, old: issuer %s, expires at %s; new: issuer %s, expires at %s",
|
||||
oldCert[0], expiresOld.Format("2006-01-02 15:04:05"), newCert[0], expiresNew.Format("2006-01-02 15:04:05"))
|
||||
|
||||
// 证书变更后会自动更新缓存,所以不需要静音
|
||||
@@ -601,17 +601,13 @@ func GetStatusCode[T float32 | uint64](percent T) int {
|
||||
func StatusCodeToString(statusCode int) string {
|
||||
switch statusCode {
|
||||
case StatusNoData:
|
||||
// return Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "StatusNoData"})
|
||||
return "No Data"
|
||||
return Localizer.T("No Data")
|
||||
case StatusGood:
|
||||
// return Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "StatusGood"})
|
||||
return "Good"
|
||||
return Localizer.T("Good")
|
||||
case StatusLowAvailability:
|
||||
// return Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "StatusLowAvailability"})
|
||||
return "Low Availability"
|
||||
return Localizer.T("Low Availability")
|
||||
case StatusDown:
|
||||
// return Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "StatusDown"})
|
||||
return "Down"
|
||||
return Localizer.T("Down")
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ func InitTimezoneAndCache() {
|
||||
|
||||
// LoadSingleton 加载子服务并执行
|
||||
func LoadSingleton() {
|
||||
initI18n() // 加载本地化服务
|
||||
loadNotifications() // 加载通知服务
|
||||
loadServers() // 加载服务器列表
|
||||
loadCronTasks() // 加载定时任务
|
||||
|
||||
Reference in New Issue
Block a user