多语言
OINK 使用 Hugo 的多语言模型,不额外引入目录约定:配置一个 languages 块,译文与原文并排放在同一个目录里,用文件名后缀区分。以下内容覆盖单语言站点扩展为双语站点需要改动的位置,以及双语站点的两处易错点:资源归属与标题锚点。
启用第二种语言
上面是本站在用的配置。四个字段的作用:
label是语言选择器里显示的名字,用该语言自己的文字书写:写简体中文,不是Chinese。locale是标准语言标签,会进<html lang>、hreflang备用链接和 Open Graph 元数据。weight同时决定语言排序和选择器的轮换顺序,小的在前。params是语言级覆盖:这里没写的键继承全局同名值。日期格式通常需要按语言各写一遍。
默认语言不带路径前缀(英文在 /docs/…),其它语言各占一个前缀(中文在 /zh/docs/…)。默认语言也需要前缀时加 defaultContentLanguageInSubdir: true。这会改变全站 URL,已上线的站点要同时配好重定向。
文件命名与资源
译文与原文并排放置,用后缀区分,Hugo 靠相同的基础文件名把它们认成同一页的两个语言版本:
content/docs/
- install.md英文
- install.zh.md中文
- _index.md
- _index.zh.md
页面包同理:index.md 与 index.zh.md 放在同一个目录里。
页面包里的资源遵循一条规则:文件名不带语言后缀的资源由所有语言共享,带语言后缀的资源只属于那种语言。
content/docs/install/
- index.md英文页
- index.zh.md中文页
- topology.webp两种语言都能用
- screenshot.zh.webp只有中文页能用
正文里引用带后缀的资源时 写不带后缀的名字:,Hugo 会按当前语言解析。
这条规则有一个推论:页面包里只有 index.zh.md、没有英文对等页时,不带后缀的资源不会分给中文页,它们归属默认语言,而默认语言在这个包里没有页面。此时所有资源都必须带 .zh. 后缀,本站 docs/ 下的中文页面包即是如此。
哪些内容需要翻译:
- 翻译:
title、description、摘要、菜单标签、标签名、图片 alt、提示块正文、shortcode 里面向读者的参数。 - 保持一致:日期、
weight、别名,以及任何影响路由的元数据。两边不一致会导致侧栏顺序在两种语言下不同。 - 不翻译:命令、配置键、文件名、URL、版本号、产品名、shortcode 名。
按语言分开的配置
三处内容不在 content/ 里,需要各语言各写一份。
菜单 写在各自语言下:
identifier 两种语言必须一致:命令面板的快速链接与搜索结果分组顺序都按它匹配。菜单的完整写法见导航与菜单。
首页数据 按语言取文件:data/home/en.yaml、data/home/zh.yaml。当前语言没有对应文件时回退到 en.yaml;单语言站点用一个 data/home.yaml 即可。见首页与落地页。
界面文案:主题自带 32 份完整界面语言包,即 Docsy 支持的 31 个 locale
文件名,再加通用 zh。每份语言包都以目标语言覆盖 OINK 的全部 194 条消息,
不再依赖生成的英文 fallback。zh 与 zh-cn 使用简体中文,zh-tw 使用繁体
中文;完整 locale 与占位符契约见架构。
要改某一条,在站点自己的 i18n/ 下建同名文件,只写要覆盖的键:
如果需要兼容 Hugo 0.160.x,并且地区化中文语言包同时存在,请为非默认的通用 zh
语言保留具体的 locale: zh-CN。从 Hugo 0.161 起,相同配置也可以使用裸
locale: zh。
缺译回退与语言选择器
语言选择器的图标本身是一个链接:点击它按 weight 顺序切到下一种语言(在末尾回到第一种),悬停或键盘聚焦才展开列出全部语言的菜单,触摸屏上菜单不展开,点按即切换。双语站点因此一次点击即可来回切换。
菜单始终列出全部配置的语言,不论当前页有没有译文:
- 目标语言有译文 → 跳到那一页;
- 目标语言没有译文 → 跳到那种语言的 首页。
回退到首页优于把读者送进 404。代价是读者不一定察觉自己被送到了首页,双语站点应当把「每个页面都有对等译文」作为约束来检查,而不是依赖回退。
这个回退用于语言选择器。1.2.0 实现将它与 SEO 分开:hreflang 只列当前页及
实际译文,博客每一分页使用自身的 canonical,后续分页不输出语言备用链接。
v1.1.0 标签尚不包含这些修正;按固定版本验收前,先核对
SEO 的版本差异。
中文页面不存在时,中文站里就没有这一页:侧栏、搜索索引、翻页顺序都不包含它。
搜索索引也按语言分开:读者在中文页面搜索只命中中文内容。中文查询采用 CJK 子串匹配,细节见全文检索。
标题锚点要对齐
Hugo 从标题文本生成 ID,中文标题生成中文 ID:/docs/install/#prerequisites 与 /zh/docs/install/#前置条件 指向同一个位置,却是两个互不相通的锚点,跨语言的深链、目录与页内跳转都会失效。
做法是在译文标题里显式写出原文的 ID:
两条纪律:
- ID 从 英文页渲染出来的 HTML 里取,不要凭标题文本推断。标题里含行内代码、徽章或 shortcode 时,生成的 ID 与标题文本不一致。
- 中英对应页面的标题数量、顺序、ID 必须一致。确实需要在中文里加一节时,给它一个独立、稳定、不与英文冲突的 ID。
本文档站的翻译检查脚本
会检查源码结构与渲染后的标题 ID。下面命令在 oink.pgsty.com checkout 中执行;
普通消费站不自带这个脚本:
借用到自己的 CI 前,需要调整脚本固定的内容栏目和 EN/ZH 文件命名约定。
它按脚本所在位置查找源文件,--public 只选择渲染产物目录。手动检查时,先选一对
代表性译文,在各自生成的 HTML 中比对标题 ID。
新页面从建立时就写显式英文 {#id},成本低于事后回补。
从右向左的语言
在语言下声明书写方向:
<html dir> 随之改变,主题额外加载 Bootstrap 的 RTL 样式表。主题自身的 CSS 全部使用逻辑属性(margin-inline-start 而不是 margin-left),镜像布局自动完成。站点自己写的 CSS 同样要用逻辑属性,否则 RTL 下会错位。
验证
-
在自己的站点根目录构建,确认两种语言的产物都在。下例假设英文在
/、中文在/zh/,请按语言配置调整;仅在启用本地搜索时检查索引: -
按固定版本的 SEO 行为检查
hreflang与 canonical。1.2.0 实现应只列实际译文、为博客每一分页生成自身的 canonical,并从第 2 页起省略语言备用链接。v1.1.0 保留旧行为;仅有这些差异不代表配置错误。 -
在有译文的页面上展开语言选择器并选择另一种语言,确认停在同一篇文档;在没有译文的页面上重复一次,确认落到目标语言的首页而不是 404。
-
两种语言各搜一次同一个概念,确认都有结果。
-
比对一对译文的标题 ID。需要接入 CI 时,按上节说明适配本文档站脚本,不要直接用它检查另一套内容目录。
相关
- 全文检索 — 分语言索引与中文查询
- 导航与菜单 — 按语言配菜单与语言选择器的位置
- 首页与落地页 —
data/home/<lang>.yaml - 编写页面 — 显式标题 ID 的写法
- 分析与 SEO —
hreflang与站点地图怎么被搜索引擎消费