badges improvements

This commit is contained in:
2026-08-23 20:31:44 -07:00
parent ac2e61731c
commit e3fea18ab2
9 changed files with 366 additions and 76 deletions
@@ -0,0 +1,36 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
// Dedupe before adding the unique index: keep the best row per
// (player, badge) — earned first, then highest progress, then newest.
app.db().newQuery(`
DELETE FROM badge_progress
WHERE id NOT IN (
SELECT id FROM (
SELECT id, ROW_NUMBER() OVER (
PARTITION BY player, badge
ORDER BY earned DESC, progress DESC, updated DESC
) AS rn
FROM badge_progress
)
WHERE rn = 1
)
`).execute()
const collection = app.findCollectionByNameOrId("pbc_1063824264")
unmarshal({
"indexes": [
"CREATE UNIQUE INDEX `idx_badge_progress_player_badge` ON `badge_progress` (`player`, `badge`)"
]
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_1063824264")
unmarshal({
"indexes": []
}, collection)
return app.save(collection)
})
+51 -8
View File
@@ -1,5 +1,5 @@
import { List } from "@mantine/core"; import { List, Text } from "@mantine/core";
import { useLingui } from "@lingui/react/macro"; import { Plural, Trans, useLingui } from "@lingui/react/macro";
import ListLink from "@/components/list-link"; import ListLink from "@/components/list-link";
import { import {
DatabaseIcon, DatabaseIcon,
@@ -10,19 +10,38 @@ import {
ListIcon, ListIcon,
} from "@phosphor-icons/react"; } from "@phosphor-icons/react";
import ListButton from "@/components/list-button"; import ListButton from "@/components/list-button";
import { migrateBadgeProgress } from "@/features/badges/server"; import { getBadgeMigrationStatus, migrateBadgeProgress } from "@/features/badges/server";
import { useState } from "react"; import { useServerQuery } from "@/lib/tanstack-query/hooks";
import toast from "@/lib/sonner";
const AdminPage = () => { const AdminPage = () => {
const { t } = useLingui(); const { t } = useLingui();
const [isMigrating, setIsMigrating] = useState(false);
const { data: migrationStatus, refetch: refetchMigrationStatus } = useServerQuery({
queryKey: ["admin", "badge-migration-status"],
queryFn: () => getBadgeMigrationStatus(),
options: {
refetchInterval: (query) =>
query.state.data?.state === "running" ? 2000 : false,
},
});
const isMigrating = migrationStatus?.state === "running";
const handleMigrateBadges = async () => { const handleMigrateBadges = async () => {
if (isMigrating) return; if (isMigrating) return;
setIsMigrating(true); const result = await migrateBadgeProgress();
await migrateBadgeProgress(); if (result.success) {
setIsMigrating(false); toast.success(
result.data.started
? t`Badge recalculation started in the background`
: t`Badge recalculation is already running`
);
} else {
toast.error(t`Failed to start badge recalculation`);
}
await refetchMigrationStatus();
}; };
return ( return (
@@ -43,6 +62,30 @@ const AdminPage = () => {
onClick={handleMigrateBadges} onClick={handleMigrateBadges}
loading={isMigrating} loading={isMigrating}
/> />
{migrationStatus?.state === "running" && (
<Text size="sm" c="dimmed" px="md" py="xs">
<Trans>
Recalculating badges in the background ({migrationStatus.playersProcessed}/{migrationStatus.totalPlayers})
</Trans>
</Text>
)}
{migrationStatus?.state === "completed" && (
<Text size="sm" c="dimmed" px="md" py="xs">
<Trans>
Last recalculation:{" "}
<Plural
value={migrationStatus.result.totalBadgesEarned}
one="# badge earned"
other="# badges earned"
/>
</Trans>
</Text>
)}
{migrationStatus?.state === "failed" && (
<Text size="sm" c="red" px="md" py="xs">
<Trans>Badge recalculation failed. Check the server logs.</Trans>
</Text>
)}
<ListLink <ListLink
label={t`Activities`} label={t`Activities`}
Icon={ListIcon} Icon={ListIcon}
+55
View File
@@ -0,0 +1,55 @@
import { pbAdmin } from "@/lib/pocketbase/client";
import { emitServerEvent } from "@/lib/events/emitter";
import { logger } from "@/lib/logger";
export type BadgeMigrationResult = {
playersProcessed: number;
totalBadgesEarned: number;
created: number;
updated: number;
deleted: number;
averageBadgesPerPlayer: string;
};
export type BadgeMigrationStatus =
| { state: "idle" }
| { state: "running"; startedAt: string; playersProcessed: number; totalPlayers: number }
| { state: "completed"; startedAt: string; finishedAt: string; result: BadgeMigrationResult }
| { state: "failed"; startedAt: string; finishedAt: string; error: string };
// In-process job state — valid only because the app runs a single replica.
let status: BadgeMigrationStatus = { state: "idle" };
export const getBadgeMigrationJobStatus = (): BadgeMigrationStatus => status;
export const startBadgeMigration = (): { started: boolean; status: BadgeMigrationStatus } => {
if (status.state === "running") {
return { started: false, status };
}
const startedAt = new Date().toISOString();
status = { state: "running", startedAt, playersProcessed: 0, totalPlayers: 0 };
void pbAdmin
.migrateBadgeProgress((playersProcessed, totalPlayers) => {
if (status.state === "running") {
status = { ...status, playersProcessed, totalPlayers };
}
})
.then((result) => {
status = { state: "completed", startedAt, finishedAt: new Date().toISOString(), result };
logger.info("Badge migration completed", result);
emitServerEvent({ type: "badge" });
})
.catch((error) => {
status = {
state: "failed",
startedAt,
finishedAt: new Date().toISOString(),
error: error instanceof Error ? error.message : String(error),
};
logger.error("Badge migration failed", error);
});
return { started: true, status };
};
+8 -5
View File
@@ -4,6 +4,7 @@ import { createServerFn } from "@tanstack/react-start";
import { pbAdmin } from "@/lib/pocketbase/client"; import { pbAdmin } from "@/lib/pocketbase/client";
import { z } from "zod"; import { z } from "zod";
import { emitServerEvent } from "@/lib/events/emitter"; import { emitServerEvent } from "@/lib/events/emitter";
import { getBadgeMigrationJobStatus, startBadgeMigration } from "./migration.server";
export const getPlayerBadges = createServerFn() export const getPlayerBadges = createServerFn()
.validator(z.string()) .validator(z.string())
@@ -15,11 +16,13 @@ export const getPlayerBadges = createServerFn()
export const migrateBadgeProgress = createServerFn() export const migrateBadgeProgress = createServerFn()
.middleware([superTokensAdminFunctionMiddleware]) .middleware([superTokensAdminFunctionMiddleware])
.handler(async () => .handler(async () =>
toServerResult(async () => { toServerResult(async () => startBadgeMigration())
const result = await pbAdmin.migrateBadgeProgress(); );
emitServerEvent({ type: "badge" });
return result; export const getBadgeMigrationStatus = createServerFn()
}) .middleware([superTokensAdminFunctionMiddleware])
.handler(async () =>
toServerResult(async () => getBadgeMigrationJobStatus())
); );
export const getAllBadges = createServerFn() export const getAllBadges = createServerFn()
+84 -39
View File
@@ -70,16 +70,6 @@ export function createBadgesService(pb: PocketBase) {
return true; return true;
}, },
async clearAllBadgeProgress(): Promise<number> {
const existingProgress = await pb.collection("badge_progress").getFullList({
filter: 'badge.type != "manual"',
});
for (const progress of existingProgress) {
await pb.collection("badge_progress").delete(progress.id);
}
return existingProgress.length;
},
async calculateBadgeProgress(playerId: string, badge: Badge): Promise<number> { async calculateBadgeProgress(playerId: string, badge: Badge): Promise<number> {
if (badge.type === "manual") { if (badge.type === "manual") {
return 0; return 0;
@@ -651,27 +641,36 @@ export function createBadgesService(pb: PocketBase) {
return transformBadgeProgress(created); return transformBadgeProgress(created);
}, },
async migrateBadgeProgress(): Promise<{ async migrateBadgeProgress(
success: boolean; onProgress?: (playersProcessed: number, totalPlayers: number) => void
): Promise<{
playersProcessed: number; playersProcessed: number;
progressRecordsCreated: number;
totalBadgesEarned: number; totalBadgesEarned: number;
created: number;
updated: number;
deleted: number;
averageBadgesPerPlayer: string; averageBadgesPerPlayer: string;
}> { }> {
await this.clearAllBadgeProgress();
const badges = await this.listBadges(); const badges = await this.listBadges();
const autoBadges = badges.filter((badge) => badge.type !== "manual");
const players = await pb.collection("players").getFullList();
const allPlayers = await pb.collection("players").getFullList(); // Phase 1: read-only calculation. Existing badge_progress rows stay
const uniquePlayers = new Set(allPlayers.map((p: any) => p.id)); // untouched until the full desired state is known.
const desired = new Map<string, {
badge: string;
player: string;
progress: number;
earned: boolean;
}>();
const failedKeys = new Set<string>();
let playersProcessed = 0;
let totalProgressRecords = 0; for (const player of players) {
let totalBadgesEarned = 0; for (const badge of autoBadges) {
const key = `${player.id}|${badge.id}`;
for (const playerId of uniquePlayers) {
for (const badge of badges) {
try { try {
const progress = await this.calculateBadgeProgress(playerId, badge); const progress = await this.calculateBadgeProgress(player.id, badge);
const target = this.getTargetProgress(badge); const target = this.getTargetProgress(badge);
const isPlacementBadge = badge.criteria.placement !== undefined; const isPlacementBadge = badge.criteria.placement !== undefined;
@@ -681,31 +680,77 @@ export function createBadgesService(pb: PocketBase) {
: progress === target; : progress === target;
if (progress > 0 || earned) { if (progress > 0 || earned) {
await this.createBadgeProgress({ desired.set(key, {
badge: badge.id, badge: badge.id,
player: playerId, player: player.id,
progress: progress, progress,
earned: earned, earned,
}); });
totalProgressRecords++;
if (earned) {
totalBadgesEarned++;
}
} }
} catch (error: any) { } catch (error: any) {
console.error(`Error processing badge "${badge.name}" for player ${playerId}:`, error.message); // Keep whatever row already exists when a calculation fails.
failedKeys.add(key);
console.error(`Error processing badge "${badge.name}" for player ${player.id}:`, error.message);
} }
} }
playersProcessed++;
onProgress?.(playersProcessed, players.length);
}
// Phase 2: apply as a diff — update changed rows, create missing ones,
// delete stale/duplicate ones. No window where all badges are gone.
const existing = await pb.collection("badge_progress").getFullList({
filter: 'badge.type != "manual"',
});
const existingByKey = new Map<string, { id: string; progress: number; earned: boolean }>();
const toDelete: string[] = [];
for (const row of existing) {
const key = `${row.player}|${row.badge}`;
if (existingByKey.has(key)) {
toDelete.push(row.id);
continue;
}
existingByKey.set(key, { id: row.id, progress: row.progress, earned: row.earned });
if (!desired.has(key) && !failedKeys.has(key)) {
toDelete.push(row.id);
}
}
let created = 0;
let updated = 0;
let totalBadgesEarned = 0;
for (const [key, want] of desired) {
if (want.earned) {
totalBadgesEarned++;
}
const row = existingByKey.get(key);
if (!row) {
await this.createBadgeProgress(want);
created++;
} else if (row.progress !== want.progress || row.earned !== want.earned) {
await this.updateBadgeProgress(row.id, {
progress: want.progress,
earned: want.earned,
});
updated++;
}
}
for (const id of toDelete) {
await this.deleteBadgeProgress(id);
} }
return { return {
success: true, playersProcessed: players.length,
playersProcessed: uniquePlayers.size, totalBadgesEarned,
progressRecordsCreated: totalProgressRecords, created,
totalBadgesEarned: totalBadgesEarned, updated,
averageBadgesPerPlayer: (totalBadgesEarned / uniquePlayers.size).toFixed(2), deleted: toDelete.length,
averageBadgesPerPlayer: players.length
? (totalBadgesEarned / players.length).toFixed(2)
: "0",
}; };
}, },
}; };
+33 -6
View File
@@ -384,7 +384,7 @@ msgid "Active device: {0}"
msgstr "Aktives Gerät: {0}" msgstr "Aktives Gerät: {0}"
#: src/app/routes/_authed/admin/activities.tsx:21 #: src/app/routes/_authed/admin/activities.tsx:21
#: src/features/admin/components/admin-page.tsx:47 #: src/features/admin/components/admin-page.tsx:90
msgid "Activities" msgid "Activities"
msgstr "Aktivitäten" msgstr "Aktivitäten"
@@ -520,7 +520,7 @@ msgstr "Ø Sieg-Marge"
msgid "Award Badge" msgid "Award Badge"
msgstr "Abzeichen verleihen" msgstr "Abzeichen verleihen"
#: src/features/admin/components/admin-page.tsx:36 #: src/features/admin/components/admin-page.tsx:55
msgid "Award Badges" msgid "Award Badges"
msgstr "Abzeichen verleihen" msgstr "Abzeichen verleihen"
@@ -554,6 +554,18 @@ msgstr "Zurück"
msgid "Badge awarded to {playerName}" msgid "Badge awarded to {playerName}"
msgstr "Abzeichen an {playerName} verliehen" msgstr "Abzeichen an {playerName} verliehen"
#: src/features/admin/components/admin-page.tsx:86
msgid "Badge recalculation failed. Check the server logs."
msgstr "Abzeichen-Neuberechnung fehlgeschlagen. Prüfe die Server-Logs."
#: src/features/admin/components/admin-page.tsx:39
msgid "Badge recalculation is already running"
msgstr "Abzeichen-Neuberechnung läuft bereits"
#: src/features/admin/components/admin-page.tsx:38
msgid "Badge recalculation started in the background"
msgstr "Abzeichen-Neuberechnung im Hintergrund gestartet"
#: src/features/admin/components/award-badges.tsx:70 #: src/features/admin/components/award-badges.tsx:70
msgid "Badge Type" msgid "Badge Type"
msgstr "Abzeichentyp" msgstr "Abzeichentyp"
@@ -581,7 +593,7 @@ msgid "Bracket"
msgstr "Turnierbaum" msgstr "Turnierbaum"
#: src/app/routes/_authed/admin/preview.tsx:13 #: src/app/routes/_authed/admin/preview.tsx:13
#: src/features/admin/components/admin-page.tsx:59 #: src/features/admin/components/admin-page.tsx:102
msgid "Bracket Preview" msgid "Bracket Preview"
msgstr "Turnierbaum-Vorschau" msgstr "Turnierbaum-Vorschau"
@@ -1060,6 +1072,10 @@ msgstr "Aktives Gerät konnte nicht festgelegt werden"
msgid "Failed to set volume" msgid "Failed to set volume"
msgstr "Lautstärke konnte nicht festgelegt werden" msgstr "Lautstärke konnte nicht festgelegt werden"
#: src/features/admin/components/admin-page.tsx:42
msgid "Failed to start badge recalculation"
msgstr "Abzeichen-Neuberechnung konnte nicht gestartet werden"
#: src/features/teams/components/team-form/index.tsx:105 #: src/features/teams/components/team-form/index.tsx:105
msgid "Failed to update team" msgid "Failed to update team"
msgstr "Team konnte nicht aktualisiert werden" msgstr "Team konnte nicht aktualisiert werden"
@@ -1322,6 +1338,11 @@ msgstr "Der Nachname muss 2-20 Zeichen lang sein und darf nur Buchstaben enthalt
msgid "Last name must be 3-20 characters long and contain only letters" msgid "Last name must be 3-20 characters long and contain only letters"
msgstr "Der Nachname muss 3-20 Zeichen lang sein und darf nur Buchstaben enthalten" msgstr "Der Nachname muss 3-20 Zeichen lang sein und darf nur Buchstaben enthalten"
#. placeholder {0}: migrationStatus.result.totalBadgesEarned
#: src/features/admin/components/admin-page.tsx:74
msgid "Last recalculation: {0, plural, one {# badge earned} other {# badges earned}}"
msgstr "Letzte Neuberechnung: {0, plural, one {# Abzeichen verdient} other {# Abzeichen verdient}}"
#: src/features/settings/components/color-scheme-picker.tsx:43 #: src/features/settings/components/color-scheme-picker.tsx:43
msgid "Light" msgid "Light"
msgstr "Hell" msgstr "Hell"
@@ -1410,7 +1431,7 @@ msgid "Manage Team Songs/Logos"
msgstr "Team-Songs/Logos verwalten" msgstr "Team-Songs/Logos verwalten"
#: src/app/routes/_authed/admin/tournaments/index.tsx:17 #: src/app/routes/_authed/admin/tournaments/index.tsx:17
#: src/features/admin/components/admin-page.tsx:31 #: src/features/admin/components/admin-page.tsx:50
msgid "Manage Tournaments" msgid "Manage Tournaments"
msgstr "Turniere verwalten" msgstr "Turniere verwalten"
@@ -1489,7 +1510,7 @@ msgstr "Matchup-Details"
msgid "Me" msgid "Me"
msgstr "Ich" msgstr "Ich"
#: src/features/admin/components/admin-page.tsx:41 #: src/features/admin/components/admin-page.tsx:60
msgid "Migrate Badge Progress" msgid "Migrate Badge Progress"
msgstr "Abzeichen-Fortschritt migrieren" msgstr "Abzeichen-Fortschritt migrieren"
@@ -1729,7 +1750,7 @@ msgstr "Sobald du angemeldet bist, kannst du andere Free Agents und ihre Telefon
msgid "One time code" msgid "One time code"
msgstr "Einmalcode" msgstr "Einmalcode"
#: src/features/admin/components/admin-page.tsx:52 #: src/features/admin/components/admin-page.tsx:95
msgid "Open Pocketbase" msgid "Open Pocketbase"
msgstr "Pocketbase öffnen" msgstr "Pocketbase öffnen"
@@ -1927,6 +1948,12 @@ msgstr "Neu würfeln"
msgid "Reactions" msgid "Reactions"
msgstr "Reaktionen" msgstr "Reaktionen"
#. placeholder {0}: migrationStatus.playersProcessed
#. placeholder {1}: migrationStatus.totalPlayers
#: src/features/admin/components/admin-page.tsx:67
msgid "Recalculating badges in the background ({0}/{1})"
msgstr "Abzeichen werden im Hintergrund neu berechnet ({0}/{1})"
#: src/features/tournaments/components/started-tournament/sections/recent-results.tsx:34 #: src/features/tournaments/components/started-tournament/sections/recent-results.tsx:34
msgid "Recently finished" msgid "Recently finished"
msgstr "Kürzlich beendet" msgstr "Kürzlich beendet"
+33 -6
View File
@@ -384,7 +384,7 @@ msgid "Active device: {0}"
msgstr "Active device: {0}" msgstr "Active device: {0}"
#: src/app/routes/_authed/admin/activities.tsx:21 #: src/app/routes/_authed/admin/activities.tsx:21
#: src/features/admin/components/admin-page.tsx:47 #: src/features/admin/components/admin-page.tsx:90
msgid "Activities" msgid "Activities"
msgstr "Activities" msgstr "Activities"
@@ -520,7 +520,7 @@ msgstr "Avg Win Margin"
msgid "Award Badge" msgid "Award Badge"
msgstr "Award Badge" msgstr "Award Badge"
#: src/features/admin/components/admin-page.tsx:36 #: src/features/admin/components/admin-page.tsx:55
msgid "Award Badges" msgid "Award Badges"
msgstr "Award Badges" msgstr "Award Badges"
@@ -554,6 +554,18 @@ msgstr "Back"
msgid "Badge awarded to {playerName}" msgid "Badge awarded to {playerName}"
msgstr "Badge awarded to {playerName}" msgstr "Badge awarded to {playerName}"
#: src/features/admin/components/admin-page.tsx:86
msgid "Badge recalculation failed. Check the server logs."
msgstr "Badge recalculation failed. Check the server logs."
#: src/features/admin/components/admin-page.tsx:39
msgid "Badge recalculation is already running"
msgstr "Badge recalculation is already running"
#: src/features/admin/components/admin-page.tsx:38
msgid "Badge recalculation started in the background"
msgstr "Badge recalculation started in the background"
#: src/features/admin/components/award-badges.tsx:70 #: src/features/admin/components/award-badges.tsx:70
msgid "Badge Type" msgid "Badge Type"
msgstr "Badge Type" msgstr "Badge Type"
@@ -581,7 +593,7 @@ msgid "Bracket"
msgstr "Bracket" msgstr "Bracket"
#: src/app/routes/_authed/admin/preview.tsx:13 #: src/app/routes/_authed/admin/preview.tsx:13
#: src/features/admin/components/admin-page.tsx:59 #: src/features/admin/components/admin-page.tsx:102
msgid "Bracket Preview" msgid "Bracket Preview"
msgstr "Bracket Preview" msgstr "Bracket Preview"
@@ -1060,6 +1072,10 @@ msgstr "Failed to set active device"
msgid "Failed to set volume" msgid "Failed to set volume"
msgstr "Failed to set volume" msgstr "Failed to set volume"
#: src/features/admin/components/admin-page.tsx:42
msgid "Failed to start badge recalculation"
msgstr "Failed to start badge recalculation"
#: src/features/teams/components/team-form/index.tsx:105 #: src/features/teams/components/team-form/index.tsx:105
msgid "Failed to update team" msgid "Failed to update team"
msgstr "Failed to update team" msgstr "Failed to update team"
@@ -1322,6 +1338,11 @@ msgstr "Last name must be 2-20 characters long and contain only letters"
msgid "Last name must be 3-20 characters long and contain only letters" msgid "Last name must be 3-20 characters long and contain only letters"
msgstr "Last name must be 3-20 characters long and contain only letters" msgstr "Last name must be 3-20 characters long and contain only letters"
#. placeholder {0}: migrationStatus.result.totalBadgesEarned
#: src/features/admin/components/admin-page.tsx:74
msgid "Last recalculation: {0, plural, one {# badge earned} other {# badges earned}}"
msgstr "Last recalculation: {0, plural, one {# badge earned} other {# badges earned}}"
#: src/features/settings/components/color-scheme-picker.tsx:43 #: src/features/settings/components/color-scheme-picker.tsx:43
msgid "Light" msgid "Light"
msgstr "Light" msgstr "Light"
@@ -1410,7 +1431,7 @@ msgid "Manage Team Songs/Logos"
msgstr "Manage Team Songs/Logos" msgstr "Manage Team Songs/Logos"
#: src/app/routes/_authed/admin/tournaments/index.tsx:17 #: src/app/routes/_authed/admin/tournaments/index.tsx:17
#: src/features/admin/components/admin-page.tsx:31 #: src/features/admin/components/admin-page.tsx:50
msgid "Manage Tournaments" msgid "Manage Tournaments"
msgstr "Manage Tournaments" msgstr "Manage Tournaments"
@@ -1489,7 +1510,7 @@ msgstr "Matchup details"
msgid "Me" msgid "Me"
msgstr "Me" msgstr "Me"
#: src/features/admin/components/admin-page.tsx:41 #: src/features/admin/components/admin-page.tsx:60
msgid "Migrate Badge Progress" msgid "Migrate Badge Progress"
msgstr "Migrate Badge Progress" msgstr "Migrate Badge Progress"
@@ -1729,7 +1750,7 @@ msgstr "Once enrolled, you can view other free agents and their phone number in
msgid "One time code" msgid "One time code"
msgstr "One time code" msgstr "One time code"
#: src/features/admin/components/admin-page.tsx:52 #: src/features/admin/components/admin-page.tsx:95
msgid "Open Pocketbase" msgid "Open Pocketbase"
msgstr "Open Pocketbase" msgstr "Open Pocketbase"
@@ -1927,6 +1948,12 @@ msgstr "Re-roll"
msgid "Reactions" msgid "Reactions"
msgstr "Reactions" msgstr "Reactions"
#. placeholder {0}: migrationStatus.playersProcessed
#. placeholder {1}: migrationStatus.totalPlayers
#: src/features/admin/components/admin-page.tsx:67
msgid "Recalculating badges in the background ({0}/{1})"
msgstr "Recalculating badges in the background ({0}/{1})"
#: src/features/tournaments/components/started-tournament/sections/recent-results.tsx:34 #: src/features/tournaments/components/started-tournament/sections/recent-results.tsx:34
msgid "Recently finished" msgid "Recently finished"
msgstr "Recently finished" msgstr "Recently finished"
+33 -6
View File
@@ -384,7 +384,7 @@ msgid "Active device: {0}"
msgstr "Dispositivo activo: {0}" msgstr "Dispositivo activo: {0}"
#: src/app/routes/_authed/admin/activities.tsx:21 #: src/app/routes/_authed/admin/activities.tsx:21
#: src/features/admin/components/admin-page.tsx:47 #: src/features/admin/components/admin-page.tsx:90
msgid "Activities" msgid "Activities"
msgstr "Actividades" msgstr "Actividades"
@@ -520,7 +520,7 @@ msgstr "Prom. Margen de Victoria"
msgid "Award Badge" msgid "Award Badge"
msgstr "Otorgar insignia" msgstr "Otorgar insignia"
#: src/features/admin/components/admin-page.tsx:36 #: src/features/admin/components/admin-page.tsx:55
msgid "Award Badges" msgid "Award Badges"
msgstr "Otorgar insignias" msgstr "Otorgar insignias"
@@ -554,6 +554,18 @@ msgstr "Atrás"
msgid "Badge awarded to {playerName}" msgid "Badge awarded to {playerName}"
msgstr "Insignia otorgada a {playerName}" msgstr "Insignia otorgada a {playerName}"
#: src/features/admin/components/admin-page.tsx:86
msgid "Badge recalculation failed. Check the server logs."
msgstr "Falló el recálculo de insignias. Revisa los registros del servidor."
#: src/features/admin/components/admin-page.tsx:39
msgid "Badge recalculation is already running"
msgstr "El recálculo de insignias ya está en curso"
#: src/features/admin/components/admin-page.tsx:38
msgid "Badge recalculation started in the background"
msgstr "Recálculo de insignias iniciado en segundo plano"
#: src/features/admin/components/award-badges.tsx:70 #: src/features/admin/components/award-badges.tsx:70
msgid "Badge Type" msgid "Badge Type"
msgstr "Tipo de insignia" msgstr "Tipo de insignia"
@@ -581,7 +593,7 @@ msgid "Bracket"
msgstr "Bracket" msgstr "Bracket"
#: src/app/routes/_authed/admin/preview.tsx:13 #: src/app/routes/_authed/admin/preview.tsx:13
#: src/features/admin/components/admin-page.tsx:59 #: src/features/admin/components/admin-page.tsx:102
msgid "Bracket Preview" msgid "Bracket Preview"
msgstr "Vista previa del bracket" msgstr "Vista previa del bracket"
@@ -1060,6 +1072,10 @@ msgstr "No se pudo establecer el dispositivo activo"
msgid "Failed to set volume" msgid "Failed to set volume"
msgstr "No se pudo ajustar el volumen" msgstr "No se pudo ajustar el volumen"
#: src/features/admin/components/admin-page.tsx:42
msgid "Failed to start badge recalculation"
msgstr "No se pudo iniciar el recálculo de insignias"
#: src/features/teams/components/team-form/index.tsx:105 #: src/features/teams/components/team-form/index.tsx:105
msgid "Failed to update team" msgid "Failed to update team"
msgstr "No se pudo actualizar el equipo" msgstr "No se pudo actualizar el equipo"
@@ -1322,6 +1338,11 @@ msgstr "El apellido debe tener entre 2 y 20 caracteres y contener solo letras"
msgid "Last name must be 3-20 characters long and contain only letters" msgid "Last name must be 3-20 characters long and contain only letters"
msgstr "El apellido debe tener entre 3 y 20 caracteres y contener solo letras" msgstr "El apellido debe tener entre 3 y 20 caracteres y contener solo letras"
#. placeholder {0}: migrationStatus.result.totalBadgesEarned
#: src/features/admin/components/admin-page.tsx:74
msgid "Last recalculation: {0, plural, one {# badge earned} other {# badges earned}}"
msgstr "Último recálculo: {0, plural, one {# insignia obtenida} other {# insignias obtenidas}}"
#: src/features/settings/components/color-scheme-picker.tsx:43 #: src/features/settings/components/color-scheme-picker.tsx:43
msgid "Light" msgid "Light"
msgstr "Claro" msgstr "Claro"
@@ -1410,7 +1431,7 @@ msgid "Manage Team Songs/Logos"
msgstr "Administrar canciones/logos del equipo" msgstr "Administrar canciones/logos del equipo"
#: src/app/routes/_authed/admin/tournaments/index.tsx:17 #: src/app/routes/_authed/admin/tournaments/index.tsx:17
#: src/features/admin/components/admin-page.tsx:31 #: src/features/admin/components/admin-page.tsx:50
msgid "Manage Tournaments" msgid "Manage Tournaments"
msgstr "Administrar torneos" msgstr "Administrar torneos"
@@ -1489,7 +1510,7 @@ msgstr "Detalles del enfrentamiento"
msgid "Me" msgid "Me"
msgstr "Yo" msgstr "Yo"
#: src/features/admin/components/admin-page.tsx:41 #: src/features/admin/components/admin-page.tsx:60
msgid "Migrate Badge Progress" msgid "Migrate Badge Progress"
msgstr "Migrar progreso de insignias" msgstr "Migrar progreso de insignias"
@@ -1729,7 +1750,7 @@ msgstr "Una vez inscrito, puedes ver a otros agentes libres y su número de tel
msgid "One time code" msgid "One time code"
msgstr "Código de un solo uso" msgstr "Código de un solo uso"
#: src/features/admin/components/admin-page.tsx:52 #: src/features/admin/components/admin-page.tsx:95
msgid "Open Pocketbase" msgid "Open Pocketbase"
msgstr "Abrir Pocketbase" msgstr "Abrir Pocketbase"
@@ -1927,6 +1948,12 @@ msgstr "Volver a sortear"
msgid "Reactions" msgid "Reactions"
msgstr "Reacciones" msgstr "Reacciones"
#. placeholder {0}: migrationStatus.playersProcessed
#. placeholder {1}: migrationStatus.totalPlayers
#: src/features/admin/components/admin-page.tsx:67
msgid "Recalculating badges in the background ({0}/{1})"
msgstr "Recalculando insignias en segundo plano ({0}/{1})"
#: src/features/tournaments/components/started-tournament/sections/recent-results.tsx:34 #: src/features/tournaments/components/started-tournament/sections/recent-results.tsx:34
msgid "Recently finished" msgid "Recently finished"
msgstr "Recién terminados" msgstr "Recién terminados"
+33 -6
View File
@@ -384,7 +384,7 @@ msgid "Active device: {0}"
msgstr "アクティブなデバイス: {0}" msgstr "アクティブなデバイス: {0}"
#: src/app/routes/_authed/admin/activities.tsx:21 #: src/app/routes/_authed/admin/activities.tsx:21
#: src/features/admin/components/admin-page.tsx:47 #: src/features/admin/components/admin-page.tsx:90
msgid "Activities" msgid "Activities"
msgstr "アクティビティ" msgstr "アクティビティ"
@@ -520,7 +520,7 @@ msgstr "平均勝差"
msgid "Award Badge" msgid "Award Badge"
msgstr "バッジを付与" msgstr "バッジを付与"
#: src/features/admin/components/admin-page.tsx:36 #: src/features/admin/components/admin-page.tsx:55
msgid "Award Badges" msgid "Award Badges"
msgstr "バッジを付与" msgstr "バッジを付与"
@@ -554,6 +554,18 @@ msgstr "戻る"
msgid "Badge awarded to {playerName}" msgid "Badge awarded to {playerName}"
msgstr "{playerName}にバッジを付与しました" msgstr "{playerName}にバッジを付与しました"
#: src/features/admin/components/admin-page.tsx:86
msgid "Badge recalculation failed. Check the server logs."
msgstr "バッジの再計算に失敗しました。サーバーログを確認してください。"
#: src/features/admin/components/admin-page.tsx:39
msgid "Badge recalculation is already running"
msgstr "バッジの再計算はすでに実行中です"
#: src/features/admin/components/admin-page.tsx:38
msgid "Badge recalculation started in the background"
msgstr "バックグラウンドでバッジの再計算を開始しました"
#: src/features/admin/components/award-badges.tsx:70 #: src/features/admin/components/award-badges.tsx:70
msgid "Badge Type" msgid "Badge Type"
msgstr "バッジの種類" msgstr "バッジの種類"
@@ -581,7 +593,7 @@ msgid "Bracket"
msgstr "ブラケット" msgstr "ブラケット"
#: src/app/routes/_authed/admin/preview.tsx:13 #: src/app/routes/_authed/admin/preview.tsx:13
#: src/features/admin/components/admin-page.tsx:59 #: src/features/admin/components/admin-page.tsx:102
msgid "Bracket Preview" msgid "Bracket Preview"
msgstr "ブラケットプレビュー" msgstr "ブラケットプレビュー"
@@ -1060,6 +1072,10 @@ msgstr "アクティブなデバイスの設定に失敗しました"
msgid "Failed to set volume" msgid "Failed to set volume"
msgstr "音量の設定に失敗しました" msgstr "音量の設定に失敗しました"
#: src/features/admin/components/admin-page.tsx:42
msgid "Failed to start badge recalculation"
msgstr "バッジの再計算を開始できませんでした"
#: src/features/teams/components/team-form/index.tsx:105 #: src/features/teams/components/team-form/index.tsx:105
msgid "Failed to update team" msgid "Failed to update team"
msgstr "チームの更新に失敗しました" msgstr "チームの更新に失敗しました"
@@ -1322,6 +1338,11 @@ msgstr "姓は2〜20文字の英字のみで入力してください"
msgid "Last name must be 3-20 characters long and contain only letters" msgid "Last name must be 3-20 characters long and contain only letters"
msgstr "姓は3〜20文字の英字のみで入力してください" msgstr "姓は3〜20文字の英字のみで入力してください"
#. placeholder {0}: migrationStatus.result.totalBadgesEarned
#: src/features/admin/components/admin-page.tsx:74
msgid "Last recalculation: {0, plural, one {# badge earned} other {# badges earned}}"
msgstr "前回の再計算:{0, plural, one {# 個のバッジを獲得} other {# 個のバッジを獲得}}"
#: src/features/settings/components/color-scheme-picker.tsx:43 #: src/features/settings/components/color-scheme-picker.tsx:43
msgid "Light" msgid "Light"
msgstr "ライト" msgstr "ライト"
@@ -1410,7 +1431,7 @@ msgid "Manage Team Songs/Logos"
msgstr "チームの曲・ロゴを管理" msgstr "チームの曲・ロゴを管理"
#: src/app/routes/_authed/admin/tournaments/index.tsx:17 #: src/app/routes/_authed/admin/tournaments/index.tsx:17
#: src/features/admin/components/admin-page.tsx:31 #: src/features/admin/components/admin-page.tsx:50
msgid "Manage Tournaments" msgid "Manage Tournaments"
msgstr "トーナメント管理" msgstr "トーナメント管理"
@@ -1489,7 +1510,7 @@ msgstr "対戦カードの詳細"
msgid "Me" msgid "Me"
msgstr "自分" msgstr "自分"
#: src/features/admin/components/admin-page.tsx:41 #: src/features/admin/components/admin-page.tsx:60
msgid "Migrate Badge Progress" msgid "Migrate Badge Progress"
msgstr "バッジ進捗を移行" msgstr "バッジ進捗を移行"
@@ -1729,7 +1750,7 @@ msgstr "登録が完了すると、他のフリーエージェントとその電
msgid "One time code" msgid "One time code"
msgstr "ワンタイムコード" msgstr "ワンタイムコード"
#: src/features/admin/components/admin-page.tsx:52 #: src/features/admin/components/admin-page.tsx:95
msgid "Open Pocketbase" msgid "Open Pocketbase"
msgstr "Pocketbaseを開く" msgstr "Pocketbaseを開く"
@@ -1927,6 +1948,12 @@ msgstr "再抽選"
msgid "Reactions" msgid "Reactions"
msgstr "リアクション" msgstr "リアクション"
#. placeholder {0}: migrationStatus.playersProcessed
#. placeholder {1}: migrationStatus.totalPlayers
#: src/features/admin/components/admin-page.tsx:67
msgid "Recalculating badges in the background ({0}/{1})"
msgstr "バックグラウンドでバッジを再計算中({0}/{1})"
#: src/features/tournaments/components/started-tournament/sections/recent-results.tsx:34 #: src/features/tournaments/components/started-tournament/sections/recent-results.tsx:34
msgid "Recently finished" msgid "Recently finished"
msgstr "最近終了した試合" msgstr "最近終了した試合"