diff --git a/messages/de.json b/messages/de.json new file mode 100644 index 0000000..178d073 --- /dev/null +++ b/messages/de.json @@ -0,0 +1,47 @@ +{ + "nav": { + "dashboard": "Dashboard", + "teams": { + "label": "Teams", + "overview": "รœbersicht", + "manage": "Teamverwaltung" + }, + "players": { + "label": "Spieler", + "overview": "รœbersicht", + "stats": "Statistiken" + }, + "schedule": "Spielplan" + }, + "dashboard": { + "title": "Willkommen im Dashboard!" + }, + "sidebar": { + "brand": "Iron:e", + "language": { + "de": "Deutsch", + "en": "Englisch" + }, + "footer": { + "login": "Mit Steam anmelden", + "profile": "Profil", + "team": "Team", + "settings": "Einstellungen", + "administration": "Administration", + "signout": "Abmelden" + } + }, + "game-banner": { + "disconnected": "Verbindung getrennt", + "player-connected": "Spieler verbunden", + "open-game": "Spiel รถffnen", + "quit": "Verlassen", + "reconnect": "Neu verbinden" + }, + "matches": { + "title": "Geplante Matches", + "description": "Keine Matches geplant.", + "filter": "Nur mein Team anzeigen", + "create-match": "Neues Match erstellen" + } +} \ No newline at end of file diff --git a/messages/en.json b/messages/en.json new file mode 100644 index 0000000..cdce50e --- /dev/null +++ b/messages/en.json @@ -0,0 +1,47 @@ +{ + "nav": { + "dashboard": "Dashboard", + "teams": { + "label": "Teams", + "overview": "Overview", + "manage": "Team Management" + }, + "players": { + "label": "Players", + "overview": "Overview", + "stats": "Statistics" + }, + "schedule": "Schedule" + }, + "dashboard": { + "title": "Welcome!" + }, + "sidebar": { + "brand": "Iron:e", + "language": { + "de": "German", + "en": "English" + }, + "footer": { + "login": "Login with Steam", + "profile": "Profile", + "team": "Team", + "settings": "Settings", + "administration": "Administration", + "signout": "Sign out" + } + }, + "game-banner": { + "disconnected": "Disconnected", + "player-connected": "Players connected", + "open-game": "Open game", + "quit": "Quit", + "reconnect": "Reconnect" + }, + "matches": { + "title": "Scheduled matches", + "description": "No matches scheduled.", + "filter": "Show my team only", + "create-match": "Create new match" + } +} diff --git a/next.config.ts b/next.config.ts index a159e5f..21a6a2e 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,4 +1,5 @@ import type { NextConfig } from 'next' +import createNextIntlPlugin from 'next-intl/plugin'; const nextConfig: NextConfig = { allowedDevOrigins: ['ironieopen.local', '*.ironieopen.local'], @@ -28,4 +29,5 @@ const nextConfig: NextConfig = { }, } -export default nextConfig +const withNextIntl = createNextIntlPlugin(); +export default withNextIntl(nextConfig); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a99d164..b33059a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,7 +33,7 @@ "nanoid": "^5.1.5", "next": "15.3.0", "next-auth-steam": "^0.4.0", - "next-intl": "^4.3.4", + "next-intl": "^4.3.9", "next-themes": "^0.4.6", "node-cron": "^3.0.3", "node-fetch": "^3.3.2", @@ -6117,9 +6117,9 @@ } }, "node_modules/next-intl": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.3.4.tgz", - "integrity": "sha512-VWLIDlGbnL/o4LnveJTJD1NOYN8lh3ZAGTWw2krhfgg53as3VsS4jzUVnArJdqvwtlpU/2BIDbWTZ7V4o1jFEw==", + "version": "4.3.9", + "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.3.9.tgz", + "integrity": "sha512-4oSROHlgy8a5Qr2vH69wxo9F6K0uc6nZM2GNzqSe6ET79DEzOmBeSijCRzD5txcI4i+XTGytu4cxFsDXLKEDpQ==", "funding": [ { "type": "individual", @@ -6130,7 +6130,7 @@ "dependencies": { "@formatjs/intl-localematcher": "^0.5.4", "negotiator": "^1.0.0", - "use-intl": "^4.3.4" + "use-intl": "^4.3.9" }, "peerDependencies": { "next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0", @@ -8013,9 +8013,9 @@ } }, "node_modules/use-intl": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.3.4.tgz", - "integrity": "sha512-sHfiU0QeJ1rirNWRxvCyvlSh9+NczcOzRnPyMeo2rtHXhVnBsvMRjE+UG4eh3lRhCxrvcqei/I0lBxsc59on1w==", + "version": "4.3.9", + "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.3.9.tgz", + "integrity": "sha512-bZu+h13HIgOvsoGleQtUe4E6gM49CRm+AH36KnJVB/qb1+Beo7jr7HNrR8YWH8oaOkQfGNm6vh0HTepxng8UTg==", "license": "MIT", "dependencies": { "@formatjs/fast-memoize": "^2.2.0", diff --git a/package.json b/package.json index 766f020..e18fe70 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "nanoid": "^5.1.5", "next": "15.3.0", "next-auth-steam": "^0.4.0", - "next-intl": "^4.3.4", + "next-intl": "^4.3.9", "next-themes": "^0.4.6", "node-cron": "^3.0.3", "node-fetch": "^3.3.2", diff --git a/src/app/admin/[tab]/page.tsx b/src/app/[locale]/admin/[tab]/page.tsx similarity index 81% rename from src/app/admin/[tab]/page.tsx rename to src/app/[locale]/admin/[tab]/page.tsx index db6a905..ec64ee0 100644 --- a/src/app/admin/[tab]/page.tsx +++ b/src/app/[locale]/admin/[tab]/page.tsx @@ -1,9 +1,9 @@ 'use client' import { notFound, usePathname } from 'next/navigation' -import Card from '@/app/components/Card' -import MatchesAdminManager from '@/app/components/admin/MatchesAdminManager' -import AdminTeamsView from '@/app/components/admin/teams/AdminTeamsView' +import Card from '../components/Card' +import MatchesAdminManager from '../components/admin/MatchesAdminManager' +import AdminTeamsView from '../components/admin/teams/AdminTeamsView' export default function AdminPage() { const pathname = usePathname() diff --git a/src/app/admin/layout.tsx b/src/app/[locale]/admin/layout.tsx similarity index 84% rename from src/app/admin/layout.tsx rename to src/app/[locale]/admin/layout.tsx index d64eb38..41d8200 100644 --- a/src/app/admin/layout.tsx +++ b/src/app/[locale]/admin/layout.tsx @@ -1,5 +1,5 @@ -import { Tabs } from '@/app/components/Tabs' -import Tab from '@/app/components/Tab' +import { Tabs } from '../components/Tabs' +import Tab from '../components/Tab' export default function AdminLayout({ children }: { children: React.ReactNode }) { return ( diff --git a/src/app/admin/page.tsx b/src/app/[locale]/admin/page.tsx similarity index 100% rename from src/app/admin/page.tsx rename to src/app/[locale]/admin/page.tsx diff --git a/src/app/admin/server/page.tsx b/src/app/[locale]/admin/server/page.tsx similarity index 92% rename from src/app/admin/server/page.tsx rename to src/app/[locale]/admin/server/page.tsx index 0275a45..8f9a50c 100644 --- a/src/app/admin/server/page.tsx +++ b/src/app/[locale]/admin/server/page.tsx @@ -1,12 +1,12 @@ // /src/app/admin/server/page.tsx import { getServerSession } from 'next-auth' -import { authOptions } from '@/app/lib/auth' -import { prisma } from '@/app/lib/prisma' +import { authOptions } from '@/lib/auth' +import { prisma } from '@/lib/prisma' import { redirect } from 'next/navigation' import { revalidatePath } from 'next/cache' import { NextRequest } from 'next/server' -import Card from '@/app/components/Card' -import ServerView from '@/app/components/admin/server/ServerView' +import Card from '../components/Card' +import ServerView from '../components/admin/server/ServerView' export const dynamic = 'force-dynamic' diff --git a/src/app/admin/teams/[teamId]/TeamAdminClient.tsx b/src/app/[locale]/admin/teams/[teamId]/TeamAdminClient.tsx similarity index 91% rename from src/app/admin/teams/[teamId]/TeamAdminClient.tsx rename to src/app/[locale]/admin/teams/[teamId]/TeamAdminClient.tsx index cb84f59..2630ba6 100644 --- a/src/app/admin/teams/[teamId]/TeamAdminClient.tsx +++ b/src/app/[locale]/admin/teams/[teamId]/TeamAdminClient.tsx @@ -3,11 +3,11 @@ import { useCallback, useEffect, useState, useRef } from 'react' import { useSession } from 'next-auth/react' -import LoadingSpinner from '@/app/components/LoadingSpinner' -import TeamMemberView from '@/app/components/TeamMemberView' -import { useTeamStore } from '@/app/lib/stores' -import { reloadTeam } from '@/app/lib/sse-actions' -import type { Player } from '@/app/types/team' +import LoadingSpinner from '../components/LoadingSpinner' +import TeamMemberView from '../components/TeamMemberView' +import { useTeamStore } from '@/lib/stores' +import { reloadTeam } from '@/lib/sse-actions' +import type { Player } from '../types/team' type Props = { teamId: string } diff --git a/src/app/admin/teams/[teamId]/page.tsx b/src/app/[locale]/admin/teams/[teamId]/page.tsx similarity index 100% rename from src/app/admin/teams/[teamId]/page.tsx rename to src/app/[locale]/admin/teams/[teamId]/page.tsx diff --git a/src/app/admin/teams/page.tsx b/src/app/[locale]/admin/teams/page.tsx similarity index 66% rename from src/app/admin/teams/page.tsx rename to src/app/[locale]/admin/teams/page.tsx index dfef165..09eb214 100644 --- a/src/app/admin/teams/page.tsx +++ b/src/app/[locale]/admin/teams/page.tsx @@ -2,8 +2,8 @@ 'use client' -import Card from '@/app/components/Card' -import AdminTeamsView from '@/app/components/admin/teams/AdminTeamsView' +import Card from '../components/Card' +import AdminTeamsView from '../components/admin/teams/AdminTeamsView' export default function AdminTeamsPage() { return ( diff --git a/src/app/components/Alert.tsx b/src/app/[locale]/components/Alert.tsx similarity index 100% rename from src/app/components/Alert.tsx rename to src/app/[locale]/components/Alert.tsx diff --git a/src/app/components/AudioPrimer.tsx b/src/app/[locale]/components/AudioPrimer.tsx similarity index 96% rename from src/app/components/AudioPrimer.tsx rename to src/app/[locale]/components/AudioPrimer.tsx index 4e9b97c..9a45b1b 100644 --- a/src/app/components/AudioPrimer.tsx +++ b/src/app/[locale]/components/AudioPrimer.tsx @@ -1,6 +1,6 @@ 'use client' import { useEffect } from 'react' -import { sound } from '@/app/lib/soundManager' +import { sound } from '@/lib/soundManager'; export default function AudioPrimer() { useEffect(() => { diff --git a/src/app/components/Button.tsx b/src/app/[locale]/components/Button.tsx similarity index 100% rename from src/app/components/Button.tsx rename to src/app/[locale]/components/Button.tsx diff --git a/src/app/components/Card.tsx b/src/app/[locale]/components/Card.tsx similarity index 100% rename from src/app/components/Card.tsx rename to src/app/[locale]/components/Card.tsx diff --git a/src/app/components/Chart.tsx b/src/app/[locale]/components/Chart.tsx similarity index 100% rename from src/app/components/Chart.tsx rename to src/app/[locale]/components/Chart.tsx diff --git a/src/app/components/ComboBox.tsx b/src/app/[locale]/components/ComboBox.tsx similarity index 100% rename from src/app/components/ComboBox.tsx rename to src/app/[locale]/components/ComboBox.tsx diff --git a/src/app/components/CommunityMatchList.tsx b/src/app/[locale]/components/CommunityMatchList.tsx similarity index 96% rename from src/app/components/CommunityMatchList.tsx rename to src/app/[locale]/components/CommunityMatchList.tsx index d18c9e3..fc682c2 100644 --- a/src/app/components/CommunityMatchList.tsx +++ b/src/app/[locale]/components/CommunityMatchList.tsx @@ -1,17 +1,18 @@ 'use client' import { useEffect, useState, useCallback } from 'react' -import { useSession } from 'next-auth/react' -import { useRouter } from 'next/navigation' -import Link from 'next/link' -import Image from 'next/image' -import { format } from 'date-fns' // ๐Ÿ‘ˆ neu -import { de } from 'date-fns/locale' -import Switch from '@/app/components/Switch' -import Button from './Button' -import Modal from './Modal' -import { Match } from '../types/match' -import { useSSEStore } from '@/app/lib/useSSEStore' +import { useSession } from 'next-auth/react' +import { useRouter, usePathname } from '@/i18n/navigation' +import { useTranslations, useLocale } from 'next-intl' +import Link from 'next/link' +import Image from 'next/image' +import { format } from 'date-fns' +import { de } from 'date-fns/locale' +import Switch from '../components/Switch' +import Button from './Button' +import Modal from './Modal' +import { Match } from '../../../types/match' +import { useSSEStore } from '@/lib/useSSEStore' type Props = { matchType?: string } @@ -57,7 +58,12 @@ function getMapVoteState(m: Match, nowMs: number) { export default function CommunityMatchList({ matchType }: Props) { const { data: session } = useSession() - const router = useRouter() + const router = useRouter() + const pathname = usePathname() + const locale = useLocale() + + const tMatches = useTranslations('matches') + const { lastEvent } = useSSEStore() const [matches, setMatches] = useState([]) @@ -272,18 +278,18 @@ export default function CommunityMatchList({ matchType }: Props) { {/* Kopfzeile */}

- Geplante Matches + {tMatches("title")}

{session?.user?.isAdmin && ( )}
@@ -291,7 +297,7 @@ export default function CommunityMatchList({ matchType }: Props) { {/* Inhalt */} {grouped.length === 0 ? ( -

Keine Matches geplant.

+

{tMatches("description")}

) : (
{grouped.map(([dateKey, dayMatches], dayIdx) => { diff --git a/src/app/components/CompRankBadge.tsx b/src/app/[locale]/components/CompRankBadge.tsx similarity index 99% rename from src/app/components/CompRankBadge.tsx rename to src/app/[locale]/components/CompRankBadge.tsx index f990d7b..c5f1df3 100644 --- a/src/app/components/CompRankBadge.tsx +++ b/src/app/[locale]/components/CompRankBadge.tsx @@ -1,5 +1,6 @@ // CompRankBadge.tsx 'use client'; + import Image from 'next/image'; import Tooltip from './Tooltip'; diff --git a/src/app/components/CreateTeamButton.tsx b/src/app/[locale]/components/CreateTeamButton.tsx similarity index 100% rename from src/app/components/CreateTeamButton.tsx rename to src/app/[locale]/components/CreateTeamButton.tsx diff --git a/src/app/components/DatePickerWithTime.tsx b/src/app/[locale]/components/DatePickerWithTime.tsx similarity index 100% rename from src/app/components/DatePickerWithTime.tsx rename to src/app/[locale]/components/DatePickerWithTime.tsx diff --git a/src/app/components/Dropdown.tsx b/src/app/[locale]/components/Dropdown.tsx similarity index 100% rename from src/app/components/Dropdown.tsx rename to src/app/[locale]/components/Dropdown.tsx diff --git a/src/app/components/DroppableZone.tsx b/src/app/[locale]/components/DroppableZone.tsx similarity index 98% rename from src/app/components/DroppableZone.tsx rename to src/app/[locale]/components/DroppableZone.tsx index 8c716f2..71ec2f4 100644 --- a/src/app/components/DroppableZone.tsx +++ b/src/app/[locale]/components/DroppableZone.tsx @@ -1,7 +1,7 @@ 'use client' import { useDroppable, useDndContext } from '@dnd-kit/core' -import { Player } from '../types/team' +import { Player } from '../../../types/team' import clsx from 'clsx' type DroppableZoneProps = { diff --git a/src/app/components/EditButton.tsx b/src/app/[locale]/components/EditButton.tsx similarity index 100% rename from src/app/components/EditButton.tsx rename to src/app/[locale]/components/EditButton.tsx diff --git a/src/app/components/EditMatchMetaModal.tsx b/src/app/[locale]/components/EditMatchMetaModal.tsx similarity index 98% rename from src/app/components/EditMatchMetaModal.tsx rename to src/app/[locale]/components/EditMatchMetaModal.tsx index 7718ecd..1145ac7 100644 --- a/src/app/components/EditMatchMetaModal.tsx +++ b/src/app/[locale]/components/EditMatchMetaModal.tsx @@ -2,10 +2,10 @@ 'use client' import { useEffect, useMemo, useRef, useState } from 'react' -import Modal from '@/app/components/Modal' -import Alert from '@/app/components/Alert' -import Select from '@/app/components/Select' -import LoadingSpinner from '@/app/components/LoadingSpinner' // โฌ…๏ธ NEU +import Modal from '../components/Modal' +import Alert from '../components/Alert' +import Select from '../components/Select' +import LoadingSpinner from '../components/LoadingSpinner' // โฌ…๏ธ NEU type TeamOption = { id: string; name: string; logo?: string | null } diff --git a/src/app/components/EditMatchPlayersModal.tsx b/src/app/[locale]/components/EditMatchPlayersModal.tsx similarity index 96% rename from src/app/components/EditMatchPlayersModal.tsx rename to src/app/[locale]/components/EditMatchPlayersModal.tsx index db967b2..5c4707f 100644 --- a/src/app/components/EditMatchPlayersModal.tsx +++ b/src/app/[locale]/components/EditMatchPlayersModal.tsx @@ -14,12 +14,12 @@ import { SortableContext, verticalListSortingStrategy, } from '@dnd-kit/sortable' -import Modal from '@/app/components/Modal' -import SortableMiniCard from '@/app/components/SortableMiniCard' -import LoadingSpinner from '@/app/components/LoadingSpinner' -import { DroppableZone } from '@/app/components/DroppableZone' +import Modal from '../components/Modal' +import SortableMiniCard from '../components/SortableMiniCard' +import LoadingSpinner from '../components/LoadingSpinner' +import { DroppableZone } from '../components/DroppableZone' -import type { Player, Team } from '@/app/types/team' +import type { Player, Team } from '../../../types/team' /* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Typen โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ export type EditSide = 'A' | 'B' diff --git a/src/app/components/Input.tsx b/src/app/[locale]/components/Input.tsx similarity index 100% rename from src/app/components/Input.tsx rename to src/app/[locale]/components/Input.tsx diff --git a/src/app/components/InvitePlayersModal.tsx b/src/app/[locale]/components/InvitePlayersModal.tsx similarity index 99% rename from src/app/components/InvitePlayersModal.tsx rename to src/app/[locale]/components/InvitePlayersModal.tsx index 7c43bbf..c8d1881 100644 --- a/src/app/components/InvitePlayersModal.tsx +++ b/src/app/[locale]/components/InvitePlayersModal.tsx @@ -5,7 +5,7 @@ import Modal from './Modal' import MiniCard from './MiniCard' import { useSession } from 'next-auth/react' import LoadingSpinner from './LoadingSpinner' -import { Player, Team } from '../types/team' +import { Player, Team } from '../../../types/team' import Pagination from './Pagination' import { AnimatePresence, motion } from 'framer-motion' diff --git a/src/app/components/LeaveTeamModal.tsx b/src/app/[locale]/components/LeaveTeamModal.tsx similarity index 96% rename from src/app/components/LeaveTeamModal.tsx rename to src/app/[locale]/components/LeaveTeamModal.tsx index ccf5c52..665ff38 100644 --- a/src/app/components/LeaveTeamModal.tsx +++ b/src/app/[locale]/components/LeaveTeamModal.tsx @@ -4,8 +4,8 @@ import { useState, useEffect } from 'react' import Modal from './Modal' import MiniCard from './MiniCard' import { useSession } from 'next-auth/react' -import { Player, Team } from '../types/team' -import { leaveTeam } from '../lib/sse-actions' +import { Player, Team } from '../../../types/team' +import { leaveTeam } from '@/lib/sse-actions' type Props = { show: boolean diff --git a/src/app/components/LoadingSpinner.tsx b/src/app/[locale]/components/LoadingSpinner.tsx similarity index 100% rename from src/app/components/LoadingSpinner.tsx rename to src/app/[locale]/components/LoadingSpinner.tsx diff --git a/src/app/components/MapVoteBanner.tsx b/src/app/[locale]/components/MapVoteBanner.tsx similarity index 99% rename from src/app/components/MapVoteBanner.tsx rename to src/app/[locale]/components/MapVoteBanner.tsx index e3b9cfd..f1c2f24 100644 --- a/src/app/components/MapVoteBanner.tsx +++ b/src/app/[locale]/components/MapVoteBanner.tsx @@ -4,8 +4,8 @@ import { useCallback, useEffect, useMemo, useState } from 'react' import { useRouter } from 'next/navigation' import { useSession } from 'next-auth/react' -import { useSSEStore } from '@/app/lib/useSSEStore' -import type { MapVoteState } from '../types/mapvote' +import { useSSEStore } from '@/lib/useSSEStore' +import type { MapVoteState } from '../../../types/mapvote' type Props = { match: any diff --git a/src/app/components/MapVotePanel.tsx b/src/app/[locale]/components/MapVotePanel.tsx similarity index 99% rename from src/app/components/MapVotePanel.tsx rename to src/app/[locale]/components/MapVotePanel.tsx index ac8caec..7fcc2b4 100644 --- a/src/app/components/MapVotePanel.tsx +++ b/src/app/[locale]/components/MapVotePanel.tsx @@ -7,15 +7,15 @@ import { useRouter } from 'next/navigation' import type React from 'react' import { AnimatePresence, motion } from 'framer-motion' import { useSession } from 'next-auth/react' -import { useSSEStore } from '@/app/lib/useSSEStore' -import { useReadyOverlayStore } from '@/app/lib/useReadyOverlayStore' +import { useSSEStore } from '@/lib/useSSEStore' +import { useReadyOverlayStore } from '@/lib/useReadyOverlayStore' import MapVoteProfileCard from './MapVoteProfileCard' import TeamPremierRankBadge from './TeamPremierRankBadge' import Button from './Button' import LoadingSpinner from './LoadingSpinner' -import type { Match, MatchPlayer } from '../types/match' -import type { MapVoteState } from '../types/mapvote' -import { MAP_OPTIONS } from '../lib/mapOptions' +import type { Match, MatchPlayer } from '../../../types/match' +import type { MapVoteState } from '../../../types/mapvote' +import { MAP_OPTIONS } from '@/lib/mapOptions' /* =================== Utilities & constants =================== */ diff --git a/src/app/components/MapVoteProfileCard.tsx b/src/app/[locale]/components/MapVoteProfileCard.tsx similarity index 100% rename from src/app/components/MapVoteProfileCard.tsx rename to src/app/[locale]/components/MapVoteProfileCard.tsx diff --git a/src/app/components/MatchDetails.tsx b/src/app/[locale]/components/MatchDetails.tsx similarity index 99% rename from src/app/components/MatchDetails.tsx rename to src/app/[locale]/components/MatchDetails.tsx index 640b656..5424539 100644 --- a/src/app/components/MatchDetails.tsx +++ b/src/app/[locale]/components/MatchDetails.tsx @@ -14,12 +14,12 @@ import EditMatchMetaModal from './EditMatchMetaModal' import EditMatchPlayersModal from './EditMatchPlayersModal' import type { EditSide } from './EditMatchPlayersModal' -import type { Match, MatchPlayer } from '../types/match' +import type { Match, MatchPlayer } from '../../../types/match' import Button from './Button' -import { MAP_OPTIONS } from '../lib/mapOptions' +import { MAP_OPTIONS } from '@/lib/mapOptions' import MapVoteBanner from './MapVoteBanner' -import { useSSEStore } from '@/app/lib/useSSEStore' -import { Team } from '../types/team' +import { useSSEStore } from '@/lib/useSSEStore' +import { Team } from '../../../types/team' import Alert from './Alert' import Image from 'next/image' import Link from 'next/link' diff --git a/src/app/components/MatchPlayerCard.tsx b/src/app/[locale]/components/MatchPlayerCard.tsx similarity index 97% rename from src/app/components/MatchPlayerCard.tsx rename to src/app/[locale]/components/MatchPlayerCard.tsx index cbfdb93..7c793b7 100644 --- a/src/app/components/MatchPlayerCard.tsx +++ b/src/app/[locale]/components/MatchPlayerCard.tsx @@ -1,6 +1,6 @@ import Table from './Table' import Image from 'next/image' -import { MatchPlayer } from '@/app/types/match' +import { MatchPlayer } from '../../../types/match' type Props = { player: MatchPlayer diff --git a/src/app/components/MatchReadyOverlay.tsx b/src/app/[locale]/components/MatchReadyOverlay.tsx similarity index 99% rename from src/app/components/MatchReadyOverlay.tsx rename to src/app/[locale]/components/MatchReadyOverlay.tsx index 999209a..f86185a 100644 --- a/src/app/components/MatchReadyOverlay.tsx +++ b/src/app/[locale]/components/MatchReadyOverlay.tsx @@ -2,11 +2,11 @@ 'use client' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { sound } from '@/app/lib/soundManager' -import { useSSEStore } from '@/app/lib/useSSEStore' +import { sound } from '@/lib/soundManager' +import { useSSEStore } from '@/lib/useSSEStore' import { useSession } from 'next-auth/react' import LoadingSpinner from './LoadingSpinner' -import { MAP_OPTIONS } from '../lib/mapOptions' +import { MAP_OPTIONS } from '@/lib/mapOptions' type Props = { open: boolean diff --git a/src/app/components/MatchTeamCard.tsx b/src/app/[locale]/components/MatchTeamCard.tsx similarity index 98% rename from src/app/components/MatchTeamCard.tsx rename to src/app/[locale]/components/MatchTeamCard.tsx index 1950f22..b51f4d9 100644 --- a/src/app/components/MatchTeamCard.tsx +++ b/src/app/[locale]/components/MatchTeamCard.tsx @@ -1,7 +1,7 @@ 'use client' -import { Team } from '@/app/types/team' -import { MatchPlayer } from '../types/match' +import { Team } from '../../../types/team' +import { MatchPlayer } from '../../../types/match' import MatchPlayerCard from './MatchPlayerCard' import Image from 'next/image' import Button from './Button' diff --git a/src/app/components/MiniCard.tsx b/src/app/[locale]/components/MiniCard.tsx similarity index 100% rename from src/app/components/MiniCard.tsx rename to src/app/[locale]/components/MiniCard.tsx diff --git a/src/app/components/MiniCardDummy.tsx b/src/app/[locale]/components/MiniCardDummy.tsx similarity index 100% rename from src/app/components/MiniCardDummy.tsx rename to src/app/[locale]/components/MiniCardDummy.tsx diff --git a/src/app/components/Modal.tsx b/src/app/[locale]/components/Modal.tsx similarity index 100% rename from src/app/components/Modal.tsx rename to src/app/[locale]/components/Modal.tsx diff --git a/src/app/components/NoTeamView.tsx b/src/app/[locale]/components/NoTeamView.tsx similarity index 98% rename from src/app/components/NoTeamView.tsx rename to src/app/[locale]/components/NoTeamView.tsx index 72bd343..3f9a3d1 100644 --- a/src/app/components/NoTeamView.tsx +++ b/src/app/[locale]/components/NoTeamView.tsx @@ -3,9 +3,9 @@ import { useEffect, useMemo, useState } from 'react' import { useSession } from 'next-auth/react' import TeamCard from './TeamCard' -import type { Team, Player } from '../types/team' -import { useSSEStore } from '@/app/lib/useSSEStore' -import { TEAM_EVENTS, INVITE_EVENTS } from '../lib/sseEvents' +import type { Team, Player } from '../../../types/team' +import { useSSEStore } from '@/lib/useSSEStore' +import { TEAM_EVENTS, INVITE_EVENTS } from '@/lib/sseEvents' type Props = { initialTeams: Team[] diff --git a/src/app/components/NotificationBell.tsx b/src/app/[locale]/components/NotificationBell.tsx similarity index 98% rename from src/app/components/NotificationBell.tsx rename to src/app/[locale]/components/NotificationBell.tsx index ba1f414..027991c 100644 --- a/src/app/components/NotificationBell.tsx +++ b/src/app/[locale]/components/NotificationBell.tsx @@ -4,9 +4,9 @@ import { useEffect, useState, useRef } from 'react' import NotificationCenter from './NotificationCenter' import { useSession } from 'next-auth/react' import { useRouter } from 'next/navigation' -import { useSSEStore } from '@/app/lib/useSSEStore' -import { NOTIFICATION_EVENTS, isSseEventType } from '../lib/sseEvents' -import { useUiChromeStore } from '@/app/lib/useUiChromeStore' +import { useSSEStore } from '@/lib/useSSEStore' +import { NOTIFICATION_EVENTS, isSseEventType } from '@/lib/sseEvents' +import { useUiChromeStore } from '@/lib/useUiChromeStore' type Notification = { id: string diff --git a/src/app/components/NotificationCenter.tsx b/src/app/[locale]/components/NotificationCenter.tsx similarity index 100% rename from src/app/components/NotificationCenter.tsx rename to src/app/[locale]/components/NotificationCenter.tsx diff --git a/src/app/components/Pagination.tsx b/src/app/[locale]/components/Pagination.tsx similarity index 100% rename from src/app/components/Pagination.tsx rename to src/app/[locale]/components/Pagination.tsx diff --git a/src/app/components/PlayerCard.tsx b/src/app/[locale]/components/PlayerCard.tsx similarity index 97% rename from src/app/components/PlayerCard.tsx rename to src/app/[locale]/components/PlayerCard.tsx index ab71b59..94f5279 100644 --- a/src/app/components/PlayerCard.tsx +++ b/src/app/[locale]/components/PlayerCard.tsx @@ -1,7 +1,7 @@ 'use client' import Image from 'next/image' -import { Player, Team } from '@/app/types/team' +import { Player, Team } from '../../../types/team' export type CardWidth = | 'sm' // max-w-sm (24rem) diff --git a/src/app/components/Popover.tsx b/src/app/[locale]/components/Popover.tsx similarity index 100% rename from src/app/components/Popover.tsx rename to src/app/[locale]/components/Popover.tsx diff --git a/src/app/components/PrelineScript.tsx b/src/app/[locale]/components/PrelineScript.tsx similarity index 100% rename from src/app/components/PrelineScript.tsx rename to src/app/[locale]/components/PrelineScript.tsx diff --git a/src/app/components/PrelineScriptWrapper.tsx b/src/app/[locale]/components/PrelineScriptWrapper.tsx similarity index 100% rename from src/app/components/PrelineScriptWrapper.tsx rename to src/app/[locale]/components/PrelineScriptWrapper.tsx diff --git a/src/app/components/PremierRankBadge.tsx b/src/app/[locale]/components/PremierRankBadge.tsx similarity index 100% rename from src/app/components/PremierRankBadge.tsx rename to src/app/[locale]/components/PremierRankBadge.tsx diff --git a/src/app/components/Profile.tsx b/src/app/[locale]/components/Profile.tsx similarity index 100% rename from src/app/components/Profile.tsx rename to src/app/[locale]/components/Profile.tsx diff --git a/src/app/components/Providers.tsx b/src/app/[locale]/components/Providers.tsx similarity index 100% rename from src/app/components/Providers.tsx rename to src/app/[locale]/components/Providers.tsx diff --git a/src/app/components/ReadyOverlayHost.tsx b/src/app/[locale]/components/ReadyOverlayHost.tsx similarity index 96% rename from src/app/components/ReadyOverlayHost.tsx rename to src/app/[locale]/components/ReadyOverlayHost.tsx index e4610ea..88fd2a3 100644 --- a/src/app/components/ReadyOverlayHost.tsx +++ b/src/app/[locale]/components/ReadyOverlayHost.tsx @@ -3,10 +3,10 @@ import { useEffect } from 'react' import { useRouter } from 'next/navigation' import { useSession } from 'next-auth/react' -import { useSSEStore } from '@/app/lib/useSSEStore' +import { useSSEStore } from '@/lib/useSSEStore' import MatchReadyOverlay from './MatchReadyOverlay' -import { useReadyOverlayStore } from '@/app/lib/useReadyOverlayStore' -import { useMatchRosterStore } from '@/app/lib/useMatchRosterStore' // โฌ…๏ธ neu +import { useReadyOverlayStore } from '@/lib/useReadyOverlayStore' +import { useMatchRosterStore } from '@/lib/useMatchRosterStore' // ---- kleiner In-Memory Cache fรผr connectHref pro matchId const CONNECT_CACHE = new Map() diff --git a/src/app/components/Select.tsx b/src/app/[locale]/components/Select.tsx similarity index 69% rename from src/app/components/Select.tsx rename to src/app/[locale]/components/Select.tsx index 9475f80..80dcde4 100644 --- a/src/app/components/Select.tsx +++ b/src/app/[locale]/components/Select.tsx @@ -1,8 +1,9 @@ // Select.tsx -import { useState, useRef, useEffect, useMemo } from "react"; +import React, { useState, useRef, useEffect, useMemo } from "react"; import { createPortal } from "react-dom"; -type Option = { value: string; label: string }; +type Option = { value: string; label: React.ReactNode }; + type SelectProps = { options: Option[]; placeholder?: string; @@ -10,6 +11,8 @@ type SelectProps = { onChange: (value: string) => void; dropDirection?: "up" | "down" | "auto"; className?: string; + showArrow?: boolean; + fullWidth?: boolean; }; export default function Select({ @@ -18,7 +21,9 @@ export default function Select({ value, onChange, dropDirection = "down", - className + className, + showArrow = true, + fullWidth = true, }: SelectProps) { const [open, setOpen] = useState(false); const [direction, setDirection] = useState<"up" | "down">("down"); @@ -26,7 +31,7 @@ export default function Select({ const rootRef = useRef(null); const buttonRef = useRef(null); - const menuRef = useRef(null); // ๐Ÿ‘ˆ NEU + const menuRef = useRef(null); const selectedOption = useMemo(() => options.find(o => o.value === value), [options, value]); @@ -70,7 +75,6 @@ export default function Select({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [open, dropDirection]); - // Click-outside: ignoriert Klicks im Portal-Menรผ useEffect(() => { const handlePointerDown = (event: MouseEvent) => { const t = event.target as Node; @@ -85,7 +89,7 @@ export default function Select({ const Menu = open ? createPortal(
    (
  • { - onChange(option.value); - setOpen(false); - }} - className={`py-2 px-4 cursor-pointer hover:bg-gray-100 dark:hover:bg-neutral-800 dark:text-neutral-200 ${ + onClick={() => { onChange(option.value); setOpen(false); }} + className={`py-2 px-3 cursor-pointer flex items-center gap-2 hover:bg-gray-100 dark:hover:bg-neutral-800 dark:text-neutral-200 ${ option.value === value ? "bg-gray-100 dark:bg-neutral-800 font-medium" : "" }`} > @@ -119,14 +120,25 @@ export default function Select({ ref={buttonRef} type="button" onClick={() => setOpen(prev => !prev)} - className={`relative py-2 px-4 pe-10 w-full cursor-pointer bg-white border border-gray-200 rounded-lg text-start text-sm text-gray-800 hover:border-gray-300 focus:border-blue-500 focus:ring focus:ring-blue-500/50 dark:bg-neutral-900 dark:border-neutral-700 dark:text-neutral-400 ${className}`} + aria-haspopup="listbox" + aria-expanded={open} + className={`relative py-2 px-3 ${showArrow ? 'pe-9' : ''} ${fullWidth ? 'w-full' : 'w-auto inline-flex'} w-full cursor-pointer bg-white border border-gray-200 rounded-lg text-start text-sm text-gray-800 hover:border-gray-300 focus:border-blue-500 focus:ring focus:ring-blue-500/50 dark:bg-neutral-900 dark:border-neutral-700 dark:text-neutral-300 ${className || ''}`} > - {selectedOption ? selectedOption.label : placeholder} - - - - + + {selectedOption ? selectedOption.label : placeholder} + + {showArrow && ( + // Pfeil: standardmรครŸig โ†‘; bei open gedreht โ†“ + + + + + + )} {Menu}
diff --git a/src/app/components/Sidebar.tsx b/src/app/[locale]/components/Sidebar.tsx similarity index 77% rename from src/app/components/Sidebar.tsx rename to src/app/[locale]/components/Sidebar.tsx index f81c0b4..5a4f740 100644 --- a/src/app/components/Sidebar.tsx +++ b/src/app/[locale]/components/Sidebar.tsx @@ -1,19 +1,28 @@ 'use client' import { useState, useMemo } from 'react' -import { useRouter, usePathname } from 'next/navigation' +import { useRouter, usePathname } from '@/i18n/navigation' +import { useTranslations, useLocale } from 'next-intl' import Button from './Button' import SidebarFooter from './SidebarFooter' +import Select from './Select'; +import 'flag-icons/css/flag-icons.min.css'; type Submenu = 'teams' | 'players' | null export default function Sidebar() { const router = useRouter() const pathname = usePathname() + const locale = useLocale() - const [isOpen, setIsOpen] = useState(false) // mobile drawer + // รœbersetzungen + const tNav = useTranslations('nav') + const tSidebar = useTranslations('sidebar') + + const [isOpen, setIsOpen] = useState(false) // mobile drawer const [openSubmenu, setOpenSubmenu] = useState(null) + // Aktive Route prรผfen (pathname kommt schon ohne Locale) const isActive = (path: string) => pathname === path const navBtnBase = @@ -28,12 +37,22 @@ export default function Sidebar() { const toggleSubmenu = (key: Exclude) => setOpenSubmenu(prev => (prev === key ? null : key)) + // โœ… Locale-Wechsel: gleiche Route behalten, nur Locale รคndern + const changeLocale = (nextLocale: 'en' | 'de') => { + if (nextLocale === locale) return + // pathname ist z.B. '/dashboard' โ€“ next-intl setzt das Locale + router.replace(pathname, {locale: nextLocale}) + setIsOpen(false) + } + // Gemeinsamer Inhalt (wird in Desktop-Aside und im Mobile-Drawer benutzt) const SidebarInner = useMemo( () => (
- Iron:e + + {tSidebar('brand')} + {/* Close-Button nur im mobilen Drawer sichtbar */} @@ -71,12 +91,14 @@ export default function Sidebar() { size="sm" variant="link" className={`${navBtnBase} ${idleClasses} justify-between`} + aria-expanded={openSubmenu === 'teams'} + aria-controls="submenu-teams" > - + - Teams + {tNav('teams.label')} {openSubmenu === 'teams' && ( -
    + @@ -119,6 +141,8 @@ export default function Sidebar() { size="sm" variant="link" className={`${navBtnBase} ${idleClasses} justify-between`} + aria-expanded={openSubmenu === 'players'} + aria-controls="submenu-players" > @@ -128,7 +152,7 @@ export default function Sidebar() { clipRule="evenodd" /> - Spieler + {tNav('players.label')} {openSubmenu === 'players' && ( -
      + @@ -179,19 +203,52 @@ export default function Sidebar() { - Spielplan + {tNav('schedule')}
    + {/* Language Switcher โ€“ ganz unten, mittig (mit Flaggen) */} +
    +
    +