Преглед изворни кода

fix(i18n): prioritize user settings over Accept-Language header

The i18n middleware runs before UserAuth, so user settings weren't
available when language was detected. Now GetLangFromContext checks
user settings first (set by UserAuth) before falling back to the
language set by middleware or Accept-Language header.
CaIon пре 1 месец
родитељ
комит
67613e0642

+ 53 - 2
i18n/i18n.go

@@ -12,6 +12,7 @@ import (
 
 
 	"github.com/QuantumNous/new-api/common"
 	"github.com/QuantumNous/new-api/common"
 	"github.com/QuantumNous/new-api/constant"
 	"github.com/QuantumNous/new-api/constant"
+	"github.com/QuantumNous/new-api/dto"
 )
 )
 
 
 const (
 const (
@@ -109,15 +110,65 @@ func Translate(lang, key string, args ...map[string]any) string {
 	return msg
 	return msg
 }
 }
 
 
+// userLangLoaderFunc is a function that loads user language from database/cache
+// It's set by the model package to avoid circular imports
+var userLangLoaderFunc func(userId int) string
+
+// SetUserLangLoader sets the function to load user language (called from model package)
+func SetUserLangLoader(loader func(userId int) string) {
+	userLangLoaderFunc = loader
+}
+
 // GetLangFromContext extracts the language setting from gin context
 // GetLangFromContext extracts the language setting from gin context
+// It checks multiple sources in priority order:
+// 1. User settings (ContextKeyUserSetting) - if already loaded (e.g., by TokenAuth)
+// 2. Lazy load user language from cache/DB using user ID
+// 3. Language set by middleware (ContextKeyLanguage) - from Accept-Language header
+// 4. Default language (English)
 func GetLangFromContext(c *gin.Context) string {
 func GetLangFromContext(c *gin.Context) string {
 	if c == nil {
 	if c == nil {
 		return DefaultLang
 		return DefaultLang
 	}
 	}
 
 
-	// Try to get language from context (set by middleware)
+	// 1. Try to get language from user settings (if already loaded by TokenAuth or other middleware)
+	if userSetting, ok := common.GetContextKeyType[dto.UserSetting](c, constant.ContextKeyUserSetting); ok {
+		if userSetting.Language != "" {
+			normalized := normalizeLang(userSetting.Language)
+			if IsSupported(normalized) {
+				return normalized
+			}
+		}
+	}
+
+	// 2. Lazy load user language using user ID (for session-based auth where full settings aren't loaded)
+	if userLangLoaderFunc != nil {
+		if userId, exists := c.Get("id"); exists {
+			if uid, ok := userId.(int); ok && uid > 0 {
+				lang := userLangLoaderFunc(uid)
+				if lang != "" {
+					normalized := normalizeLang(lang)
+					if IsSupported(normalized) {
+						return normalized
+					}
+				}
+			}
+		}
+	}
+
+	// 3. Try to get language from context (set by I18n middleware from Accept-Language)
 	if lang := c.GetString(string(constant.ContextKeyLanguage)); lang != "" {
 	if lang := c.GetString(string(constant.ContextKeyLanguage)); lang != "" {
-		return normalizeLang(lang)
+		normalized := normalizeLang(lang)
+		if IsSupported(normalized) {
+			return normalized
+		}
+	}
+
+	// 4. Try Accept-Language header directly (fallback if middleware didn't run)
+	if acceptLang := c.GetHeader("Accept-Language"); acceptLang != "" {
+		lang := ParseAcceptLanguage(acceptLang)
+		if IsSupported(lang) {
+			return lang
+		}
 	}
 	}
 
 
 	return DefaultLang
 	return DefaultLang

+ 2 - 0
main.go

@@ -288,6 +288,8 @@ func InitResources() error {
 	} else {
 	} else {
 		common.SysLog("i18n initialized with languages: " + strings.Join(i18n.SupportedLanguages(), ", "))
 		common.SysLog("i18n initialized with languages: " + strings.Join(i18n.SupportedLanguages(), ", "))
 	}
 	}
+	// Register user language loader for lazy loading
+	i18n.SetUserLangLoader(model.GetUserLanguage)
 
 
 	return nil
 	return nil
 }
 }

