i18n
This commit is contained in:
@@ -6,6 +6,7 @@ import MatchDock from "./match-dock";
|
||||
import useAppShellHeight from "@/hooks/use-appshell-height";
|
||||
import { Match } from "@/features/matches/types";
|
||||
import styles from "./styles.module.css";
|
||||
import { Trans } from "@lingui/react/macro";
|
||||
|
||||
interface BracketViewProps {
|
||||
bracket: BracketData;
|
||||
@@ -95,14 +96,14 @@ const BracketView: React.FC<BracketViewProps> = ({ bracket, showControls, groupC
|
||||
>
|
||||
<div>
|
||||
<Text fw={600} size="md" m={16}>
|
||||
Winners Bracket
|
||||
<Trans>Winners Bracket</Trans>
|
||||
</Text>
|
||||
<Bracket rounds={bracket.winners} orders={orders} showControls={showControls} groupConfig={groupConfig} renderMatch={renderMatch} onMatchTap={handleMatchTap} selectedMatchLid={selectedLid} nextUpMatchId={nextUpMatchId} />
|
||||
</div>
|
||||
{bracket.losers && bracket.losers.length > 0 && bracket.losers.some(round => round.length > 0) && (
|
||||
<div>
|
||||
<Text fw={600} size="md" m={16}>
|
||||
Losers Bracket
|
||||
<Trans>Losers Bracket</Trans>
|
||||
</Text>
|
||||
<Bracket rounds={bracket.losers} orders={orders} showControls={showControls} groupConfig={groupConfig} renderMatch={renderMatch} onMatchTap={handleMatchTap} selectedMatchLid={selectedLid} nextUpMatchId={nextUpMatchId} />
|
||||
</div>
|
||||
|
||||
@@ -16,6 +16,7 @@ import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useSpotifyPlayback } from "@/lib/spotify/hooks";
|
||||
import { getGroupLabel } from "../utils/group-label";
|
||||
import styles from "./styles.module.css";
|
||||
import { Trans, useLingui } from "@lingui/react/macro";
|
||||
|
||||
interface MatchCardProps {
|
||||
match: Match;
|
||||
@@ -42,6 +43,7 @@ export const MatchCard: React.FC<MatchCardProps> = ({
|
||||
const queryClient = useQueryClient();
|
||||
const editSheet = useSheet();
|
||||
const { playTrack, pause } = useSpotifyPlayback();
|
||||
const { t, i18n } = useLingui();
|
||||
|
||||
const canTap = !!(onTap && match.home && match.away);
|
||||
|
||||
@@ -59,9 +61,9 @@ export const MatchCard: React.FC<MatchCardProps> = ({
|
||||
match.home_cups !== undefined &&
|
||||
match.away_cups !== undefined &&
|
||||
match.home_cups > match.away_cups,
|
||||
groupLabel: !match.home && match.home_seed ? getGroupLabel(match.home_seed, groupConfig) : undefined,
|
||||
groupLabel: !match.home && match.home_seed ? getGroupLabel(i18n, match.home_seed, groupConfig) : undefined,
|
||||
}),
|
||||
[match, orders, groupConfig]
|
||||
[match, orders, groupConfig, i18n]
|
||||
);
|
||||
const awaySlot = useMemo(
|
||||
() => ({
|
||||
@@ -75,9 +77,9 @@ export const MatchCard: React.FC<MatchCardProps> = ({
|
||||
match.away_cups !== undefined &&
|
||||
match.home_cups !== undefined &&
|
||||
match.away_cups > match.home_cups,
|
||||
groupLabel: !match.away && match.away_seed ? getGroupLabel(match.away_seed, groupConfig) : undefined,
|
||||
groupLabel: !match.away && match.away_seed ? getGroupLabel(i18n, match.away_seed, groupConfig) : undefined,
|
||||
}),
|
||||
[match, orders, groupConfig]
|
||||
[match, orders, groupConfig, i18n]
|
||||
);
|
||||
|
||||
const showToolbar = useMemo(
|
||||
@@ -95,7 +97,7 @@ export const MatchCard: React.FC<MatchCardProps> = ({
|
||||
|
||||
const start = useServerMutation({
|
||||
mutationFn: startMatch,
|
||||
successMessage: "Match started!",
|
||||
successMessage: t`Match started!`,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: tournamentKeys.details(match.tournament.id),
|
||||
@@ -112,12 +114,12 @@ export const MatchCard: React.FC<MatchCardProps> = ({
|
||||
editSheet.close();
|
||||
if (data?.downstreamReset) {
|
||||
toast.error(
|
||||
"Downstream matches were reset because the correction changed who advances."
|
||||
t`Downstream matches were reset because the correction changed who advances.`
|
||||
);
|
||||
}
|
||||
if (data?.groupEditAfterKnockout) {
|
||||
toast.error(
|
||||
"Group result changed after the bracket was seeded — reseed the knockout stage if needed."
|
||||
t`Group result changed after the bracket was seeded — reseed the knockout stage if needed.`
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -186,7 +188,7 @@ export const MatchCard: React.FC<MatchCardProps> = ({
|
||||
|
||||
const handleSpeakerClick = useCallback(async () => {
|
||||
if (!hasWalkoutData || !match.home?.name || !match.away?.name) {
|
||||
await speak(`${match.home?.name || "Home"} vs. ${match.away?.name || "Away"}`);
|
||||
await speak(t`${match.home?.name || t({ message: "Home", context: "match team" })} vs. ${match.away?.name || t({ message: "Away", context: "match team" })}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -196,14 +198,14 @@ export const MatchCard: React.FC<MatchCardProps> = ({
|
||||
|
||||
await playTeamWalkout(homeTeam);
|
||||
await speak(homeTeam.name);
|
||||
await speak("versus");
|
||||
await speak(t`versus`);
|
||||
await playTeamWalkout(awayTeam);
|
||||
await speak(awayTeam.name);
|
||||
await speak("have fun, good luck!");
|
||||
await speak(t`have fun, good luck!`);
|
||||
|
||||
} catch (error) {
|
||||
console.warn('Walkout sequence error:', error);
|
||||
await speak(`${match.home.name} vs. ${match.away.name}`);
|
||||
await speak(t`${match.home.name} vs. ${match.away.name}`);
|
||||
}
|
||||
}, [hasWalkoutData, match.home, match.away, speak, playTeamWalkout]);
|
||||
|
||||
@@ -221,10 +223,10 @@ export const MatchCard: React.FC<MatchCardProps> = ({
|
||||
|
||||
await playTeamWalkout(homeTeam);
|
||||
await speak(homeTeam.name);
|
||||
await speak("versus");
|
||||
await speak(t`versus`);
|
||||
await playTeamWalkout(awayTeam);
|
||||
await speak(awayTeam.name);
|
||||
await speak("have fun, good luck!");
|
||||
await speak(t`have fun, good luck!`);
|
||||
} catch (error) {
|
||||
console.warn('Auto-walkout sequence error:', error);
|
||||
}
|
||||
@@ -283,7 +285,7 @@ export const MatchCard: React.FC<MatchCardProps> = ({
|
||||
}
|
||||
aria-label={
|
||||
canTap
|
||||
? `Match ${match.order}: ${match.home!.name} vs ${match.away!.name}`
|
||||
? t`Match ${match.order}: ${match.home!.name} vs ${match.away!.name}`
|
||||
: undefined
|
||||
}
|
||||
style={{
|
||||
@@ -315,7 +317,7 @@ export const MatchCard: React.FC<MatchCardProps> = ({
|
||||
c="dimmed"
|
||||
fw="bold"
|
||||
>
|
||||
* If necessary
|
||||
<Trans>* If necessary</Trans>
|
||||
</Text>
|
||||
)}
|
||||
|
||||
@@ -331,7 +333,7 @@ export const MatchCard: React.FC<MatchCardProps> = ({
|
||||
e.stopPropagation();
|
||||
handleSpeakerClick();
|
||||
}}
|
||||
aria-label="Announce matchup"
|
||||
aria-label={t`Announce matchup`}
|
||||
>
|
||||
<SpeakerHighIcon size={12} />
|
||||
</ActionIcon>
|
||||
@@ -349,7 +351,7 @@ export const MatchCard: React.FC<MatchCardProps> = ({
|
||||
h="100%"
|
||||
radius="sm"
|
||||
ml={-4}
|
||||
aria-label="Start match"
|
||||
aria-label={t`Start match`}
|
||||
style={{
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
@@ -371,7 +373,7 @@ export const MatchCard: React.FC<MatchCardProps> = ({
|
||||
h="100%"
|
||||
radius="sm"
|
||||
ml={-4}
|
||||
aria-label="Edit match score"
|
||||
aria-label={t`Edit match score`}
|
||||
style={{
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
@@ -383,7 +385,7 @@ export const MatchCard: React.FC<MatchCardProps> = ({
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
<Sheet title="Edit Match" {...editSheet.props}>
|
||||
<Sheet title={t`Edit Match`} {...editSheet.props}>
|
||||
<MatchForm
|
||||
match={match}
|
||||
onSubmit={handleFormSubmit}
|
||||
|
||||
@@ -23,6 +23,7 @@ import TeamHeadToHeadSheet from "@/features/matches/components/team-head-to-head
|
||||
import { MatchReport } from "./match-report";
|
||||
import Sheet from "@/components/sheet/sheet";
|
||||
import { useSheet } from "@/hooks/use-sheet";
|
||||
import { Trans, useLingui } from "@lingui/react/macro";
|
||||
|
||||
const EASE: [number, number, number, number] = [0.32, 0.72, 0, 1];
|
||||
|
||||
@@ -81,6 +82,7 @@ const TeamRow = ({
|
||||
const MatchDock = ({ match, onClose }: MatchDockProps) => {
|
||||
const reduceMotion = useReducedMotion();
|
||||
const h2hSheet = useSheet();
|
||||
const { t } = useLingui();
|
||||
const hasPrivate = match?.home?.private || match?.away?.private;
|
||||
const ended = match?.status === "ended";
|
||||
const started = match?.status === "started";
|
||||
@@ -118,14 +120,17 @@ const MatchDock = ({ match, onClose }: MatchDockProps) => {
|
||||
/>
|
||||
)}
|
||||
<Text size="xs" fw={600} c="dimmed" lineClamp={1}>
|
||||
Match {match.order} · Round {match.round + 1}
|
||||
{match.is_losers_bracket && " (Losers)"}
|
||||
{match.is_losers_bracket ? (
|
||||
<Trans>Match {match.order} · Round {match.round + 1} (Losers)</Trans>
|
||||
) : (
|
||||
<Trans>Match {match.order} · Round {match.round + 1}</Trans>
|
||||
)}
|
||||
</Text>
|
||||
</Group>
|
||||
<CloseButton
|
||||
size="sm"
|
||||
onClick={onClose}
|
||||
aria-label="Close match actions"
|
||||
aria-label={t`Close match actions`}
|
||||
/>
|
||||
</Group>
|
||||
|
||||
@@ -165,12 +170,12 @@ const MatchDock = ({ match, onClose }: MatchDockProps) => {
|
||||
</Suspense>
|
||||
</Box>
|
||||
{!hasPrivate && (
|
||||
<Tooltip label="Head to Head" withArrow position="top">
|
||||
<Tooltip label={t`Head to Head`} withArrow position="top">
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
onClick={h2hSheet.open}
|
||||
aria-label="View head-to-head"
|
||||
aria-label={t`View head-to-head`}
|
||||
w={40}
|
||||
style={{ flexShrink: 0 }}
|
||||
>
|
||||
@@ -207,7 +212,7 @@ const MatchDock = ({ match, onClose }: MatchDockProps) => {
|
||||
</AnimatePresence>
|
||||
|
||||
{match?.home && match?.away && h2hSheet.isOpen && (
|
||||
<Sheet title="Head to Head" {...h2hSheet.props}>
|
||||
<Sheet title={t`Head to Head`} {...h2hSheet.props}>
|
||||
<TeamHeadToHeadSheet
|
||||
team1={match.home}
|
||||
team2={match.away}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Button, TextInput, Stack, Group, Text, Flex, Divider, NumberInput } from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { Match } from "@/features/matches/types";
|
||||
import { Trans, useLingui } from "@lingui/react/macro";
|
||||
|
||||
interface MatchFormProps {
|
||||
match: Match;
|
||||
@@ -19,6 +20,7 @@ export const MatchForm: React.FC<MatchFormProps> = ({
|
||||
onCancel,
|
||||
loading = false,
|
||||
}) => {
|
||||
const { t } = useLingui();
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
home_cups: match.home_cups || 10,
|
||||
@@ -27,42 +29,42 @@ export const MatchForm: React.FC<MatchFormProps> = ({
|
||||
},
|
||||
validate: {
|
||||
home_cups: (value, values) => {
|
||||
if (value === null || value === undefined) return "Home cups is required";
|
||||
if (value === null || value === undefined) return t`Home cups is required`;
|
||||
if (values.ot_count > 0) return null;
|
||||
|
||||
|
||||
const homeCups = Number(value);
|
||||
const awayCups = Number(values.away_cups);
|
||||
|
||||
|
||||
if (homeCups !== 10 && awayCups !== 10) {
|
||||
return "At least one team must have 10 cups";
|
||||
return t`At least one team must have 10 cups`;
|
||||
}
|
||||
|
||||
|
||||
if (homeCups === 10 && awayCups === 10) {
|
||||
return "Both teams cannot have 10 cups";
|
||||
return t`Both teams cannot have 10 cups`;
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
},
|
||||
away_cups: (value, values) => {
|
||||
if (value === null || value === undefined) return "Away cups is required";
|
||||
if (value === null || value === undefined) return t`Away cups is required`;
|
||||
if (values.ot_count > 0) return null;
|
||||
|
||||
|
||||
const awayCups = Number(value);
|
||||
const homeCups = Number(values.home_cups);
|
||||
|
||||
|
||||
if (homeCups !== 10 && awayCups !== 10) {
|
||||
return "At least one team must have 10 cups";
|
||||
return t`At least one team must have 10 cups`;
|
||||
}
|
||||
|
||||
|
||||
if (homeCups === 10 && awayCups === 10) {
|
||||
return "Both teams cannot have 10 cups";
|
||||
return t`Both teams cannot have 10 cups`;
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
},
|
||||
ot_count: (value) =>
|
||||
value === null || value === undefined
|
||||
? "Overtime count is required"
|
||||
? t`Overtime count is required`
|
||||
: null,
|
||||
},
|
||||
transformValues: (values) => ({
|
||||
@@ -85,7 +87,7 @@ export const MatchForm: React.FC<MatchFormProps> = ({
|
||||
<Group gap="xs">
|
||||
<Stack gap={0}>
|
||||
<Text fw={500} size="sm">
|
||||
{match.home?.name} Cups
|
||||
<Trans>{match.home?.name} Cups</Trans>
|
||||
</Text>
|
||||
{
|
||||
match.home?.players?.map(p => (<Text key={p.id} size='xs' c='dimmed'>
|
||||
@@ -108,7 +110,7 @@ export const MatchForm: React.FC<MatchFormProps> = ({
|
||||
<Group gap="xs">
|
||||
<Stack gap={0}>
|
||||
<Text fw={500} size="sm">
|
||||
{match.away?.name} Cups
|
||||
<Trans>{match.away?.name} Cups</Trans>
|
||||
</Text>
|
||||
{
|
||||
match.away?.players?.map(p => (<Text key={p.id} size='xs' c='dimmed'>
|
||||
@@ -130,7 +132,7 @@ export const MatchForm: React.FC<MatchFormProps> = ({
|
||||
|
||||
<Group gap="xs">
|
||||
<Text fw={500} size="sm">
|
||||
OT Count
|
||||
<Trans>OT Count</Trans>
|
||||
</Text>
|
||||
<TextInput
|
||||
ml='auto'
|
||||
@@ -147,7 +149,7 @@ export const MatchForm: React.FC<MatchFormProps> = ({
|
||||
|
||||
<Stack mt="md">
|
||||
<Button type="submit" loading={loading}>
|
||||
Update Match
|
||||
<Trans>Update Match</Trans>
|
||||
</Button>
|
||||
<Button
|
||||
variant="subtle"
|
||||
@@ -155,7 +157,7 @@ export const MatchForm: React.FC<MatchFormProps> = ({
|
||||
onClick={onCancel}
|
||||
disabled={loading}
|
||||
>
|
||||
Cancel
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useSheet } from "@/hooks/use-sheet";
|
||||
import Sheet from "@/components/sheet/sheet";
|
||||
import { MatchForm } from "./match-form";
|
||||
import toast from "@/lib/sonner";
|
||||
import { Trans, useLingui } from "@lingui/react/macro";
|
||||
|
||||
const teamId = (t: Match["home"]): string | undefined =>
|
||||
!t ? undefined : typeof t === "string" ? t : t.id;
|
||||
@@ -30,6 +31,7 @@ interface MatchReportProps {
|
||||
export const MatchReport: React.FC<MatchReportProps> = ({ match, compact }) => {
|
||||
const { user } = useAuth();
|
||||
const formSheet = useSheet();
|
||||
const { t } = useLingui();
|
||||
const [acknowledgedReport, setAcknowledgedReport] = useState<string | null>(
|
||||
null
|
||||
);
|
||||
@@ -73,15 +75,15 @@ export const MatchReport: React.FC<MatchReportProps> = ({ match, compact }) => {
|
||||
const report = useReportMatchScore(user?.id, {
|
||||
onSuccess: (data: any) => {
|
||||
if (data?.finalized) {
|
||||
toast.success("Score confirmed 🍻");
|
||||
toast.success(t`Score confirmed 🍻`);
|
||||
if (data.downstreamReset) {
|
||||
toast.error(
|
||||
"Downstream matches were reset because the correction changed who advances."
|
||||
t`Downstream matches were reset because the correction changed who advances.`
|
||||
);
|
||||
}
|
||||
if (data.groupEditAfterKnockout) {
|
||||
toast.error(
|
||||
"Group result changed after the bracket was seeded — reseed the knockout stage if needed."
|
||||
t`Group result changed after the bracket was seeded — reseed the knockout stage if needed.`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -92,12 +94,12 @@ export const MatchReport: React.FC<MatchReportProps> = ({ match, compact }) => {
|
||||
onSuccess: (data: any) => {
|
||||
if (data?.downstreamReset) {
|
||||
toast.error(
|
||||
"Downstream matches were reset because the correction changed who advances."
|
||||
t`Downstream matches were reset because the correction changed who advances.`
|
||||
);
|
||||
}
|
||||
if (data?.groupEditAfterKnockout) {
|
||||
toast.error(
|
||||
"Group result changed after the bracket was seeded — reseed the knockout stage if needed."
|
||||
t`Group result changed after the bracket was seeded — reseed the knockout stage if needed.`
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -109,21 +111,21 @@ export const MatchReport: React.FC<MatchReportProps> = ({ match, compact }) => {
|
||||
(data: { home_cups: number; away_cups: number; ot_count: number }) => {
|
||||
report.mutate({ data: { ...data, matchId: match.id } });
|
||||
formSheet.close();
|
||||
toast.success("Score submitted — waiting for the other team");
|
||||
toast.success(t`Score submitted — waiting for the other team`);
|
||||
},
|
||||
[report, match.id, formSheet.close]
|
||||
[report, match.id, formSheet.close, t]
|
||||
);
|
||||
|
||||
const submitConfirm = useCallback(() => {
|
||||
confirm.mutate({ data: { matchId: match.id } });
|
||||
formSheet.close();
|
||||
toast.success("Score confirmed 🍻");
|
||||
}, [confirm, match.id, formSheet.close]);
|
||||
toast.success(t`Score confirmed 🍻`);
|
||||
}, [confirm, match.id, formSheet.close, t]);
|
||||
|
||||
const submitClear = useCallback(() => {
|
||||
clear.mutate({ data: { matchId: match.id } });
|
||||
toast.success("Report cleared");
|
||||
}, [clear, match.id]);
|
||||
toast.success(t`Report cleared`);
|
||||
}, [clear, match.id, t]);
|
||||
|
||||
const openForm = useCallback(() => {
|
||||
setAcknowledgedReport(pendingSignature);
|
||||
@@ -143,7 +145,7 @@ export const MatchReport: React.FC<MatchReportProps> = ({ match, compact }) => {
|
||||
|
||||
const formSheetEl = (
|
||||
<Sheet
|
||||
title={sheetTakenOver ? "Confirm Score" : "Report Score"}
|
||||
title={sheetTakenOver ? t`Confirm Score` : t`Report Score`}
|
||||
{...formSheet.props}
|
||||
>
|
||||
{sheetTakenOver ? (
|
||||
@@ -152,16 +154,16 @@ export const MatchReport: React.FC<MatchReportProps> = ({ match, compact }) => {
|
||||
variant="light"
|
||||
color="yellow"
|
||||
icon={<LightningIcon size={18} weight="fill" />}
|
||||
title={`${teamName(reportingTeam) ?? "The other team"} just reported a score`}
|
||||
title={t`${teamName(reportingTeam) ?? t`The other team`} just reported a score`}
|
||||
>
|
||||
Does this look right?
|
||||
<Trans>Does this look right?</Trans>
|
||||
</Alert>
|
||||
|
||||
<Paper withBorder p="md">
|
||||
<Group justify="center" gap="md" wrap="nowrap">
|
||||
<Stack gap={2} align="center" style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text size="sm" fw={600} ta="center" lineClamp={1}>
|
||||
{teamName(match.home) ?? "Home"}
|
||||
{teamName(match.home) ?? t({ message: "Home", context: "match team" })}
|
||||
</Text>
|
||||
<Text fz={34} fw={700} lh={1.2}>
|
||||
{match.reported_home_cups}
|
||||
@@ -172,7 +174,7 @@ export const MatchReport: React.FC<MatchReportProps> = ({ match, compact }) => {
|
||||
</Text>
|
||||
<Stack gap={2} align="center" style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text size="sm" fw={600} ta="center" lineClamp={1}>
|
||||
{teamName(match.away) ?? "Away"}
|
||||
{teamName(match.away) ?? t({ message: "Away", context: "match team" })}
|
||||
</Text>
|
||||
<Text fz={34} fw={700} lh={1.2}>
|
||||
{match.reported_away_cups}
|
||||
@@ -188,7 +190,7 @@ export const MatchReport: React.FC<MatchReportProps> = ({ match, compact }) => {
|
||||
onClick={submitConfirm}
|
||||
loading={confirm.isPending}
|
||||
>
|
||||
Confirm {scoreText}
|
||||
<Trans>Confirm {scoreText}</Trans>
|
||||
</Button>
|
||||
<Button
|
||||
fullWidth
|
||||
@@ -196,7 +198,7 @@ export const MatchReport: React.FC<MatchReportProps> = ({ match, compact }) => {
|
||||
onClick={dismissTakeover}
|
||||
disabled={confirm.isPending}
|
||||
>
|
||||
Not right — enter our score
|
||||
<Trans>Not right — enter our score</Trans>
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
@@ -220,7 +222,7 @@ export const MatchReport: React.FC<MatchReportProps> = ({ match, compact }) => {
|
||||
onClick={openForm}
|
||||
loading={report.isPending}
|
||||
>
|
||||
Report Score
|
||||
<Trans>Report Score</Trans>
|
||||
</Button>
|
||||
{formSheetEl}
|
||||
</>
|
||||
@@ -237,11 +239,13 @@ export const MatchReport: React.FC<MatchReportProps> = ({ match, compact }) => {
|
||||
gap={compact ? 4 : "xs"}
|
||||
>
|
||||
<Text size="xs" c="dimmed" lineClamp={2} style={{ minWidth: 0 }}>
|
||||
Waiting for {teamName(opposingTeam) ?? "the other team"} to confirm{" "}
|
||||
·{" "}
|
||||
<Text span fw={600} c="bright">
|
||||
{scoreText}
|
||||
</Text>
|
||||
<Trans>
|
||||
Waiting for {teamName(opposingTeam) ?? t`the other team`} to confirm{" "}
|
||||
·{" "}
|
||||
<Text span fw={600} c="bright">
|
||||
{scoreText}
|
||||
</Text>
|
||||
</Trans>
|
||||
</Text>
|
||||
<Button
|
||||
size={size}
|
||||
@@ -251,7 +255,7 @@ export const MatchReport: React.FC<MatchReportProps> = ({ match, compact }) => {
|
||||
loading={clear.isPending}
|
||||
style={{ flexShrink: 0 }}
|
||||
>
|
||||
Cancel
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
</Flex>
|
||||
);
|
||||
@@ -262,10 +266,12 @@ export const MatchReport: React.FC<MatchReportProps> = ({ match, compact }) => {
|
||||
<>
|
||||
<Stack gap={6}>
|
||||
<Text size="xs" c="dimmed" lineClamp={2}>
|
||||
{teamName(reportingTeam) ?? "Opponent"} reported{" "}
|
||||
<Text span fw={600} c="bright">
|
||||
{scoreText}
|
||||
</Text>
|
||||
<Trans>
|
||||
{teamName(reportingTeam) ?? t`Opponent`} reported{" "}
|
||||
<Text span fw={600} c="bright">
|
||||
{scoreText}
|
||||
</Text>
|
||||
</Trans>
|
||||
</Text>
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
<Button
|
||||
@@ -275,7 +281,7 @@ export const MatchReport: React.FC<MatchReportProps> = ({ match, compact }) => {
|
||||
loading={confirm.isPending}
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
Confirm
|
||||
<Trans>Confirm</Trans>
|
||||
</Button>
|
||||
<Button
|
||||
size={size}
|
||||
@@ -283,7 +289,7 @@ export const MatchReport: React.FC<MatchReportProps> = ({ match, compact }) => {
|
||||
onClick={openForm}
|
||||
disabled={confirm.isPending}
|
||||
>
|
||||
Not right
|
||||
<Trans>Not right</Trans>
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
@@ -294,7 +300,9 @@ export const MatchReport: React.FC<MatchReportProps> = ({ match, compact }) => {
|
||||
|
||||
return (
|
||||
<Text size="xs" c="dimmed" lineClamp={2}>
|
||||
Pending: {scoreText} · reported by {teamName(reportingTeam) ?? "a team"}
|
||||
<Trans>
|
||||
Pending: {scoreText} · reported by {teamName(reportingTeam) ?? t`a team`}
|
||||
</Trans>
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { SeedBadge } from "./seed-badge";
|
||||
import { TeamInfo } from "@/features/teams/types";
|
||||
import AnimatedScore from "@/features/matches/components/animated-score";
|
||||
import classes from "./match-slot.module.css";
|
||||
import { Trans } from "@lingui/react/macro";
|
||||
|
||||
export type MatchSlotState = "winner" | "correct" | "incorrect";
|
||||
|
||||
@@ -97,11 +98,15 @@ export const MatchSlot: React.FC<MatchSlotProps> = ({
|
||||
</Text>
|
||||
) : from ? (
|
||||
<Text c="dimmed" size="xs" truncate style={{ minWidth: 0, flex: 1 }}>
|
||||
{from_loser ? "Loser" : "Winner"} of Match {from}
|
||||
{from_loser ? (
|
||||
<Trans>Loser of Match {from}</Trans>
|
||||
) : (
|
||||
<Trans>Winner of Match {from}</Trans>
|
||||
)}
|
||||
</Text>
|
||||
) : (
|
||||
<Text c="dimmed" size="xs" truncate style={{ minWidth: 0, flex: 1 }}>
|
||||
TBD
|
||||
<Trans>TBD</Trans>
|
||||
</Text>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Flex, Text, Select, Card } from "@mantine/core";
|
||||
import { useLingui } from "@lingui/react/macro";
|
||||
|
||||
interface Team {
|
||||
id: string;
|
||||
@@ -11,9 +12,10 @@ interface SeedListProps {
|
||||
}
|
||||
|
||||
export function SeedList({ teams, onSeedChange }: SeedListProps) {
|
||||
const { t } = useLingui();
|
||||
const seedOptions = teams.map((_, index) => ({
|
||||
value: index.toString(),
|
||||
label: `Seed ${index + 1}`,
|
||||
label: t`Seed ${index + 1}`,
|
||||
}));
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
import { msg } from "@lingui/core/macro";
|
||||
import type { I18n } from "@lingui/core";
|
||||
|
||||
export interface GroupConfig {
|
||||
num_groups: number;
|
||||
advance_per_group: number;
|
||||
}
|
||||
|
||||
const wildcardMsg = msg`Wildcard {n}`;
|
||||
const groupRankMsg = msg`{group} {rank, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}`;
|
||||
|
||||
export const formatWildcardLabel = (i18n: I18n, n: number): string =>
|
||||
i18n._({ ...wildcardMsg, values: { n } });
|
||||
|
||||
export const formatGroupRankLabel = (
|
||||
i18n: I18n,
|
||||
group: string,
|
||||
rank: number
|
||||
): string => i18n._({ ...groupRankMsg, values: { group, rank } });
|
||||
|
||||
export function getGroupLabel(
|
||||
i18n: I18n,
|
||||
seed: number | undefined,
|
||||
groupConfig: GroupConfig | undefined
|
||||
): string | undefined {
|
||||
@@ -19,7 +35,7 @@ export function getGroupLabel(
|
||||
|
||||
if (seed > totalQualifiedTeams && wildcardsNeeded > 0) {
|
||||
const wildcardNumber = seed - totalQualifiedTeams;
|
||||
return `Wildcard ${wildcardNumber}`;
|
||||
return formatWildcardLabel(i18n, wildcardNumber);
|
||||
}
|
||||
|
||||
const pairIndex = Math.floor((seed - 1) / 2);
|
||||
@@ -31,19 +47,15 @@ export function getGroupLabel(
|
||||
|
||||
const rank = rankIndex + 1;
|
||||
const groupName = groupNames[groupIndex] || `${groupIndex + 1}`;
|
||||
const rankSuffix =
|
||||
rank === 1 ? "1st" : rank === 2 ? "2nd" : rank === 3 ? "3rd" : `${rank}th`;
|
||||
|
||||
return `${groupName} ${rankSuffix}`;
|
||||
return formatGroupRankLabel(i18n, groupName, rank);
|
||||
} else {
|
||||
const groupIndex = (pairIndex + 1) % numGroups;
|
||||
const rankIndex = advancePerGroup - 1 - Math.floor(pairIndex / numGroups);
|
||||
|
||||
const rank = rankIndex + 1;
|
||||
const groupName = groupNames[groupIndex] || `${groupIndex + 1}`;
|
||||
const rankSuffix =
|
||||
rank === 1 ? "1st" : rank === 2 ? "2nd" : rank === 3 ? "3rd" : `${rank}th`;
|
||||
|
||||
return `${groupName} ${rankSuffix}`;
|
||||
return formatGroupRankLabel(i18n, groupName, rank);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user