「2023.002|EN」Issues Series: About Hexo and NexT Theme Multi-Language Support

New Requirements

My old blog used a single language en, but published posts in two languages, and the two had no overlap. That is, the article was either Chinese or English.

I plan to publish each post in three languages, Chinese, Japanese and English, in this new blog, so I fiddled with the multi-language support of Hexo and NexT Theme, and here I record the problems encountered.

Features of Hexo and NexT Theme Multi-Language Support

Version

  • "hexo": "6.3.0"
  • "hexo-theme-next": "8.17.1"
  • Hexo Docs/ Customization / Internationalization (i18n)
  • NexT Docs / Theme Settings / i18n

Refer to the official documents of Hexo and NexT Theme respectively to find the relevant configuration

Hexo

_config.yml (Hexo)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
language:
- en
- zh-CN
- ja

permalink: :category/:lang/:title/
permalink_defaults:
lang: en

i18n_dir: :lang

new_post_name: :lang/:title.md # File name of new posts

# Home page setting
index_generator:
single_language_index: false # show multiple language posts in homepage

NexT Theme

_config.next.yml (NexT Theme)
1
2
# Show multilingual switcher in footer.
language_switcher: true

Issues

Issue#1[Partially Solved]: Multi-language Support Not Working

I pointed out the problem I encountered when configuring multi-language support in the official document of NexT Theme:

the doc missed a necessary plugin hexo-generator-index-i18n, what's more, in my test you should also create a post with each language, or the related i18n_dir will not be generated. The latter may be a bug.

pwnfan, "NexT User Docs – Starting to Use – Internationalization"comment about problem in multi-language setting

There are two problems here:

  1. The document does not mention that you must install a multi-language related plugin hexo-generator-index-i18n for multi-language support.
  2. You must publish posts in multiple languages, and the related multi-language folder i18n_dir will be created.

Let's discuss these two problems separately.

Missing hexo-generator-index-i18n Plugin

The solution is simple, just use the following command to install it:

1
$ npm install hexo-generator-index-i18n --save

i18n_dir and Post Article Association Problem

First, let's describe the problem in detail. Use the following command to view my blog source (Hexo) directory, as follows:

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
28
29
30
31
32
33
34
35
36
37
38
(base) ➜  Blog tree pwnfan -L 1
pwnfan
├── README.md
├── _config.next.yml
├── _config.yml
├── db.json
├── node_modules
├── package-lock.json
├── package.json
├── public
├── scaffolds
├── source
└── themes

6 directories, 6 files

(base) ➜ Blog tree pwnfan/public -L 1
pwnfan/public
├── 404.html
├── LICENSE
├── about
├── archives
├── atom.xml
├── css
├── en
│ └── index.html # Hexo NexT Theme Home Page(original English)
├── images
├── index.html
├── ja
│ └── index.html # Hexo NexT Theme Home Page(Japanese Translations)
├── lib
├── post
├── search.xml
├── sitemap.txt
├── sitemap.xml
├── tags
└── zh-CN
└── index.html # Hexo NexT Theme Home Page(Simple Chinese Translations)

Issue#2[Solved]: Multi-Language Post Assets Containing Issues

Expected Outcome

Post assets are resources that Hexo's posts need to reference, the most common of which are images.

If we follow the design concept of Hexo, when writing multi-language posts, we need to create multiple versions of the same blog post in different languages, such as:

1
2
3
4
5
6
# default language is English
hexo new post "About Hexo and NexT Theme Mult-Language Support"
# japanese
hexo new post --lang ja "About Hexo and NexT Theme Mult-Language Support"
# simple Chinese
hexo new post --lang zh-CN "About Hexo and NexT Theme Mult-Language Support"

At this time, the _posts directory structure is as follows:

1
2
3
4
5
6
7
8
source/_posts
├── en
│ └── Issues-Series-About-Hexo-and-NexT-Theme-Multi-Language-Support.md
├── images
├── ja
│ └── About-Hexo-and-NexT-Theme-Mult-Language-Support.md
└── zh-CN
└── About-Hexo-and-NexT-Theme-Mult-Language-Support.md

In this case, if the article needs to reference pictures, it may encounter the following two situations:

  • The same pictures are used for different language posts: the most common situation
  • Some pictures used by different language posts may be different: less common, but occasionally there will be such requirements, such as the pictures we make contain text descriptions, and will be made according to the corresponding language version containing the corresponding language text description.
    • Summarize this requirement: both exist the same assets (such as pictures), and also support language-related independent assets (such as pictures)
    • This raises a question: how should the picture (Assets) folder be organized to meet the above requirements?

Issue Analysis && Relevant Information

