diff --git a/backend/cmd/map/handlers/admin.go b/backend/cmd/map/handlers/admin.go index 5532843..5c60bf9 100644 --- a/backend/cmd/map/handlers/admin.go +++ b/backend/cmd/map/handlers/admin.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + htmltemplate "html/template" "io" "net/http" "net/mail" @@ -269,11 +270,12 @@ func logoFilePath(custom bool, logo string) string { return "/static/svg/icons/badges/badge-" + slug + ".svg" } -func adminTemplateFuncs() template.FuncMap { +func (h *HandlersMap) adminTemplateFuncs(r *http.Request) template.FuncMap { return template.FuncMap{ "logoFilePath": logoFilePath, "logoIsImage": isCustomLogoAssetPath, "logoSymbol": normalizeLogoSymbolName, + "T": h.T(r.Context()), } } @@ -478,16 +480,19 @@ func (h *HandlersMap) AdminTemplateHandler(w http.ResponseWriter, r *http.Reques return } // Prepare template - t, err := template.ParseFiles( - h.Config.Map.TemplatesDir + "/admin/index.html") + t, err := template.New("index.html").Funcs(h.adminTemplateFuncs(r)).ParseFiles(h.Config.Map.TemplatesDir + "/admin/index.html") if err != nil { log.Err(err).Msg("error getting admin template") return } // Prepare template data authenticated := h.IsAuthenticated(r.Context()) + tr := h.T(r.Context()) + i18nJSON, _ := json.Marshal(h.LocaleMessages(r.Context())) templateData := AdminTemplateData{ - Title: "MapCTF Admin: Dashboard", + Title: tr("admin.title.dashboard"), + Lang: h.Locale(r.Context()).String(), + I18NJSON: htmltemplate.JS(i18nJSON), UUID: uuid, Authenticated: authenticated, Admin: h.IsAdmin(r.Context()), @@ -513,16 +518,19 @@ func (h *HandlersMap) AdminSettingsTemplateHandler(w http.ResponseWriter, r *htt return } // Prepare template - t, err := template.ParseFiles( - h.Config.Map.TemplatesDir + "/admin/settings.html") + t, err := template.New("settings.html").Funcs(h.adminTemplateFuncs(r)).ParseFiles(h.Config.Map.TemplatesDir + "/admin/settings.html") if err != nil { log.Err(err).Msg("error getting admin template") return } // Prepare template data authenticated := h.IsAuthenticated(r.Context()) + tr := h.T(r.Context()) + i18nJSON, _ := json.Marshal(h.LocaleMessages(r.Context())) templateData := AdminSettingsTemplateData{ - Title: "MapCTF Admin: Settings", + Title: tr("admin.title.settings"), + Lang: h.Locale(r.Context()).String(), + I18NJSON: htmltemplate.JS(i18nJSON), UUID: uuid, Authenticated: authenticated, Admin: h.IsAdmin(r.Context()), @@ -1700,16 +1708,19 @@ func (h *HandlersMap) AdminControlsTemplateHandler(w http.ResponseWriter, r *htt return } // Prepare template - t, err := template.ParseFiles( - h.Config.Map.TemplatesDir + "/admin/controls.html") + t, err := template.New("controls.html").Funcs(h.adminTemplateFuncs(r)).ParseFiles(h.Config.Map.TemplatesDir + "/admin/controls.html") if err != nil { log.Err(err).Msg("error getting admin template") return } // Prepare template data authenticated := h.IsAuthenticated(r.Context()) + tr := h.T(r.Context()) + i18nJSON, _ := json.Marshal(h.LocaleMessages(r.Context())) templateData := AdminControlsTemplateData{ - Title: "MapCTF Admin: Controls", + Title: tr("admin.title.controls"), + Lang: h.Locale(r.Context()).String(), + I18NJSON: htmltemplate.JS(i18nJSON), UUID: uuid, Authenticated: authenticated, Admin: h.IsAdmin(r.Context()), @@ -1735,16 +1746,19 @@ func (h *HandlersMap) AdminTeamsTemplateHandler(w http.ResponseWriter, r *http.R return } // Prepare template - t, err := template.New("teams.html").Funcs(adminTemplateFuncs()).ParseFiles( - h.Config.Map.TemplatesDir + "/admin/teams.html") + t, err := template.New("teams.html").Funcs(h.adminTemplateFuncs(r)).ParseFiles(h.Config.Map.TemplatesDir + "/admin/teams.html") if err != nil { log.Err(err).Msg("error getting admin template") return } // Prepare template data authenticated := h.IsAuthenticated(r.Context()) + tr := h.T(r.Context()) + i18nJSON, _ := json.Marshal(h.LocaleMessages(r.Context())) templateData := AdminTeamsTemplateData{ - Title: "MapCTF Admin: Teams", + Title: tr("admin.title.teams"), + Lang: h.Locale(r.Context()).String(), + I18NJSON: htmltemplate.JS(i18nJSON), UUID: uuid, Authenticated: authenticated, Admin: h.IsAdmin(r.Context()), @@ -1797,15 +1811,18 @@ func (h *HandlersMap) AdminTeamLogosTemplateHandler(w http.ResponseWriter, r *ht h.ErrorInvalidUUID(w, r) return } - t, err := template.New("team-logos.html").Funcs(adminTemplateFuncs()).ParseFiles( - h.Config.Map.TemplatesDir + "/admin/team-logos.html") + t, err := template.New("team-logos.html").Funcs(h.adminTemplateFuncs(r)).ParseFiles(h.Config.Map.TemplatesDir + "/admin/team-logos.html") if err != nil { log.Err(err).Msg("error getting admin team-logos template") return } authenticated := h.IsAuthenticated(r.Context()) + tr := h.T(r.Context()) + i18nJSON, _ := json.Marshal(h.LocaleMessages(r.Context())) templateData := AdminTeamLogosTemplateData{ - Title: "MapCTF Admin: Team Logos", + Title: tr("admin.title.team_logos"), + Lang: h.Locale(r.Context()).String(), + I18NJSON: htmltemplate.JS(i18nJSON), UUID: uuid, Authenticated: authenticated, Admin: h.IsAdmin(r.Context()), @@ -3231,16 +3248,19 @@ func (h *HandlersMap) AdminUsersTemplateHandler(w http.ResponseWriter, r *http.R return } // Prepare template - t, err := template.ParseFiles( - h.Config.Map.TemplatesDir + "/admin/users.html") + t, err := template.New("users.html").Funcs(h.adminTemplateFuncs(r)).ParseFiles(h.Config.Map.TemplatesDir + "/admin/users.html") if err != nil { log.Err(err).Msg("error getting admin template") return } // Prepare template data authenticated := h.IsAuthenticated(r.Context()) + tr := h.T(r.Context()) + i18nJSON, _ := json.Marshal(h.LocaleMessages(r.Context())) templateData := AdminUsersTemplateData{ - Title: "MapCTF Admin: Users", + Title: tr("admin.title.users"), + Lang: h.Locale(r.Context()).String(), + I18NJSON: htmltemplate.JS(i18nJSON), UUID: uuid, Authenticated: authenticated, Admin: h.IsAdmin(r.Context()), @@ -4143,16 +4163,19 @@ func (h *HandlersMap) AdminChallengesTemplateHandler(w http.ResponseWriter, r *h return } // Prepare template - t, err := template.ParseFiles( - h.Config.Map.TemplatesDir + "/admin/challenges.html") + t, err := template.New("challenges.html").Funcs(h.adminTemplateFuncs(r)).ParseFiles(h.Config.Map.TemplatesDir + "/admin/challenges.html") if err != nil { log.Err(err).Msg("error getting admin template") return } // Prepare template data authenticated := h.IsAuthenticated(r.Context()) + tr := h.T(r.Context()) + i18nJSON, _ := json.Marshal(h.LocaleMessages(r.Context())) templateData := AdminChallengesTemplateData{ - Title: "MapCTF Admin: Challenges", + Title: tr("admin.title.challenges"), + Lang: h.Locale(r.Context()).String(), + I18NJSON: htmltemplate.JS(i18nJSON), UUID: uuid, Authenticated: authenticated, Admin: h.IsAdmin(r.Context()), @@ -4977,8 +5000,7 @@ func (h *HandlersMap) AdminActivityTemplateHandler(w http.ResponseWriter, r *htt return } // Prepare template - t, err := template.ParseFiles( - h.Config.Map.TemplatesDir + "/admin/activity.html") + t, err := template.New("activity.html").Funcs(h.adminTemplateFuncs(r)).ParseFiles(h.Config.Map.TemplatesDir + "/admin/activity.html") if err != nil { log.Err(err).Msg("error getting admin template") return @@ -4986,8 +5008,12 @@ func (h *HandlersMap) AdminActivityTemplateHandler(w http.ResponseWriter, r *htt // Prepare template data authenticated := h.IsAuthenticated(r.Context()) currentUsername := strings.TrimSpace(h.Sessions.GetString(r.Context(), string(ContextKeyUser))) + tr := h.T(r.Context()) + i18nJSON, _ := json.Marshal(h.LocaleMessages(r.Context())) templateData := AdminActivityTemplateData{ - Title: "MapCTF Admin: Activity", + Title: tr("admin.title.activity"), + Lang: h.Locale(r.Context()).String(), + I18NJSON: htmltemplate.JS(i18nJSON), UUID: uuid, Authenticated: authenticated, Admin: h.IsAdmin(r.Context()), @@ -5132,14 +5158,17 @@ func (h *HandlersMap) AdminChatTemplateHandler(w http.ResponseWriter, r *http.Re h.ErrorInvalidUUID(w, r) return } - t, err := template.ParseFiles( - h.Config.Map.TemplatesDir + "/admin/chat.html") + t, err := template.New("chat.html").Funcs(h.adminTemplateFuncs(r)).ParseFiles(h.Config.Map.TemplatesDir + "/admin/chat.html") if err != nil { log.Err(err).Msg("error getting admin chat template") return } + tr := h.T(r.Context()) + i18nJSON, _ := json.Marshal(h.LocaleMessages(r.Context())) templateData := AdminChatTemplateData{ - Title: "MapCTF Admin: Chat", + Title: tr("admin.title.chat"), + Lang: h.Locale(r.Context()).String(), + I18NJSON: htmltemplate.JS(i18nJSON), UUID: uuid, Authenticated: h.IsAuthenticated(r.Context()), Admin: h.IsAdmin(r.Context()), @@ -5336,16 +5365,19 @@ func (h *HandlersMap) AdminCountriesTemplateHandler(w http.ResponseWriter, r *ht return } // Prepare template - t, err := template.ParseFiles( - h.Config.Map.TemplatesDir + "/admin/countries.html") + t, err := template.New("countries.html").Funcs(h.adminTemplateFuncs(r)).ParseFiles(h.Config.Map.TemplatesDir + "/admin/countries.html") if err != nil { log.Err(err).Msg("error getting admin template") return } // Prepare template data authenticated := h.IsAuthenticated(r.Context()) + tr := h.T(r.Context()) + i18nJSON, _ := json.Marshal(h.LocaleMessages(r.Context())) templateData := AdminCountriesTemplateData{ - Title: "MapCTF Admin: Countries", + Title: tr("admin.title.countries"), + Lang: h.Locale(r.Context()).String(), + I18NJSON: htmltemplate.JS(i18nJSON), UUID: uuid, Authenticated: authenticated, Admin: h.IsAdmin(r.Context()), diff --git a/backend/cmd/map/handlers/admin_test.go b/backend/cmd/map/handlers/admin_test.go index 76c7c44..39cd2ee 100644 --- a/backend/cmd/map/handlers/admin_test.go +++ b/backend/cmd/map/handlers/admin_test.go @@ -693,7 +693,7 @@ func TestAdminAddLogoModalAllowsRasterImageUploads(t *testing.T) { require.Contains(t, string(adminJS), `var ADMIN_LOGO_UPLOAD_MAX_LABEL = "512 KB";`) require.Contains(t, string(adminJS), `var ADMIN_LOGO_UPLOAD_RECOMMENDED_SIZE = "64 x 48 px";`) require.Contains(t, string(adminJS), `function formatAdminLogoFileSize(bytes)`) - require.Contains(t, string(adminJS), `Selected file: " + logoFile.name + " (" + formatAdminLogoFileSize(logoFile.size) + ")"`) + require.Contains(t, string(adminJS), `t("admin.js.selected_file_prefix", "Selected file: ") + logoFile.name + " (" + formatAdminLogoFileSize(logoFile.size) + ")"`) require.Contains(t, string(adminJS), `logoFile.size > ADMIN_LOGO_UPLOAD_MAX_BYTES`) require.Contains(t, string(adminJS), `function ensureAdminLogoUploadFormats(form)`) require.Contains(t, string(adminJS), `ensureAdminLogoUploadFormats(form);`) @@ -1360,8 +1360,8 @@ func TestAdminChallengesEditorGroupsTaxonomyAndScoringRows(t *testing.T) { css := string(mustReadFile(t, filepath.Join("..", "templates", "static", "css", "mapctf.css"))) require.Contains(t, template, `admin-challenge-grid-row admin-challenge-grid-row--taxonomy`) - require.Contains(t, template, `for="challenge-category-{{ .ID }}">Category`) - require.Contains(t, template, `for="challenge-country-{{ .ID }}">Country`) + require.Contains(t, template, `for="challenge-category-{{ .ID }}">{{ T "admin.challenges.category" }}`) + require.Contains(t, template, `for="challenge-country-{{ .ID }}">{{ T "admin.challenges.country" }}`) require.Contains(t, css, `.admin-box .admin-challenge-grid-row--taxonomy`) require.Contains(t, css, `grid-template-columns: repeat(2, minmax(0, 1fr));`) require.Contains(t, css, `.admin-box .admin-score-group`) diff --git a/backend/cmd/map/handlers/login_i18n_test.go b/backend/cmd/map/handlers/login_i18n_test.go index 6137dc7..891232e 100644 --- a/backend/cmd/map/handlers/login_i18n_test.go +++ b/backend/cmd/map/handlers/login_i18n_test.go @@ -11,6 +11,8 @@ import ( "github.com/jmpsec/mapctf/pkg/config" "github.com/jmpsec/mapctf/pkg/i18n" "github.com/jmpsec/mapctf/pkg/settings" + "github.com/jmpsec/mapctf/pkg/teams" + "github.com/jmpsec/mapctf/pkg/users" "github.com/stretchr/testify/require" ) @@ -159,3 +161,46 @@ func TestGameboardHandlerRendersConfiguredLocale(t *testing.T) { } require.Contains(t, body, `window.MCTF_LANG = "es"`) } + +func TestAdminUsersHandlerRendersConfiguredLocale(t *testing.T) { + if _, err := os.Stat(filepath.Join("..", "templates", "admin", "users.html")); err != nil { + t.Skipf("admin users template not available: %v", err) + } + + db := newJSONTestDB(t) + 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)) + teamManager, err := teams.CreateTeams(db) + require.NoError(t, err) + userManager, err := users.CreateUserManager(db, &config.ConfigurationJWT{Secret: "test-secret", HoursToExpire: 24}) + require.NoError(t, err) + catalog, err := i18n.New() + require.NoError(t, err) + sessions := scs.New() + handler := CreateHandlersMap( + WithConfig(config.MapCTFConfiguration{Map: config.ConfigurationMap{UUID: jsonTestUUID, TemplatesDir: filepath.Join("..", "templates")}}), + WithSettings(settingsManager), + WithTeams(teamManager), + WithUsers(userManager), + WithSessions(sessions), + WithI18N(catalog), + ) + + req := newTemplateRequestWithUUID(http.MethodGet, "/"+jsonTestUUID+"/admin/users", jsonTestUUID) + ctx, err := sessions.Load(req.Context(), "") + require.NoError(t, err) + sessions.Put(ctx, string(ContextKeyUser), "admin") + sessions.Put(ctx, string(ContextKeyAdmin), true) + req = req.WithContext(ctx) + + rr := httptest.NewRecorder() + handler.LocaleMiddleware(http.HandlerFunc(handler.AdminUsersTemplateHandler)).ServeHTTP(rr, req) + + require.Equal(t, http.StatusOK, rr.Code, "body: %s", rr.Body.String()) + body := rr.Body.String() + for _, want := range []string{``, "Administración del juego", "Gestión de usuarios", "Añadir usuario", `window.MCTF_LANG = "es"`} { + require.Contains(t, body, want) + } +} diff --git a/backend/cmd/map/handlers/types-templates.go b/backend/cmd/map/handlers/types-templates.go index 37670eb..38da704 100644 --- a/backend/cmd/map/handlers/types-templates.go +++ b/backend/cmd/map/handlers/types-templates.go @@ -114,6 +114,8 @@ type RegistrationTemplateData struct { // AdminSettingsTemplateData for passing data to the admin settings template type AdminSettingsTemplateData struct { Title string + Lang string + I18NJSON template.JS UUID string Authenticated bool Admin bool @@ -143,6 +145,8 @@ type AdminSettingsTemplateData struct { // AdminTemplateData for passing data to the admin template type AdminTemplateData struct { Title string + Lang string + I18NJSON template.JS UUID string Authenticated bool Admin bool @@ -153,6 +157,8 @@ type AdminTemplateData struct { // AdminControlsTemplateData for passing data to the admin controls template type AdminControlsTemplateData struct { Title string + Lang string + I18NJSON template.JS UUID string Authenticated bool Admin bool @@ -163,6 +169,8 @@ type AdminControlsTemplateData struct { // AdminUsersTemplateData for passing data to the admin users template type AdminUsersTemplateData struct { Title string + Lang string + I18NJSON template.JS UUID string Authenticated bool Admin bool @@ -176,6 +184,8 @@ type AdminUsersTemplateData struct { // AdminTeamsTemplateData for passing data to the admin teams template type AdminTeamsTemplateData struct { Title string + Lang string + I18NJSON template.JS UUID string Authenticated bool Admin bool @@ -190,6 +200,8 @@ type AdminTeamsTemplateData struct { // AdminTeamLogosTemplateData for passing data to the admin team-logos template type AdminTeamLogosTemplateData struct { Title string + Lang string + I18NJSON template.JS UUID string Authenticated bool Admin bool @@ -203,6 +215,8 @@ type AdminTeamLogosTemplateData struct { // AdminChallengesTemplateData for passing data to the admin challenges template type AdminChallengesTemplateData struct { Title string + Lang string + I18NJSON template.JS UUID string Authenticated bool Admin bool @@ -229,6 +243,8 @@ type AdminChallengeActivityEntry struct { // AdminActivityTemplateData for passing data to the admin activity template type AdminActivityTemplateData struct { Title string + Lang string + I18NJSON template.JS UUID string Authenticated bool Admin bool @@ -241,6 +257,8 @@ type AdminActivityTemplateData struct { // AdminChatTemplateData for passing data to the admin chat template type AdminChatTemplateData struct { Title string + Lang string + I18NJSON template.JS UUID string Authenticated bool Admin bool @@ -253,6 +271,8 @@ type AdminChatTemplateData struct { // AdminCountriesTemplateData for passing data to the admin countries template type AdminCountriesTemplateData struct { Title string + Lang string + I18NJSON template.JS UUID string Authenticated bool Admin bool diff --git a/backend/cmd/map/templates/admin/activity.html b/backend/cmd/map/templates/admin/activity.html index c6755ca..2d3f803 100644 --- a/backend/cmd/map/templates/admin/activity.html +++ b/backend/cmd/map/templates/admin/activity.html @@ -1,5 +1,5 @@ - + @@ -238,48 +238,48 @@ -

Game Admin

+

{{ T "admin.nav.game_admin" }}

- End Game - Pause Game + {{ T "admin.nav.end_game" }} + {{ T "admin.nav.pause_game" }}
-

Activity

+

{{ T "admin.activity.page_header" }}

- status_ - ready + {{ T "admin.status_label" }} + {{ T "admin.status_ready" }}
-

Activity Log

+

{{ T "admin.activity.log" }}

- +
@@ -329,6 +329,7 @@

Activity Log

+ diff --git a/backend/cmd/map/templates/admin/challenges.html b/backend/cmd/map/templates/admin/challenges.html index 12b06dd..0aa4124 100644 --- a/backend/cmd/map/templates/admin/challenges.html +++ b/backend/cmd/map/templates/admin/challenges.html @@ -1,5 +1,5 @@ - + @@ -126,29 +126,29 @@ -

Game Admin

+

{{ T "admin.nav.game_admin" }}

- End Game - Pause Game + {{ T "admin.nav.end_game" }} + {{ T "admin.nav.pause_game" }}
@@ -156,20 +156,20 @@

Game Admin

-

Challenges

+

{{ T "admin.nav.challenges" }}

- status_ - ready + {{ T "admin.status_label" }} + {{ T "admin.status_ready" }}
-

Challenge Management

+

{{ T "admin.challenges.page_header" }}

- +
@@ -208,23 +208,23 @@

Challenge Management

- +
- +
- +
- + {{ if $challengeCountryOptions }} @@ -250,41 +250,41 @@

Challenge Management

- +
- +
- +
- +
- +
- +
- +
- - + +
@@ -299,7 +299,7 @@

Challenge Management

value="true" {{ if .Active }}checked{{ end }} /> - + Challenge Management value="false" {{ if not .Active }}checked{{ end }} /> - +
@@ -349,7 +349,7 @@

Challenge Management

{{ end }} {{ else }}
- +
{{ end }} @@ -358,29 +358,29 @@

Challenge Management

-

Challenge Actions

+

{{ T "admin.challenges.actions" }}

- +
- - Export All + + {{ T "admin.challenges.export_all" }}
- +
- - + +
- +
- - + +
@@ -390,9 +390,9 @@

Challenge Actions

-

Challenge Categories

+

{{ T "admin.challenges.categories" }}

- +
@@ -432,7 +432,7 @@

Challenge Categories

{{ end }} {{ else }}
- +
{{ end }}
@@ -442,6 +442,7 @@

Challenge Categories

+ diff --git a/backend/cmd/map/templates/admin/chat.html b/backend/cmd/map/templates/admin/chat.html index 09f8ed1..9a66e91 100644 --- a/backend/cmd/map/templates/admin/chat.html +++ b/backend/cmd/map/templates/admin/chat.html @@ -1,5 +1,5 @@ - + @@ -123,46 +123,46 @@ -

Game Admin

+

{{ T "admin.nav.game_admin" }}

- End Game - Pause Game + {{ T "admin.nav.end_game" }} + {{ T "admin.nav.pause_game" }}
-

World Chat

+

{{ T "admin.chat.page_header" }}

- status_ - ready + {{ T "admin.status_label" }} + {{ T "admin.status_ready" }}
-

Chat Entries

+

{{ T "admin.chat.entries" }}

@@ -185,7 +185,7 @@

Chat Entries

- +
@@ -201,6 +201,7 @@

Chat Entries

+ diff --git a/backend/cmd/map/templates/admin/controls.html b/backend/cmd/map/templates/admin/controls.html index 90f5553..d56c70e 100644 --- a/backend/cmd/map/templates/admin/controls.html +++ b/backend/cmd/map/templates/admin/controls.html @@ -1,5 +1,5 @@ - + @@ -64,39 +64,39 @@ -

Game Admin

+

{{ T "admin.nav.game_admin" }}

- End Game - Pause Game + {{ T "admin.nav.end_game" }} + {{ T "admin.nav.pause_game" }}
-

Game Controls

+

{{ T "admin.controls.page_header" }}

- status_ - ready + {{ T "admin.status_label" }} + {{ T "admin.status_ready" }}
@@ -104,15 +104,15 @@

Game Controls

-

Game Actions

+

{{ T "admin.controls.game_actions" }}

- +
- - Export Full Game + + {{ T "admin.game.export_full" }}
@@ -122,29 +122,29 @@

Game Actions

-

Users Actions

+

{{ T "admin.controls.users_actions" }}

- +
- - Export Users + + {{ T "admin.users.export" }}
- +
- - + +
- +
- +
@@ -153,53 +153,53 @@

Users Actions

-

Teams Actions

+

{{ T "admin.controls.teams_actions" }}

- +
- +
- - Export logos + + {{ T "admin.logos.export" }}
- +
- - + +
- +
- - + +
- +
- - + +
- +
- - + +
@@ -208,30 +208,30 @@

Teams Actions

-

Challenges Actions

+

{{ T "admin.controls.challenges_actions" }}

- +
- - Export Challenges + + {{ T "admin.challenges.export_all" }}
- +
- - + +
- +
- - + +
@@ -240,22 +240,22 @@

Challenges Actions

-

Settings Actions

+

{{ T "admin.controls.settings_actions" }}

- +
- - Export Settings + + {{ T "admin.settings.export" }}
- +
- +
@@ -264,20 +264,20 @@

Settings Actions

-

Countries Actions

+

{{ T "admin.controls.countries_actions" }}

- +
- +
- +
@@ -287,6 +287,7 @@

Countries Actions

+ diff --git a/backend/cmd/map/templates/admin/countries.html b/backend/cmd/map/templates/admin/countries.html index e10ab5d..119d36a 100644 --- a/backend/cmd/map/templates/admin/countries.html +++ b/backend/cmd/map/templates/admin/countries.html @@ -1,5 +1,5 @@ - + @@ -108,29 +108,29 @@
-

Game Admin

+

{{ T "admin.nav.game_admin" }}

@@ -139,32 +139,32 @@

Game Admin

Countries

- status_ - ready + {{ T "admin.status_label" }} + {{ T "admin.status_ready" }}
-

Country Management

+

{{ T "admin.countries.page_header" }}

-

World Map (hover a country to jump to its row)

+

{{ T "admin.countries.world_map" }}

{{ if .Countries }} - - - - - - + + + + + + @@ -189,9 +189,9 @@

World Map (hover a country to jump to its row)

- + - +
@@ -207,6 +207,7 @@

World Map (hover a country to jump to its row)

+ diff --git a/backend/cmd/map/templates/admin/index.html b/backend/cmd/map/templates/admin/index.html index 5370976..08391f6 100644 --- a/backend/cmd/map/templates/admin/index.html +++ b/backend/cmd/map/templates/admin/index.html @@ -1,5 +1,5 @@ - + @@ -23,29 +23,29 @@ -

Game Admin

+

{{ T "admin.nav.game_admin" }}

@@ -53,15 +53,15 @@

Game Admin

-

Operations dashboard

+

{{ T "admin.dashboard.header" }}

- status_ - ready + {{ T "admin.status_label" }} + {{ T "admin.status_ready" }}
@@ -70,7 +70,7 @@

Operations dashboard

- Live game metrics + {{ T "admin.dashboard.live_metrics" }}
Event window 14:00-18:30 UTC @@ -80,27 +80,27 @@

Operations dashboard

- Game state + {{ T "admin.dashboard.kpi_game_state" }} Live 4h 17m remaining
- Teams online + {{ T "admin.dashboard.kpi_teams_online" }} 18 / 24 75% active in last 5m
- Flags per hour + {{ T "admin.dashboard.kpi_flags_hour" }} 142 +18% over baseline
- Solve coverage + {{ T "admin.dashboard.kpi_solve_coverage" }} 73% 34 of 46 challenges touched
- Platform pulse + {{ T "admin.dashboard.kpi_platform_pulse" }} p95 118ms API stable, DB nominal
@@ -111,10 +111,10 @@

Operations dashboard

- Competition -

Leaderboard preview

+ {{ T "admin.dashboard.kicker_competition" }} +

{{ T "admin.dashboard.leaderboard_preview" }}

- View board + {{ T "admin.dashboard.view_board" }}
@@ -147,8 +147,8 @@

Leaderboard preview

- Progress -

Capture velocity

+ {{ T "admin.dashboard.kicker_progress" }} +

{{ T "admin.dashboard.capture_velocity" }}

@@ -164,7 +164,7 @@

Capture velocity

- Last 10m + {{ T "admin.dashboard.last_10m" }} 27 solves
@@ -172,10 +172,10 @@

Capture velocity

- Challenges -

Challenge health

+ {{ T "admin.dashboard.kicker_challenges" }} +

{{ T "admin.dashboard.challenge_health" }}

- Manage + {{ T "admin.dashboard.manage" }}
@@ -199,8 +199,8 @@

Challenge health

- Platform -

Infrastructure status

+ {{ T "admin.dashboard.kicker_platform" }} +

{{ T "admin.dashboard.infrastructure_status" }}

@@ -214,8 +214,8 @@

Infrastructure status

- Operations -

Incident queue

+ {{ T "admin.dashboard.kicker_operations" }} +

{{ T "admin.dashboard.incident_queue" }}

@@ -230,8 +230,8 @@

Incident queue

- Timeline -

Recent game activity

+ {{ T "admin.dashboard.kicker_timeline" }} +

{{ T "admin.dashboard.recent_activity" }}

    @@ -246,15 +246,15 @@

    Recent game activity

    @@ -264,6 +264,7 @@

    Quick actions

+ diff --git a/backend/cmd/map/templates/admin/settings.html b/backend/cmd/map/templates/admin/settings.html index c14e254..238ef55 100644 --- a/backend/cmd/map/templates/admin/settings.html +++ b/backend/cmd/map/templates/admin/settings.html @@ -1,5 +1,5 @@ - + @@ -54,55 +54,55 @@
-

Game Admin

+

{{ T "admin.nav.game_admin" }}

-

Settings

+

{{ T "admin.settings.page_header" }}

- status_ - ready + {{ T "admin.status_label" }} + {{ T "admin.status_ready" }}
-

Settings Actions

+

{{ T "admin.settings.actions" }}

- +
- - Export Settings - + + {{ T "admin.settings.export" }} +
@@ -112,7 +112,7 @@

Settings Actions

-

Login Settings

+

{{ T "admin.settings.login" }}

@@ -122,9 +122,9 @@

Login Settings

- + - +
@@ -134,9 +134,9 @@

Login Settings

- + - +
@@ -146,7 +146,7 @@

Login Settings

-

Registration Settings

+

{{ T "admin.settings.registration" }}

@@ -156,9 +156,9 @@

Registration Settings

- + - +
@@ -168,9 +168,9 @@

Registration Settings

- + - +
@@ -180,9 +180,9 @@

Registration Settings

- + - +
@@ -210,7 +210,7 @@

Registration Settings

-

Scoring Settings

+

{{ T "admin.settings.scoring" }}

@@ -220,9 +220,9 @@

Scoring Settings

- + - +
@@ -232,9 +232,9 @@

Scoring Settings

- + - +
@@ -244,9 +244,9 @@

Scoring Settings

- + - +
@@ -256,7 +256,7 @@

Scoring Settings

-

Game Settings

+

{{ T "admin.settings.game" }}

@@ -266,9 +266,9 @@

Game Settings

- + - +
@@ -278,9 +278,9 @@

Game Settings

- + - +
@@ -304,7 +304,7 @@

Game Settings

-

Branding Settings

+

{{ T "admin.settings.branding" }}

@@ -328,7 +328,7 @@

Branding Settings

-

Localization Settings

+

{{ T "admin.settings.localization" }}

@@ -352,7 +352,7 @@

Localization Settings

-

Gameboard Settings

+

{{ T "admin.settings.gameboard" }}

@@ -362,9 +362,9 @@

Gameboard Settings

- + - +
@@ -381,6 +381,7 @@

Gameboard Settings

+ diff --git a/backend/cmd/map/templates/admin/team-logos.html b/backend/cmd/map/templates/admin/team-logos.html index 9144136..70b3a25 100644 --- a/backend/cmd/map/templates/admin/team-logos.html +++ b/backend/cmd/map/templates/admin/team-logos.html @@ -1,5 +1,5 @@ - + @@ -54,29 +54,29 @@
-

Game Admin

+

{{ T "admin.nav.game_admin" }}

@@ -84,39 +84,39 @@

Game Admin

-

Team logos

+

{{ T "admin.logos.page_header" }}

- status_ - ready + {{ T "admin.status_label" }} + {{ T "admin.status_ready" }}
-

Logo actions

+

{{ T "admin.logos.actions" }}

- +
- - Export logos + + {{ T "admin.logos.export" }}
- +
- - + +
- +
- +
@@ -127,15 +127,15 @@

Logo actions

{{ $uuid := .UUID }}
-

Logos {{ len .AllLogos }}

+

{{ T "admin.logos.all" }} {{ len .AllLogos }}

- - + +
-

Custom logos {{ len .CustomLogos }}

+

{{ T "admin.logos.custom" }} {{ len .CustomLogos }}

@@ -153,7 +153,7 @@

Custom logos {{ len .CustomLogos }} + aria-label="{{ T "admin.logos.edit_logo" }} {{ if .Name }}{{ .Name }}{{ else }}{{ T "admin.logos.unnamed" }}{{ end }}"> {{ if logoIsImage .Logo }} @@ -163,29 +163,29 @@

Custom logos {{ len .CustomLogos }} {{ end }} - {{ if .Name }}{{ .Name }}{{ else }}Unnamed{{ end }} + {{ if .Name }}{{ .Name }}{{ else }}{{ T "admin.logos.unnamed" }}{{ end }} - + {{ if .Enabled }}✅{{ else }}❌{{ end }} - {{ if .Enabled }}Enabled{{ else }}Not Enabled{{ end }} + {{ if .Enabled }}{{ T "admin.logos.enabled" }}{{ else }}{{ T "admin.logos.not_enabled" }}{{ end }} - - {{ if .Protected }}📦Platform{{ else }}🎨Custom{{ end }} + + {{ if .Protected }}📦{{ T "admin.logos.platform" }}{{ else }}🎨{{ T "admin.logos.custom_type" }}{{ end }} - + {{ if .Protected }}🔒{{ else }}🔓{{ end }} - {{ if .Protected }}Protected{{ else }}Not Protected{{ end }} + {{ if .Protected }}{{ T "admin.logos.protected" }}{{ else }}{{ T "admin.logos.not_protected" }}{{ end }} - + {{ if .Used }}🔥{{ else }}🆓{{ end }} - {{ if .Used }}Used{{ else }}Not Used{{ end }} + {{ if .Used }}{{ T "admin.logos.used" }}{{ else }}{{ T "admin.logos.not_used" }}{{ end }} {{ end }} {{ else }}
- +
{{ end }}

@@ -194,7 +194,7 @@

Custom logos {{ len .CustomLogos }}
-

Catalog {{ len .PlatformLogos }}

+

{{ T "admin.logos.catalog" }} {{ len .PlatformLogos }}

@@ -244,7 +244,7 @@

Catalog {{ len .PlatformLogos }} - +

{{ end }}
@@ -254,6 +254,7 @@

Catalog {{ len .PlatformLogos }}

+ diff --git a/backend/cmd/map/templates/admin/teams.html b/backend/cmd/map/templates/admin/teams.html index 5c403cf..17e663a 100644 --- a/backend/cmd/map/templates/admin/teams.html +++ b/backend/cmd/map/templates/admin/teams.html @@ -1,5 +1,5 @@ - + @@ -54,29 +54,29 @@
-

Game Admin

+

{{ T "admin.nav.game_admin" }}

@@ -84,23 +84,23 @@

Game Admin

-

Team Management

+

{{ T "admin.teams.page_header" }}

- - + +
- status_ - ready + {{ T "admin.status_label" }} + {{ T "admin.status_ready" }}
@@ -123,10 +123,10 @@

- + - +
@@ -135,13 +135,13 @@

- +
- + {{ $members := index $.TeamMembers .ID }}
- +
@@ -167,38 +167,38 @@

- - + +
- + {{ $teamLogo := .Logo }}
- + - + - +
- + - + - +
@@ -206,9 +206,9 @@

- EDIT - - + {{ T "admin.edit" }} + +
@@ -216,10 +216,10 @@

{{ else }}
-

Teams

+

{{ T "admin.teams.no_found_title" }}

- +
{{ end }} @@ -228,35 +228,35 @@

Teams

-

Team Actions

+

{{ T "admin.teams.actions" }}

- +
- - Export teams + + {{ T "admin.teams.export" }}
- +
- - + +
- +
- - + +
- +
- +
@@ -266,6 +266,7 @@

Team Actions

+ diff --git a/backend/cmd/map/templates/admin/users.html b/backend/cmd/map/templates/admin/users.html index 586e72c..3d8527e 100644 --- a/backend/cmd/map/templates/admin/users.html +++ b/backend/cmd/map/templates/admin/users.html @@ -1,5 +1,5 @@ - + @@ -54,29 +54,29 @@

-

Game Admin

+

{{ T "admin.nav.game_admin" }}

@@ -84,21 +84,21 @@

Game Admin

-

User Management

+

{{ T "admin.users.page_header" }}

- - + +
- status_ - ready + {{ T "admin.status_label" }} + {{ T "admin.status_ready" }}
CountryCountry CodeAssignedChallengeSVGActive{{ T "admin.countries.th_country" }}{{ T "admin.countries.th_code" }}{{ T "admin.countries.th_assigned" }}{{ T "admin.countries.th_challenge" }}{{ T "admin.countries.th_svg" }}{{ T "admin.countries.th_active" }}