返回 CodeWhale
layout.tsx
根目录 / web / app / [locale] / layout.tsx
1 import type { Metadata } from "next";
2 import { Fraunces, IBM_Plex_Sans, JetBrains_Mono, Noto_Serif_SC } from "next/font/google";
3 import { Nav } from "@/components/nav";
4 import { Footer } from "@/components/footer";
5 import { locales, type Locale } from "@/lib/i18n/config";
6 import { getChrome, getHome } from "@/lib/i18n/dictionaries";
7 import { buildPageMetadata } from "@/lib/page-meta";
8 import "../globals.css";
9
10 // Fraunces is the newspaper-era display face the community asked to keep —
11 // crisp, editorial, a little futuristic. Body stays IBM Plex for instrument feel.
12 const display = Fraunces({
13 subsets: ["latin", "vietnamese"],
14 weight: ["400", "500", "600", "700"],
15 variable: "--font-display",
16 display: "swap",
17 });
18
19 const body = IBM_Plex_Sans({
20 subsets: ["latin", "cyrillic", "vietnamese"],
21 weight: ["400", "500", "600"],
22 variable: "--font-body",
23 display: "swap",
24 });
25
26 const mono = JetBrains_Mono({
27 subsets: ["latin", "cyrillic"],
28 weight: ["400", "500", "600"],
29 variable: "--font-mono",
30 display: "swap",
31 });
32
33 // Noto Serif SC is heavy; load only what we need for decorative anchors.
34 const cjk = Noto_Serif_SC({
35 subsets: ["latin"],
36 weight: ["400", "700"],
37 variable: "--font-cjk",
38 display: "swap",
39 preload: false,
40 });
41
42 export function generateStaticParams() {
43 return locales.map((locale) => ({ locale }));
44 }
45
46 export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise<Metadata> {
47 const { locale } = await params;
48 const home = getHome(locale);
49 return buildPageMetadata({
50 path: "/",
51 locale,
52 title: home.metaTitle,
53 description: home.metaDescription,
54 });
55 }
56
57 export default async function LocaleLayout({
58 children,
59 params,
60 }: {
61 children: React.ReactNode;
62 params: Promise<{ locale: string }>;
63 }) {
64 const { locale } = await params;
65 const chrome = getChrome(locale);
66
67 return (
68 <html
69 lang={locale}
70 className={`${display.variable} ${body.variable} ${mono.variable} ${cjk.variable}`}
71 suppressHydrationWarning
72 >
73 <body>
74 {/* Apply the persisted docs theme before paint so there is no flash.
75 "auto" leaves data-theme unset and defers to prefers-color-scheme. */}
76 <script
77 dangerouslySetInnerHTML={{
78 __html:
79 "(function(){try{var t=localStorage.getItem('cw-theme');if(t==='light'||t==='dark'){document.documentElement.setAttribute('data-theme',t);}}catch(e){}})();",
80 }}
81 />
82 <a href="#main-content" className="skip-link">
83 {chrome.skipToContent}
84 </a>
85 <Nav locale={locale as Locale} />
86 <main id="main-content">{children}</main>
87 <Footer locale={locale as Locale} />
88 </body>
89 </html>
90 );
91 }
92
92 lines Plain Text