← 全部日志

英文版上线:一次「类型全过、构建才报错」的 RSC 序列化课

2026-06-14约 1.5 小时i18n架构RSC踩坑

目标

把主站做成中英双语,给海外/英文面试官一个完整的英文版本。需求拍板的三个边界:

  • 主站全套英文,构建日志留中文——日志是写给中文读者的施工实录,翻译收益低、维护成本高;英文页诚实标注「build log is in Chinese」。
  • AI 分身在英文模式也说英文——核心展品不能只有壳是英文。
  • 中文默认在 /,英文在 /en,手动切换——不做基于 Accept-Language 的自动跳转(爬虫/分享不可控),切换按钮常驻导航。

关键决策:选「最不容易出事」的 i18n 形态

Next 有好几种 i18n 玩法(middleware 重写、[lang] 动态段、route group)。这站已经上线、且九屏 scrub 引擎 + 聊天岛对路由结构敏感,改路由结构 = 高风险。所以选了最克制的一套:

  • 一套根 layout<html lang="zh-CN">),英文页面用 /en/* 实体路由,页面体外包一层 <div lang="en"> 就近声明语言;
  • getContent(lang) 单一数据源:中英两套 content/ 在模块加载时各跑一遍 zod 校验(写错 → next build 直接挂),组件全部改成吃 content prop,中英复用同一套组件体;
  • 界面 chrome 文案(按钮、页脚、导出头)抽到 src/lib/ui.tsgetUi(lang),不混进 content/(那是个人内容);
  • 导航/页脚需要按当前路径决定语言与镜像链接,做成了两个极小的客户端组件usePathname 判语言)——这违反了「客户端 JS 只许聊天岛 + 导航高亮」的铁律一点点,但在「单 layout + 静态导出」下,server 组件拿不到当前 pathname(headers() 会把 layout 打成动态、毁掉 SSG),权衡后接受,并在此如实记录。

结果

  • 新增 /en/en/projects/en/projects/[slug]/en/ai-twin/en/about/en/privacy + 独立英文 OG 图,共 45 个静态页全部预渲染通过。
  • 架构图 SVG、隐私页、AI 分身原理页这些「长 prose + 内联强调 + 表格」的页面,做成 { content } 驱动、按 lang 分支的双语视图组件(src/components/pages/*),中英同源。
  • AI 分身:buildSystemPrompt(lang) 用对应语言的资料 + 英文版行为边界;演示模式兜底、限流 429、上游报错提示全部按 ?lang=en 出英文;/en/ai-twin 页里展示的 system prompt 就是 buildSystemPrompt("en") 的真实输出(与线上同源)。
  • 全站 hreflang 互链(每个中文页 ↔ 英文页)、sitemap 中英成对。

踩坑与纠正

1. 最贵的坑:tsc 全过,next build 才炸。 Ui 类型里有一项 chatExportContact: (origin) => string——导出对话纪要时拼联系人行,origin 只有客户端知道,所以写成了函数。npm run typecheck 一路绿灯。直到 next build 预渲染 /en/projects/noworries 才报:

Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server".

根因:页脚和聊天岛是客户端组件,会收到整个 Ui 对象,而函数不能跨 RSC → 客户端边界序列化。tsc 只查类型形状,不查「这东西能不能被 React 序列化过边界」——这类错误只有真构建才抓得到。纠正:把函数改成带 {origin} 占位符的纯字符串,客户端 .replace("{origin}", origin);整个 Ui 变成可序列化,顺手把页脚 props 收窄到只传 6 条页脚文案(别把聊天/招聘文案塞进每页页脚 bundle)。教训:跨 RSC 边界的 props 必须当可序列化数据设计,且 typecheck 绿 ≠ build 绿,提交前 build 不能省。

2. 标题模板把中文品牌缀到英文页。 根 layout 的 title.template = "%s · 黄一航",于是英文页标题变成「About · 黄一航」。改用 title: { absolute } 让英文页跳过模板。

3. 英文首页终章里混进中文日志卡。 终章「彩蛋作品」拉最近几篇构建日志做证据,而日志留中文——英文页里就露出中文标题。没有硬翻日志,而是在英文文案里诚实写明「build log is in Chinese」,CTA 也标注 (in Chinese),让人点之前就知道。

用时与备注

约 1.5 小时(含一轮 6 agent 并行翻译全部 content 模块、组件大改、四场景聊天验证)。验证矩阵在 ?lang=en 下全部复跑:① 无 env → 英文演示兜底 ② mock → live 流式 ③ 错误 key → 优雅落英文演示、HTTP 200 不 5xx ④ 连发 → 英文 429。中文侧回归确认未被重构破坏。