Bladeren bron

i18n: add

Yi 1 week geleden
bovenliggende
commit
c0fc7dde50
11 gewijzigde bestanden met toevoegingen van 201 en 14 verwijderingen
  1. 1 0
      go.mod
  2. 4 0
      go.sum
  3. 7 5
      http/middleware/auth.go
  4. 138 0
      http/middleware/i18n.go
  5. 1 1
      http/route/system_api.go
  6. 6 0
      locales/en.json
  7. 6 0
      locales/zh.json
  8. 5 2
      model/system_user.go
  9. 0 2
      module/backend/pasture.go
  10. 32 4
      module/backend/sql.go
  11. 1 0
      module/backend/system_service.go

+ 1 - 0
go.mod

@@ -69,6 +69,7 @@ require (
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
 	github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
+	github.com/nicksnyder/go-i18n/v2 v2.0.2 // indirect
 	github.com/nyaruka/phonenumbers v1.1.7 // indirect
 	github.com/onsi/gomega v1.27.1 // indirect
 	github.com/patrickmn/go-cache v2.1.0+incompatible // indirect

+ 4 - 0
go.sum

@@ -1325,6 +1325,7 @@ gitee.com/xuyiping_admin/go_proto v0.0.0-20250708055755-eca7bde9521e h1:6J5ou2Tg
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250708055755-eca7bde9521e/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
 gitee.com/xuyiping_admin/pkg v0.0.0-20250613101634-36c36a2d27d0 h1:ZCOqEAnGm6+DTAhACigzWKbwMKtleb8/7OzP2xfHG7g=
 gitee.com/xuyiping_admin/pkg v0.0.0-20250613101634-36c36a2d27d0/go.mod h1:8tF25X6pE9WkFCczlNAC0K2mrjwKvhhp02I7o0HtDxY=
+github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
@@ -2007,6 +2008,8 @@ github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi
 github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
 github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4=
 github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
+github.com/nicksnyder/go-i18n/v2 v2.0.2 h1:KsHGcTByIM0mHZKQGy0nlJLOjPNjQ6MVib/3PvsBDNY=
+github.com/nicksnyder/go-i18n/v2 v2.0.2/go.mod h1:JXS4+OKhbcwDoVTEj0sLFWL1vOwec2g/YBAxZ9owJqY=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
 github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
@@ -2389,6 +2392,7 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf
 golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=

+ 7 - 5
http/middleware/auth.go

@@ -14,11 +14,13 @@ import (
 )
 
 const (
-	Authorization = "Authorization"
-	ToKenPrefix   = "Bearer "
-	UserName      = "userName"
-	FarmId        = "FarmId"
-	XRequestId    = "X-Request-Id"
+	Authorization   = "Authorization"
+	ToKenPrefix     = "Bearer "
+	UserName        = "userName"
+	FarmId          = "FarmId"
+	XRequestId      = "X-Request-Id"
+	LanguageContent = "languageContent"
+	Language        = "language"
 )
 
 func GetToken(c *gin.Context) string {

+ 138 - 0
http/middleware/i18n.go

@@ -0,0 +1,138 @@
+package middleware
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"os"
+	"path"
+	"path/filepath"
+	"strings"
+
+	"gitee.com/xuyiping_admin/pkg/logger/zaplog"
+	"gitee.com/xuyiping_admin/pkg/xerr"
+	"github.com/gin-gonic/gin"
+	"github.com/nicksnyder/go-i18n/v2/i18n"
+	"go.uber.org/zap"
+	"golang.org/x/text/language"
+)
+
+var (
+	localesDir      = "./locales"
+	bundle          *i18n.Bundle
+	defaultLanguage = language.Chinese
+	supported       = map[string]bool{ // 支持的语言列表
+		"zh": true, // 中文
+		"en": true, // 英文
+		// 添加其他支持的语言...
+	}
+)
+
+// I18N 初始化国际化中间件
+func I18N() gin.HandlerFunc {
+	bundle = i18n.NewBundle(defaultLanguage)
+	bundle.RegisterUnmarshalFunc("json", json.Unmarshal)
+
+	// 加载翻译文件
+	if err := loadTranslations(); err != nil {
+		zaplog.Error("Failed to load translations", zap.Error(err))
+	}
+
+	return func(c *gin.Context) {
+		lang := detectLanguage(c.Request)
+		localize := i18n.NewLocalizer(bundle, lang)
+		c.Set(LanguageContent, localize)
+		c.Set(Language, lang)
+		c.Next()
+	}
+}
+
+// loadTranslations 加载所有翻译文件
+func loadTranslations() error {
+	absPath, err := filepath.Abs(localesDir)
+	if err != nil {
+		return xerr.WithStack(err)
+	}
+
+	files, err := ioutil.ReadDir(absPath)
+	if err != nil {
+		return xerr.WithStack(fmt.Errorf("failed to read files dir: %w", err))
+	}
+
+	if len(files) == 0 {
+		return xerr.Custom("no found in files directory")
+	}
+
+	loaded := false
+	for _, entry := range files {
+		if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".json") {
+			continue
+		}
+
+		filePath := path.Join(absPath, entry.Name())
+		data, err := os.ReadFile(filePath)
+		if err != nil {
+			zaplog.Error("Failed to read translation file",
+				zap.String("file", filePath),
+				zap.Error(err))
+			continue
+		}
+
+		messageFile, err := bundle.ParseMessageFileBytes(data, filePath)
+		if err != nil {
+			zaplog.Error("Failed to parse translation file",
+				zap.String("file", filePath),
+				zap.Error(err))
+			continue
+		}
+
+		zaplog.Info("Loaded translation",
+			zap.String("language", messageFile.Tag.String()),
+			zap.Any("messageFile", messageFile))
+		loaded = true
+	}
+
+	if !loaded {
+		return xerr.Custom("no valid translation files were loaded")
+	}
+	return nil
+}
+
+// detectLanguage 检测客户端语言偏好
+func detectLanguage(r *http.Request) string {
+	/*// 1. 检查查询参数
+	if lang := r.URL.Query().Get("lang"); lang != "" {
+		return normalizeLanguage(lang)
+	}
+
+	// 2. 检查 Cookie
+	if langCookie, err := r.Cookie("lang"); err == nil && langCookie.Value != "" {
+		return normalizeLanguage(langCookie.Value)
+	}
+	*/
+	// 3. 检查 Accept-Language 头
+	if acceptLang := r.Header.Get("Accept-Language"); acceptLang != "" {
+		if langs := strings.Split(acceptLang, ","); len(langs) > 0 {
+			return normalizeLanguage(langs[0])
+		}
+	}
+	// 默认返回中文
+	return "zh"
+}
+
+// normalizeLanguage 规范化语言代码
+func normalizeLanguage(lang string) string {
+	// 去除权重值 (如 en;q=0.9 -> en)
+	if parts := strings.Split(lang, ";"); len(parts) > 0 {
+		lang = parts[0]
+	}
+
+	// 转换为小写并去除地区代码 (en-US -> en)
+	lang = strings.ToLower(strings.Split(lang, "-")[0])
+
+	if supported[lang] {
+		return lang
+	}
+	return "zh" // 默认中文
+}

