为GitHub博客添加多语言支持
2026-07-28 更新!
本文撰写于使用Jekyll框架时期。现已迁移至Astro!
2024-09-15 更新!
支持多语言功能虽好,但维护过于困难和复杂,因此已恢复至应用插件之前的状态。要真正支持多语言,需要修改的地方比想象中多得多,必须承受与原始主题合并过程极为复杂的不便。
插件介绍
在GitHub博客环境中实现多语言功能的Jekyll插件主要有jekyll-polyglot和jekyll-multiple-languages-plugin两种。我使用的是前者——jekyll-polyglot,该插件根据每个文章front matter中定义的lang值,在根URL后插入I18N语言代码,从而生成多语言翻译页面。该插件以后者jekyll-multiple-languages-plugin为模型开发,官方指南从安装方法到使用注意事项均在GitHub Polyglot仓库中有详细说明。
准备工作
安装和设置插件
group :jekyll_plugins do
gem "jekyll-polyglot"
end
在Gemfile中如上注册插件,然后运行gem install jekyll-polyglot命令安装插件。
plugins:
- jekyll-polyglot
languages: ["ko", "en"]
default_lang: "ko"
exclude_from_localization: ['javascript', 'images', 'css', 'sitemap.xml']
parallel_localizaion: true
安装插件后,需在_config.yml中添加以上内容。languages填写页面支持的语言,default_lang填写页面的默认语言。需要注意的是,在Windows环境下parallel_localization选项无法正常工作,因此必须将其设为false。
修复正则表达式bug
安装插件并构建时,会遇到“‘relative_url_regex’: target of repeat operator is not specified:“的错误。此错误是因为插件的site.rb文件中某些正则表达式无法处理Chirpy主题的_config.yml中exlude: *.gem *.gemspec *.config.js等通配符(*)。我向插件作者咨询了此问题,但得到的回答是以此文档为依据,认为Chirpy主题在_config.yml中错误使用了全局模式。
然而,考虑到Minimal-Mistakes等其他Jekyll主题也在使用全局模式,似乎有必要修改插件代码本身。这种情况下需要自行修改并使用插件,因此我将项目fork到了我的GitHub仓库,并在Gemfile中如下引用:
gem 'jekyll-polyglot', git: 'https://github.com/hyngng/jekyll-polyglot', branch: 'master'
然后将插件jekyll-polyglot-1.8.0/lib/jekyll/polyglot/patches/jekyll路径下site.rb中的relative_url_regex()和absolute_url_regex()两个函数修改如下:
def relative_url_regex(disabled = false)
regex = ''
unless disabled
@exclude.each do |x|
escaped_x = Regexp.escape(x)
regex += "(?!#{escaped_x})"
end
@languages.each do |x|
escaped_x = Regexp.escape(x)
regex += "(?!#{escaped_x}\/)"
end
end
start = disabled ? 'ferh' : 'href'
%r{#{start}="?#{@baseurl}/((?:#{regex}[^,'"\s/?.]+\.?)*(?:/[^\]\[)("'\s]*)?)"}
end
...
def absolute_url_regex(url, disabled = false)
regex = ''
unless disabled
@exclude.each do |x|
escaped_x = Regexp.escape(x)
regex += "(?!#{escaped_x})"
end
@languages.each do |x|
escaped_x = Regexp.escape(x)
regex += "(?!#{escaped_x}\/)"
end
end
start = disabled ? 'ferh' : 'href'
%r{(?<!hreflang="#{@default_lang}" )#{start}="?#{url}#{@baseurl}/((?:#{regex}[^,'"\s/?.]+\.?)*(?:/[^\]\[)("'\s]*)?)"}
end
修改函数后运行bundle exec jekyll s命令,确认构建成功。
修改文章文件属性
---
lang: en
permalink: example-url-here
---
对于需要翻译的文章,需在front matter中指定语言值。默认使用ko、en等I18N国家代码,我使用的是ko-KR和en。其中permalink指定该文章的URL路径,这是因为在Jekyll中,具有相同URL的两个文件默认被视为相同内容,因此需要人为区分原文和翻译版。
_posts/2010-03-01-salad-recipes-en.md
_posts/2010-03-01-salad-recipes-sv.md
_posts/2010-03-01-salad-recipes-fr.md
如果不想使用front matter中的permalink来区分文章语言,也可以按上述方式修改文件名来区分,但这样页面URL可能会包含同语反复,例如example.github.io/en/2010-03-01-salad-recipes-en。
模板修改
以下内容仅针对Chirpy主题,如果使用其他Jekyll模板,可以跳过本节直接进入其他工作。但如果需要像一样修改Chirpy模板,以下内容可能有所帮助。
- jekyll-polyglot插件可用的变量
site.default_lang:_config.yml中声明的默认语言值。site.active_lang:当前网页中激活的语言值。page.lang:front matter中声明的文章语言值。
利用以上三个变量,可以编写如{% raw %}{% if page.lang == site.default_lang %}{% endraw %}的条件语句,根据上下文限制页面显示的语言。
加载站点语言
{% if site.active_lang %}
{% assign lang = site.active_lang %}
{% elsif site.data.locales[page.lang] %}
{% assign lang = page.lang %}
{% elsif site.data.locales[site.lang] %}
{% assign lang = site.lang %}
{% else %}
{% assign lang = 'site.default_lang'' %}
{% endif %}
Chirpy模板在_includes/lang.html这个单独的文件中设置语言。将上述文件修改后,可以在各个布局文件中通过加载lang.html来使用。
按语言显示内容
{% include lang.html %}
大部分情况如上所示通过加载lang.html处理,而对于分页等仅靠修改语言指定无法解决的问题,我单独编写了补充逻辑。大多数情况下,是修改为在特定语言页面中只显示与该语言编写的文章相关的信息。
<div id="post-list" class="flex-grow-1 px-xl-1">
{% for post in posts %}
{% if post.lang == site.active_lang %}
<article class="card-wrapper card">...</article>
{% endif %}
{% endfor %}
</div>
例如在_layouts/home.html中添加{% raw %}{% if post.lang == site.active_lang %}{% endraw %}条件,使首页在站点语言为英语时只显示lang: en的文章。其他具体修改过的文件如下:
| 用途 | 文件路径 |
|---|---|
| 通用框架页面 | _layouts/default.html |
| 首页 | _layouts/home.html |
| 分类页 | _layouts/category.html |
| 标签页 | _layouts/tags.html |
| 归档页 | _layouts/archive.html |
| 关于页 | _layouts/about.html |
| 最近修改的文章 | _includes/update-list.html |
| 标签浏览 | _includes/trending_tags.html |
| 相关文章 | _includes/related-posts.html |
| 文章导航 | _includes/post-nav.html |
| 分页 | _includes/post-paginator.html |
区分关于页内容
{% if site.active_lang == 'ko-KR' %}
## 한국어 자기소개
...
{% elsif site.active_lang == 'en' %}
## English Self-Introduction
...
{% endif %}
这是按语言在关于(about)页面显示不同内容的方法。起初以为需要创建about-en.md等单独文件,后来发现只需在同一文件中根据站点语言显示不同内容,这是最简便的方法。
自然地显示字数
<span
class="readtime"
data-bs-toggle="tooltip"
data-bs-placement="bottom"
title="{{ words }}{% if site.active_lang != 'ko-KR' %}{{ ' ' }}{% endif %}{{ site.data.locales[include.lang].post.words }}
>
这是一个我在意并修改的小细节。该主题在文章顶部将鼠标悬停在阅读时间上时会显示字数,但无论什么语言,字数和”字”之间都会有一个空格,显示为”1000 字”。我个人觉得不太自然,因此修改为韩语中显示为”1000자”,其他语言中显示为”1000 words”(带空格)。
其他工作
在头部声明页面语言
{% I18n_Headers %}
这是Google搜索中心文档的国际化和多语言指南中的建议。虽然不是必须的,但如果在乎SEO,建议在头部添加上述代码以声明页面语言。该代码在构建后会转换如下:
<meta http-equiv="Content-Language" content="ko-KR">
<link rel="alternate" hreflang="ko-KR" href="ttps://hyngng.github.io/posts/:title/"/>
<link rel="alternate" hreflang="en" href="https://hyngng.github.io/en/posts/:title/"/>
将插件包含在构建过程中
name: Jekyll site CI
on:
push:
branches: [ "site" ]
pull_request:
branches: [ "site" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build the site in the jekyll/builder container
run: |
docker run \
-v $:/srv/jekyll -v $/_site:/srv/jekyll/_site \
jekyll/builder:latest /bin/bash -c "chmod -R 777 /srv/jekyll && jekyll build --future"
- name: Push
uses: s0/git-publish-subdir-action@develop
env:
REPO: self
BRANCH: main
FOLDER: _site
GITHUB_TOKEN: $
MESSAGE: "Build: ({sha}) {msg}"
jekyll-polyglot与内置插件不同,被视为外部插件,出于安全原因需要单独构建。在.github/workflows/路径下创建新的.yml文件并按上述内容编写,即可正常构建。
在Sitemap中包含所有页面
...
{% for lang in site.languages %}
{% for post in site.posts %}
{% if lang == post.lang %}
<url>
<loc>
{{ site.url }}
{% if lang == site.default_lang %}
{{ post.url }}
{% else %}
{{ post.url | prepend: lang | prepend: '/' }}
{% endif %}
</loc>
...
</url>
{% endif %}
{% endfor %}
{% endfor %}
Sitemap是多语言支持时最大的问题之一,因为它只对默认页面生成<loc>标签。我修改为对site.languages中的所有语言各检查一次,其中对于从设为lang: en的文件自动生成的韩语页面等无效元素则忽略。
添加语言切换按钮
{% for lang in site.languages %}
<div class="lang" style="display: inline;">
<a style="
{% if lang == site.active_lang %}
font-weight: bold;
{% endif %}"
href="
{% if lang == site.default_lang %}
{{site.baseurl}}{{page.url}}
{% else %}
{{site.baseurl}}/{{ lang }}{{page.url}}
{% endif %}">
{{ lang }}
</a>
{% if forloop.last == false %}
<span class="lang-border"> </span>
{% endif %}
</div>
{% endfor %}
如有需要,可以用上述代码在任意位置添加语言切换按钮。不过个人认为我的博客并没有语言专属的独家内容,访问者也没有必要特意查看其他语言版本,因此没有添加。
按语言区分Feed内容
{% assign filtered_posts = site.posts | where: "lang", site.active_lang %}
{% for post in filtered_posts limit: 5 %}
<entry> ... </entry>
{% endfor %}
Feed也修改为根据语言设置,仅将匹配site.active_lang的文章动态生成到filtered_posts中。在站长工具中注册时,分别注册了feed.xml和/en/feed.xml。
效果截图


结语
不容易。jekyll-polyglot给人的感觉与其说是灵活方便,不如说是累赘。应用过程绝不能说轻松方便,也曾考虑过不如单独开设一个英文页面分成两个来管理,但页面内容联动和搜索曝光设置等方面的缺点似乎更多,所以还是使用了jekyll-polyglot。不过只要能实现,在想要创建自带的多语言支持功能时,jekyll-polyglot带来的优势是毋庸置疑的。