i18n.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. package i18n
  2. import (
  3. "embed"
  4. "strings"
  5. "sync"
  6. "github.com/gin-gonic/gin"
  7. "github.com/nicksnyder/go-i18n/v2/i18n"
  8. "golang.org/x/text/language"
  9. "gopkg.in/yaml.v3"
  10. "github.com/QuantumNous/new-api/common"
  11. "github.com/QuantumNous/new-api/constant"
  12. )
  13. const (
  14. LangZh = "zh"
  15. LangEn = "en"
  16. DefaultLang = LangEn // Fallback to English if language not supported
  17. )
  18. //go:embed locales/*.yaml
  19. var localeFS embed.FS
  20. var (
  21. bundle *i18n.Bundle
  22. localizers = make(map[string]*i18n.Localizer)
  23. mu sync.RWMutex
  24. initOnce sync.Once
  25. )
  26. // Init initializes the i18n bundle and loads all translation files
  27. func Init() error {
  28. var initErr error
  29. initOnce.Do(func() {
  30. bundle = i18n.NewBundle(language.Chinese)
  31. bundle.RegisterUnmarshalFunc("yaml", yaml.Unmarshal)
  32. // Load embedded translation files
  33. files := []string{"locales/zh.yaml", "locales/en.yaml"}
  34. for _, file := range files {
  35. _, err := bundle.LoadMessageFileFS(localeFS, file)
  36. if err != nil {
  37. initErr = err
  38. return
  39. }
  40. }
  41. // Pre-create localizers for supported languages
  42. localizers[LangZh] = i18n.NewLocalizer(bundle, LangZh)
  43. localizers[LangEn] = i18n.NewLocalizer(bundle, LangEn)
  44. // Set the TranslateMessage function in common package
  45. common.TranslateMessage = T
  46. })
  47. return initErr
  48. }
  49. // GetLocalizer returns a localizer for the specified language
  50. func GetLocalizer(lang string) *i18n.Localizer {
  51. lang = normalizeLang(lang)
  52. mu.RLock()
  53. loc, ok := localizers[lang]
  54. mu.RUnlock()
  55. if ok {
  56. return loc
  57. }
  58. // Create new localizer for unknown language (fallback to default)
  59. mu.Lock()
  60. defer mu.Unlock()
  61. // Double-check after acquiring write lock
  62. if loc, ok = localizers[lang]; ok {
  63. return loc
  64. }
  65. loc = i18n.NewLocalizer(bundle, lang, DefaultLang)
  66. localizers[lang] = loc
  67. return loc
  68. }
  69. // T translates a message key using the language from gin context
  70. func T(c *gin.Context, key string, args ...map[string]any) string {
  71. lang := GetLangFromContext(c)
  72. return Translate(lang, key, args...)
  73. }
  74. // Translate translates a message key for the specified language
  75. func Translate(lang, key string, args ...map[string]any) string {
  76. loc := GetLocalizer(lang)
  77. config := &i18n.LocalizeConfig{
  78. MessageID: key,
  79. }
  80. if len(args) > 0 && args[0] != nil {
  81. config.TemplateData = args[0]
  82. }
  83. msg, err := loc.Localize(config)
  84. if err != nil {
  85. // Return key as fallback if translation not found
  86. return key
  87. }
  88. return msg
  89. }
  90. // GetLangFromContext extracts the language setting from gin context
  91. func GetLangFromContext(c *gin.Context) string {
  92. if c == nil {
  93. return DefaultLang
  94. }
  95. // Try to get language from context (set by middleware)
  96. if lang := c.GetString(string(constant.ContextKeyLanguage)); lang != "" {
  97. return normalizeLang(lang)
  98. }
  99. return DefaultLang
  100. }
  101. // ParseAcceptLanguage parses the Accept-Language header and returns the preferred language
  102. func ParseAcceptLanguage(header string) string {
  103. if header == "" {
  104. return DefaultLang
  105. }
  106. // Simple parsing: take the first language tag
  107. parts := strings.Split(header, ",")
  108. if len(parts) == 0 {
  109. return DefaultLang
  110. }
  111. // Get the first language and remove quality value
  112. firstLang := strings.TrimSpace(parts[0])
  113. if idx := strings.Index(firstLang, ";"); idx > 0 {
  114. firstLang = firstLang[:idx]
  115. }
  116. return normalizeLang(firstLang)
  117. }
  118. // normalizeLang normalizes language code to supported format
  119. func normalizeLang(lang string) string {
  120. lang = strings.ToLower(strings.TrimSpace(lang))
  121. // Handle common variations
  122. switch {
  123. case strings.HasPrefix(lang, "zh"):
  124. return LangZh
  125. case strings.HasPrefix(lang, "en"):
  126. return LangEn
  127. default:
  128. return DefaultLang
  129. }
  130. }
  131. // SupportedLanguages returns a list of supported language codes
  132. func SupportedLanguages() []string {
  133. return []string{LangZh, LangEn}
  134. }
  135. // IsSupported checks if a language code is supported
  136. func IsSupported(lang string) bool {
  137. lang = normalizeLang(lang)
  138. for _, supported := range SupportedLanguages() {
  139. if lang == supported {
  140. return true
  141. }
  142. }
  143. return false
  144. }