GitHub Actions 可通过 Node.js 脚本(推荐 cheerio 解析 DOM)自动提取 HTML 中 data-i18n 等属性值,去重生成标准 key 列表 keys.json 作为源事实;再同步 CSV 翻译结果到按 BCP 47 命名的 locales/zh-CN.json 等文件,并校验 JSON 合法性,杜绝人工干预。

如何用 GitHub Actions 自动提取 HTML 中的 data-i18n 键并生成语言 JSON 文件
静态页面里手动维护多语言 JSON 容易漏键、错拼、不同步——自动化提取才是可持续方案。核心不是“翻译”,而是“确保所有待译文本都被发现并结构化”。
- 用正则扫描所有 HTML 文件,匹配
data-i18n="key"、data-i18n-title="tooltip.save"等属性值,去重后输出为标准 key 列表 - GitHub Actions 中推荐用 Node.js 脚本(而非 shell grep),因为要处理嵌套属性如
data-i18n-placeholder和带点号的键名(nav.home) - 脚本必须跳过注释和 JS 字符串里的伪
data-i18n,否则会引入脏数据;建议用cheerio解析 DOM 而非正则硬匹配 - 生成的
keys.json应作为源事实(source of truth),后续翻译平台或人工填表都基于它,避免“改了 HTML 却忘了更新 JSON”
CI/CD 流程中如何自动同步翻译结果到 locales/ 目录
人工把 Excel 或 CSV 里的译文复制进 JSON 是最大错误源头。自动化同步的关键是约定好字段映射和空值策略。
- 翻译平台导出 CSV 时,列名必须固定为
key、zh、en、ja—— 不能是“中文”“English”,否则脚本无法识别 - CI 脚本读取 CSV 后,逐行写入对应语言 JSON:若某行
zh为空,保留原 JSON 中该 key 的值(不覆盖),避免误删 - 必须校验 JSON 格式合法性(
JSON.parse()+JSON.stringify(..., null, 2)),失败则中断部署并报错,不产出损坏文件 - 推荐在
locales/下只保留最终生效的 JSON,不要存中间格式(如 .xlsx),否则构建时容易误读旧文件
为什么 locales/en.json 和 locales/zh-CN.json 不能混用 BCP 47 标准
浏览器 navigator.language 返回的是 zh-CN,但你的文件名如果叫 zh.json,加载逻辑就可能断裂——这不是命名偏好问题,是运行时真实失败点。
- 统一采用完整 BCP 47 标签命名:
en-US.json、zh-CN.json、ja-JP.json,避免zh.json这类模糊别名 - 加载器代码里做最小截断映射:用户语言是
zh-HK,先查zh-HK.json,不存在则 fallback 到zh.json(如有),再 fallback 到en-US.json - 服务端注入
<html lang="...">时,也必须用完整标签,否则 CSS 的::lang(zh-CN)伪类不触发 - 注意:Vercel/Netlify 的 _redirects 不支持按 Accept-Language 分流,只能靠路径前缀,所以文件名和路由前缀必须严格对齐
构建时生成多语言 HTML 的陷阱:链接路径与 hreflang 必须动态生成
用 Hugo 或 Eleventy 生成 /zh/index.html 时,如果所有内部链接还是写死 href="about.html",生成结果必然错乱——自动化构建不会自动修复路径语义。
立即学习“前端免费学习笔记(深入)”;
- 模板里所有链接必须用相对路径加语言前缀:
href="/{{ lang }}/about.html",而不是href="about.html"或../about.html -
<link rel="alternate" hreflang="zh-CN" href="/zh/about.html">这类标签不能手写,必须由构建工具遍历所有语言版本自动生成,否则 SEO 失效 - 生成的每个 HTML 文件头部都要有唯一
<html lang="zh-CN">,且值来自当前构建语言变量,不是模板里硬编码的lang="zh" - 警告:Eleventy 的
eleventyComputed可用于动态计算hreflang值,但若依赖page.url,需确认其是否包含语言前缀——默认不含,得手动拼接
locales/zh.json,就等于打开了同步缺口。真正的自动化,是让任何修改都必须经过提取 → 翻译 → 校验 → 写入这一闭环,中间不插手。



















