From 2eab6def8b1693d4e074a94f7d0991a1685a3ba8 Mon Sep 17 00:00:00 2001 From: Javier Marcos <1271349+javuto@users.noreply.github.com> Date: Fri, 26 Jun 2026 19:02:23 +0200 Subject: [PATCH 1/3] Completed i8n in es and two bugs fixed --- backend/cmd/map/handlers/admin.go | 670 +++++++++++++------------- backend/cmd/map/handlers/auth.go | 28 +- backend/cmd/map/handlers/auth_test.go | 40 ++ backend/cmd/map/handlers/json.go | 68 +-- backend/cmd/map/handlers/post.go | 144 +++--- backend/cmd/map/handlers/profile.go | 48 +- backend/cmd/map/handlers/throttle.go | 2 +- 7 files changed, 520 insertions(+), 480 deletions(-) diff --git a/backend/cmd/map/handlers/admin.go b/backend/cmd/map/handlers/admin.go index 5c60bf9..94e404c 100644 --- a/backend/cmd/map/handlers/admin.go +++ b/backend/cmd/map/handlers/admin.go @@ -724,13 +724,13 @@ func (h *HandlersMap) AdminSettingsPOSTHandler(w http.ResponseWriter, r *http.Re if strings.Contains(r.Header.Get(ContentType), JSONApplication) { if err := json.NewDecoder(r.Body).Decode(&req); err != nil { log.Err(err).Msg("error parsing admin settings JSON payload") - writeError(http.StatusBadRequest, "Invalid JSON payload") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_json_payload")) return } } else { if err := r.ParseForm(); err != nil { log.Err(err).Msg("error parsing admin settings form") - writeError(http.StatusBadRequest, "Invalid form payload") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_form_payload")) return } req.SettingName = r.FormValue("setting_name") @@ -746,19 +746,19 @@ func (h *HandlersMap) AdminSettingsPOSTHandler(w http.ResponseWriter, r *http.Re settingValue = strings.TrimSpace(req.Value) } if settingName == "" { - writeError(http.StatusBadRequest, "Missing setting_name") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.missing_setting_name")) return } setBoolSetting := func(setter func(bool, string, string) error, setting string) bool { parsed, err := strconv.ParseBool(strings.ToLower(settingValue)) if err != nil { - writeError(http.StatusBadRequest, "Invalid boolean for "+setting) + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_boolean_for", setting)) return false } if err := setter(parsed, username, uuid); err != nil { log.Err(err).Msgf("error updating %s", setting) - writeError(http.StatusInternalServerError, "Failed to update "+setting) + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_update_setting", setting)) return false } return true @@ -766,12 +766,12 @@ func (h *HandlersMap) AdminSettingsPOSTHandler(w http.ResponseWriter, r *http.Re setIntSetting := func(setter func(int, string, string) error, setting string) bool { parsed, err := strconv.Atoi(settingValue) if err != nil { - writeError(http.StatusBadRequest, "Invalid integer for "+setting) + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_integer_for", setting)) return false } if err := setter(parsed, username, uuid); err != nil { log.Err(err).Msgf("error updating %s", setting) - writeError(http.StatusInternalServerError, "Failed to update "+setting) + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_update_setting", setting)) return false } return true @@ -805,7 +805,7 @@ func (h *HandlersMap) AdminSettingsPOSTHandler(w http.ResponseWriter, r *http.Re case "registration_token": if err := h.Settings.SetRegistrationToken(settingValue, username, uuid); err != nil { log.Err(err).Msg("error updating registration_token") - writeError(http.StatusInternalServerError, "Failed to update registration_token") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_update_reg_token")) return } case "scoring_enabled": @@ -831,19 +831,19 @@ func (h *HandlersMap) AdminSettingsPOSTHandler(w http.ResponseWriter, r *http.Re case "custom_org": if err := h.Settings.SetCustomOrg(settingValue, username, uuid); err != nil { log.Err(err).Msg("error updating custom_org") - writeError(http.StatusInternalServerError, "Failed to update custom_org") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_update_custom_org")) return } case "custom_logo": if err := h.Settings.SetCustomLogo(settingValue, username, uuid); err != nil { log.Err(err).Msg("error updating custom_logo") - writeError(http.StatusInternalServerError, "Failed to update custom_logo") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_update_custom_logo")) return } case "language": if err := h.Settings.SetLanguage(settingValue, username, uuid); err != nil { log.Err(err).Msg("error updating language") - writeError(http.StatusInternalServerError, "Failed to update language") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_update_language")) return } case "leaderboard_limit": @@ -857,31 +857,31 @@ func (h *HandlersMap) AdminSettingsPOSTHandler(w http.ResponseWriter, r *http.Re case "game_start_time": gameStartTime, err := time.ParseInLocation("2006-01-02T15:04", settingValue, time.Local) if err != nil { - writeError(http.StatusBadRequest, "Invalid game_start_time format") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_game_start_format")) return } if err := h.Settings.SetGameStartTime(gameStartTime, username, uuid); err != nil { log.Err(err).Msg("error updating game_start_time") - writeError(http.StatusInternalServerError, "Failed to update game_start_time") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_update_game_start")) return } case "game_end_time": gameEndTime, err := time.ParseInLocation("2006-01-02T15:04", settingValue, time.Local) if err != nil { - writeError(http.StatusBadRequest, "Invalid game_end_time format") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_game_end_format")) return } if err := h.Settings.SetGameEndTime(gameEndTime, username, uuid); err != nil { log.Err(err).Msg("error updating game_end_time") - writeError(http.StatusInternalServerError, "Failed to update game_end_time") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_update_game_end")) return } default: - writeError(http.StatusBadRequest, "Unsupported setting") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.unsupported_setting")) return } - writeSuccess("Updated " + settingName) + writeSuccess(h.T(r.Context())("admin.msg.updated_setting", settingName)) } func (h *HandlersMap) buildAdminSettingsTransferPayload(uuid string) (adminSettingsTransferPayload, error) { @@ -1371,25 +1371,25 @@ func (h *HandlersMap) AdminGameExportHandler(w http.ResponseWriter, r *http.Requ settingsPayload, err := h.buildAdminSettingsTransferPayload(uuid) if err != nil { log.Err(err).Msg("error loading settings for full-game export") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{Success: false, Status: "error", Message: "Failed to load settings"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{Success: false, Status: "error", Message: h.T(r.Context())("admin.msg.failed_load_settings")}) return } usersPayload, err := h.buildAdminUsersTransferPayload(uuid) if err != nil { log.Err(err).Msg("error loading users for full-game export") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{Success: false, Status: "error", Message: "Failed to load users"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{Success: false, Status: "error", Message: h.T(r.Context())("admin.msg.failed_load_users")}) return } teamsPayload, err := h.buildAdminTeamsTransferPayload(uuid) if err != nil { log.Err(err).Msg("error loading teams for full-game export") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{Success: false, Status: "error", Message: "Failed to load teams/logos"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{Success: false, Status: "error", Message: h.T(r.Context())("admin.msg.failed_load_teams_logos")}) return } challengesPayload, err := h.buildAdminChallengesTransferPayload(uuid) if err != nil { log.Err(err).Msg("error loading challenges for full-game export") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{Success: false, Status: "error", Message: "Failed to load challenges/categories"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{Success: false, Status: "error", Message: h.T(r.Context())("admin.msg.failed_load_challenges")}) return } @@ -1405,7 +1405,7 @@ func (h *HandlersMap) AdminGameExportHandler(w http.ResponseWriter, r *http.Requ output, err := json.MarshalIndent(payload, "", " ") if err != nil { log.Err(err).Msg("error marshaling full-game export JSON") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{Success: false, Status: "error", Message: "Failed to generate export JSON"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{Success: false, Status: "error", Message: h.T(r.Context())("admin.msg.failed_export_json")}) return } @@ -1448,14 +1448,14 @@ func (h *HandlersMap) AdminGameImportHandler(w http.ResponseWriter, r *http.Requ isJSON := strings.Contains(contentType, JSONApplication) isMultipart := strings.Contains(contentType, "multipart/form-data") if !isJSON && !isMultipart { - writeError(http.StatusUnsupportedMediaType, "Content-Type must be application/json or multipart/form-data") + writeError(http.StatusUnsupportedMediaType, h.T(r.Context())("admin.msg.content_type_form")) return } var payload adminGameTransferPayload if err := h.decodeGameImportPayload(r, &payload); err != nil { log.Err(err).Msg("error parsing full-game import payload") - writeError(http.StatusBadRequest, "Invalid full-game import payload") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_full_game_payload")) return } @@ -1474,32 +1474,32 @@ func (h *HandlersMap) AdminGameImportHandler(w http.ResponseWriter, r *http.Requ logosCreated, logosUpdated, logosSkipped, err := h.importAdminTeamLogosFromPayload(uuid, payload.Teams.Logos) if err != nil { log.Err(err).Msg("error importing full-game logos") - writeError(http.StatusInternalServerError, "Failed importing logos") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_importing_logos")) return } teamsCreated, teamsUpdated, teamsSkipped, err := h.importAdminTeamsFromPayload(uuid, payload.Teams.Teams) if err != nil { log.Err(err).Msg("error importing full-game teams") - writeError(http.StatusInternalServerError, "Failed importing teams") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_importing_teams")) return } if err := h.Teams.SyncLogoUsage(uuid); err != nil { log.Err(err).Msg("error syncing logo usage after full-game teams import") - writeError(http.StatusInternalServerError, "Import completed but failed to sync logo usage") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.import_sync_fail")) return } usersCreated, usersUpdated, usersSkipped, err := h.importAdminUsersFromPayload(uuid, payload.Users.Users) if err != nil { log.Err(err).Msg("error importing full-game users") - writeError(http.StatusInternalServerError, "Failed importing users") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_importing_users")) return } challengesImported, categoriesCreated, challengesSkipped, challengesNoCountry, err := h.importAdminChallengesFromPayload(uuid, payload.Challenges) if err != nil { log.Err(err).Msg("error importing full-game challenges") - writeError(http.StatusInternalServerError, "Failed importing challenges") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_importing_challenges")) return } @@ -1507,10 +1507,10 @@ func (h *HandlersMap) AdminGameImportHandler(w http.ResponseWriter, r *http.Requ "settings updated " + strconv.Itoa(settingsUpdated), "logos created " + strconv.Itoa(logosCreated), "logos updated " + strconv.Itoa(logosUpdated), - "teams created " + strconv.Itoa(teamsCreated), - "teams updated " + strconv.Itoa(teamsUpdated), - "users created " + strconv.Itoa(usersCreated), - "users updated " + strconv.Itoa(usersUpdated), + h.T(r.Context())("admin.msg.teams_created", teamsCreated), + h.T(r.Context())("admin.msg.teams_updated", teamsUpdated), + h.T(r.Context())("admin.msg.users_created", usersCreated), + h.T(r.Context())("admin.msg.users_updated", usersUpdated), "challenges imported " + strconv.Itoa(challengesImported), "categories created " + strconv.Itoa(categoriesCreated), } @@ -1518,10 +1518,10 @@ func (h *HandlersMap) AdminGameImportHandler(w http.ResponseWriter, r *http.Requ messageParts = append(messageParts, "settings skipped "+strconv.Itoa(settingsSkipped)) } if logosSkipped > 0 { - messageParts = append(messageParts, "logos skipped "+strconv.Itoa(logosSkipped)) + messageParts = append(messageParts, h.T(r.Context())("admin.msg.logos_skipped", logosSkipped)) } if teamsSkipped > 0 { - messageParts = append(messageParts, "teams skipped "+strconv.Itoa(teamsSkipped)) + messageParts = append(messageParts, h.T(r.Context())("admin.msg.teams_skipped", teamsSkipped)) } if usersSkipped > 0 { messageParts = append(messageParts, "users skipped "+strconv.Itoa(usersSkipped)) @@ -1533,7 +1533,7 @@ func (h *HandlersMap) AdminGameImportHandler(w http.ResponseWriter, r *http.Requ messageParts = append(messageParts, "challenges without country "+strconv.Itoa(challengesNoCountry)) } - writeSuccess("Full game import complete: " + strings.Join(messageParts, ", ")) + writeSuccess(h.T(r.Context())("admin.msg.full_import_complete") + strings.Join(messageParts, ", ")) } // AdminSettingsExportHandler exports settings as JSON @@ -1555,7 +1555,7 @@ func (h *HandlersMap) AdminSettingsExportHandler(w http.ResponseWriter, r *http. HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{ Success: false, Status: "error", - Message: "Failed to load settings", + Message: h.T(r.Context())("admin.msg.failed_load_settings"), }) return } @@ -1597,14 +1597,14 @@ func (h *HandlersMap) AdminSettingsImportHandler(w http.ResponseWriter, r *http. isJSON := strings.Contains(contentType, JSONApplication) isMultipart := strings.Contains(contentType, "multipart/form-data") if !isJSON && !isMultipart { - writeError(http.StatusUnsupportedMediaType, "Content-Type must be application/json or multipart/form-data") + writeError(http.StatusUnsupportedMediaType, h.T(r.Context())("admin.msg.content_type_form")) return } var payload adminSettingsTransferPayload if err := h.decodeSettingsImportPayload(r, &payload); err != nil { log.Err(err).Msg("error parsing settings import payload") - writeError(http.StatusBadRequest, "Invalid settings import payload") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_settings_payload")) return } @@ -1624,7 +1624,7 @@ func (h *HandlersMap) AdminSettingsImportHandler(w http.ResponseWriter, r *http. if skippedSettings > 0 { messageParts = append(messageParts, "settings skipped "+strconv.Itoa(skippedSettings)) } - writeSuccess("Import complete: " + strings.Join(messageParts, ", ")) + writeSuccess(h.T(r.Context())("admin.msg.import_complete") + strings.Join(messageParts, ", ")) } // AdminSettingsResetDefaultsPOSTHandler resets settings to default values @@ -1687,12 +1687,12 @@ func (h *HandlersMap) AdminSettingsResetDefaultsPOSTHandler(w http.ResponseWrite for _, update := range updates { if err := update(); err != nil { log.Err(err).Msg("error resetting settings to defaults") - writeError(http.StatusInternalServerError, "Failed resetting settings to defaults") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_resetting_defaults")) return } } - writeSuccess("Settings reset to defaults") + writeSuccess(h.T(r.Context())("admin.msg.settings_reset_defaults")) } // AdminControlsTemplateHandler for admin controls page for GET requests @@ -1878,23 +1878,23 @@ func (h *HandlersMap) AdminTeamsPOSTHandler(w http.ResponseWriter, r *http.Reque }) } if !strings.EqualFold(r.Header.Get("X-Requested-With"), "XMLHttpRequest") { - writeError(http.StatusBadRequest, "AJAX requests only") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.ajax_only")) return } if !strings.Contains(strings.ToLower(r.Header.Get(ContentType)), JSONApplication) { - writeError(http.StatusUnsupportedMediaType, "Content-Type must be application/json") + writeError(http.StatusUnsupportedMediaType, h.T(r.Context())("feed.content_type")) return } var req AdminTeamCreateRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { log.Err(err).Msg("error parsing admin teams JSON payload") - writeError(http.StatusBadRequest, "Invalid JSON payload") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_json_payload")) return } name := strings.TrimSpace(req.Name) logo := strings.TrimSpace(req.Logo) if name == "" { - writeError(http.StatusBadRequest, "Team name is required") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.team_name_required")) return } if _, err := h.Teams.Register(name, logo, uuid); err != nil { @@ -1902,7 +1902,7 @@ func (h *HandlersMap) AdminTeamsPOSTHandler(w http.ResponseWriter, r *http.Reque writeError(http.StatusBadRequest, err.Error()) return } - writeSuccess("Team created") + writeSuccess(h.T(r.Context())("admin.msg.team_created")) } func (h *HandlersMap) importAdminTeamLogosFromPayload(uuid string, logos []adminTeamsTransferLogo) (int, int, int, error) { @@ -2061,7 +2061,7 @@ func (h *HandlersMap) AdminTeamsExportHandler(w http.ResponseWriter, r *http.Req HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{ Success: false, Status: "error", - Message: "Failed to load teams/logos", + Message: h.T(r.Context())("admin.msg.failed_load_teams_logos"), }) return } @@ -2072,7 +2072,7 @@ func (h *HandlersMap) AdminTeamsExportHandler(w http.ResponseWriter, r *http.Req HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{ Success: false, Status: "error", - Message: "Failed to generate export JSON", + Message: h.T(r.Context())("admin.msg.failed_export_json"), }) return } @@ -2103,7 +2103,7 @@ func (h *HandlersMap) AdminTeamsExportTeamsHandler(w http.ResponseWriter, r *htt HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{ Success: false, Status: "error", - Message: "Failed to load teams", + Message: h.T(r.Context())("admin.msg.failed_load_teams"), }) return } @@ -2132,7 +2132,7 @@ func (h *HandlersMap) AdminTeamsExportTeamsHandler(w http.ResponseWriter, r *htt HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{ Success: false, Status: "error", - Message: "Failed to generate export JSON", + Message: h.T(r.Context())("admin.msg.failed_export_json"), }) return } @@ -2163,7 +2163,7 @@ func (h *HandlersMap) AdminTeamsExportLogosHandler(w http.ResponseWriter, r *htt HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{ Success: false, Status: "error", - Message: "Failed to load logos", + Message: h.T(r.Context())("admin.msg.failed_load_logos"), }) return } @@ -2193,7 +2193,7 @@ func (h *HandlersMap) AdminTeamsExportLogosHandler(w http.ResponseWriter, r *htt HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{ Success: false, Status: "error", - Message: "Failed to generate export JSON", + Message: h.T(r.Context())("admin.msg.failed_export_json"), }) return } @@ -2236,25 +2236,25 @@ func (h *HandlersMap) AdminTeamsImportHandler(w http.ResponseWriter, r *http.Req var payload adminTeamsTransferPayload if err := h.decodeTeamsImportPayload(r, &payload); err != nil { log.Err(err).Msg("error parsing teams import payload") - writeError(http.StatusBadRequest, "Invalid import payload") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_import_payload")) return } if len(payload.Logos) == 0 && len(payload.Teams) == 0 { - writeError(http.StatusBadRequest, "No teams or logos found in import payload") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.no_teams_or_logos_in_payload")) return } createdLogos, updatedLogos, skippedLogos, err := h.importAdminTeamLogosFromPayload(uuid, payload.Logos) if err != nil { log.Err(err).Msg("error importing logos from combined import") - writeError(http.StatusInternalServerError, "Failed to import logos") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_import_logos")) return } var existingTeams []teams.PlatformTeam if err := h.Teams.DB.Where("uuid = ?", uuid).Find(&existingTeams).Error; err != nil { log.Err(err).Msg("error loading existing teams for import") - writeError(http.StatusInternalServerError, "Failed to load teams") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_load_teams")) return } teamsByName := make(map[string]teams.PlatformTeam, len(existingTeams)) @@ -2298,7 +2298,7 @@ func (h *HandlersMap) AdminTeamsImportHandler(w http.ResponseWriter, r *http.Req }) if result.Error != nil { log.Err(result.Error).Msg("error updating team from import") - writeError(http.StatusInternalServerError, "Failed to import teams") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_import_teams")) return } updatedTeams++ @@ -2308,13 +2308,13 @@ func (h *HandlersMap) AdminTeamsImportHandler(w http.ResponseWriter, r *http.Req newTeam, err := h.Teams.New(name, logo, inTeam.Protected, inTeam.Visible, uuid) if err != nil { log.Err(err).Msg("error creating team object from import") - writeError(http.StatusBadRequest, "Failed to import teams") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.failed_import_teams")) return } newTeam.Active = inTeam.Active if err := h.Teams.Create(newTeam); err != nil { log.Err(err).Msg("error saving team from import") - writeError(http.StatusInternalServerError, "Failed to import teams") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_import_teams")) return } createdTeams++ @@ -2323,22 +2323,22 @@ func (h *HandlersMap) AdminTeamsImportHandler(w http.ResponseWriter, r *http.Req messageParts := []string{ "logos created " + strconv.Itoa(createdLogos), "logos updated " + strconv.Itoa(updatedLogos), - "teams created " + strconv.Itoa(createdTeams), - "teams updated " + strconv.Itoa(updatedTeams), + h.T(r.Context())("admin.msg.teams_created", createdTeams), + h.T(r.Context())("admin.msg.teams_updated", updatedTeams), } if skippedLogos > 0 { - messageParts = append(messageParts, "logos skipped "+strconv.Itoa(skippedLogos)) + messageParts = append(messageParts, h.T(r.Context())("admin.msg.logos_skipped", skippedLogos)) } if skippedTeams > 0 { - messageParts = append(messageParts, "teams skipped "+strconv.Itoa(skippedTeams)) + messageParts = append(messageParts, h.T(r.Context())("admin.msg.teams_skipped", skippedTeams)) } if err := h.Teams.SyncLogoUsage(uuid); err != nil { log.Err(err).Msg("error syncing logo usage after teams import") - writeError(http.StatusInternalServerError, "Import completed but failed to sync logo usage") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.import_sync_fail")) return } - writeSuccess("Import complete: " + strings.Join(messageParts, ", ")) + writeSuccess(h.T(r.Context())("admin.msg.import_complete") + strings.Join(messageParts, ", ")) } // AdminTeamsImportTeamsHandler imports teams only from JSON payload/file @@ -2364,29 +2364,29 @@ func (h *HandlersMap) AdminTeamsImportTeamsHandler(w http.ResponseWriter, r *htt var payload adminTeamsTransferPayload if err := h.decodeTeamsImportPayload(r, &payload); err != nil { log.Err(err).Msg("error parsing teams-only import payload") - writeError(http.StatusBadRequest, "Invalid import payload") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_import_payload")) return } if len(payload.Teams) == 0 { - writeError(http.StatusBadRequest, "No teams found in import payload") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.no_teams_in_payload")) return } createdTeams, updatedTeams, skippedTeams, err := h.importAdminTeamsFromPayload(uuid, payload.Teams) if err != nil { log.Err(err).Msg("error importing teams") - writeError(http.StatusInternalServerError, "Failed to import teams") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_import_teams")) return } messageParts := []string{ - "teams created " + strconv.Itoa(createdTeams), - "teams updated " + strconv.Itoa(updatedTeams), + h.T(r.Context())("admin.msg.teams_created", createdTeams), + h.T(r.Context())("admin.msg.teams_updated", updatedTeams), } if skippedTeams > 0 { - messageParts = append(messageParts, "teams skipped "+strconv.Itoa(skippedTeams)) + messageParts = append(messageParts, h.T(r.Context())("admin.msg.teams_skipped", skippedTeams)) } - writeSuccess("Import complete: " + strings.Join(messageParts, ", ")) + writeSuccess(h.T(r.Context())("admin.msg.import_complete") + strings.Join(messageParts, ", ")) } // AdminTeamsImportLogosHandler imports logos only from JSON payload/file @@ -2412,18 +2412,18 @@ func (h *HandlersMap) AdminTeamsImportLogosHandler(w http.ResponseWriter, r *htt var payload adminTeamsTransferPayload if err := h.decodeTeamsImportPayload(r, &payload); err != nil { log.Err(err).Msg("error parsing logos-only import payload") - writeError(http.StatusBadRequest, "Invalid import payload") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_import_payload")) return } if len(payload.Logos) == 0 { - writeError(http.StatusBadRequest, "No logos found in import payload") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.no_logos_in_payload")) return } createdLogos, updatedLogos, skippedLogos, err := h.importAdminTeamLogosFromPayload(uuid, payload.Logos) if err != nil { log.Err(err).Msg("error importing logos") - writeError(http.StatusInternalServerError, "Failed to import logos") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_import_logos")) return } @@ -2432,14 +2432,14 @@ func (h *HandlersMap) AdminTeamsImportLogosHandler(w http.ResponseWriter, r *htt "logos updated " + strconv.Itoa(updatedLogos), } if skippedLogos > 0 { - messageParts = append(messageParts, "logos skipped "+strconv.Itoa(skippedLogos)) + messageParts = append(messageParts, h.T(r.Context())("admin.msg.logos_skipped", skippedLogos)) } if err := h.Teams.SyncLogoUsage(uuid); err != nil { log.Err(err).Msg("error syncing logo usage after logos import") - writeError(http.StatusInternalServerError, "Import completed but failed to sync logo usage") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.import_sync_fail")) return } - writeSuccess("Import complete: " + strings.Join(messageParts, ", ")) + writeSuccess(h.T(r.Context())("admin.msg.import_complete") + strings.Join(messageParts, ", ")) } // AdminTeamsEnableAllPOSTHandler enables all teams @@ -2456,10 +2456,10 @@ func (h *HandlersMap) AdminTeamsEnableAllPOSTHandler(w http.ResponseWriter, r *h result := h.Teams.DB.Model(&teams.PlatformTeam{}).Where("uuid = ?", uuid).Update("active", true) if result.Error != nil { log.Err(result.Error).Msg("error enabling all teams") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{Success: false, Status: "error", Message: "Failed to enable all teams"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{Success: false, Status: "error", Message: h.T(r.Context())("admin.msg.failed_enable_teams")}) return } - HTTPResponse(w, JSONApplicationUTF8, http.StatusOK, adminActionResponse{Success: true, Status: "ok", Message: "Enabled " + strconv.FormatInt(result.RowsAffected, 10) + " team(s)"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusOK, adminActionResponse{Success: true, Status: "ok", Message: h.T(r.Context())("admin.msg.enabled_teams", result.RowsAffected)}) } // AdminTeamsDisableAllPOSTHandler disables all teams @@ -2476,10 +2476,10 @@ func (h *HandlersMap) AdminTeamsDisableAllPOSTHandler(w http.ResponseWriter, r * result := h.Teams.DB.Model(&teams.PlatformTeam{}).Where("uuid = ?", uuid).Update("active", false) if result.Error != nil { log.Err(result.Error).Msg("error disabling all teams") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{Success: false, Status: "error", Message: "Failed to disable all teams"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{Success: false, Status: "error", Message: h.T(r.Context())("admin.msg.failed_disable_teams")}) return } - HTTPResponse(w, JSONApplicationUTF8, http.StatusOK, adminActionResponse{Success: true, Status: "ok", Message: "Disabled " + strconv.FormatInt(result.RowsAffected, 10) + " team(s)"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusOK, adminActionResponse{Success: true, Status: "ok", Message: h.T(r.Context())("admin.msg.disabled_teams", result.RowsAffected)}) } // AdminTeamsVisibleAllPOSTHandler sets all teams visible @@ -2496,10 +2496,10 @@ func (h *HandlersMap) AdminTeamsVisibleAllPOSTHandler(w http.ResponseWriter, r * result := h.Teams.DB.Model(&teams.PlatformTeam{}).Where("uuid = ?", uuid).Update("visible", true) if result.Error != nil { log.Err(result.Error).Msg("error setting all teams visible") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{Success: false, Status: "error", Message: "Failed to make all teams visible"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{Success: false, Status: "error", Message: h.T(r.Context())("admin.msg.failed_visible_teams")}) return } - HTTPResponse(w, JSONApplicationUTF8, http.StatusOK, adminActionResponse{Success: true, Status: "ok", Message: "Set visible for " + strconv.FormatInt(result.RowsAffected, 10) + " team(s)"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusOK, adminActionResponse{Success: true, Status: "ok", Message: h.T(r.Context())("admin.msg.visible_teams", result.RowsAffected)}) } // AdminTeamsInvisibleAllPOSTHandler sets all teams invisible @@ -2516,10 +2516,10 @@ func (h *HandlersMap) AdminTeamsInvisibleAllPOSTHandler(w http.ResponseWriter, r result := h.Teams.DB.Model(&teams.PlatformTeam{}).Where("uuid = ?", uuid).Update("visible", false) if result.Error != nil { log.Err(result.Error).Msg("error setting all teams invisible") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{Success: false, Status: "error", Message: "Failed to make all teams invisible"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{Success: false, Status: "error", Message: h.T(r.Context())("admin.msg.failed_invisible_teams")}) return } - HTTPResponse(w, JSONApplicationUTF8, http.StatusOK, adminActionResponse{Success: true, Status: "ok", Message: "Set invisible for " + strconv.FormatInt(result.RowsAffected, 10) + " team(s)"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusOK, adminActionResponse{Success: true, Status: "ok", Message: h.T(r.Context())("admin.msg.invisible_teams", result.RowsAffected)}) } // AdminTeamLogosEnableAllPOSTHandler enables all team logos @@ -2536,10 +2536,10 @@ func (h *HandlersMap) AdminTeamLogosEnableAllPOSTHandler(w http.ResponseWriter, result := h.Teams.DB.Model(&teams.TeamLogo{}).Where("uuid = ?", uuid).Update("enabled", true) if result.Error != nil { log.Err(result.Error).Msg("error enabling all logos") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{Success: false, Status: "error", Message: "Failed to enable all logos"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{Success: false, Status: "error", Message: h.T(r.Context())("admin.msg.failed_enable_logos")}) return } - HTTPResponse(w, JSONApplicationUTF8, http.StatusOK, adminActionResponse{Success: true, Status: "ok", Message: "Enabled " + strconv.FormatInt(result.RowsAffected, 10) + " logo(s)"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusOK, adminActionResponse{Success: true, Status: "ok", Message: h.T(r.Context())("admin.msg.enabled_logos", result.RowsAffected)}) } // AdminTeamLogosDisableAllPOSTHandler disables all team logos @@ -2556,10 +2556,10 @@ func (h *HandlersMap) AdminTeamLogosDisableAllPOSTHandler(w http.ResponseWriter, result := h.Teams.DB.Model(&teams.TeamLogo{}).Where("uuid = ?", uuid).Update("enabled", false) if result.Error != nil { log.Err(result.Error).Msg("error disabling all logos") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{Success: false, Status: "error", Message: "Failed to disable all logos"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{Success: false, Status: "error", Message: h.T(r.Context())("admin.msg.failed_disable_logos")}) return } - HTTPResponse(w, JSONApplicationUTF8, http.StatusOK, adminActionResponse{Success: true, Status: "ok", Message: "Disabled " + strconv.FormatInt(result.RowsAffected, 10) + " logo(s)"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusOK, adminActionResponse{Success: true, Status: "ok", Message: h.T(r.Context())("admin.msg.disabled_logos", result.RowsAffected)}) } // AdminTeamLogosDeleteAllPOSTHandler deletes all custom team logos for the map UUID. @@ -2576,7 +2576,7 @@ func (h *HandlersMap) AdminTeamLogosDeleteAllPOSTHandler(w http.ResponseWriter, var customs []teams.TeamLogo if err := h.Teams.DB.Where("uuid = ? AND custom = ?", uuid, true).Find(&customs).Error; err != nil { log.Err(err).Msg("error loading custom logos for delete-all") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{Success: false, Status: "error", Message: "Failed to load custom logos"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{Success: false, Status: "error", Message: h.T(r.Context())("admin.msg.failed_load_custom_logos")}) return } for _, row := range customs { @@ -2585,15 +2585,15 @@ func (h *HandlersMap) AdminTeamLogosDeleteAllPOSTHandler(w http.ResponseWriter, result := h.Teams.DB.Where("uuid = ? AND custom = ?", uuid, true).Delete(&teams.TeamLogo{}) if result.Error != nil { log.Err(result.Error).Msg("error deleting custom logos") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{Success: false, Status: "error", Message: "Failed to delete custom logos"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{Success: false, Status: "error", Message: h.T(r.Context())("admin.msg.failed_delete_logos")}) return } if err := h.Teams.SyncLogoUsage(uuid); err != nil { log.Err(err).Msg("error syncing logo usage after delete-all custom logos") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{Success: false, Status: "error", Message: "Custom logos deleted but failed to sync logo usage"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{Success: false, Status: "error", Message: h.T(r.Context())("admin.msg.logos_deleted_sync_fail")}) return } - HTTPResponse(w, JSONApplicationUTF8, http.StatusOK, adminActionResponse{Success: true, Status: "ok", Message: "Deleted " + strconv.FormatInt(result.RowsAffected, 10) + " custom logo(s); platform logos unchanged"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusOK, adminActionResponse{Success: true, Status: "ok", Message: h.T(r.Context())("admin.msg.deleted_logos", result.RowsAffected)}) } func (h *HandlersMap) removeCustomUploadedLogoFile(logoSymbol string) { @@ -2652,7 +2652,7 @@ func (h *HandlersMap) AdminTeamsDeleteAllPOSTHandler(w http.ResponseWriter, r *h tx := h.Teams.DB.Begin() if tx.Error != nil { log.Err(tx.Error).Msg("error starting delete-all-teams transaction") - writeError(http.StatusInternalServerError, "Failed to delete all teams") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_delete_all_teams")) return } defer func() { @@ -2665,40 +2665,40 @@ func (h *HandlersMap) AdminTeamsDeleteAllPOSTHandler(w http.ResponseWriter, r *h if err := tx.Model(&users.PlatformUser{}).Where("uuid = ?", uuid).Update("team_id", users.NoTeamID).Error; err != nil { tx.Rollback() log.Err(err).Msg("error clearing user teams before bulk delete") - writeError(http.StatusInternalServerError, "Failed to delete all teams") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_delete_all_teams")) return } if err := tx.Where("uuid = ?", uuid).Delete(&teams.TeamMembership{}).Error; err != nil { tx.Rollback() log.Err(err).Msg("error deleting memberships before bulk delete") - writeError(http.StatusInternalServerError, "Failed to delete all teams") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_delete_all_teams")) return } if err := tx.Where("uuid = ?", uuid).Delete(&teams.TeamScore{}).Error; err != nil { tx.Rollback() log.Err(err).Msg("error deleting scores before bulk delete") - writeError(http.StatusInternalServerError, "Failed to delete all teams") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_delete_all_teams")) return } deleteResult := tx.Where("uuid = ?", uuid).Delete(&teams.PlatformTeam{}) if deleteResult.Error != nil { tx.Rollback() log.Err(deleteResult.Error).Msg("error deleting all teams") - writeError(http.StatusInternalServerError, "Failed to delete all teams") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_delete_all_teams")) return } if err := tx.Commit().Error; err != nil { log.Err(err).Msg("error committing delete-all-teams transaction") - writeError(http.StatusInternalServerError, "Failed to delete all teams") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_delete_all_teams")) return } if err := h.Teams.SyncLogoUsage(uuid); err != nil { log.Err(err).Msg("error syncing logo usage after delete-all-teams") - writeError(http.StatusInternalServerError, "Deleted teams but failed to sync logo usage") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.deleted_team_sync_fail")) return } - writeSuccess("Deleted " + strconv.FormatInt(deleteResult.RowsAffected, 10) + " team(s)") + writeSuccess(h.T(r.Context())("admin.msg.deleted_team_count", deleteResult.RowsAffected)) } // AdminTeamUpdatePOSTHandler updates editable team settings from admin view @@ -2720,7 +2720,7 @@ func (h *HandlersMap) AdminTeamUpdatePOSTHandler(w http.ResponseWriter, r *http. HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, adminActionResponse{ Success: false, Status: "error", - Message: "Invalid team id", + Message: h.T(r.Context())("admin.msg.invalid_team_id"), }) return } @@ -2741,24 +2741,24 @@ func (h *HandlersMap) AdminTeamUpdatePOSTHandler(w http.ResponseWriter, r *http. } if !strings.EqualFold(r.Header.Get("X-Requested-With"), "XMLHttpRequest") { - writeError(http.StatusBadRequest, "AJAX requests only") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.ajax_only")) return } if !strings.Contains(strings.ToLower(r.Header.Get(ContentType)), JSONApplication) { - writeError(http.StatusUnsupportedMediaType, "Content-Type must be application/json") + writeError(http.StatusUnsupportedMediaType, h.T(r.Context())("feed.content_type")) return } var req AdminTeamUpdateRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { log.Err(err).Msg("error parsing admin team update JSON payload") - writeError(http.StatusBadRequest, "Invalid JSON payload") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_json_payload")) return } name := strings.TrimSpace(req.Name) if name == "" { - writeError(http.StatusBadRequest, "Team name is required") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.team_name_required")) return } var duplicateCount int64 @@ -2766,25 +2766,25 @@ func (h *HandlersMap) AdminTeamUpdatePOSTHandler(w http.ResponseWriter, r *http. Where("uuid = ? AND name = ? AND id <> ?", uuid, name, uint(teamID)). Count(&duplicateCount).Error; err != nil { log.Err(err).Msg("error validating team name uniqueness") - writeError(http.StatusInternalServerError, "Failed to validate team name") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_validate_team_name")) return } if duplicateCount > 0 { - writeError(http.StatusBadRequest, "Team name already exists") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.team_name_already_exists")) return } logoInput := strings.TrimSpace(req.Logo) logo := normalizeLogoValue(logoInput) if logoInput == "" { - writeError(http.StatusBadRequest, "Logo is required") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.logo_is_required")) return } if strings.EqualFold(logoInput, "random") { randomLogo, err := h.Teams.RandomLogo(uuid) if err != nil { log.Err(err).Msg("error getting random logo for team update") - writeError(http.StatusInternalServerError, "Failed to resolve random logo") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_resolve_random_logo")) return } logo = normalizeLogoValue(randomLogo.Logo) @@ -2792,19 +2792,19 @@ func (h *HandlersMap) AdminTeamUpdatePOSTHandler(w http.ResponseWriter, r *http. active, err := strconv.ParseBool(strings.ToLower(strings.TrimSpace(req.Active))) if err != nil { - writeError(http.StatusBadRequest, "Invalid active value") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_active")) return } visible, err := strconv.ParseBool(strings.ToLower(strings.TrimSpace(req.Visible))) if err != nil { - writeError(http.StatusBadRequest, "Invalid visible value") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_visible")) return } protected, err := strconv.ParseBool(strings.ToLower(strings.TrimSpace(req.Protected))) if err != nil { - writeError(http.StatusBadRequest, "Invalid protected value") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_protected")) return } @@ -2819,20 +2819,20 @@ func (h *HandlersMap) AdminTeamUpdatePOSTHandler(w http.ResponseWriter, r *http. }) if updateResult.Error != nil { log.Err(updateResult.Error).Msg("error updating team") - writeError(http.StatusInternalServerError, "Failed to update team") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_update_team")) return } if updateResult.RowsAffected == 0 { - writeError(http.StatusNotFound, "Team not found") + writeError(http.StatusNotFound, h.T(r.Context())("admin.msg.team_not_found")) return } if err := h.Teams.SyncLogoUsage(uuid); err != nil { log.Err(err).Msg("error syncing logo usage after team update") - writeError(http.StatusInternalServerError, "Team updated but failed to sync logo usage") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.team_updated_sync_fail")) return } - writeSuccess("Team updated") + writeSuccess(h.T(r.Context())("admin.msg.team_updated")) } // AdminTeamDeletePOSTHandler deletes a team from the admin view @@ -2854,7 +2854,7 @@ func (h *HandlersMap) AdminTeamDeletePOSTHandler(w http.ResponseWriter, r *http. HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, adminActionResponse{ Success: false, Status: "error", - Message: "Invalid team id", + Message: h.T(r.Context())("admin.msg.invalid_team_id"), }) return } @@ -2876,25 +2876,25 @@ func (h *HandlersMap) AdminTeamDeletePOSTHandler(w http.ResponseWriter, r *http. } if !strings.EqualFold(r.Header.Get("X-Requested-With"), "XMLHttpRequest") { - writeError(http.StatusBadRequest, "AJAX requests only") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.ajax_only")) return } var team teams.PlatformTeam if err := h.Teams.DB.Where("id = ? AND uuid = ?", teamID, uuid).First(&team).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - writeError(http.StatusNotFound, "Team not found") + writeError(http.StatusNotFound, h.T(r.Context())("admin.msg.team_not_found")) return } log.Err(err).Msg("error loading team to delete") - writeError(http.StatusInternalServerError, "Failed to load team") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_load_team")) return } tx := h.Teams.DB.Begin() if tx.Error != nil { log.Err(tx.Error).Msg("error starting team delete transaction") - writeError(http.StatusInternalServerError, "Failed to delete team") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_delete_team")) return } defer func() { @@ -2909,21 +2909,21 @@ func (h *HandlersMap) AdminTeamDeletePOSTHandler(w http.ResponseWriter, r *http. Update("team_id", users.NoTeamID).Error; err != nil { tx.Rollback() log.Err(err).Msg("error clearing users team assignment before delete") - writeError(http.StatusInternalServerError, "Failed to delete team") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_delete_team")) return } if err := tx.Where("team_id = ? AND uuid = ?", teamID, uuid).Delete(&teams.TeamMembership{}).Error; err != nil { tx.Rollback() log.Err(err).Msg("error deleting team memberships before delete") - writeError(http.StatusInternalServerError, "Failed to delete team") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_delete_team")) return } if err := tx.Where("team_id = ? AND uuid = ?", teamID, uuid).Delete(&teams.TeamScore{}).Error; err != nil { tx.Rollback() log.Err(err).Msg("error deleting team scores before delete") - writeError(http.StatusInternalServerError, "Failed to delete team") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_delete_team")) return } @@ -2931,27 +2931,27 @@ func (h *HandlersMap) AdminTeamDeletePOSTHandler(w http.ResponseWriter, r *http. if deleteResult.Error != nil { tx.Rollback() log.Err(deleteResult.Error).Msg("error deleting team") - writeError(http.StatusInternalServerError, "Failed to delete team") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_delete_team")) return } if deleteResult.RowsAffected == 0 { tx.Rollback() - writeError(http.StatusNotFound, "Team not found") + writeError(http.StatusNotFound, h.T(r.Context())("admin.msg.team_not_found")) return } if err := tx.Commit().Error; err != nil { log.Err(err).Msg("error committing team delete transaction") - writeError(http.StatusInternalServerError, "Failed to delete team") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_delete_team")) return } if err := h.Teams.SyncLogoUsage(uuid); err != nil { log.Err(err).Msg("error syncing logo usage after team delete") - writeError(http.StatusInternalServerError, "Team deleted but failed to sync logo usage") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.team_deleted_sync_fail")) return } - writeSuccess("Team deleted") + writeSuccess(h.T(r.Context())("admin.msg.team_deleted")) } // AdminTeamLogosPOSTHandler for admin team logos creation via POST requests @@ -2983,14 +2983,14 @@ func (h *HandlersMap) AdminTeamLogosPOSTHandler(w http.ResponseWriter, r *http.R } if !strings.EqualFold(r.Header.Get("X-Requested-With"), "XMLHttpRequest") { - writeError(http.StatusBadRequest, "AJAX requests only") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.ajax_only")) return } contentType := strings.ToLower(r.Header.Get(ContentType)) isJSON := strings.Contains(contentType, JSONApplication) isMultipart := strings.Contains(contentType, "multipart/form-data") if !isJSON && !isMultipart { - writeError(http.StatusUnsupportedMediaType, "Content-Type must be application/json or multipart/form-data") + writeError(http.StatusUnsupportedMediaType, h.T(r.Context())("admin.msg.content_type_form")) return } @@ -3005,7 +3005,7 @@ func (h *HandlersMap) AdminTeamLogosPOSTHandler(w http.ResponseWriter, r *http.R if isMultipart { if err := r.ParseMultipartForm(maxCustomLogoUploadBytes * 2); err != nil { - writeError(http.StatusBadRequest, "Invalid multipart form payload") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_multipart")) return } name = strings.TrimSpace(r.FormValue("name")) @@ -3017,11 +3017,11 @@ func (h *HandlersMap) AdminTeamLogosPOSTHandler(w http.ResponseWriter, r *http.R uploadData, readErr := io.ReadAll(io.LimitReader(file, maxCustomLogoUploadBytes+1)) if readErr != nil { - writeError(http.StatusBadRequest, "Failed to read uploaded logo file") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.failed_read_logo_file")) return } if int64(len(uploadData)) > maxCustomLogoUploadBytes { - writeError(http.StatusBadRequest, "Uploaded logo file is too large") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.uploaded_logo_too_large")) return } @@ -3031,7 +3031,7 @@ func (h *HandlersMap) AdminTeamLogosPOSTHandler(w http.ResponseWriter, r *http.R } slug := sanitizeLogoSlug(slugInput) if slug == "" { - writeError(http.StatusBadRequest, "Invalid logo slug") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_logo_slug")) return } @@ -3041,18 +3041,18 @@ func (h *HandlersMap) AdminTeamLogosPOSTHandler(w http.ResponseWriter, r *http.R ext := strings.ToLower(filepath.Ext(fileHeader.Filename)) if rasterLogoContentTypeForExt(ext) != "" { if rasterErr := validateRasterLogoUpload(ext, uploadData); rasterErr != nil { - writeError(http.StatusBadRequest, "Invalid raster logo file") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_raster_logo")) return } uploadedLogoExt = ext uploadedLogoAsset = true rawLogo = customLogoAssetPath(slug, ext) } else if ext != "" && ext != ".svg" { - writeError(http.StatusBadRequest, "Invalid uploaded logo file type") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_uploaded_logo_type")) return } else { if _, svgErr := buildUploadedLogoSymbol(slug, uploadData); svgErr != nil { - writeError(http.StatusBadRequest, "Invalid SVG file") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_svg")) return } uploadedLogoData = []byte(svgScriptTagPattern.ReplaceAllString(string(uploadData), "")) @@ -3062,17 +3062,17 @@ func (h *HandlersMap) AdminTeamLogosPOSTHandler(w http.ResponseWriter, r *http.R } } else if err != nil { if errors.Is(err, http.ErrMissingFile) { - writeError(http.StatusBadRequest, "Logo file is required") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.logo_file_required")) return } - writeError(http.StatusBadRequest, "Invalid uploaded logo file") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_uploaded_logo")) return } } else { var req AdminLogoCreateRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { log.Err(err).Msg("error parsing admin logos JSON payload") - writeError(http.StatusBadRequest, "Invalid JSON payload") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_json_payload")) return } name = strings.TrimSpace(req.Name) @@ -3080,30 +3080,30 @@ func (h *HandlersMap) AdminTeamLogosPOSTHandler(w http.ResponseWriter, r *http.R } if name == "" { - writeError(http.StatusBadRequest, "Logo name is required") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.logo_name_required")) return } if strings.EqualFold(rawLogo, "random") { - writeError(http.StatusBadRequest, "Random is not a valid logo for logo creation") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.random_not_valid_logo")) return } logo := normalizeLogoValue(rawLogo) if rawLogo == "" || logo == "" { - writeError(http.StatusBadRequest, "Logo symbol is required") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.logo_symbol_required")) return } if h.Teams.ExistsLogo(name, uuid) { - writeError(http.StatusBadRequest, "Logo already exists") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.logo_already_exists")) return } if uploadedLogoSlug != "" && uploadedLogoAsset { if err := saveUploadedLogoAssetFile(h.Config.Map.StaticDir, uploadedLogoSlug, uploadedLogoExt, uploadedLogoData); err != nil { if strings.Contains(strings.ToLower(err.Error()), "already exists") { - writeError(http.StatusBadRequest, "Logo file already exists") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.logo_file_already_exists")) return } log.Err(err).Msg("error saving custom logo file") - writeError(http.StatusInternalServerError, "Failed to store custom logo file") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_store_logo_file")) return } } @@ -3111,16 +3111,16 @@ func (h *HandlersMap) AdminTeamLogosPOSTHandler(w http.ResponseWriter, r *http.R newLogo, err := h.Teams.NewLogo(name, logo, true, true, 0, uuid) if err != nil { log.Err(err).Msg("error creating logo object") - writeError(http.StatusBadRequest, "Failed to create logo") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.failed_create_logo")) return } if err := h.Teams.CreateLogo(newLogo); err != nil { log.Err(err).Msg("error saving logo") - writeError(http.StatusInternalServerError, "Failed to create logo") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_create_logo")) return } - writeSuccess("Logo created") + writeSuccess(h.T(r.Context())("admin.msg.logo_created")) } // AdminTeamLogoUpdatePOSTHandler updates editable team logo settings from admin view @@ -3142,7 +3142,7 @@ func (h *HandlersMap) AdminTeamLogoUpdatePOSTHandler(w http.ResponseWriter, r *h HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, adminActionResponse{ Success: false, Status: "error", - Message: "Invalid logo id", + Message: h.T(r.Context())("admin.msg.invalid_logo_id"), }) return } @@ -3163,52 +3163,52 @@ func (h *HandlersMap) AdminTeamLogoUpdatePOSTHandler(w http.ResponseWriter, r *h } if !strings.EqualFold(r.Header.Get("X-Requested-With"), "XMLHttpRequest") { - writeError(http.StatusBadRequest, "AJAX requests only") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.ajax_only")) return } if !strings.Contains(strings.ToLower(r.Header.Get(ContentType)), JSONApplication) { - writeError(http.StatusUnsupportedMediaType, "Content-Type must be application/json") + writeError(http.StatusUnsupportedMediaType, h.T(r.Context())("feed.content_type")) return } var req AdminLogoUpdateRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { log.Err(err).Msg("error parsing admin logo update JSON payload") - writeError(http.StatusBadRequest, "Invalid JSON payload") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_json_payload")) return } name := strings.TrimSpace(req.Name) if name == "" { - writeError(http.StatusBadRequest, "Logo name is required") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.logo_name_required")) return } enabled, err := strconv.ParseBool(strings.ToLower(strings.TrimSpace(req.Enabled))) if err != nil { - writeError(http.StatusBadRequest, "Invalid enabled value") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_enabled")) return } protected, err := strconv.ParseBool(strings.ToLower(strings.TrimSpace(req.Protected))) if err != nil { - writeError(http.StatusBadRequest, "Invalid protected value") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_protected")) return } var existing teams.TeamLogo if err := h.Teams.DB.Where("id = ? AND uuid = ?", uint(logoID), uuid).First(&existing).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - writeError(http.StatusNotFound, "Logo not found") + writeError(http.StatusNotFound, h.T(r.Context())("admin.msg.logo_not_found")) return } log.Err(err).Msg("error loading logo for update") - writeError(http.StatusInternalServerError, "Failed to load logo") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_load_logo")) return } if !existing.Custom && name != strings.TrimSpace(existing.Name) { - writeError(http.StatusBadRequest, "Platform logo name cannot be changed") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.platform_logo_no_rename")) return } @@ -3224,15 +3224,15 @@ func (h *HandlersMap) AdminTeamLogoUpdatePOSTHandler(w http.ResponseWriter, r *h Updates(updates) if updateResult.Error != nil { log.Err(updateResult.Error).Msg("error updating logo") - writeError(http.StatusInternalServerError, "Failed to update logo") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_update_logo")) return } if updateResult.RowsAffected == 0 { - writeError(http.StatusNotFound, "Logo not found") + writeError(http.StatusNotFound, h.T(r.Context())("admin.msg.logo_not_found")) return } - writeSuccess("Logo updated") + writeSuccess(h.T(r.Context())("admin.msg.logo_updated")) } // AdminUsersTemplateHandler for admin users page for GET requests @@ -3321,18 +3321,18 @@ func (h *HandlersMap) AdminUsersPOSTHandler(w http.ResponseWriter, r *http.Reque } if !strings.EqualFold(r.Header.Get("X-Requested-With"), "XMLHttpRequest") { - writeError(http.StatusBadRequest, "AJAX requests only") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.ajax_only")) return } if !strings.Contains(strings.ToLower(r.Header.Get(ContentType)), JSONApplication) { - writeError(http.StatusUnsupportedMediaType, "Content-Type must be application/json") + writeError(http.StatusUnsupportedMediaType, h.T(r.Context())("feed.content_type")) return } var req AdminUserCreateRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { log.Err(err).Msg("error parsing admin users JSON payload") - writeError(http.StatusBadRequest, "Invalid JSON payload") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_json_payload")) return } @@ -3346,7 +3346,7 @@ func (h *HandlersMap) AdminUsersPOSTHandler(w http.ResponseWriter, r *http.Reque activeStr := strings.TrimSpace(req.Active) if username == "" || password == "" { - writeError(http.StatusBadRequest, "Username and password are required") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.user_pass_required")) return } @@ -3354,7 +3354,7 @@ func (h *HandlersMap) AdminUsersPOSTHandler(w http.ResponseWriter, r *http.Reque if teamIDStr != "" { parsedTeamID, err := strconv.ParseUint(teamIDStr, 10, 64) if err != nil { - writeError(http.StatusBadRequest, "Invalid team_id") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_team_id")) return } teamID = uint(parsedTeamID) @@ -3364,7 +3364,7 @@ func (h *HandlersMap) AdminUsersPOSTHandler(w http.ResponseWriter, r *http.Reque if adminStr != "" { parsedAdmin, err := strconv.ParseBool(strings.ToLower(adminStr)) if err != nil { - writeError(http.StatusBadRequest, "Invalid admin value") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_admin")) return } admin = parsedAdmin @@ -3374,7 +3374,7 @@ func (h *HandlersMap) AdminUsersPOSTHandler(w http.ResponseWriter, r *http.Reque if serviceStr != "" { parsedService, err := strconv.ParseBool(strings.ToLower(serviceStr)) if err != nil { - writeError(http.StatusBadRequest, "Invalid service value") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_service")) return } service = parsedService @@ -3384,7 +3384,7 @@ func (h *HandlersMap) AdminUsersPOSTHandler(w http.ResponseWriter, r *http.Reque if activeStr != "" { parsedActive, err := strconv.ParseBool(strings.ToLower(activeStr)) if err != nil { - writeError(http.StatusBadRequest, "Invalid active value") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_active")) return } active = parsedActive @@ -3399,19 +3399,19 @@ func (h *HandlersMap) AdminUsersPOSTHandler(w http.ResponseWriter, r *http.Reque if err := h.Users.Create(user); err != nil { log.Err(err).Msg("error saving user") - writeError(http.StatusInternalServerError, "Failed to create user") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_create_user")) return } if !active { if err := h.Users.SetActive(false, username, uuid); err != nil { log.Err(err).Msg("error setting active flag for new user") - writeError(http.StatusInternalServerError, "User created but failed to set active flag") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.user_created_active_fail")) return } } - writeSuccess("User created") + writeSuccess(h.T(r.Context())("admin.msg.user_created")) } // AdminUserUpdatePOSTHandler updates editable user settings from admin view @@ -3433,7 +3433,7 @@ func (h *HandlersMap) AdminUserUpdatePOSTHandler(w http.ResponseWriter, r *http. HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, adminActionResponse{ Success: false, Status: "error", - Message: "Invalid user id", + Message: h.T(r.Context())("admin.msg.invalid_user_id"), }) return } @@ -3454,18 +3454,18 @@ func (h *HandlersMap) AdminUserUpdatePOSTHandler(w http.ResponseWriter, r *http. } if !strings.EqualFold(r.Header.Get("X-Requested-With"), "XMLHttpRequest") { - writeError(http.StatusBadRequest, "AJAX requests only") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.ajax_only")) return } if !strings.Contains(strings.ToLower(r.Header.Get(ContentType)), JSONApplication) { - writeError(http.StatusUnsupportedMediaType, "Content-Type must be application/json") + writeError(http.StatusUnsupportedMediaType, h.T(r.Context())("feed.content_type")) return } var req AdminUserUpdateRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { log.Err(err).Msg("error parsing admin user update JSON payload") - writeError(http.StatusBadRequest, "Invalid JSON payload") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_json_payload")) return } @@ -3478,7 +3478,7 @@ func (h *HandlersMap) AdminUserUpdatePOSTHandler(w http.ResponseWriter, r *http. if email != "" { parsedEmail, err := mail.ParseAddress(email) if err != nil || parsedEmail.Address != email { - writeError(http.StatusBadRequest, "email is invalid") + writeError(http.StatusBadRequest, h.T(r.Context())("profile.email_invalid")) return } } @@ -3493,29 +3493,29 @@ func (h *HandlersMap) AdminUserUpdatePOSTHandler(w http.ResponseWriter, r *http. serviceStr := strings.TrimSpace(req.Service) activeStr := strings.TrimSpace(req.Active) if adminStr == "" || serviceStr == "" || activeStr == "" { - writeError(http.StatusBadRequest, "Admin, service and active values are required") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.admin_service_active_required")) return } teamID64, err := strconv.ParseUint(teamIDStr, 10, 64) if err != nil { - writeError(http.StatusBadRequest, "Invalid team_id") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_team_id")) return } teamID := uint(teamID64) adminValue, err := strconv.ParseBool(strings.ToLower(adminStr)) if err != nil { - writeError(http.StatusBadRequest, "Invalid admin value") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_admin")) return } serviceValue, err := strconv.ParseBool(strings.ToLower(serviceStr)) if err != nil { - writeError(http.StatusBadRequest, "Invalid service value") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_service")) return } activeValue, err := strconv.ParseBool(strings.ToLower(activeStr)) if err != nil { - writeError(http.StatusBadRequest, "Invalid active value") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_active")) return } @@ -3523,11 +3523,11 @@ func (h *HandlersMap) AdminUserUpdatePOSTHandler(w http.ResponseWriter, r *http. var teamCount int64 if err := h.Teams.DB.Model(&teams.PlatformTeam{}).Where("id = ? AND uuid = ?", teamID, uuid).Count(&teamCount).Error; err != nil { log.Err(err).Msg("error validating team for user update") - writeError(http.StatusInternalServerError, "Failed to validate team") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_validate_team")) return } if teamCount == 0 { - writeError(http.StatusBadRequest, "Team not found") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.team_not_found")) return } } @@ -3542,15 +3542,15 @@ func (h *HandlersMap) AdminUserUpdatePOSTHandler(w http.ResponseWriter, r *http. Updates(updates) if updateResult.Error != nil { log.Err(updateResult.Error).Msg("error updating user") - writeError(http.StatusInternalServerError, "Failed to update user") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_update_user")) return } if updateResult.RowsAffected == 0 { - writeError(http.StatusNotFound, "User not found") + writeError(http.StatusNotFound, h.T(r.Context())("admin.msg.user_not_found")) return } - writeSuccess("User updated") + writeSuccess(h.T(r.Context())("admin.msg.user_updated")) } // AdminUserPasswordPOSTHandler resets a user's password from the admin view @@ -3572,7 +3572,7 @@ func (h *HandlersMap) AdminUserPasswordPOSTHandler(w http.ResponseWriter, r *htt HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, adminActionResponse{ Success: false, Status: "error", - Message: "Invalid user id", + Message: h.T(r.Context())("admin.msg.invalid_user_id"), }) return } @@ -3593,31 +3593,31 @@ func (h *HandlersMap) AdminUserPasswordPOSTHandler(w http.ResponseWriter, r *htt } if !strings.EqualFold(r.Header.Get("X-Requested-With"), "XMLHttpRequest") { - writeError(http.StatusBadRequest, "AJAX requests only") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.ajax_only")) return } if !strings.Contains(strings.ToLower(r.Header.Get(ContentType)), JSONApplication) { - writeError(http.StatusUnsupportedMediaType, "Content-Type must be application/json") + writeError(http.StatusUnsupportedMediaType, h.T(r.Context())("feed.content_type")) return } var req AdminUserPasswordRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { log.Err(err).Msg("error parsing admin user password JSON payload") - writeError(http.StatusBadRequest, "Invalid JSON payload") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_json_payload")) return } switch { case strings.TrimSpace(req.NewPassword) == "": - writeError(http.StatusBadRequest, "new password is required") + writeError(http.StatusBadRequest, h.T(r.Context())("profile.new_required")) return } passHash, err := h.Users.HashPasswordWithSalt(req.NewPassword) if err != nil { log.Err(err).Msg("error hashing admin user password") - writeError(http.StatusInternalServerError, "Failed to update password") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_update_password")) return } @@ -3626,15 +3626,15 @@ func (h *HandlersMap) AdminUserPasswordPOSTHandler(w http.ResponseWriter, r *htt Update("pass_hash", passHash) if updateResult.Error != nil { log.Err(updateResult.Error).Msg("error updating admin user password") - writeError(http.StatusInternalServerError, "Failed to update password") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_update_password")) return } if updateResult.RowsAffected == 0 { - writeError(http.StatusNotFound, "User not found") + writeError(http.StatusNotFound, h.T(r.Context())("admin.msg.user_not_found")) return } - writeSuccess("Password updated") + writeSuccess(h.T(r.Context())("profile.password_updated_msg")) } // AdminUsersExportHandler exports users as JSON @@ -3656,7 +3656,7 @@ func (h *HandlersMap) AdminUsersExportHandler(w http.ResponseWriter, r *http.Req HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{ Success: false, Status: "error", - Message: "Failed to load users", + Message: h.T(r.Context())("admin.msg.failed_load_users"), }) return } @@ -3667,7 +3667,7 @@ func (h *HandlersMap) AdminUsersExportHandler(w http.ResponseWriter, r *http.Req HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{ Success: false, Status: "error", - Message: "Failed to generate export JSON", + Message: h.T(r.Context())("admin.msg.failed_export_json"), }) return } @@ -3710,18 +3710,18 @@ func (h *HandlersMap) AdminUsersImportHandler(w http.ResponseWriter, r *http.Req var payload adminUsersTransferPayload if err := h.decodeUsersImportPayload(r, &payload); err != nil { log.Err(err).Msg("error parsing users import payload") - writeError(http.StatusBadRequest, "Invalid import payload") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_import_payload")) return } if len(payload.Users) == 0 { - writeError(http.StatusBadRequest, "No users found in import payload") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.no_users_in_payload")) return } createdUsers, updatedUsers, skippedUsers, err := h.importAdminUsersFromPayload(uuid, payload.Users) if err != nil { log.Err(err).Msg("error importing users") - writeError(http.StatusInternalServerError, "Failed to import users") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_import_users")) return } @@ -3733,7 +3733,7 @@ func (h *HandlersMap) AdminUsersImportHandler(w http.ResponseWriter, r *http.Req messageParts = append(messageParts, "users skipped "+strconv.Itoa(skippedUsers)) } - writeSuccess("Import complete: " + strings.Join(messageParts, ", ")) + writeSuccess(h.T(r.Context())("admin.msg.import_complete") + strings.Join(messageParts, ", ")) } // AdminUsersEnableAllPOSTHandler enables all users @@ -3755,7 +3755,7 @@ func (h *HandlersMap) AdminUsersEnableAllPOSTHandler(w http.ResponseWriter, r *h HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{ Success: false, Status: "error", - Message: "Failed to enable all users", + Message: h.T(r.Context())("admin.msg.failed_enable_users"), }) return } @@ -3763,7 +3763,7 @@ func (h *HandlersMap) AdminUsersEnableAllPOSTHandler(w http.ResponseWriter, r *h HTTPResponse(w, JSONApplicationUTF8, http.StatusOK, adminActionResponse{ Success: true, Status: "ok", - Message: "Enabled " + strconv.FormatInt(result.RowsAffected, 10) + " user(s)", + Message: h.T(r.Context())("admin.msg.enabled_users", result.RowsAffected), }) } @@ -3785,7 +3785,7 @@ func (h *HandlersMap) AdminUsersDisableAllPOSTHandler(w http.ResponseWriter, r * HTTPResponse(w, JSONApplicationUTF8, http.StatusUnauthorized, adminActionResponse{ Success: false, Status: "error", - Message: "Not authenticated", + Message: h.T(r.Context())("admin.msg.not_authenticated"), }) return } @@ -3798,7 +3798,7 @@ func (h *HandlersMap) AdminUsersDisableAllPOSTHandler(w http.ResponseWriter, r * HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{ Success: false, Status: "error", - Message: "Failed to disable all users", + Message: h.T(r.Context())("admin.msg.failed_disable_users"), }) return } @@ -3806,7 +3806,7 @@ func (h *HandlersMap) AdminUsersDisableAllPOSTHandler(w http.ResponseWriter, r * HTTPResponse(w, JSONApplicationUTF8, http.StatusOK, adminActionResponse{ Success: true, Status: "ok", - Message: "Disabled " + strconv.FormatInt(result.RowsAffected, 10) + " user(s). Current user not modified.", + Message: h.T(r.Context())("admin.msg.disabled_users", result.RowsAffected), }) } @@ -3828,7 +3828,7 @@ func (h *HandlersMap) AdminUsersDeleteAllPOSTHandler(w http.ResponseWriter, r *h HTTPResponse(w, JSONApplicationUTF8, http.StatusUnauthorized, adminActionResponse{ Success: false, Status: "error", - Message: "Not authenticated", + Message: h.T(r.Context())("admin.msg.not_authenticated"), }) return } @@ -3839,7 +3839,7 @@ func (h *HandlersMap) AdminUsersDeleteAllPOSTHandler(w http.ResponseWriter, r *h HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{ Success: false, Status: "error", - Message: "Failed to delete all users", + Message: h.T(r.Context())("admin.msg.failed_delete_users"), }) return } @@ -3847,7 +3847,7 @@ func (h *HandlersMap) AdminUsersDeleteAllPOSTHandler(w http.ResponseWriter, r *h HTTPResponse(w, JSONApplicationUTF8, http.StatusOK, adminActionResponse{ Success: true, Status: "ok", - Message: "Deleted " + strconv.FormatInt(result.RowsAffected, 10) + " user(s). Current user preserved.", + Message: h.T(r.Context())("admin.msg.deleted_users", result.RowsAffected), }) } @@ -3870,7 +3870,7 @@ func (h *HandlersMap) AdminChallengesExportHandler(w http.ResponseWriter, r *htt HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{ Success: false, Status: "error", - Message: "Failed to load challenges/categories", + Message: h.T(r.Context())("admin.msg.failed_load_challenges"), }) return } @@ -3880,7 +3880,7 @@ func (h *HandlersMap) AdminChallengesExportHandler(w http.ResponseWriter, r *htt HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, adminActionResponse{ Success: false, Status: "error", - Message: "Failed to generate export JSON", + Message: h.T(r.Context())("admin.msg.failed_export_json"), }) return } @@ -3923,18 +3923,18 @@ func (h *HandlersMap) AdminChallengesImportHandler(w http.ResponseWriter, r *htt var payload adminChallengesTransferPayload if err := h.decodeChallengeImportPayload(r, &payload); err != nil { log.Err(err).Msg("error parsing challenges import payload") - writeError(http.StatusBadRequest, "Invalid import payload") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_import_payload")) return } if len(payload.Challenges) == 0 { - writeError(http.StatusBadRequest, "No challenges found in import payload") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.no_challenges_in_payload")) return } importedChallenges, createdCategories, skippedChallenges, unassignedCountries, err := h.importAdminChallengesFromPayload(uuid, payload) if err != nil { log.Err(err).Msg("error importing challenges") - writeError(http.StatusInternalServerError, "Failed to import challenges") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_import_challenges")) return } @@ -3982,7 +3982,7 @@ func (h *HandlersMap) AdminChallengesDeleteAllPOSTHandler(w http.ResponseWriter, challengesList, err := h.Challenges.GetAll(uuid) if err != nil { log.Err(err).Msg("error loading challenges before bulk delete") - writeError(http.StatusInternalServerError, "Failed to load challenges") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_load_challenges")) return } @@ -3994,7 +3994,7 @@ func (h *HandlersMap) AdminChallengesDeleteAllPOSTHandler(w http.ResponseWriter, exists, err := h.Countries.Exists(countryCode) if err != nil { log.Err(err).Msg("error checking challenge country before bulk delete") - writeError(http.StatusInternalServerError, "Failed to release challenge countries") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_release_country_refs")) return } if !exists { @@ -4002,7 +4002,7 @@ func (h *HandlersMap) AdminChallengesDeleteAllPOSTHandler(w http.ResponseWriter, } if err := h.Countries.ReleaseCountry(countryCode); err != nil { log.Err(err).Msg("error releasing challenge country before bulk delete") - writeError(http.StatusInternalServerError, "Failed to release challenge countries") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_release_country_refs")) return } } @@ -4010,11 +4010,11 @@ func (h *HandlersMap) AdminChallengesDeleteAllPOSTHandler(w http.ResponseWriter, deletedCount, err := h.Challenges.DeleteAll(uuid) if err != nil { log.Err(err).Msg("error deleting all challenges") - writeError(http.StatusInternalServerError, "Failed to delete all challenges") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_delete_all_challenges")) return } - writeSuccess("Deleted " + strconv.FormatInt(deletedCount, 10) + " challenge(s)") + writeSuccess(h.T(r.Context())("admin.msg.deleted_challenge_count", deletedCount)) } // AdminChallengesEnableAllPOSTHandler enables all challenges @@ -4048,7 +4048,7 @@ func (h *HandlersMap) AdminChallengesEnableAllPOSTHandler(w http.ResponseWriter, updatedCount, err := h.Challenges.SetAllActive(uuid, true) if err != nil { log.Err(err).Msg("error enabling all challenges") - writeError(http.StatusInternalServerError, "Failed to enable all challenges") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_enable_all_challenges")) return } @@ -4056,7 +4056,7 @@ func (h *HandlersMap) AdminChallengesEnableAllPOSTHandler(w http.ResponseWriter, h.createAdminActivityLog(r, "enabled", fmt.Sprintf("enabled all challenges (%d)", updatedCount), 0) } - writeSuccess("Enabled " + strconv.FormatInt(updatedCount, 10) + " challenge(s)") + writeSuccess(h.T(r.Context())("admin.msg.enabled_challenge_count", updatedCount)) } // AdminChallengesDisableAllPOSTHandler disables all challenges @@ -4090,7 +4090,7 @@ func (h *HandlersMap) AdminChallengesDisableAllPOSTHandler(w http.ResponseWriter updatedCount, err := h.Challenges.SetAllActive(uuid, false) if err != nil { log.Err(err).Msg("error disabling all challenges") - writeError(http.StatusInternalServerError, "Failed to disable all challenges") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_disable_all_challenges")) return } @@ -4098,7 +4098,7 @@ func (h *HandlersMap) AdminChallengesDisableAllPOSTHandler(w http.ResponseWriter h.createAdminActivityLog(r, "disabled", fmt.Sprintf("disabled all challenges (%d)", updatedCount), 0) } - writeSuccess("Disabled " + strconv.FormatInt(updatedCount, 10) + " challenge(s)") + writeSuccess(h.T(r.Context())("admin.msg.disabled_challenge_count", updatedCount)) } // AdminChallengeCategoriesDeleteAllPOSTHandler deletes all categories after ensuring no challenges exist @@ -4132,22 +4132,22 @@ func (h *HandlersMap) AdminChallengeCategoriesDeleteAllPOSTHandler(w http.Respon challengesList, err := h.Challenges.GetAll(uuid) if err != nil { log.Err(err).Msg("error loading challenges before category bulk delete") - writeError(http.StatusInternalServerError, "Failed to load challenges") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_load_challenges")) return } if len(challengesList) > 0 { - writeError(http.StatusBadRequest, "Delete all challenges before deleting categories") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.delete_challenges_before_categories")) return } deletedCount, err := h.Challenges.DeleteAllCategories(uuid) if err != nil { log.Err(err).Msg("error deleting all categories") - writeError(http.StatusInternalServerError, "Failed to delete all categories") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_delete_all_categories")) return } - writeSuccess("Deleted " + strconv.FormatInt(deletedCount, 10) + " categor(ies)") + writeSuccess(h.T(r.Context())("admin.msg.deleted_category_count", deletedCount)) } // AdminChallengesTemplateHandler for admin challenges page for GET requests @@ -4293,7 +4293,7 @@ func (h *HandlersMap) AdminChallengesTemplateHandler(w http.ResponseWriter, r *h Label: "Hint", Subject: teamNamesByID[hintEntry.TeamID], Action: "hint", - Message: "Hint requested", + Message: h.T(r.Context())("hint.requested"), Arguments: "penalty=" + strconv.Itoa(hintEntry.Penalty), At: hintEntry.CreatedAt, }) @@ -4307,7 +4307,7 @@ func (h *HandlersMap) AdminChallengesTemplateHandler(w http.ResponseWriter, r *h Label: "Failure", Subject: teamNamesByID[failureEntry.TeamID], Action: "failure", - Message: "Incorrect submission", + Message: h.T(r.Context())("score.incorrect_submission"), Arguments: "flag=" + failureEntry.Flag, At: failureEntry.CreatedAt, }) @@ -4373,14 +4373,14 @@ func (h *HandlersMap) AdminChallengesPOSTHandler(w http.ResponseWriter, r *http. } if !strings.Contains(r.Header.Get(ContentType), JSONApplication) { - writeError(http.StatusUnsupportedMediaType, "Content-Type must be application/json") + writeError(http.StatusUnsupportedMediaType, h.T(r.Context())("feed.content_type")) return } var req AdminChallengeCreateRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { log.Err(err).Msg("error parsing admin challenges JSON payload") - writeError(http.StatusBadRequest, "Invalid JSON payload") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_json_payload")) return } @@ -4388,7 +4388,7 @@ func (h *HandlersMap) AdminChallengesPOSTHandler(w http.ResponseWriter, r *http. description := strings.TrimSpace(req.Description) challengeURL, err := challenges.NormalizeChallengeURL(req.URL) if err != nil { - writeError(http.StatusBadRequest, "Invalid challenge URL") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_challenge_url")) return } categoryIDStr := strings.TrimSpace(req.CategoryID) @@ -4403,7 +4403,7 @@ func (h *HandlersMap) AdminChallengesPOSTHandler(w http.ResponseWriter, r *http. hint := strings.TrimSpace(req.Hint) if title == "" || flag == "" { - writeError(http.StatusBadRequest, "Title and flag are required") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.title_flag_required")) return } if hintPenaltyStr == "" { @@ -4415,56 +4415,56 @@ func (h *HandlersMap) AdminChallengesPOSTHandler(w http.ResponseWriter, r *http. if country != "" { selectedCountry, err := h.Countries.GetByCode(country) if err != nil { - writeError(http.StatusBadRequest, "Invalid country code") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_country_code")) return } if !selectedCountry.Active || selectedCountry.Assigned { - writeError(http.StatusBadRequest, "Country must be active and available") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.country_active_available")) return } } categoryID, err := strconv.ParseUint(categoryIDStr, 10, 64) if err != nil || categoryID == 0 { - writeError(http.StatusBadRequest, "Please select a category") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.select_category")) return } category, err := h.Challenges.GetCategoryByID(uint(categoryID), uuid) if err != nil { - writeError(http.StatusBadRequest, "Please select a valid category") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.select_valid_category")) return } active := false if activeStr != "" { parsedActive, err := strconv.ParseBool(activeStr) if err != nil { - writeError(http.StatusBadRequest, "Invalid active value") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_active")) return } active = parsedActive } points, err := strconv.ParseInt(pointsStr, 10, 64) if err != nil { - writeError(http.StatusBadRequest, "Invalid points value") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_points")) return } bonus, err := strconv.ParseInt(bonusStr, 10, 64) if err != nil { - writeError(http.StatusBadRequest, "Invalid bonus value") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_bonus")) return } bonusDecay, err := strconv.ParseInt(bonusDecayStr, 10, 64) if err != nil { - writeError(http.StatusBadRequest, "Invalid bonus_decay value") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_bonus_decay")) return } hintPenalty, err := strconv.ParseInt(hintPenaltyStr, 10, 64) if err != nil { - writeError(http.StatusBadRequest, "Invalid hint_penalty value") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_hint_penalty")) return } helpPenalty, err := strconv.ParseInt(helpPenaltyStr, 10, 64) if err != nil { - writeError(http.StatusBadRequest, "Invalid help_penalty value") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_help_penalty")) return } @@ -4487,14 +4487,14 @@ func (h *HandlersMap) AdminChallengesPOSTHandler(w http.ResponseWriter, r *http. if err := h.Challenges.CreateAndReturn(&challenge); err != nil { log.Err(err).Msg("error creating challenge") - writeError(http.StatusInternalServerError, "Failed to create challenge") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_create_challenge")) return } if country != "" { if err := h.Countries.AssignCountryToChallenge(country, challenge.ID); err != nil { log.Err(err).Msg("error assigning country to challenge") _ = h.Challenges.Delete(challenge.ID, uuid) - writeError(http.StatusInternalServerError, "Failed to assign country to challenge") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_assign_country")) return } } @@ -4506,7 +4506,7 @@ func (h *HandlersMap) AdminChallengesPOSTHandler(w http.ResponseWriter, r *http. createMsg := fmt.Sprintf(logs.ActivityCreateChallenge, countryLabel, category.Name, challenge.Points) h.createAdminActivityLogVisible(r, false, "created", createMsg, challenge.ID) - writeSuccess("Challenge created") + writeSuccess(h.T(r.Context())("admin.msg.challenge_created")) } // AdminChallengeUpdatePOSTHandler for updating an existing challenge via POST requests @@ -4539,21 +4539,21 @@ func (h *HandlersMap) AdminChallengeUpdatePOSTHandler(w http.ResponseWriter, r * } if !strings.Contains(r.Header.Get(ContentType), JSONApplication) { - writeError(http.StatusUnsupportedMediaType, "Content-Type must be application/json") + writeError(http.StatusUnsupportedMediaType, h.T(r.Context())("feed.content_type")) return } challengeIDStr := strings.TrimSpace(chi.URLParam(r, "id")) challengeID, err := strconv.ParseUint(challengeIDStr, 10, 64) if err != nil || challengeID == 0 { - writeError(http.StatusBadRequest, "Invalid challenge id") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_challenge_id")) return } var req AdminChallengeCreateRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { log.Err(err).Msg("error parsing admin challenge update JSON payload") - writeError(http.StatusBadRequest, "Invalid JSON payload") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_json_payload")) return } @@ -4561,7 +4561,7 @@ func (h *HandlersMap) AdminChallengeUpdatePOSTHandler(w http.ResponseWriter, r * description := strings.TrimSpace(req.Description) challengeURL, err := challenges.NormalizeChallengeURL(req.URL) if err != nil { - writeError(http.StatusBadRequest, "Invalid challenge URL") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_challenge_url")) return } categoryIDStr := strings.TrimSpace(req.CategoryID) @@ -4576,7 +4576,7 @@ func (h *HandlersMap) AdminChallengeUpdatePOSTHandler(w http.ResponseWriter, r * hint := strings.TrimSpace(req.Hint) if title == "" || flag == "" { - writeError(http.StatusBadRequest, "Title and flag are required") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.title_flag_required")) return } if hintPenaltyStr == "" { @@ -4587,7 +4587,7 @@ func (h *HandlersMap) AdminChallengeUpdatePOSTHandler(w http.ResponseWriter, r * } categoryID, err := strconv.ParseUint(categoryIDStr, 10, 64) if err != nil || categoryID == 0 { - writeError(http.StatusBadRequest, "Valid category_id is required") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.valid_category_id_required")) return } active, err := strconv.ParseBool(activeStr) @@ -4598,40 +4598,40 @@ func (h *HandlersMap) AdminChallengeUpdatePOSTHandler(w http.ResponseWriter, r * case "inactive", "off": active = false default: - writeError(http.StatusBadRequest, "Invalid active value") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_active")) return } } points, err := strconv.ParseInt(pointsStr, 10, 64) if err != nil { - writeError(http.StatusBadRequest, "Invalid points value") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_points")) return } bonus, err := strconv.ParseInt(bonusStr, 10, 64) if err != nil { - writeError(http.StatusBadRequest, "Invalid bonus value") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_bonus")) return } bonusDecay, err := strconv.ParseInt(bonusDecayStr, 10, 64) if err != nil { - writeError(http.StatusBadRequest, "Invalid bonus_decay value") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_bonus_decay")) return } hintPenalty, err := strconv.ParseInt(hintPenaltyStr, 10, 64) if err != nil { - writeError(http.StatusBadRequest, "Invalid hint_penalty value") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_hint_penalty")) return } helpPenalty, err := strconv.ParseInt(helpPenaltyStr, 10, 64) if err != nil { - writeError(http.StatusBadRequest, "Invalid help_penalty value") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_help_penalty")) return } challenge, err := h.Challenges.GetByID(uint(challengeID), uuid) if err != nil { log.Err(err).Msg("error loading challenge to update") - writeError(http.StatusNotFound, "Challenge not found") + writeError(http.StatusNotFound, h.T(r.Context())("admin.msg.challenge_not_found")) return } previousActive := challenge.Active @@ -4639,11 +4639,11 @@ func (h *HandlersMap) AdminChallengeUpdatePOSTHandler(w http.ResponseWriter, r * if country != previousCountry && country != "" { selectedCountry, err := h.Countries.GetByCode(country) if err != nil { - writeError(http.StatusBadRequest, "Invalid country code") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_country_code")) return } if !selectedCountry.Active || selectedCountry.Assigned { - writeError(http.StatusBadRequest, "Country must be active and available") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.country_active_available")) return } } @@ -4664,7 +4664,7 @@ func (h *HandlersMap) AdminChallengeUpdatePOSTHandler(w http.ResponseWriter, r * if err := h.Challenges.Update(challenge); err != nil { log.Err(err).Msg("error updating challenge") - writeError(http.StatusInternalServerError, "Failed to update challenge") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_update_challenge")) return } if previousCountry != country { @@ -4672,13 +4672,13 @@ func (h *HandlersMap) AdminChallengeUpdatePOSTHandler(w http.ResponseWriter, r * exists, err := h.Countries.Exists(previousCountry) if err != nil { log.Err(err).Msg("error checking previous challenge country") - writeError(http.StatusInternalServerError, "Failed to update challenge country assignment") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_update_country_assignment")) return } if exists { if err := h.Countries.ReleaseCountry(previousCountry); err != nil { log.Err(err).Msg("error releasing previous challenge country") - writeError(http.StatusInternalServerError, "Failed to update challenge country assignment") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_update_country_assignment")) return } } @@ -4686,7 +4686,7 @@ func (h *HandlersMap) AdminChallengeUpdatePOSTHandler(w http.ResponseWriter, r * if country != "" { if err := h.Countries.AssignCountryToChallenge(country, challenge.ID); err != nil { log.Err(err).Msg("error assigning updated challenge country") - writeError(http.StatusInternalServerError, "Failed to update challenge country assignment") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_update_country_assignment")) return } } @@ -4703,7 +4703,7 @@ func (h *HandlersMap) AdminChallengeUpdatePOSTHandler(w http.ResponseWriter, r * category, err := h.Challenges.GetCategoryByID(challenge.CategoryID, uuid) if err != nil { log.Err(err).Msg("error retrieving challenge category for admin activity") - writeError(http.StatusInternalServerError, "Failed to retrieve challenge category") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_retrieve_category")) return } categoryName = category.Name @@ -4718,7 +4718,7 @@ func (h *HandlersMap) AdminChallengeUpdatePOSTHandler(w http.ResponseWriter, r * h.createAdminActivityLog(r, action, actionMsg, challenge.ID) } - writeSuccess("Challenge updated") + writeSuccess(h.T(r.Context())("admin.msg.challenge_updated")) } // AdminChallengeDeletePOSTHandler for deleting an existing challenge via POST requests @@ -4752,14 +4752,14 @@ func (h *HandlersMap) AdminChallengeDeletePOSTHandler(w http.ResponseWriter, r * challengeIDStr := strings.TrimSpace(chi.URLParam(r, "id")) challengeID, err := strconv.ParseUint(challengeIDStr, 10, 64) if err != nil || challengeID == 0 { - writeError(http.StatusBadRequest, "Invalid challenge id") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_challenge_id")) return } challenge, err := h.Challenges.GetByID(uint(challengeID), uuid) if err != nil { log.Err(err).Msg("error loading challenge to delete") - writeError(http.StatusNotFound, "Challenge not found") + writeError(http.StatusNotFound, h.T(r.Context())("admin.msg.challenge_not_found")) return } country := strings.ToUpper(strings.TrimSpace(challenge.Country)) @@ -4767,13 +4767,13 @@ func (h *HandlersMap) AdminChallengeDeletePOSTHandler(w http.ResponseWriter, r * exists, err := h.Countries.Exists(country) if err != nil { log.Err(err).Msg("error checking challenge country before delete") - writeError(http.StatusInternalServerError, "Failed to release challenge country") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_release_country")) return } if exists { if err := h.Countries.ReleaseCountry(country); err != nil { log.Err(err).Msg("error releasing challenge country before delete") - writeError(http.StatusInternalServerError, "Failed to release challenge country") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_release_country")) return } } @@ -4781,11 +4781,11 @@ func (h *HandlersMap) AdminChallengeDeletePOSTHandler(w http.ResponseWriter, r * if err := h.Challenges.Delete(uint(challengeID), uuid); err != nil { log.Err(err).Msg("error deleting challenge") - writeError(http.StatusInternalServerError, "Failed to delete challenge") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_delete_challenge")) return } - writeSuccess("Challenge deleted") + writeSuccess(h.T(r.Context())("admin.msg.challenge_deleted")) } // AdminChallengeCategoriesPOSTHandler for admin challenge categories creation via POST requests @@ -4816,14 +4816,14 @@ func (h *HandlersMap) AdminChallengeCategoriesPOSTHandler(w http.ResponseWriter, } if !strings.Contains(r.Header.Get(ContentType), JSONApplication) { - writeError(http.StatusUnsupportedMediaType, "Content-Type must be application/json") + writeError(http.StatusUnsupportedMediaType, h.T(r.Context())("feed.content_type")) return } var req AdminCategoryCreateRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { log.Err(err).Msg("error parsing admin category JSON payload") - writeError(http.StatusBadRequest, "Invalid JSON payload") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_json_payload")) return } @@ -4832,7 +4832,7 @@ func (h *HandlersMap) AdminChallengeCategoriesPOSTHandler(w http.ResponseWriter, logo := strings.TrimSpace(req.Logo) if name == "" { - writeError(http.StatusBadRequest, "Category name is required") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.category_name_required")) return } @@ -4845,11 +4845,11 @@ func (h *HandlersMap) AdminChallengeCategoriesPOSTHandler(w http.ResponseWriter, if err := h.Challenges.CreateCategory(category); err != nil { log.Err(err).Msg("error creating category") - writeError(http.StatusInternalServerError, "Failed to create category") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_create_category")) return } - writeSuccess("Category created") + writeSuccess(h.T(r.Context())("admin.msg.category_created")) } // AdminChallengeCategoryUpdatePOSTHandler updates an existing challenge category via POST requests @@ -4880,21 +4880,21 @@ func (h *HandlersMap) AdminChallengeCategoryUpdatePOSTHandler(w http.ResponseWri } if !strings.Contains(r.Header.Get(ContentType), JSONApplication) { - writeError(http.StatusUnsupportedMediaType, "Content-Type must be application/json") + writeError(http.StatusUnsupportedMediaType, h.T(r.Context())("feed.content_type")) return } categoryIDStr := strings.TrimSpace(chi.URLParam(r, "id")) categoryID, err := strconv.ParseUint(categoryIDStr, 10, 64) if err != nil || categoryID == 0 { - writeError(http.StatusBadRequest, "Invalid category id") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_category_id")) return } var req AdminCategoryCreateRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { log.Err(err).Msg("error parsing admin category update JSON payload") - writeError(http.StatusBadRequest, "Invalid JSON payload") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_json_payload")) return } @@ -4903,14 +4903,14 @@ func (h *HandlersMap) AdminChallengeCategoryUpdatePOSTHandler(w http.ResponseWri logo := strings.TrimSpace(req.Logo) if name == "" { - writeError(http.StatusBadRequest, "Category name is required") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.category_name_required")) return } category, err := h.Challenges.GetCategoryByID(uint(categoryID), uuid) if err != nil { log.Err(err).Msg("error loading category to update") - writeError(http.StatusNotFound, "Category not found") + writeError(http.StatusNotFound, h.T(r.Context())("admin.msg.category_not_found")) return } @@ -4920,11 +4920,11 @@ func (h *HandlersMap) AdminChallengeCategoryUpdatePOSTHandler(w http.ResponseWri if err := h.Challenges.UpdateCategory(category); err != nil { log.Err(err).Msg("error updating category") - writeError(http.StatusInternalServerError, "Failed to update category") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_update_category")) return } - writeSuccess("Category updated") + writeSuccess(h.T(r.Context())("admin.msg.category_updated")) } // AdminChallengeCategoryDeletePOSTHandler deletes a category if no challenges are assigned @@ -4957,34 +4957,34 @@ func (h *HandlersMap) AdminChallengeCategoryDeletePOSTHandler(w http.ResponseWri categoryIDStr := strings.TrimSpace(chi.URLParam(r, "id")) categoryID, err := strconv.ParseUint(categoryIDStr, 10, 64) if err != nil || categoryID == 0 { - writeError(http.StatusBadRequest, "Invalid category id") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_category_id")) return } if _, err := h.Challenges.GetCategoryByID(uint(categoryID), uuid); err != nil { log.Err(err).Msg("error loading category to delete") - writeError(http.StatusNotFound, "Category not found") + writeError(http.StatusNotFound, h.T(r.Context())("admin.msg.category_not_found")) return } hasChallenges, err := h.Challenges.CategoryHasChallenges(uint(categoryID), uuid) if err != nil { log.Err(err).Msg("error checking category usage") - writeError(http.StatusInternalServerError, "Failed to verify category usage") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_verify_category_usage")) return } if hasChallenges { - writeError(http.StatusConflict, "Category is assigned to challenges and was not deleted") + writeError(http.StatusConflict, h.T(r.Context())("admin.msg.category_assigned_not_deleted")) return } if err := h.Challenges.DeleteCategory(uint(categoryID), uuid); err != nil { log.Err(err).Msg("error deleting category") - writeError(http.StatusInternalServerError, "Failed to delete category") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_delete_category")) return } - writeSuccess("Category deleted") + writeSuccess(h.T(r.Context())("admin.msg.category_deleted")) } // AdminActivityTemplateHandler for admin activity page for GET requests @@ -5062,19 +5062,19 @@ func (h *HandlersMap) AdminActivityPOSTHandler(w http.ResponseWriter, r *http.Re } if !strings.Contains(r.Header.Get(ContentType), JSONApplication) { - writeError(http.StatusUnsupportedMediaType, "Content-Type must be application/json") + writeError(http.StatusUnsupportedMediaType, h.T(r.Context())("feed.content_type")) return } if h.Logs == nil || h.Sessions == nil { - writeError(http.StatusInternalServerError, "Activity logging is unavailable") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.activity_logging_unavailable")) return } var req AdminActivityCreateRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { log.Err(err).Msg("error parsing admin activity JSON payload") - writeError(http.StatusBadRequest, "Invalid JSON payload") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_json_payload")) return } @@ -5082,23 +5082,23 @@ func (h *HandlersMap) AdminActivityPOSTHandler(w http.ResponseWriter, r *http.Re action := strings.TrimSpace(req.Action) message := strings.TrimSpace(req.Message) if subject == "" && message == "" { - writeError(http.StatusBadRequest, "Subject or message is required") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.subject_message_required")) return } activity, err := h.Logs.NewActivity(req.Visible, subject, action, message, 0, uuid) if err != nil { log.Err(err).Msg("error building custom admin activity log") - writeError(http.StatusInternalServerError, "Failed to build activity entry") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_build_activity")) return } if err := h.Logs.CreateActivity(activity); err != nil { log.Err(err).Msg("error creating custom admin activity log") - writeError(http.StatusInternalServerError, "Failed to create activity entry") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_create_activity")) return } - writeSuccess("Activity entry created") + writeSuccess(h.T(r.Context())("admin.msg.activity_entry_created")) } // AdminActivityDeletePOSTHandler deletes a custom activity entry. @@ -5123,27 +5123,27 @@ func (h *HandlersMap) AdminActivityDeletePOSTHandler(w http.ResponseWriter, r *h } if h.Logs == nil { - writeError(http.StatusInternalServerError, "Activity logging is unavailable") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.activity_logging_unavailable")) return } idValue := strings.TrimSpace(chi.URLParam(r, "id")) id, err := strconv.ParseUint(idValue, 10, 64) if err != nil || id == 0 { - writeError(http.StatusBadRequest, "Invalid activity entry ID") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_activity_id")) return } if err := h.Logs.DeleteActivity(uint(id), uuid); err != nil { log.Err(err).Msg("error deleting activity entry") - writeError(http.StatusInternalServerError, "Failed to delete activity entry") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_delete_activity")) return } HTTPResponse(w, JSONApplicationUTF8, http.StatusOK, adminActionResponse{ Success: true, Status: "ok", - Message: "Activity entry deleted", + Message: h.T(r.Context())("admin.msg.activity_deleted"), }) } @@ -5247,14 +5247,14 @@ func (h *HandlersMap) AdminChatSetHiddenPOSTHandler(w http.ResponseWriter, r *ht idStr := strings.TrimSpace(chi.URLParam(r, "id")) id, err := strconv.ParseUint(idStr, 10, 64) if err != nil || id == 0 { - writeError(http.StatusBadRequest, "Invalid chat id") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_chat_id")) return } entry, err := h.Chat.GetByID(uint(id)) if err != nil { log.Err(err).Msg("error loading chat entry") - writeError(http.StatusNotFound, "Chat message not found") + writeError(http.StatusNotFound, h.T(r.Context())("admin.msg.chat_message_not_found")) return } @@ -5263,13 +5263,13 @@ func (h *HandlersMap) AdminChatSetHiddenPOSTHandler(w http.ResponseWriter, r *ht var req adminChatVisibilityRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { log.Err(err).Msg("error parsing admin chat visibility JSON payload") - writeError(http.StatusBadRequest, "Invalid JSON payload") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_json_payload")) return } parsedHiddenValue, err := parseAdminChatHiddenValue(req.Hidden) if err != nil { log.Err(err).Msg("error parsing admin chat hidden value") - writeError(http.StatusBadRequest, "Invalid hidden value") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_hidden")) return } hiddenValue = parsedHiddenValue @@ -5279,19 +5279,19 @@ func (h *HandlersMap) AdminChatSetHiddenPOSTHandler(w http.ResponseWriter, r *ht if err := h.Chat.SetHiddenByID(uint(id), hiddenValue); err != nil { log.Err(err).Msg("error updating chat visibility") - writeError(http.StatusInternalServerError, "Failed to update chat visibility") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_update_chat_visibility")) return } if hiddenValue { - writeSuccess("Chat message hidden") + writeSuccess(h.T(r.Context())("admin.msg.chat_message_hidden")) return } if entry.Hidden { - writeSuccess("Chat message unhidden") + writeSuccess(h.T(r.Context())("admin.msg.chat_message_unhidden")) return } - writeSuccess("Chat visibility updated") + writeSuccess(h.T(r.Context())("admin.msg.chat_visibility_updated")) } // AdminChatDeletePOSTHandler deletes a chat entry @@ -5333,23 +5333,23 @@ func (h *HandlersMap) AdminChatDeletePOSTHandler(w http.ResponseWriter, r *http. idStr := strings.TrimSpace(chi.URLParam(r, "id")) id, err := strconv.ParseUint(idStr, 10, 64) if err != nil || id == 0 { - writeError(http.StatusBadRequest, "Invalid chat id") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_chat_id")) return } if _, err := h.Chat.GetByID(uint(id)); err != nil { log.Err(err).Msg("error loading chat entry for delete") - writeError(http.StatusNotFound, "Chat message not found") + writeError(http.StatusNotFound, h.T(r.Context())("admin.msg.chat_message_not_found")) return } if err := h.Chat.Delete(uint(id)); err != nil { log.Err(err).Msg("error deleting chat entry") - writeError(http.StatusInternalServerError, "Failed to delete chat message") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_delete_chat")) return } - writeSuccess("Chat message deleted") + writeSuccess(h.T(r.Context())("admin.msg.chat_message_deleted")) } // AdminCountriesTemplateHandler for admin countries page for GET requests @@ -5443,18 +5443,18 @@ func (h *HandlersMap) AdminCountriesDeleteAllPOSTHandler(w http.ResponseWriter, } if h.Countries == nil || h.Challenges == nil { - writeError(http.StatusInternalServerError, "Countries or challenges manager is not initialized") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.countries_or_challenges_not_initialized")) return } if !strings.EqualFold(r.Header.Get("X-Requested-With"), "XMLHttpRequest") { - writeError(http.StatusBadRequest, "AJAX requests only") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.ajax_only")) return } tx := h.Countries.DB.Begin() if tx.Error != nil { log.Err(tx.Error).Msg("error starting delete-all-countries transaction") - writeError(http.StatusInternalServerError, "Failed to delete all countries") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_delete_all_countries")) return } defer func() { @@ -5467,7 +5467,7 @@ func (h *HandlersMap) AdminCountriesDeleteAllPOSTHandler(w http.ResponseWriter, if err := tx.Model(&challenges.Challenge{}).Where("uuid = ?", uuid).Update("country", "").Error; err != nil { tx.Rollback() log.Err(err).Msg("error clearing challenge country references before bulk country delete") - writeError(http.StatusInternalServerError, "Failed to clear challenge country references") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_clear_country_refs")) return } @@ -5475,17 +5475,17 @@ func (h *HandlersMap) AdminCountriesDeleteAllPOSTHandler(w http.ResponseWriter, if deleteResult.Error != nil { tx.Rollback() log.Err(deleteResult.Error).Msg("error deleting all countries") - writeError(http.StatusInternalServerError, "Failed to delete all countries") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_delete_all_countries")) return } if err := tx.Commit().Error; err != nil { log.Err(err).Msg("error committing delete-all-countries transaction") - writeError(http.StatusInternalServerError, "Failed to delete all countries") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_delete_all_countries")) return } - writeSuccess("Deleted " + strconv.FormatInt(deleteResult.RowsAffected, 10) + " country(ies)") + writeSuccess(h.T(r.Context())("admin.msg.deleted_country_count", deleteResult.RowsAffected)) } // AdminCountryUpdatePOSTHandler for updating country active status via POST requests @@ -5526,16 +5526,16 @@ func (h *HandlersMap) AdminCountryUpdatePOSTHandler(w http.ResponseWriter, r *ht } countryIDParam := strings.TrimSpace(chi.URLParam(r, "id")) if countryIDParam == "" { - writeError(http.StatusBadRequest, "Missing country ID") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.missing_country_id")) return } countryID, err := strconv.ParseUint(countryIDParam, 10, 32) if err != nil { - writeError(http.StatusBadRequest, "Invalid country ID") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_country_id")) return } if h.Countries == nil { - writeError(http.StatusInternalServerError, "Countries manager is not initialized") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.countries_not_initialized")) return } activeValue := "false" @@ -5544,7 +5544,7 @@ func (h *HandlersMap) AdminCountryUpdatePOSTHandler(w http.ResponseWriter, r *ht Active bool `json:"active"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - writeError(http.StatusBadRequest, "Invalid JSON payload") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_json_payload")) return } if req.Active { @@ -5552,7 +5552,7 @@ func (h *HandlersMap) AdminCountryUpdatePOSTHandler(w http.ResponseWriter, r *ht } } else { if err := r.ParseForm(); err != nil { - writeError(http.StatusBadRequest, "Invalid form payload") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_form_payload")) return } activeValue = strings.TrimSpace(r.FormValue("active")) @@ -5562,13 +5562,13 @@ func (h *HandlersMap) AdminCountryUpdatePOSTHandler(w http.ResponseWriter, r *ht } active, err := strconv.ParseBool(strings.ToLower(activeValue)) if err != nil { - writeError(http.StatusBadRequest, "Invalid active value") + writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_active")) return } if err := h.Countries.SetActiveByID(uint(countryID), active); err != nil { log.Err(err).Msg("error updating country active status") - writeError(http.StatusInternalServerError, "Failed to update country status") + writeError(http.StatusInternalServerError, h.T(r.Context())("admin.msg.failed_update_country_status")) return } - writeSuccess("Country status updated") + writeSuccess(h.T(r.Context())("admin.msg.country_status_updated")) } diff --git a/backend/cmd/map/handlers/auth.go b/backend/cmd/map/handlers/auth.go index c3e9ad9..53edfd6 100644 --- a/backend/cmd/map/handlers/auth.go +++ b/backend/cmd/map/handlers/auth.go @@ -32,39 +32,39 @@ func (h *HandlersMap) LoginPOSTHandler(w http.ResponseWriter, r *http.Request) { var l MapLoginRequest // Parse request JSON body if err := json.NewDecoder(r.Body).Decode(&l); err != nil { - log.Err(err).Msg("error parsing POST body") - HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: "error parsing POST body"}) + log.Err(err).Msg(h.T(r.Context())("auth.error_parse_body")) + HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: h.T(r.Context())("auth.error_parse_body")}) return } if l.Username == "" || l.Password == "" { - HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: "username and password are required"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: h.T(r.Context())("auth.user_pass_required")}) return } valid, user := h.Users.CheckLoginCredentials(l.Username, l.Password, uuid) if !valid { - HTTPResponse(w, JSONApplicationUTF8, http.StatusUnauthorized, MapErrorResponse{Error: "invalid credentials"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusUnauthorized, MapErrorResponse{Error: h.T(r.Context())("auth.invalid_credentials")}) return } if !user.Admin { if h.Settings == nil { log.Err(errors.New("settings manager not initialized")).Msg("error checking login_enabled") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "login is unavailable"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("auth.login_unavailable")}) return } loginEnabled, err := h.Settings.GetLoginEnabled(uuid) if err != nil { log.Err(err).Msg("error getting login enabled setting") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "login is unavailable"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("auth.login_unavailable")}) return } if !loginEnabled { - HTTPResponse(w, JSONApplicationUTF8, http.StatusForbidden, MapErrorResponse{Error: "login is disabled"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusForbidden, MapErrorResponse{Error: h.T(r.Context())("auth.login_disabled")}) return } } err := h.Sessions.RenewToken(r.Context()) if err != nil { - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "error renewing session"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("auth.error_renew_session")}) return } h.Sessions.Put(r.Context(), string(ContextKeyUser), user.Username) @@ -79,7 +79,7 @@ func (h *HandlersMap) LoginPOSTHandler(w http.ResponseWriter, r *http.Request) { } HTTPResponse(w, JSONApplicationUTF8, http.StatusOK, MapLoginResponse{ Success: true, - Message: "Login successful", + Message: h.T(r.Context())("login.success_msg"), Redirect: redirectTo, }) } @@ -92,19 +92,19 @@ func (h *HandlersMap) LogoutPOSTHandler(w http.ResponseWriter, r *http.Request) // Get UUID from URL path uuid := chi.URLParam(r, "uuid") if uuid == "" { - log.Err(errors.New("UUID is required")).Msg("UUID is required") - HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: "UUID is required"}) + log.Err(errors.New(h.T(r.Context())("auth.uuid_required"))).Msg(h.T(r.Context())("auth.uuid_required")) + HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: h.T(r.Context())("auth.uuid_required")}) return } if err := h.Sessions.Destroy(r.Context()); err != nil { - log.Err(err).Msg("error destroying session") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "error destroying session"}) + log.Err(err).Msg(h.T(r.Context())("auth.error_destroy_session")) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("auth.error_destroy_session")}) return } // Send response HTTPResponse(w, JSONApplicationUTF8, http.StatusOK, MapLogoutResponse{ Success: true, - Message: "Logout successful", + Message: h.T(r.Context())("login.logout_msg"), Redirect: "/" + uuid + "/login", }) } diff --git a/backend/cmd/map/handlers/auth_test.go b/backend/cmd/map/handlers/auth_test.go index 05ddc36..f495da7 100644 --- a/backend/cmd/map/handlers/auth_test.go +++ b/backend/cmd/map/handlers/auth_test.go @@ -10,6 +10,7 @@ import ( "github.com/alexedwards/scs/v2" "github.com/jmpsec/mapctf/pkg/config" + "github.com/jmpsec/mapctf/pkg/i18n" "github.com/jmpsec/mapctf/pkg/settings" "github.com/jmpsec/mapctf/pkg/users" "github.com/stretchr/testify/require" @@ -166,3 +167,42 @@ func TestLoginPOSTHandlerAllowsNonAdminWhenLoginEnabled(t *testing.T) { require.Equal(t, "alice", sessions.GetString(req.Context(), string(ContextKeyUser))) require.False(t, sessions.GetBool(req.Context(), string(ContextKeyAdmin))) } + +func TestLoginPOSTHandlerReturnsLocalizedMessage(t *testing.T) { + db := newJSONTestDB(t) + userManager, err := users.CreateUserManager(db, &config.ConfigurationJWT{Secret: "test-secret", HoursToExpire: 24}) + require.NoError(t, err) + settingsManager, err := settings.CreateSettingsManager(db, "test-service") + require.NoError(t, err) + require.NoError(t, settingsManager.Initialization(jsonTestUUID)) + require.NoError(t, settingsManager.SetLanguage("es", jsonSettingsAuthor, jsonTestUUID)) + + catalog, err := i18n.New() + require.NoError(t, err) + sessions := scs.New() + handler := CreateHandlersMap( + WithConfig(config.MapCTFConfiguration{Map: config.ConfigurationMap{UUID: jsonTestUUID}}), + WithUsers(userManager), + WithSettings(settingsManager), + WithSessions(sessions), + WithI18N(catalog), + ) + + admin, err := userManager.New("admin", "password123", "admin@example.com", "Admin", true, false, jsonTestUUID, 0) + require.NoError(t, err) + require.NoError(t, userManager.Create(admin)) + + req := newJSONBodyRequestWithUUID(http.MethodPost, "/login", jsonTestUUID, MapLoginRequest{Username: "admin", Password: "password123"}) + ctx, err := sessions.Load(req.Context(), "") + require.NoError(t, err) + req = req.WithContext(ctx) + + rec := httptest.NewRecorder() + handler.LocaleMiddleware(http.HandlerFunc(handler.LoginPOSTHandler)).ServeHTTP(rec, req) + + require.Equal(t, http.StatusOK, rec.Code) + var resp MapLoginResponse + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.True(t, resp.Success) + require.Equal(t, "Acceso correcto", resp.Message) +} diff --git a/backend/cmd/map/handlers/json.go b/backend/cmd/map/handlers/json.go index 323626b..9cdfd5f 100644 --- a/backend/cmd/map/handlers/json.go +++ b/backend/cmd/map/handlers/json.go @@ -67,13 +67,13 @@ type JSONGameClockResponse struct { func (h *HandlersMap) validatedJSONUUID(w http.ResponseWriter, r *http.Request) (string, bool) { uuid := chi.URLParam(r, "uuid") if uuid == "" { - log.Err(errors.New("UUID is required")).Msg("UUID is required") - HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: "UUID is required"}) + log.Err(errors.New(h.T(r.Context())("auth.uuid_required"))).Msg(h.T(r.Context())("auth.uuid_required")) + HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: h.T(r.Context())("auth.uuid_required")}) return "", false } if uuid != h.Config.Map.UUID { log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) - HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: "invalid UUID"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: h.T(r.Context())("feed.invalid_uuid")}) return "", false } return uuid, true @@ -92,8 +92,8 @@ func (h *HandlersMap) JSONActivityHandler(w http.ResponseWriter, r *http.Request // Get all activity logs for the given UUID activityLogs, err := h.Logs.AllActivity(uuid) if err != nil { - log.Err(err).Msg("error retrieving activity logs") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "error retrieving activity logs"}) + log.Err(err).Msg(h.T(r.Context())("feed.error_activity")) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("feed.error_activity")}) return } visibleActivityLogs := make([]logs.ActivityLog, 0, len(activityLogs)) @@ -116,8 +116,8 @@ func (h *HandlersMap) JSONGameClockHandler(w http.ResponseWriter, r *http.Reques return } if h.Settings == nil { - log.Err(errors.New("settings manager not initialized")).Msg("error retrieving game clock data") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "error retrieving game clock data"}) + log.Err(errors.New("settings manager not initialized")).Msg(h.T(r.Context())("feed.error_game_clock")) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("feed.error_game_clock")}) return } @@ -131,7 +131,7 @@ func (h *HandlersMap) JSONGameClockHandler(w http.ResponseWriter, r *http.Reques response.GameStarted = gameStarted } else if !errors.Is(err, gorm.ErrRecordNotFound) { log.Err(err).Msg("error retrieving game_started for game clock data") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "error retrieving game clock data"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("feed.error_game_clock")}) return } @@ -140,7 +140,7 @@ func (h *HandlersMap) JSONGameClockHandler(w http.ResponseWriter, r *http.Reques response.GamePaused = gamePaused } else if !errors.Is(err, gorm.ErrRecordNotFound) { log.Err(err).Msg("error retrieving game_paused for game clock data") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "error retrieving game clock data"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("feed.error_game_clock")}) return } @@ -149,7 +149,7 @@ func (h *HandlersMap) JSONGameClockHandler(w http.ResponseWriter, r *http.Reques response.GameStartTime = &gameStartTime } else if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { log.Err(err).Msg("error retrieving game_start_time for game clock data") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "error retrieving game clock data"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("feed.error_game_clock")}) return } @@ -163,7 +163,7 @@ func (h *HandlersMap) JSONGameClockHandler(w http.ResponseWriter, r *http.Reques response.RemainingMS = remaining.Milliseconds() } else if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { log.Err(err).Msg("error retrieving game_end_time for game clock data") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "error retrieving game clock data"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("feed.error_game_clock")}) return } @@ -187,15 +187,15 @@ func (h *HandlersMap) JSONTeamsHandler(w http.ResponseWriter, r *http.Request) { // Get all teams for the given UUID allTeams, err := h.Teams.GetAll(uuid) if err != nil { - log.Err(err).Msg("error retrieving teams") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "error retrieving teams"}) + log.Err(err).Msg(h.T(r.Context())("feed.error_teams")) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("feed.error_teams")}) return } showTeamMembers, err := h.Settings.GetGameboardShowTeamMembers(uuid) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { log.Err(err).Msg("error retrieving gameboard_show_team_members setting") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "error retrieving team settings"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("feed.error_team_settings")}) return } @@ -204,7 +204,7 @@ func (h *HandlersMap) JSONTeamsHandler(w http.ResponseWriter, r *http.Request) { allUsers, err := h.Users.GetAll(uuid) if err != nil { log.Err(err).Msg("error retrieving users for teams JSON") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "error retrieving team members"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("feed.error_team_members")}) return } for _, user := range allUsers { @@ -252,8 +252,8 @@ func (h *HandlersMap) JSONChallengesHandler(w http.ResponseWriter, r *http.Reque // Get all active challenges for the given UUID challenges, err := h.Challenges.GetActive(uuid) if err != nil { - log.Err(err).Msg("error retrieving challenges") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "error retrieving challenges"}) + log.Err(err).Msg(h.T(r.Context())("feed.error_challenges")) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("feed.error_challenges")}) return } // Send response @@ -273,22 +273,22 @@ func (h *HandlersMap) JSONCountriesHandler(w http.ResponseWriter, r *http.Reques } if h.Countries == nil || h.Challenges == nil { - log.Err(errors.New("countries or challenges manager not initialized")).Msg("error retrieving country data") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "error retrieving country data"}) + log.Err(errors.New("countries or challenges manager not initialized")).Msg(h.T(r.Context())("feed.error_country")) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("feed.error_country")}) return } allCountries, err := h.Countries.GetAll() if err != nil { log.Err(err).Msg("error retrieving countries") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "error retrieving country data"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("feed.error_country")}) return } activeChallenges, err := h.Challenges.GetActive(uuid) if err != nil { log.Err(err).Msg("error retrieving active challenges for country data") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "error retrieving country data"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("feed.error_country")}) return } @@ -296,7 +296,7 @@ func (h *HandlersMap) JSONCountriesHandler(w http.ResponseWriter, r *http.Reques allCategories, err := h.Challenges.GetAllCategories(uuid) if err != nil { log.Err(err).Msg("error retrieving categories for country data") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "error retrieving country data"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("feed.error_country")}) return } for _, category := range allCategories { @@ -328,7 +328,7 @@ func (h *HandlersMap) JSONCountriesHandler(w http.ResponseWriter, r *http.Reques user, err := h.Users.Get(username, uuid) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { log.Err(err).Msg("error retrieving current user for country data") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "error retrieving country data"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("feed.error_country")}) return } currentTeamID = user.TeamID @@ -338,7 +338,7 @@ func (h *HandlersMap) JSONCountriesHandler(w http.ResponseWriter, r *http.Reques var allTeams []teams.PlatformTeam if err := h.Teams.DB.Where("uuid = ?", uuid).Find(&allTeams).Error; err != nil { log.Err(err).Msg("error retrieving teams for country data") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "error retrieving country data"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("feed.error_country")}) return } @@ -350,7 +350,7 @@ func (h *HandlersMap) JSONCountriesHandler(w http.ResponseWriter, r *http.Reques var teamScores []teams.TeamScore if err := h.Teams.DB.Where("uuid = ?", uuid).Order("created_at ASC").Find(&teamScores).Error; err != nil { log.Err(err).Msg("error retrieving team scores for country data") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "error retrieving country data"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("feed.error_country")}) return } @@ -442,29 +442,29 @@ func (h *HandlersMap) JSONWorldDominationHandler(w http.ResponseWriter, r *http. } if h.Challenges == nil || h.Teams == nil || h.Users == nil || h.Sessions == nil { - log.Err(errors.New("world domination dependencies not initialized")).Msg("error retrieving world domination data") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "error retrieving world domination data"}) + log.Err(errors.New("world domination dependencies not initialized")).Msg(h.T(r.Context())("feed.error_domination")) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("feed.error_domination")}) return } username := h.Sessions.GetString(r.Context(), string(ContextKeyUser)) if username == "" { - log.Err(errors.New("user not authenticated")).Msg("user not authenticated") - HTTPResponse(w, JSONApplicationUTF8, http.StatusUnauthorized, MapErrorResponse{Error: "user not authenticated"}) + log.Err(errors.New(h.T(r.Context())("chat.not_authenticated"))).Msg(h.T(r.Context())("chat.not_authenticated")) + HTTPResponse(w, JSONApplicationUTF8, http.StatusUnauthorized, MapErrorResponse{Error: h.T(r.Context())("chat.not_authenticated")}) return } user, err := h.Users.Get(username, uuid) if err != nil { log.Err(err).Msg("error retrieving user for world domination data") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "error retrieving world domination data"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("feed.error_domination")}) return } activeChallenges, err := h.Challenges.GetActive(uuid) if err != nil { log.Err(err).Msg("error retrieving active challenges for world domination data") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "error retrieving world domination data"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("feed.error_domination")}) return } @@ -499,7 +499,7 @@ func (h *HandlersMap) JSONWorldDominationHandler(w http.ResponseWriter, r *http. var scores []teams.TeamScore if err := h.Teams.DB.Where("uuid = ?", uuid).Find(&scores).Error; err != nil { log.Err(err).Msg("error retrieving team scores for world domination data") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "error retrieving world domination data"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("feed.error_domination")}) return } @@ -552,8 +552,8 @@ func (h *HandlersMap) JSONChatHandler(w http.ResponseWriter, r *http.Request) { // Get all chat entries for the given UUID chatEntries, err := h.Chat.GetVisible() if err != nil { - log.Err(err).Msg("error retrieving chat entries") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "error retrieving chat entries"}) + log.Err(err).Msg(h.T(r.Context())("feed.error_chat")) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("feed.error_chat")}) return } // Send response diff --git a/backend/cmd/map/handlers/post.go b/backend/cmd/map/handlers/post.go index 4a22f3a..f867d22 100644 --- a/backend/cmd/map/handlers/post.go +++ b/backend/cmd/map/handlers/post.go @@ -33,62 +33,62 @@ func (h *HandlersMap) RegistrationPOSTHandler(w http.ResponseWriter, r *http.Req var req MapRegistrationRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { log.Err(err).Msg("error parsing request body") - HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: "invalid request body"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: h.T(r.Context())("reg.invalid_body")}) return } regType, err := h.Settings.GetRegistrationType(uuid) if err != nil { log.Err(err).Msg("error getting registration type setting") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "failed to get registration type setting"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("reg.failed_type_setting")}) return } if req.Token == "" && regType == settings.TokenRegistration { - log.Err(errors.New("registration token is required")).Msg("registration token is required") - HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: "registration token is required"}) + log.Err(errors.New(h.T(r.Context())("reg.token_required"))).Msg(h.T(r.Context())("reg.token_required")) + HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: h.T(r.Context())("reg.token_required")}) return } if regType == settings.TokenRegistration { regToken, err := h.Settings.GetRegistrationToken(uuid) if err != nil { log.Err(err).Msg("error getting registration token setting") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "failed to get registration token setting"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("reg.failed_token_setting")}) return } if req.Token != regToken { - log.Err(errors.New("invalid registration token")).Msg("invalid registration token") - HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: "invalid registration token"}) + log.Err(errors.New(h.T(r.Context())("reg.invalid_token"))).Msg(h.T(r.Context())("reg.invalid_token")) + HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: h.T(r.Context())("reg.invalid_token")}) return } } if req.Username == "" || req.Password == "" { - HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: "username and password are required"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: h.T(r.Context())("auth.user_pass_required")}) return } if req.Email == "" { - HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: "email is required"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: h.T(r.Context())("reg.email_required")}) return } if req.Team == "" { - HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: "team is required"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: h.T(r.Context())("reg.team_required")}) return } // Register team nTeam, err := h.Teams.Register(req.Team, req.Logo, uuid) if err != nil { log.Err(err).Msg("error registering team") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "failed to register team"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("reg.failed_register_team")}) return } // Register user err = h.Users.Register(req.Username, req.Password, req.Name, req.Email, nTeam.ID, uuid) if err != nil { log.Err(err).Msg("error registering user") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "failed to register user"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("reg.failed_register_user")}) return } HTTPResponse(w, JSONApplicationUTF8, http.StatusOK, MapRegistrationResponse{ Success: true, - Message: "Registration successful", + Message: h.T(r.Context())("reg.success"), Redirect: "/" + uuid + "/login", }) } @@ -109,32 +109,32 @@ func (h *HandlersMap) ChatPOSTHandler(w http.ResponseWriter, r *http.Request) { var req ChatEntryRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { log.Err(err).Msg("error parsing request body") - HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: "invalid request body"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: h.T(r.Context())("reg.invalid_body")}) return } chatMaxLen, err := h.Settings.GetGameboardChatMaxLen(uuid) if err != nil { log.Err(err).Msg("error getting gameboard chat max length setting") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "failed to get gameboard chat max length setting"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("chat.failed_maxlen_setting")}) return } // Get user from session username := h.Sessions.GetString(r.Context(), string(ContextKeyUser)) if username == "" { - log.Err(errors.New("user not authenticated")).Msg("user not authenticated") - HTTPResponse(w, JSONApplicationUTF8, http.StatusUnauthorized, MapErrorResponse{Error: "user not authenticated"}) + log.Err(errors.New(h.T(r.Context())("chat.not_authenticated"))).Msg(h.T(r.Context())("chat.not_authenticated")) + HTTPResponse(w, JSONApplicationUTF8, http.StatusUnauthorized, MapErrorResponse{Error: h.T(r.Context())("chat.not_authenticated")}) return } // Get user team teamID, err := h.Users.Get(username, uuid) if err != nil { log.Err(err).Msg("error getting user for chat message") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "failed to get user for chat message"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("chat.failed_get_user")}) return } if err := h.Chat.CreateNew(username, req.Message, teamID.TeamID, chatMaxLen); err != nil { log.Err(err).Msg("error creating chat message") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "failed to create chat message"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("chat.failed_create")}) return } HTTPResponse(w, JSONApplicationUTF8, http.StatusOK, MapChatResponse{ @@ -156,68 +156,68 @@ func (h *HandlersMap) ScorePOSTHandler(w http.ResponseWriter, r *http.Request) { if h.Settings == nil || h.Users == nil || h.Teams == nil || h.Challenges == nil || h.Logs == nil || h.Sessions == nil { log.Err(errors.New("score handler dependencies not initialized")).Msg("error scoring challenge") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "scoring is unavailable"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("score.unavailable")}) return } var req MapScoreRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { log.Err(err).Msg("error parsing score request body") - HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: "invalid request body"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: h.T(r.Context())("reg.invalid_body")}) return } countryCode := strings.ToUpper(strings.TrimSpace(req.CountryCode)) submittedFlag := strings.TrimSpace(req.Flag) if countryCode == "" || submittedFlag == "" { - HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: "country_code and flag are required"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: h.T(r.Context())("score.country_flag_required")}) return } scoringEnabled, err := h.Settings.GetScoringEnabled(uuid) if err != nil { log.Err(err).Msg("error retrieving scoring setting") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "failed to retrieve scoring setting"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("score.failed_setting")}) return } if !scoringEnabled { - HTTPResponse(w, JSONApplicationUTF8, http.StatusForbidden, MapErrorResponse{Error: "scoring is disabled"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusForbidden, MapErrorResponse{Error: h.T(r.Context())("score.disabled")}) return } username := h.Sessions.GetString(r.Context(), string(ContextKeyUser)) if username == "" { - log.Err(errors.New("user not authenticated")).Msg("user not authenticated") - HTTPResponse(w, JSONApplicationUTF8, http.StatusUnauthorized, MapErrorResponse{Error: "user not authenticated"}) + log.Err(errors.New(h.T(r.Context())("chat.not_authenticated"))).Msg(h.T(r.Context())("chat.not_authenticated")) + HTTPResponse(w, JSONApplicationUTF8, http.StatusUnauthorized, MapErrorResponse{Error: h.T(r.Context())("chat.not_authenticated")}) return } user, err := h.Users.Get(username, uuid) if err != nil { log.Err(err).Msg("error retrieving user for score request") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "failed to retrieve user"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("score.failed_retrieve_user")}) return } if user.TeamID == 0 { - HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: "user is not assigned to a team"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: h.T(r.Context())("score.no_team")}) return } var team teams.PlatformTeam if err := h.Teams.DB.Where("id = ? AND uuid = ?", user.TeamID, uuid).First(&team).Error; err != nil { log.Err(err).Msg("error retrieving team for score request") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "failed to retrieve team"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("score.failed_retrieve_team")}) return } var challenge challenges.Challenge if err := h.Challenges.DB.Where("uuid = ? AND active = ? AND country = ?", uuid, true, countryCode).First(&challenge).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - HTTPResponse(w, JSONApplicationUTF8, http.StatusNotFound, MapErrorResponse{Error: "no active challenge found for country"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusNotFound, MapErrorResponse{Error: h.T(r.Context())("score.no_active_challenge")}) return } log.Err(err).Msg("error retrieving challenge for score request") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "failed to retrieve challenge"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("score.failed_retrieve_challenge")}) return } @@ -225,7 +225,7 @@ func (h *HandlersMap) ScorePOSTHandler(w http.ResponseWriter, r *http.Request) { if err := h.Teams.DB.Where("uuid = ? AND team_id = ? AND challenge_id = ?", uuid, user.TeamID, challenge.ID).First(&existingScore).Error; err == nil { HTTPResponse(w, JSONApplicationUTF8, http.StatusConflict, MapScoreResponse{ Success: false, - Message: "Your team already completed this challenge", + Message: h.T(r.Context())("score.already_completed"), CountryCode: countryCode, ChallengeID: challenge.ID, TotalPoints: team.Points, @@ -233,7 +233,7 @@ func (h *HandlersMap) ScorePOSTHandler(w http.ResponseWriter, r *http.Request) { return } else if !errors.Is(err, gorm.ErrRecordNotFound) { log.Err(err).Msg("error checking existing team score") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "failed to evaluate current score"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("score.failed_evaluate")}) return } @@ -241,18 +241,18 @@ func (h *HandlersMap) ScorePOSTHandler(w http.ResponseWriter, r *http.Request) { failureLog, err := h.Logs.NewFailuresLog(challenge.ID, user.TeamID, submittedFlag, uuid) if err != nil { log.Err(err).Msg("error building failure log") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "failed to record scoring attempt"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("score.failed_record")}) return } if err := h.Logs.CreateFailuresLog(failureLog); err != nil { log.Err(err).Msg("error creating failure log") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "failed to record scoring attempt"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("score.failed_record")}) return } HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapScoreResponse{ Success: false, - Message: "Incorrect flag", + Message: h.T(r.Context())("score.incorrect_flag"), CountryCode: countryCode, ChallengeID: challenge.ID, TotalPoints: team.Points, @@ -268,7 +268,7 @@ func (h *HandlersMap) ScorePOSTHandler(w http.ResponseWriter, r *http.Request) { category, err := h.Challenges.GetCategoryByID(challenge.CategoryID, uuid) if err != nil { log.Err(err).Msg("error retrieving challenge category for score activity") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "failed to retrieve challenge category"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("score.failed_retrieve_category")}) return } categoryName = category.Name @@ -314,13 +314,13 @@ func (h *HandlersMap) ScorePOSTHandler(w http.ResponseWriter, r *http.Request) { return nil }); err != nil { log.Err(err).Msg("error storing successful score") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "failed to score challenge"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("score.failed_score")}) return } HTTPResponse(w, JSONApplicationUTF8, http.StatusOK, MapScoreResponse{ Success: true, - Message: "Challenge completed", + Message: h.T(r.Context())("score.completed"), CountryCode: countryCode, ChallengeID: challenge.ID, PointsAwarded: awardedPoints, @@ -342,65 +342,65 @@ func (h *HandlersMap) HintPOSTHandler(w http.ResponseWriter, r *http.Request) { if h.Settings == nil || h.Users == nil || h.Teams == nil || h.Challenges == nil || h.Logs == nil || h.Sessions == nil { log.Err(errors.New("hint handler dependencies not initialized")).Msg("error requesting hint") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "hint requests are unavailable"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("hint.requests_unavailable")}) return } var req MapHintRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { log.Err(err).Msg("error parsing hint request body") - HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: "invalid request body"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: h.T(r.Context())("reg.invalid_body")}) return } countryCode := strings.ToUpper(strings.TrimSpace(req.CountryCode)) if countryCode == "" { - HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: "country_code is required"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: h.T(r.Context())("hint.country_required")}) return } scoringHintsEnabled, err := h.Settings.GetScoringHints(uuid) if err != nil { log.Err(err).Msg("error retrieving scoring_hints setting") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "failed to retrieve hint setting"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("hint.failed_setting")}) return } if !scoringHintsEnabled { - HTTPResponse(w, JSONApplicationUTF8, http.StatusForbidden, MapErrorResponse{Error: "hint requests are disabled"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusForbidden, MapErrorResponse{Error: h.T(r.Context())("hint.requests_disabled")}) return } username := h.Sessions.GetString(r.Context(), string(ContextKeyUser)) if username == "" { - log.Err(errors.New("user not authenticated")).Msg("user not authenticated") - HTTPResponse(w, JSONApplicationUTF8, http.StatusUnauthorized, MapErrorResponse{Error: "user not authenticated"}) + log.Err(errors.New(h.T(r.Context())("chat.not_authenticated"))).Msg(h.T(r.Context())("chat.not_authenticated")) + HTTPResponse(w, JSONApplicationUTF8, http.StatusUnauthorized, MapErrorResponse{Error: h.T(r.Context())("chat.not_authenticated")}) return } user, err := h.Users.Get(username, uuid) if err != nil { log.Err(err).Msg("error retrieving user for hint request") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "failed to retrieve user"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("score.failed_retrieve_user")}) return } if user.TeamID == 0 { - HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: "user is not assigned to a team"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: h.T(r.Context())("score.no_team")}) return } var challenge challenges.Challenge if err := h.Challenges.DB.Where("uuid = ? AND active = ? AND country = ?", uuid, true, countryCode).First(&challenge).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - HTTPResponse(w, JSONApplicationUTF8, http.StatusNotFound, MapErrorResponse{Error: "no active challenge found for country"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusNotFound, MapErrorResponse{Error: h.T(r.Context())("score.no_active_challenge")}) return } log.Err(err).Msg("error retrieving challenge for hint request") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "failed to retrieve challenge"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("score.failed_retrieve_challenge")}) return } if strings.TrimSpace(challenge.Hint) == "" { - HTTPResponse(w, JSONApplicationUTF8, http.StatusNotFound, MapErrorResponse{Error: "no hint is available for this challenge"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusNotFound, MapErrorResponse{Error: h.T(r.Context())("hint.not_available")}) return } @@ -412,7 +412,7 @@ func (h *HandlersMap) HintPOSTHandler(w http.ResponseWriter, r *http.Request) { var team teams.PlatformTeam if err := h.Teams.DB.Where("id = ? AND uuid = ?", user.TeamID, uuid).First(&team).Error; err != nil { log.Err(err).Msg("error retrieving team for hint request") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "failed to retrieve team"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("score.failed_retrieve_team")}) return } @@ -420,7 +420,7 @@ func (h *HandlersMap) HintPOSTHandler(w http.ResponseWriter, r *http.Request) { if err := h.Logs.DB.Where("uuid = ? AND team_id = ? AND challenge_id = ?", uuid, user.TeamID, challenge.ID).First(&existingHint).Error; err == nil { HTTPResponse(w, JSONApplicationUTF8, http.StatusOK, MapHintResponse{ Success: true, - Message: "Hint already unlocked", + Message: h.T(r.Context())("hint.already_unlocked"), CountryCode: countryCode, ChallengeID: challenge.ID, Hint: challenge.Hint, @@ -431,14 +431,14 @@ func (h *HandlersMap) HintPOSTHandler(w http.ResponseWriter, r *http.Request) { return } else if !errors.Is(err, gorm.ErrRecordNotFound) { log.Err(err).Msg("error checking existing hint log") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "failed to evaluate hint state"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("hint.failed_evaluate")}) return } if team.Points < penalty { HTTPResponse(w, JSONApplicationUTF8, http.StatusConflict, MapHintResponse{ Success: false, - Message: "Your team does not have enough points for this hint", + Message: h.T(r.Context())("hint.not_enough_points"), CountryCode: countryCode, ChallengeID: challenge.ID, Penalty: penalty, @@ -453,7 +453,7 @@ func (h *HandlersMap) HintPOSTHandler(w http.ResponseWriter, r *http.Request) { category, err := h.Challenges.GetCategoryByID(challenge.CategoryID, uuid) if err != nil { log.Err(err).Msg("error retrieving challenge category for hint activity") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "failed to retrieve challenge category"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("score.failed_retrieve_category")}) return } categoryName = category.Name @@ -510,7 +510,7 @@ func (h *HandlersMap) HintPOSTHandler(w http.ResponseWriter, r *http.Request) { if err.Error() == "insufficient points" { HTTPResponse(w, JSONApplicationUTF8, http.StatusConflict, MapHintResponse{ Success: false, - Message: "Your team does not have enough points for this hint", + Message: h.T(r.Context())("hint.not_enough_points"), CountryCode: countryCode, ChallengeID: challenge.ID, Penalty: penalty, @@ -519,13 +519,13 @@ func (h *HandlersMap) HintPOSTHandler(w http.ResponseWriter, r *http.Request) { return } log.Err(err).Msg("error storing successful hint request") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "failed to unlock hint"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("hint.failed_unlock")}) return } HTTPResponse(w, JSONApplicationUTF8, http.StatusOK, MapHintResponse{ Success: true, - Message: "Hint unlocked", + Message: h.T(r.Context())("hint.unlocked"), CountryCode: countryCode, ChallengeID: challenge.ID, Hint: challenge.Hint, @@ -549,71 +549,71 @@ func (h *HandlersMap) HintGETHandler(w http.ResponseWriter, r *http.Request) { if h.Users == nil || h.Teams == nil || h.Challenges == nil || h.Logs == nil || h.Sessions == nil { log.Err(errors.New("hint get handler dependencies not initialized")).Msg("error retrieving unlocked hint") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "hint retrieval is unavailable"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("hint.retrieval_unavailable")}) return } countryCode := strings.ToUpper(strings.TrimSpace(r.URL.Query().Get("country_code"))) if countryCode == "" { - HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: "country_code is required"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: h.T(r.Context())("hint.country_required")}) return } username := h.Sessions.GetString(r.Context(), string(ContextKeyUser)) if username == "" { - log.Err(errors.New("user not authenticated")).Msg("user not authenticated") - HTTPResponse(w, JSONApplicationUTF8, http.StatusUnauthorized, MapErrorResponse{Error: "user not authenticated"}) + log.Err(errors.New(h.T(r.Context())("chat.not_authenticated"))).Msg(h.T(r.Context())("chat.not_authenticated")) + HTTPResponse(w, JSONApplicationUTF8, http.StatusUnauthorized, MapErrorResponse{Error: h.T(r.Context())("chat.not_authenticated")}) return } user, err := h.Users.Get(username, uuid) if err != nil { log.Err(err).Msg("error retrieving user for hint retrieval") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "failed to retrieve user"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("score.failed_retrieve_user")}) return } if user.TeamID == 0 { - HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: "user is not assigned to a team"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: h.T(r.Context())("score.no_team")}) return } var challenge challenges.Challenge if err := h.Challenges.DB.Where("uuid = ? AND active = ? AND country = ?", uuid, true, countryCode).First(&challenge).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - HTTPResponse(w, JSONApplicationUTF8, http.StatusNotFound, MapErrorResponse{Error: "no active challenge found for country"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusNotFound, MapErrorResponse{Error: h.T(r.Context())("score.no_active_challenge")}) return } log.Err(err).Msg("error retrieving challenge for hint retrieval") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "failed to retrieve challenge"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("score.failed_retrieve_challenge")}) return } if strings.TrimSpace(challenge.Hint) == "" { - HTTPResponse(w, JSONApplicationUTF8, http.StatusNotFound, MapErrorResponse{Error: "no hint is available for this challenge"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusNotFound, MapErrorResponse{Error: h.T(r.Context())("hint.not_available")}) return } var existingHint logs.HintsLog if err := h.Logs.DB.Where("uuid = ? AND team_id = ? AND challenge_id = ?", uuid, user.TeamID, challenge.ID).First(&existingHint).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - HTTPResponse(w, JSONApplicationUTF8, http.StatusNotFound, MapErrorResponse{Error: "hint not unlocked"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusNotFound, MapErrorResponse{Error: h.T(r.Context())("hint.not_unlocked")}) return } log.Err(err).Msg("error checking unlocked hint state") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "failed to evaluate hint state"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("hint.failed_evaluate")}) return } var team teams.PlatformTeam if err := h.Teams.DB.Where("id = ? AND uuid = ?", user.TeamID, uuid).First(&team).Error; err != nil { log.Err(err).Msg("error retrieving team for hint retrieval") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "failed to retrieve team"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("score.failed_retrieve_team")}) return } HTTPResponse(w, JSONApplicationUTF8, http.StatusOK, MapHintResponse{ Success: true, - Message: "Hint already unlocked", + Message: h.T(r.Context())("hint.already_unlocked"), CountryCode: countryCode, ChallengeID: challenge.ID, Hint: challenge.Hint, diff --git a/backend/cmd/map/handlers/profile.go b/backend/cmd/map/handlers/profile.go index c15c7ec..873fb60 100644 --- a/backend/cmd/map/handlers/profile.go +++ b/backend/cmd/map/handlers/profile.go @@ -31,18 +31,18 @@ func (h *HandlersMap) ProfileGETHandler(w http.ResponseWriter, r *http.Request) } if h.Users == nil { log.Error().Msg("users manager not initialized") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "profile is unavailable"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("profile.unavailable")}) return } user, err := h.Users.Get(username, uuid) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - HTTPResponse(w, JSONApplicationUTF8, http.StatusUnauthorized, MapErrorResponse{Error: "authentication required"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusUnauthorized, MapErrorResponse{Error: h.T(r.Context())("profile.auth_required")}) return } log.Err(err).Msg("error retrieving profile user") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "profile is unavailable"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("profile.unavailable")}) return } @@ -54,7 +54,7 @@ func (h *HandlersMap) ProfileGETHandler(w http.ResponseWriter, r *http.Request) team, found, err := h.currentProfileTeam(user.TeamID, uuid) if err != nil { log.Err(err).Msg("error retrieving profile team") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "profile is unavailable"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("profile.unavailable")}) return } if found { @@ -87,17 +87,17 @@ func (h *HandlersMap) ProfilePOSTHandler(w http.ResponseWriter, r *http.Request) } if h.Users == nil { log.Error().Msg("users manager not initialized") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "profile is unavailable"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("profile.unavailable")}) return } if !strings.Contains(strings.ToLower(r.Header.Get(ContentType)), JSONApplication) { - HTTPResponse(w, JSONApplicationUTF8, http.StatusUnsupportedMediaType, MapErrorResponse{Error: "Content-Type must be application/json"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusUnsupportedMediaType, MapErrorResponse{Error: h.T(r.Context())("feed.content_type")}) return } var req MapProfileAccountUpdateRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: "invalid JSON payload"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: h.T(r.Context())("feed.invalid_json")}) return } @@ -106,7 +106,7 @@ func (h *HandlersMap) ProfilePOSTHandler(w http.ResponseWriter, r *http.Request) if email != "" { parsed, err := mail.ParseAddress(email) if err != nil || parsed.Address != email { - HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: "email is invalid"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: h.T(r.Context())("profile.email_invalid")}) return } } @@ -119,18 +119,18 @@ func (h *HandlersMap) ProfilePOSTHandler(w http.ResponseWriter, r *http.Request) }) if result.Error != nil { log.Err(result.Error).Msg("error updating profile account") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "profile could not be updated"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("profile.not_updated")}) return } if result.RowsAffected == 0 { - HTTPResponse(w, JSONApplicationUTF8, http.StatusUnauthorized, MapErrorResponse{Error: "authentication required"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusUnauthorized, MapErrorResponse{Error: h.T(r.Context())("profile.auth_required")}) return } user, err := h.Users.Get(username, uuid) if err != nil { log.Err(err).Msg("error retrieving updated profile user") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "profile could not be updated"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("profile.not_updated")}) return } @@ -155,47 +155,47 @@ func (h *HandlersMap) ProfilePasswordPOSTHandler(w http.ResponseWriter, r *http. } if h.Users == nil { log.Error().Msg("users manager not initialized") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "profile is unavailable"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("profile.unavailable")}) return } if !strings.Contains(strings.ToLower(r.Header.Get(ContentType)), JSONApplication) { - HTTPResponse(w, JSONApplicationUTF8, http.StatusUnsupportedMediaType, MapErrorResponse{Error: "Content-Type must be application/json"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusUnsupportedMediaType, MapErrorResponse{Error: h.T(r.Context())("feed.content_type")}) return } var req MapProfilePasswordRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: "invalid JSON payload"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: h.T(r.Context())("feed.invalid_json")}) return } switch { case req.CurrentPassword == "": - HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: "current password is required"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: h.T(r.Context())("profile.current_required")}) return case strings.TrimSpace(req.NewPassword) == "": - HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: "new password is required"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: h.T(r.Context())("profile.new_required")}) return case utf8.RuneCountInString(req.NewPassword) < minProfilePasswordLength: - HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: "new password must be at least 8 characters"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: h.T(r.Context())("profile.new_min_length")}) return case req.NewPassword != req.ConfirmPassword: - HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: "new passwords do not match"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: h.T(r.Context())("profile.new_mismatch")}) return case req.CurrentPassword == req.NewPassword: - HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: "new password must be different"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: h.T(r.Context())("profile.new_must_differ")}) return } valid, _ := h.Users.CheckLoginCredentials(username, req.CurrentPassword, uuid) if !valid { - HTTPResponse(w, JSONApplicationUTF8, http.StatusUnauthorized, MapErrorResponse{Error: "current password is incorrect"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusUnauthorized, MapErrorResponse{Error: h.T(r.Context())("profile.current_incorrect")}) return } if err := h.Users.SetPassword(username, req.NewPassword, uuid); err != nil { log.Err(err).Msg("error updating profile password") - HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: "password could not be updated"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusInternalServerError, MapErrorResponse{Error: h.T(r.Context())("profile.password_not_updated")}) return } @@ -207,20 +207,20 @@ func (h *HandlersMap) ProfilePasswordPOSTHandler(w http.ResponseWriter, r *http. func (h *HandlersMap) profileSessionUsername(w http.ResponseWriter, r *http.Request) (username string, ok bool) { if h.Sessions == nil { - HTTPResponse(w, JSONApplicationUTF8, http.StatusUnauthorized, MapErrorResponse{Error: "authentication required"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusUnauthorized, MapErrorResponse{Error: h.T(r.Context())("profile.auth_required")}) return "", false } // SCS panics if the LoadAndSave middleware has not prepared the context. defer func() { if recover() != nil { - HTTPResponse(w, JSONApplicationUTF8, http.StatusUnauthorized, MapErrorResponse{Error: "authentication required"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusUnauthorized, MapErrorResponse{Error: h.T(r.Context())("profile.auth_required")}) username = "" ok = false } }() username = h.Sessions.GetString(r.Context(), string(ContextKeyUser)) if username == "" { - HTTPResponse(w, JSONApplicationUTF8, http.StatusUnauthorized, MapErrorResponse{Error: "authentication required"}) + HTTPResponse(w, JSONApplicationUTF8, http.StatusUnauthorized, MapErrorResponse{Error: h.T(r.Context())("profile.auth_required")}) return "", false } return username, true diff --git a/backend/cmd/map/handlers/throttle.go b/backend/cmd/map/handlers/throttle.go index 6024c3d..641c6ea 100644 --- a/backend/cmd/map/handlers/throttle.go +++ b/backend/cmd/map/handlers/throttle.go @@ -127,7 +127,7 @@ func (h *HandlersMap) teamThrottleKey(r *http.Request) (string, error) { uuid := h.Config.Map.UUID username := h.Sessions.GetString(r.Context(), string(ContextKeyUser)) if username == "" { - return "", errors.New("user not authenticated") + return "", errors.New(h.T(r.Context())("chat.not_authenticated")) } user, err := h.Users.Get(username, uuid) From 0db3669e084be3898da08a57c1fc34cf2a83193c Mon Sep 17 00:00:00 2001 From: Javier Marcos <1271349+javuto@users.noreply.github.com> Date: Sat, 27 Jun 2026 09:26:40 +0200 Subject: [PATCH 2/3] linter issues --- backend/cmd/api/handlers/utils.go | 15 +++ backend/cmd/api/main.go | 14 +-- backend/cmd/map/handlers/admin.go | 136 +++++++++++++------------- backend/cmd/map/handlers/auth.go | 2 +- backend/cmd/map/handlers/get.go | 4 +- backend/cmd/map/handlers/json.go | 2 +- backend/cmd/map/handlers/locale.go | 8 +- backend/cmd/map/handlers/post.go | 10 +- backend/cmd/map/handlers/templates.go | 19 ++-- backend/cmd/map/handlers/utils.go | 15 +++ backend/cmd/map/main.go | 6 +- backend/pkg/backend/backend.go | 6 +- backend/pkg/cache/cache.go | 2 +- backend/pkg/challenges/challenges.go | 38 +++---- backend/pkg/chat/chat.go | 14 +-- backend/pkg/logs/activity.go | 8 +- backend/pkg/settings/settings.go | 16 +-- backend/pkg/settings/settings_test.go | 10 +- backend/pkg/teams/logos.go | 2 +- backend/pkg/teams/scores_test.go | 64 ++++++------ backend/pkg/teams/teams.go | 10 +- backend/pkg/users/users.go | 2 +- 22 files changed, 221 insertions(+), 182 deletions(-) diff --git a/backend/cmd/api/handlers/utils.go b/backend/cmd/api/handlers/utils.go index 6eacaab..b038414 100644 --- a/backend/cmd/api/handlers/utils.go +++ b/backend/cmd/api/handlers/utils.go @@ -102,3 +102,18 @@ func getRealIP(r *http.Request) string { } return ip } + +// RealIP is a middleware that overrides r.RemoteAddr with the originating +// client IP resolved from the X-Real-Ip / X-Forwarded-For headers. It replaces +// chi's deprecated middleware.RealIP, which is vulnerable to IP spoofing +// because it trusts those headers unconditionally regardless of whether the +// upstream infrastructure actually sets them. Use this only behind a trusted +// reverse proxy that populates the headers. +func RealIP(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if ip := getRealIP(r); ip != "" { + r.RemoteAddr = ip + } + next.ServeHTTP(w, r) + }) +} diff --git a/backend/cmd/api/main.go b/backend/cmd/api/main.go index 6d7cede..47eb34d 100644 --- a/backend/cmd/api/main.go +++ b/backend/cmd/api/main.go @@ -80,8 +80,6 @@ const ( apiAdminPath = "/admin" // API teams path apiTeamsPath = "/teams" - // API users path - apiUsersPath = "/users" // API settings path apiSettingsPath = "/settings" // API challenges path @@ -248,7 +246,7 @@ func mapCTFService() { })) // Middleware muxAPI.Use(middleware.RequestID) - muxAPI.Use(middleware.RealIP) + muxAPI.Use(handlers.RealIP) muxAPI.Use(middleware.Recoverer) // Root muxAPI.Get(rootPath, handlersCTF.RootHandler) @@ -281,12 +279,12 @@ func mapCTFService() { r.Use(handlersCTF.RequireAdmin) r.Get(apiTeamsPath, handlersCTF.AdminTeamsHandler) // GET /api/v1/admin/{uuid}/teams r.Post(apiTeamsPath, handlersCTF.CreateTeamHandler) // POST /api/v1/admin/{uuid}/teams - //r.Delete(apiTeamsPath+"/{entID}/{id}", handlersCTF.DeleteTeamHandler) // DELETE /api/v1/admin/teams/{entID}/{id} + // r.Delete(apiTeamsPath+"/{entID}/{id}", handlersCTF.DeleteTeamHandler) // DELETE /api/v1/admin/teams/{entID}/{id} r.Get(apiSettingsPath, handlersCTF.SettingsHandler) // GET /api/v1/admin/{uuid}/settings r.Get(apiChallengesPath, handlersCTF.AdminChallengesHandler) // GET /api/v1/admin/{uuid}/challenges r.Post(apiChallengesPath, handlersCTF.CreateChallengeHandler) // POST /api/v1/admin/{uuid}/challenges - //r.Patch(apiChallengesPath+"/{entID}/{id}", handlersCTF.UpdateChallengeHandler) // PATCH /api/v1/admin/challenges/{entID}/{id} - //r.Delete(apiChallengesPath+"/{entID}/{id}", handlersCTF.DeleteChallengeHandler) // DELETE /api/v1/admin/challenges/{entID}/{id} + // r.Patch(apiChallengesPath+"/{entID}/{id}", handlersCTF.UpdateChallengeHandler) // PATCH /api/v1/admin/challenges/{entID}/{id} + // r.Delete(apiChallengesPath+"/{entID}/{id}", handlersCTF.DeleteChallengeHandler) // DELETE /api/v1/admin/challenges/{entID}/{id} }) }) }) @@ -558,7 +556,9 @@ func main() { if usersMgr.Exists(username, uuid) { // User exists, reset password log.Info().Msgf("User '%s' already exists for UUID %s, resetting password...", username, uuid) - usersMgr.SetPassword(username, password, uuid) + if err := usersMgr.SetPassword(username, password, uuid); err != nil { + return fmt.Errorf("failed to reset password for admin user '%s': %w", username, err) + } fmt.Printf("Password reset successfully for admin user '%s' (UUID %s).\n", username, uuid) } else { // User doesn't exist, create it diff --git a/backend/cmd/map/handlers/admin.go b/backend/cmd/map/handlers/admin.go index 94e404c..5a97b8d 100644 --- a/backend/cmd/map/handlers/admin.go +++ b/backend/cmd/map/handlers/admin.go @@ -475,7 +475,7 @@ func (h *HandlersMap) AdminTemplateHandler(w http.ResponseWriter, r *http.Reques // Get UUID from URL path parameters and validate it uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -513,7 +513,7 @@ func (h *HandlersMap) AdminSettingsTemplateHandler(w http.ResponseWriter, r *htt // Get UUID from URL path parameters and validate it uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -685,7 +685,7 @@ func (h *HandlersMap) AdminSettingsPOSTHandler(w http.ResponseWriter, r *http.Re // Get UUID from URL path parameters and validate it uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -1363,7 +1363,7 @@ func (h *HandlersMap) AdminGameExportHandler(w http.ResponseWriter, r *http.Requ uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -1424,7 +1424,7 @@ func (h *HandlersMap) AdminGameImportHandler(w http.ResponseWriter, r *http.Requ uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -1544,7 +1544,7 @@ func (h *HandlersMap) AdminSettingsExportHandler(w http.ResponseWriter, r *http. uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -1573,7 +1573,7 @@ func (h *HandlersMap) AdminSettingsImportHandler(w http.ResponseWriter, r *http. uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -1635,7 +1635,7 @@ func (h *HandlersMap) AdminSettingsResetDefaultsPOSTHandler(w http.ResponseWrite uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -1703,7 +1703,7 @@ func (h *HandlersMap) AdminControlsTemplateHandler(w http.ResponseWriter, r *htt // Get UUID from URL path parameters and validate it uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -1741,7 +1741,7 @@ func (h *HandlersMap) AdminTeamsTemplateHandler(w http.ResponseWriter, r *http.R // Get UUID from URL path parameters and validate it uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -1807,7 +1807,7 @@ func (h *HandlersMap) AdminTeamLogosTemplateHandler(w http.ResponseWriter, r *ht } uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -1847,7 +1847,9 @@ func (h *HandlersMap) AdminTeamLogosTemplateHandler(w http.ResponseWriter, r *ht templateData.AllLogos = allLogos templateData.CustomLogos = customLogos templateData.PlatformLogos = platformLogos - t.Execute(w, templateData) + if err := t.Execute(w, templateData); err != nil { + log.Err(err).Msg("error rendering admin logos template") + } } // AdminTeamsPOSTHandler for admin teams page for POST requests @@ -1859,7 +1861,7 @@ func (h *HandlersMap) AdminTeamsPOSTHandler(w http.ResponseWriter, r *http.Reque // Get UUID from URL path parameters and validate it uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -2050,7 +2052,7 @@ func (h *HandlersMap) AdminTeamsExportHandler(w http.ResponseWriter, r *http.Req uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -2092,7 +2094,7 @@ func (h *HandlersMap) AdminTeamsExportTeamsHandler(w http.ResponseWriter, r *htt uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -2152,7 +2154,7 @@ func (h *HandlersMap) AdminTeamsExportLogosHandler(w http.ResponseWriter, r *htt uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -2213,7 +2215,7 @@ func (h *HandlersMap) AdminTeamsImportHandler(w http.ResponseWriter, r *http.Req uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -2349,7 +2351,7 @@ func (h *HandlersMap) AdminTeamsImportTeamsHandler(w http.ResponseWriter, r *htt uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -2397,7 +2399,7 @@ func (h *HandlersMap) AdminTeamsImportLogosHandler(w http.ResponseWriter, r *htt uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -2449,7 +2451,7 @@ func (h *HandlersMap) AdminTeamsEnableAllPOSTHandler(w http.ResponseWriter, r *h } uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -2469,7 +2471,7 @@ func (h *HandlersMap) AdminTeamsDisableAllPOSTHandler(w http.ResponseWriter, r * } uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -2489,7 +2491,7 @@ func (h *HandlersMap) AdminTeamsVisibleAllPOSTHandler(w http.ResponseWriter, r * } uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -2509,7 +2511,7 @@ func (h *HandlersMap) AdminTeamsInvisibleAllPOSTHandler(w http.ResponseWriter, r } uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -2529,7 +2531,7 @@ func (h *HandlersMap) AdminTeamLogosEnableAllPOSTHandler(w http.ResponseWriter, } uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -2549,7 +2551,7 @@ func (h *HandlersMap) AdminTeamLogosDisableAllPOSTHandler(w http.ResponseWriter, } uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -2569,7 +2571,7 @@ func (h *HandlersMap) AdminTeamLogosDeleteAllPOSTHandler(w http.ResponseWriter, } uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -2629,7 +2631,7 @@ func (h *HandlersMap) AdminTeamsDeleteAllPOSTHandler(w http.ResponseWriter, r *h uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -2709,7 +2711,7 @@ func (h *HandlersMap) AdminTeamUpdatePOSTHandler(w http.ResponseWriter, r *http. uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -2843,7 +2845,7 @@ func (h *HandlersMap) AdminTeamDeletePOSTHandler(w http.ResponseWriter, r *http. uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -2962,7 +2964,7 @@ func (h *HandlersMap) AdminTeamLogosPOSTHandler(w http.ResponseWriter, r *http.R uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -3039,7 +3041,8 @@ func (h *HandlersMap) AdminTeamLogosPOSTHandler(w http.ResponseWriter, r *http.R uploadedLogoData = uploadData ext := strings.ToLower(filepath.Ext(fileHeader.Filename)) - if rasterLogoContentTypeForExt(ext) != "" { + switch { + case rasterLogoContentTypeForExt(ext) != "": if rasterErr := validateRasterLogoUpload(ext, uploadData); rasterErr != nil { writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_raster_logo")) return @@ -3047,10 +3050,10 @@ func (h *HandlersMap) AdminTeamLogosPOSTHandler(w http.ResponseWriter, r *http.R uploadedLogoExt = ext uploadedLogoAsset = true rawLogo = customLogoAssetPath(slug, ext) - } else if ext != "" && ext != ".svg" { + case ext != "" && ext != ".svg": writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_uploaded_logo_type")) return - } else { + default: if _, svgErr := buildUploadedLogoSymbol(slug, uploadData); svgErr != nil { writeError(http.StatusBadRequest, h.T(r.Context())("admin.msg.invalid_svg")) return @@ -3131,7 +3134,7 @@ func (h *HandlersMap) AdminTeamLogoUpdatePOSTHandler(w http.ResponseWriter, r *h uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -3243,7 +3246,7 @@ func (h *HandlersMap) AdminUsersTemplateHandler(w http.ResponseWriter, r *http.R // Get UUID from URL path parameters and validate it uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -3300,7 +3303,7 @@ func (h *HandlersMap) AdminUsersPOSTHandler(w http.ResponseWriter, r *http.Reque // Get UUID from URL path parameters and validate it uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -3422,7 +3425,7 @@ func (h *HandlersMap) AdminUserUpdatePOSTHandler(w http.ResponseWriter, r *http. uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -3561,7 +3564,7 @@ func (h *HandlersMap) AdminUserPasswordPOSTHandler(w http.ResponseWriter, r *htt uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -3608,8 +3611,7 @@ func (h *HandlersMap) AdminUserPasswordPOSTHandler(w http.ResponseWriter, r *htt return } - switch { - case strings.TrimSpace(req.NewPassword) == "": + if strings.TrimSpace(req.NewPassword) == "" { writeError(http.StatusBadRequest, h.T(r.Context())("profile.new_required")) return } @@ -3645,7 +3647,7 @@ func (h *HandlersMap) AdminUsersExportHandler(w http.ResponseWriter, r *http.Req uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -3687,7 +3689,7 @@ func (h *HandlersMap) AdminUsersImportHandler(w http.ResponseWriter, r *http.Req uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -3744,7 +3746,7 @@ func (h *HandlersMap) AdminUsersEnableAllPOSTHandler(w http.ResponseWriter, r *h uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -3775,7 +3777,7 @@ func (h *HandlersMap) AdminUsersDisableAllPOSTHandler(w http.ResponseWriter, r * uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -3818,7 +3820,7 @@ func (h *HandlersMap) AdminUsersDeleteAllPOSTHandler(w http.ResponseWriter, r *h uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -3859,7 +3861,7 @@ func (h *HandlersMap) AdminChallengesExportHandler(w http.ResponseWriter, r *htt uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -3900,7 +3902,7 @@ func (h *HandlersMap) AdminChallengesImportHandler(w http.ResponseWriter, r *htt uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -3959,7 +3961,7 @@ func (h *HandlersMap) AdminChallengesDeleteAllPOSTHandler(w http.ResponseWriter, uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -4025,7 +4027,7 @@ func (h *HandlersMap) AdminChallengesEnableAllPOSTHandler(w http.ResponseWriter, uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -4067,7 +4069,7 @@ func (h *HandlersMap) AdminChallengesDisableAllPOSTHandler(w http.ResponseWriter uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -4109,7 +4111,7 @@ func (h *HandlersMap) AdminChallengeCategoriesDeleteAllPOSTHandler(w http.Respon uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -4158,7 +4160,7 @@ func (h *HandlersMap) AdminChallengesTemplateHandler(w http.ResponseWriter, r *h // Get UUID from URL path parameters and validate it uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -4352,7 +4354,7 @@ func (h *HandlersMap) AdminChallengesPOSTHandler(w http.ResponseWriter, r *http. // Get UUID from URL path parameters and validate it uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -4518,7 +4520,7 @@ func (h *HandlersMap) AdminChallengeUpdatePOSTHandler(w http.ResponseWriter, r * // Get UUID from URL path parameters and validate it uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -4729,7 +4731,7 @@ func (h *HandlersMap) AdminChallengeDeletePOSTHandler(w http.ResponseWriter, r * uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -4795,7 +4797,7 @@ func (h *HandlersMap) AdminChallengeCategoriesPOSTHandler(w http.ResponseWriter, } uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -4859,7 +4861,7 @@ func (h *HandlersMap) AdminChallengeCategoryUpdatePOSTHandler(w http.ResponseWri } uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -4934,7 +4936,7 @@ func (h *HandlersMap) AdminChallengeCategoryDeletePOSTHandler(w http.ResponseWri } uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -4995,7 +4997,7 @@ func (h *HandlersMap) AdminActivityTemplateHandler(w http.ResponseWriter, r *htt // Get UUID from URL path parameters and validate it uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -5041,7 +5043,7 @@ func (h *HandlersMap) AdminActivityPOSTHandler(w http.ResponseWriter, r *http.Re uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -5109,7 +5111,7 @@ func (h *HandlersMap) AdminActivityDeletePOSTHandler(w http.ResponseWriter, r *h uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -5154,7 +5156,7 @@ func (h *HandlersMap) AdminChatTemplateHandler(w http.ResponseWriter, r *http.Re } uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -5215,7 +5217,7 @@ func (h *HandlersMap) AdminChatSetHiddenPOSTHandler(w http.ResponseWriter, r *ht } uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -5301,7 +5303,7 @@ func (h *HandlersMap) AdminChatDeletePOSTHandler(w http.ResponseWriter, r *http. } uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -5360,7 +5362,7 @@ func (h *HandlersMap) AdminCountriesTemplateHandler(w http.ResponseWriter, r *ht // Get UUID from URL path parameters and validate it uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -5422,7 +5424,7 @@ func (h *HandlersMap) AdminCountriesDeleteAllPOSTHandler(w http.ResponseWriter, uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -5496,7 +5498,7 @@ func (h *HandlersMap) AdminCountryUpdatePOSTHandler(w http.ResponseWriter, r *ht // Get UUID from URL path parameters and validate it uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } diff --git a/backend/cmd/map/handlers/auth.go b/backend/cmd/map/handlers/auth.go index 53edfd6..4a5ab0d 100644 --- a/backend/cmd/map/handlers/auth.go +++ b/backend/cmd/map/handlers/auth.go @@ -25,7 +25,7 @@ func (h *HandlersMap) LoginPOSTHandler(w http.ResponseWriter, r *http.Request) { // Get UUID from URL path parameters and validate it uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } diff --git a/backend/cmd/map/handlers/get.go b/backend/cmd/map/handlers/get.go index 431b775..4e5c52d 100644 --- a/backend/cmd/map/handlers/get.go +++ b/backend/cmd/map/handlers/get.go @@ -44,8 +44,8 @@ func (h *HandlersMap) ErrorHandler(w http.ResponseWriter, r *http.Request) { } } templateData := ErrorTemplateData{ - Title: "MapCTF: Error", - Error: msg, + Title: "MapCTF: Error", + Error: msg, Status: status, Header: header, } diff --git a/backend/cmd/map/handlers/json.go b/backend/cmd/map/handlers/json.go index 9cdfd5f..d89de1a 100644 --- a/backend/cmd/map/handlers/json.go +++ b/backend/cmd/map/handlers/json.go @@ -72,7 +72,7 @@ func (h *HandlersMap) validatedJSONUUID(w http.ResponseWriter, r *http.Request) return "", false } if uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) HTTPResponse(w, JSONApplicationUTF8, http.StatusBadRequest, MapErrorResponse{Error: h.T(r.Context())("feed.invalid_uuid")}) return "", false } diff --git a/backend/cmd/map/handlers/locale.go b/backend/cmd/map/handlers/locale.go index b7c7fcb..0e03b4d 100644 --- a/backend/cmd/map/handlers/locale.go +++ b/backend/cmd/map/handlers/locale.go @@ -11,11 +11,15 @@ import ( "golang.org/x/text/language" ) +// contextKey is a dedicated type for request context keys so they never +// collide with keys defined elsewhere using the built-in string type. +type contextKey string + const ( // ContextKeyLocale stores the resolved language.Tag in the request context. - ContextKeyLocale string = "locale" + ContextKeyLocale contextKey = "locale" // ContextKeyT stores the per-request translation func in the request context. - ContextKeyT string = "t" + ContextKeyT contextKey = "t" ) var ( diff --git a/backend/cmd/map/handlers/post.go b/backend/cmd/map/handlers/post.go index f867d22..ee469e0 100644 --- a/backend/cmd/map/handlers/post.go +++ b/backend/cmd/map/handlers/post.go @@ -25,7 +25,7 @@ func (h *HandlersMap) RegistrationPOSTHandler(w http.ResponseWriter, r *http.Req // Get UUID from URL path parameters and validate it uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -101,7 +101,7 @@ func (h *HandlersMap) ChatPOSTHandler(w http.ResponseWriter, r *http.Request) { // Get UUID from URL path parameters and validate it uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -149,7 +149,7 @@ func (h *HandlersMap) ScorePOSTHandler(w http.ResponseWriter, r *http.Request) { uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -335,7 +335,7 @@ func (h *HandlersMap) HintPOSTHandler(w http.ResponseWriter, r *http.Request) { uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -542,7 +542,7 @@ func (h *HandlersMap) HintGETHandler(w http.ResponseWriter, r *http.Request) { uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } diff --git a/backend/cmd/map/handlers/templates.go b/backend/cmd/map/handlers/templates.go index 2fec84e..b3e1621 100644 --- a/backend/cmd/map/handlers/templates.go +++ b/backend/cmd/map/handlers/templates.go @@ -28,7 +28,7 @@ func (h *HandlersMap) IndexTemplateHandler(w http.ResponseWriter, r *http.Reques // Get UUID from URL path parameters and validate it uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -93,7 +93,7 @@ func (h *HandlersMap) LoginHandler(w http.ResponseWriter, r *http.Request) { // Get UUID from URL path parameters and validate it uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -153,7 +153,7 @@ func (h *HandlersMap) RegistrationTemplateHandler(w http.ResponseWriter, r *http // Get UUID from URL path parameters and validate it uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -225,7 +225,7 @@ func (h *HandlersMap) CountdownTemplateHandler(w http.ResponseWriter, r *http.Re // Get UUID from URL path parameters and validate it uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -307,7 +307,7 @@ func (h *HandlersMap) RulesTemplateHandler(w http.ResponseWriter, r *http.Reques // Get UUID from URL path parameters and validate it uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -344,7 +344,7 @@ func (h *HandlersMap) GameboardTemplateHandler(w http.ResponseWriter, r *http.Re // Get UUID from URL path parameters and validate it uuid := chi.URLParam(r, "uuid") if uuid == "" || uuid != h.Config.Map.UUID { - log.Err(errors.New("Invalid UUID")).Msgf("UUID: %s", uuid) + log.Err(errors.New("invalid UUID")).Msgf("UUID: %s", uuid) h.ErrorInvalidUUID(w, r) return } @@ -426,9 +426,10 @@ func (h *HandlersMap) GameboardTemplateHandler(w http.ResponseWriter, r *http.Re } if h.Countries != nil { countriesList, err := h.Countries.GetAll() - if err != nil { + switch { + case err != nil: log.Warn().Err(err).Msg("error loading countries for gameboard") - } else if h.Challenges != nil { + case h.Challenges != nil: activeChallenges, challengeErr := h.Challenges.GetActive(uuid) if challengeErr != nil { log.Warn().Err(challengeErr).Msg("error loading active challenges for gameboard countries") @@ -455,7 +456,7 @@ func (h *HandlersMap) GameboardTemplateHandler(w http.ResponseWriter, r *http.Re } templateData.Countries = renderCountries } - } else { + default: templateData.Countries = countriesList } } diff --git a/backend/cmd/map/handlers/utils.go b/backend/cmd/map/handlers/utils.go index 13be522..6b126ed 100644 --- a/backend/cmd/map/handlers/utils.go +++ b/backend/cmd/map/handlers/utils.go @@ -90,3 +90,18 @@ func getRealIP(r *http.Request) string { } return ip } + +// RealIP is a middleware that overrides r.RemoteAddr with the originating +// client IP resolved from the X-Real-Ip / X-Forwarded-For headers. It replaces +// chi's deprecated middleware.RealIP, which is vulnerable to IP spoofing +// because it trusts those headers unconditionally regardless of whether the +// upstream infrastructure actually sets them. Use this only behind a trusted +// reverse proxy that populates the headers. +func RealIP(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if ip := getRealIP(r); ip != "" { + r.RemoteAddr = ip + } + next.ServeHTTP(w, r) + }) +} diff --git a/backend/cmd/map/main.go b/backend/cmd/map/main.go index a1349b9..3279a89 100644 --- a/backend/cmd/map/main.go +++ b/backend/cmd/map/main.go @@ -299,7 +299,7 @@ func mapCTFService() { muxMap := chi.NewRouter() // Middleware muxMap.Use(middleware.RequestID) - muxMap.Use(middleware.RealIP) + muxMap.Use(handlers.RealIP) muxMap.Use(middleware.Logger) muxMap.Use(middleware.Recoverer) muxMap.Use(middleware.Timeout(30 * time.Second)) @@ -674,7 +674,9 @@ func main() { if usersMgr.Exists(username, uuid) { // User exists, reset password log.Info().Msgf("User '%s' already exists for UUID %s, resetting password...", username, uuid) - usersMgr.SetPassword(username, password, uuid) + if err := usersMgr.SetPassword(username, password, uuid); err != nil { + return fmt.Errorf("failed to reset password for admin user '%s': %w", username, err) + } fmt.Printf("Password reset successfully for admin user '%s' (UUID %s).\n", username, uuid) } else { // User doesn't exist, create it diff --git a/backend/pkg/backend/backend.go b/backend/pkg/backend/backend.go index 32ac3ff..ce1de26 100644 --- a/backend/pkg/backend/backend.go +++ b/backend/pkg/backend/backend.go @@ -44,7 +44,7 @@ func LoadConfiguration(file, key string) (config.ConfigurationDB, error) { // Backend values dbRaw := viper.Sub(key) if dbRaw == nil { - return config, fmt.Errorf("YAML key %s not found in %s", key, file) + return config, fmt.Errorf("yAML key %s not found in %s", key, file) } if err := dbRaw.Unmarshal(&config); err != nil { return config, err @@ -121,7 +121,7 @@ func (db *DBManager) Check() error { func CreateDBManagerFile(file string) (*DBManager, error) { dbConfig, err := LoadConfiguration(file, DBKey) if err != nil { - return nil, fmt.Errorf("Failed to load DB configuration - %w", err) + return nil, fmt.Errorf("failed to load DB configuration - %w", err) } return CreateDBManager(dbConfig) } @@ -133,7 +133,7 @@ func CreateDBManager(dbConfig config.ConfigurationDB) (*DBManager, error) { db.DSN = PrepareDSN(dbConfig) dbConn, err := db.GetDB() if err != nil { - return nil, fmt.Errorf("Failed to get DB - %w", err) + return nil, fmt.Errorf("failed to get DB - %w", err) } db.Conn = dbConn return db, nil diff --git a/backend/pkg/cache/cache.go b/backend/pkg/cache/cache.go index a963bd2..71324be 100644 --- a/backend/pkg/cache/cache.go +++ b/backend/pkg/cache/cache.go @@ -4,8 +4,8 @@ import ( "context" "fmt" - redis "github.com/redis/go-redis/v9" "github.com/jmpsec/mapctf/pkg/config" + redis "github.com/redis/go-redis/v9" ) const ( diff --git a/backend/pkg/challenges/challenges.go b/backend/pkg/challenges/challenges.go index ce847c6..811239a 100644 --- a/backend/pkg/challenges/challenges.go +++ b/backend/pkg/challenges/challenges.go @@ -50,7 +50,7 @@ func CreateChallengeManager(backend *gorm.DB) (*ChallengeManager, error) { DB: backend, } if err := backend.AutoMigrate(&Challenge{}); err != nil { - return nil, fmt.Errorf("Failed to AutoMigrate table (challenges): %w", err) + return nil, fmt.Errorf("failed to AutoMigrate table (challenges): %w", err) } // Best-effort legacy migration: copy old penalty values into hint_penalty. if backend.Migrator().HasColumn(&Challenge{}, "penalty") { @@ -60,7 +60,7 @@ func CreateChallengeManager(backend *gorm.DB) (*ChallengeManager, error) { } // table categories if err := backend.AutoMigrate(&Category{}); err != nil { - return nil, fmt.Errorf("Failed to AutoMigrate table (categories): %w", err) + return nil, fmt.Errorf("failed to AutoMigrate table (categories): %w", err) } return c, nil } @@ -68,7 +68,7 @@ func CreateChallengeManager(backend *gorm.DB) (*ChallengeManager, error) { // Create challenge func (m *ChallengeManager) Create(challenge Challenge) error { if err := m.DB.Create(&challenge).Error; err != nil { - return fmt.Errorf("Create Challenge %w", err) + return fmt.Errorf("create Challenge %w", err) } return nil } @@ -76,7 +76,7 @@ func (m *ChallengeManager) Create(challenge Challenge) error { // CreateAndReturn challenge and populate its generated fields (ID, timestamps) func (m *ChallengeManager) CreateAndReturn(challenge *Challenge) error { if err := m.DB.Create(challenge).Error; err != nil { - return fmt.Errorf("Create Challenge %w", err) + return fmt.Errorf("create Challenge %w", err) } return nil } @@ -100,7 +100,7 @@ func (m *ChallengeManager) Update(challenge Challenge) error { "hint_penalty": challenge.HintPenalty, "help_penalty": challenge.HelpPenalty, }).Error; err != nil { - return fmt.Errorf("Update Challenge %w", err) + return fmt.Errorf("update Challenge %w", err) } return nil } @@ -133,7 +133,7 @@ func NormalizeChallengeURL(raw string) (string, error) { // Delete challenge func (m *ChallengeManager) Delete(id uint, uuid string) error { if err := m.DB.Where("id = ? AND uuid = ?", id, uuid).Delete(&Challenge{}).Error; err != nil { - return fmt.Errorf("Delete Challenge %w", err) + return fmt.Errorf("delete Challenge %w", err) } return nil } @@ -142,7 +142,7 @@ func (m *ChallengeManager) Delete(id uint, uuid string) error { func (m *ChallengeManager) DeleteAll(uuid string) (int64, error) { result := m.DB.Where("uuid = ?", uuid).Delete(&Challenge{}) if result.Error != nil { - return 0, fmt.Errorf("Delete All Challenges %w", result.Error) + return 0, fmt.Errorf("delete All Challenges %w", result.Error) } return result.RowsAffected, nil } @@ -151,7 +151,7 @@ func (m *ChallengeManager) DeleteAll(uuid string) (int64, error) { func (m *ChallengeManager) SetAllActive(uuid string, active bool) (int64, error) { result := m.DB.Model(&Challenge{}).Where("uuid = ?", uuid).Update("active", active) if result.Error != nil { - return 0, fmt.Errorf("Set All Challenge Active %w", result.Error) + return 0, fmt.Errorf("set all challenge active: %w", result.Error) } return result.RowsAffected, nil } @@ -159,7 +159,7 @@ func (m *ChallengeManager) SetAllActive(uuid string, active bool) (int64, error) // Create category func (m *ChallengeManager) CreateCategory(category Category) error { if err := m.DB.Create(&category).Error; err != nil { - return fmt.Errorf("Create Category: %w", err) + return fmt.Errorf("create Category: %w", err) } return nil } @@ -173,7 +173,7 @@ func (m *ChallengeManager) UpdateCategory(category Category) error { "description": category.Description, "logo": category.Logo, }).Error; err != nil { - return fmt.Errorf("Update Category: %w", err) + return fmt.Errorf("update Category: %w", err) } return nil } @@ -181,7 +181,7 @@ func (m *ChallengeManager) UpdateCategory(category Category) error { // DeleteCategory deletes a category by id and uuid func (m *ChallengeManager) DeleteCategory(id uint, uuid string) error { if err := m.DB.Where("id = ? AND uuid = ?", id, uuid).Delete(&Category{}).Error; err != nil { - return fmt.Errorf("Delete Category: %w", err) + return fmt.Errorf("delete Category: %w", err) } return nil } @@ -192,7 +192,7 @@ func (m *ChallengeManager) CategoryHasChallenges(categoryID uint, uuid string) ( if err := m.DB.Model(&Challenge{}). Where("category_id = ? AND uuid = ?", categoryID, uuid). Count(&count).Error; err != nil { - return false, fmt.Errorf("Category Has Challenges: %w", err) + return false, fmt.Errorf("category Has Challenges: %w", err) } return count > 0, nil } @@ -201,7 +201,7 @@ func (m *ChallengeManager) CategoryHasChallenges(categoryID uint, uuid string) ( func (m *ChallengeManager) GetByID(id uint, uuid string) (Challenge, error) { var challenge Challenge if err := m.DB.Where("id = ? AND uuid = ?", id, uuid).First(&challenge).Error; err != nil { - return Challenge{}, fmt.Errorf("Get Challenge by ID and Entity: %w", err) + return Challenge{}, fmt.Errorf("get challenge by ID and entity: %w", err) } return challenge, nil } @@ -210,7 +210,7 @@ func (m *ChallengeManager) GetByID(id uint, uuid string) (Challenge, error) { func (m *ChallengeManager) GetAll(uuid string) ([]Challenge, error) { var challenges []Challenge if err := m.DB.Where("uuid = ?", uuid).Find(&challenges).Error; err != nil { - return challenges, fmt.Errorf("Get All Challenges by Entity: %w", err) + return challenges, fmt.Errorf("get All Challenges by Entity: %w", err) } return challenges, nil } @@ -219,7 +219,7 @@ func (m *ChallengeManager) GetAll(uuid string) ([]Challenge, error) { func (m *ChallengeManager) GetActive(uuid string) ([]Challenge, error) { var challenges []Challenge if err := m.DB.Where("uuid = ? AND active = ?", uuid, true).Find(&challenges).Error; err != nil { - return challenges, fmt.Errorf("Get Active Challenges by Entity: %w", err) + return challenges, fmt.Errorf("get Active Challenges by Entity: %w", err) } return challenges, nil } @@ -228,7 +228,7 @@ func (m *ChallengeManager) GetActive(uuid string) ([]Challenge, error) { func (m *ChallengeManager) GetAllCategories(uuid string) ([]Category, error) { var categories []Category if err := m.DB.Where("uuid = ?", uuid).Find(&categories).Error; err != nil { - return categories, fmt.Errorf("Get All Categories by Entity: %w", err) + return categories, fmt.Errorf("get All Categories by Entity: %w", err) } return categories, nil } @@ -237,7 +237,7 @@ func (m *ChallengeManager) GetAllCategories(uuid string) ([]Category, error) { func (m *ChallengeManager) DeleteAllCategories(uuid string) (int64, error) { result := m.DB.Where("uuid = ?", uuid).Delete(&Category{}) if result.Error != nil { - return 0, fmt.Errorf("Delete All Categories %w", result.Error) + return 0, fmt.Errorf("delete All Categories %w", result.Error) } return result.RowsAffected, nil } @@ -246,7 +246,7 @@ func (m *ChallengeManager) DeleteAllCategories(uuid string) (int64, error) { func (m *ChallengeManager) GetCategoryByID(id uint, uuid string) (Category, error) { var category Category if err := m.DB.Where("id = ? AND uuid = ?", id, uuid).First(&category).Error; err != nil { - return Category{}, fmt.Errorf("Get Category by ID and Entity: %w", err) + return Category{}, fmt.Errorf("get Category by ID and Entity: %w", err) } return category, nil } @@ -290,5 +290,5 @@ func (m *ChallengeManager) NewCategory(name, description, logo string, uuid stri UUID: uuid, }, nil } - return Category{}, fmt.Errorf("Category with name '%s' already exists", name) + return Category{}, fmt.Errorf("category with name '%s' already exists", name) } diff --git a/backend/pkg/chat/chat.go b/backend/pkg/chat/chat.go index 5db2907..a073dd5 100644 --- a/backend/pkg/chat/chat.go +++ b/backend/pkg/chat/chat.go @@ -46,7 +46,7 @@ func CreateChatManager(backend *gorm.DB, uuid string) (*ChatManager, error) { // Create new chat entry func (m *ChatManager) Create(entry ChatEntry) error { if err := m.DB.Create(&entry).Error; err != nil { - return fmt.Errorf("Create ChatEntry %w", err) + return fmt.Errorf("create ChatEntry %w", err) } return nil } @@ -105,7 +105,7 @@ func (m *ChatManager) New(username, body string, uuid string, teamID uint, maxLe func (m *ChatManager) CreateNew(username, body string, teamID uint, maxLen int) error { entry, err := m.New(username, body, m.UUID, teamID, maxLen) if err != nil { - return fmt.Errorf("CreateNew ChatEntry %w", err) + return fmt.Errorf("createNew ChatEntry %w", err) } return m.Create(entry) } @@ -122,7 +122,7 @@ func (m *ChatManager) GetByID(id uint) (ChatEntry, error) { // Delete chat entry by ID func (m *ChatManager) Delete(id uint) error { if err := m.DB.Where("id = ? AND uuid = ?", id, m.UUID).Delete(&ChatEntry{}).Error; err != nil { - return fmt.Errorf("Delete ChatEntry %w", err) + return fmt.Errorf("delete ChatEntry %w", err) } return nil } @@ -130,7 +130,7 @@ func (m *ChatManager) Delete(id uint) error { // DeleteAll chat entries by UUID func (m *ChatManager) DeleteAll() error { if err := m.DB.Where("uuid = ?", m.UUID).Delete(&ChatEntry{}).Error; err != nil { - return fmt.Errorf("DeleteAll ChatEntries %w", err) + return fmt.Errorf("deleteAll ChatEntries %w", err) } return nil } @@ -138,7 +138,7 @@ func (m *ChatManager) DeleteAll() error { // DeleteAllByTeamID chat entries by team ID and UUID func (m *ChatManager) DeleteAllByTeamID(teamID uint) error { if err := m.DB.Where("team_id = ? AND uuid = ?", teamID, m.UUID).Delete(&ChatEntry{}).Error; err != nil { - return fmt.Errorf("DeleteAllByTeamID ChatEntries %w", err) + return fmt.Errorf("deleteAllByTeamID ChatEntries %w", err) } return nil } @@ -146,7 +146,7 @@ func (m *ChatManager) DeleteAllByTeamID(teamID uint) error { // DeleteAllByUsername chat entries by username and UUID func (m *ChatManager) DeleteAllByUsername(username string) error { if err := m.DB.Where("username = ? AND uuid = ?", username, m.UUID).Delete(&ChatEntry{}).Error; err != nil { - return fmt.Errorf("DeleteAllByUsername ChatEntries %w", err) + return fmt.Errorf("deleteAllByUsername ChatEntries %w", err) } return nil } @@ -154,7 +154,7 @@ func (m *ChatManager) DeleteAllByUsername(username string) error { // SetHiddenByID sets the Hidden field of a chat entry by ID func (m *ChatManager) SetHiddenByID(id uint, hidden bool) error { if err := m.DB.Model(&ChatEntry{}).Where("id = ? AND uuid = ?", id, m.UUID).Update("hidden", hidden).Error; err != nil { - return fmt.Errorf("SetHiddenByID ChatEntry %w", err) + return fmt.Errorf("setHiddenByID ChatEntry %w", err) } return nil } diff --git a/backend/pkg/logs/activity.go b/backend/pkg/logs/activity.go index 18b4f51..098751f 100644 --- a/backend/pkg/logs/activity.go +++ b/backend/pkg/logs/activity.go @@ -37,7 +37,7 @@ type ActivityLog struct { // CreateActivity logs a new activity func (l *LogManager) CreateActivity(activity ActivityLog) error { if err := l.DB.Create(&activity).Error; err != nil { - return fmt.Errorf("Create ActivityLog %w", err) + return fmt.Errorf("create ActivityLog: %w", err) } return nil } @@ -96,7 +96,7 @@ func (l *LogManager) NewAnnouncement(subject, message string, uuid string) (Acti func (l *LogManager) AllActivity(uuid string) ([]ActivityLog, error) { var activities []ActivityLog if err := l.DB.Where("uuid = ?", uuid).Find(&activities).Error; err != nil { - return activities, fmt.Errorf("Get All Activity Logs for UUID: %w", err) + return activities, fmt.Errorf("get all activity logs for UUID: %w", err) } return activities, nil } @@ -104,7 +104,7 @@ func (l *LogManager) AllActivity(uuid string) ([]ActivityLog, error) { // DeleteActivity deletes a single activity log by ID scoped to the given UUID. func (l *LogManager) DeleteActivity(id uint, uuid string) error { if err := l.DB.Where("id = ? AND uuid = ?", id, uuid).Delete(&ActivityLog{}).Error; err != nil { - return fmt.Errorf("Delete ActivityLog: %w", err) + return fmt.Errorf("delete ActivityLog: %w", err) } return nil } @@ -113,7 +113,7 @@ func (l *LogManager) DeleteActivity(id uint, uuid string) error { func (l *LogManager) GetActivityByChallengeID(challengeID uint, uuid string) ([]ActivityLog, error) { var activities []ActivityLog if err := l.DB.Where("challenge_id = ? AND uuid = ?", challengeID, uuid).Find(&activities).Error; err != nil { - return activities, fmt.Errorf("Get Activity Logs by Challenge ID: %w", err) + return activities, fmt.Errorf("get Activity Logs by Challenge ID: %w", err) } return activities, nil } diff --git a/backend/pkg/settings/settings.go b/backend/pkg/settings/settings.go index 4829214..66b8ce5 100644 --- a/backend/pkg/settings/settings.go +++ b/backend/pkg/settings/settings.go @@ -83,8 +83,8 @@ var StringSettings = map[string]string{ // DateSettings to be used as check for valid setting and to keep default value var DateSettings = map[string]time.Time{ - GameStartTime: time.Time{}, - GameEndTime: time.Time{}, + GameStartTime: {}, + GameEndTime: {}, } // IntSettings to be used as check for valid setting and to keep default value @@ -236,11 +236,11 @@ func (m *SettingsManager) Initialization(uuid string) error { // Create new setting func (m *SettingsManager) Create(setting PlatformSetting) error { if err := m.DB.Create(&setting).Error; err != nil { - return fmt.Errorf("Create PlatformSetting %w", err) + return fmt.Errorf("create PlatformSetting %w", err) } // Log the creation event if err := m.LogEvent(setting.ID, EventCreate, m.Service, setting.UUID); err != nil { - return fmt.Errorf("LogEvent PlatformSetting %w", err) + return fmt.Errorf("logEvent PlatformSetting %w", err) } return nil } @@ -288,7 +288,7 @@ func (m *SettingsManager) LogEvent(settingID uint, event string, changedBy strin UUID: uuid, } if err := m.DB.Create(&log).Error; err != nil { - return fmt.Errorf("Create SettingLog %w", err) + return fmt.Errorf("create SettingLog %w", err) } return nil } @@ -325,10 +325,10 @@ func (m *SettingsManager) New(name, valueType, description string, uuid string, // Save new setting func (m *SettingsManager) Save(setting PlatformSetting, username string) error { if err := m.DB.Save(&setting).Error; err != nil { - return fmt.Errorf("Save PlatformSetting %w", err) + return fmt.Errorf("save PlatformSetting %w", err) } if err := m.LogEvent(setting.ID, EventUpdate, username, setting.UUID); err != nil { - return fmt.Errorf("LogEvent PlatformSetting %w", err) + return fmt.Errorf("logEvent PlatformSetting %w", err) } return nil } @@ -349,7 +349,7 @@ func (m *SettingsManager) Change(name, valueType string, uuid string, value any, return fmt.Errorf("failed to update setting value: %w", err) } if err := m.LogEvent(setting.ID, EventUpdate, username, setting.UUID); err != nil { - return fmt.Errorf("LogEvent PlatformSetting %w", err) + return fmt.Errorf("logEvent PlatformSetting %w", err) } return nil } diff --git a/backend/pkg/settings/settings_test.go b/backend/pkg/settings/settings_test.go index 8d5722c..5477974 100644 --- a/backend/pkg/settings/settings_test.go +++ b/backend/pkg/settings/settings_test.go @@ -345,7 +345,7 @@ func TestChangeSuccessAndLogFailure(t *testing.T) { err := m.Change("with_value_column", TypeString, "tenant-a", "after2", "alice") require.Error(t, err) - require.Contains(t, err.Error(), "LogEvent PlatformSetting") + require.Contains(t, err.Error(), "logEvent PlatformSetting") }) } @@ -355,11 +355,11 @@ func TestCreateAndLogEventFailWhenDBClosed(t *testing.T) { err := m.Create(PlatformSetting{Name: "x", UUID: "tenant-a"}) require.Error(t, err) - require.Contains(t, err.Error(), "Create PlatformSetting") + require.Contains(t, err.Error(), "create PlatformSetting") err = m.LogEvent(1, EventUpdate, "alice", "tenant-a") require.Error(t, err) - require.Contains(t, err.Error(), "Create SettingLog") + require.Contains(t, err.Error(), "create SettingLog") } func TestSaveFailsWhenDBClosed(t *testing.T) { @@ -379,7 +379,7 @@ func TestSaveFailsWhenDBClosed(t *testing.T) { require.NoError(t, sqlDB.Close()) err = m.Save(loaded, "alice") require.Error(t, err) - require.Contains(t, err.Error(), "Save PlatformSetting") + require.Contains(t, err.Error(), "save PlatformSetting") } func TestTypedGettersAndSetters(t *testing.T) { @@ -574,5 +574,5 @@ func TestSaveFailsWhenLogInsertFails(t *testing.T) { err = m.Save(loaded, "alice") require.Error(t, err) - require.Contains(t, err.Error(), "LogEvent PlatformSetting") + require.Contains(t, err.Error(), "logEvent PlatformSetting") } diff --git a/backend/pkg/teams/logos.go b/backend/pkg/teams/logos.go index 740d65f..6f0cfcd 100644 --- a/backend/pkg/teams/logos.go +++ b/backend/pkg/teams/logos.go @@ -115,7 +115,7 @@ func (m *TeamManager) NewLogo(name, logo string, enabled, custom bool, createdBy // CreateLogo to save a new team logo func (m *TeamManager) CreateLogo(logo TeamLogo) error { if err := m.DB.Create(&logo).Error; err != nil { - return fmt.Errorf("Create Team Logo: %w", err) + return fmt.Errorf("create Team Logo: %w", err) } return nil } diff --git a/backend/pkg/teams/scores_test.go b/backend/pkg/teams/scores_test.go index eda7236..2f7405a 100644 --- a/backend/pkg/teams/scores_test.go +++ b/backend/pkg/teams/scores_test.go @@ -62,9 +62,9 @@ func TestGetScores(t *testing.T) { // Create test scores testScores := []TeamScore{ - {TeamID: 1, ChallengeID: 10, Points: 100, UUID: testUUID1, ScoredBy: "5"}, - {TeamID: 1, ChallengeID: 20, Points: 200, UUID: testUUID1, ScoredBy: "5"}, - {TeamID: 1, ChallengeID: 30, Points: 150, UUID: testUUID1, ScoredBy: "6"}, + {TeamID: 1, ChallengeID: 10, Points: 100, UUID: testUUID1, ScoredBy: "5"}, + {TeamID: 1, ChallengeID: 20, Points: 200, UUID: testUUID1, ScoredBy: "5"}, + {TeamID: 1, ChallengeID: 30, Points: 150, UUID: testUUID1, ScoredBy: "6"}, } for _, score := range testScores { @@ -176,9 +176,9 @@ func TestGetScoresMultipleTeams(t *testing.T) { // Create scores for different teams testScores := []TeamScore{ - {TeamID: 1, ChallengeID: 10, Points: 100, UUID: testUUID1, ScoredBy: "5"}, - {TeamID: 2, ChallengeID: 10, Points: 200, UUID: testUUID1, ScoredBy: "6"}, - {TeamID: 1, ChallengeID: 20, Points: 150, UUID: testUUID1, ScoredBy: "5"}, + {TeamID: 1, ChallengeID: 10, Points: 100, UUID: testUUID1, ScoredBy: "5"}, + {TeamID: 2, ChallengeID: 10, Points: 200, UUID: testUUID1, ScoredBy: "6"}, + {TeamID: 1, ChallengeID: 20, Points: 150, UUID: testUUID1, ScoredBy: "5"}, } for _, score := range testScores { @@ -214,9 +214,9 @@ func TestGetScoresMultipleEntities(t *testing.T) { // Create scores for different UUIDs testScores := []TeamScore{ - {TeamID: 1, ChallengeID: 10, Points: 100, UUID: testUUID1, ScoredBy: "5"}, - {TeamID: 1, ChallengeID: 20, Points: 200, UUID: testUUID2, ScoredBy: "6"}, - {TeamID: 1, ChallengeID: 30, Points: 150, UUID: testUUID1, ScoredBy: "5"}, + {TeamID: 1, ChallengeID: 10, Points: 100, UUID: testUUID1, ScoredBy: "5"}, + {TeamID: 1, ChallengeID: 20, Points: 200, UUID: testUUID2, ScoredBy: "6"}, + {TeamID: 1, ChallengeID: 30, Points: 150, UUID: testUUID1, ScoredBy: "5"}, } for _, score := range testScores { @@ -402,9 +402,9 @@ func TestCreateScoreMultiple(t *testing.T) { _, manager := setupTestDBForScores(t) scores := []TeamScore{ - {TeamID: 1, ChallengeID: 10, Points: 100, UUID: testUUID1, ScoredBy: "5"}, - {TeamID: 1, ChallengeID: 20, Points: 200, UUID: testUUID1, ScoredBy: "5"}, - {TeamID: 2, ChallengeID: 10, Points: 150, UUID: testUUID1, ScoredBy: "6"}, + {TeamID: 1, ChallengeID: 10, Points: 100, UUID: testUUID1, ScoredBy: "5"}, + {TeamID: 1, ChallengeID: 20, Points: 200, UUID: testUUID1, ScoredBy: "5"}, + {TeamID: 2, ChallengeID: 10, Points: 150, UUID: testUUID1, ScoredBy: "6"}, } for _, score := range scores { @@ -452,9 +452,9 @@ func TestGetScoreTotal(t *testing.T) { // Create test scores testScores := []TeamScore{ - {TeamID: 1, ChallengeID: 10, Points: 100, UUID: testUUID1, ScoredBy: "5"}, - {TeamID: 1, ChallengeID: 20, Points: 200, UUID: testUUID1, ScoredBy: "5"}, - {TeamID: 1, ChallengeID: 30, Points: 150, UUID: testUUID1, ScoredBy: "6"}, + {TeamID: 1, ChallengeID: 10, Points: 100, UUID: testUUID1, ScoredBy: "5"}, + {TeamID: 1, ChallengeID: 20, Points: 200, UUID: testUUID1, ScoredBy: "5"}, + {TeamID: 1, ChallengeID: 30, Points: 150, UUID: testUUID1, ScoredBy: "6"}, } for _, score := range testScores { @@ -553,10 +553,10 @@ func TestGetScoreTotalMultipleTeams(t *testing.T) { // Create scores for different teams testScores := []TeamScore{ - {TeamID: 1, ChallengeID: 10, Points: 100, UUID: testUUID1, ScoredBy: "5"}, - {TeamID: 2, ChallengeID: 10, Points: 200, UUID: testUUID1, ScoredBy: "6"}, - {TeamID: 1, ChallengeID: 20, Points: 150, UUID: testUUID1, ScoredBy: "5"}, - {TeamID: 2, ChallengeID: 20, Points: 250, UUID: testUUID1, ScoredBy: "6"}, + {TeamID: 1, ChallengeID: 10, Points: 100, UUID: testUUID1, ScoredBy: "5"}, + {TeamID: 2, ChallengeID: 10, Points: 200, UUID: testUUID1, ScoredBy: "6"}, + {TeamID: 1, ChallengeID: 20, Points: 150, UUID: testUUID1, ScoredBy: "5"}, + {TeamID: 2, ChallengeID: 20, Points: 250, UUID: testUUID1, ScoredBy: "6"}, } for _, score := range testScores { @@ -592,9 +592,9 @@ func TestGetScoreTotalMultipleEntities(t *testing.T) { // Create scores for different UUIDs testScores := []TeamScore{ - {TeamID: 1, ChallengeID: 10, Points: 100, UUID: testUUID1, ScoredBy: "5"}, - {TeamID: 1, ChallengeID: 20, Points: 200, UUID: testUUID2, ScoredBy: "6"}, - {TeamID: 1, ChallengeID: 30, Points: 150, UUID: testUUID1, ScoredBy: "5"}, + {TeamID: 1, ChallengeID: 10, Points: 100, UUID: testUUID1, ScoredBy: "5"}, + {TeamID: 1, ChallengeID: 20, Points: 200, UUID: testUUID2, ScoredBy: "6"}, + {TeamID: 1, ChallengeID: 30, Points: 150, UUID: testUUID1, ScoredBy: "5"}, } for _, score := range testScores { @@ -630,9 +630,9 @@ func TestGetScoreTotalWithNegativePoints(t *testing.T) { // Create scores with negative points (penalties) testScores := []TeamScore{ - {TeamID: 1, ChallengeID: 10, Points: 100, UUID: testUUID1, ScoredBy: "5"}, - {TeamID: 1, ChallengeID: 20, Points: -50, UUID: testUUID1, ScoredBy: "5"}, - {TeamID: 1, ChallengeID: 30, Points: 200, UUID: testUUID1, ScoredBy: "6"}, + {TeamID: 1, ChallengeID: 10, Points: 100, UUID: testUUID1, ScoredBy: "5"}, + {TeamID: 1, ChallengeID: 20, Points: -50, UUID: testUUID1, ScoredBy: "5"}, + {TeamID: 1, ChallengeID: 30, Points: 200, UUID: testUUID1, ScoredBy: "6"}, } for _, score := range testScores { @@ -658,8 +658,8 @@ func TestGetScoreTotalZeroPoints(t *testing.T) { // Create scores with zero points testScores := []TeamScore{ - {TeamID: 1, ChallengeID: 10, Points: 0, UUID: testUUID1, ScoredBy: "5"}, - {TeamID: 1, ChallengeID: 20, Points: 0, UUID: testUUID1, ScoredBy: "5"}, + {TeamID: 1, ChallengeID: 10, Points: 0, UUID: testUUID1, ScoredBy: "5"}, + {TeamID: 1, ChallengeID: 20, Points: 0, UUID: testUUID1, ScoredBy: "5"}, } for _, score := range testScores { @@ -685,9 +685,9 @@ func TestGetScoreTotalLargeNumbers(t *testing.T) { // Create scores with large point values testScores := []TeamScore{ - {TeamID: 1, ChallengeID: 10, Points: 1000000, UUID: testUUID1, ScoredBy: "5"}, - {TeamID: 1, ChallengeID: 20, Points: 2000000, UUID: testUUID1, ScoredBy: "5"}, - {TeamID: 1, ChallengeID: 30, Points: 3000000, UUID: testUUID1, ScoredBy: "6"}, + {TeamID: 1, ChallengeID: 10, Points: 1000000, UUID: testUUID1, ScoredBy: "5"}, + {TeamID: 1, ChallengeID: 20, Points: 2000000, UUID: testUUID1, ScoredBy: "5"}, + {TeamID: 1, ChallengeID: 30, Points: 3000000, UUID: testUUID1, ScoredBy: "6"}, } for _, score := range testScores { @@ -760,7 +760,7 @@ func TestGetScoresWithClosedDB(t *testing.T) { } sqlDB.Close() - _, err = manager.GetScores(1, testUUID1) + _, err = manager.GetScores(1, testUUID1) if err == nil { t.Error("Expected error when getting scores with closed database") } @@ -777,7 +777,7 @@ func TestGetScoreTotalWithClosedDB(t *testing.T) { } sqlDB.Close() - _, err = manager.GetScoreTotal(1, testUUID1) + _, err = manager.GetScoreTotal(1, testUUID1) if err == nil { t.Error("Expected error when getting score total with closed database") } diff --git a/backend/pkg/teams/teams.go b/backend/pkg/teams/teams.go index 7619db6..20b6645 100644 --- a/backend/pkg/teams/teams.go +++ b/backend/pkg/teams/teams.go @@ -50,19 +50,19 @@ func CreateTeams(backend *gorm.DB) (*TeamManager, error) { } // table platform_teams if err := backend.AutoMigrate(&PlatformTeam{}); err != nil { - return nil, fmt.Errorf("Failed to AutoMigrate table (platform_teams): %w", err) + return nil, fmt.Errorf("failed to AutoMigrate table (platform_teams): %w", err) } // table team_memberships if err := backend.AutoMigrate(&TeamMembership{}); err != nil { - return nil, fmt.Errorf("Failed to AutoMigrate table (team_memberships): %w", err) + return nil, fmt.Errorf("failed to AutoMigrate table (team_memberships): %w", err) } // table team_scores if err := backend.AutoMigrate(&TeamScore{}); err != nil { - return nil, fmt.Errorf("Failed to AutoMigrate table (team_scores): %w", err) + return nil, fmt.Errorf("failed to AutoMigrate table (team_scores): %w", err) } // table team_logos if err := backend.AutoMigrate(&TeamLogo{}); err != nil { - return nil, fmt.Errorf("Failed to AutoMigrate table (team_logos): %w", err) + return nil, fmt.Errorf("failed to AutoMigrate table (team_logos): %w", err) } return t, nil } @@ -70,7 +70,7 @@ func CreateTeams(backend *gorm.DB) (*TeamManager, error) { // Create new team func (m *TeamManager) Create(team PlatformTeam) error { if err := m.DB.Create(&team).Error; err != nil { - return fmt.Errorf("Create PlatformTeam %w", err) + return fmt.Errorf("create PlatformTeam %w", err) } return nil } diff --git a/backend/pkg/users/users.go b/backend/pkg/users/users.go index 95ce4ca..1d7c187 100644 --- a/backend/pkg/users/users.go +++ b/backend/pkg/users/users.go @@ -67,7 +67,7 @@ func CreateUserManager(backend *gorm.DB, jwtConfig *config.ConfigurationJWT) (*U // Create new user func (m *UserManager) Create(user PlatformUser) error { if err := m.DB.Create(&user).Error; err != nil { - return fmt.Errorf("Create PlatformUser %w", err) + return fmt.Errorf("create PlatformUser %w", err) } return nil } From 265ec54ae919cf200ec9f2f046c68ee957ae4be5 Mon Sep 17 00:00:00 2001 From: Javier Marcos <1271349+javuto@users.noreply.github.com> Date: Sat, 27 Jun 2026 10:06:32 +0200 Subject: [PATCH 3/3] Adding locales --- .gitignore | 1 + backend/pkg/i18n/locales/en.json | 838 +++++++++++++++++++++++++++++++ backend/pkg/i18n/locales/es.json | 838 +++++++++++++++++++++++++++++++ 3 files changed, 1677 insertions(+) create mode 100644 backend/pkg/i18n/locales/en.json create mode 100644 backend/pkg/i18n/locales/es.json diff --git a/.gitignore b/.gitignore index 7343968..96734ba 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ coverage.out *.json !map-countries-seed.json !team-logos-seed.json +!backend/pkg/i18n/locales/*.json *.yaml !mapctf.example.yaml *.yml diff --git a/backend/pkg/i18n/locales/en.json b/backend/pkg/i18n/locales/en.json new file mode 100644 index 0000000..5de3b3a --- /dev/null +++ b/backend/pkg/i18n/locales/en.json @@ -0,0 +1,838 @@ +{ + "nav.play_ctf": "Play CTF", + "nav.rules": "Rules", + "nav.registration": "Registration", + "nav.gameboard": "Gameboard", + "nav.login": "Login", + "page.index.title": "MapCTF: Welcome to the platform", + "page.login.title": "MapCTF: Login to platform", + "page.registration.title": "MapCTF: Register to platform", + "page.countdown.title": "MapCTF: Countdown to event", + "page.rules.title": "MapCTF: Rules of the game", + "page.gameboard.title": "MapCTF: Gameboard", + "index.eyebrow": "Global Capture The Flag", + "index.headline": "Conquer the world", + "index.copy": "Build your team, enter the map, and turn challenge solves into territory. MAPCTF keeps the experience simple: register, deploy, and take control before time runs out.", + "index.cta_enter": "Enter", + "index.note_label": "Mission brief:", + "index.note_body": "review the event status, assemble your team, and be ready to move when the map goes live.", + "page.login.type_team": "Team Login", + "page.login.type_admin": "Admin Login", + "page.login.msg_enabled": "Login to play Capture The Flag here.", + "page.login.msg_disabled": "Team login is currently disabled. Only admins can login at this time.", + "login.message_label": "Message", + "login.username_label": "Username", + "login.password_label": "Password", + "login.button": "Login", + "login.success_redirect_admin": "Login successful. Redirecting...", + "login.success_redirect": "Success. Redirecting...", + "ajax.request_failed": "Request failed. Please try again.", + "registration.msg_enabled": "Register to play Capture The Flag here. Once you have registered, simply login for future site visits.", + "registration.msg_disabled": "Team Registration will be open soon, stay tuned!", + "registration.type_open": "Open Registration", + "registration.type_token": "Registration with Token", + "registration.name_label": "Full Name", + "registration.email_label": "Email", + "registration.team_name_label": "Team Name", + "registration.token_label": "Registration Token", + "registration.emblem_title": "Choose an Emblem", + "registration.emblem_aria": "Emblem options", + "registration.button": "Sign Up", + "registration.try_again": "Try Again", + "countdown.status_in_progress": "Game In Progress", + "countdown.status_tba": "To Be Announced", + "countdown.msg_game_live": "The game is live. End time not set yet.", + "countdown.msg_start_not_set": "Start time not set yet.", + "countdown.status_countdown_end": "Countdown to game end", + "countdown.status_game_started": "Game already started", + "countdown.status_standby": "Game on Standby", + "countdown.unit_days": "Days", + "countdown.unit_hours": "Hours", + "countdown.unit_minutes": "Minutes", + "countdown.unit_seconds": "Seconds", + "countdown.js_game_ended": "The game has ended", + "countdown.js_game_started": "The game has already started", + "countdown.js_countdown_end": "Countdown to game end", + "countdown.js_standby": "Game on Standby", + "rules.heading": "Official CTF Rules", + "rules.intro": "Following actions are prohibited, unless explicitly told otherwise by event Admins.", + "rules.search_placeholder": "Type to search", + "rules.toc_title": "Table of Contents", + "rules.rule_label": "Rule", + "rules.title_cooperation": "Cooperation", + "rules.title_attacking": "Attacking Scoreboard", + "rules.title_sabotage": "Sabotage", + "rules.title_bruteforcing": "Bruteforcing", + "rules.title_dos": "Denial Of Service", + "gb.nav_label": "Navigation", + "gb.profile": "Profile", + "gb.admin": "🔐 Admin", + "gb.logout": "Logout", + "gb.scoreboard": "Scoreboard", + "gb.tab_you": "You", + "gb.tab_others": "Others", + "gb.tab_help": "Help", + "gb.tab_all": "All", + "gb.leaderboard": "Leaderboard", + "gb.activity": "Activity", + "gb.teams": "Teams", + "gb.filter": "Filter", + "gb.world_domination": "World Domination", + "gb.world_chat": "World Chat", + "gb.game_clock": "Game Clock", + "gb.your_team": "Your team", + "gb.no_team": "No Team", + "gb.rank": "Rank", + "gb.points": "Points", + "gb.all_teams": "All Teams", + "gb.category": "Category", + "gb.point_value": "Point Value", + "gb.team_prefix": "Team:", + "gb.domination_completed_zero": "0 / 0 Challenges Completed", + "gb.completion_state": "Completion State", + "gb.win_rate": "Win Rate", + "gb.lose_rate": "Lose Rate", + "gb.loading_chat": "Loading chat...", + "gb.type_to_chat": "Type to chat", + "gb.send": "Send", + "gb.clock_start": "[Start]", + "gb.clock_end": "[End]", + "gb.last_score": "Last score", + "gb.no_teams_yet": "No teams yet", + "gb.no_activity_yet": "No activity yet", + "gb.announcement": "Announcement", + "gb.gameboard_kicker": "Gameboard", + "gb.hint_locked": "Locked", + "gb.hint_unlocked": "Unlocked", + "gb.open_challenge": "Open challenge", + "gb.captured": "Captured", + "gb.open_status": "Open", + "gb.unavailable": "Unavailable", + "gb.score_axis": "Score", + "modal.capture_label": "capture_", + "modal.enter_flag": "Enter your flag", + "modal.hint_button": "Hint", + "modal.help_button": "Help", + "modal.submit": "Submit", + "modal.help_label": "help_", + "modal.help_request_lead": "You have requested help from another team. You will be able to capture", + "modal.help_request_trail": "in 10 minutes. You may close this window and continue playing CTF.", + "modal.hint_label": "hint_", + "modal.hint_copy": "Spend points to unlock extra challenge intel for this country.", + "modal.hint_empty": "No hint revealed yet.", + "modal.pts": "PTS", + "modal.category": "category", + "modal.flag_owner": "flag_owner", + "modal.completed_by": "completed_by >", + "modal.captured_label": "captured_", + "modal.scoreboard_label": "scoreboard_", + "modal.filter_label": "filter_", + "modal.rank_label": "rank_", + "modal.team_name_label": "team_name_", + "modal.base_pts_label": "base_pts_", + "modal.quiz_pts_label": "quiz_pts_", + "modal.flag_pts_label": "flag_pts_", + "modal.total_label": "total_", + "modal.last_score_label": "last_score", + "modal.team_members_label": "team_members", + "modal.choose_logo": "choose_logo", + "modal.save": "Save", + "profile.kicker": "Account", + "profile.title": "Profile", + "profile.team_label": "Team", + "profile.loading": "Loading...", + "profile.signed_in_as": "Signed in as", + "profile.points": "points", + "profile.rank": "rank", + "profile.last_score": "last score", + "profile.account": "account", + "profile.full_name": "Full name", + "profile.email": "Email", + "profile.role": "Role", + "profile.status": "Status", + "profile.change_password": "change_password", + "profile.current_password": "Current password", + "profile.new_password": "New password", + "profile.confirm_password": "Confirm password", + "profile.update": "Update", + "profile.role_admin": "Admin", + "profile.role_service": "Service", + "profile.role_player": "Player", + "profile.status_active": "Active", + "profile.status_inactive": "Inactive", + "profile.updated_msg": "Profile updated", + "profile.password_updated_msg": "Password updated", + "modal.help_team_lead": "Team", + "modal.help_team_middle": "would like to help you answer", + "modal.accept": "Accept", + "modal.deny": "Deny", + "modal.help_confirm_lead": "Are you sure you would like to help team", + "modal.yes": "Yes", + "modal.no": "No", + "admin.status_label": "status_", + "admin.status_ready": "ready", + "admin.on": "On", + "admin.off": "Off", + "admin.edit": "EDIT", + "admin.delete": "Delete", + "admin.save": "Save", + "admin.none": "None", + "admin.never": "never", + "admin.import_export": "Import / Export", + "admin.bulk_delete": "Bulk delete", + "admin.disabled_suffix": "(disabled)", + "admin.title.dashboard": "MapCTF Admin: Dashboard", + "admin.title.settings": "MapCTF Admin: Settings", + "admin.title.controls": "MapCTF Admin: Controls", + "admin.title.teams": "MapCTF Admin: Teams", + "admin.title.team_logos": "MapCTF Admin: Team Logos", + "admin.title.users": "MapCTF Admin: Users", + "admin.title.challenges": "MapCTF Admin: Challenges", + "admin.title.activity": "MapCTF Admin: Activity", + "admin.title.chat": "MapCTF Admin: Chat", + "admin.title.countries": "MapCTF Admin: Countries", + "admin.nav.dashboard": "Dashboard", + "admin.nav.settings": "Settings", + "admin.nav.controls": "Controls", + "admin.nav.users": "Users", + "admin.nav.teams": "Teams", + "admin.nav.team_logos": "Team logos", + "admin.nav.challenges": "Challenges", + "admin.nav.countries": "Countries", + "admin.nav.activity": "Activity", + "admin.nav.chat": "Chat", + "admin.nav.end_game": "End Game", + "admin.nav.pause_game": "Pause Game", + "admin.nav.game_admin": "Game Admin", + "admin.dashboard.header": "Operations dashboard", + "admin.dashboard.open_gameboard": "Open Gameboard", + "admin.dashboard.game_controls": "Game Controls", + "admin.dashboard.live_metrics": "Live game metrics", + "admin.dashboard.kpi_game_state": "Game state", + "admin.dashboard.kpi_teams_online": "Teams online", + "admin.dashboard.kpi_flags_hour": "Flags per hour", + "admin.dashboard.kpi_solve_coverage": "Solve coverage", + "admin.dashboard.kpi_platform_pulse": "Platform pulse", + "admin.dashboard.kicker_competition": "Competition", + "admin.dashboard.leaderboard_preview": "Leaderboard preview", + "admin.dashboard.view_board": "View board", + "admin.dashboard.kicker_progress": "Progress", + "admin.dashboard.capture_velocity": "Capture velocity", + "admin.dashboard.last_10m": "Last 10m", + "admin.dashboard.kicker_challenges": "Challenges", + "admin.dashboard.challenge_health": "Challenge health", + "admin.dashboard.manage": "Manage", + "admin.dashboard.kicker_platform": "Platform", + "admin.dashboard.infrastructure_status": "Infrastructure status", + "admin.dashboard.kicker_operations": "Operations", + "admin.dashboard.incident_queue": "Incident queue", + "admin.dashboard.kicker_timeline": "Timeline", + "admin.dashboard.recent_activity": "Recent game activity", + "admin.dashboard.kicker_control": "Control plane", + "admin.dashboard.quick_actions": "Quick actions", + "admin.dashboard.review_users": "Review users", + "admin.dashboard.manage_teams": "Manage teams", + "admin.dashboard.audit_activity": "Audit activity", + "admin.dashboard.moderate_chat": "Moderate chat", + "admin.users.page_header": "User Management", + "admin.users.add": "Add User", + "admin.users.search": "Search users...", + "admin.users.actions": "User Actions", + "admin.users.import": "Import Users", + "admin.users.export": "Export Users", + "admin.users.bulk_state": "Bulk user state", + "admin.users.enable_all": "Enable All Users", + "admin.users.disable_all": "Disable All Users", + "admin.users.delete_all": "Delete All Users", + "admin.users.set_password": "Set Password", + "admin.users.username": "Username", + "admin.users.name": "Name", + "admin.users.email": "Email", + "admin.users.team": "Team", + "admin.users.admin": "Admin", + "admin.users.service": "Service", + "admin.users.last_ip": "Last IP", + "admin.users.last_ua": "Last User Agent", + "admin.users.last_access": "Last Access", + "admin.users.no_found_title": "No Users Found", + "admin.users.no_found": "No users found.", + "admin.teams.page_header": "Team Management", + "admin.teams.add": "Add Team", + "admin.teams.search": "Search teams...", + "admin.teams.actions": "Team Actions", + "admin.teams.import": "Import teams", + "admin.teams.export": "Export teams", + "admin.teams.bulk_state": "Bulk team state", + "admin.teams.enable_all": "Enable All Teams", + "admin.teams.disable_all": "Disable All Teams", + "admin.teams.bulk_visibility": "Bulk team visibility", + "admin.teams.make_visible": "Make All Visible", + "admin.teams.make_invisible": "Make All Invisible", + "admin.teams.delete_all": "Delete All Teams", + "admin.teams.team_name": "Team Name", + "admin.teams.team_members": "Team Members", + "admin.teams.points": "Points", + "admin.teams.last_score": "Last Score", + "admin.teams.logo": "Logo", + "admin.teams.visible": "Visible", + "admin.teams.protected": "Protected", + "admin.teams.no_members": "No members assigned", + "admin.teams.random_auto": "Random (auto)", + "admin.teams.no_found_title": "Teams", + "admin.teams.no_found": "No teams found.", + "admin.logos.page_header": "Team logos", + "admin.logos.actions": "Logo actions", + "admin.logos.search": "Search all logos...", + "admin.logos.add": "Add Logo", + "admin.logos.import": "Import logos", + "admin.logos.export": "Export logos", + "admin.logos.bulk_state": "Bulk logo state", + "admin.logos.enable_all": "Enable All Logos", + "admin.logos.disable_all": "Disable All Logos", + "admin.logos.delete_all_custom": "Delete all custom logos", + "admin.logos.all": "Logos", + "admin.logos.custom": "Custom logos", + "admin.logos.catalog": "Catalog", + "admin.logos.no_custom": "No custom logos found", + "admin.logos.no_platform": "No platform logos found", + "admin.logos.unnamed": "Unnamed", + "admin.logos.edit_logo": "Edit logo", + "admin.logos.enabled": "Enabled", + "admin.logos.not_enabled": "Not Enabled", + "admin.logos.platform": "Platform", + "admin.logos.custom_type": "Custom", + "admin.logos.protected": "Protected", + "admin.logos.not_protected": "Not Protected", + "admin.logos.used": "Used", + "admin.logos.not_used": "Not Used", + "admin.challenges.page_header": "Challenge Management", + "admin.challenges.add": "Add Challenge", + "admin.challenges.actions": "Challenge Actions", + "admin.challenges.categories": "Challenge Categories", + "admin.challenges.add_category": "Add Category", + "admin.challenges.import_all": "Import All", + "admin.challenges.export_all": "Export All", + "admin.challenges.enable_all": "Enable All Challenges", + "admin.challenges.disable_all": "Disable All Challenges", + "admin.challenges.delete_all": "Delete All Challenges", + "admin.challenges.delete_all_categories": "Delete All Categories", + "admin.challenges.no_found": "No challenges found", + "admin.challenges.no_categories": "No categories found", + "admin.settings.page_header": "Settings", + "admin.settings.actions": "Settings Actions", + "admin.settings.import": "Import Settings", + "admin.settings.export": "Export Settings", + "admin.settings.reset_defaults": "Reset to Defaults", + "admin.settings.login": "Login Settings", + "admin.settings.registration": "Registration Settings", + "admin.settings.scoring": "Scoring Settings", + "admin.settings.game": "Game Settings", + "admin.settings.branding": "Branding Settings", + "admin.settings.localization": "Localization Settings", + "admin.settings.gameboard": "Gameboard Settings", + "admin.controls.page_header": "Game Controls", + "admin.controls.game_actions": "Game Actions", + "admin.controls.users_actions": "Users Actions", + "admin.controls.teams_actions": "Teams Actions", + "admin.controls.challenges_actions": "Challenges Actions", + "admin.controls.settings_actions": "Settings Actions", + "admin.controls.countries_actions": "Countries Actions", + "admin.controls.import_export_full": "Import / Export Full Game", + "admin.controls.import_export_teams": "Import / Export teams", + "admin.controls.import_export_logos": "Import / Export logos", + "admin.controls.bulk_challenge_state": "Bulk challenge state", + "admin.controls.management": "Management", + "admin.controls.reset": "Reset", + "admin.game.import_full": "Import Full Game", + "admin.game.export_full": "Export Full Game", + "admin.countries.open_page": "Open Countries Page", + "admin.countries.delete_all": "Delete All Countries", + "admin.countries.page_header": "Country Management", + "admin.countries.world_map": "World Map (hover a country to jump to its row)", + "admin.countries.th_country": "Country", + "admin.countries.th_code": "Country Code", + "admin.countries.th_assigned": "Assigned", + "admin.countries.th_challenge": "Challenge", + "admin.countries.th_svg": "SVG", + "admin.countries.th_active": "Active", + "admin.activity.page_header": "Activity", + "admin.activity.log": "Activity Log", + "admin.activity.add_entry": "Add Entry", + "admin.chat.page_header": "World Chat", + "admin.chat.entries": "Chat Entries", + "admin.challenges.title": "Title", + "admin.challenges.description": "Description", + "admin.challenges.resource_url": "Resource URL", + "admin.challenges.category": "Category", + "admin.challenges.country": "Country", + "admin.challenges.points": "Points", + "admin.challenges.bonus": "Bonus", + "admin.challenges.bonus_decay": "Bonus Decay", + "admin.challenges.hint_penalty": "Hint Penalty", + "admin.challenges.help_penalty": "Help Penalty", + "admin.challenges.flag": "Flag", + "admin.challenges.hint": "Hint", + "admin.challenges.active": "Active", + "admin.challenges.inactive": "Inactive", + "admin.challenges.act_action": "action", + "admin.challenges.act_message": "message", + "admin.challenges.act_args": "args", + "admin.challenges.no_activity": "No activity for this challenge yet.", + "admin.challenges.system": "System", + "admin.teams.export_full": "Export teams + logos", + "modal.admin_kicker": "admin_", + "modal.status_kicker": "status_", + "admin.modal.cancel": "Cancel", + "admin.modal.no": "No", + "admin.modal.yes": "Yes", + "admin.modal.create_user": "Create User", + "admin.modal.create_team": "Create Team", + "admin.modal.create_category": "Create Category", + "admin.modal.create_challenge": "Create Challenge", + "admin.modal.create_entry": "Create Entry", + "admin.modal.create_logo": "Create Logo", + "admin.modal.save_changes": "Save Changes", + "admin.modal.set_password": "Set Password", + "admin.modal.new_password": "New Password", + "admin.modal.set_password_note": "Set a new password for", + "admin.modal.set_password_user": "this user", + "admin.modal.unsaved": "Unsaved", + "admin.modal.changes": "Changes", + "admin.modal.unsaved_msg": "You have unsaved changes. Leave this page and discard them?", + "admin.modal.delete_challenge": "Delete Challenge", + "admin.modal.delete_team": "Delete Team", + "admin.modal.delete_category": "Delete Category", + "admin.modal.delete_entry": "Delete Entry", + "admin.modal.delete_all_challenges": "Delete All Challenges", + "admin.modal.delete_all_teams": "Delete All Teams", + "admin.modal.delete_all_users": "Delete All Users", + "admin.modal.delete_all_categories": "Delete All Categories", + "admin.modal.delete_all_countries": "Delete All Countries", + "admin.modal.delete_all_logos": "Delete All Logos", + "admin.modal.disable_all_users": "Disable All Users", + "admin.modal.logout": "Logout", + "admin.modal.reset_defaults": "Reset to Defaults", + "admin.modal.edit_logo": "Edit Logo", + "admin.modal.select_country": "Select country", + "admin.modal.confirm_delete_challenge": "Are you sure you want to delete this challenge?", + "admin.modal.confirm_delete_team": "Are you sure you want to delete this team?", + "admin.modal.confirm_delete_category": "Are you sure you want to delete this category?", + "admin.modal.confirm_delete_activity": "Are you sure you want to delete this activity entry?", + "admin.modal.confirm_delete_all_challenges": "Are you sure you want to delete all challenges? This cannot be undone.", + "admin.modal.confirm_delete_all_teams": "Are you sure you want to delete all teams? This cannot be undone.", + "admin.modal.confirm_delete_all_users": "Are you sure you want to delete all users? This cannot be undone.", + "admin.modal.confirm_delete_all_categories": "Are you sure you want to delete all categories? Challenges must be deleted first.", + "admin.modal.confirm_delete_all_countries": "Are you sure you want to delete all countries? This cannot be undone and will clear country assignments from challenges.", + "admin.modal.confirm_delete_all_logos": "Are you sure you want to delete all custom logos? This cannot be undone.", + "admin.modal.confirm_disable_all_users": "Are you sure you want to disable all users? This cannot be undone.", + "admin.activity.kicker": "Admin Activity Log", + "admin.activity.subject": "Subject", + "admin.activity.action": "Action", + "admin.activity.visible": "Visible", + "admin.activity.message": "Message", + "admin.activity.example": "Example", + "admin.activity.visible_opt": "Visible", + "admin.activity.hidden_opt": "Hidden", + "admin.activity.action_announcement": "Announcement", + "admin.activity.action_completed": "Completed", + "admin.activity.action_enabled": "Enabled", + "admin.activity.action_disabled": "Disabled", + "admin.activity.subject_ph": "Blue Team or Admin", + "admin.activity.message_ph": "Captured Spain", + "admin.logos.logo_name": "Logo Name", + "admin.logos.file": "File", + "admin.logos.upload_file": "Upload Logo File", + "admin.logos.accepted_formats": "Accepted formats", + "admin.logos.upload_note": "Upload an SVG, GIF, PNG, or JPG/JPEG file.", + "admin.logos.max_size": "Max file size: 512 KB", + "admin.logos.recommended_size": "Recommended size: 64 x 48 px", + "admin.logos.no_file": "No file selected", + "admin.logos.slug_note": "Use the file slug to control the stored logo name.", + "admin.category.name": "Name", + "admin.category.description": "Description", + "admin.category.logo": "Logo", + "admin.modal.delete_all_logos_lead": "Delete all", + "admin.modal.delete_all_logos_trail": "logos? Platform badges stay in the catalog. This cannot be undone.", + "admin.modal.reset_settings_title": "Reset Settings", + "admin.modal.confirm_reset_settings": "Are you sure you want to reset all settings to default values?", + "admin.modal.confirm_logout": "Are you sure you want to logout?", + "admin.js.modal_unavailable": "Modal system unavailable", + "admin.js.request_failed": "Request failed", + "admin.js.unable_challenge_row": "Unable to locate challenge row", + "admin.js.title_flag_required": "Title and flag are required", + "admin.js.new_password_required": "New password is required", + "admin.js.logo_name_required": "Logo name is required", + "admin.js.failed_update_user": "Failed to update user", + "admin.js.failed_update_team": "Failed to update team", + "admin.js.failed_update_password": "Failed to update password", + "admin.js.failed_update_logo": "Failed to update logo", + "admin.js.failed_update_challenge": "Failed to update challenge", + "admin.js.failed_update_category": "Failed to update category", + "admin.js.failed_import_challenges": "Failed to import challenges", + "admin.js.failed_delete_challenge": "Failed to delete challenge", + "admin.js.failed_delete_activity_entry": "Failed to delete activity entry", + "admin.js.failed_create_user": "Failed to create user", + "admin.js.failed_create_team": "Failed to create team", + "admin.js.failed_create_logo": "Failed to create logo", + "admin.js.failed_create_challenge": "Failed to create challenge", + "admin.js.failed_create_category": "Failed to create category", + "admin.js.failed_create_activity_entry": "Failed to create activity entry", + "admin.js.category_required": "Category is required", + "admin.js.users_imported": "Users imported", + "admin.js.username_password_required": "Username and password are required", + "admin.js.user_updated": "User updated", + "admin.js.user_created": "User created", + "admin.js.upload_logo_first": "Upload a logo file before creating a custom logo", + "admin.js.updated": "Updated", + "admin.js.unable_challenge_form": "Unable to locate challenge form", + "admin.js.unable_activity_entry": "Unable to locate activity entry", + "admin.js.teams_imported": "Teams imported", + "admin.js.team_updated": "Team updated", + "admin.js.team_name_required": "Team name is required", + "admin.js.team_deleted": "Team deleted", + "admin.js.team_created": "Team created", + "admin.js.team_full_required": "Team name, logo, active, visible and protected values are required", + "admin.js.subject_message_required": "Subject or message is required", + "admin.js.settings_reset_defaults": "Settings reset to defaults", + "admin.js.settings_imported": "Settings imported", + "admin.js.settings_import_missing": "Settings import file input not found", + "admin.js.reset_confirm": "Reset all settings to defaults?", + "admin.js.platform_logo_no_rename": "Platform logo name cannot be changed", + "admin.js.password_modal_unavailable": "Password modal is unavailable", + "admin.js.activity_created": "Activity entry created", + "admin.js.activity_deleted": "Activity entry deleted", + "admin.js.admin_service_active_required": "Admin, service and active values are required", + "admin.js.all_categories_deleted": "All categories deleted", + "admin.js.all_challenges_deleted": "All challenges deleted", + "admin.js.all_challenges_disabled": "All challenges disabled", + "admin.js.all_challenges_enabled": "All challenges enabled", + "admin.js.all_countries_deleted": "All countries deleted", + "admin.js.all_logos_deleted": "All logos deleted", + "admin.js.all_logos_disabled": "All logos disabled", + "admin.js.all_logos_enabled": "All logos enabled", + "admin.js.all_teams_deleted": "All teams deleted", + "admin.js.all_teams_disabled": "All teams disabled", + "admin.js.all_teams_enabled": "All teams enabled", + "admin.js.all_teams_invisible": "All teams set invisible", + "admin.js.all_teams_visible": "All teams set visible", + "admin.js.all_users_deleted": "All users deleted", + "admin.js.all_users_disabled": "All users disabled", + "admin.js.all_users_enabled": "All users enabled", + "admin.js.category_created": "Category created", + "admin.js.category_deleted": "Category deleted", + "admin.js.category_name_required": "Category name is required", + "admin.js.category_updated": "Category updated", + "admin.js.challenge_created": "Challenge created", + "admin.js.challenge_deleted": "Challenge deleted", + "admin.js.challenge_updated": "Challenge updated", + "admin.js.challenges_imported": "Challenges imported", + "admin.js.delete_all_categories_confirm": "Delete all categories? Challenges must be deleted first.", + "admin.js.delete_all_challenges_confirm": "Delete all challenges? This cannot be undone.", + "admin.js.delete_all_teams_confirm": "Delete all teams? This cannot be undone.", + "admin.js.delete_all_users_confirm": "Delete all users? This cannot be undone.", + "admin.js.delete_category": "Delete category", + "admin.js.delete_challenge": "Delete challenge", + "admin.js.delete_team": "Delete team", + "admin.js.delete_activity_confirm": "Delete this activity entry?", + "admin.js.disable_all_users_confirm": "Disable all users?", + "admin.js.edit_category": "Edit Category", + "admin.js.email_invalid": "Email is invalid", + "admin.js.enabled_protected_required": "Enabled and protected values are required", + "admin.js.full_game_imported": "Full game imported", + "admin.js.import_file_missing": "Import file input not found", + "admin.js.logo_created": "Logo created", + "admin.js.logo_updated": "Logo updated", + "admin.js.logos_imported": "Logos imported", + "admin.js.max_size_prefix": "Max file size: ", + "admin.js.max_upload_prefix": "Maximum logo upload size is ", + "admin.js.recommended_size_prefix": "Recommended size: ", + "admin.js.selected_file_prefix": "Selected file: ", + "admin.js.upload_formats_aria": "Accepted logo upload formats", + "admin.js.upload_size_aria": "Logo upload size limits", + "login.success_msg": "Login successful", + "login.logout_msg": "Logout successful", + "auth.error_parse_body": "error parsing POST body", + "auth.error_renew_session": "error renewing session", + "auth.error_destroy_session": "error destroying session", + "auth.uuid_required": "UUID is required", + "auth.user_pass_required": "username and password are required", + "auth.invalid_credentials": "invalid credentials", + "auth.login_unavailable": "login is unavailable", + "auth.login_disabled": "login is disabled", + "reg.invalid_body": "invalid request body", + "reg.failed_type_setting": "failed to get registration type setting", + "reg.token_required": "registration token is required", + "reg.failed_token_setting": "failed to get registration token setting", + "reg.invalid_token": "invalid registration token", + "reg.email_required": "email is required", + "reg.team_required": "team is required", + "reg.failed_register_team": "failed to register team", + "reg.failed_register_user": "failed to register user", + "reg.success": "Registration successful", + "chat.failed_maxlen_setting": "failed to get gameboard chat max length\t setting", + "chat.not_authenticated": "user not authenticated", + "chat.failed_get_user": "failed to get user for chat message", + "chat.failed_create": "failed to create chat message", + "chat.shown": "Shown", + "chat.hidden": "Hidden", + "score.unavailable": "scoring is unavailable", + "score.country_flag_required": "country_code and flag are required", + "score.failed_setting": "failed to retrieve scoring setting", + "score.disabled": "scoring is disabled", + "score.failed_retrieve_user": "failed to retrieve user", + "score.no_team": "user is not assigned to a team", + "score.failed_retrieve_team": "failed to retrieve team", + "score.no_active_challenge": "no active challenge found for country", + "score.failed_retrieve_challenge": "failed to retrieve challenge", + "score.already_completed": "Your team already completed this challenge", + "score.failed_evaluate": "failed to evaluate current score", + "score.failed_record": "failed to record scoring attempt", + "score.incorrect_flag": "Incorrect flag", + "score.failed_retrieve_category": "failed to retrieve challenge category", + "score.failed_score": "failed to score challenge", + "score.completed": "Challenge completed", + "score.incorrect_submission": "Incorrect submission", + "hint.requests_unavailable": "hint requests are unavailable", + "hint.country_required": "country_code is required", + "hint.failed_setting": "failed to retrieve hint setting", + "hint.requests_disabled": "hint requests are disabled", + "hint.not_available": "no hint is available for this challenge", + "hint.already_unlocked": "Hint already unlocked", + "hint.failed_evaluate": "failed to evaluate hint state", + "hint.not_enough_points": "Your team does not have enough points for this hint", + "hint.failed_unlock": "failed to unlock hint", + "hint.unlocked": "Hint unlocked", + "hint.retrieval_unavailable": "hint retrieval is unavailable", + "hint.requested": "Hint requested", + "hint.not_unlocked": "hint not unlocked", + "profile.auth_required": "authentication required", + "profile.unavailable": "profile is unavailable", + "profile.not_updated": "profile could not be updated", + "profile.current_required": "current password is required", + "profile.current_incorrect": "current password is incorrect", + "profile.new_required": "new password is required", + "profile.new_min_length": "new password must be at least 8 characters", + "profile.new_must_differ": "new password must be different", + "profile.new_mismatch": "new passwords do not match", + "profile.password_not_updated": "password could not be updated", + "profile.email_invalid": "email is invalid", + "admin.msg.failed_load_settings": "Failed to load settings", + "admin.msg.failed_load_users": "Failed to load users", + "admin.msg.failed_load_teams_logos": "Failed to load teams/logos", + "admin.msg.failed_load_challenges": "Failed to load challenges", + "admin.msg.failed_load_teams": "Failed to load teams", + "admin.msg.failed_load_logos": "Failed to load logos", + "admin.msg.failed_load_custom_logos": "Failed to load custom logos", + "admin.msg.failed_export_json": "Failed to generate export JSON", + "admin.msg.failed_enable_users": "Failed to enable all users", + "admin.msg.failed_disable_users": "Failed to disable all users", + "admin.msg.failed_delete_users": "Failed to delete all users", + "admin.msg.failed_enable_teams": "Failed to enable all teams", + "admin.msg.failed_disable_teams": "Failed to disable all teams", + "admin.msg.failed_visible_teams": "Failed to make all teams visible", + "admin.msg.failed_invisible_teams": "Failed to make all teams invisible", + "admin.msg.failed_enable_logos": "Failed to enable all logos", + "admin.msg.failed_disable_logos": "Failed to disable all logos", + "admin.msg.failed_delete_logos": "Failed to delete custom logos", + "admin.msg.enabled_users": "Enabled %d user(s)", + "admin.msg.disabled_users": "Disabled %d user(s). Current user not modified.", + "admin.msg.deleted_users": "Deleted %d user(s). Current user preserved.", + "admin.msg.enabled_teams": "Enabled %d team(s)", + "admin.msg.disabled_teams": "Disabled %d team(s)", + "admin.msg.visible_teams": "Set visible for %d team(s)", + "admin.msg.invisible_teams": "Set invisible for %d team(s)", + "admin.msg.enabled_logos": "Enabled %d logo(s)", + "admin.msg.disabled_logos": "Disabled %d logo(s)", + "admin.msg.deleted_logos": "Deleted %d custom logo(s); platform logos unchanged", + "admin.msg.activity_deleted": "Activity entry deleted", + "admin.msg.custom_activity": "Custom activity", + "admin.msg.invalid_logo_id": "Invalid logo id", + "admin.msg.invalid_team_id": "Invalid team_id", + "admin.msg.invalid_user_id": "Invalid user id", + "admin.msg.not_authenticated": "Not authenticated", + "admin.msg.other_uuid": "Other UUID", + "feed.error_activity": "error retrieving activity logs", + "feed.error_challenges": "error retrieving challenges", + "feed.error_chat": "error retrieving chat entries", + "feed.error_country": "error retrieving country data", + "feed.error_game_clock": "error retrieving game clock data", + "feed.error_team_members": "error retrieving team members", + "feed.error_team_settings": "error retrieving team settings", + "feed.error_teams": "error retrieving teams", + "feed.error_domination": "error retrieving world domination data", + "feed.invalid_uuid": "invalid UUID", + "feed.invalid_json": "invalid JSON payload", + "feed.not_authenticated": "user not authenticated", + "feed.content_type": "Content-Type must be application/json", + "admin.msg.logos_deleted_sync_fail": "Custom logos deleted but failed to sync logo usage", + "admin.msg.ajax_only": "AJAX requests only", + "admin.msg.activity_logging_unavailable": "Activity logging is unavailable", + "admin.msg.activity_entry_created": "Activity entry created", + "admin.msg.admin_service_active_required": "Admin, service and active values are required", + "admin.msg.category_created": "Category created", + "admin.msg.category_deleted": "Category deleted", + "admin.msg.category_updated": "Category updated", + "admin.msg.category_assigned_not_deleted": "Category is assigned to challenges and was not deleted", + "admin.msg.category_name_required": "Category name is required", + "admin.msg.category_not_found": "Category not found", + "admin.msg.delete_challenges_before_categories": "Delete all challenges before deleting categories", + "admin.msg.challenge_created": "Challenge created", + "admin.msg.challenge_deleted": "Challenge deleted", + "admin.msg.challenge_updated": "Challenge updated", + "admin.msg.challenge_not_found": "Challenge not found", + "admin.msg.chat_message_deleted": "Chat message deleted", + "admin.msg.chat_message_hidden": "Chat message hidden", + "admin.msg.chat_message_unhidden": "Chat message unhidden", + "admin.msg.chat_message_not_found": "Chat message not found", + "admin.msg.chat_visibility_updated": "Chat visibility updated", + "admin.msg.content_type_form": "Content-Type must be application/json or multipart/form-data", + "admin.msg.countries_not_initialized": "Countries manager is not initialized", + "admin.msg.countries_or_challenges_not_initialized": "Countries or challenges manager is not initialized", + "admin.msg.country_active_available": "Country must be active and available", + "admin.msg.country_status_updated": "Country status updated", + "admin.msg.deleted_team_sync_fail": "Deleted teams but failed to sync logo usage", + "admin.msg.team_deleted_sync_fail": "Team deleted but failed to sync logo usage", + "admin.msg.team_updated_sync_fail": "Team updated but failed to sync logo usage", + "admin.msg.import_sync_fail": "Import completed but failed to sync logo usage", + "admin.msg.failed_importing_challenges": "Failed importing challenges", + "admin.msg.failed_importing_logos": "Failed importing logos", + "admin.msg.failed_importing_teams": "Failed importing teams", + "admin.msg.failed_importing_users": "Failed importing users", + "admin.msg.failed_resetting_defaults": "Failed resetting settings to defaults", + "admin.msg.failed_assign_country": "Failed to assign country to challenge", + "admin.msg.failed_build_activity": "Failed to build activity entry", + "admin.msg.failed_clear_country_refs": "Failed to clear challenge country references", + "admin.msg.failed_create_activity": "Failed to create activity entry", + "admin.msg.failed_create_category": "Failed to create category", + "admin.msg.failed_create_challenge": "Failed to create challenge", + "admin.msg.failed_create_logo": "Failed to create logo", + "admin.msg.failed_create_user": "Failed to create user", + "admin.msg.failed_delete_activity": "Failed to delete activity entry", + "admin.msg.failed_delete_all_categories": "Failed to delete all categories", + "admin.msg.failed_delete_all_challenges": "Failed to delete all challenges", + "admin.msg.failed_delete_all_countries": "Failed to delete all countries", + "admin.msg.failed_delete_all_teams": "Failed to delete all teams", + "admin.msg.failed_delete_category": "Failed to delete category", + "admin.msg.failed_delete_challenge": "Failed to delete challenge", + "admin.msg.failed_delete_chat": "Failed to delete chat message", + "admin.msg.failed_delete_team": "Failed to delete team", + "admin.msg.failed_disable_all_challenges": "Failed to disable all challenges", + "admin.msg.failed_enable_all_challenges": "Failed to enable all challenges", + "admin.msg.failed_import_challenges": "Failed to import challenges", + "admin.msg.failed_import_logos": "Failed to import logos", + "admin.msg.failed_import_teams": "Failed to import teams", + "admin.msg.failed_import_users": "Failed to import users", + "admin.msg.failed_load_logo": "Failed to load logo", + "admin.msg.failed_load_team": "Failed to load team", + "admin.msg.failed_read_logo_file": "Failed to read uploaded logo file", + "admin.msg.failed_release_country_refs": "Failed to release challenge countries", + "admin.msg.failed_release_country": "Failed to release challenge country", + "admin.msg.failed_resolve_random_logo": "Failed to resolve random logo", + "admin.msg.failed_retrieve_category": "Failed to retrieve challenge category", + "admin.msg.failed_store_logo_file": "Failed to store custom logo file", + "admin.msg.failed_update_category": "Failed to update category", + "admin.msg.failed_update_country_assignment": "Failed to update challenge country assignment", + "admin.msg.failed_update_challenge": "Failed to update challenge", + "admin.msg.failed_update_chat_visibility": "Failed to update chat visibility", + "admin.msg.failed_update_country_status": "Failed to update country status", + "admin.msg.failed_update_custom_logo": "Failed to update custom_logo", + "admin.msg.failed_update_custom_org": "Failed to update custom_org", + "admin.msg.failed_update_game_end": "Failed to update game_end_time", + "admin.msg.failed_update_game_start": "Failed to update game_start_time", + "admin.msg.failed_update_language": "Failed to update language", + "admin.msg.failed_update_logo": "Failed to update logo", + "admin.msg.failed_update_password": "Failed to update password", + "admin.msg.failed_update_reg_token": "Failed to update registration_token", + "admin.msg.failed_update_team": "Failed to update team", + "admin.msg.failed_update_user": "Failed to update user", + "admin.msg.failed_validate_team_name": "Failed to validate team name", + "admin.msg.failed_validate_team": "Failed to validate team", + "admin.msg.failed_verify_category_usage": "Failed to verify category usage", + "admin.msg.full_import_complete": "Full game import complete: ", + "admin.msg.import_complete": "Import complete: ", + "admin.msg.invalid_json_payload": "Invalid JSON payload", + "admin.msg.invalid_svg": "Invalid SVG file", + "admin.msg.invalid_active": "Invalid active value", + "admin.msg.invalid_activity_id": "Invalid activity entry ID", + "admin.msg.invalid_admin": "Invalid admin value", + "admin.msg.invalid_bonus": "Invalid bonus value", + "admin.msg.invalid_bonus_decay": "Invalid bonus_decay value", + "admin.msg.invalid_category_id": "Invalid category id", + "admin.msg.invalid_challenge_url": "Invalid challenge URL", + "admin.msg.invalid_challenge_id": "Invalid challenge id", + "admin.msg.invalid_chat_id": "Invalid chat id", + "admin.msg.invalid_country_id": "Invalid country ID", + "admin.msg.invalid_country_code": "Invalid country code", + "admin.msg.invalid_enabled": "Invalid enabled value", + "admin.msg.invalid_form_payload": "Invalid form payload", + "admin.msg.invalid_full_game_payload": "Invalid full-game import payload", + "admin.msg.invalid_game_end_format": "Invalid game_end_time format", + "admin.msg.invalid_game_start_format": "Invalid game_start_time format", + "admin.msg.invalid_help_penalty": "Invalid help_penalty value", + "admin.msg.invalid_hidden": "Invalid hidden value", + "admin.msg.invalid_hint_penalty": "Invalid hint_penalty value", + "admin.msg.invalid_import_payload": "Invalid import payload", + "admin.msg.invalid_logo_slug": "Invalid logo slug", + "admin.msg.invalid_multipart": "Invalid multipart form payload", + "admin.msg.invalid_points": "Invalid points value", + "admin.msg.invalid_protected": "Invalid protected value", + "admin.msg.invalid_raster_logo": "Invalid raster logo file", + "admin.msg.invalid_service": "Invalid service value", + "admin.msg.invalid_settings_payload": "Invalid settings import payload", + "admin.msg.invalid_uploaded_logo_type": "Invalid uploaded logo file type", + "admin.msg.invalid_uploaded_logo": "Invalid uploaded logo file", + "admin.msg.invalid_visible": "Invalid visible value", + "admin.msg.logo_already_exists": "Logo already exists", + "admin.msg.logo_created": "Logo created", + "admin.msg.logo_file_already_exists": "Logo file already exists", + "admin.msg.logo_file_required": "Logo file is required", + "admin.msg.logo_is_required": "Logo is required", + "admin.msg.logo_not_found": "Logo not found", + "admin.msg.logo_symbol_required": "Logo symbol is required", + "admin.msg.logo_updated": "Logo updated", + "admin.msg.missing_country_id": "Missing country ID", + "admin.msg.missing_setting_name": "Missing setting_name", + "admin.msg.no_challenges_in_payload": "No challenges found in import payload", + "admin.msg.no_logos_in_payload": "No logos found in import payload", + "admin.msg.no_teams_in_payload": "No teams found in import payload", + "admin.msg.no_teams_or_logos_in_payload": "No teams or logos found in import payload", + "admin.msg.no_users_in_payload": "No users found in import payload", + "admin.msg.platform_logo_no_rename": "Platform logo name cannot be changed", + "admin.msg.select_category": "Please select a category", + "admin.msg.select_valid_category": "Please select a valid category", + "admin.msg.random_not_valid_logo": "Random is not a valid logo for logo creation", + "admin.msg.settings_reset_defaults": "Settings reset to defaults", + "admin.msg.team_created": "Team created", + "admin.msg.team_deleted": "Team deleted", + "admin.msg.team_name_already_exists": "Team name already exists", + "admin.msg.team_name_required": "Team name is required", + "admin.msg.team_not_found": "Team not found", + "admin.msg.team_updated": "Team updated", + "admin.msg.unsupported_setting": "Unsupported setting", + "admin.msg.uploaded_logo_too_large": "Uploaded logo file is too large", + "admin.msg.user_created": "User created", + "admin.msg.user_created_active_fail": "User created but failed to set active flag", + "admin.msg.user_not_found": "User not found", + "admin.msg.user_updated": "User updated", + "admin.msg.valid_category_id_required": "Valid category_id is required", + "admin.msg.failed_update_setting": "Failed to update %s", + "admin.msg.invalid_boolean_for": "Invalid boolean for %s", + "admin.msg.invalid_integer_for": "Invalid integer for %s", + "admin.msg.updated_setting": "Updated %s", + "admin.msg.deleted_team_count": "Deleted %d team(s)", + "admin.msg.deleted_challenge_count": "Deleted %d challenge(s)", + "admin.msg.enabled_challenge_count": "Enabled %d challenge(s)", + "admin.msg.disabled_challenge_count": "Disabled %d challenge(s)", + "admin.msg.deleted_category_count": "Deleted %d categor(ies)", + "admin.msg.deleted_country_count": "Deleted %d country(ies)", + "admin.msg.teams_created": "teams created %d", + "admin.msg.teams_updated": "teams updated %d", + "admin.msg.users_created": "users created %d", + "admin.msg.users_updated": "users updated %d", + "admin.msg.logos_skipped": "logos skipped %d", + "admin.msg.teams_skipped": "teams skipped %d", + "admin.msg.logo_name_required": "Logo name is required", + "admin.msg.subject_message_required": "Subject or message is required", + "admin.msg.title_flag_required": "Title and flag are required", + "admin.msg.user_pass_required": "Username and password are required" +} diff --git a/backend/pkg/i18n/locales/es.json b/backend/pkg/i18n/locales/es.json new file mode 100644 index 0000000..6588a62 --- /dev/null +++ b/backend/pkg/i18n/locales/es.json @@ -0,0 +1,838 @@ +{ + "nav.play_ctf": "Jugar CTF", + "nav.rules": "Reglas", + "nav.registration": "Registro", + "nav.gameboard": "Tablero", + "nav.login": "Acceso", + "page.index.title": "MapCTF: Bienvenido a la plataforma", + "page.login.title": "MapCTF: Acceso a la plataforma", + "page.registration.title": "MapCTF: Registro en la plataforma", + "page.countdown.title": "MapCTF: Cuenta atrás del evento", + "page.rules.title": "MapCTF: Reglas del juego", + "page.gameboard.title": "MapCTF: Tablero", + "index.eyebrow": "Capture The Flag Global", + "index.headline": "Conquista el mundo", + "index.copy": "Forma tu equipo, entra en el mapa y convierte los retos resueltos en territorio. MAPCTF mantiene la experiencia simple: regístrate, despliega y toma el control antes de que se agote el tiempo.", + "index.cta_enter": "Entrar", + "index.note_label": "Resumen de la misión:", + "index.note_body": "revisa el estado del evento, forma tu equipo y prepárate para actuar cuando el mapa se active.", + "page.login.type_team": "Acceso de equipo", + "page.login.type_admin": "Acceso de administrador", + "page.login.msg_enabled": "Accede para jugar Capture The Flag aquí.", + "page.login.msg_disabled": "El acceso de equipos está deshabilitado. Solo los administradores pueden acceder en este momento.", + "login.message_label": "Mensaje", + "login.username_label": "Usuario", + "login.password_label": "Contraseña", + "login.button": "Acceder", + "login.success_redirect_admin": "Acceso correcto. Redirigiendo...", + "login.success_redirect": "Correcto. Redirigiendo...", + "ajax.request_failed": "La solicitud falló. Inténtalo de nuevo.", + "registration.msg_enabled": "Regístrate para jugar Capture The Flag aquí. Una vez registrado, simplemente accede para futuras visitas.", + "registration.msg_disabled": "¡El registro de equipos abrirá pronto, estad atentos!", + "registration.type_open": "Registro abierto", + "registration.type_token": "Registro con token", + "registration.name_label": "Nombre completo", + "registration.email_label": "Correo", + "registration.team_name_label": "Nombre del equipo", + "registration.token_label": "Token de registro", + "registration.emblem_title": "Elige un emblema", + "registration.emblem_aria": "Opciones de emblema", + "registration.button": "Registrarse", + "registration.try_again": "Reintentar", + "countdown.status_in_progress": "Partida en curso", + "countdown.status_tba": "Por anunciar", + "countdown.msg_game_live": "La partida está en marcha. Aún no se ha fijado la hora de fin.", + "countdown.msg_start_not_set": "Aún no se ha fijado la hora de inicio.", + "countdown.status_countdown_end": "Cuenta atrás para el fin", + "countdown.status_game_started": "La partida ya ha comenzado", + "countdown.status_standby": "Partida en espera", + "countdown.unit_days": "Días", + "countdown.unit_hours": "Horas", + "countdown.unit_minutes": "Minutos", + "countdown.unit_seconds": "Segundos", + "countdown.js_game_ended": "La partida ha terminado", + "countdown.js_game_started": "La partida ya ha comenzado", + "countdown.js_countdown_end": "Cuenta atrás para el fin", + "countdown.js_standby": "Partida en espera", + "rules.heading": "Reglas oficiales del CTF", + "rules.intro": "Las siguientes acciones están prohibidas, salvo que los administradores del evento indiquen lo contrario.", + "rules.search_placeholder": "Escribe para buscar", + "rules.toc_title": "Índice", + "rules.rule_label": "Regla", + "rules.title_cooperation": "Cooperación", + "rules.title_attacking": "Atacar el marcador", + "rules.title_sabotage": "Sabotaje", + "rules.title_bruteforcing": "Fuerza bruta", + "rules.title_dos": "Denegación de servicio", + "gb.nav_label": "Navegación", + "gb.profile": "Perfil", + "gb.admin": "🔐 Admin", + "gb.logout": "Cerrar sesión", + "gb.scoreboard": "Marcador", + "gb.tab_you": "Tú", + "gb.tab_others": "Otros", + "gb.tab_help": "Ayuda", + "gb.tab_all": "Todos", + "gb.leaderboard": "Clasificación", + "gb.activity": "Actividad", + "gb.teams": "Equipos", + "gb.filter": "Filtro", + "gb.world_domination": "Dominación mundial", + "gb.world_chat": "Chat mundial", + "gb.game_clock": "Reloj de juego", + "gb.your_team": "Tu equipo", + "gb.no_team": "Sin equipo", + "gb.rank": "Posición", + "gb.points": "Puntos", + "gb.all_teams": "Todos los equipos", + "gb.category": "Categoría", + "gb.point_value": "Valor en puntos", + "gb.team_prefix": "Equipo:", + "gb.domination_completed_zero": "0 / 0 Retos completados", + "gb.completion_state": "Estado de finalización", + "gb.win_rate": "Tasa de victorias", + "gb.lose_rate": "Tasa de derrotas", + "gb.loading_chat": "Cargando chat...", + "gb.type_to_chat": "Escribe para chatear", + "gb.send": "Enviar", + "gb.clock_start": "[Inicio]", + "gb.clock_end": "[Fin]", + "gb.last_score": "Última puntuación", + "gb.no_teams_yet": "Aún no hay equipos", + "gb.no_activity_yet": "Aún no hay actividad", + "gb.announcement": "Anuncio", + "gb.gameboard_kicker": "Tablero", + "gb.hint_locked": "Bloqueado", + "gb.hint_unlocked": "Desbloqueado", + "gb.open_challenge": "Abrir reto", + "gb.captured": "Capturado", + "gb.open_status": "Abierto", + "gb.unavailable": "No disponible", + "gb.score_axis": "Puntuación", + "modal.capture_label": "capturar_", + "modal.enter_flag": "Introduce tu flag", + "modal.hint_button": "Pista", + "modal.help_button": "Ayuda", + "modal.submit": "Enviar", + "modal.help_label": "ayuda_", + "modal.help_request_lead": "Has solicitado ayuda a otro equipo. Podrás capturar", + "modal.help_request_trail": "en 10 minutos. Puedes cerrar esta ventana y seguir jugando al CTF.", + "modal.hint_label": "pista_", + "modal.hint_copy": "Gasta puntos para desbloquear información extra del reto de este país.", + "modal.hint_empty": "Todavía no se ha revelado ninguna pista.", + "modal.pts": "PTS", + "modal.category": "categoría", + "modal.flag_owner": "propietario_flag", + "modal.completed_by": "completado_por >", + "modal.captured_label": "capturado_", + "modal.scoreboard_label": "marcador_", + "modal.filter_label": "filtro_", + "modal.rank_label": "pos._", + "modal.team_name_label": "equipo_", + "modal.base_pts_label": "pts_base_", + "modal.quiz_pts_label": "pts_quiz_", + "modal.flag_pts_label": "pts_flag_", + "modal.total_label": "total_", + "modal.last_score_label": "última_puntuación", + "modal.team_members_label": "miembros_del_equipo", + "modal.choose_logo": "elegir_logo", + "modal.save": "Guardar", + "profile.kicker": "Cuenta", + "profile.title": "Perfil", + "profile.team_label": "Equipo", + "profile.loading": "Cargando...", + "profile.signed_in_as": "Sesión iniciada como", + "profile.points": "puntos", + "profile.rank": "posición", + "profile.last_score": "última puntuación", + "profile.account": "cuenta", + "profile.full_name": "Nombre completo", + "profile.email": "Correo", + "profile.role": "Rol", + "profile.status": "Estado", + "profile.change_password": "cambiar_contraseña", + "profile.current_password": "Contraseña actual", + "profile.new_password": "Nueva contraseña", + "profile.confirm_password": "Confirmar contraseña", + "profile.update": "Actualizar", + "profile.role_admin": "Administrador", + "profile.role_service": "Servicio", + "profile.role_player": "Jugador", + "profile.status_active": "Activo", + "profile.status_inactive": "Inactivo", + "profile.updated_msg": "Perfil actualizado", + "profile.password_updated_msg": "Contraseña actualizada", + "modal.help_team_lead": "El equipo", + "modal.help_team_middle": "te gustaría ayudar a responder", + "modal.accept": "Aceptar", + "modal.deny": "Rechazar", + "modal.help_confirm_lead": "¿Seguro que quieres ayudar al equipo", + "modal.yes": "Sí", + "modal.no": "No", + "admin.status_label": "estado_", + "admin.status_ready": "listo", + "admin.on": "Sí", + "admin.off": "No", + "admin.edit": "EDITAR", + "admin.delete": "Eliminar", + "admin.save": "Guardar", + "admin.none": "Ninguno", + "admin.never": "nunca", + "admin.import_export": "Importar / Exportar", + "admin.bulk_delete": "Borrado masivo", + "admin.disabled_suffix": "(desactivado)", + "admin.title.dashboard": "MapCTF Admin: Panel", + "admin.title.settings": "MapCTF Admin: Ajustes", + "admin.title.controls": "MapCTF Admin: Controles", + "admin.title.teams": "MapCTF Admin: Equipos", + "admin.title.team_logos": "MapCTF Admin: Logos de equipos", + "admin.title.users": "MapCTF Admin: Usuarios", + "admin.title.challenges": "MapCTF Admin: Retos", + "admin.title.activity": "MapCTF Admin: Actividad", + "admin.title.chat": "MapCTF Admin: Chat", + "admin.title.countries": "MapCTF Admin: Países", + "admin.nav.dashboard": "Panel", + "admin.nav.settings": "Ajustes", + "admin.nav.controls": "Controles", + "admin.nav.users": "Usuarios", + "admin.nav.teams": "Equipos", + "admin.nav.team_logos": "Logos de equipos", + "admin.nav.challenges": "Retos", + "admin.nav.countries": "Países", + "admin.nav.activity": "Actividad", + "admin.nav.chat": "Chat", + "admin.nav.end_game": "Terminar partida", + "admin.nav.pause_game": "Pausar partida", + "admin.nav.game_admin": "Administración del juego", + "admin.dashboard.header": "Panel de operaciones", + "admin.dashboard.open_gameboard": "Abrir tablero", + "admin.dashboard.game_controls": "Controles del juego", + "admin.dashboard.live_metrics": "Métricas en vivo", + "admin.dashboard.kpi_game_state": "Estado del juego", + "admin.dashboard.kpi_teams_online": "Equipos en línea", + "admin.dashboard.kpi_flags_hour": "Flags por hora", + "admin.dashboard.kpi_solve_coverage": "Cobertura de retos", + "admin.dashboard.kpi_platform_pulse": "Pulso de plataforma", + "admin.dashboard.kicker_competition": "Competición", + "admin.dashboard.leaderboard_preview": "Vista previa del marcador", + "admin.dashboard.view_board": "Ver tablero", + "admin.dashboard.kicker_progress": "Progreso", + "admin.dashboard.capture_velocity": "Velocidad de captura", + "admin.dashboard.last_10m": "Últimos 10m", + "admin.dashboard.kicker_challenges": "Retos", + "admin.dashboard.challenge_health": "Salud de los retos", + "admin.dashboard.manage": "Gestionar", + "admin.dashboard.kicker_platform": "Plataforma", + "admin.dashboard.infrastructure_status": "Estado de infraestructura", + "admin.dashboard.kicker_operations": "Operaciones", + "admin.dashboard.incident_queue": "Cola de incidentes", + "admin.dashboard.kicker_timeline": "Cronología", + "admin.dashboard.recent_activity": "Actividad reciente", + "admin.dashboard.kicker_control": "Plano de control", + "admin.dashboard.quick_actions": "Acciones rápidas", + "admin.dashboard.review_users": "Revisar usuarios", + "admin.dashboard.manage_teams": "Gestionar equipos", + "admin.dashboard.audit_activity": "Auditar actividad", + "admin.dashboard.moderate_chat": "Moderar chat", + "admin.users.page_header": "Gestión de usuarios", + "admin.users.add": "Añadir usuario", + "admin.users.search": "Buscar usuarios...", + "admin.users.actions": "Acciones de usuarios", + "admin.users.import": "Importar usuarios", + "admin.users.export": "Exportar usuarios", + "admin.users.bulk_state": "Estado masivo de usuarios", + "admin.users.enable_all": "Activar todos los usuarios", + "admin.users.disable_all": "Desactivar todos los usuarios", + "admin.users.delete_all": "Eliminar todos los usuarios", + "admin.users.set_password": "Establecer contraseña", + "admin.users.username": "Usuario", + "admin.users.name": "Nombre", + "admin.users.email": "Correo", + "admin.users.team": "Equipo", + "admin.users.admin": "Admin", + "admin.users.service": "Servicio", + "admin.users.last_ip": "Última IP", + "admin.users.last_ua": "Último user agent", + "admin.users.last_access": "Último acceso", + "admin.users.no_found_title": "No se encontraron usuarios", + "admin.users.no_found": "No se encontraron usuarios.", + "admin.teams.page_header": "Gestión de equipos", + "admin.teams.add": "Añadir equipo", + "admin.teams.search": "Buscar equipos...", + "admin.teams.actions": "Acciones de equipos", + "admin.teams.import": "Importar equipos", + "admin.teams.export": "Exportar equipos", + "admin.teams.bulk_state": "Estado masivo de equipos", + "admin.teams.enable_all": "Activar todos los equipos", + "admin.teams.disable_all": "Desactivar todos los equipos", + "admin.teams.bulk_visibility": "Visibilidad masiva de equipos", + "admin.teams.make_visible": "Hacer todos visibles", + "admin.teams.make_invisible": "Hacer todos invisibles", + "admin.teams.delete_all": "Eliminar todos los equipos", + "admin.teams.team_name": "Nombre del equipo", + "admin.teams.team_members": "Miembros del equipo", + "admin.teams.points": "Puntos", + "admin.teams.last_score": "Última puntuación", + "admin.teams.logo": "Logo", + "admin.teams.visible": "Visible", + "admin.teams.protected": "Protegido", + "admin.teams.no_members": "No hay miembros asignados", + "admin.teams.random_auto": "Aleatorio (auto)", + "admin.teams.no_found_title": "Equipos", + "admin.teams.no_found": "No se encontraron equipos.", + "admin.logos.page_header": "Logos de equipos", + "admin.logos.actions": "Acciones de logos", + "admin.logos.search": "Buscar logos...", + "admin.logos.add": "Añadir logo", + "admin.logos.import": "Importar logos", + "admin.logos.export": "Exportar logos", + "admin.logos.bulk_state": "Estado masivo de logos", + "admin.logos.enable_all": "Activar todos los logos", + "admin.logos.disable_all": "Desactivar todos los logos", + "admin.logos.delete_all_custom": "Eliminar todos los logos personalizados", + "admin.logos.all": "Logos", + "admin.logos.custom": "Logos personalizados", + "admin.logos.catalog": "Catálogo", + "admin.logos.no_custom": "No se encontraron logos personalizados", + "admin.logos.no_platform": "No se encontraron logos de plataforma", + "admin.logos.unnamed": "Sin nombre", + "admin.logos.edit_logo": "Editar logo", + "admin.logos.enabled": "Activado", + "admin.logos.not_enabled": "Desactivado", + "admin.logos.platform": "Plataforma", + "admin.logos.custom_type": "Personalizado", + "admin.logos.protected": "Protegido", + "admin.logos.not_protected": "No protegido", + "admin.logos.used": "En uso", + "admin.logos.not_used": "Sin usar", + "admin.challenges.page_header": "Gestión de retos", + "admin.challenges.add": "Añadir reto", + "admin.challenges.actions": "Acciones de retos", + "admin.challenges.categories": "Categorías de retos", + "admin.challenges.add_category": "Añadir categoría", + "admin.challenges.import_all": "Importar todo", + "admin.challenges.export_all": "Exportar todo", + "admin.challenges.enable_all": "Activar todos los retos", + "admin.challenges.disable_all": "Desactivar todos los retos", + "admin.challenges.delete_all": "Eliminar todos los retos", + "admin.challenges.delete_all_categories": "Eliminar todas las categorías", + "admin.challenges.no_found": "No se encontraron retos", + "admin.challenges.no_categories": "No se encontraron categorías", + "admin.settings.page_header": "Ajustes", + "admin.settings.actions": "Acciones de ajustes", + "admin.settings.import": "Importar ajustes", + "admin.settings.export": "Exportar ajustes", + "admin.settings.reset_defaults": "Restablecer valores predeterminados", + "admin.settings.login": "Ajustes de acceso", + "admin.settings.registration": "Ajustes de registro", + "admin.settings.scoring": "Ajustes de puntuación", + "admin.settings.game": "Ajustes del juego", + "admin.settings.branding": "Ajustes de marca", + "admin.settings.localization": "Ajustes de localización", + "admin.settings.gameboard": "Ajustes del tablero", + "admin.controls.page_header": "Controles del juego", + "admin.controls.game_actions": "Acciones del juego", + "admin.controls.users_actions": "Acciones de usuarios", + "admin.controls.teams_actions": "Acciones de equipos", + "admin.controls.challenges_actions": "Acciones de retos", + "admin.controls.settings_actions": "Acciones de ajustes", + "admin.controls.countries_actions": "Acciones de países", + "admin.controls.import_export_full": "Importar / Exportar juego completo", + "admin.controls.import_export_teams": "Importar / Exportar equipos", + "admin.controls.import_export_logos": "Importar / Exportar logos", + "admin.controls.bulk_challenge_state": "Estado masivo de retos", + "admin.controls.management": "Gestión", + "admin.controls.reset": "Restablecer", + "admin.game.import_full": "Importar juego completo", + "admin.game.export_full": "Exportar juego completo", + "admin.countries.open_page": "Abrir página de países", + "admin.countries.delete_all": "Eliminar todos los países", + "admin.countries.page_header": "Gestión de países", + "admin.countries.world_map": "Mapa mundial (pasa el cursor por un país para saltar a su fila)", + "admin.countries.th_country": "País", + "admin.countries.th_code": "Código de país", + "admin.countries.th_assigned": "Asignado", + "admin.countries.th_challenge": "Reto", + "admin.countries.th_svg": "SVG", + "admin.countries.th_active": "Activo", + "admin.activity.page_header": "Actividad", + "admin.activity.log": "Registro de actividad", + "admin.activity.add_entry": "Añadir entrada", + "admin.chat.page_header": "Chat mundial", + "admin.chat.entries": "Entradas de chat", + "admin.challenges.title": "Título", + "admin.challenges.description": "Descripción", + "admin.challenges.resource_url": "URL del recurso", + "admin.challenges.category": "Categoría", + "admin.challenges.country": "País", + "admin.challenges.points": "Puntos", + "admin.challenges.bonus": "Bonus", + "admin.challenges.bonus_decay": "Decaimiento de bonus", + "admin.challenges.hint_penalty": "Penalización de pista", + "admin.challenges.help_penalty": "Penalización de ayuda", + "admin.challenges.flag": "Flag", + "admin.challenges.hint": "Pista", + "admin.challenges.active": "Activo", + "admin.challenges.inactive": "Inactivo", + "admin.challenges.act_action": "acción", + "admin.challenges.act_message": "mensaje", + "admin.challenges.act_args": "args", + "admin.challenges.no_activity": "Todavía no hay actividad para este reto.", + "admin.challenges.system": "Sistema", + "admin.teams.export_full": "Exportar equipos + logos", + "modal.admin_kicker": "admin_", + "modal.status_kicker": "estado_", + "admin.modal.cancel": "Cancelar", + "admin.modal.no": "No", + "admin.modal.yes": "Sí", + "admin.modal.create_user": "Crear usuario", + "admin.modal.create_team": "Crear equipo", + "admin.modal.create_category": "Crear categoría", + "admin.modal.create_challenge": "Crear reto", + "admin.modal.create_entry": "Crear entrada", + "admin.modal.create_logo": "Crear logo", + "admin.modal.save_changes": "Guardar cambios", + "admin.modal.set_password": "Establecer contraseña", + "admin.modal.new_password": "Nueva contraseña", + "admin.modal.set_password_note": "Establece una nueva contraseña para", + "admin.modal.set_password_user": "este usuario", + "admin.modal.unsaved": "Sin guardar", + "admin.modal.changes": "Cambios", + "admin.modal.unsaved_msg": "Tienes cambios sin guardar. ¿Salir de esta página y descartarlos?", + "admin.modal.delete_challenge": "Eliminar reto", + "admin.modal.delete_team": "Eliminar equipo", + "admin.modal.delete_category": "Eliminar categoría", + "admin.modal.delete_entry": "Eliminar entrada", + "admin.modal.delete_all_challenges": "Eliminar todos los retos", + "admin.modal.delete_all_teams": "Eliminar todos los equipos", + "admin.modal.delete_all_users": "Eliminar todos los usuarios", + "admin.modal.delete_all_categories": "Eliminar todas las categorías", + "admin.modal.delete_all_countries": "Eliminar todos los países", + "admin.modal.delete_all_logos": "Eliminar todos los logos", + "admin.modal.disable_all_users": "Desactivar todos los usuarios", + "admin.modal.logout": "Cerrar sesión", + "admin.modal.reset_defaults": "Restablecer valores predeterminados", + "admin.modal.edit_logo": "Editar logo", + "admin.modal.select_country": "Seleccionar país", + "admin.modal.confirm_delete_challenge": "¿Seguro que quieres eliminar este reto?", + "admin.modal.confirm_delete_team": "¿Seguro que quieres eliminar este equipo?", + "admin.modal.confirm_delete_category": "¿Seguro que quieres eliminar esta categoría?", + "admin.modal.confirm_delete_activity": "¿Seguro que quieres eliminar esta entrada de actividad?", + "admin.modal.confirm_delete_all_challenges": "¿Seguro que quieres eliminar todos los retos? Esto no se puede deshacer.", + "admin.modal.confirm_delete_all_teams": "¿Seguro que quieres eliminar todos los equipos? Esto no se puede deshacer.", + "admin.modal.confirm_delete_all_users": "¿Seguro que quieres eliminar todos los usuarios? Esto no se puede deshacer.", + "admin.modal.confirm_delete_all_categories": "¿Seguro que quieres eliminar todas las categorías? Los retos deben eliminarse primero.", + "admin.modal.confirm_delete_all_countries": "¿Seguro que quieres eliminar todos los países? Esto no se puede deshacer y limpiará las asignaciones de países de los retos.", + "admin.modal.confirm_delete_all_logos": "¿Seguro que quieres eliminar todos los logos personalizados? Esto no se puede deshacer.", + "admin.modal.confirm_disable_all_users": "¿Seguro que quieres desactivar todos los usuarios? Esto no se puede deshacer.", + "admin.activity.kicker": "Registro de actividad del admin", + "admin.activity.subject": "Asunto", + "admin.activity.action": "Acción", + "admin.activity.visible": "Visible", + "admin.activity.message": "Mensaje", + "admin.activity.example": "Ejemplo", + "admin.activity.visible_opt": "Visible", + "admin.activity.hidden_opt": "Oculto", + "admin.activity.action_announcement": "Anuncio", + "admin.activity.action_completed": "Completado", + "admin.activity.action_enabled": "Activado", + "admin.activity.action_disabled": "Desactivado", + "admin.activity.subject_ph": "Blue Team o Admin", + "admin.activity.message_ph": "Capturado España", + "admin.logos.logo_name": "Nombre del logo", + "admin.logos.file": "Archivo", + "admin.logos.upload_file": "Subir archivo de logo", + "admin.logos.accepted_formats": "Formatos aceptados", + "admin.logos.upload_note": "Sube un archivo SVG, GIF, PNG o JPG/JPEG.", + "admin.logos.max_size": "Tamaño máximo: 512 KB", + "admin.logos.recommended_size": "Tamaño recomendado: 64 x 48 px", + "admin.logos.no_file": "Ningún archivo seleccionado", + "admin.logos.slug_note": "Usa el slug del archivo para controlar el nombre del logo almacenado.", + "admin.category.name": "Nombre", + "admin.category.description": "Descripción", + "admin.category.logo": "Logo", + "admin.modal.delete_all_logos_lead": "¿Eliminar todos los", + "admin.modal.delete_all_logos_trail": "logos? Los badges de plataforma se quedan en el catálogo. Esto no se puede deshacer.", + "admin.modal.reset_settings_title": "Restablecer ajustes", + "admin.modal.confirm_reset_settings": "¿Seguro que quieres restablecer todos los ajustes a los valores predeterminados?", + "admin.modal.confirm_logout": "¿Seguro que quieres cerrar sesión?", + "admin.js.modal_unavailable": "Sistema de modales no disponible", + "admin.js.request_failed": "La solicitud falló", + "admin.js.unable_challenge_row": "No se pudo localizar la fila del reto", + "admin.js.title_flag_required": "El título y el flag son obligatorios", + "admin.js.new_password_required": "La nueva contraseña es obligatoria", + "admin.js.logo_name_required": "El nombre del logo es obligatorio", + "admin.js.failed_update_user": "Error al actualizar el usuario", + "admin.js.failed_update_team": "Error al actualizar el equipo", + "admin.js.failed_update_password": "Error al actualizar la contraseña", + "admin.js.failed_update_logo": "Error al actualizar el logo", + "admin.js.failed_update_challenge": "Error al actualizar el reto", + "admin.js.failed_update_category": "Error al actualizar la categoría", + "admin.js.failed_import_challenges": "Error al importar los retos", + "admin.js.failed_delete_challenge": "Error al eliminar el reto", + "admin.js.failed_delete_activity_entry": "Error al eliminar la entrada de actividad", + "admin.js.failed_create_user": "Error al crear el usuario", + "admin.js.failed_create_team": "Error al crear el equipo", + "admin.js.failed_create_logo": "Error al crear el logo", + "admin.js.failed_create_challenge": "Error al crear el reto", + "admin.js.failed_create_category": "Error al crear la categoría", + "admin.js.failed_create_activity_entry": "Error al crear la entrada de actividad", + "admin.js.category_required": "La categoría es obligatoria", + "admin.js.users_imported": "Usuarios importados", + "admin.js.username_password_required": "El usuario y la contraseña son obligatorios", + "admin.js.user_updated": "Usuario actualizado", + "admin.js.user_created": "Usuario creado", + "admin.js.upload_logo_first": "Sube un archivo de logo antes de crear un logo personalizado", + "admin.js.updated": "Actualizado", + "admin.js.unable_challenge_form": "No se pudo localizar el formulario del reto", + "admin.js.unable_activity_entry": "No se pudo localizar la entrada de actividad", + "admin.js.teams_imported": "Equipos importados", + "admin.js.team_updated": "Equipo actualizado", + "admin.js.team_name_required": "El nombre del equipo es obligatorio", + "admin.js.team_deleted": "Equipo eliminado", + "admin.js.team_created": "Equipo creado", + "admin.js.team_full_required": "El nombre del equipo, el logo, activo, visible y los valores protegidos son obligatorios", + "admin.js.subject_message_required": "El asunto o el mensaje son obligatorios", + "admin.js.settings_reset_defaults": "Ajustes restablecidos a los valores predeterminados", + "admin.js.settings_imported": "Ajustes importados", + "admin.js.settings_import_missing": "No se encontró la entrada de archivo de importación de ajustes", + "admin.js.reset_confirm": "¿Restablecer todos los ajustes a los valores predeterminados?", + "admin.js.platform_logo_no_rename": "El nombre del logo de plataforma no se puede cambiar", + "admin.js.password_modal_unavailable": "El modal de contraseña no está disponible", + "admin.js.activity_created": "Entrada de actividad creada", + "admin.js.activity_deleted": "Entrada de actividad eliminada", + "admin.js.admin_service_active_required": "Los valores de admin, servicio y activo son obligatorios", + "admin.js.all_categories_deleted": "Todas las categorías eliminadas", + "admin.js.all_challenges_deleted": "Todos los retos eliminados", + "admin.js.all_challenges_disabled": "Todos los retos desactivados", + "admin.js.all_challenges_enabled": "Todos los retos activados", + "admin.js.all_countries_deleted": "Todos los países eliminados", + "admin.js.all_logos_deleted": "Todos los logos eliminados", + "admin.js.all_logos_disabled": "Todos los logos desactivados", + "admin.js.all_logos_enabled": "Todos los logos activados", + "admin.js.all_teams_deleted": "Todos los equipos eliminados", + "admin.js.all_teams_disabled": "Todos los equipos desactivados", + "admin.js.all_teams_enabled": "Todos los equipos activados", + "admin.js.all_teams_invisible": "Todos los equipos establecidos como invisibles", + "admin.js.all_teams_visible": "Todos los equipos establecidos como visibles", + "admin.js.all_users_deleted": "Todos los usuarios eliminados", + "admin.js.all_users_disabled": "Todos los usuarios desactivados", + "admin.js.all_users_enabled": "Todos los usuarios activados", + "admin.js.category_created": "Categoría creada", + "admin.js.category_deleted": "Categoría eliminada", + "admin.js.category_name_required": "El nombre de la categoría es obligatorio", + "admin.js.category_updated": "Categoría actualizada", + "admin.js.challenge_created": "Reto creado", + "admin.js.challenge_deleted": "Reto eliminado", + "admin.js.challenge_updated": "Reto actualizado", + "admin.js.challenges_imported": "Retos importados", + "admin.js.delete_all_categories_confirm": "¿Eliminar todas las categorías? Los retos deben eliminarse primero.", + "admin.js.delete_all_challenges_confirm": "¿Eliminar todos los retos? Esto no se puede deshacer.", + "admin.js.delete_all_teams_confirm": "¿Eliminar todos los equipos? Esto no se puede deshacer.", + "admin.js.delete_all_users_confirm": "¿Eliminar todos los usuarios? Esto no se puede deshacer.", + "admin.js.delete_category": "Eliminar categoría", + "admin.js.delete_challenge": "Eliminar reto", + "admin.js.delete_team": "Eliminar equipo", + "admin.js.delete_activity_confirm": "¿Eliminar esta entrada de actividad?", + "admin.js.disable_all_users_confirm": "¿Desactivar todos los usuarios?", + "admin.js.edit_category": "Editar categoría", + "admin.js.email_invalid": "El correo no es válido", + "admin.js.enabled_protected_required": "Los valores de activado y protegido son obligatorios", + "admin.js.full_game_imported": "Juego completo importado", + "admin.js.import_file_missing": "No se encontró la entrada de archivo de importación", + "admin.js.logo_created": "Logo creado", + "admin.js.logo_updated": "Logo actualizado", + "admin.js.logos_imported": "Logos importados", + "admin.js.max_size_prefix": "Tamaño máximo: ", + "admin.js.max_upload_prefix": "El tamaño máximo de subida de logo es ", + "admin.js.recommended_size_prefix": "Tamaño recomendado: ", + "admin.js.selected_file_prefix": "Archivo seleccionado: ", + "admin.js.upload_formats_aria": "Formatos de subida de logo aceptados", + "admin.js.upload_size_aria": "Límites de tamaño de subida de logo", + "login.success_msg": "Acceso correcto", + "login.logout_msg": "Sesión cerrada correctamente", + "auth.error_parse_body": "error al analizar el cuerpo de la petición", + "auth.error_renew_session": "error al renovar la sesión", + "auth.error_destroy_session": "error al destruir la sesión", + "auth.uuid_required": "UUID requerido", + "auth.user_pass_required": "el usuario y la contraseña son obligatorios", + "auth.invalid_credentials": "credenciales no válidas", + "auth.login_unavailable": "el acceso no está disponible", + "auth.login_disabled": "el acceso está desactivado", + "reg.invalid_body": "cuerpo de petición no válido", + "reg.failed_type_setting": "error al obtener el ajuste de tipo de registro", + "reg.token_required": "el token de registro es obligatorio", + "reg.failed_token_setting": "error al obtener el ajuste del token de registro", + "reg.invalid_token": "token de registro no válido", + "reg.email_required": "el correo es obligatorio", + "reg.team_required": "el equipo es obligatorio", + "reg.failed_register_team": "error al registrar el equipo", + "reg.failed_register_user": "error al registrar el usuario", + "reg.success": "Registro correcto", + "chat.failed_maxlen_setting": "error al obtener el ajuste de longitud máxima del chat", + "chat.not_authenticated": "usuario no autenticado", + "chat.failed_get_user": "error al obtener el usuario del mensaje de chat", + "chat.failed_create": "error al crear el mensaje de chat", + "chat.shown": "Visible", + "chat.hidden": "Oculto", + "score.unavailable": "la puntuación no está disponible", + "score.country_flag_required": "country_code y flag son obligatorios", + "score.failed_setting": "error al recuperar el ajuste de puntuación", + "score.disabled": "la puntuación está desactivada", + "score.failed_retrieve_user": "error al recuperar el usuario", + "score.no_team": "el usuario no está asignado a un equipo", + "score.failed_retrieve_team": "error al recuperar el equipo", + "score.no_active_challenge": "no se encontró un reto activo para el país", + "score.failed_retrieve_challenge": "error al recuperar el reto", + "score.already_completed": "Tu equipo ya completó este reto", + "score.failed_evaluate": "error al evaluar la puntuación actual", + "score.failed_record": "error al registrar el intento de puntuación", + "score.incorrect_flag": "Flag incorrecto", + "score.failed_retrieve_category": "error al recuperar la categoría del reto", + "score.failed_score": "error al puntuar el reto", + "score.completed": "Reto completado", + "score.incorrect_submission": "Envío incorrecto", + "hint.requests_unavailable": "las solicitudes de pista no están disponibles", + "hint.country_required": "country_code es obligatorio", + "hint.failed_setting": "error al recuperar el ajuste de pistas", + "hint.requests_disabled": "las solicitudes de pista están desactivadas", + "hint.not_available": "no hay pista disponible para este reto", + "hint.already_unlocked": "Pista ya desbloqueada", + "hint.failed_evaluate": "error al evaluar el estado de la pista", + "hint.not_enough_points": "Tu equipo no tiene suficientes puntos para esta pista", + "hint.failed_unlock": "error al desbloquear la pista", + "hint.unlocked": "Pista desbloqueada", + "hint.retrieval_unavailable": "la recuperación de pistas no está disponible", + "hint.requested": "Pista solicitada", + "hint.not_unlocked": "pista no desbloqueada", + "profile.auth_required": "se requiere autenticación", + "profile.unavailable": "el perfil no está disponible", + "profile.not_updated": "no se pudo actualizar el perfil", + "profile.current_required": "la contraseña actual es obligatoria", + "profile.current_incorrect": "la contraseña actual es incorrecta", + "profile.new_required": "la nueva contraseña es obligatoria", + "profile.new_min_length": "la nueva contraseña debe tener al menos 8 caracteres", + "profile.new_must_differ": "la nueva contraseña debe ser diferente", + "profile.new_mismatch": "las nuevas contraseñas no coinciden", + "profile.password_not_updated": "no se pudo actualizar la contraseña", + "profile.email_invalid": "el correo no es válido", + "admin.msg.failed_load_settings": "Error al cargar los ajustes", + "admin.msg.failed_load_users": "Error al cargar los usuarios", + "admin.msg.failed_load_teams_logos": "Error al cargar equipos/logos", + "admin.msg.failed_load_challenges": "Error al cargar los retos", + "admin.msg.failed_load_teams": "Error al cargar los equipos", + "admin.msg.failed_load_logos": "Error al cargar los logos", + "admin.msg.failed_load_custom_logos": "Error al cargar los logos personalizados", + "admin.msg.failed_export_json": "Error al generar el JSON de exportación", + "admin.msg.failed_enable_users": "Error al activar todos los usuarios", + "admin.msg.failed_disable_users": "Error al desactivar todos los usuarios", + "admin.msg.failed_delete_users": "Error al eliminar todos los usuarios", + "admin.msg.failed_enable_teams": "Error al activar todos los equipos", + "admin.msg.failed_disable_teams": "Error al desactivar todos los equipos", + "admin.msg.failed_visible_teams": "Error al hacer visibles todos los equipos", + "admin.msg.failed_invisible_teams": "Error al hacer invisibles todos los equipos", + "admin.msg.failed_enable_logos": "Error al activar todos los logos", + "admin.msg.failed_disable_logos": "Error al desactivar todos los logos", + "admin.msg.failed_delete_logos": "Error al eliminar los logos personalizados", + "admin.msg.enabled_users": "%d usuario(s) activado(s)", + "admin.msg.disabled_users": "%d usuario(s) desactivado(s). El usuario actual no se modificó.", + "admin.msg.deleted_users": "%d usuario(s) eliminado(s). El usuario actual se conservó.", + "admin.msg.enabled_teams": "%d equipo(s) activado(s)", + "admin.msg.disabled_teams": "%d equipo(s) desactivado(s)", + "admin.msg.visible_teams": "Establecido visible para %d equipo(s)", + "admin.msg.invisible_teams": "Establecido invisible para %d equipo(s)", + "admin.msg.enabled_logos": "%d logo(s) activado(s)", + "admin.msg.disabled_logos": "%d logo(s) desactivado(s)", + "admin.msg.deleted_logos": "%d logo(s) personalizado(s) eliminado(s); los logos de plataforma sin cambios", + "admin.msg.activity_deleted": "Entrada de actividad eliminada", + "admin.msg.custom_activity": "Actividad personalizada", + "admin.msg.invalid_logo_id": "ID de logo no válido", + "admin.msg.invalid_team_id": "team_id no válido", + "admin.msg.invalid_user_id": "ID de usuario no válido", + "admin.msg.not_authenticated": "No autenticado", + "admin.msg.other_uuid": "Otro UUID", + "feed.error_activity": "error al recuperar los registros de actividad", + "feed.error_challenges": "error al recuperar los retos", + "feed.error_chat": "error al recuperar las entradas de chat", + "feed.error_country": "error al recuperar los datos del país", + "feed.error_game_clock": "error al recuperar los datos del reloj de partida", + "feed.error_team_members": "error al recuperar los miembros del equipo", + "feed.error_team_settings": "error al recuperar los ajustes del equipo", + "feed.error_teams": "error al recuperar los equipos", + "feed.error_domination": "error al recuperar los datos de dominación mundial", + "feed.invalid_uuid": "UUID no válido", + "feed.invalid_json": "payload JSON no válido", + "feed.not_authenticated": "usuario no autenticado", + "feed.content_type": "Content-Type debe ser application/json", + "admin.msg.logos_deleted_sync_fail": "Logos personalizados eliminados pero error al sincronizar el uso de logos", + "admin.msg.ajax_only": "Solo peticiones AJAX", + "admin.msg.activity_logging_unavailable": "El registro de actividad no está disponible", + "admin.msg.activity_entry_created": "Entrada de actividad creada", + "admin.msg.admin_service_active_required": "Los valores de admin, servicio y activo son obligatorios", + "admin.msg.category_created": "Categoría creada", + "admin.msg.category_deleted": "Categoría eliminada", + "admin.msg.category_updated": "Categoría actualizada", + "admin.msg.category_assigned_not_deleted": "La categoría está asignada a retos y no se eliminó", + "admin.msg.category_name_required": "El nombre de la categoría es obligatorio", + "admin.msg.category_not_found": "Categoría no encontrada", + "admin.msg.delete_challenges_before_categories": "Elimina todos los retos antes de eliminar las categorías", + "admin.msg.challenge_created": "Reto creado", + "admin.msg.challenge_deleted": "Reto eliminado", + "admin.msg.challenge_updated": "Reto actualizado", + "admin.msg.challenge_not_found": "Reto no encontrado", + "admin.msg.chat_message_deleted": "Mensaje de chat eliminado", + "admin.msg.chat_message_hidden": "Mensaje de chat oculto", + "admin.msg.chat_message_unhidden": "Mensaje de chat mostrado", + "admin.msg.chat_message_not_found": "Mensaje de chat no encontrado", + "admin.msg.chat_visibility_updated": "Visibilidad del chat actualizada", + "admin.msg.content_type_form": "Content-Type debe ser application/json o multipart/form-data", + "admin.msg.countries_not_initialized": "El gestor de países no está inicializado", + "admin.msg.countries_or_challenges_not_initialized": "El gestor de países o retos no está inicializado", + "admin.msg.country_active_available": "El país debe estar activo y disponible", + "admin.msg.country_status_updated": "Estado del país actualizado", + "admin.msg.deleted_team_sync_fail": "Equipos eliminados pero error al sincronizar el uso de logos", + "admin.msg.team_deleted_sync_fail": "Equipo eliminado pero error al sincronizar el uso de logos", + "admin.msg.team_updated_sync_fail": "Equipo actualizado pero error al sincronizar el uso de logos", + "admin.msg.import_sync_fail": "Importación completada pero error al sincronizar el uso de logos", + "admin.msg.failed_importing_challenges": "Error al importar los retos", + "admin.msg.failed_importing_logos": "Error al importar los logos", + "admin.msg.failed_importing_teams": "Error al importar los equipos", + "admin.msg.failed_importing_users": "Error al importar los usuarios", + "admin.msg.failed_resetting_defaults": "Error al restablecer los ajustes predeterminados", + "admin.msg.failed_assign_country": "Error al asignar el país al reto", + "admin.msg.failed_build_activity": "Error al construir la entrada de actividad", + "admin.msg.failed_clear_country_refs": "Error al limpiar las referencias de país del reto", + "admin.msg.failed_create_activity": "Error al crear la entrada de actividad", + "admin.msg.failed_create_category": "Error al crear la categoría", + "admin.msg.failed_create_challenge": "Error al crear el reto", + "admin.msg.failed_create_logo": "Error al crear el logo", + "admin.msg.failed_create_user": "Error al crear el usuario", + "admin.msg.failed_delete_activity": "Error al eliminar la entrada de actividad", + "admin.msg.failed_delete_all_categories": "Error al eliminar todas las categorías", + "admin.msg.failed_delete_all_challenges": "Error al eliminar todos los retos", + "admin.msg.failed_delete_all_countries": "Error al eliminar todos los países", + "admin.msg.failed_delete_all_teams": "Error al eliminar todos los equipos", + "admin.msg.failed_delete_category": "Error al eliminar la categoría", + "admin.msg.failed_delete_challenge": "Error al eliminar el reto", + "admin.msg.failed_delete_chat": "Error al eliminar el mensaje de chat", + "admin.msg.failed_delete_team": "Error al eliminar el equipo", + "admin.msg.failed_disable_all_challenges": "Error al desactivar todos los retos", + "admin.msg.failed_enable_all_challenges": "Error al activar todos los retos", + "admin.msg.failed_import_challenges": "Error al importar los retos", + "admin.msg.failed_import_logos": "Error al importar los logos", + "admin.msg.failed_import_teams": "Error al importar los equipos", + "admin.msg.failed_import_users": "Error al importar los usuarios", + "admin.msg.failed_load_logo": "Error al cargar el logo", + "admin.msg.failed_load_team": "Error al cargar el equipo", + "admin.msg.failed_read_logo_file": "Error al leer el archivo de logo subido", + "admin.msg.failed_release_country_refs": "Error al liberar los países del reto", + "admin.msg.failed_release_country": "Error al liberar el país del reto", + "admin.msg.failed_resolve_random_logo": "Error al resolver el logo aleatorio", + "admin.msg.failed_retrieve_category": "Error al recuperar la categoría del reto", + "admin.msg.failed_store_logo_file": "Error al almacenar el archivo de logo personalizado", + "admin.msg.failed_update_category": "Error al actualizar la categoría", + "admin.msg.failed_update_country_assignment": "Error al actualizar la asignación de país del reto", + "admin.msg.failed_update_challenge": "Error al actualizar el reto", + "admin.msg.failed_update_chat_visibility": "Error al actualizar la visibilidad del chat", + "admin.msg.failed_update_country_status": "Error al actualizar el estado del país", + "admin.msg.failed_update_custom_logo": "Error al actualizar custom_logo", + "admin.msg.failed_update_custom_org": "Error al actualizar custom_org", + "admin.msg.failed_update_game_end": "Error al actualizar game_end_time", + "admin.msg.failed_update_game_start": "Error al actualizar game_start_time", + "admin.msg.failed_update_language": "Error al actualizar el idioma", + "admin.msg.failed_update_logo": "Error al actualizar el logo", + "admin.msg.failed_update_password": "Error al actualizar la contraseña", + "admin.msg.failed_update_reg_token": "Error al actualizar registration_token", + "admin.msg.failed_update_team": "Error al actualizar el equipo", + "admin.msg.failed_update_user": "Error al actualizar el usuario", + "admin.msg.failed_validate_team_name": "Error al validar el nombre del equipo", + "admin.msg.failed_validate_team": "Error al validar el equipo", + "admin.msg.failed_verify_category_usage": "Error al verificar el uso de la categoría", + "admin.msg.full_import_complete": "Importación de juego completo completada: ", + "admin.msg.import_complete": "Importación completada: ", + "admin.msg.invalid_json_payload": "Payload JSON no válido", + "admin.msg.invalid_svg": "Archivo SVG no válido", + "admin.msg.invalid_active": "Valor de active no válido", + "admin.msg.invalid_activity_id": "ID de entrada de actividad no válido", + "admin.msg.invalid_admin": "Valor de admin no válido", + "admin.msg.invalid_bonus": "Valor de bonus no válido", + "admin.msg.invalid_bonus_decay": "Valor de bonus_decay no válido", + "admin.msg.invalid_category_id": "ID de categoría no válido", + "admin.msg.invalid_challenge_url": "URL del reto no válida", + "admin.msg.invalid_challenge_id": "ID de reto no válido", + "admin.msg.invalid_chat_id": "ID de chat no válido", + "admin.msg.invalid_country_id": "ID de país no válido", + "admin.msg.invalid_country_code": "Código de país no válido", + "admin.msg.invalid_enabled": "Valor de enabled no válido", + "admin.msg.invalid_form_payload": "Payload de formulario no válido", + "admin.msg.invalid_full_game_payload": "Payload de importación de juego completo no válido", + "admin.msg.invalid_game_end_format": "Formato de game_end_time no válido", + "admin.msg.invalid_game_start_format": "Formato de game_start_time no válido", + "admin.msg.invalid_help_penalty": "Valor de help_penalty no válido", + "admin.msg.invalid_hidden": "Valor de hidden no válido", + "admin.msg.invalid_hint_penalty": "Valor de hint_penalty no válido", + "admin.msg.invalid_import_payload": "Payload de importación no válido", + "admin.msg.invalid_logo_slug": "Slug de logo no válido", + "admin.msg.invalid_multipart": "Payload de formulario multipart no válido", + "admin.msg.invalid_points": "Valor de points no válido", + "admin.msg.invalid_protected": "Valor de protected no válido", + "admin.msg.invalid_raster_logo": "Archivo de logo raster no válido", + "admin.msg.invalid_service": "Valor de service no válido", + "admin.msg.invalid_settings_payload": "Payload de importación de ajustes no válido", + "admin.msg.invalid_uploaded_logo_type": "Tipo de archivo de logo subido no válido", + "admin.msg.invalid_uploaded_logo": "Archivo de logo subido no válido", + "admin.msg.invalid_visible": "Valor de visible no válido", + "admin.msg.logo_already_exists": "El logo ya existe", + "admin.msg.logo_created": "Logo creado", + "admin.msg.logo_file_already_exists": "El archivo de logo ya existe", + "admin.msg.logo_file_required": "El archivo de logo es obligatorio", + "admin.msg.logo_is_required": "El logo es obligatorio", + "admin.msg.logo_not_found": "Logo no encontrado", + "admin.msg.logo_symbol_required": "El símbolo del logo es obligatorio", + "admin.msg.logo_updated": "Logo actualizado", + "admin.msg.missing_country_id": "Falta el ID de país", + "admin.msg.missing_setting_name": "Falta setting_name", + "admin.msg.no_challenges_in_payload": "No se encontraron retos en el payload de importación", + "admin.msg.no_logos_in_payload": "No se encontraron logos en el payload de importación", + "admin.msg.no_teams_in_payload": "No se encontraron equipos en el payload de importación", + "admin.msg.no_teams_or_logos_in_payload": "No se encontraron equipos ni logos en el payload de importación", + "admin.msg.no_users_in_payload": "No se encontraron usuarios en el payload de importación", + "admin.msg.platform_logo_no_rename": "El nombre del logo de plataforma no se puede cambiar", + "admin.msg.select_category": "Selecciona una categoría", + "admin.msg.select_valid_category": "Selecciona una categoría válida", + "admin.msg.random_not_valid_logo": "Random no es un logo válido para la creación de logos", + "admin.msg.settings_reset_defaults": "Ajustes restablecidos a los valores predeterminados", + "admin.msg.team_created": "Equipo creado", + "admin.msg.team_deleted": "Equipo eliminado", + "admin.msg.team_name_already_exists": "El nombre del equipo ya existe", + "admin.msg.team_name_required": "El nombre del equipo es obligatorio", + "admin.msg.team_not_found": "Equipo no encontrado", + "admin.msg.team_updated": "Equipo actualizado", + "admin.msg.unsupported_setting": "Ajuste no soportado", + "admin.msg.uploaded_logo_too_large": "El archivo de logo subido es demasiado grande", + "admin.msg.user_created": "Usuario creado", + "admin.msg.user_created_active_fail": "Usuario creado pero error al establecer el flag active", + "admin.msg.user_not_found": "Usuario no encontrado", + "admin.msg.user_updated": "Usuario actualizado", + "admin.msg.valid_category_id_required": "Se requiere un category_id válido", + "admin.msg.failed_update_setting": "Error al actualizar %s", + "admin.msg.invalid_boolean_for": "Valor booleano no válido para %s", + "admin.msg.invalid_integer_for": "Valor entero no válido para %s", + "admin.msg.updated_setting": "Actualizado %s", + "admin.msg.deleted_team_count": "%d equipo(s) eliminado(s)", + "admin.msg.deleted_challenge_count": "%d reto(s) eliminado(s)", + "admin.msg.enabled_challenge_count": "%d reto(s) activado(s)", + "admin.msg.disabled_challenge_count": "%d reto(s) desactivado(s)", + "admin.msg.deleted_category_count": "%d categoría(s) eliminada(s)", + "admin.msg.deleted_country_count": "%d país(es) eliminado(s)", + "admin.msg.teams_created": "equipos creados %d", + "admin.msg.teams_updated": "equipos actualizados %d", + "admin.msg.users_created": "usuarios creados %d", + "admin.msg.users_updated": "usuarios actualizados %d", + "admin.msg.logos_skipped": "logos omitidos %d", + "admin.msg.teams_skipped": "equipos omitidos %d", + "admin.msg.logo_name_required": "El nombre del logo es obligatorio", + "admin.msg.subject_message_required": "El asunto o el mensaje son obligatorios", + "admin.msg.title_flag_required": "El título y el flag son obligatorios", + "admin.msg.user_pass_required": "El usuario y la contraseña son obligatorios" +}