We know that there are two ways to organize the Assets folder in Hexo:

  • All posts share the same Assets folder, such as the images folder.
    • Although this is the default way, I personally do not recommend it.
    • When there are more articles, resources such as pictures are all included in the same folder. Although they can be distinguished by file name, it will still be very messy.
    • Its advantage is that it can conveniently reuse assets previously added in different places such as post, but in fact in such blog applications, the reuse rate of assets is extremely low.
  • Each post has its own assets folder.
    • I prefer this way.
    • Reference documents:
      • Hexo Docs - Asset Folders
      • Hexo Docs - Tag Plugins - Include Assets
    • Specific setting method: set post_asset_folder: true in the Hexo configuration file _config.yml.
    • This way also encounters problems when dealing with the above requirement "that is, there are both the same assets (such as pictures) and language-related independent assets (such as pictures)".
      • You can crudely store the same pictures in the corresponding language assets folders, but there will be duplicate files which will waste disk space and remote git repository space.

After we set post_asset_folder: true and add multi-language configuration, the folder structure is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
(base) ➜  pwnfan git:(dev) ✗ tree source/_posts                             
source/_posts
├── en
│ ├── Issues-Series-About-Hexo-and-NexT-Theme-Multi-Language-Support
│ └── Issues-Series-About-Hexo-and-NexT-Theme-Multi-Language-Support.md
├── images
├── ja
│ ├── About-Hexo-and-NexT-Theme-Mult-Language-Support
│ └── About-Hexo-and-NexT-Theme-Mult-Language-Support.md
└── zh-CN
├── Issues-Series-About-Hexo-and-NexT-Theme-Multi-Language-Support
└── Issues-Series-About-Hexo-and-NexT-Theme-Multi-Language-Support.md

Solution

To meet the requirement of "both existing assets (such as pictures) and language-related independent assets (such as pictures)", the following methods can be used:

  1. As recommended in the previous section, set post_asset_folder: true in the Hexo configuration file _config.yml.

  2. For the same assets: if the original post language is English, then translate it into Chinese post and Japanese post respectively. Then you can directly use the asset of the English post in the Chinese and Japanese posts to avoid the problem of storing duplicate files mentioned earlier.

    • Here it needs to be specially explained that when referring to external assets in Chinese and Japanese posts, you cannot use relative paths, but need to use this "absolute path" {% img /en/post/Issues-Series-About-Hexo-and-NexT-Theme-Multi-Language-Support/1.png "image title" %}
      • The reason why the "absolute path" contains quotation marks is because it is not a real absolute path, but an absolute path based on the directory {blog_source_folder}/public/ as the root directory.
  3. Language-related independent assets: just put them in the corresponding language assets folder.

Issue#3 [Resolved]: Bug in NexT Theme when switching languages on different pages ⚠️

Issue Details

  • Currently, when switching languages back and forth on different pages (home page, post, 404, tags, etc.) under the NexT Theme, there might be an issue where the switched page returns a 404 error.

Expected Outcome

  • Home Page:
    • Language switch should be available.
    • The current NexT Theme already supports this type of language switching.
  • Non-post Pages (about, 404, tags, etc.):
    • The current NexT Theme returns a 404 error after switching to another language on these pages because it doesn't support customizing multiple languages for these types of pages, but the generated switch URL specifies a language, leading to this bug. To keep things simple, the expectation is to disable language switching or make it invalid on these pages.
    • If users switch languages on these pages, even if we make the switch ineffective as described in the expected way, meaning keeping the URL unchanged, NexT's implementation mechanism will still trigger a page refresh. To achieve a no-refresh behavior, the required modifications are relatively complex. To keep things simple, we will leave it unchanged.
  • Post Pages:
    • Language switching should be allowed, and if there's no corresponding post in the chosen language, it should return a 404 error.
    • The current NexT Theme doesn't handle custom permalink language conversion defined in the config very well. This is another bug that needs to be fixed.

The reason for this problem is due to a bug in the NexT Theme when generating URLs for multiple languages. It fails to fully consider the current path, permalink format, and their relation to multiple languages.

The problematic code can be found at

bug in Line #10 function `i18n_path(language)`hexo-theme-next/layout/_partials/languages.njk
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{%- if theme.language_switcher and languages.length > 1 %}
<div class="languages">
<label class="lang-select-label">
<i class="fa fa-language"></i>
<span>{{ language_name(page.lang) }}</span>
<i class="fa fa-angle-up" aria-hidden="true"></i>
</label>
<select class="lang-select" data-canonical="" aria-label="{{ __('accessibility.select_lang') }}">
{% for language in languages %}
<option value="{{ language }}" data-href="{{ i18n_path(language) }}" selected="">
{{ language_name(language) }}
</option>
{% endfor %}
</select>
</div>
{%- endif %}

