feat: add i18n support

This commit is contained in:
uubulb
2024-11-01 05:07:04 +08:00
parent 482d787a56
commit 5114fc2854
30 changed files with 930 additions and 91 deletions

View File

@@ -1,8 +1,6 @@
package controller
import (
"errors"
"fmt"
"strconv"
"time"
@@ -99,7 +97,7 @@ func updateAlertRule(c *gin.Context) (any, error) {
var r model.AlertRule
if err := singleton.DB.First(&r, id).Error; err != nil {
return nil, fmt.Errorf("alert id %d does not exist", id)
return nil, singleton.Localizer.ErrorT("alert id %d does not exist", id)
}
if err := validateRule(&r); err != nil {
@@ -154,22 +152,22 @@ func validateRule(r *model.AlertRule) error {
for _, rule := range r.Rules {
if !rule.IsTransferDurationRule() {
if rule.Duration < 3 {
return errors.New("错误: Duration 至少为 3")
return singleton.Localizer.ErrorT("duration need to be at least 3")
}
} else {
if rule.CycleInterval < 1 {
return errors.New("错误: cycle_interval 至少为 1")
return singleton.Localizer.ErrorT("cycle_interval need to be at least 1")
}
if rule.CycleStart == nil {
return errors.New("错误: cycle_start 未设置")
return singleton.Localizer.ErrorT("cycle_start is not set")
}
if rule.CycleStart.After(time.Now()) {
return errors.New("错误: cycle_start 是个未来值")
return singleton.Localizer.ErrorT("cycle_start is a future value")
}
}
}
} else {
return errors.New("至少定义一条规则")
return singleton.Localizer.ErrorT("need to configure at least a single rule")
}
return nil
}

View File

@@ -185,7 +185,7 @@ func commonHandler[T any](handler handlerFunc[T]) func(*gin.Context) {
switch err.(type) {
case *gormError:
log.Printf("NEZHA>> gorm error: %v", err)
c.JSON(http.StatusOK, newErrorResponse(errors.New("database error")))
c.JSON(http.StatusOK, newErrorResponse(singleton.Localizer.ErrorT("database error")))
return
case *wsError:
// Connection is upgraded to WebSocket, so c.Writer is no longer usable

View File

@@ -1,7 +1,6 @@
package controller
import (
"errors"
"fmt"
"strconv"
@@ -61,7 +60,7 @@ func createCron(c *gin.Context) (uint64, error) {
cr.Cover = cf.Cover
if cr.TaskType == model.CronTypeCronTask && cr.Cover == model.CronCoverAlertTrigger {
return 0, errors.New("计划任务类型不得使用触发服务器执行方式")
return 0, singleton.Localizer.ErrorT("scheduled tasks cannot be triggered by alarms")
}
// 对于计划任务类型需要更新CronJob
@@ -120,7 +119,7 @@ func updateCron(c *gin.Context) (any, error) {
cr.Cover = cf.Cover
if cr.TaskType == model.CronTypeCronTask && cr.Cover == model.CronCoverAlertTrigger {
return nil, errors.New("计划任务类型不得使用触发服务器执行方式")
return nil, singleton.Localizer.ErrorT("scheduled tasks cannot be triggered by alarms")
}
// 对于计划任务类型需要更新CronJob
@@ -159,7 +158,7 @@ func manualTriggerCron(c *gin.Context) (any, error) {
var cr model.Cron
if err := singleton.DB.First(&cr, id).Error; err != nil {
return nil, fmt.Errorf("task id %d does not exist", id)
return nil, singleton.Localizer.ErrorT("task id %d does not exist", id)
}
singleton.ManualTrigger(&cr)

View File

@@ -1,8 +1,6 @@
package controller
import (
"errors"
"fmt"
"strconv"
"github.com/gin-gonic/gin"
@@ -55,7 +53,7 @@ func createDDNS(c *gin.Context) (uint64, error) {
}
if df.MaxRetries < 1 || df.MaxRetries > 10 {
return 0, errors.New("重试次数必须为大于 1 且不超过 10 的整数")
return 0, singleton.Localizer.ErrorT("the retry count must be an integer between 1 and 10")
}
p.Name = df.Name
@@ -78,7 +76,7 @@ func createDDNS(c *gin.Context) (uint64, error) {
// IDN to ASCII
domainValid, domainErr := idna.Lookup.ToASCII(domain)
if domainErr != nil {
return 0, fmt.Errorf("域名 %s 解析错误: %v", domain, domainErr)
return 0, singleton.Localizer.ErrorT("error parsing %s: %v", domain, domainErr)
}
p.Domains[n] = domainValid
}
@@ -119,12 +117,12 @@ func updateDDNS(c *gin.Context) (any, error) {
}
if df.MaxRetries < 1 || df.MaxRetries > 10 {
return nil, errors.New("重试次数必须为大于 1 且不超过 10 的整数")
return nil, singleton.Localizer.ErrorT("the retry count must be an integer between 1 and 10")
}
var p model.DDNSProfile
if err = singleton.DB.First(&p, id).Error; err != nil {
return nil, fmt.Errorf("profile id %d does not exist", id)
return nil, singleton.Localizer.ErrorT("profile id %d does not exist", id)
}
p.Name = df.Name
@@ -147,7 +145,7 @@ func updateDDNS(c *gin.Context) (any, error) {
// IDN to ASCII
domainValid, domainErr := idna.Lookup.ToASCII(domain)
if domainErr != nil {
return nil, fmt.Errorf("域名 %s 解析错误: %v", domain, domainErr)
return nil, singleton.Localizer.ErrorT("error parsing %s: %v", domain, domainErr)
}
p.Domains[n] = domainValid
}

View File

@@ -1,7 +1,6 @@
package controller
import (
"errors"
"strconv"
"time"
@@ -26,7 +25,7 @@ import (
// @Success 200 {object} model.CreateFMResponse
// @Router /file [get]
func createFM(c *gin.Context) (*model.CreateFMResponse, error) {
idStr := c.Param("id")
idStr := c.Query("id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
return nil, err
@@ -43,7 +42,7 @@ func createFM(c *gin.Context) (*model.CreateFMResponse, error) {
server := singleton.ServerList[id]
singleton.ServerLock.RUnlock()
if server == nil || server.TaskStream == nil {
return nil, errors.New("server not found or not connected")
return nil, singleton.Localizer.ErrorT("server not found or not connected")
}
fmData, _ := utils.Json.Marshal(&model.TaskFM{

View File

@@ -1,7 +1,6 @@
package controller
import (
"fmt"
"strconv"
"github.com/gin-gonic/gin"
@@ -93,7 +92,7 @@ func updateNAT(c *gin.Context) (any, error) {
var n model.NAT
if err = singleton.DB.First(&n, id).Error; err != nil {
return nil, fmt.Errorf("profile id %d does not exist", id)
return nil, singleton.Localizer.ErrorT("profile id %d does not exist", id)
}
n.Name = nf.Name

View File

@@ -1,7 +1,6 @@
package controller
import (
"fmt"
"strconv"
"github.com/gin-gonic/gin"
@@ -65,7 +64,7 @@ func createNotification(c *gin.Context) (uint64, error) {
}
// 未勾选跳过检查
if !nf.SkipCheck {
if err := ns.Send("这是测试消息"); err != nil {
if err := ns.Send(singleton.Localizer.T("a test message")); err != nil {
return 0, err
}
}
@@ -104,7 +103,7 @@ func updateNotification(c *gin.Context) (any, error) {
var n model.Notification
if err := singleton.DB.First(&n, id).Error; err != nil {
return nil, fmt.Errorf("notification id %d does not exist", id)
return nil, singleton.Localizer.ErrorT("notification id %d does not exist", id)
}
n.Name = nf.Name
@@ -123,7 +122,7 @@ func updateNotification(c *gin.Context) (any, error) {
}
// 未勾选跳过检查
if !nf.SkipCheck {
if err := ns.Send("这是测试消息"); err != nil {
if err := ns.Send(singleton.Localizer.T("a test message")); err != nil {
return nil, err
}
}

View File

@@ -1,7 +1,6 @@
package controller
import (
"fmt"
"slices"
"strconv"
@@ -78,7 +77,7 @@ func createNotificationGroup(c *gin.Context) (uint64, error) {
}
if count != int64(len(ngf.Notifications)) {
return 0, fmt.Errorf("have invalid notification id")
return 0, singleton.Localizer.ErrorT("have invalid notification id")
}
err := singleton.DB.Transaction(func(tx *gorm.DB) error {
@@ -129,7 +128,7 @@ func updateNotificationGroup(c *gin.Context) (any, error) {
}
var ngDB model.NotificationGroup
if err := singleton.DB.First(&ngDB, id).Error; err != nil {
return nil, fmt.Errorf("group id %d does not exist", id)
return nil, singleton.Localizer.ErrorT("group id %d does not exist", id)
}
ngDB.Name = ngf.Name
@@ -140,7 +139,7 @@ func updateNotificationGroup(c *gin.Context) (any, error) {
return nil, newGormError("%v", err)
}
if count != int64(len(ngf.Notifications)) {
return nil, fmt.Errorf("have invalid notification id")
return nil, singleton.Localizer.ErrorT("have invalid notification id")
}
err = singleton.DB.Transaction(func(tx *gorm.DB) error {

View File

@@ -1,7 +1,6 @@
package controller
import (
"fmt"
"strconv"
"github.com/gin-gonic/gin"
@@ -57,7 +56,7 @@ func updateServer(c *gin.Context) (any, error) {
var s model.Server
if err := singleton.DB.First(&s, id).Error; err != nil {
return nil, fmt.Errorf("server id %d does not exist", id)
return nil, singleton.Localizer.ErrorT("server id %d does not exist", id)
}
s.Name = sf.Name

View File

@@ -1,7 +1,6 @@
package controller
import (
"fmt"
"slices"
"strconv"
@@ -76,7 +75,7 @@ func createServerGroup(c *gin.Context) (uint64, error) {
return 0, newGormError("%v", err)
}
if count != int64(len(sgf.Servers)) {
return 0, fmt.Errorf("have invalid server id")
return 0, singleton.Localizer.ErrorT("have invalid server id")
}
err := singleton.DB.Transaction(func(tx *gorm.DB) error {
@@ -128,7 +127,7 @@ func updateServerGroup(c *gin.Context) (any, error) {
var sgDB model.ServerGroup
if err := singleton.DB.First(&sgDB, id).Error; err != nil {
return nil, fmt.Errorf("group id %d does not exist", id)
return nil, singleton.Localizer.ErrorT("group id %d does not exist", id)
}
sgDB.Name = sg.Name
@@ -137,7 +136,7 @@ func updateServerGroup(c *gin.Context) (any, error) {
return nil, err
}
if count != int64(len(sg.Servers)) {
return nil, fmt.Errorf("have invalid server id")
return nil, singleton.Localizer.ErrorT("have invalid server id")
}
err = singleton.DB.Transaction(func(tx *gorm.DB) error {

View File

@@ -1,8 +1,6 @@
package controller
import (
"errors"
"fmt"
"strconv"
"strings"
"time"
@@ -78,14 +76,14 @@ func listServiceHistory(c *gin.Context) ([]*model.ServiceInfos, error) {
singleton.ServerLock.RLock()
server, ok := singleton.ServerList[id]
if !ok {
return nil, errors.New("server not found")
return nil, singleton.Localizer.ErrorT("server not found")
}
_, isMember := c.Get(model.CtxKeyAuthorizedUser)
authorized := isMember // TODO || isViewPasswordVerfied
if server.HideForGuest && !authorized {
return nil, errors.New("unauthorized")
return nil, singleton.Localizer.ErrorT("unauthorized")
}
singleton.ServerLock.RUnlock()
@@ -154,7 +152,7 @@ func listServerWithServices(c *gin.Context) ([]uint64, error) {
server, ok := singleton.ServerList[id]
if !ok {
singleton.ServerLock.RUnlock()
return nil, errors.New("server not found")
return nil, singleton.Localizer.ErrorT("server not found")
}
if !server.HideForGuest || authorized {
@@ -201,7 +199,7 @@ func createService(c *gin.Context) (uint64, error) {
m.FailTriggerTasks = mf.FailTriggerTasks
if err := singleton.DB.Create(&m).Error; err != nil {
return 0, err
return 0, newGormError("%v", err)
}
var skipServers []uint64
@@ -246,7 +244,7 @@ func updateService(c *gin.Context) (any, error) {
}
var m model.Service
if err := singleton.DB.First(&m, id).Error; err != nil {
return nil, fmt.Errorf("service id %d does not exist", id)
return nil, singleton.Localizer.ErrorT("service id %d does not exist", id)
}
m.Name = mf.Name
m.Target = strings.TrimSpace(mf.Target)
@@ -265,7 +263,7 @@ func updateService(c *gin.Context) (any, error) {
m.FailTriggerTasks = mf.FailTriggerTasks
if err := singleton.DB.Save(&m).Error; err != nil {
return nil, err
return nil, newGormError("%v", err)
}
var skipServers []uint64

View File

@@ -63,9 +63,10 @@ func updateConfig(c *gin.Context) (any, error) {
singleton.Conf.CustomCodeDashboard = sf.CustomCodeDashboard
if err := singleton.Conf.Save(); err != nil {
return nil, err
return nil, newGormError("%v", err)
}
singleton.OnNameserverUpdate()
singleton.OnUpdateLang(singleton.Conf.Language)
return nil, nil
}

View File

@@ -1,7 +1,6 @@
package controller
import (
"errors"
"time"
"github.com/gin-gonic/gin"
@@ -41,7 +40,7 @@ func createTerminal(c *gin.Context) (*model.CreateTerminalResponse, error) {
server := singleton.ServerList[createTerminalReq.ServerID]
singleton.ServerLock.RUnlock()
if server == nil || server.TaskStream == nil {
return nil, errors.New("server not found or not connected")
return nil, singleton.Localizer.ErrorT("server not found or not connected")
}
terminalData, _ := utils.Json.Marshal(&model.TerminalTask{

View File

@@ -1,8 +1,6 @@
package controller
import (
"errors"
"github.com/gin-gonic/gin"
"github.com/naiba/nezha/model"
"github.com/naiba/nezha/service/singleton"
@@ -44,10 +42,10 @@ func createUser(c *gin.Context) (uint64, error) {
}
if len(uf.Password) < 6 {
return 0, errors.New("password length must be greater than 6")
return 0, singleton.Localizer.ErrorT("password length must be greater than 6")
}
if uf.Username == "" {
return 0, errors.New("username can't be empty")
return 0, singleton.Localizer.ErrorT("username can't be empty")
}
var u model.User