Hexo 作为一个静态博客框架,原生对多语言的支持能力有限,仅支持生成单一语言的网站。为了解决动态切换语言的需求,社区大佬开发了如 hexo-generator-i18n 等插件。但其功能仍局限于 page 和 archive 的多语言化,而对于 post 的多语言则无能为力。
hexo-theme-reimu 最初也不支持多语言,但在 v1.4.0 版本中,我参考了 hexo-generator-i18n
的实现,为其添加了 page/post/archive 多语言支持,并在 v1.5.0 版本中进一步完善了功能。本文将介绍我是如何为 hexo-theme-reimu 添加多语言支持的,以及如何使用 hexo-theme-reimu 的多语言功能。
设计思路
前提:
- 假设默认语言的路径为:
https://:baseurl/a/b.html
,则其他语言的路径为:https://:baseurl/:lang/a/b.html
。 - 一切页面的生成皆以默认语言为基础/兜底,未提供默认语言版本的内容不会在导航中展示。
page 多语言支持
page 由于没有上一篇文章 (prev) 和下一篇文章 (next) 这些功能,且是直接按照 source
下目录结构进行生成的,所以多语言支持比较简单。对于 source
目录下已经存在的多语言 page 直接调用 Hexo 自带的 generator 生成即可,而对于那些只有默认语言的 page,我们需要手动编写一个 generator 将默认语言的 page 复制到其他语言的路径下。【source#generator-page-i18n】
post 多语言支持
post 是最难处理的部分,因为 Hexo 默认是基于 permalink 来生成 post 的,而 permalink 默认是不包含语言信息的。这意味着我们必须在 front-matter 中自定义字段来显示标识语言的类型,例如:
1 | title: Hello |
此外,post 还存在上一篇文章 (prev) 和下一篇文章 (next) 这些功能,这块我们也要支持,保证展示正确的文章标题并且不出现重复文章。
为了解决以上问题,我进行了如下的设计:
- Hexo 默认的 post 生成器生成的 prev 和 post 显然无法满足我们的需求,所以我们需要重写 Hexo 默认的 post 生成器,在重写的时候正确链接 post 的 prev 和 post。【source#generator-post】【source#generator-post-with-lang】
- 不含有
lang
的文章和含有lang
的文章分别生成 - 不含有
lang
的文章的 prev 和 next 链接到相邻的不含lang
的文章 - 含有
lang
的文章生成对应的路径,并将其 prev 和 next 链接到相邻的含有/不含lang
的文章
- 不含有
- 那些没有用户自定义
lang
字段的 post,我们需要手动编写一个 generator 将默认语言的 post 复制到其他语言的路径下。【source#generator-post-i18n】
archive 多语言支持
archive 包括了 index
、tag
、category
和 archive
这四种归档页面,其主要的难点在于:
- 如何在不同语言下生成对应语言的归档页面(例如一篇post在中文下标题是《你好》,在英语下标题是《Hello》,则在中文归档页面下应该显示《你好》,在英语归档页面下应该显示《Hello》)
- 如果存在用户自己编写的多语言 post,如何防止其在不同语言下重复显示
hexo-generator-i18n
由于不支持 post 的多语言支持,所以其仅仅粗暴地将默认语言的 archive 页面复制到其他语言的路径下,这样会导致不同语言下的归档页面展示相同的内容,且会重复显示用户自己编写的多语言 post。
为了解决这个问题,我优化了 hexo-generator-i18n
的实现:
- 为了防止展示重复的 post,我们需要重写 Hexo 默认的 archive 生成器(hexo-generator-index、hexo-generator-category、hexo-generator-tag、hexo-generator-archive),在重写的时候排除掉一切用户自己编写的多语言 post。【source#generator-override】
- 因为排除掉了用户自己编写的多语言 post,为了正确展示 post 的标题,我们需要在模板渲染时将 post 的标题替换为对应的语言文章的标题(如果存在的话)。【source#helper-get_posts_by_lang】
- 编写一个 generator 将默认语言的 archive 复制到其他语言的路径下。【source#generator-archive-i18n】
链接跳转
Hexo 自带的 url_for
helper 不存在多语言支持,所以我们需要自己编写一个 helper 来生成多语言的链接。【source#helper-url_for_lang】
搜索多语言支持
hexo-theme-reimu 同时支持 Algolia 搜索(hexo-algoliasearch)和本地搜索(hexo-generator-search),但这两个插件本身不支持 post 多语言(含有 lang
的 post 的 permalink 本身不携带 lang
信息,所以多语言文章的跳转链接是错误的)
- 对于 Algolia 搜索,我基于此 fork 了代码@reimujs/hexo-algoliasearch
- 对于本机搜索,我直接将其内置到了主题中,同时修复了多语言文章的跳转链接【source#search】
pjax 多语言支持
pjax 本身也并不支持多语言之间的切换,例如如果从中文页面跳转到英文页面,那么网页上菜单等内容不会跟着变成英文(事实上这种情况不能调用 pjax)。
好在 pjax 提供了 getElements
hook,我们可以通过重写这个函数来指定页面上的哪些 a 标签可以调用 pjax。(只有跳转到同一语言站点才可以使用 pjax)【source#Pjax.prototype.getElements】
如何使用
假设默认语言为 zh-CN
,其他语言为 en
。
基础配置
外层 _config.yml
配置:
1 | language: zh-CN # 不允许为数组 |
内层 _config.yml
(_config.reimu.yml
) 配置:
1 | i18n: |
page 多语言
在 source
目录下创建 en
目录,将对应语言的 page 放入其中:
1 | |—— source |
以上结构会生成如下的多语言 page:
1 | |—— public |
post 多语言
WARNING
由于多语言的 post 的 permalink 需要和默认语言的 post 保持一致,而默认的 permalink 的格式是 :year/:month/:day/:title/
,title
一般不同语言下是不同的,所以建议自己在 front-matter 中定义一个自定义字段,如 urlname
来代替 title
,并在 permalink 中使用 :urlname/
。
假设 _config.yml
里 permalink 格式如下:
1 | permalink: :year/:month/:day/:urlname/ |
默认语言 front-matter 如下:
1 |
|
以上文章会生成 https://:baseurl/2023/12/25/hello/
。
则其他语言(en)的 front-matter 必须如下:
1 |
|
以上文章会生成 https://:baseurl/en/2023/12/25/hello/
。
已知问题
- 由于主题通过重写 post 生成器来实现多语言支持,当启用此功能后,
hexo s
的热重载功能将无法自动检测文章更新。若需要实时预览修改后的多语言内容,请手动执行hexo clean && hexo g
刷新缓存。