i18n_path(language) code:

bug in i18n_path()hexo-theme-next/scripts/helpers/engine.js
100
101
102
103
104
105
106
107
/**
* Get page path given a certain language tag
*/
hexo.extend.helper.register('i18n_path', function(language) {
const { path, lang } = this.page;
const base = path.startsWith(lang) ? path.slice(lang.length + 1) : path;
return this.url_for(`${this.languages.indexOf(language) === 0 ? '' : '/' + language}/${base}`);
});

Solution

I attempted to solve this issue in my forked version with the following commit:

https://github.com/pwnfan/hexo-theme-next/commit/4a4ce6301ba5e0f7e05eea353c5c771e102dc8bb

I implemented and used a new function called i18n_path_pwnfan. However, as the "Expected Outcome" section contains my personal subjective viewpoints and requirements, it might not be suitable for an official pull request. Therefore, I made the fix only in my own forked repository:

invoke customized function `i18n_path_pwnfan()`hexo-theme-next/layout/_partials/languages.njk
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{%- if theme.language_switcher and languages.length > 1 %}
<div class="languages">
<label class="lang-select-label">
<i class="fa fa-language"></i>
<span>{{ language_name(page.lang) }}</span>
<i class="fa fa-angle-up" aria-hidden="true"></i>
</label>
<select class="lang-select" data-canonical="" aria-label="{{ __('accessibility.select_lang') }}">
{% for language in languages %}
<option value="{{ language }}" data-href="{{ i18n_path_pwnfan(language) }}" selected="">
{{ language_name(language) }}
</option>
{% endfor %}
</select>
</div>
{%- endif %}
source code of customized function `i18n_path_pwnfan()`hexo-theme-next/scripts/helpers/engine.js
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/**
* Get page path given a certain language tag, pwnfan_customized
*/
hexo.extend.helper.register('i18n_path_pwnfan', function (language) {
const { path, lang } = this.page;
let base = path;

if (path === "index.html") { // homepage without language path
base = `${language}/${path}`;
} else if (path === `${lang}/index.html`) { // homepage with a language path
if (language === "en") {
language = "";
}
base = path.replace(`${lang}/`, `${language}/`);
}
else if (!path.endsWith(".html")) { // post page
if (path.startsWith(`${lang}/`)) {
base = `${language}/${path.slice(lang.length + 1)}`;
} else {
base = path.replace(`/${lang}/`, `/${language}/`);
}

}
return this.url_for(base);
});

Issue#4: How to Translate?

Please refer to another post 「2023.003/EN」Translate Hexo Articles efficiently using ChatGPT for reference.

Another Way to Support Multiple Languages in Hexo NexT Theme?

As we can see, the multi-language support and inclusion of Hexo and NexT Theme are:

  • Multi-language support for basic elements of homepages, such as menus, etc.
  • Multi-language posts

Another way I can think of to achieve multi-language posts indirectly is:

  1. Set Hexo and NexT Theme to only one language, such as en

  2. Take advantage of the Tabs in the Tag Plugins exclusive to NexT Theme, which can indirectly realize multi-language. Refer to the following code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    {% tabs Multiple Language Post Example %}
    <!-- tab English -->
    Where are you from?
    <!-- endtab -->

    <!-- tab 日本語 -->
    ご出身地はどちらですか。
    <!-- endtab -->

    <!-- tab 中文 -->
    你来自哪里?
    <!-- endtab -->
    {% endtabs %}

    The effect is as follows:

    Where are you from?

    ご出身地はどちらですか。

    你来自哪里?

The problems with this approach are:

  • No multi-language support for basic elements of homepages, such as menus, etc.
  • There are two ways to organize Tags in posts (taking en/ja/zh-CN as an example)
    • Only use 3 Tags in the post, each containing all the content in different languages
      • Advantages: relatively simple and clear code
      • Disadvantages
        • Tabs have a fixed style frame, affecting the page style of the entire blog
        • ToC generation has problems
    • For each paragraph in the post, use independent 3 language Tags
      • Advantages:
        • ToC works normally
        • Does not affect the overall page style of the blog
      • Disadvantages:
        • When there are many paragraphs in the article, a large number of Tags {% tabs %} need to be used, which is cumbersome and the code is not neat
        • ToC can only use one language

Best Practice for Multi-Language Support for Technical Documents

The previous section records the attempts and thoughts made to meet my own needs for writing multi-language posts with Hexo and NexT Theme.

So which is the most elegant among the multi-language documents of technical documents I have seen so far? I wrote another article 「2023.004|EN」Best Practices Series: Multi-Language Support for Technical Documents to discuss this issue.