feat(i18n): add locale layout and dynamic lang attribute

Add [locale] layout with locale validation and static rendering support. Update root layout to read x-next-intl-locale header for dynamic lang attribute.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
nxtkofi 2026-04-21 20:53:13 +02:00
parent c4a35e97c3
commit 85ea4e6200
2 changed files with 32 additions and 2 deletions

View file

@ -0,0 +1,26 @@
import { setRequestLocale } from 'next-intl/server';
import { hasLocale } from 'next-intl';
import { notFound } from 'next/navigation';
import { routing } from '@/i18n/routing';
export function generateStaticParams() {
return routing.locales.map((locale) => ({ locale }));
}
export default async function LocaleLayout({
children,
params,
}: {
children: React.ReactNode;
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
if (!hasLocale(routing.locales, locale)) {
notFound();
}
setRequestLocale(locale);
return <>{children}</>;
}

View file

@ -1,4 +1,5 @@
import type { Metadata } from 'next'; import type { Metadata } from 'next';
import { headers } from 'next/headers';
import './globals.css'; import './globals.css';
import { NextIntlClientProvider } from 'next-intl'; import { NextIntlClientProvider } from 'next-intl';
import { ThemeProvider } from '@wrksz/themes/next'; import { ThemeProvider } from '@wrksz/themes/next';
@ -14,14 +15,17 @@ export const metadata: Metadata = {
description: 'Create SaaS in a day!', description: 'Create SaaS in a day!',
}; };
export default function RootLayout({ export default async function RootLayout({
children, children,
}: Readonly<{ }: Readonly<{
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
const headersList = await headers();
const locale = headersList.get('x-next-intl-locale') || 'en';
return ( return (
<html <html
lang="en" lang={locale}
className={cn('h-full antialiased', 'font-mono', geistMono.variable)} className={cn('h-full antialiased', 'font-mono', geistMono.variable)}
suppressHydrationWarning suppressHydrationWarning
> >