Add Spanish and Arabic versions, a contact form captcha and a multilingual sitemap

- English, Spanish (/es) and Arabic (/ar, right to left) versions of every page, with a language switch
- Self-hosted "I'm not a robot" check on the contact form; the message field is optional
- Google Tag Manager, GA4 and Search Console verification in the shared layout
- Sitemap, robots.txt and canonical links point at aigrowthtechnologies.com
This commit is contained in:
Alejandro Veras
2026-10-13 11:53:18 -04:00
parent e6d06d6fd0
commit 828a81f3a5
105 changed files with 21097 additions and 788 deletions
+49 -15
View File
@@ -4,10 +4,41 @@ import Link from "next/link";
import { usePathname } from "next/navigation";
import { useEffect, useState } from "react";
import Logo from "./logo";
import { mainNav } from "@/lib/routes";
import { localeNames, localeShort, locales, localize, splitLocale, type Locale } from "@/i18n/config";
import type { NavItem } from "@/i18n/nav";
export default function SiteHeader() {
export type HeaderLabels = {
primary: string;
bookCall: string;
openMenu: string;
closeMenu: string;
language: string;
logoHome: string;
};
function LanguageSwitch({ locale, path, label }: { locale: Locale; path: string; label: string }) {
return (
<nav className="lang-switch" aria-label={label}>
{locales.map((l) =>
// A plain link: switching language swaps the document's lang and direction.
l === locale ? (
<span key={l} aria-current="true" lang={l} title={localeNames[l]}>
{localeShort[l]}
</span>
) : (
<a key={l} href={localize(path, l)} hrefLang={l} lang={l} title={localeNames[l]}>
{localeShort[l]}
</a>
),
)}
</nav>
);
}
export default function SiteHeader({ locale, nav, t }: { locale: Locale; nav: NavItem[]; t: HeaderLabels }) {
const pathname = usePathname();
const { path } = splitLocale(pathname);
const current = localize(path, locale);
const [scrolled, setScrolled] = useState(false);
const [hidden, setHidden] = useState(false);
const [open, setOpen] = useState(false);
@@ -46,7 +77,8 @@ export default function SiteHeader() {
return () => window.removeEventListener("keydown", onKey);
}, [open]);
const isActive = (href: string) => pathname === href || pathname.startsWith(href + "/");
const isActive = (href: string) => current === href || current.startsWith(href + "/");
const contactHref = localize("/contact", locale);
const classes = ["site-header", scrolled || open ? "is-scrolled" : "", hidden && !open ? "is-hidden" : ""]
.filter(Boolean)
.join(" ");
@@ -55,9 +87,9 @@ export default function SiteHeader() {
<>
<header className={classes}>
<div className="wrap bar">
<Logo />
<nav className="nav" aria-label="Primary">
{mainNav.map((l) =>
<Logo href={localize("/", locale)} label={t.logoHome} />
<nav className="nav" aria-label={t.primary}>
{nav.map((l) =>
l.children ? (
<div key={l.href} className="nav-group">
<Link href={l.href} className={isActive(l.href) ? "is-active" : undefined}>
@@ -66,12 +98,12 @@ export default function SiteHeader() {
<div className="nav-panel">
<div className="nav-panel-inner">
<Link href={l.href} className="nav-panel-all">
All {l.label} <span aria-hidden="true">→</span>
{l.allLabel} <span className="arrow" aria-hidden="true">→</span>
</Link>
<ul>
{l.children.map((c) => (
<li key={c.href}>
<Link href={c.href} className={pathname === c.href ? "is-active" : undefined}>
<Link href={c.href} className={current === c.href ? "is-active" : undefined}>
{c.label}
</Link>
</li>
@@ -87,15 +119,16 @@ export default function SiteHeader() {
),
)}
</nav>
<Link href="/contact" className="btn btn-solid header-cta">
Book a Strategy Call <span className="arrow">→</span>
<LanguageSwitch locale={locale} path={path} label={t.language} />
<Link href={contactHref} className="btn btn-solid header-cta">
{t.bookCall} <span className="arrow">→</span>
</Link>
<button
type="button"
className="menu-toggle"
aria-expanded={open}
aria-controls="mobile-menu"
aria-label={open ? "Close menu" : "Open menu"}
aria-label={open ? t.closeMenu : t.openMenu}
onClick={() => setOpen((v) => !v)}
>
<span />
@@ -105,10 +138,10 @@ export default function SiteHeader() {
</header>
<div id="mobile-menu" className={`mobile-menu${open ? " is-open" : ""}`} aria-hidden={!open} inert={!open}>
<ul>
{mainNav.map((l) => (
{nav.map((l) => (
<li key={l.href}>
<Link href={l.href}>
{l.label} <span aria-hidden="true">→</span>
{l.label} <span className="arrow" aria-hidden="true">→</span>
</Link>
{l.children && (
<div className="mobile-sub">
@@ -122,8 +155,9 @@ export default function SiteHeader() {
</li>
))}
</ul>
<Link href="/contact" className="btn btn-solid">
Book a Strategy Call <span className="arrow">→</span>
<LanguageSwitch locale={locale} path={path} label={t.language} />
<Link href={contactHref} className="btn btn-solid">
{t.bookCall} <span className="arrow">→</span>
</Link>
</div>
</>