Hexo i18n Configuration
When running a personal blog, you might encounter this requirement: wanting your website to support multiple languages so that readers from different regions can easily read your content.
This is where website internationalization (also known as i18n) comes into play.
For blogs built with Hexo, implementing internationalization requires consideration not only of content translation but also template rendering and other issues.
-
This article primarily uses the
hexo-generator-plus
plugin.Before starting the configuration, please ensure you have uninstalled the following plugins to avoid conflicts:
hexo-generator-archive
hexo-generator-category
hexo-generator-index
hexo-generator-tag
-
This article uses the Pug templating language.
-
For the language switching solution in the navigation bar, I have only implemented bilingual logic.
Basic Configuration
To avoid confusion:
The
_config.yml
in the Hexo root directory will be referred to as Hexo Configuration
themes/**/_config.yml
will be referred to as Theme Configuration
First, we need to make some basic settings in Hexo's configuration file. These settings will determine the website's language options and URL structure.
1 | language: [zh, en] # Supported language list, first one is default |
Then configure the desired menu links in the theme configuration:
1 | menu: |
Directory Structure
Here is the required directory structure:
1 | source/ |
Of course, you can choose other languages, but other language directories need to match the names in
themes/**/languages/*.yml
.
Please ensure each *.md
file has lang: **
in its Front-Matter.
Language File Configuration
Fixed website text (such as navigation menus, button text, etc.) needs to be internationalized through language files. These files should be placed in the themes/**/languages/
directory.
Here's my examples:
-
zh.yml
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16menu:
home: 首页
archive: 归档
tags: 标签
categories: 分类
about: 关于
friendlinks: 友情链接
archive_title: 归档
tags_title: 标签
categories_title: 分类
prev: 上一页
next: 下一页
prev_post: 上一篇
next_post: 下一篇
more: ...阅读全文
translated: 翻译 · 原文地址 -
en.yml
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16menu:
home: HOME
archive: ARC
tags: TAGS
categories: CATE
about: ABOUT
friendlinks: Friend Links
archive_title: Archive
tags_title: Tags
categories_title: Categories
prev: PREV
next: NEXT
prev_post: PREV POST
next_post: NEXT POST
more: ...MORE
translated: Translate · Original Link
Template File Implementation
From here on, I'll only write about the solution used for my blog website.
Please modify according to your own theme.
Category Page Templates
Category pages need two templates: category index page and specific category page.
-
Category list page (
category-index.pug
):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20extends partial/layout
block container
.archive
// Title content prioritizes page.title
// If it doesn't exist, use the i18n function __() to get categories_title translation
h2.archive-title= page.title || __('categories_title')
.category-list
// Get all category data
each category in get_categories().data
// Calculate number of posts matching current language for each category
- var postCount = category.posts.data ? category.posts.data.filter(post => post.lang === page.lang).length : 0
if postCount > 0
.category-item
// Each category shows as a link, including category name and post count
// url_for_lang() generates multilingual-supported URL
- var categoryPath = category.slug || category.name
a.post-title-link(href=url_for_lang(page.lang, 'categories/' + categoryPath))
= category.name
span.category-count= ` (${postCount})` -
Specific category page (
category.pug
):1
2
3
4
5
6
7extends partial/layout
block container
include mixins/post
.archive
h2.archive-title= page.category
+postList()Here, post-related functionality is encapsulated in a series of mixins for reuse across different pages (
mixins/post.pug
):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28mixin postInfo(item)
.post-info
!= full_date(item.date, 'l')
// If post has from property and current page is home or post page
if item.from && (is_home() || is_post())
// Show a link indicating post translation source
a.post-from(href=item.from target="_blank" title=item.from)!= __('translated')
mixin posts()
ul.home.post-list
// Iterate through all posts
- for (var post of page.posts.data || [])
// Only show posts matching current page language
- if (post.lang == page.lang)
li.post-list-item
article.post-block
h2.post-title
a.post-title-link(href= url_for(post.path))
!= post.title
+postInfo(post)
// If there's an excerpt, show it with "read more" link
if post.excerpt
.post-content
!= post.excerpt
a.read-more(href= url_for(post.path))!= __('more')
else
.post-content
!= post.content
Tag Page Templates
Almost identical to category page templates:
-
tag-index.pug
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22extends partial/layout
block container
include mixins/post
.archive
h2.archive-title= page.title || __('tags_title')
.tag-list
each tag in get_tags().data
- var postCount = tag.posts.data ? tag.posts.data.filter(post => post.lang === page.lang).length : 0
if postCount > 0
.tag-item
- var tagPath = tag.slug || tag.name
a.post-title-link(href=url_for_lang(page.lang, 'tags/' + tagPath))
= tag.name
span.tag-count= ` (${postCount})`
block pagination
include mixins/paginator
+home()
block copyright
include partial/copyright -
tag.pug
:1
2
3
4
5
6
7
8
9
10
11
12
13
14extends partial/layout
block container
include mixins/post
.archive
h2.archive-title= page.tag
+postList()
block pagination
include mixins/paginator
+home()
block copyright
include partial/copyright
Archive Page Template
Archive page only needs one archive.pug
:
1 | extends partial/layout |
Its mixin:
1 | mixin postList() |
Navigation Bar Implementation
The navigation bar is the key interface for language switching (nav.pug
).
Since my blog theme's navigation bar isn't wide, I wrote top and bottom sections to separate some links:
1 | ul.nav.nav-list |
Usage
After completing the above configuration, specify the language in the Front-Matter when creating new articles:
1 | --- |
If you want to create versions of the same article in other languages, just create a new Markdown file, specify the appropriate lang
, and link to the original article using the from
field in the Front-Matter:
1 | --- |