diff --git a/internal/handler/dashboard_handler.go b/internal/handler/dashboard_handler.go index 55b64060..9a4b71fa 100644 --- a/internal/handler/dashboard_handler.go +++ b/internal/handler/dashboard_handler.go @@ -20,7 +20,7 @@ func (s *Server) Stats(c *gin.Context) { now := time.Now() rpmStats, err := s.getRPMStats(now) if err != nil { - response.Error(c, app_errors.NewAPIError(app_errors.ErrDatabase, "failed to get rpm stats")) + response.ErrorI18nFromAPIError(c, app_errors.ErrDatabase, "database.rpm_stats_failed") return } twentyFourHoursAgo := now.Add(-24 * time.Hour) @@ -28,12 +28,12 @@ func (s *Server) Stats(c *gin.Context) { currentPeriod, err := s.getHourlyStats(twentyFourHoursAgo, now) if err != nil { - response.Error(c, app_errors.NewAPIError(app_errors.ErrDatabase, "failed to get current period stats")) + response.ErrorI18nFromAPIError(c, app_errors.ErrDatabase, "database.current_stats_failed") return } previousPeriod, err := s.getHourlyStats(fortyEightHoursAgo, twentyFourHoursAgo) if err != nil { - response.Error(c, app_errors.NewAPIError(app_errors.ErrDatabase, "failed to get previous period stats")) + response.ErrorI18nFromAPIError(c, app_errors.ErrDatabase, "database.previous_stats_failed") return } @@ -125,7 +125,7 @@ func (s *Server) Chart(c *gin.Context) { query = query.Where("group_id = ?", groupID) } if err := query.Order("time asc").Find(&hourlyStats).Error; err != nil { - response.Error(c, app_errors.NewAPIError(app_errors.ErrDatabase, "failed to get chart data")) + response.ErrorI18nFromAPIError(c, app_errors.ErrDatabase, "database.chart_data_failed") return } @@ -272,14 +272,14 @@ func checkPasswordSecurity(c *gin.Context, password, keyType string) []models.Se if len(password) < 16 { warnings = append(warnings, models.SecurityWarning{ Type: keyType, - Message: i18n.Message(c, "security.password_too_short", map[string]interface{}{"keyType": keyType, "length": len(password)}), + Message: i18n.Message(c, "security.password_too_short", map[string]any{"keyType": keyType, "length": len(password)}), Severity: "high", // 长度不足是高风险 Suggestion: i18n.Message(c, "security.password_recommendation_16"), }) } else if len(password) < 32 { warnings = append(warnings, models.SecurityWarning{ Type: keyType, - Message: i18n.Message(c, "security.password_short", map[string]interface{}{"keyType": keyType, "length": len(password)}), + Message: i18n.Message(c, "security.password_short", map[string]any{"keyType": keyType, "length": len(password)}), Severity: "medium", Suggestion: i18n.Message(c, "security.password_recommendation_32"), }) @@ -297,7 +297,7 @@ func checkPasswordSecurity(c *gin.Context, password, keyType string) []models.Se if strings.Contains(lower, pattern) { warnings = append(warnings, models.SecurityWarning{ Type: keyType, - Message: i18n.Message(c, "security.password_weak_pattern", map[string]interface{}{"keyType": keyType, "pattern": pattern}), + Message: i18n.Message(c, "security.password_weak_pattern", map[string]any{"keyType": keyType, "pattern": pattern}), Severity: "high", Suggestion: i18n.Message(c, "security.password_avoid_common"), }) @@ -309,7 +309,7 @@ func checkPasswordSecurity(c *gin.Context, password, keyType string) []models.Se if len(password) >= 16 && !hasGoodComplexity(password) { warnings = append(warnings, models.SecurityWarning{ Type: keyType, - Message: i18n.Message(c, "security.password_low_complexity", map[string]interface{}{"keyType": keyType}), + Message: i18n.Message(c, "security.password_low_complexity", map[string]any{"keyType": keyType}), Severity: "medium", Suggestion: i18n.Message(c, "security.password_complexity"), }) diff --git a/internal/handler/group_handler.go b/internal/handler/group_handler.go index fc586cea..8353c7c3 100644 --- a/internal/handler/group_handler.go +++ b/internal/handler/group_handler.go @@ -190,7 +190,7 @@ func (s *Server) CreateGroup(c *gin.Context) { channelType := strings.TrimSpace(req.ChannelType) if !isValidChannelType(channelType) { supported := strings.Join(channel.GetChannels(), ", ") - response.ErrorI18nFromAPIError(c, app_errors.ErrValidation, "validation.invalid_channel_type", map[string]interface{}{"types": supported}) + response.ErrorI18nFromAPIError(c, app_errors.ErrValidation, "validation.invalid_channel_type", map[string]any{"types": supported}) return } @@ -202,13 +202,13 @@ func (s *Server) CreateGroup(c *gin.Context) { cleanedUpstreams, err := validateAndCleanUpstreams(req.Upstreams) if err != nil { - response.Error(c, app_errors.NewAPIError(app_errors.ErrValidation, err.Error())) + response.ErrorI18nFromAPIError(c, app_errors.ErrValidation, "validation.invalid_upstreams", map[string]any{"error": err.Error()}) return } cleanedConfig, err := s.validateAndCleanConfig(req.Config) if err != nil { - response.Error(c, app_errors.NewAPIError(app_errors.ErrValidation, fmt.Sprintf("Invalid config format: %v", err))) + response.ErrorI18nFromAPIError(c, app_errors.ErrValidation, "error.invalid_config_format", map[string]any{"error": err.Error()}) return } @@ -235,7 +235,7 @@ func (s *Server) CreateGroup(c *gin.Context) { // Check for duplicate keys if seenKeys[canonicalKey] { - response.ErrorI18nFromAPIError(c, app_errors.ErrValidation, "validation.duplicate_header", map[string]interface{}{"key": canonicalKey}) + response.ErrorI18nFromAPIError(c, app_errors.ErrValidation, "validation.duplicate_header", map[string]any{"key": canonicalKey}) return } seenKeys[canonicalKey] = true @@ -250,7 +250,7 @@ func (s *Server) CreateGroup(c *gin.Context) { if len(normalizedHeaderRules) > 0 { headerRulesBytes, err := json.Marshal(normalizedHeaderRules) if err != nil { - response.Error(c, app_errors.NewAPIError(app_errors.ErrInternalServer, fmt.Sprintf("Failed to process header rules: %v", err))) + response.ErrorI18nFromAPIError(c, app_errors.ErrInternalServer, "error.process_header_rules", map[string]any{"error": err.Error()}) return } headerRulesJSON = headerRulesBytes @@ -378,7 +378,7 @@ func (s *Server) UpdateGroup(c *gin.Context) { cleanedChannelType := strings.TrimSpace(*req.ChannelType) if !isValidChannelType(cleanedChannelType) { supported := strings.Join(channel.GetChannels(), ", ") - response.ErrorI18nFromAPIError(c, app_errors.ErrValidation, "validation.invalid_channel_type", map[string]interface{}{"types": supported}) + response.ErrorI18nFromAPIError(c, app_errors.ErrValidation, "validation.invalid_channel_type", map[string]any{"types": supported}) return } group.ChannelType = cleanedChannelType @@ -409,7 +409,7 @@ func (s *Server) UpdateGroup(c *gin.Context) { if req.Config != nil { cleanedConfig, err := s.validateAndCleanConfig(req.Config) if err != nil { - response.Error(c, app_errors.NewAPIError(app_errors.ErrValidation, fmt.Sprintf("Invalid config format: %v", err))) + response.ErrorI18nFromAPIError(c, app_errors.ErrValidation, "error.invalid_config_format", map[string]any{"error": err.Error()}) return } group.Config = cleanedConfig @@ -436,7 +436,7 @@ func (s *Server) UpdateGroup(c *gin.Context) { // Check for duplicate keys if seenKeys[canonicalKey] { - response.ErrorI18nFromAPIError(c, app_errors.ErrValidation, "validation.duplicate_header", map[string]interface{}{"key": canonicalKey}) + response.ErrorI18nFromAPIError(c, app_errors.ErrValidation, "validation.duplicate_header", map[string]any{"key": canonicalKey}) return } seenKeys[canonicalKey] = true @@ -451,7 +451,7 @@ func (s *Server) UpdateGroup(c *gin.Context) { if len(normalizedHeaderRules) > 0 { headerRulesBytes, err := json.Marshal(normalizedHeaderRules) if err != nil { - response.Error(c, app_errors.NewAPIError(app_errors.ErrInternalServer, fmt.Sprintf("Failed to process header rules: %v", err))) + response.ErrorI18nFromAPIError(c, app_errors.ErrInternalServer, "error.process_header_rules", map[string]any{"error": err.Error()}) return } headerRulesJSON = headerRulesBytes @@ -607,8 +607,7 @@ func (s *Server) DeleteGroup(c *gin.Context) { "error": err, }).Error("Failed to remove keys from memory store, rolling back transaction") - response.Error(c, app_errors.NewAPIError(app_errors.ErrDatabase, - "Failed to delete group: unable to clean up cache")) + response.ErrorI18nFromAPIError(c, app_errors.ErrDatabase, "error.delete_group_cache") return } } @@ -623,7 +622,7 @@ func (s *Server) DeleteGroup(c *gin.Context) { if err := s.GroupManager.Invalidate(); err != nil { logrus.WithContext(c.Request.Context()).WithError(err).Error("failed to invalidate group cache") } - response.Success(c, gin.H{"message": "Group and associated keys deleted successfully"}) + response.SuccessI18n(c, "success.group_deleted", nil) } // ConfigOption represents a single configurable option for a group. diff --git a/internal/handler/handler.go b/internal/handler/handler.go index d29e5cf8..65fb90c5 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -8,6 +8,7 @@ import ( "gpt-load/internal/config" "gpt-load/internal/encryption" + "gpt-load/internal/i18n" "gpt-load/internal/services" "gpt-load/internal/types" @@ -84,7 +85,7 @@ func (s *Server) Login(c *gin.Context) { if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "success": false, - "message": "Invalid request format", + "message": i18n.Message(c, "auth.invalid_request"), }) return } @@ -96,12 +97,12 @@ func (s *Server) Login(c *gin.Context) { if isValid { c.JSON(http.StatusOK, LoginResponse{ Success: true, - Message: "Authentication successful", + Message: i18n.Message(c, "auth.authentication_successful"), }) } else { c.JSON(http.StatusUnauthorized, LoginResponse{ Success: false, - Message: "Authentication failed", + Message: i18n.Message(c, "auth.authentication_failed"), }) } } diff --git a/internal/handler/key_handler.go b/internal/handler/key_handler.go index e4a9c234..1ca0711f 100644 --- a/internal/handler/key_handler.go +++ b/internal/handler/key_handler.go @@ -16,27 +16,32 @@ import ( ) // validateGroupIDFromQuery validates and parses group ID from a query parameter. -func validateGroupIDFromQuery(c *gin.Context) (uint, error) { +// Returns 0 and false if validation fails (error is already sent to client) +func validateGroupIDFromQuery(c *gin.Context) (uint, bool) { groupIDStr := c.Query("group_id") if groupIDStr == "" { - return 0, fmt.Errorf("group_id query parameter is required") + response.ErrorI18nFromAPIError(c, app_errors.ErrBadRequest, "validation.group_id_required") + return 0, false } groupID, err := strconv.Atoi(groupIDStr) if err != nil || groupID <= 0 { - return 0, fmt.Errorf("invalid group_id format") + response.ErrorI18nFromAPIError(c, app_errors.ErrBadRequest, "validation.invalid_group_id_format") + return 0, false } - return uint(groupID), nil + return uint(groupID), true } // validateKeysText validates the keys text input -func validateKeysText(keysText string) error { +// Returns false if validation fails (error is already sent to client) +func validateKeysText(c *gin.Context, keysText string) bool { if strings.TrimSpace(keysText) == "" { - return fmt.Errorf("keys text cannot be empty") + response.ErrorI18nFromAPIError(c, app_errors.ErrValidation, "validation.keys_text_empty") + return false } - return nil + return true } // findGroupByID is a helper function to find a group by its ID. @@ -82,8 +87,7 @@ func (s *Server) AddMultipleKeys(c *gin.Context) { return } - if err := validateKeysText(req.KeysText); err != nil { - response.Error(c, app_errors.NewAPIError(app_errors.ErrValidation, err.Error())) + if !validateKeysText(c, req.KeysText) { return } @@ -115,8 +119,7 @@ func (s *Server) AddMultipleKeysAsync(c *gin.Context) { return } - if err := validateKeysText(req.KeysText); err != nil { - response.Error(c, app_errors.NewAPIError(app_errors.ErrValidation, err.Error())) + if !validateKeysText(c, req.KeysText) { return } @@ -131,9 +134,8 @@ func (s *Server) AddMultipleKeysAsync(c *gin.Context) { // ListKeysInGroup handles listing all keys within a specific group with pagination. func (s *Server) ListKeysInGroup(c *gin.Context) { - groupID, err := validateGroupIDFromQuery(c) - if err != nil { - response.Error(c, app_errors.NewAPIError(app_errors.ErrBadRequest, err.Error())) + groupID, ok := validateGroupIDFromQuery(c) + if !ok { return } @@ -189,8 +191,7 @@ func (s *Server) DeleteMultipleKeys(c *gin.Context) { return } - if err := validateKeysText(req.KeysText); err != nil { - response.Error(c, app_errors.NewAPIError(app_errors.ErrValidation, err.Error())) + if !validateKeysText(c, req.KeysText) { return } @@ -222,8 +223,7 @@ func (s *Server) DeleteMultipleKeysAsync(c *gin.Context) { return } - if err := validateKeysText(req.KeysText); err != nil { - response.Error(c, app_errors.NewAPIError(app_errors.ErrValidation, err.Error())) + if !validateKeysText(c, req.KeysText) { return } @@ -248,8 +248,7 @@ func (s *Server) RestoreMultipleKeys(c *gin.Context) { return } - if err := validateKeysText(req.KeysText); err != nil { - response.Error(c, app_errors.NewAPIError(app_errors.ErrValidation, err.Error())) + if !validateKeysText(c, req.KeysText) { return } @@ -287,8 +286,7 @@ func (s *Server) TestMultipleKeys(c *gin.Context) { return } - if err := validateKeysText(req.KeysText); err != nil { - response.Error(c, app_errors.NewAPIError(app_errors.ErrValidation, err.Error())) + if !validateKeysText(c, req.KeysText) { return } @@ -364,7 +362,7 @@ func (s *Server) RestoreAllInvalidKeys(c *gin.Context) { return } - response.Success(c, gin.H{"message": fmt.Sprintf("%d keys restored.", rowsAffected)}) + response.SuccessI18n(c, "success.keys_restored", nil, map[string]any{"count": rowsAffected}) } // ClearAllInvalidKeys deletes all 'inactive' keys from a group. @@ -385,7 +383,7 @@ func (s *Server) ClearAllInvalidKeys(c *gin.Context) { return } - response.Success(c, gin.H{"message": fmt.Sprintf("%d invalid keys cleared.", rowsAffected)}) + response.SuccessI18n(c, "success.invalid_keys_cleared", nil, map[string]any{"count": rowsAffected}) } // ClearAllKeys deletes all keys from a group. @@ -406,14 +404,13 @@ func (s *Server) ClearAllKeys(c *gin.Context) { return } - response.Success(c, gin.H{"message": fmt.Sprintf("%d keys cleared.", rowsAffected)}) + response.SuccessI18n(c, "success.all_keys_cleared", nil, map[string]any{"count": rowsAffected}) } // ExportKeys handles exporting keys to a text file. func (s *Server) ExportKeys(c *gin.Context) { - groupID, err := validateGroupIDFromQuery(c) - if err != nil { - response.Error(c, app_errors.NewAPIError(app_errors.ErrBadRequest, err.Error())) + groupID, ok := validateGroupIDFromQuery(c) + if !ok { return } @@ -438,8 +435,7 @@ func (s *Server) ExportKeys(c *gin.Context) { c.Header("Content-Disposition", "attachment; filename="+filename) c.Header("Content-Type", "text/plain; charset=utf-8") - err = s.KeyService.StreamKeysToWriter(groupID, statusFilter, c.Writer) - if err != nil { + if err := s.KeyService.StreamKeysToWriter(groupID, statusFilter, c.Writer); err != nil { log.Printf("Failed to stream keys: %v", err) } } diff --git a/internal/handler/log_handler.go b/internal/handler/log_handler.go index 37a00ecb..3023bf3a 100644 --- a/internal/handler/log_handler.go +++ b/internal/handler/log_handler.go @@ -3,6 +3,7 @@ package handler import ( "fmt" app_errors "gpt-load/internal/errors" + "gpt-load/internal/i18n" "gpt-load/internal/models" "gpt-load/internal/response" "log" @@ -56,7 +57,7 @@ func (s *Server) ExportLogs(c *gin.Context) { err := s.LogService.StreamLogKeysToCSV(c, c.Writer) if err != nil { log.Printf("Failed to stream log keys to CSV: %v", err) - c.JSON(500, gin.H{"error": "Failed to export logs"}) + c.JSON(500, gin.H{"error": i18n.Message(c, "error.export_logs")}) return } } diff --git a/internal/handler/settings_handler.go b/internal/handler/settings_handler.go index 1cf03594..8753ef46 100644 --- a/internal/handler/settings_handler.go +++ b/internal/handler/settings_handler.go @@ -85,7 +85,5 @@ func (s *Server) UpdateSettings(c *gin.Context) { time.Sleep(100 * time.Millisecond) // 等待异步更新配置 - response.Success(c, gin.H{ - "message": "Settings updated successfully. Configuration will be reloaded in the background across all instances.", - }) + response.SuccessI18n(c, "settings.update_success", nil) } diff --git a/internal/i18n/i18n.go b/internal/i18n/i18n.go index 26bc473f..1349d65f 100644 --- a/internal/i18n/i18n.go +++ b/internal/i18n/i18n.go @@ -110,7 +110,7 @@ func normalizeLanguageCode(lang string) string { } // T 翻译消息 -func T(localizer *i18n.Localizer, msgID string, data ...map[string]interface{}) string { +func T(localizer *i18n.Localizer, msgID string, data ...map[string]any) string { config := &i18n.LocalizeConfig{ MessageID: msgID, } diff --git a/internal/i18n/locales/en-US.go b/internal/i18n/locales/en-US.go index adff8aaf..76e4526a 100644 --- a/internal/i18n/locales/en-US.go +++ b/internal/i18n/locales/en-US.go @@ -55,6 +55,10 @@ var MessagesEnUS = map[string]string{ "validation.invalid_channel_type": "Invalid channel type. Supported types: {types}", "validation.test_model_empty": "Test model cannot be empty or contain only spaces", "validation.invalid_status_value": "Invalid status value", + "validation.invalid_upstreams": "Invalid upstreams configuration: {error}", + "validation.group_id_required": "group_id query parameter is required", + "validation.invalid_group_id_format": "Invalid group_id format", + "validation.keys_text_empty": "Keys text cannot be empty", // Task related "task.validation_started": "Key validation task started", @@ -151,4 +155,26 @@ var MessagesEnUS = map[string]string{ "error.upstream_weight_positive": "upstream weight must be a positive integer", "error.marshal_upstreams_failed": "failed to marshal cleaned upstreams", "error.unknown_config_field": "unknown config field: '{field}'", + "error.invalid_config_format": "Invalid config format: {error}", + "error.process_header_rules": "Failed to process header rules: {error}", + "error.invalidate_group_cache": "failed to invalidate group cache", + "error.unmarshal_header_rules": "Failed to unmarshal header rules", + "error.delete_group_cache": "Failed to delete group: unable to clean up cache", + "error.get_total_keys": "failed to get total keys: {error}", + "error.get_active_keys": "failed to get active keys: {error}", + "error.get_hourly_requests": "failed to get hourly total requests: {error}", + "error.get_hourly_failures": "failed to get hourly failed requests: {error}", + "error.get_daily_stats": "failed to get daily stats: {error}", + "error.get_weekly_stats": "failed to get weekly stats: {error}", + "error.decrypt_key_copy": "Failed to decrypt key during group copy, skipping", + "error.start_import_task": "Failed to start async key import task for group copy", + "error.export_logs": "Failed to export logs", + + // Login related + "auth.invalid_request": "Invalid request format", + "auth.authentication_successful": "Authentication successful", + "auth.authentication_failed": "Authentication failed", + + // Settings success message + "settings.update_success": "Settings updated successfully. Configuration will be reloaded in the background across all instances.", } diff --git a/internal/i18n/locales/ja-JP.go b/internal/i18n/locales/ja-JP.go index 2d9a30c9..978a435d 100644 --- a/internal/i18n/locales/ja-JP.go +++ b/internal/i18n/locales/ja-JP.go @@ -55,6 +55,10 @@ var MessagesJaJP = map[string]string{ "validation.invalid_channel_type": "無効なチャンネルタイプ。サポートされるタイプ: {types}", "validation.test_model_empty": "テストモデルは空またはスペースのみにできません", "validation.invalid_status_value": "無効なステータス値", + "validation.invalid_upstreams": "無効なupstreams設定: {error}", + "validation.group_id_required": "group_idクエリパラメータが必要です", + "validation.invalid_group_id_format": "無効なgroup_id形式", + "validation.keys_text_empty": "キーテキストは空にできません", // タスク関連 "task.validation_started": "キー検証タスクが開始されました", @@ -151,4 +155,26 @@ var MessagesJaJP = map[string]string{ "error.upstream_weight_positive": "upstreamの重みは正の整数である必要があります", "error.marshal_upstreams_failed": "クリーンアップされたupstreamsのシリアル化に失敗しました", "error.unknown_config_field": "未知の設定フィールド: '{field}'", + "error.invalid_config_format": "無効な設定形式: {error}", + "error.process_header_rules": "ヘッダールールの処理に失敗しました: {error}", + "error.invalidate_group_cache": "グループキャッシュの無効化に失敗しました", + "error.unmarshal_header_rules": "ヘッダールールのアンマーシャルに失敗しました", + "error.delete_group_cache": "グループの削除に失敗: キャッシュをクリーンアップできません", + "error.get_total_keys": "合計キー数の取得に失敗: {error}", + "error.get_active_keys": "アクティブキー数の取得に失敗: {error}", + "error.get_hourly_requests": "時間別リクエスト数の取得に失敗: {error}", + "error.get_hourly_failures": "時間別失敗数の取得に失敗: {error}", + "error.get_daily_stats": "日別統計の取得に失敗: {error}", + "error.get_weekly_stats": "週別統計の取得に失敗: {error}", + "error.decrypt_key_copy": "グループコピー中のキー復号化に失敗、スキップします", + "error.start_import_task": "グループコピー用の非同期キーインポートタスクの開始に失敗しました", + "error.export_logs": "ログのエクスポートに失敗しました", + + // ログイン関連 + "auth.invalid_request": "無効なリクエスト形式", + "auth.authentication_successful": "認証成功", + "auth.authentication_failed": "認証失敗", + + // 設定成功メッセージ + "settings.update_success": "設定が正常に更新されました。設定はすべてのインスタンスでバックグラウンドで再読み込みされます。", } diff --git a/internal/i18n/locales/zh-CN.go b/internal/i18n/locales/zh-CN.go index a0c4dfdc..5282c004 100644 --- a/internal/i18n/locales/zh-CN.go +++ b/internal/i18n/locales/zh-CN.go @@ -55,6 +55,10 @@ var MessagesZhCN = map[string]string{ "validation.invalid_channel_type": "无效的通道类型。支持的类型有: {types}", "validation.test_model_empty": "测试模型不能为空或只有空格", "validation.invalid_status_value": "无效的状态值", + "validation.invalid_upstreams": "upstreams配置错误: {error}", + "validation.group_id_required": "需要提供group_id参数", + "validation.invalid_group_id_format": "无效的group_id格式", + "validation.keys_text_empty": "密钥文本不能为空", // 任务相关 "task.validation_started": "密钥验证任务已开始", @@ -151,4 +155,26 @@ var MessagesZhCN = map[string]string{ "error.upstream_weight_positive": "upstream权重必须是正整数", "error.marshal_upstreams_failed": "序列化清理后的upstreams失败", "error.unknown_config_field": "未知的配置字段: '{field}'", + "error.invalid_config_format": "无效的配置格式: {error}", + "error.process_header_rules": "处理请求头规则失败: {error}", + "error.invalidate_group_cache": "刷新分组缓存失败", + "error.unmarshal_header_rules": "解析请求头规则失败", + "error.delete_group_cache": "删除分组失败: 无法清理缓存", + "error.get_total_keys": "获取密钥总数失败: {error}", + "error.get_active_keys": "获取活跃密钥数失败: {error}", + "error.get_hourly_requests": "获取每小时请求数失败: {error}", + "error.get_hourly_failures": "获取每小时失败数失败: {error}", + "error.get_daily_stats": "获取每日统计失败: {error}", + "error.get_weekly_stats": "获取每周统计失败: {error}", + "error.decrypt_key_copy": "解密密钥时失败,跳过该密钥", + "error.start_import_task": "启动异步密钥导入任务失败", + "error.export_logs": "导出日志失败", + + // 登录相关 + "auth.invalid_request": "无效的请求格式", + "auth.authentication_successful": "认证成功", + "auth.authentication_failed": "认证失败", + + // 设置成功消息 + "settings.update_success": "设置更新成功。配置将在后台在所有实例间重新加载。", } diff --git a/internal/i18n/middleware.go b/internal/i18n/middleware.go index 3169903b..519f5eef 100644 --- a/internal/i18n/middleware.go +++ b/internal/i18n/middleware.go @@ -17,17 +17,17 @@ func Middleware() gin.HandlerFunc { return func(c *gin.Context) { // 获取 Accept-Language 头 acceptLang := c.GetHeader("Accept-Language") - + // 获取 Localizer localizer := GetLocalizer(acceptLang) - + // 将 Localizer 存储到 Context 中 c.Set(LocalizerKey, localizer) - + // 存储当前语言 lang := normalizeLanguageCode(acceptLang) c.Set(LangKey, lang) - + c.Next() } } @@ -54,10 +54,10 @@ func GetLangFromContext(c *gin.Context) string { } // Success 返回成功响应(带国际化消息) -func Success(c *gin.Context, msgID string, data interface{}) { +func Success(c *gin.Context, msgID string, data any) { localizer := GetLocalizerFromContext(c) message := T(localizer, msgID) - + c.JSON(200, gin.H{ "success": true, "message": message, @@ -67,10 +67,10 @@ func Success(c *gin.Context, msgID string, data interface{}) { } // SuccessWithData 返回成功响应(带模板数据) -func SuccessWithData(c *gin.Context, msgID string, templateData map[string]interface{}, data interface{}) { +func SuccessWithData(c *gin.Context, msgID string, templateData map[string]any, data any) { localizer := GetLocalizerFromContext(c) message := T(localizer, msgID, templateData) - + c.JSON(200, gin.H{ "success": true, "message": message, @@ -83,7 +83,7 @@ func SuccessWithData(c *gin.Context, msgID string, templateData map[string]inter func Error(c *gin.Context, code int, msgID string) { localizer := GetLocalizerFromContext(c) message := T(localizer, msgID) - + c.JSON(code, gin.H{ "success": false, "message": message, @@ -92,10 +92,10 @@ func Error(c *gin.Context, code int, msgID string) { } // ErrorWithData 返回错误响应(带模板数据) -func ErrorWithData(c *gin.Context, code int, msgID string, templateData map[string]interface{}) { +func ErrorWithData(c *gin.Context, code int, msgID string, templateData map[string]any) { localizer := GetLocalizerFromContext(c) message := T(localizer, msgID, templateData) - + c.JSON(code, gin.H{ "success": false, "message": message, @@ -104,7 +104,7 @@ func ErrorWithData(c *gin.Context, code int, msgID string, templateData map[stri } // Message 获取国际化消息 -func Message(c *gin.Context, msgID string, templateData ...map[string]interface{}) string { +func Message(c *gin.Context, msgID string, templateData ...map[string]any) string { localizer := GetLocalizerFromContext(c) return T(localizer, msgID, templateData...) -} \ No newline at end of file +} diff --git a/internal/response/response.go b/internal/response/response.go index a02e4e7c..db320ec7 100644 --- a/internal/response/response.go +++ b/internal/response/response.go @@ -40,7 +40,7 @@ func Error(c *gin.Context, apiErr *app_errors.APIError) { } // SuccessI18n sends a standardized success response with i18n message. -func SuccessI18n(c *gin.Context, msgID string, data any, templateData ...map[string]interface{}) { +func SuccessI18n(c *gin.Context, msgID string, data any, templateData ...map[string]any) { message := i18n.Message(c, msgID, templateData...) c.JSON(http.StatusOK, SuccessResponse{ Code: 0, @@ -50,7 +50,7 @@ func SuccessI18n(c *gin.Context, msgID string, data any, templateData ...map[str } // ErrorI18n sends a standardized error response with i18n message. -func ErrorI18n(c *gin.Context, httpStatus int, code string, msgID string, templateData ...map[string]interface{}) { +func ErrorI18n(c *gin.Context, httpStatus int, code string, msgID string, templateData ...map[string]any) { message := i18n.Message(c, msgID, templateData...) c.JSON(httpStatus, ErrorResponse{ Code: code, @@ -59,7 +59,7 @@ func ErrorI18n(c *gin.Context, httpStatus int, code string, msgID string, templa } // ErrorI18nFromAPIError sends a standardized error response using an APIError with i18n message. -func ErrorI18nFromAPIError(c *gin.Context, apiErr *app_errors.APIError, msgID string, templateData ...map[string]interface{}) { +func ErrorI18nFromAPIError(c *gin.Context, apiErr *app_errors.APIError, msgID string, templateData ...map[string]any) { message := i18n.Message(c, msgID, templateData...) c.JSON(apiErr.HTTPStatus, ErrorResponse{ Code: apiErr.Code,