「2023.002|JP」Issuesシリーズ: HexoとNexTテーマの多言語サポートについて

新しい要件

私の古いブログは単一言語enを使用しましたが、2つの言語で記事を公開しましたが、2つは重複していませんでした。つまり、記事は中国語または英語のいずれかでした。

この新しいブログでは、中国語、日本語、英語の3つの言語で各記事を公開する予定なので、HexoとNexT Themeの多言語サポートをいじってみましたが、ここで遭遇した問題を記録します。

HexoとNexTテーマの多言語サポートの特徴

バージョン

  • "hexo": "6.3.0"
  • "hexo-theme-next": "8.17.1"

関連文書

  • Hexo Docs/ Customization / Internationalization (i18n)
  • NexT Docs / Theme Settings / i18n

関連設定

HexoとNexT Themeの公式文書をそれぞれ参照して関連設定を見つけます

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[一部解決]: 多言語サポートが機能しない

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

ここでは2つの問題があります:

  1. ドキュメントには、多言語サポートのためにhexo-generator-index-i18nという多言語関連プラグインをインストールする必要があるということが記載されていません。
  2. 複数の言語で投稿を公開しなければならず、関連する多言語フォルダーi18n_dirが作成されます。

これら2つの問題を別々に論じましょう。

hexo-generator-index-i18n プラグインの欠如

解決策は簡単です。以下のコマンドを使用してインストールします:

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

i18n_dir と記事のアソシエーションの問題

まず、詳細に問題を説明しましょう。次のコマンドを使用して、私のブログソース(Hexo)ディレクトリを表示します。

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[解決済み]: 多言語 post の assets の問題

期待結果

post assets は、Hexoの記事が参照する必要がある最も一般的なリソースです。

Hexoのデザインコンセプトに従って、多言語の記事を書く場合、異なる言語で同じブログ記事の複数バージョンを作成する必要があります。例えば:

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"

この時点で、_posts ディレクトリ構造は次のようになります。

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

この場合、記事が画像を参照する必要がある場合、次の2つの状況に遭遇する可能性があります:

  • 異なる言語の post で同じ画像が使用される:最も一般的な状況
  • 異なる言語の post で使用されるいくつかの画像は異なる可能性があります:よりまれながら、時々そのような要件があります。例えば、我々が作成した画像にテキスト説明が含まれており、対応する言語版に対応する言語のテキスト説明を含むように作成されます。
    • この要件を要約すると:同じ assets(画像など)が存在し、言語関連の独立した assets(画像など)もサポートする
    • これは、上記の要件を満たすために画像(Assets)フォルダをどのように整理すべきかという疑問を引き起こします。

Issue 分析と関連情報

HexoのAssetスフォルダーの組織方法は2つあります。

  • 全ての post が同じAssetスフォルダーを共有する、例えば images フォルダーなどです。
    • これはデフォルトの方法ですが、個人的には推奨しません。
    • 記事が多いとき、画像などリソースは全部同じフォルダー内に入ってしまいます。ファイル名を見て判別することは出来ますが、かなり混乱します。
    • その利点としては、post などの場所で追加したAssetを適当に再利用が出来る事ですが、実際のBlogアプリケーション内でAssetの再利用率は非常に低いものと考えられます。
  • 各記事が独自のAssetスフォルダーを持つ
    • 私はこの方法を好みます。
    • 参考文献:
      • Hexo Docs - Asset Folders
      • Hexo Docs - Tag Plugins - Include Assets
    • 具体的な設定方法:Hexoの設定ファイル_config.yml内に post_asset_folder: true を設定します。
    • この方法では、上述の「言語関係なく独立したAsset(画像など)と、共通のAsset(同じ画像など)の両方が存在する」という要件を処理する場合に問題があります。
      • 同じ画像を対応した言語のAssetフォルダーに馬鹿正直に格納する事は可能ですが、同じファイルの繰り返しでディスク容量やリモートgitレポジトリーの容量を無駄に使う可能性があります

post_asset_folder: trueを設定し、マルチ言語構成を追加した後、フォルダ構造は以下の通りです:

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

解決策

同じassetsと言語関連の独立したassets(画像など)の両方を満たすために、以下の方法が使用できます:

  1. 前の節で推奨されているように、Hexoの構成ファイル_config.ymlpost_asset_folder: trueを設定します。

  2. 同じ assets:もし元の post の言語が英語なら、それを中国語と日本語の post にそれぞれ翻訳します。そうすることで、先ほど述べた重複ファイルを格納する問題を避けることができます。

    • ここで特に説明する必要があるのは、中国語と日本語の post で外部 assets を参照する際、相対パスを使用できず、この「絶対パス」{% img /en/post/Issues-Series-About-Hexo-and-NexT-Theme-Multi-Language-Support/1.png "image title" %}を使用する必要があるということです。
      • 「絶対パス」に引用符が含まれている理由は、実際の絶対パスではなく、{blog_source_folder}/public/ディレクトリをルートディレクトリとした絶対パスであるためです。
  3. 言語関連の独立した assets:対応する言語の assets にそれらを置くだけです。

