| 1 | import type { Metadata } from "next"; |
| 2 | import localFont from "next/font/local"; |
| 3 | import { IBM_Plex_Mono, Newsreader } from "next/font/google"; |
| 4 | import { Nav } from "@/components/nav"; |
| 5 | import { Footer } from "@/components/footer"; |
| 6 | import { UsageCounting } from "@/components/usage-counting"; |
| 7 | import { BUILD_FACTS } from "@/lib/facts"; |
| 8 | import { localeDirection, locales, type Locale } from "@/lib/i18n/config"; |
| 9 | import { getChrome, getHome } from "@/lib/i18n/dictionaries"; |
| 10 | import { serializeJsonLd } from "@/lib/json-ld"; |
| 11 | import { buildPageMetadata } from "@/lib/page-meta"; |
| 12 | import { buildSiteJsonLd } from "@/lib/site-schema"; |
| 13 | import "../globals.css"; |
| 14 | |
| 15 | // Shannon Sans 0.110 supplies body and small-heading roles through one asset. |
| 16 | // Its OFL notice lives beside it; Newsreader and IBM Plex Mono keep their roles. |
| 17 | const sans = localFont({ |
| 18 | src: "../../public/brand/fonts/ShannonSans-Variable.woff2", |
| 19 | weight: "100 900", |
| 20 | style: "normal", |
| 21 | variable: "--font-shannon-sans", |
| 22 | display: "swap", |
| 23 | }); |
| 24 | |
| 25 | // IBM Plex Mono is the GPUI app's code face; it fills the same role here. |
| 26 | const mono = IBM_Plex_Mono({ |
| 27 | subsets: ["latin", "latin-ext", "cyrillic"], |
| 28 | weight: ["400", "500", "600"], |
| 29 | variable: "--font-mono", |
| 30 | display: "swap", |
| 31 | }); |
| 32 | |
| 33 | // Newsreader's optical-size axis is what lets the same face set a 5rem title |
| 34 | // and a 1.3rem running head without looking like two fonts. |
| 35 | const serif = Newsreader({ |
| 36 | subsets: ["latin", "latin-ext"], |
| 37 | weight: ["400", "500"], |
| 38 | style: ["normal", "italic"], |
| 39 | variable: "--font-serif", |
| 40 | display: "swap", |
| 41 | }); |
| 42 | |
| 43 | export function generateStaticParams() { |
| 44 | return locales.map((locale) => ({ locale })); |
| 45 | } |
| 46 | |
| 47 | export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise<Metadata> { |
| 48 | const { locale } = await params; |
| 49 | const home = getHome(locale); |
| 50 | return buildPageMetadata({ |
| 51 | path: "/", |
| 52 | locale, |
| 53 | title: home.metaTitle, |
| 54 | description: home.metaDescription, |
| 55 | }); |
| 56 | } |
| 57 | |
| 58 | export default async function LocaleLayout({ |
| 59 | children, |
| 60 | params, |
| 61 | }: { |
| 62 | children: React.ReactNode; |
| 63 | params: Promise<{ locale: string }>; |
| 64 | }) { |
| 65 | const { locale } = await params; |
| 66 | const chrome = getChrome(locale); |
| 67 | // RTL locales (e.g. ar) set the document direction from the canonical |
| 68 | // registry so the browser handles bidirectional layout from the root. |
| 69 | const dir = localeDirection(locale); |
| 70 | const siteJsonLd = buildSiteJsonLd(locale); |
| 71 | |
| 72 | return ( |
| 73 | <html |
| 74 | lang={locale} |
| 75 | dir={dir} |
| 76 | className={`${sans.variable} ${mono.variable} ${serif.variable}`} |
| 77 | suppressHydrationWarning |
| 78 | > |
| 79 | <body> |
| 80 | <script |
| 81 | type="application/ld+json" |
| 82 | dangerouslySetInnerHTML={{ __html: serializeJsonLd(siteJsonLd) }} |
| 83 | /> |
| 84 | {/* Apply the persisted docs theme before paint so there is no flash. |
| 85 | The site default is the paper sheet; only an explicit "dark" |
| 86 | choice re-themes the docs subtree to the whale's stage. */} |
| 87 | <script |
| 88 | dangerouslySetInnerHTML={{ |
| 89 | __html: |
| 90 | "(function(){try{var t=localStorage.getItem('cw-theme');if(t==='light'||t==='dark'){document.documentElement.setAttribute('data-theme',t);}}catch(e){}})();", |
| 91 | }} |
| 92 | /> |
| 93 | <a href="#main-content" className="skip-link"> |
| 94 | {chrome.skipToContent} |
| 95 | </a> |
| 96 | <Nav locale={locale as Locale} /> |
| 97 | <main id="main-content">{children}</main> |
| 98 | <Footer locale={locale as Locale} /> |
| 99 | {/* Aggregate usage counting, on by default — see lib/telemetry. The |
| 100 | choice lives on the privacy page; every opt-out stays off. */} |
| 101 | <UsageCounting appVersion={BUILD_FACTS.version ?? "0.0.0"} /> |
| 102 | </body> |
| 103 | </html> |
| 104 | ); |
| 105 | } |
| 106 |