From 55c7f54b373080babbad4a19412ab16dac3b91d2 Mon Sep 17 00:00:00 2001 From: fylorn <249551762+fylorn@users.noreply.github.com> Date: Thu, 1 Oct 2026 13:51:54 +0800 Subject: [PATCH] 404 page: the site header and footer follow the visitor's language too GitHub Pages serves one 404 page for every address, built as an English page; its script switched the body to Chinese, but the header, the footer and the search box stayed English. SiteHeader, SiteFooter and SearchModal take a `bilingual` prop, set only by the 404 page. With it, every piece of text is rendered in both languages (a small Bi component), links carry data-localize for the page's script to point at /zh-CN, attributes such as aria-labels and the search box's texts carry their Chinese values in data-zh-attrs, and the language menu marks the visitor's language. Every other page renders exactly as before. Co-Authored-By: Claude Opus 5.5 --- src/components/Bi.astro | 20 ++++++++++++ src/components/SearchModal.astro | 53 ++++++++++++++++++++++-------- src/components/SiteFooter.astro | 35 ++++++++++++-------- src/components/SiteHeader.astro | 56 ++++++++++++++++++++------------ src/pages/404.astro | 18 +++++++--- 5 files changed, 132 insertions(+), 50 deletions(-) create mode 100644 src/components/Bi.astro diff --git a/src/components/Bi.astro b/src/components/Bi.astro new file mode 100644 index 0000000..50b6877 --- /dev/null +++ b/src/components/Bi.astro @@ -0,0 +1,20 @@ +--- +// A piece of text in the page's language. On a page that picks its language in +// the browser (the 404 page: GitHub Pages serves one for every language), both +// are rendered and the page's script shows the visitor's. +import { getLang } from "~/i18n"; + +interface Props { + en: string; + zh: string; + /** Render both languages for the page's script to choose from */ + both?: boolean; +} + +const { en, zh, both = false } = Astro.props; +const lang = getLang(Astro); +--- + +{both ? ( + {en} +) : lang === "zh-CN" ? zh : en} diff --git a/src/components/SearchModal.astro b/src/components/SearchModal.astro index 684836d..b323630 100644 --- a/src/components/SearchModal.astro +++ b/src/components/SearchModal.astro @@ -1,16 +1,34 @@ --- import { getLang } from "~/i18n"; +import Bi from "./Bi.astro"; +interface Props { + /** Render the text in both languages for the page's script to choose from (the 404 page; see Bi.astro) */ + bilingual?: boolean; +} + +const { bilingual = false } = Astro.props; const lang = getLang(Astro); -const placeholder = lang === "zh-CN" ? "搜索文档…" : "Search docs…"; -const buttonLabel = lang === "zh-CN" ? "搜索" : "Search"; -const emptyHint = lang === "zh-CN" ? "开始输入以搜索文档。" : "Start typing to search the docs."; -const noResults = lang === "zh-CN" ? "未找到结果" : "No results"; -// Used when the page is a product's docs page (DocsLayout sets data-docs-product). -const scopedPlaceholder = lang === "zh-CN" ? "搜索 {product} 文档…" : "Search {product} docs…"; -const scopeLabel = lang === "zh-CN" ? "范围" : "Scope"; -const scopeAll = lang === "zh-CN" ? "全部文档" : "All docs"; -const scopeProduct = lang === "zh-CN" ? "{product} 文档" : "{product} docs"; +const text = { + placeholder: { en: "Search docs…", zh: "搜索文档…" }, + button: { en: "Search", zh: "搜索" }, + emptyHint: { en: "Start typing to search the docs.", zh: "开始输入以搜索文档。" }, + noResults: { en: "No results", zh: "未找到结果" }, + // Used when the page is a product's docs page (DocsLayout sets data-docs-product). + scopedPlaceholder: { en: "Search {product} docs…", zh: "搜索 {product} 文档…" }, + scopeLabel: { en: "Scope", zh: "范围" }, + scopeAll: { en: "All docs", zh: "全部文档" }, + scopeProduct: { en: "{product} docs", zh: "{product} 文档" }, +}; +const pick = (k: keyof typeof text) => (lang === "zh-CN" ? text[k].zh : text[k].en); +const placeholder = pick("placeholder"); +const buttonLabel = pick("button"); +const emptyHint = pick("emptyHint"); +const noResults = pick("noResults"); +const scopedPlaceholder = pick("scopedPlaceholder"); +const scopeProduct = pick("scopeProduct"); +/** On a bilingual page, attributes the page's script swaps in for Chinese visitors */ +const zhAttrs = (attrs: Record) => (bilingual ? JSON.stringify(attrs) : undefined); ---
- +
@@ -96,7 +105,7 @@ const closeLabel = lang === "zh-CN" ? "关闭" : "Close"; - + @@ -104,7 +113,8 @@ const closeLabel = lang === "zh-CN" ? "关闭" : "Close";
{langOptions.map((opt) => { const href = localePath(opt.code, targetPath(opt.code)); - const isActive = opt.code === lang; + // On a bilingual page the script marks the visitor's language instead + const isActive = !bilingual && opt.code === lang; return ( @@ -155,6 +166,7 @@ const closeLabel = lang === "zh-CN" ? "关闭" : "Close"; type="button" data-mobile-nav-toggle aria-label={menuLabel} + data-zh-attrs={zhAttrs({ "aria-label": "菜单" })} aria-expanded="false" aria-controls="mobile-nav-panel" class="md:hidden flex items-center justify-center w-9 h-9 rounded-md border border-white/10 text-[var(--color-muted)] hover:text-white hover:border-white/25 transition-colors" @@ -178,28 +190,30 @@ const closeLabel = lang === "zh-CN" ? "关闭" : "Close"; hidden class="md:hidden border-t border-white/10 bg-[var(--color-bg)]/95 backdrop-blur-md" > -
- +