+ 0 - 11
middleware/auth.go

@@ -132,17 +132,6 @@ func authHelper(c *gin.Context, minRole int) {
 	c.Set("user_group", session.Get("group"))
 	c.Set("user_group", session.Get("group"))
 	c.Set("use_access_token", useAccessToken)
 	c.Set("use_access_token", useAccessToken)
 
 
-	//userCache, err := model.GetUserCache(id.(int))
-	//if err != nil {
-	//	c.JSON(http.StatusOK, gin.H{
-	//		"success": false,
-	//		"message": err.Error(),
-	//	})
-	//	c.Abort()
-	//	return
-	//}
-	//userCache.WriteContext(c)
-
 	c.Next()
 	c.Next()
 }
 }
 
 

+ 10 - 0
model/user_cache.go

@@ -221,3 +221,13 @@ func updateUserSettingCache(userId int, setting string) error {
 	}
 	}
 	return common.RedisHSetField(getUserCacheKey(userId), "Setting", setting)
 	return common.RedisHSetField(getUserCacheKey(userId), "Setting", setting)
 }
 }
+
+// GetUserLanguage returns the user's language preference from cache
+// Uses the existing GetUserCache mechanism for efficiency
+func GetUserLanguage(userId int) string {
+	userCache, err := GetUserCache(userId)
+	if err != nil {
+		return ""
+	}
+	return userCache.GetSetting().Language
+}

+ 1 - 1
web/src/hooks/channels/useChannelsData.jsx

@@ -491,7 +491,7 @@ export const useChannelsData = () => {
     }
     }
     const { success, message } = res.data;
     const { success, message } = res.data;
     if (success) {
     if (success) {
-      showSuccess('操作成功完成!');
+      showSuccess(t('操作成功完成!'));
       let newChannels = [...channels];
       let newChannels = [...channels];
       for (let i = 0; i < newChannels.length; i++) {
       for (let i = 0; i < newChannels.length; i++) {
         if (newChannels[i].tag === tag) {
         if (newChannels[i].tag === tag) {

+ 1 - 1
web/src/hooks/redemptions/useRedemptionsData.jsx

@@ -145,7 +145,7 @@ export const useRedemptionsData = () => {
 
 
       const { success, message } = res.data;
       const { success, message } = res.data;
       if (success) {
       if (success) {
-        showSuccess('操作成功完成!');
+        showSuccess(t('操作成功完成!'));
         let redemption = res.data.data;
         let redemption = res.data.data;
         let newRedemptions = [...redemptions];
         let newRedemptions = [...redemptions];
         if (action !== REDEMPTION_ACTIONS.DELETE) {
         if (action !== REDEMPTION_ACTIONS.DELETE) {

+ 1 - 1
web/src/hooks/tokens/useTokensData.jsx

@@ -174,7 +174,7 @@ export const useTokensData = (openFluentNotification) => {
     }
     }
     const { success, message } = res.data;
     const { success, message } = res.data;
     if (success) {
     if (success) {
-      showSuccess('操作成功完成!');
+      showSuccess(t('操作成功完成!'));
       let token = res.data.data;
       let token = res.data.data;
       let newTokens = [...tokens];
       let newTokens = [...tokens];
       if (action !== 'delete') {
       if (action !== 'delete') {

+ 1 - 1
web/src/hooks/users/useUsersData.jsx

@@ -132,7 +132,7 @@ export const useUsersData = () => {
 
 
     const { success, message } = res.data;
     const { success, message } = res.data;
     if (success) {
     if (success) {
-      showSuccess('操作成功完成!');
+      showSuccess(t('操作成功完成!'));
       const user = res.data.data;
       const user = res.data.data;
 
 
       // Create a new array and new object to ensure React detects changes
       // Create a new array and new object to ensure React detects changes