返回 CodeWhale
nav.tsx
根目录 / web / components / nav.tsx
1 import Link from "next/link";
2 import type { Locale } from "@/lib/i18n/config";
3 import { FACTS } from "@/lib/facts.generated";
4 import { fill, getChrome } from "@/lib/i18n/dictionaries";
5 import { navLinks, REPO_URL, DISCORD_URL } from "@/lib/i18n/links";
6 import { fetchRepoStats, formatStars } from "@/lib/github";
7 import { getEnv } from "@/lib/kv";
8 import { LocaleSwitcher } from "./locale-switcher";
9 import { MobileMenu } from "./mobile-menu";
10 import { NavLinks } from "./nav-links";
11 import { Seal } from "./seal";
12 import { ThemeToggle } from "./theme-toggle";
13 import { Whale } from "./whale";
14
15 /**
16 * Newspaper masthead + primary nav.
17 *
18 * One dictionary path for every routed locale: labels, the issue strip, the
19 * wordmark seal and strapline, the star-badge aria, and the theme/menu
20 * controls all come from `getChrome(locale)`. No `isZh` branch, and the
21 * masthead weekday uses the locale's own Intl tag rather than en-US.
22 */
23 export async function Nav({ locale = "en" }: { locale?: Locale }) {
24 const chrome = getChrome(locale);
25 const links = navLinks(locale, chrome);
26 const homeHref = `/${locale}`;
27
28 // Live star count — cached by fetchRepoStats. Falls back to a plain GitHub
29 // label when the API is unreachable at build time.
30 let stars = 0;
31 try {
32 const env = await getEnv();
33 stars = (await fetchRepoStats(env.GITHUB_TOKEN)).stars;
34 } catch {
35 /* keep fallback label */
36 }
37
38 const now = new Date();
39 const issueDate = now.toISOString().slice(0, 10);
40 const weekday = now.toLocaleDateString(chrome.dateLocale, {
41 weekday: "long",
42 month: "long",
43 day: "numeric",
44 });
45 const versionLabel = FACTS.version ? `v${FACTS.version}` : "v0.9.x";
46
47 return (
48 <header className="site-nav paper-nav">
49 {/* Issue / build strip — the newspaper masthead people loved */}
50 <div className="paper-issue-bar">
51 <div className="paper-issue-inner">
52 <div className="paper-issue-left">
53 <span>{fill(chrome.issueLabel, { date: issueDate })}</span>
54 <span className="paper-issue-sep" aria-hidden>
55 ·
56 </span>
57 <span className="hidden sm:inline">{weekday}</span>
58 </div>
59 <div className="paper-issue-right">
60 <span className="hidden md:inline">codewhale.net</span>
61 <span className="tabular">{versionLabel}</span>
62 </div>
63 </div>
64 </div>
65
66 <div className="site-nav-inner paper-nav-inner">
67 <Link href={homeHref} className="site-wordmark paper-wordmark" aria-label={chrome.navHomeAria}>
68 <Seal char={chrome.wordmarkSeal} size="md" />
69 <div className="paper-wordmark-text">
70 <span className="paper-wordmark-name">
71 Codewhale
72 <Whale size={18} className="paper-wordmark-whale" />
73 </span>
74 <span className="paper-wordmark-tag">{chrome.wordmarkTag}</span>
75 </div>
76 </Link>
77
78 <NavLinks links={links} primaryAria={chrome.navPrimaryAria} />
79
80 <div className="site-nav-actions">
81 <ThemeToggle
82 autoLabel={chrome.themeAuto}
83 lightLabel={chrome.themeLight}
84 darkLabel={chrome.themeDark}
85 ariaTemplate={chrome.themeAria}
86 titleLabel={chrome.themeTitle}
87 />
88 <LocaleSwitcher current={locale} />
89 <Link
90 href={REPO_URL}
91 className="site-github-link paper-star-badge"
92 aria-label={chrome.starsAria}
93 >
94 ★ {stars > 0 ? formatStars(stars) : chrome.githubFallback}
95 </Link>
96 <Link
97 href={DISCORD_URL}
98 className="site-discord-link paper-discord-badge"
99 aria-label="Discord"
100 >
101 Discord
102 </Link>
103 <Link
104 href={`/${locale}/install`}
105 className="paper-install-cta hidden md:inline-flex"
106 >
107 {chrome.installCta}
108 </Link>
109 <MobileMenu
110 installHref={`/${locale}/install`}
111 installLabel={chrome.installCta}
112 links={links}
113 openLabel={chrome.menuOpen}
114 closeLabel={chrome.menuClose}
115 />
116 </div>
117 </div>
118 </header>
119 );
120 }
121
121 lines Plain Text