badges improvements
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { List } from "@mantine/core";
|
||||
import { useLingui } from "@lingui/react/macro";
|
||||
import { List, Text } from "@mantine/core";
|
||||
import { Plural, Trans, useLingui } from "@lingui/react/macro";
|
||||
import ListLink from "@/components/list-link";
|
||||
import {
|
||||
DatabaseIcon,
|
||||
@@ -10,19 +10,38 @@ import {
|
||||
ListIcon,
|
||||
} from "@phosphor-icons/react";
|
||||
import ListButton from "@/components/list-button";
|
||||
import { migrateBadgeProgress } from "@/features/badges/server";
|
||||
import { useState } from "react";
|
||||
import { getBadgeMigrationStatus, migrateBadgeProgress } from "@/features/badges/server";
|
||||
import { useServerQuery } from "@/lib/tanstack-query/hooks";
|
||||
import toast from "@/lib/sonner";
|
||||
|
||||
const AdminPage = () => {
|
||||
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 () => {
|
||||
if (isMigrating) return;
|
||||
|
||||
setIsMigrating(true);
|
||||
await migrateBadgeProgress();
|
||||
setIsMigrating(false);
|
||||
const result = await migrateBadgeProgress();
|
||||
if (result.success) {
|
||||
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 (
|
||||
@@ -43,6 +62,30 @@ const AdminPage = () => {
|
||||
onClick={handleMigrateBadges}
|
||||
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
|
||||
label={t`Activities`}
|
||||
Icon={ListIcon}
|
||||
|
||||
@@ -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 };
|
||||
};
|
||||
@@ -4,6 +4,7 @@ import { createServerFn } from "@tanstack/react-start";
|
||||
import { pbAdmin } from "@/lib/pocketbase/client";
|
||||
import { z } from "zod";
|
||||
import { emitServerEvent } from "@/lib/events/emitter";
|
||||
import { getBadgeMigrationJobStatus, startBadgeMigration } from "./migration.server";
|
||||
|
||||
export const getPlayerBadges = createServerFn()
|
||||
.validator(z.string())
|
||||
@@ -15,11 +16,13 @@ export const getPlayerBadges = createServerFn()
|
||||
export const migrateBadgeProgress = createServerFn()
|
||||
.middleware([superTokensAdminFunctionMiddleware])
|
||||
.handler(async () =>
|
||||
toServerResult(async () => {
|
||||
const result = await pbAdmin.migrateBadgeProgress();
|
||||
emitServerEvent({ type: "badge" });
|
||||
return result;
|
||||
})
|
||||
toServerResult(async () => startBadgeMigration())
|
||||
);
|
||||
|
||||
export const getBadgeMigrationStatus = createServerFn()
|
||||
.middleware([superTokensAdminFunctionMiddleware])
|
||||
.handler(async () =>
|
||||
toServerResult(async () => getBadgeMigrationJobStatus())
|
||||
);
|
||||
|
||||
export const getAllBadges = createServerFn()
|
||||
|
||||
@@ -70,16 +70,6 @@ export function createBadgesService(pb: PocketBase) {
|
||||
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> {
|
||||
if (badge.type === "manual") {
|
||||
return 0;
|
||||
@@ -651,27 +641,36 @@ export function createBadgesService(pb: PocketBase) {
|
||||
return transformBadgeProgress(created);
|
||||
},
|
||||
|
||||
async migrateBadgeProgress(): Promise<{
|
||||
success: boolean;
|
||||
async migrateBadgeProgress(
|
||||
onProgress?: (playersProcessed: number, totalPlayers: number) => void
|
||||
): Promise<{
|
||||
playersProcessed: number;
|
||||
progressRecordsCreated: number;
|
||||
totalBadgesEarned: number;
|
||||
created: number;
|
||||
updated: number;
|
||||
deleted: number;
|
||||
averageBadgesPerPlayer: string;
|
||||
}> {
|
||||
await this.clearAllBadgeProgress();
|
||||
|
||||
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();
|
||||
const uniquePlayers = new Set(allPlayers.map((p: any) => p.id));
|
||||
// Phase 1: read-only calculation. Existing badge_progress rows stay
|
||||
// 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;
|
||||
let totalBadgesEarned = 0;
|
||||
|
||||
for (const playerId of uniquePlayers) {
|
||||
for (const badge of badges) {
|
||||
for (const player of players) {
|
||||
for (const badge of autoBadges) {
|
||||
const key = `${player.id}|${badge.id}`;
|
||||
try {
|
||||
const progress = await this.calculateBadgeProgress(playerId, badge);
|
||||
const progress = await this.calculateBadgeProgress(player.id, badge);
|
||||
const target = this.getTargetProgress(badge);
|
||||
|
||||
const isPlacementBadge = badge.criteria.placement !== undefined;
|
||||
@@ -681,31 +680,77 @@ export function createBadgesService(pb: PocketBase) {
|
||||
: progress === target;
|
||||
|
||||
if (progress > 0 || earned) {
|
||||
await this.createBadgeProgress({
|
||||
desired.set(key, {
|
||||
badge: badge.id,
|
||||
player: playerId,
|
||||
progress: progress,
|
||||
earned: earned,
|
||||
player: player.id,
|
||||
progress,
|
||||
earned,
|
||||
});
|
||||
|
||||
totalProgressRecords++;
|
||||
|
||||
if (earned) {
|
||||
totalBadgesEarned++;
|
||||
}
|
||||
}
|
||||
} 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 {
|
||||
success: true,
|
||||
playersProcessed: uniquePlayers.size,
|
||||
progressRecordsCreated: totalProgressRecords,
|
||||
totalBadgesEarned: totalBadgesEarned,
|
||||
averageBadgesPerPlayer: (totalBadgesEarned / uniquePlayers.size).toFixed(2),
|
||||
playersProcessed: players.length,
|
||||
totalBadgesEarned,
|
||||
created,
|
||||
updated,
|
||||
deleted: toDelete.length,
|
||||
averageBadgesPerPlayer: players.length
|
||||
? (totalBadgesEarned / players.length).toFixed(2)
|
||||
: "0",
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -384,7 +384,7 @@ msgid "Active device: {0}"
|
||||
msgstr "Aktives Gerät: {0}"
|
||||
|
||||
#: 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"
|
||||
msgstr "Aktivitäten"
|
||||
|
||||
@@ -520,7 +520,7 @@ msgstr "Ø Sieg-Marge"
|
||||
msgid "Award Badge"
|
||||
msgstr "Abzeichen verleihen"
|
||||
|
||||
#: src/features/admin/components/admin-page.tsx:36
|
||||
#: src/features/admin/components/admin-page.tsx:55
|
||||
msgid "Award Badges"
|
||||
msgstr "Abzeichen verleihen"
|
||||
|
||||
@@ -554,6 +554,18 @@ msgstr "Zurück"
|
||||
msgid "Badge awarded to {playerName}"
|
||||
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
|
||||
msgid "Badge Type"
|
||||
msgstr "Abzeichentyp"
|
||||
@@ -581,7 +593,7 @@ msgid "Bracket"
|
||||
msgstr "Turnierbaum"
|
||||
|
||||
#: 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"
|
||||
msgstr "Turnierbaum-Vorschau"
|
||||
|
||||
@@ -1060,6 +1072,10 @@ msgstr "Aktives Gerät konnte nicht festgelegt werden"
|
||||
msgid "Failed to set volume"
|
||||
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
|
||||
msgid "Failed to update team"
|
||||
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"
|
||||
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
|
||||
msgid "Light"
|
||||
msgstr "Hell"
|
||||
@@ -1410,7 +1431,7 @@ msgid "Manage Team Songs/Logos"
|
||||
msgstr "Team-Songs/Logos verwalten"
|
||||
|
||||
#: 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"
|
||||
msgstr "Turniere verwalten"
|
||||
|
||||
@@ -1489,7 +1510,7 @@ msgstr "Matchup-Details"
|
||||
msgid "Me"
|
||||
msgstr "Ich"
|
||||
|
||||
#: src/features/admin/components/admin-page.tsx:41
|
||||
#: src/features/admin/components/admin-page.tsx:60
|
||||
msgid "Migrate Badge Progress"
|
||||
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"
|
||||
msgstr "Einmalcode"
|
||||
|
||||
#: src/features/admin/components/admin-page.tsx:52
|
||||
#: src/features/admin/components/admin-page.tsx:95
|
||||
msgid "Open Pocketbase"
|
||||
msgstr "Pocketbase öffnen"
|
||||
|
||||
@@ -1927,6 +1948,12 @@ msgstr "Neu würfeln"
|
||||
msgid "Reactions"
|
||||
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
|
||||
msgid "Recently finished"
|
||||
msgstr "Kürzlich beendet"
|
||||
|
||||
@@ -384,7 +384,7 @@ msgid "Active device: {0}"
|
||||
msgstr "Active device: {0}"
|
||||
|
||||
#: 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"
|
||||
msgstr "Activities"
|
||||
|
||||
@@ -520,7 +520,7 @@ msgstr "Avg Win Margin"
|
||||
msgid "Award Badge"
|
||||
msgstr "Award Badge"
|
||||
|
||||
#: src/features/admin/components/admin-page.tsx:36
|
||||
#: src/features/admin/components/admin-page.tsx:55
|
||||
msgid "Award Badges"
|
||||
msgstr "Award Badges"
|
||||
|
||||
@@ -554,6 +554,18 @@ msgstr "Back"
|
||||
msgid "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
|
||||
msgid "Badge Type"
|
||||
msgstr "Badge Type"
|
||||
@@ -581,7 +593,7 @@ msgid "Bracket"
|
||||
msgstr "Bracket"
|
||||
|
||||
#: 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"
|
||||
msgstr "Bracket Preview"
|
||||
|
||||
@@ -1060,6 +1072,10 @@ msgstr "Failed to set active device"
|
||||
msgid "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
|
||||
msgid "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"
|
||||
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
|
||||
msgid "Light"
|
||||
msgstr "Light"
|
||||
@@ -1410,7 +1431,7 @@ msgid "Manage Team Songs/Logos"
|
||||
msgstr "Manage Team Songs/Logos"
|
||||
|
||||
#: 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"
|
||||
msgstr "Manage Tournaments"
|
||||
|
||||
@@ -1489,7 +1510,7 @@ msgstr "Matchup details"
|
||||
msgid "Me"
|
||||
msgstr "Me"
|
||||
|
||||
#: src/features/admin/components/admin-page.tsx:41
|
||||
#: src/features/admin/components/admin-page.tsx:60
|
||||
msgid "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"
|
||||
msgstr "One time code"
|
||||
|
||||
#: src/features/admin/components/admin-page.tsx:52
|
||||
#: src/features/admin/components/admin-page.tsx:95
|
||||
msgid "Open Pocketbase"
|
||||
msgstr "Open Pocketbase"
|
||||
|
||||
@@ -1927,6 +1948,12 @@ msgstr "Re-roll"
|
||||
msgid "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
|
||||
msgid "Recently finished"
|
||||
msgstr "Recently finished"
|
||||
|
||||
@@ -384,7 +384,7 @@ msgid "Active device: {0}"
|
||||
msgstr "Dispositivo activo: {0}"
|
||||
|
||||
#: 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"
|
||||
msgstr "Actividades"
|
||||
|
||||
@@ -520,7 +520,7 @@ msgstr "Prom. Margen de Victoria"
|
||||
msgid "Award Badge"
|
||||
msgstr "Otorgar insignia"
|
||||
|
||||
#: src/features/admin/components/admin-page.tsx:36
|
||||
#: src/features/admin/components/admin-page.tsx:55
|
||||
msgid "Award Badges"
|
||||
msgstr "Otorgar insignias"
|
||||
|
||||
@@ -554,6 +554,18 @@ msgstr "Atrás"
|
||||
msgid "Badge awarded to {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
|
||||
msgid "Badge Type"
|
||||
msgstr "Tipo de insignia"
|
||||
@@ -581,7 +593,7 @@ msgid "Bracket"
|
||||
msgstr "Bracket"
|
||||
|
||||
#: 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"
|
||||
msgstr "Vista previa del bracket"
|
||||
|
||||
@@ -1060,6 +1072,10 @@ msgstr "No se pudo establecer el dispositivo activo"
|
||||
msgid "Failed to set volume"
|
||||
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
|
||||
msgid "Failed to update team"
|
||||
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"
|
||||
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
|
||||
msgid "Light"
|
||||
msgstr "Claro"
|
||||
@@ -1410,7 +1431,7 @@ msgid "Manage Team Songs/Logos"
|
||||
msgstr "Administrar canciones/logos del equipo"
|
||||
|
||||
#: 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"
|
||||
msgstr "Administrar torneos"
|
||||
|
||||
@@ -1489,7 +1510,7 @@ msgstr "Detalles del enfrentamiento"
|
||||
msgid "Me"
|
||||
msgstr "Yo"
|
||||
|
||||
#: src/features/admin/components/admin-page.tsx:41
|
||||
#: src/features/admin/components/admin-page.tsx:60
|
||||
msgid "Migrate Badge Progress"
|
||||
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"
|
||||
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"
|
||||
msgstr "Abrir Pocketbase"
|
||||
|
||||
@@ -1927,6 +1948,12 @@ msgstr "Volver a sortear"
|
||||
msgid "Reactions"
|
||||
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
|
||||
msgid "Recently finished"
|
||||
msgstr "Recién terminados"
|
||||
|
||||
@@ -384,7 +384,7 @@ msgid "Active device: {0}"
|
||||
msgstr "アクティブなデバイス: {0}"
|
||||
|
||||
#: 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"
|
||||
msgstr "アクティビティ"
|
||||
|
||||
@@ -520,7 +520,7 @@ msgstr "平均勝差"
|
||||
msgid "Award Badge"
|
||||
msgstr "バッジを付与"
|
||||
|
||||
#: src/features/admin/components/admin-page.tsx:36
|
||||
#: src/features/admin/components/admin-page.tsx:55
|
||||
msgid "Award Badges"
|
||||
msgstr "バッジを付与"
|
||||
|
||||
@@ -554,6 +554,18 @@ msgstr "戻る"
|
||||
msgid "Badge awarded to {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
|
||||
msgid "Badge Type"
|
||||
msgstr "バッジの種類"
|
||||
@@ -581,7 +593,7 @@ msgid "Bracket"
|
||||
msgstr "ブラケット"
|
||||
|
||||
#: 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"
|
||||
msgstr "ブラケットプレビュー"
|
||||
|
||||
@@ -1060,6 +1072,10 @@ msgstr "アクティブなデバイスの設定に失敗しました"
|
||||
msgid "Failed to set volume"
|
||||
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
|
||||
msgid "Failed to update team"
|
||||
msgstr "チームの更新に失敗しました"
|
||||
@@ -1322,6 +1338,11 @@ msgstr "姓は2〜20文字の英字のみで入力してください"
|
||||
msgid "Last name must be 3-20 characters long and contain only letters"
|
||||
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
|
||||
msgid "Light"
|
||||
msgstr "ライト"
|
||||
@@ -1410,7 +1431,7 @@ msgid "Manage Team Songs/Logos"
|
||||
msgstr "チームの曲・ロゴを管理"
|
||||
|
||||
#: 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"
|
||||
msgstr "トーナメント管理"
|
||||
|
||||
@@ -1489,7 +1510,7 @@ msgstr "対戦カードの詳細"
|
||||
msgid "Me"
|
||||
msgstr "自分"
|
||||
|
||||
#: src/features/admin/components/admin-page.tsx:41
|
||||
#: src/features/admin/components/admin-page.tsx:60
|
||||
msgid "Migrate Badge Progress"
|
||||
msgstr "バッジ進捗を移行"
|
||||
|
||||
@@ -1729,7 +1750,7 @@ msgstr "登録が完了すると、他のフリーエージェントとその電
|
||||
msgid "One time code"
|
||||
msgstr "ワンタイムコード"
|
||||
|
||||
#: src/features/admin/components/admin-page.tsx:52
|
||||
#: src/features/admin/components/admin-page.tsx:95
|
||||
msgid "Open Pocketbase"
|
||||
msgstr "Pocketbaseを開く"
|
||||
|
||||
@@ -1927,6 +1948,12 @@ msgstr "再抽選"
|
||||
msgid "Reactions"
|
||||
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
|
||||
msgid "Recently finished"
|
||||
msgstr "最近終了した試合"
|
||||
|
||||
Reference in New Issue
Block a user