Initial commit
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user