109 lines
3.8 KiB
TypeScript
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 }));
|
|
}
|