i18n
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { Divider, Group, Paper, Stack, Text } from "@mantine/core";
|
||||
import { Trans, useLingui } from "@lingui/react/macro";
|
||||
import { TeamInfo } from "@/features/teams/types";
|
||||
import TeamAvatar from "@/components/team-avatar";
|
||||
import PlayerAvatar from "@/components/player-avatar";
|
||||
@@ -40,10 +41,14 @@ export const MatchupSheet: React.FC<MatchupSheetProps> = ({
|
||||
away,
|
||||
isOpen,
|
||||
}) => {
|
||||
const { t } = useLingui();
|
||||
|
||||
if (!home && !away) {
|
||||
return (
|
||||
<Text size="sm" c="dimmed" ta="center" py="md">
|
||||
Pick the earlier matches first — these teams aren't decided yet.
|
||||
<Trans>
|
||||
Pick the earlier matches first — these teams aren't decided yet.
|
||||
</Trans>
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
@@ -56,15 +61,15 @@ export const MatchupSheet: React.FC<MatchupSheetProps> = ({
|
||||
<TeamRow team={home} />
|
||||
) : (
|
||||
<Text size="sm" c="dimmed">
|
||||
Home team TBD — pick the earlier matches first
|
||||
<Trans>Home team TBD — pick the earlier matches first</Trans>
|
||||
</Text>
|
||||
)}
|
||||
<Divider label="vs" labelPosition="center" />
|
||||
<Divider label={t`vs`} labelPosition="center" />
|
||||
{away ? (
|
||||
<TeamRow team={away} />
|
||||
) : (
|
||||
<Text size="sm" c="dimmed">
|
||||
Away team TBD — pick the earlier matches first
|
||||
<Trans>Away team TBD — pick the earlier matches first</Trans>
|
||||
</Text>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from "@mantine/core";
|
||||
import { CaretLeftIcon, CaretRightIcon, InfoIcon } from "@phosphor-icons/react";
|
||||
import WizardOrbIcon from "@/components/wizard-orb-icon";
|
||||
import { Trans, useLingui } from "@lingui/react/macro";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Tournament } from "@/features/tournaments/types";
|
||||
@@ -35,6 +36,7 @@ export const PredictionEditor: React.FC<PredictionEditorProps> = ({
|
||||
tournament,
|
||||
initialPicks,
|
||||
}) => {
|
||||
const { t, i18n } = useLingui();
|
||||
const navigate = useNavigate();
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const matchupSheet = useSheet();
|
||||
@@ -176,7 +178,7 @@ export const PredictionEditor: React.FC<PredictionEditorProps> = ({
|
||||
c="var(--mantine-primary-color-filled)"
|
||||
style={{ letterSpacing: 0.5 }}
|
||||
>
|
||||
{getMatchLabel(matches, activeMatch)}
|
||||
{getMatchLabel(i18n, matches, activeMatch)}
|
||||
</Text>
|
||||
</Group>
|
||||
<WinnerSelector
|
||||
@@ -195,7 +197,7 @@ export const PredictionEditor: React.FC<PredictionEditorProps> = ({
|
||||
radius="md"
|
||||
onClick={() => stepTo(-1)}
|
||||
disabled={activeIndex <= 0}
|
||||
aria-label="Previous match"
|
||||
aria-label={t`Previous match`}
|
||||
>
|
||||
<CaretLeftIcon size={16} />
|
||||
</ActionIcon>
|
||||
@@ -205,7 +207,7 @@ export const PredictionEditor: React.FC<PredictionEditorProps> = ({
|
||||
radius="md"
|
||||
onClick={() => stepTo(1)}
|
||||
disabled={activeIndex < 0 || activeIndex >= pickable.length - 1}
|
||||
aria-label="Next match"
|
||||
aria-label={t`Next match`}
|
||||
>
|
||||
<CaretRightIcon size={16} />
|
||||
</ActionIcon>
|
||||
@@ -215,7 +217,7 @@ export const PredictionEditor: React.FC<PredictionEditorProps> = ({
|
||||
radius="md"
|
||||
onClick={matchupSheet.open}
|
||||
disabled={!activeResolved?.home && !activeResolved?.away}
|
||||
aria-label="Matchup details"
|
||||
aria-label={t`Matchup details`}
|
||||
>
|
||||
<InfoIcon size={16} />
|
||||
</ActionIcon>
|
||||
@@ -229,7 +231,7 @@ export const PredictionEditor: React.FC<PredictionEditorProps> = ({
|
||||
loading={submit.isPending}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Submit
|
||||
<Trans>Submit</Trans>
|
||||
</Button>
|
||||
</Group>
|
||||
</Group>
|
||||
@@ -238,7 +240,7 @@ export const PredictionEditor: React.FC<PredictionEditorProps> = ({
|
||||
</Box>
|
||||
|
||||
<Sheet
|
||||
title={activeMatch ? getMatchLabel(matches, activeMatch) : "Matchup"}
|
||||
title={activeMatch ? getMatchLabel(i18n, matches, activeMatch) : t`Matchup`}
|
||||
{...matchupSheet.props}
|
||||
>
|
||||
<MatchupSheet
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
} from "@mantine/core";
|
||||
import { CrownIcon, InfoIcon } from "@phosphor-icons/react";
|
||||
import WizardOrbIcon from "@/components/wizard-orb-icon";
|
||||
import { Plural, Trans, useLingui } from "@lingui/react/macro";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
import { Tournament } from "@/features/tournaments/types";
|
||||
import PlayerAvatar from "@/components/player-avatar";
|
||||
@@ -28,6 +29,7 @@ interface PredictionLeaderboardProps {
|
||||
export const PredictionLeaderboard: React.FC<PredictionLeaderboardProps> = ({
|
||||
tournament,
|
||||
}) => {
|
||||
const { t } = useLingui();
|
||||
const navigate = useNavigate();
|
||||
const { data: leaderboard } = usePredictionsLeaderboard(tournament.id);
|
||||
|
||||
@@ -60,9 +62,11 @@ export const PredictionLeaderboard: React.FC<PredictionLeaderboardProps> = ({
|
||||
})
|
||||
}
|
||||
>
|
||||
{myPrediction?.prediction
|
||||
? "Edit Your Prediction"
|
||||
: "Make Your Prediction"}
|
||||
{myPrediction?.prediction ? (
|
||||
<Trans>Edit Your Prediction</Trans>
|
||||
) : (
|
||||
<Trans>Make Your Prediction</Trans>
|
||||
)}
|
||||
</Button>
|
||||
) : undefined;
|
||||
|
||||
@@ -76,11 +80,13 @@ export const PredictionLeaderboard: React.FC<PredictionLeaderboardProps> = ({
|
||||
/>
|
||||
<Stack align="center" gap={4}>
|
||||
<Title order={3} c="dimmed" ta="center">
|
||||
Predictions are open
|
||||
<Trans>Predictions are open</Trans>
|
||||
</Title>
|
||||
<Text size="sm" c="dimmed" ta="center" maw={280}>
|
||||
Other players' predictions are hidden until the tournament
|
||||
starts.
|
||||
<Trans>
|
||||
Other players' predictions are hidden until the tournament
|
||||
starts.
|
||||
</Trans>
|
||||
</Text>
|
||||
</Stack>
|
||||
{cta}
|
||||
@@ -88,7 +94,11 @@ export const PredictionLeaderboard: React.FC<PredictionLeaderboardProps> = ({
|
||||
{leaderboard.submitters.length > 0 && (
|
||||
<>
|
||||
<Text px="md" size="sm" fw={600} pb="xs">
|
||||
{leaderboard.count} bracket{leaderboard.count === 1 ? "" : "s"} in
|
||||
<Plural
|
||||
value={leaderboard.count}
|
||||
one="# bracket in"
|
||||
other="# brackets in"
|
||||
/>
|
||||
</Text>
|
||||
{leaderboard.submitters.map((player, index) => {
|
||||
const name = `${player.first_name} ${player.last_name}`;
|
||||
@@ -120,10 +130,10 @@ export const PredictionLeaderboard: React.FC<PredictionLeaderboardProps> = ({
|
||||
/>
|
||||
<Stack align="center" gap={4}>
|
||||
<Title order={3} c="dimmed" ta="center">
|
||||
No predictions
|
||||
<Trans>No predictions</Trans>
|
||||
</Title>
|
||||
<Text size="sm" c="dimmed" ta="center" maw={280}>
|
||||
Nobody made a prediction for this tournament.
|
||||
<Trans>Nobody made a prediction for this tournament.</Trans>
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
@@ -134,14 +144,14 @@ export const PredictionLeaderboard: React.FC<PredictionLeaderboardProps> = ({
|
||||
<Stack gap={0}>
|
||||
<Group px="md" justify="space-between" align="center" wrap="nowrap">
|
||||
<Text size="lg" fw={600}>
|
||||
Predictions
|
||||
<Trans>Predictions</Trans>
|
||||
</Text>
|
||||
<Popover position="bottom-end" withArrow shadow="md">
|
||||
<Popover.Target>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
aria-label="How prediction scoring works"
|
||||
aria-label={t`How prediction scoring works`}
|
||||
>
|
||||
<InfoIcon size={14} />
|
||||
</ActionIcon>
|
||||
@@ -149,42 +159,50 @@ export const PredictionLeaderboard: React.FC<PredictionLeaderboardProps> = ({
|
||||
<Popover.Dropdown>
|
||||
<Box maw={280}>
|
||||
<Text size="sm" fw={500} mb="xs">
|
||||
Prediction Scoring:
|
||||
<Trans>Prediction Scoring:</Trans>
|
||||
</Text>
|
||||
<Text size="xs" mb={2}>
|
||||
• Each correct pick earns points, doubling every round
|
||||
<Trans>
|
||||
• Each correct pick earns points, doubling every round
|
||||
</Trans>
|
||||
</Text>
|
||||
<Text size="xs" mb={2}>
|
||||
• <strong>Winners bracket:</strong> 10, 20, 40, 80…
|
||||
<Trans>
|
||||
• <strong>Winners bracket:</strong> 10, 20, 40, 80…
|
||||
</Trans>
|
||||
</Text>
|
||||
<Text size="xs" mb={2}>
|
||||
• <strong>Losers bracket:</strong> 5, 10, 20, 40…
|
||||
<Trans>
|
||||
• <strong>Losers bracket:</strong> 5, 10, 20, 40…
|
||||
</Trans>
|
||||
</Text>
|
||||
<Text size="xs" mb={2}>
|
||||
• <strong>Bracket reset:</strong> only picked if your bracket
|
||||
triggers it — worth double the Final
|
||||
<Trans>
|
||||
• <strong>Bracket reset:</strong> only picked if your
|
||||
bracket triggers it — worth double the Final
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<Divider my="sm" />
|
||||
|
||||
<Text size="sm" fw={500} mb="xs">
|
||||
Tiebreakers:
|
||||
<Trans>Tiebreakers:</Trans>
|
||||
</Text>
|
||||
<Text size="xs" mb={2}>
|
||||
1. Correct champion pick
|
||||
<Trans>1. Correct champion pick</Trans>
|
||||
</Text>
|
||||
<Text size="xs" mb={2}>
|
||||
2. Earlier submission
|
||||
<Trans>2. Earlier submission</Trans>
|
||||
</Text>
|
||||
<Text size="xs" mt="xs" c="dimmed">
|
||||
* PICKS shows correct picks / total picks made
|
||||
<Trans>* PICKS shows correct picks / total picks made</Trans>
|
||||
</Text>
|
||||
</Box>
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
</Group>
|
||||
<Text px="md" c="dimmed" size="xs" fw={500}>
|
||||
Correct picks are worth more each round
|
||||
<Trans>Correct picks are worth more each round</Trans>
|
||||
</Text>
|
||||
{leaderboard.entries.map((entry, index) => {
|
||||
const name = `${entry.player.first_name} ${entry.player.last_name}`;
|
||||
@@ -240,7 +258,7 @@ export const PredictionLeaderboard: React.FC<PredictionLeaderboardProps> = ({
|
||||
<Group gap="md" wrap="nowrap">
|
||||
<Stack gap={0} ta="center">
|
||||
<Text size="xs" c="dimmed" fw={700}>
|
||||
PTS
|
||||
<Trans>PTS</Trans>
|
||||
</Text>
|
||||
<Text size="sm" fw={700}>
|
||||
{entry.points}
|
||||
@@ -248,7 +266,7 @@ export const PredictionLeaderboard: React.FC<PredictionLeaderboardProps> = ({
|
||||
</Stack>
|
||||
<Stack gap={0} ta="center">
|
||||
<Text size="xs" c="dimmed" fw={700}>
|
||||
PICKS
|
||||
<Trans>PICKS</Trans>
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
{entry.correct}/{entry.total}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Card, Flex, Text } from "@mantine/core";
|
||||
import { Trans } from "@lingui/react/macro";
|
||||
import React from "react";
|
||||
import { MatchSlot, MatchSlotState } from "@/features/bracket/components/match-slot";
|
||||
import { Match } from "@/features/matches/types";
|
||||
@@ -104,7 +105,7 @@ export const PredictionMatchCard: React.FC<PredictionMatchCardProps> = ({
|
||||
c="dimmed"
|
||||
fw="bold"
|
||||
>
|
||||
* If necessary
|
||||
<Trans>* If necessary</Trans>
|
||||
</Text>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { Group, Text, UnstyledButton } from "@mantine/core";
|
||||
import { Trans } from "@lingui/react/macro";
|
||||
import { CrownIcon } from "@phosphor-icons/react";
|
||||
import { TeamInfo } from "@/features/teams/types";
|
||||
import TeamAvatar from "@/components/team-avatar";
|
||||
@@ -35,7 +36,7 @@ const TeamChip = ({
|
||||
}}
|
||||
>
|
||||
<Text size="xs" c="dimmed">
|
||||
TBD
|
||||
<Trans>TBD</Trans>
|
||||
</Text>
|
||||
</Group>
|
||||
);
|
||||
@@ -102,7 +103,7 @@ export const WinnerSelector: React.FC<WinnerSelectorProps> = ({
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
<Text size="xs" c="dimmed" fw={700}>
|
||||
vs
|
||||
<Trans>vs</Trans>
|
||||
</Text>
|
||||
<TeamChip
|
||||
team={away}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useLingui } from "@lingui/react/macro";
|
||||
import {
|
||||
useServerMutation,
|
||||
useServerSuspenseQuery,
|
||||
@@ -43,10 +44,11 @@ export const usePlayerPrediction = (tournamentId: string, playerId: string) =>
|
||||
|
||||
export const useSubmitPrediction = (tournamentId: string) => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useLingui();
|
||||
|
||||
return useServerMutation({
|
||||
mutationFn: submitPrediction,
|
||||
successMessage: "Prediction saved!",
|
||||
successMessage: t`Prediction saved!`,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: predictionKeys.tournament(tournamentId),
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { msg } from "@lingui/core/macro";
|
||||
import type { I18n } from "@lingui/core";
|
||||
import { Match } from "@/features/matches/types";
|
||||
import { Team, TeamInfo } from "@/features/teams/types";
|
||||
import { Tournament } from "@/features/tournaments/types";
|
||||
@@ -150,8 +152,12 @@ export const isPredictionComplete = (
|
||||
);
|
||||
};
|
||||
|
||||
export const getMatchLabel = (matches: Match[], match: Match): string => {
|
||||
if (match.reset) return "Bracket Reset";
|
||||
export const getMatchLabel = (
|
||||
i18n: I18n,
|
||||
matches: Match[],
|
||||
match: Match
|
||||
): string => {
|
||||
if (match.reset) return i18n._(msg`Bracket Reset`);
|
||||
|
||||
const winners = matches.filter(
|
||||
(m) => isBracketMatch(m) && !m.reset && !m.is_losers_bracket
|
||||
@@ -161,24 +167,24 @@ export const getMatchLabel = (matches: Match[], match: Match): string => {
|
||||
!highest || current.lid > highest.lid ? current : highest,
|
||||
undefined
|
||||
);
|
||||
if (!grandFinal) return `Match ${match.order}`;
|
||||
if (!grandFinal) return i18n._({ ...msg`Match {order}`, values: { order: match.order } });
|
||||
|
||||
const hasLosersBracket = matches.some(
|
||||
(m) => isBracketMatch(m) && m.is_losers_bracket
|
||||
);
|
||||
|
||||
if (match.lid === grandFinal.lid) return "Final";
|
||||
if (match.lid === grandFinal.lid) return i18n._(msg`Final`);
|
||||
if (
|
||||
hasLosersBracket &&
|
||||
!match.is_losers_bracket &&
|
||||
grandFinal.home_from_lid === match.lid
|
||||
) {
|
||||
return "Winners Bracket Final";
|
||||
return i18n._(msg`Winners Bracket Final`);
|
||||
}
|
||||
if (match.is_losers_bracket && grandFinal.away_from_lid === match.lid) {
|
||||
return "Losers Bracket Final";
|
||||
return i18n._(msg`Losers Bracket Final`);
|
||||
}
|
||||
return `Match ${match.order}`;
|
||||
return i18n._({ ...msg`Match {order}`, values: { order: match.order } });
|
||||
};
|
||||
|
||||
export type PickResult = "correct" | "incorrect" | "pending";
|
||||
|
||||
Reference in New Issue
Block a user