--- name: multilingual-site-revamp display_name: 多语言站点改造 description: 把已有的单语种站点扩成多语种(子目录镜像 + hreflang + 本地生成),覆盖架构选型、字符串抽取、术语表、增量翻译缓存、质检与分批上线。适合静态站、半静态站与手写 HTML 站点。 icon: "🌐" trigger: 多语言 多语种 国际化 i18n 本地化 hreflang 站点改造 出海建站 inputs: - name: site_root description: 站点源码根目录(静态站即 HTML 所在目录) required: true - name: target_locales description: 目标语种代码,如 en,ja,ko,zh-TW,es,de,fr,vi,th required: true - name: url_strategy description: subdir(默认)/ subdomain / ccTLD,决定目录结构与 hreflang 写法 required: false scripts: [extract.py, llm.py, build.py, check.py] --- # 多语言站点改造 ## Overview 把一个已经在运营的单语种站点,扩成 N 个语种、且机器可读层面(hreflang / sitemap / 结构化数据)全部正确的站点。核心不是"翻译文字",而是**建立一条可重复执行的生成管线**:源语言只维护一份,其余语种全部是生成物。 参考实现:songbing.com.cn(134 页 × 10 语种 = 1340 URL,全静态,check 脚本 0 error / 0 warning)。 ## 什么时候用 / 不用 用: - 站点已有稳定内容与收录,不想为 i18n 重构框架 - 页面是手写 HTML / 模板生成,但结构相对统一(每页都有标题、正文、页脚) - 目标语种 ≥ 3,且预期会持续更新内容(增量翻译价值才体现出来) 不用: - 站点还没定型、页面结构天天变 → 先定型再翻译,否则每次改结构都要重译 - 只有 1-2 个页面要翻 → 手工翻更快 - 需要按用户语言自动 301 跳转 → 这是另一件事,且多数搜索引擎不推荐 ## Workflow ### 阶段 1 · 定架构(deterministic) 先定三件事,写进决策表再动手: | 决策 | 选项 | 判据 | | --- | --- | --- | | URL 策略 | 子目录 `/en/` | 默认选它:同一份部署、权重集中、证书与日志通用 | | | 子域名 `en.site.com` | 只有需要独立 CDN/独立团队/独立合规时才选;每个子域要单独配证书、GSC、分析 | | | 独立域名 `site.de` | 有本地化品牌与本地合规需求时才选;成本最高 | | 路径是否翻译 slug | 不翻译,镜像原路径 | 默认不翻:别名映射是长期维护负担,hreflang 已足够表达对应关系 | | 是否自动跳转 | 不自动跳 | 语言选择器给建议,不强制 301。自动跳转会干扰爬虫与回访用户 | 验证:手写一个 `/en/index.html` 占位页,确认目录结构、相对路径、静态资源都能正常访问,再开始批量。 ### 阶段 2 · 盘点可翻译面(deterministic) 逐类过一遍,产出"翻 / 不翻"清单: 必翻:`
` / `` 里的中文散文**(提示词、检查清单、ASCII 结构图——读者要照抄,必须翻)。
不翻:品牌名与平台名(百度、抖音、微信、小红书…)、ICP 备案号(法规要求保留中文)、纯 ASCII 代码与 URL、URL slug、统计数据里的数字格式(用 `Intl.NumberFormat` 处理,别直译"万/亿")。
验证:把清单写成术语表 `glossary.json` 的 `keep` 数组,后面所有语种共用。
### 阶段 3 · 写抽取器(deterministic,难度最高的 20%)
不要用 DOM 解析再序列化——内联样式、注释、未加引号的属性都会被打散。正确做法是**只改字节区间**:
1. 用 quote-aware 的 tokenizer 扫 `tag / text / comment / raw` 四类 token
2. 只抽取"含目标语言字符"的文本节点与指定属性
3. 每段生成一个**文本指纹** `key = sha1(text)[:16]`,作为翻译缓存的键
4. 回填时按 `(start, end)` 从后往前替换,源页与生成页除文字外逐字节一致
验证:对同一页面连续跑两次,第二次应无 diff(幂等)。回填后标签序列必须与源页完全一致。
### 阶段 4 · 接翻译(agentic 判断 + deterministic 调用)
- 用 OpenAI 兼容接口,按"语种 + 术语表 + 语境"拼 system prompt,一次请求批量返回 JSON 数组
- 缓存按 `key` 存 `cache/.json`,改一段只重译那一段;全量重翻用 `--force`
- 术语表 `keep` 按**长度倒序**替换,否则短词先吃掉长词
- 失败三级补丁:strict 重问 → 含换行的长段按行拆开重问 → 纯文本逐段重问
- 人工校对结果写 `manual.json`(按源文精确匹配),渲染时覆盖缓存——这是唯一允许手改的地方
验证:`--dry-run` 打印每语种待译段数,新增页面时该数字应等于新页面段数而不是全站段数。
### 阶段 5 · 生成器(deterministic)
每页按顺序拼装,全部用可识别的注释块包裹,便于幂等重跑:
1. `strip` 掉旧的生成块(否则重跑会叠加)
2. head 注入:canonical(指向自己)、`og:url`、`og:locale`、hreflang 集群 + `x-default`
3. 导航头按语种生成(菜单文案走字典,链接用相对路径计算,不要硬编码)
4. 页脚合规块按语种注入(备案号保留中文,免责声明翻译)
5. 回填正文,写出 `//`
hreflang 只输出**目标页真实存在**的语种——指向 404 的 hreflang 比不写更糟。
### 阶段 6 · 质检与上线(deterministic)
五道闸全部为 0 才算完成:
| 闸门 | 判据 |
| --- | --- |
| 残留文字 | 非中文页的可见文本里不应有源语言字符(日文用简体字特征表判定,不能用"含汉字") |
| 死链 | 每页每语种的 `href` / `src` 都能解析到真实文件 |
| hreflang 互指 | 每个 alternate 都能回指自己,缺语种即报错 |
| 标签结构 | 生成页的标签序列与源页逐项相等(最隐蔽的错就是这里) |
| 字典完整 | 工具页 JS 字典的键全齐,且没有回吐源语言的值 |
上线:按语种目录**分批** commit + push,先灰度 1 个语种看日志与收录,再放其余;`sitemap-i18n.xml` 与导航入口放在最后一批。回滚 = 删除该语种目录。
## Lessons Learned
### Do
- 先定架构与清单,再写代码。80% 的返工来自架构反复。
- 用文本指纹做缓存键:内容不变就不重译,这是成本控制的全部。
- 生成物全部加注释围栏,保证幂等可重跑。
- 把"校验"做成脚本而不是人工抽查:1330 页人工是看不完的。
- 品牌名、法规文本(ICP)走白名单,永不进模型。
### Don't
- 不要重排 DOM 再输出(内联样式/注释/引号风格会被毁)。
- 不要让 hreflang 指向不存在的页面。
- 不要把所有语种的 canonical 都指向源语言页——那是自废收录。
- 不要用"整站重翻"解决问题:先看是缓存键错、术语表顺序错,还是抽取器漏了区域。
- 不要在生成页上手改文字:下次生成就没了,改源语言页或 `manual.json`。
### Common Failures
- **按钮文案回退成源语言**:文案在 inline `