+ 1 - 1
http/route/system_api.go

@@ -61,6 +61,6 @@ func SystemAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 func authRouteGroup(s *gin.Engine, relativePath string) *gin.RouterGroup {
 	group := s.Group(relativePath)
 	// 中间件鉴权
-	group.Use(middleware.RequireAdmin(), middleware.GinLogger(), middleware.Pagination())
+	group.Use(middleware.I18N(), middleware.RequireAdmin(), middleware.GinLogger(), middleware.Pagination())
 	return group
 }

+ 6 - 0
locales/en.json

@@ -0,0 +1,6 @@
+{
+  "auth": {
+    "unPasture": "The user has not configured relevant farm data. Please contact the administrator!",
+    "unLogin": "Please login first!"
+  }
+}

+ 6 - 0
locales/zh.json

@@ -0,0 +1,6 @@
+{
+  "auth": {
+    "unPasture": "当前用户未配置相关牧场数据,请联系管理员!",
+    "unLogin": "请先登录!"
+  }
+}

+ 5 - 2
model/system_user.go

@@ -6,6 +6,8 @@ import (
 	"strings"
 	"time"
 
+	"github.com/nicksnyder/go-i18n/v2/i18n"
+
 	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
 )
 
@@ -78,8 +80,9 @@ func (s *SystemUser) GetPastureIds() []int32 {
 }
 
 type UserModel struct {
-	SystemUser *SystemUser
-	AppPasture *AppPastureList
+	SystemUser      *SystemUser
+	AppPasture      *AppPastureList
+	LanguageContent *i18n.Localizer
 }
 
 type SystemUserSlice []*SystemUser

