i18n.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. package middleware
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io/ioutil"
  6. "net/http"
  7. "os"
  8. "path"
  9. "path/filepath"
  10. "strings"
  11. "gitee.com/xuyiping_admin/pkg/logger/zaplog"
  12. "gitee.com/xuyiping_admin/pkg/xerr"
  13. "github.com/gin-gonic/gin"
  14. "github.com/nicksnyder/go-i18n/v2/i18n"
  15. "go.uber.org/zap"
  16. "golang.org/x/text/language"
  17. )
  18. var (
  19. localesDir = "./locales"
  20. bundle *i18n.Bundle
  21. defaultLanguage = language.Chinese
  22. supported = map[string]bool{ // 支持的语言列表
  23. "zh": true, // 中文
  24. "en": true, // 英文
  25. // 添加其他支持的语言...
  26. }
  27. )
  28. // I18N 初始化国际化中间件
  29. func I18N() gin.HandlerFunc {
  30. bundle = i18n.NewBundle(defaultLanguage)
  31. bundle.RegisterUnmarshalFunc("json", json.Unmarshal)
  32. // 加载翻译文件
  33. if err := loadTranslations(); err != nil {
  34. zaplog.Error("Failed to load translations", zap.Error(err))
  35. }
  36. return func(c *gin.Context) {
  37. lang := detectLanguage(c.Request)
  38. localize := i18n.NewLocalizer(bundle, lang)
  39. c.Set(LanguageContent, localize)
  40. c.Set(Language, lang)
  41. c.Next()
  42. }
  43. }
  44. // loadTranslations 加载所有翻译文件
  45. func loadTranslations() error {
  46. absPath, err := filepath.Abs(localesDir)
  47. if err != nil {
  48. return xerr.WithStack(err)
  49. }
  50. files, err := ioutil.ReadDir(absPath)
  51. if err != nil {
  52. return xerr.WithStack(fmt.Errorf("failed to read files dir: %w", err))
  53. }
  54. if len(files) == 0 {
  55. return xerr.Custom("no found in files directory")
  56. }
  57. loaded := false
  58. for _, entry := range files {
  59. if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".json") {
  60. continue
  61. }
  62. filePath := path.Join(absPath, entry.Name())
  63. data, err := os.ReadFile(filePath)
  64. if err != nil {
  65. zaplog.Error("Failed to read translation file",
  66. zap.String("file", filePath),
  67. zap.Error(err))
  68. continue
  69. }
  70. messageFile, err := bundle.ParseMessageFileBytes(data, filePath)
  71. if err != nil {
  72. zaplog.Error("Failed to parse translation file",
  73. zap.String("file", filePath),
  74. zap.Error(err))
  75. continue
  76. }
  77. zaplog.Info("Loaded translation",
  78. zap.String("language", messageFile.Tag.String()),
  79. zap.Any("messageFile", messageFile))
  80. loaded = true
  81. }
  82. if !loaded {
  83. return xerr.Custom("no valid translation files were loaded")
  84. }
  85. return nil
  86. }
  87. // detectLanguage 检测客户端语言偏好
  88. func detectLanguage(r *http.Request) string {
  89. // 1. 检查查询参数
  90. if lang := r.URL.Query().Get("language"); lang != "" {
  91. return normalizeLanguage(lang)
  92. }
  93. // 2. 检查 Cookie
  94. if langCookie, err := r.Cookie("language"); err == nil && langCookie.Value != "" {
  95. return normalizeLanguage(langCookie.Value)
  96. }
  97. // 3. 检查 Accept-Language 头
  98. if acceptLang := r.Header.Get("Accept-Language"); acceptLang != "" {
  99. if langs := strings.Split(acceptLang, ","); len(langs) > 0 {
  100. return normalizeLanguage(langs[0])
  101. }
  102. }
  103. // 默认返回中文
  104. return defaultLanguage.String()
  105. }
  106. // normalizeLanguage 规范化语言代码
  107. func normalizeLanguage(lang string) string {
  108. // 去除权重值 (如 en;q=0.9 -> en)
  109. if parts := strings.Split(lang, ";"); len(parts) > 0 {
  110. lang = parts[0]
  111. }
  112. // 转换为小写并去除地区代码 (en-US -> en)
  113. lang = strings.ToLower(strings.Split(lang, "-")[0])
  114. if supported[lang] {
  115. return lang
  116. }
  117. return defaultLanguage.String()
  118. }