diff --git a/app/[username]/ProfilePageClient.tsx b/app/[locale]/[username]/ProfilePageClient.tsx similarity index 97% rename from app/[username]/ProfilePageClient.tsx rename to app/[locale]/[username]/ProfilePageClient.tsx index 52148727..d04f0bd4 100644 --- a/app/[username]/ProfilePageClient.tsx +++ b/app/[locale]/[username]/ProfilePageClient.tsx @@ -1,8 +1,8 @@ 'use client' import React, { useEffect, useState } from 'react' -import { useTranslation } from 'next-i18next' -import { useRouter, useSearchParams } from 'next/navigation' +import { useTranslations } from 'next-intl' +import { useRouter, useSearchParams } from '~/i18n/navigation' import InfiniteScroll from 'react-infinite-scroll-component' // Components @@ -60,7 +60,7 @@ const ProfilePageClient: React.FC = ({ initialRaid, initialRecency }) => { - const { t } = useTranslation('common') + const t = useTranslations('common') const router = useRouter() const searchParams = useSearchParams() @@ -234,4 +234,4 @@ const ProfilePageClient: React.FC = ({ ) } -export default ProfilePageClient \ No newline at end of file +export default ProfilePageClient diff --git a/app/[username]/page.tsx b/app/[locale]/[username]/page.tsx similarity index 100% rename from app/[username]/page.tsx rename to app/[locale]/[username]/page.tsx diff --git a/app/error.tsx b/app/[locale]/error.tsx similarity index 100% rename from app/error.tsx rename to app/[locale]/error.tsx diff --git a/app/global-error.tsx b/app/[locale]/global-error.tsx similarity index 100% rename from app/global-error.tsx rename to app/[locale]/global-error.tsx diff --git a/app/[locale]/layout.tsx b/app/[locale]/layout.tsx new file mode 100644 index 00000000..bf35829e --- /dev/null +++ b/app/[locale]/layout.tsx @@ -0,0 +1,80 @@ +import { Metadata, Viewport } from 'next' +import localFont from 'next/font/local' +import { NextIntlClientProvider } from 'next-intl' +import { getMessages } from 'next-intl/server' +import { Viewport as ToastViewport } from '@radix-ui/react-toast' +import { locales } from '../../i18n.config' + +import '../../styles/globals.scss' + +// Components +import Providers from '../components/Providers' +import Header from '../components/Header' +import UpdateToastClient from '../components/UpdateToastClient' +import VersionHydrator from '../components/VersionHydrator' + +// Generate static params for all locales +export function generateStaticParams() { + return locales.map((locale) => ({ locale })) +} + +// Metadata +export const metadata: Metadata = { + title: 'granblue.team', + description: 'Create, save, and share Granblue Fantasy party compositions', +} + +// Viewport configuration (Next.js 13+ requires separate export) +export const viewport: Viewport = { + width: 'device-width', + initialScale: 1, + viewportFit: 'cover', +} + +// Font +const goalking = localFont({ + src: '../../pages/fonts/gk-variable.woff2', + fallback: ['system-ui', 'inter', 'helvetica neue', 'sans-serif'], + variable: '--font-goalking', +}) + +export default async function LocaleLayout({ + children, + params: { locale } +}: { + children: React.ReactNode + params: { locale: string } +}) { + // Load messages for the locale + const messages = await getMessages() + + // Fetch version data on the server + let version = null + try { + const baseUrl = process.env.NEXT_PUBLIC_URL || 'http://localhost:1234' + const res = await fetch(`${baseUrl}/api/version`, { + cache: 'no-store' + }) + if (res.ok) { + version = await res.json() + } + } catch (error) { + console.error('Failed to fetch version data:', error) + } + + return ( + + + + +
+ + +
{children}
+ + + + + + ) +} \ No newline at end of file diff --git a/app/new/NewPartyClient.tsx b/app/[locale]/new/NewPartyClient.tsx similarity index 91% rename from app/new/NewPartyClient.tsx rename to app/[locale]/new/NewPartyClient.tsx index 5329eb66..a2ef5f41 100644 --- a/app/new/NewPartyClient.tsx +++ b/app/[locale]/new/NewPartyClient.tsx @@ -1,8 +1,8 @@ 'use client' import React, { useEffect, useState } from 'react' -import { useTranslation } from 'next-i18next' -import { useRouter } from 'next/navigation' +import { useTranslations } from 'next-intl' +import { useRouter } from '~/i18n/navigation' import dynamic from 'next/dynamic' // Components @@ -24,7 +24,7 @@ const NewPartyClient: React.FC = ({ raidGroups, error = false }) => { - const { t } = useTranslation('common') + const t = useTranslations('common') const router = useRouter() // State for tab management @@ -76,4 +76,4 @@ const NewPartyClient: React.FC = ({ return } -export default NewPartyClient \ No newline at end of file +export default NewPartyClient diff --git a/app/new/PartyWrapper.tsx b/app/[locale]/new/PartyWrapper.tsx similarity index 100% rename from app/new/PartyWrapper.tsx rename to app/[locale]/new/PartyWrapper.tsx diff --git a/app/new/page.tsx b/app/[locale]/new/page.tsx similarity index 100% rename from app/new/page.tsx rename to app/[locale]/new/page.tsx diff --git a/app/not-found.tsx b/app/[locale]/not-found.tsx similarity index 77% rename from app/not-found.tsx rename to app/[locale]/not-found.tsx index f71f5537..8df4e3b8 100644 --- a/app/not-found.tsx +++ b/app/[locale]/not-found.tsx @@ -1,13 +1,15 @@ import { Metadata } from 'next' -import Link from 'next/link' -import { useTranslation } from 'next-i18next' +import { Link } from '~/i18n/navigation' +import { getTranslations } from 'next-intl/server' export const metadata: Metadata = { title: 'Page not found / granblue.team', description: 'The page you were looking for could not be found' } -export default function NotFound() { +export default async function NotFound() { + const t = await getTranslations('common') + return (
@@ -24,4 +26,4 @@ export default function NotFound() {
) -} \ No newline at end of file +} diff --git a/app/p/[party]/PartyPageClient.tsx b/app/[locale]/p/[party]/PartyPageClient.tsx similarity index 90% rename from app/p/[party]/PartyPageClient.tsx rename to app/[locale]/p/[party]/PartyPageClient.tsx index c8e8d3d5..46404e57 100644 --- a/app/p/[party]/PartyPageClient.tsx +++ b/app/[locale]/p/[party]/PartyPageClient.tsx @@ -1,8 +1,8 @@ 'use client' import React, { useEffect, useState } from 'react' -import { useTranslation } from 'next-i18next' -import { useRouter } from 'next/navigation' +import { useTranslations } from 'next-intl' +import { useRouter } from '~/i18n/navigation' // Utils import { appState } from '~/utils/appState' @@ -20,7 +20,7 @@ interface Props { const PartyPageClient: React.FC = ({ party, raidGroups }) => { const router = useRouter() - const { t } = useTranslation('common') + const t = useTranslations('common') // State for tab management const [selectedTab, setSelectedTab] = useState(GridType.Weapon) @@ -71,4 +71,4 @@ const PartyPageClient: React.FC = ({ party, raidGroups }) => { ) } -export default PartyPageClient \ No newline at end of file +export default PartyPageClient diff --git a/app/p/[party]/page.tsx b/app/[locale]/p/[party]/page.tsx similarity index 100% rename from app/p/[party]/page.tsx rename to app/[locale]/p/[party]/page.tsx diff --git a/app/page.tsx b/app/[locale]/page.tsx similarity index 100% rename from app/page.tsx rename to app/[locale]/page.tsx diff --git a/app/saved/SavedPageClient.tsx b/app/[locale]/saved/SavedPageClient.tsx similarity index 96% rename from app/saved/SavedPageClient.tsx rename to app/[locale]/saved/SavedPageClient.tsx index bed6ff61..d006d189 100644 --- a/app/saved/SavedPageClient.tsx +++ b/app/[locale]/saved/SavedPageClient.tsx @@ -1,8 +1,8 @@ 'use client' import React, { useEffect, useState } from 'react' -import { useTranslation } from 'next-i18next' -import { useRouter, useSearchParams } from 'next/navigation' +import { useTranslations } from 'next-intl' +import { useRouter, useSearchParams } from '~/i18n/navigation' // Components import FilterBar from '~/components/filters/FilterBar' @@ -43,7 +43,7 @@ const SavedPageClient: React.FC = ({ initialRecency, error = false }) => { - const { t } = useTranslation('common') + const t = useTranslations('common') const router = useRouter() const searchParams = useSearchParams() @@ -201,4 +201,4 @@ const SavedPageClient: React.FC = ({ ) } -export default SavedPageClient \ No newline at end of file +export default SavedPageClient diff --git a/app/saved/page.tsx b/app/[locale]/saved/page.tsx similarity index 100% rename from app/saved/page.tsx rename to app/[locale]/saved/page.tsx diff --git a/app/server-error/page.tsx b/app/[locale]/server-error/page.tsx similarity index 100% rename from app/server-error/page.tsx rename to app/[locale]/server-error/page.tsx diff --git a/app/teams/TeamsPageClient.tsx b/app/[locale]/teams/TeamsPageClient.tsx similarity index 97% rename from app/teams/TeamsPageClient.tsx rename to app/[locale]/teams/TeamsPageClient.tsx index b6be996d..c45ac130 100644 --- a/app/teams/TeamsPageClient.tsx +++ b/app/[locale]/teams/TeamsPageClient.tsx @@ -1,8 +1,8 @@ 'use client' import React, { useEffect, useState } from 'react' -import { useTranslation } from 'next-i18next' -import { useRouter, useSearchParams } from 'next/navigation' +import { useTranslations } from 'next-intl' +import { useRouter, useSearchParams } from '~/i18n/navigation' import InfiniteScroll from 'react-infinite-scroll-component' // Hooks @@ -55,7 +55,7 @@ const TeamsPageClient: React.FC = ({ initialRecency, error = false }) => { - const { t } = useTranslation('common') + const t = useTranslations('common') const router = useRouter() const searchParams = useSearchParams() @@ -245,4 +245,4 @@ const TeamsPageClient: React.FC = ({ ) } -export default TeamsPageClient \ No newline at end of file +export default TeamsPageClient diff --git a/app/teams/page.tsx b/app/[locale]/teams/page.tsx similarity index 100% rename from app/teams/page.tsx rename to app/[locale]/teams/page.tsx diff --git a/app/unauthorized/page.tsx b/app/[locale]/unauthorized/page.tsx similarity index 100% rename from app/unauthorized/page.tsx rename to app/[locale]/unauthorized/page.tsx diff --git a/app/components/Header.tsx b/app/components/Header.tsx index 2b6341d7..513e3fa1 100644 --- a/app/components/Header.tsx +++ b/app/components/Header.tsx @@ -2,11 +2,12 @@ import React, { useState } from 'react' import { deleteCookie } from 'cookies-next' -import { useRouter } from 'next/navigation' -import { useTranslation } from 'next-i18next' +import { useRouter } from '~/i18n/navigation' +import { useTranslations } from 'next-intl' +import { useLocale } from 'next-intl' import classNames from 'classnames' import clonedeep from 'lodash.clonedeep' -import Link from 'next/link' +import { Link } from '~/i18n/navigation' import { accountState, initialAccountState } from '~/utils/accountState' import { appState, initialAppState } from '~/utils/appState' @@ -37,11 +38,11 @@ import styles from '~/components/Header/index.module.scss' const Header = () => { // Localization - const { t } = useTranslation('common') + const t = useTranslations('common') + const locale = useLocale() // Router const router = useRouter() - const locale = 'en' // TODO: Update when implementing internationalization with App Router // State management const [alertOpen, setAlertOpen] = useState(false) @@ -402,4 +403,4 @@ const Header = () => { ) } -export default Header \ No newline at end of file +export default Header diff --git a/app/layout.tsx b/app/layout.tsx index 382f3d0c..b47993e8 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,49 +1,8 @@ -import { Metadata, Viewport } from 'next' -import localFont from 'next/font/local' -import { Viewport as ToastViewport } from '@radix-ui/react-toast' - -import '../styles/globals.scss' - -// Components -import Providers from './components/Providers' -import Header from './components/Header' -import UpdateToastClient from './components/UpdateToastClient' - -// Metadata -export const metadata: Metadata = { - title: 'granblue.team', - description: 'Create, save, and share Granblue Fantasy party compositions', -} - -// Viewport configuration (Next.js 13+ requires separate export) -export const viewport: Viewport = { - width: 'device-width', - initialScale: 1, - viewportFit: 'cover', -} - -// Font -const goalking = localFont({ - src: '../pages/fonts/gk-variable.woff2', - fallback: ['system-ui', 'inter', 'helvetica neue', 'sans-serif'], - variable: '--font-goalking', -}) - +// Minimal root layout - all content is handled in [locale]/layout.tsx export default function RootLayout({ children, }: { children: React.ReactNode }) { - return ( - - - -
- -
{children}
- - - - - ) + return children } \ No newline at end of file diff --git a/pages/about.tsx b/pages/about.tsx index 963c346f..8431f3c8 100644 --- a/pages/about.tsx +++ b/pages/about.tsx @@ -3,6 +3,7 @@ import Head from 'next/head' import { useRouter } from 'next/router' import { useTranslation } from 'next-i18next' +import { NextIntlClientProvider } from 'next-intl' import { serverSideTranslations } from 'next-i18next/serverSideTranslations' import { AboutTabs } from '~utils/enums' @@ -19,6 +20,7 @@ import type { NextApiRequest, NextApiResponse } from 'next' interface Props { page?: string + messages: Record } const AboutRoute: React.FC = (props: Props) => { @@ -100,35 +102,37 @@ const AboutRoute: React.FC = (props: Props) => { return (
{pageHead()} -
- - - {t('about.segmented_control.about')} - - - {t('about.segmented_control.updates')} - - - {t('about.segmented_control.roadmap')} - - - {currentSection()} -
+ +
+ + + {t('about.segmented_control.about')} + + + {t('about.segmented_control.updates')} + + + {t('about.segmented_control.roadmap')} + + + {currentSection()} +
+
) } @@ -149,6 +153,12 @@ export const getServerSideProps = async ({ req, res, locale, query }: { req: Nex props: { page: req.url?.slice(1), ...(await serverSideTranslations(locale, ['common', 'about', 'updates'])), + // Provide next-intl messages for child components using next-intl + messages: { + common: (await import(`../public/locales/${locale}/common.json`)).default, + about: (await import(`../public/locales/${locale}/about.json`)).default, + updates: (await import(`../public/locales/${locale}/updates.json`)).default, + }, // Will be passed to the page component as props }, }