Prechádzať zdrojové kódy

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 mesiac pred
rodič
commit
67613e0642

+ 53 - 2
i18n/i18n.go

@@ -12,6 +12,7 @@ import (
 
 	"github.com/QuantumNous/new-api/common"
 	"github.com/QuantumNous/new-api/constant"
+	"github.com/QuantumNous/new-api/dto"
 )
 
 const (
@@ -109,15 +110,65 @@ func Translate(lang, key string, args ...map[string]any) string {
 	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
+// 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 {
 	if c == nil {
 		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 != "" {
-		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

+ 2 - 0
main.go

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

+ 10 - 0
model/user_cache.go

@@ -221,3 +221,13 @@ func updateUserSettingCache(userId int, setting string) error {
 	}
 	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;
     if (success) {
-      showSuccess('操作成功完成!');
+      showSuccess(t('操作成功完成!'));
       let newChannels = [...channels];
       for (let i = 0; i < newChannels.length; i++) {
         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;
       if (success) {
-        showSuccess('操作成功完成!');
+        showSuccess(t('操作成功完成!'));
         let redemption = res.data.data;
         let newRedemptions = [...redemptions];
         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;
     if (success) {
-      showSuccess('操作成功完成!');
+      showSuccess(t('操作成功完成!'));
       let token = res.data.data;
       let newTokens = [...tokens];
       if (action !== 'delete') {

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

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