+ 0 - 2
module/backend/pasture.go

@@ -459,7 +459,6 @@ func (s *StoreEntry) SearchDealerList(crx context.Context, req *pasturePb.Search
 	if err != nil {
 		return nil, xerr.WithStack(err)
 	}
-
 	saleDealerList := make([]*model.SaleDealer, 0)
 	pref := s.DB.Model(new(model.SaleDealer)).
 		Where("pasture_id = ?", userModel.AppPasture.Id)
@@ -471,7 +470,6 @@ func (s *StoreEntry) SearchDealerList(crx context.Context, req *pasturePb.Search
 	if err = pref.Where("is_show = ?", pasturePb.IsShow_Ok).Find(&saleDealerList).Error; err != nil {
 		return nil, xerr.WithStack(err)
 	}
-
 	return &pasturePb.SearchDealerResponse{
 		Code: http.StatusOK,
 		Msg:  "ok",

+ 32 - 4
module/backend/sql.go

@@ -7,6 +7,8 @@ import (
 	"kpt-pasture/model"
 	"strings"
 
+	"github.com/nicksnyder/go-i18n/v2/i18n"
+
 	"gitee.com/xuyiping_admin/pkg/logger/zaplog"
 	"go.uber.org/zap"
 
@@ -27,9 +29,17 @@ func (s *StoreEntry) GetUserModel(ctx context.Context) (*model.UserModel, error)
 		return nil, xerr.WithStack(err)
 	}
 
+	i18nTemplate, err := s.GetCurrentUserLanguage(ctx)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
 	systemUserPastureIds := strings.Split(systemUser.PastureIds, ",")
 	if len(systemUserPastureIds) == 0 {
-		return nil, xerr.Custom("当前用户未配置相关牧场数据,请联系管理员!")
+		message, _ := i18nTemplate.Localize(&i18n.LocalizeConfig{
+			MessageID: "auth.unPasture",
+		})
+		return nil, xerr.Custom(message)
 	}
 
 	var info bool
@@ -38,13 +48,18 @@ func (s *StoreEntry) GetUserModel(ctx context.Context) (*model.UserModel, error)
 			info = true
 		}
 	}
+
 	if !info {
-		return nil, xerr.Custom("该用户未没有该牧场操作权限,请联系管理员开通牧场权限")
+		message, _ := i18nTemplate.Localize(&i18n.LocalizeConfig{
+			MessageID: "auth.unPasture",
+		})
+		return nil, xerr.Custom(message)
 	}
 
 	return &model.UserModel{
-		SystemUser: systemUser,
-		AppPasture: appPasture,
+		SystemUser:      systemUser,
+		AppPasture:      appPasture,
+		LanguageContent: i18nTemplate,
 	}, nil
 }
 
@@ -120,6 +135,19 @@ func (s *StoreEntry) GetCurrentUserName(ctx context.Context) (string, error) {
 	}
 }
 
+// GetCurrentUserLanguage 获取用户系统语言
+func (s *StoreEntry) GetCurrentUserLanguage(ctx context.Context) (*i18n.Localizer, error) {
+	lg := ctx.Value(LanguageContent)
+	if lg == nil {
+		return nil, xerr.Customf("language error")
+	}
+	lag, ok := lg.(*i18n.Localizer)
+	if !ok {
+		return nil, xerr.Customf("language error")
+	}
+	return lag, nil
+}
+
 // GetFarmId 获取当前牧场Id
 func (s *StoreEntry) GetFarmId(ctx context.Context) string {
 	farmId := ctx.Value(CurrentFarmId)

+ 1 - 0
module/backend/system_service.go

@@ -23,6 +23,7 @@ import (
 const (
 	CurrentUserName = "userName"
 	CurrentFarmId   = "FarmId"
+	LanguageContent = "languageContent"
 )
 
 // Login 用户登录