Serve the site in English, Spanish and Arabic, add a captcha, make the message optional
- Move every route under app/[lang]; a proxy keeps English on unprefixed URLs and serves /es and /ar - Interface strings and homepage copy per locale; content pages load a translated JSON when one exists - Language switch in the header, hreflang alternates and a multilingual sitemap - Right-to-left layout for Arabic with logical CSS properties, Noto Sans Arabic and no letter-spacing - Self-hosted proof-of-work "I'm not a robot" check (no third-party account) replaces Turnstile - The message field is optional; failed attempts refill the form instead of clearing it
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import { Suspense } from "react";
|
||||
import type { Metadata, Viewport } from "next";
|
||||
import { Inter, Noto_Sans_Arabic } from "next/font/google";
|
||||
import "../globals.css";
|
||||
import SiteHeader from "@/components/site-header";
|
||||
import SiteFooter from "@/components/site-footer";
|
||||
import { dirOf, locales, localize, ogLocale } from "@/i18n/config";
|
||||
import { mainNav } from "@/i18n/nav";
|
||||
import { getUI } from "@/i18n/server";
|
||||
import { localeAlternates } from "@/lib/metadata";
|
||||
import { SITE_URL } from "@/lib/routes";
|
||||
|
||||
const inter = Inter({
|
||||
variable: "--font-inter",
|
||||
subsets: ["latin"],
|
||||
display: "swap",
|
||||
});
|
||||
|
||||
// Only Arabic pages use it, so it isn't preloaded everywhere.
|
||||
const arabic = Noto_Sans_Arabic({
|
||||
variable: "--font-arabic",
|
||||
subsets: ["arabic"],
|
||||
display: "swap",
|
||||
preload: false,
|
||||
});
|
||||
|
||||
export function generateStaticParams() {
|
||||
return locales.map((lang) => ({ lang }));
|
||||
}
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const { locale, t } = await getUI();
|
||||
return {
|
||||
metadataBase: new URL(SITE_URL),
|
||||
title: { default: t.meta.title, template: "%s" },
|
||||
description: t.meta.description,
|
||||
alternates: localeAlternates("/", locale),
|
||||
openGraph: {
|
||||
type: "website",
|
||||
url: localize("/", locale),
|
||||
siteName: "AI Growth Technologies",
|
||||
locale: ogLocale[locale],
|
||||
title: t.meta.title,
|
||||
description: t.meta.description,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const viewport: Viewport = {
|
||||
themeColor: "#000000",
|
||||
colorScheme: "dark",
|
||||
};
|
||||
|
||||
export default async function RootLayout({ children }: LayoutProps<"/[lang]">) {
|
||||
const { locale, t } = await getUI();
|
||||
const headerLabels = {
|
||||
primary: t.nav.primary,
|
||||
bookCall: t.bookCall,
|
||||
openMenu: t.nav.openMenu,
|
||||
closeMenu: t.nav.closeMenu,
|
||||
language: t.nav.language,
|
||||
logoHome: t.logoHome,
|
||||
};
|
||||
|
||||
return (
|
||||
<html
|
||||
lang={locale}
|
||||
dir={dirOf(locale)}
|
||||
className={`${inter.variable} ${arabic.variable}`}
|
||||
data-scroll-behavior="smooth"
|
||||
>
|
||||
<body>
|
||||
<a href="#main" className="skip-link">
|
||||
{t.skip}
|
||||
</a>
|
||||
{/* The header reads the pathname, which is request data on dynamic routes. */}
|
||||
<Suspense>
|
||||
<SiteHeader locale={locale} nav={mainNav(t, locale)} t={headerLabels} />
|
||||
</Suspense>
|
||||
<main id="main">{children}</main>
|
||||
<SiteFooter />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user