828a81f3a5
- 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
84 lines
2.7 KiB
TypeScript
84 lines
2.7 KiB
TypeScript
import { content } from "@/content/pages";
|
|
import { localize, type Locale } from "@/i18n/config";
|
|
import { hrefFor, industryNav, serviceNav } from "./routes";
|
|
|
|
export type SectionKind = "text" | "cards" | "steps" | "faq" | "links" | "stats" | "offices" | "showcase";
|
|
|
|
/** A page as stored in the JSON files: CTAs are plain labels from the copy deck. */
|
|
type RawItem = {
|
|
kicker: string | null;
|
|
title: string;
|
|
subtitle: string | null;
|
|
body: string[];
|
|
list: string[];
|
|
cta: string | null;
|
|
num?: string;
|
|
};
|
|
|
|
type RawSection = {
|
|
eyebrow: string | null;
|
|
title: string | null;
|
|
intro: string[];
|
|
items: RawItem[];
|
|
outro: string[];
|
|
ctas: string[];
|
|
form: boolean;
|
|
kind: SectionKind | string;
|
|
};
|
|
|
|
type RawPage = {
|
|
slug: string;
|
|
path: string;
|
|
meta: { title: string | null; description: string | null };
|
|
hero: RawSection;
|
|
sections: RawSection[];
|
|
};
|
|
|
|
/** A page ready to render: every CTA carries its localized label and destination. */
|
|
export type Cta = { label: string; href: string };
|
|
export type Item = Omit<RawItem, "cta"> & { cta: Cta | null };
|
|
export type Section = Omit<RawSection, "ctas" | "items"> & { ctas: Cta[]; items: Item[] };
|
|
export type Page = Omit<RawPage, "hero" | "sections"> & {
|
|
locale: Locale;
|
|
hero: Section;
|
|
sections: Section[];
|
|
};
|
|
|
|
type Key = keyof (typeof content)["en"];
|
|
|
|
// Destinations come from the English labels, which the route map understands; translated pages
|
|
// share the English structure, so the same positions line up.
|
|
function resolveSection(s: RawSection, en: RawSection, path: string, locale: Locale): Section {
|
|
const to = (label: string) => localize(hrefFor(label, path), locale);
|
|
return {
|
|
...s,
|
|
ctas: s.ctas.map((label, i) => ({ label, href: to(en.ctas[i] ?? label) })),
|
|
items: s.items.map((it, i) => ({
|
|
...it,
|
|
cta: it.cta ? { label: it.cta, href: to(en.items[i]?.cta ?? it.cta) } : null,
|
|
})),
|
|
};
|
|
}
|
|
|
|
function getPage(locale: Locale, key: Key): Page {
|
|
const raw = content[locale][key] as RawPage;
|
|
const en = content.en[key] as RawPage;
|
|
return {
|
|
...raw,
|
|
locale,
|
|
hero: resolveSection(raw.hero, en.hero, en.path, locale),
|
|
sections: raw.sections.map((s, i) => resolveSection(s, en.sections[i], en.path, locale)),
|
|
};
|
|
}
|
|
|
|
export const getStaticPage = (locale: Locale, key: "about" | "contact" | "services" | "industries") =>
|
|
getPage(locale, key);
|
|
|
|
export function getService(locale: Locale, slug: string): Page | undefined {
|
|
return serviceNav.some((s) => s.slug === slug) ? getPage(locale, `services_${slug}` as Key) : undefined;
|
|
}
|
|
|
|
export function getIndustry(locale: Locale, slug: string): Page | undefined {
|
|
return industryNav.some((s) => s.slug === slug) ? getPage(locale, `industries_${slug}` as Key) : undefined;
|
|
}
|