Skip to content

Commit 5335ca6

Browse files
committed
feat(i18n): 文档站双语切换 locale (cookie + middleware IP 判断)
- 新建 middleware.ts:首次访问 /docs 时根据 IP geo / Accept-Language 判断默认 locale,写入 cookie;默认 zh(社区主体语言) - [...slug]/page.tsx: 从 cookie 读 locale,尝试加载 slug.en.mdx / slug.zh.mdx 翻译版,不存在时静默 fallback 到原文(不展示碍眼横幅) - docs-i18n-design.md: 完整双语化设计方案(frontmatter 约定、 contributors 脚本改造、翻译流程、术语词表方向)
1 parent cf6997d commit 5335ca6

3 files changed

Lines changed: 499 additions & 1 deletion

File tree

app/docs/[...slug]/page.tsx

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { LicenseNotice } from "@/app/components/LicenseNotice";
1616
import { PageFeedback } from "@/app/components/PageFeedback";
1717
import { DocHistoryPanel } from "@/app/components/DocHistoryPanel";
1818
import { DocShareButton } from "@/app/components/DocShareButton";
19+
import { cookies } from "next/headers";
1920
// Extract clean text content from MDX - no longer used on client/page side
2021
// content fetching moved to API route for performance
2122

@@ -25,14 +26,60 @@ interface Param {
2526
}>;
2627
}
2728

29+
/** 从 cookie 读取用户语言偏好,未设置时返回 null */
30+
async function getLocaleFromCookie(): Promise<"zh" | "en" | null> {
31+
const cookieStore = await cookies();
32+
const val = cookieStore.get("locale")?.value;
33+
if (val === "zh" || val === "en") return val;
34+
return null;
35+
}
36+
37+
/**
38+
* 根据 locale 尝试加载对应语言版本的文档。
39+
* 翻译文件命名规则:原文 slug 最后一段加上语言后缀,例如
40+
* slug = ["ai", "rl"] → 英文版尝试 ["ai", "rl.en"]
41+
*
42+
* 若对应翻译版不存在,fallback 到原文。
43+
*/
44+
function getPageWithLocale(
45+
slug: string[] | undefined,
46+
locale: "zh" | "en" | null,
47+
) {
48+
const originalPage = source.getPage(slug);
49+
if (!locale || !slug || slug.length === 0)
50+
return { page: originalPage, isFallback: false };
51+
52+
const originalLang =
53+
(originalPage?.data as { lang?: string } | undefined)?.lang ?? null;
54+
55+
// 已经是目标语言,直接返回
56+
if (originalLang === locale) return { page: originalPage, isFallback: false };
57+
58+
// 尝试加载翻译版:slug 末尾加语言后缀
59+
const lastSegment = slug[slug.length - 1];
60+
const translatedSlug = [...slug.slice(0, -1), `${lastSegment}.${locale}`];
61+
const translatedPage = source.getPage(translatedSlug);
62+
63+
if (translatedPage) {
64+
return { page: translatedPage, isFallback: false };
65+
}
66+
67+
// 翻译版不存在,fallback 到原文
68+
return { page: originalPage, isFallback: true };
69+
}
70+
2871
export default async function DocPage({ params }: Param) {
2972
const { slug } = await params;
30-
const page = source.getPage(slug);
73+
const locale = await getLocaleFromCookie();
74+
const { page } = getPageWithLocale(slug, locale);
3175

3276
if (page == null) {
3377
notFound();
3478
}
3579

80+
// 静默 fallback:翻译版不存在时直接展示原文,不再显示"暂无英文版"横幅
81+
// 原因:中文为默认语言,大多数文档本身就是中文;显示 banner 反而让 UI 碍眼
82+
3683
// 统一通过工具函数生成 Edit 链接,内部已处理中文目录编码
3784
const editUrl = buildDocsEditUrl(page.path);
3885
const docIdFromPage =

0 commit comments

Comments
 (0)