Issue#3 [解決済み]:NexTテーマで異なるページで言語を切り替える際のバグ ⚠️

Issue詳細

  • 現在、NexTテーマの下で異なるページ(ホームページ、投稿、404、タグなど)で言語を切り替える際に、切り替えたページが404エラーを返す可能性があります。

期待結果

  • ホームページ:
    • 言語の切り替えが可能であること。
    • 現在のNexTテーマはこの種の言語切り替えにすでに対応しています。
  • 投稿以外のページ(about、404、tagsなど):
    • 現在のNexTテーマは、これらのページの多言語対応をサポートしていないため、別の言語に切り替えた後にこれらのページで404エラーが返されることがあります。生成された切り替え用のURLが言語を指定しているため、このバグが発生しています。シンプルにするため、これらのページでは言語の切り替え機能を削除にするか、切り替えを無効にします。
    • ユーザーがこれらのページで言語を切り替えた場合、期待通りに切り替えを無効にしてURLを変更しなくても、NexTの実装メカニズムによりページがリフレッシュされます。リフレッシュなしの動作を実現するには、必要な修正が比較的複雑です。シンプルにするため、これらの動作は変更しないでおきます。
  • 投稿ページ:
    • 言語の切り替えを許可し、選択された言語の対応する投稿が存在しない場合は、404エラーを返すこと。
    • 現在のNexTテーマは、設定で定義されたカスタムpermalinkの言語変換をうまく処理していません。これは別の修正が必要なバグです。

Issue分析と関連情報

この問題の原因は、NexTテーマが複数の言語用のURLを生成する際にバグがあるためです。現在のパス、permalink形式、およびそれらが複数の言語と関連していることを十分に考慮していません。

問題のあるコードは以下にあります:

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) コード:

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}`);
});

解決策

この問題に対処するために、自分のフォークバージョンで次のコミットを試作しました:

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

i18n_path_pwnfan という新しい関数を実装して使用しました。ただし、「期待結果」セクションには私の個人的な主観的な視点や要件が含まれているため、公式のpull request には適していないかもしれません。そのため、修正は自分のフォークしたリポジトリにのみ行いました:

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: どのように翻訳するか?

参考として、「2023.003|JP」ChatGPTを使用してHexo記事を効率的に翻訳を参照してください。

Hexo NexTテーマで複数言語をサポートする別の方法?

HexoとNexTテーマのマルチ言語サポートと含まれていることがわかります:

  • ホームページの基本要素などのマルチ言語サポート
  • 多言語の post

間接的にマルチ言語の記事を実現する別の方法として思いつくのは:

  1. HexoとNexTテーマを1つの言語(例えばen)に設定する

  2. NexTテーマ専用のタグプラグインのタブを活用して間接的にマルチ言語を実現します。 次のコードを参照してください:

    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 %}

    効果は以下の通りです:

    Where are you from?

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

    你来自哪里?

    このアプローチの問題点は次の通りです:

  • ホームページの基本的な要素(メニューなど)に関して、マルチ言語サポートがありません。
  • 投稿内で Tags を整理する方法が2つあります(en/ja/zh-CNを例として)
    • 投稿内で3つの Tags を使用し、それぞれが異なる言語のコンテンツを含める
      • 利点:比較的単純で明確なコード
      • 欠点:
        • タブは固定のスタイルフレームを持っているため、ブログ全体のページスタイルに影響を与える
        • ToCの生成に問題がある
    • post 文章内の各段落に独立した3言語の Tags を使用する
      • 利点:
        • ToCが正常に動作する
        • ブログ全体のページスタイルに影響を与えない
      • 欠点:
        • 記事内の段落が多い場合、多くのタグ{% tabs %}を使用する必要があり、面倒でコードがきれいではありません
        • ToCは1つの言語しか使用できません

技術文書のマルチ言語サポートのベストプラクティス

前節では、HexoとNexT Themeを使用してマルチ言語の投稿を書くための私自身のニーズを満たすための試みと考えを記録しました。

それでは、今まで見てきた技術文書のマルチ言語文書の中で最もエレガントなものはどれでしょうか? この問題を議論するために、別の記事「2023.004|JP」ベストプラクティスシリーズ:技術文書の多言語対応を書きました。