Polyglotを使用してJekyllブログで多言語サポートを実装する方法 (1) - Polyglotプラグインの適用 & hreflang altタグ、サイトマップ、言語選択ボタンの実装
'jekyll-theme-chirpy'ベースのJekyllブログにPolyglotプラグインを適用して多言語サポートを実装したプロセスを紹介します。 この投稿はシリーズの最初の記事で、Polyglotプラグインの適用とHTMLヘッダー、サイトマップの修正部分を扱います。
概要
約4ヶ月前の2024年7月初め、Jekyll基盤でGithub Pagesを通じてホスティングしているこのブログにPolyglotプラグインを適用して多言語サポートの実装を追加しました。 このシリーズでは、ChirpyテーマにPolyglotプラグインを適用する過程で発生したバグとその解決過程、そしてSEOを考慮したHTMLヘッダーとsitemap.xmlの作成方法を共有します。 シリーズは2つの記事で構成されており、現在読んでいるこの記事はそのシリーズの最初の記事です。
- 第1回:Polyglotプラグインの適用 & hreflang altタグ、サイトマップ、言語選択ボタンの実装(本文)
- 第2回:Chirpyテーマのビルド失敗と検索機能エラーのトラブルシューティング
要件
- ビルドした結果物(ウェブページ)を言語別のパス(例:
/posts/ko/
、/posts/ja/
)で区別して提供できること。 - 多言語サポートに追加的に必要な時間と労力を可能な限り最小化するために、作成した原本のマークダウンファイルのYAML front matterに’lang’および’permalink’タグを一つ一つ指定しなくても、ビルド時に該当ファイルが位置するローカルパス(例:
/_posts/ko/
、/_posts/ja/
)に応じて自動的に言語を認識できること。 - サイト内の各ページのヘッダー部分は適切なContent-Languageメタタグとhreflang代替タグを含み、Googleの多言語検索のためのSEOガイドラインを満たすこと。
- サイト内で各言語をサポートするすべてのページリンクを漏れなく
sitemap.xml
で提供できること、またsitemap.xml
自体は重複なくルートパスに1つだけ存在すること。 - Chirpyテーマで提供されるすべての機能が各言語ページで正常に動作すること。そうでない場合は正常に動作するように修正すること。
- ‘Recently Updated’、’Trending Tags’機能の正常動作
- GitHub Actionsを利用したビルド過程でエラーが発生しないこと
- ブログ右上の投稿検索機能の正常動作
Polyglotプラグインの適用
Jekyllは多言語ブログを基本サポートしていないため、上記の要件を満たす多言語ブログの実装には外部プラグインを活用する必要があります。検索してみるとPolyglotが多言語ウェブサイト実装用途で多く使用されており、上記の要件のほとんどを満たすことができるため、このプラグインを採用しました。
プラグインのインストール
私はBundlerを使用しているため、Gemfile
に次の内容を追加しました。
1
2
3
group :jekyll_plugins do
gem "jekyll-polyglot"
end
その後、ターミナルでbundle update
を実行すると自動的にインストールが完了します。
Bundlerを使用しない場合は、ターミナルでgem install jekyll-polyglot
コマンドでgemを直接インストールした後、_config.yml
に次のようにプラグインを追加することもできます。
1
2
plugins:
- jekyll-polyglot
設定の構成
次に_config.yml
ファイルを開き、以下の内容を追加します。
1
2
3
4
5
6
# Polyglot Settings
languages: ["en", "ko", "es", "pt-BR", "ja", "fr", "de"]
default_lang: "en"
exclude_from_localization: ["javascript", "images", "css", "public", "assets", "sitemap"]
parallel_localization: false
lang_from_path: true
- languages: サポートしたい言語のリスト
- default_lang: デフォルトのフォールバック言語
- exclude_from_localization: ローカライゼーション対象から除外するルートファイル/フォルダパス文字列の正規表現指定
- parallel_localization: ビルド過程で多言語処理を並列化するかどうかを指定するブール値
- lang_from_path: ブール値で、’true’に設定すると投稿マークダウンファイル内にYAML front matterで’lang’属性を別途明示しなくても、該当マークダウンファイルのパス文字列が言語コードを含んでいれば、これを自動的に認識して使用する
サイトマッププロトコルの公式ドキュメントでは次のように明記されています。
“The location of a Sitemap file determines the set of URLs that can be included in that Sitemap. A Sitemap file located at http://example.com/catalog/sitemap.xml can include any URLs starting with http://example.com/catalog/ but can not include URLs starting with http://example.com/images/.”
“It is strongly recommended that you place your Sitemap at the root directory of your web server.”
これを遵守するためには、同じ内容の
sitemap.xml
ファイルが言語別に作成されずにルートディレクトリに1つだけ存在するように’exclude_from_localization’リストに追加して、以下の誤った例のようにならないようにする必要があります。誤った例(各ファイルの内容は言語別に異なるものではなく、すべて同じ):
/sitemap.xml
/ko/sitemap.xml
/es/sitemap.xml
/pt-BR/sitemap.xml
/ja/sitemap.xml
/fr/sitemap.xml
/de/sitemap.xml
‘parallel_localization’を’true’に指定するとビルド時間が大幅に短縮されるメリットがありますが、2024年7月時点で本ブログに対してこの機能を有効にした際、ページ右側のサイドバーの’Recently Updated’と’Trending Tags’部分のリンクタイトルが正常に処理されず、他の言語と混ざってしまうバグがありました。まだ安定化が不十分なようですので、サイトに適用する前に事前にテストを行う必要があります。また、Windowsを使用する場合もこの機能がサポートされないため、無効化する必要があります。
また、Jekyll 4.0では次のようにCSSソースマップの生成を無効化する必要があります。
1
2
sass:
sourcemap: never # In Jekyll 4.0 , SCSS source maps will generate improperly due to how Polyglot operates
投稿作成時の注意点
多言語投稿作成時に注意すべき点は次の通りです。
- 適切な言語コードの指定:ファイルパス(例:
/_posts/ko/example-post.md
)またはYAML front matterの’lang’属性(例:lang: ko
)を利用して適切なISO言語コードを指定する必要があります。Chrome開発者ドキュメントの例を参考にしてください。
ただし、Chrome開発者ドキュメントでは地域コードを’pt_BR’のような形式で表記していますが、実際には’pt-BR’のように_の代わりに-を使用する必要があります。これは後にHTMLヘッダーにhreflang代替タグを追加する際に正常に動作するためです。
- ファイルパスと名前は一貫性がある必要があります。
詳細については、GitHub untra/polyglotリポジトリのREADMEを参照してください。
HTMLヘッダーとサイトマップの修正
次に、SEOのためにブログ内の各ページのHTMLヘッダーにContent-Languageメタタグとhreflang代替タグを挿入する必要があります。
HTMLヘッダー
2024年11月時点の最新バージョンである1.8.1リリース基準で、Polyglotはページヘッダー部分で{% I18n_Headers %}
Liquidタグを呼び出す際に上記の作業を自動的に行う機能があります。 しかし、これはそのページに’permalink’属性タグを明示的に指定していることを前提としており、そうでない場合は正常に動作しません。
したがって、私はChirpyテーマのHead.htmlを取得した後、以下のように直接内容を追加しました。 Polyglot公式ブログのSEO Recipesページを参考に作業しましたが、page.permalink
がない場合はpage.url
属性を代わりに使用するように修正しました。 また、Google Search Central公式ドキュメントを参考に、サイトのデフォルト言語ページに対するhreflang属性値としてsite.default_lang
の代わりにx-default
を指定することで、サイトがサポートする言語リストに訪問者の優先言語がない場合や訪問者の優先言語を認識できない場合にフォールバックとしてそのページリンクを認識するようにしました。
1
2
3
4
5
6
<meta http-equiv="Content-Language" content="{{site.active_lang}}">
{% if site.default_lang %}<link rel="alternate" hreflang="x-default" href="{{site.url}}{{page.url}}" />{% endif %}
{% for lang in site.languages %}{% if lang == site.default_lang %}{% continue %}{% endif %}
<link rel="alternate" hreflang="{{lang}}" href="{{site.url}}/{{lang}}{{page.url}}" />
{% endfor %}
サイトマップ
Jekyllでビルド時に自動生成されるサイトマップは多言語ページを正常にサポートしないため、ルートディレクトリにsitemap.xml
ファイルを作成し、次のように内容を入力します。
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
---
layout: content
---
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">
{% for lang in site.languages %}
{% for node in site.pages %}
{% comment %}<!-- very lazy check to see if page is in the exclude list - this means excluded pages are not gonna be in the sitemap at all, write exceptions as necessary -->{% endcomment %}
{% unless site.exclude_from_localization contains node.path %}
{% comment %}<!-- assuming if there's not layout assigned, then not include the page in the sitemap, you may want to change this -->{% endcomment %}
{% if node.layout %}
<url>
<loc>{% if lang == site.default_lang %}{{ node.url | absolute_url }}{% else %}{{ node.url | prepend: lang | prepend: '/' | absolute_url }}{% endif %}</loc>
{% if node.last_modified_at and node.last_modified_at != node.date %}<lastmod>{{ node.last_modified_at | date: '%Y-%m-%dT%H:%M:%S%:z' }}</lastmod>{% elsif node.date %}<lastmod>{{ node.date | date: '%Y-%m-%dT%H:%M:%S%:z' }}</lastmod>{% endif %}
</url>
{% endif %}
{% endunless %}
{% endfor %}
{% comment %}<!-- This loops through all site collections including posts -->{% endcomment %}
{% for collection in site.collections %}
{% for node in site[collection.label] %}
<url>
<loc>{% if lang == site.default_lang %}{{ node.url | absolute_url }}{% else %}{{ node.url | prepend: lang | prepend: '/' | absolute_url }}{% endif %}</loc>
{% if node.last_modified_at and node.last_modified_at != node.date %}<lastmod>{{ node.last_modified_at | date: '%Y-%m-%dT%H:%M:%S%:z' }}</lastmod>{% elsif node.date %}<lastmod>{{ node.date | date: '%Y-%m-%dT%H:%M:%S%:z' }}</lastmod>{% endif %}
</url>
{% endfor %}
{% endfor %}
{% endfor %}
</urlset>
サイドバーに言語選択ボタンを追加
_includes/lang-selector.html
ファイルを作成し、次のように内容を入力しました。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<p>
{%- for lang in site.languages -%}
{%- if lang == site.default_lang -%}
<a ferh="{{ page.url }}" style="display:inline-block; white-space:nowrap;">
{%- if lang == site.active_lang -%}
<b>{{ lang }}</b>
{%- else -%}
{{ lang }}
{%- endif -%}
</a>
{%- else -%}
<a href="/{{ lang }}{{ page.url }}" style="display:inline-block; white-space:nowrap;">
{%- if lang == site.active_lang -%}
<b>{{ lang }}</b>
{%- else -%}
{{ lang }}
{%- endif -%}
</a>
{%- endif -%}
{%- endfor -%}
</p>
次に、Chirpyテーマの_includes/sidebar.html
の”sidebar-bottom”クラス部分に次の3行を追加して、先ほど作成した_includes/lang-selector.html
の内容をJekyllがページビルド時に読み込むようにしました。
1
2
3
<div class="lang-selector">
{%- include lang-selector.html -%}
</div>
さらなる読み物
パート2に続きます。