Stage M1
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import type { Metadata, Viewport } from 'next';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { hasLocale, NextIntlClientProvider } from 'next-intl';
|
||||
import { getTranslations, setRequestLocale } from 'next-intl/server';
|
||||
|
||||
import { LOCALE_TAGS, type Locale } from '@sport/types';
|
||||
|
||||
import { routing } from '@/i18n/routing';
|
||||
|
||||
import '@/styles/globals.css';
|
||||
|
||||
/**
|
||||
* This is the root layout — there is no `app/layout.tsx`. Every route lives
|
||||
* under `[locale]`, so the `<html lang>` attribute can be set from the resolved
|
||||
* locale rather than hard-coded, which screen readers and translation tools
|
||||
* both depend on.
|
||||
*/
|
||||
export function generateStaticParams() {
|
||||
return routing.locales.map((locale) => ({ locale }));
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ locale: string }>;
|
||||
}): Promise<Metadata> {
|
||||
const { locale } = await params;
|
||||
const t = await getTranslations({ locale, namespace: 'meta' });
|
||||
|
||||
return {
|
||||
title: { default: t('defaultTitle'), template: `%s | ${t('siteName')}` },
|
||||
description: t('defaultDescription'),
|
||||
metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL ?? 'http://localhost:3000'),
|
||||
// Tells search engines these are translations of one another rather than
|
||||
// duplicate content — the single most important i18n SEO signal.
|
||||
alternates: {
|
||||
canonical: '/',
|
||||
languages: { vi: '/', en: '/en' },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const viewport: Viewport = {
|
||||
themeColor: '#111111',
|
||||
width: 'device-width',
|
||||
initialScale: 1,
|
||||
};
|
||||
|
||||
export default async function LocaleLayout({
|
||||
children,
|
||||
params,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
params: Promise<{ locale: string }>;
|
||||
}) {
|
||||
const { locale } = await params;
|
||||
|
||||
if (!hasLocale(routing.locales, locale)) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
// Required for static rendering: without it every page under this layout
|
||||
// opts into dynamic rendering the moment it reads a translation.
|
||||
setRequestLocale(locale);
|
||||
|
||||
return (
|
||||
<html lang={LOCALE_TAGS[locale as Locale]} suppressHydrationWarning>
|
||||
<body className="min-h-screen antialiased">
|
||||
<NextIntlClientProvider>{children}</NextIntlClientProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user