diff --git a/src/app/[locale]/[...rest]/page.tsx b/src/app/[locale]/[...rest]/page.tsx new file mode 100644 index 0000000..8aac932 --- /dev/null +++ b/src/app/[locale]/[...rest]/page.tsx @@ -0,0 +1,6 @@ +import { notFound } from "next/navigation" + +// Catch unknown paths below a locale so they render [locale]/not-found.tsx +export default function CatchAll() { + notFound() +} diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index a33eefe..ff19902 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -1,17 +1,24 @@ // app/[locale]/layout.tsx -import { NextIntlClientProvider } from "next-intl" -import { getLocale } from "next-intl/server" +import { hasLocale, NextIntlClientProvider } from "next-intl" +import { notFound } from "next/navigation" import LanguageSelector from "@components/ui/languageSelector" import UmamiAnalytics from "@/components/umamiAnalytics" +import { locales } from "@/i18n" import { getAnalyticsConfig } from "@/lib/analyticsConfig" export default async function LocaleLayout({ children, + params, }: { children: React.ReactNode + params: Promise<{ locale: string }> }) { - const locale = (await getLocale()) as "no" | "en" + const { locale } = await params + + // [locale] matches any first path segment, e.g. /favicon.txt or /de + if (!hasLocale(locales, locale)) notFound() + const messages = (await import(`../../../messages/${locale}.json`)).default const analyticsConfig = getAnalyticsConfig() diff --git a/src/app/[locale]/not-found.tsx b/src/app/[locale]/not-found.tsx new file mode 100644 index 0000000..d89ef8f --- /dev/null +++ b/src/app/[locale]/not-found.tsx @@ -0,0 +1,9 @@ +import NotFound from "@/components/page-sections/notFound" + +import { getLocale } from "next-intl/server" + +export default async function LocaleNotFound() { + const locale = await getLocale() + + return +} diff --git a/src/app/global-not-found.tsx b/src/app/global-not-found.tsx deleted file mode 100644 index 3aeecbe..0000000 --- a/src/app/global-not-found.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import Layout from "@/app/layout" - -import { generateSeoMetadata, generateSeoViewport } from "@/components/seo" - -import { Metadata, Viewport } from "next" -import { getTranslations } from "next-intl/server" -import { useTranslations } from "next-intl" -import Link from "next/link" - -import * as styles from "./global-not-found.module.css" - -export async function generateMetadata(): Promise { - const t = await getTranslations("404") - - return generateSeoMetadata({ - title: t("title"), - description: "Uh oh :(", - }) -} - -export function generateViewport(): Viewport { - return generateSeoViewport() -} - -const NotFoundPage = () => { - const t = useTranslations("404") - - return ( - -
-

{t("title")}

-

- {t("text")}{" "} - - 👀 - -

- - {t("back")} - -
-
- ) -} - -export default NotFoundPage diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx index fe0937a..a6b4787 100644 --- a/src/app/not-found.tsx +++ b/src/app/not-found.tsx @@ -1,5 +1,18 @@ -import { redirect } from "next/navigation" +import NotFound from "@/components/page-sections/notFound" -export default function RootPage() { - redirect("/en/404") +import { defaultLocale } from "@/i18n" + +import { Metadata } from "next" +import { getTranslations } from "next-intl/server" + +export async function generateMetadata(): Promise { + const t = await getTranslations({ locale: defaultLocale, namespace: "404" }) + + return { title: `${t("title")} | UiO Gaming` } +} + +// Rendered when [locale]/layout.tsx rejects an unknown locale (e.g. /de), +// so there is no valid locale to use +export default function RootNotFound() { + return } diff --git a/src/app/global-not-found.module.css b/src/components/page-sections/notFound.module.css similarity index 100% rename from src/app/global-not-found.module.css rename to src/components/page-sections/notFound.module.css diff --git a/src/components/page-sections/notFound.tsx b/src/components/page-sections/notFound.tsx new file mode 100644 index 0000000..7359f80 --- /dev/null +++ b/src/components/page-sections/notFound.tsx @@ -0,0 +1,28 @@ +import { getTranslations } from "next-intl/server" +import Link from "next/link" + +import * as styles from "./notFound.module.css" + +const NotFound = async ({ locale }: { locale: string }) => { + const t = await getTranslations({ locale, namespace: "404" }) + + return ( +
+

{t("title")}

+

+ {t("text")}{" "} + + 👀 + +

+ + {t("back")} + +
+ ) +} + +export default NotFound diff --git a/src/i18n.ts b/src/i18n.ts index 0af1ace..f7e2bad 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -1,15 +1,18 @@ -import { notFound } from "next/navigation" +import { hasLocale } from "next-intl" import { getRequestConfig } from "next-intl/server" -const locales = ["no", "en"] +export const locales = ["no", "en"] as const +export const defaultLocale = "no" -export default getRequestConfig(async ({ requestLocale }) => { - const locale = await requestLocale - - if (!locale || !locales.includes(locale as "no" | "en")) notFound() +export default getRequestConfig(async ({ locale, requestLocale }) => { + // An explicit locale (e.g. getTranslations({ locale })) takes precedence. + // Fall back to the default instead of throwing, so pages rendered outside + // a valid locale (like the root not-found page) can still be translated + const requested = locale ?? (await requestLocale) + const resolved = hasLocale(locales, requested) ? requested : defaultLocale return { - locale, - messages: (await import(`../messages/${locale}.json`)).default, + locale: resolved, + messages: (await import(`../messages/${resolved}.json`)).default, } })