132 lines
3.9 KiB
TypeScript
132 lines
3.9 KiB
TypeScript
import type { Metadata, Viewport } from "next";
|
|
import { Geist, Sora, JetBrains_Mono } from "next/font/google";
|
|
import "../globals.css";
|
|
import {
|
|
locales,
|
|
getDictionary,
|
|
isValidLocale,
|
|
defaultLocale,
|
|
type Locale,
|
|
} from "@/lib/dictionaries";
|
|
import { SITE_URL, localeAlternates } from "@/lib/seo";
|
|
import Navbar from "@/components/layout/Navbar";
|
|
import Footer from "@/components/layout/Footer";
|
|
import ScanlineOverlay from "@/components/ui/ScanlineOverlay";
|
|
import LangBanner from "@/components/ui/LangBanner";
|
|
|
|
const geistSans = Geist({
|
|
subsets: ["latin"],
|
|
variable: "--font-geist-sans",
|
|
display: "swap",
|
|
});
|
|
|
|
const sora = Sora({
|
|
subsets: ["latin"],
|
|
variable: "--font-sora",
|
|
display: "swap",
|
|
weight: ["300", "400", "500", "600", "700"],
|
|
});
|
|
|
|
const jetbrains = JetBrains_Mono({
|
|
subsets: ["latin"],
|
|
variable: "--font-jetbrains",
|
|
display: "swap",
|
|
weight: ["400", "500"],
|
|
});
|
|
|
|
// Pre-render both locale shells at build time
|
|
export function generateStaticParams() {
|
|
return locales.map((locale) => ({ locale }));
|
|
}
|
|
|
|
// Tell the browser the document is dark before any CSS/JS runs. This sets the
|
|
// <meta name="color-scheme"> / theme-color in the SSR <head> so the initial
|
|
// canvas (and mobile browser chrome) paints dark, never white.
|
|
export const viewport: Viewport = {
|
|
colorScheme: "dark",
|
|
themeColor: "#0F1117",
|
|
};
|
|
|
|
export async function generateMetadata({
|
|
params,
|
|
}: {
|
|
params: Promise<{ locale: string }>;
|
|
}): Promise<Metadata> {
|
|
const { locale } = await params;
|
|
const isSR = locale === "sr";
|
|
|
|
return {
|
|
title: {
|
|
default: isSR
|
|
? "Name — Full-stack inženjering, od početka do kraja"
|
|
: "Name — Full-stack engineering, end to end",
|
|
template: "%s · Name",
|
|
},
|
|
description: isSR
|
|
? "Premium full-stack razvoj, deployment i self-hosted infrastruktura."
|
|
: "Premium full-stack development, deployment, and self-hosted infrastructure.",
|
|
metadataBase: new URL(SITE_URL),
|
|
|
|
// Canonical — self-referencing within each locale.
|
|
// Subpages override this with their own path via localeAlternates().
|
|
alternates: localeAlternates(locale),
|
|
|
|
openGraph: {
|
|
title: isSR
|
|
? "Name — Full-stack inženjering"
|
|
: "Name — Full-stack engineering, end to end",
|
|
description: isSR
|
|
? "Premium full-stack razvoj i self-hosted infrastruktura."
|
|
: "Premium full-stack development and self-hosted infrastructure.",
|
|
type: "website",
|
|
locale: isSR ? "sr_RS" : "en_US",
|
|
alternateLocale: isSR ? ["en_US"] : ["sr_RS"],
|
|
siteName: "ksan.dev",
|
|
url: `${SITE_URL}/${locale}`,
|
|
},
|
|
|
|
twitter: {
|
|
card: "summary_large_image",
|
|
title: isSR ? "Name — Full-stack inženjering" : "Name — Full-stack engineering",
|
|
},
|
|
|
|
robots: {
|
|
index: true,
|
|
follow: true,
|
|
"max-image-preview": "large",
|
|
"max-snippet": -1,
|
|
},
|
|
};
|
|
}
|
|
|
|
export default async function LocaleLayout({
|
|
children,
|
|
params,
|
|
}: {
|
|
children: React.ReactNode;
|
|
params: Promise<{ locale: string }>;
|
|
}) {
|
|
const { locale: rawLocale } = await params;
|
|
const locale: Locale = isValidLocale(rawLocale) ? rawLocale : defaultLocale;
|
|
const dict = getDictionary(locale);
|
|
|
|
return (
|
|
<html
|
|
lang={locale}
|
|
className={`${geistSans.variable} ${sora.variable} ${jetbrains.variable}`}
|
|
// Inline so it is present in the very first HTML bytes — applied before
|
|
// the external stylesheet loads and before hydration. This is what kills
|
|
// the one-frame white flash (esp. in dev, where CSS is JS-injected).
|
|
style={{ colorScheme: "dark", backgroundColor: "#0F1117" }}
|
|
>
|
|
<body className="relative min-h-screen overflow-x-hidden">
|
|
<ScanlineOverlay />
|
|
<LangBanner locale={locale} dict={dict} />
|
|
<Navbar locale={locale} dict={dict} />
|
|
<main className="relative z-10">{children}</main>
|
|
<Footer locale={locale} dict={dict} />
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|