diff --git a/i18n/i18n.go b/i18n/i18n.go index 45c5c38ca..99505ee45 100644 --- a/i18n/i18n.go +++ b/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 diff --git a/main.go b/main.go index 1c737b042..4f9cf84ee 100644 --- a/main.go +++ b/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 } diff --git a/middleware/auth.go b/middleware/auth.go index a5d283d26..0bb27ead0 100644 --- a/middleware/auth.go +++ b/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() } diff --git a/model/user_cache.go b/model/user_cache.go index 7a6af0987..2ba1f18ec 100644 --- a/model/user_cache.go +++ b/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 +} diff --git a/web/src/hooks/channels/useChannelsData.jsx b/web/src/hooks/channels/useChannelsData.jsx index 1ce0785fb..12a757ab9 100644 --- a/web/src/hooks/channels/useChannelsData.jsx +++ b/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) { diff --git a/web/src/hooks/redemptions/useRedemptionsData.jsx b/web/src/hooks/redemptions/useRedemptionsData.jsx index 23c5b8d84..26b0f3a10 100644 --- a/web/src/hooks/redemptions/useRedemptionsData.jsx +++ b/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) { diff --git a/web/src/hooks/tokens/useTokensData.jsx b/web/src/hooks/tokens/useTokensData.jsx index a34508f49..fb86c0cfb 100644 --- a/web/src/hooks/tokens/useTokensData.jsx +++ b/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') { diff --git a/web/src/hooks/users/useUsersData.jsx b/web/src/hooks/users/useUsersData.jsx index f906be543..96e1a194d 100644 --- a/web/src/hooks/users/useUsersData.jsx +++ b/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