Files
baya-monorepo/client/src/app/[locale]/layout.tsx
T
2026-06-21 00:05:07 +03:30

109 lines
3.8 KiB
TypeScript

import type { ReactNode } from 'react';
import type { Metadata, Viewport } from 'next';
import localFont from 'next/font/local';
import { setRequestLocale, getMessages } from 'next-intl/server';
import { NextIntlClientProvider } from 'next-intl';
import { getThemeMode } from '@/lib/cookies/server';
import { AppStoreProvider } from '@/store';
import { ThemeProvider, getDirection } from '@/theme';
import { BRAND } from '@/theme/colors';
import { NotistackProvider } from '@/lib/toast';
import { QueryProvider } from '@/lib/query/QueryProvider';
import { routing } from '@/i18n/routing';
import '../globals.css';
import '@/theme/tokens.css';
/*
* This is the application's ROOT layout — it renders <html> and <body>.
*
* Why <html> lives here and NOT in a layout above the [locale] segment:
* `lang` and `dir` must track the active locale, and the only layout that
* re-renders when the locale changes is the one keyed on the [locale] param.
* A layout placed above [locale] is shared between /fa and /en, so it is
* statically cached with the defaultLocale and never re-renders on a locale
* switch — leaving `dir`/`lang` frozen on the default ('fa'/'rtl'). Sourcing
* the locale from URL params here means no header reads, no caching surprises.
*
* setRequestLocale(locale) is called first so that any server component
* deeper in the tree that calls getLocale() / getTranslations() gets the
* right locale from React.cache instead of falling through to the header
* fallback. getMessages({ locale }) passes the locale explicitly so the
* config callback in src/i18n/request.ts receives it via requestLocale
* directly (Promise.resolve(locale)) rather than reading it from the cache.
*/
// FA brand font — Mikhak, a free Persian typeface.
// preload: false + conditional className (below) ensure the woff2 files are
// only fetched on Persian routes, never on /en.
const mikhak = localFont({
src: [
{ path: '../fonts/Mikhak-Regular.woff2', weight: '400', style: 'normal' },
{ path: '../fonts/Mikhak-Medium.woff2', weight: '500', style: 'normal' },
{ path: '../fonts/Mikhak-Bold.woff2', weight: '700', style: 'normal' },
],
display: 'swap',
variable: '--font-mikhak',
preload: false,
});
export const viewport: Viewport = {
themeColor: BRAND.teal,
};
export const metadata: Metadata = {
title: 'Balinyaar | بالین‌یار',
description: 'Balinyaar web application',
manifest: '/site.webmanifest',
};
export default async function LocaleLayout({
children,
params,
}: {
children: ReactNode;
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
const safeLocale = routing.locales.includes(locale as (typeof routing.locales)[number])
? locale
: routing.defaultLocale;
setRequestLocale(safeLocale);
const messages = await getMessages({ locale: safeLocale });
const { colorScheme, defaultMode } = await getThemeMode();
const dir = getDirection(safeLocale);
// Only attach the Mikhak font variable on RTL (Persian) routes so the
// Persian typeface is not loaded for English pages.
const fontClassName = safeLocale === 'fa' ? mikhak.variable : undefined;
return (
<html
lang={safeLocale}
dir={dir}
className={fontClassName}
data-mui-color-scheme={colorScheme}
>
<body>
<NextIntlClientProvider locale={safeLocale} messages={messages}>
<AppStoreProvider>
<ThemeProvider dir={dir} defaultMode={defaultMode}>
<QueryProvider>
<NotistackProvider>
{children}
</NotistackProvider>
</QueryProvider>
</ThemeProvider>
</AppStoreProvider>
</NextIntlClientProvider>
</body>
</html>
);
}
export function generateStaticParams() {
return routing.locales.map((locale) => ({ locale }));
}