はじめに
これまで自前だったサイトジェネレーターからHugoに移行を始めています。
ついでに、Data Templatesの仕組みを利用してベタ打ちだった内容を、外部データから自動生成する事も目指しています。
日英に対応する必要があり、hugoのi18n機能についてあまり情報がなかったので、Data Templatesの機能と合わせて、メモを残しておくことにしました。
Hugo v0.9x以降を利用する際の考慮点
Hugoのバージョンアップ後、セキュリティモデルが変更されたため、asciidoctorコマンドを利用する場合、次のようなエラーになります。
hugo v0.92.2+extended linux/amd64 BuildDate=unknown
Error: Error building site: "/home/user01/hugo/site01/content/_index.ja.adoc:1:1": access den
ied: "asciidoctor" is not whitelisted in policy "security.exec.allow"; the current security configuration is:
[security]
enableInlineShortcodes = false
[security.exec]
allow = ['^dart-sass-embedded$', '^go$', '^npx$', '^postcss$']
osEnv = ['(?i)^(PATH|PATHEXT|APPDATA|TMP|TEMP|TERM)$']
[security.funcs]
getenv = ['^HUGO_']
[security.http]
methods = ['(?i)GET|POST']
urls = ['.*']
これに対応するため、config.toml に以下のような変更を加えています。
[security]
enableInlineShortcodes = true
[security.exec]
allow = [ "^asciidoctor$", "^sass$" ]
Hugo i18n機能の利用方針
content/ ディレクトリの構造として、イントラネットのサイトは、
content/{en,ja}/
のようにディレクトリ階層の中で言語を分離しています。
また別に作成しているサイトでは、content/_index.{en,ja}.adoc
のように同一階層の中でファイルで言語を切り替える方法を採用しています。
これまで利用してきて、前者の content/{jp,en}/ のような構造は、無駄が多いと感じています。理由は、トップレベルでディレクトリを分けてしまった場合に、その下の構造を一致させるには一定の労力が必要になるからです。
大規模プロジェクトでHugoを利用している例としてgithub.com/kubernetes/website.git では、content/直下のディレクトリを言語毎に分けています。複数チームでメインの文書を作成するチームと翻訳するチームといった分担をする場合には、それぞれ干渉しないために、トップディレクトリを分けた方が適当に思えます。
しかし、一人ないしは単一のチームがコンテンツの更新を担当しているケースでは、ファイルのsuffixで、.{ja,en}.md と分ける後者のタイプが作業効率や構造の把握といった面からは便利だと思います。
Kubernetes/website.gitのconfig.toml上では、contents/{en,cn,...}/ のようにディレクトリを言語毎に分けていますが、DefaultContentLanguage = "en"
かつ defaultContentLanguageInSubdir = false
としていて、英語以外の言語を選択した場合に、/cn/のような言語毎のサブディレクトリに誘導されます。
defaultContentLanguageInSubdir = true
とすれば、en(英語)をデフォルトとして表示されますが、全ての言語が並列に配置されます。サイトを構成する際に、日本語版を正として、英語版は参考情報として正確さを担保したくないといった場合には、DefaultContentLanguage = "ja"
、defaultContentLanguageInSubdir = false
とする方法もあるかもしれません。
いずれの場合も、static/ ディレクトリは、あまり使用しないようにした方が良いと思われます。
利用しているディレクトリ構造の概要
hugoコマンドで、proj_root以下にデフォルトのディレクトリ構造を作成します。
$ hugo new site proj_root
さらにディレクトリ、ファイルを配置し、以下のようなディレクトリ構造としています。
proj_root/
+ i18n/en.toml
+ i18n/ja.toml
+ data/en/activity/20170401.01.toml
+ data/en/activity/...
+ data/en/menu/nav_header.yaml
+ data/en/menu/nav_main.yaml
+ data/en/menu/...
+ data/ja/activity/20170401.01.toml
+ data/ja/activity/...
+ data/ja/menu/nav_header.yaml
+ data/ja/menu/nav_main.yaml
+ data/ja/menu/...
+ content/en/activity/20170401.01.adoc
+ content/en/...
+ content/ja/...
+ layouts/...
+ ...
(注: "nav-main.yaml"のようにハイフンを含むファイル名では問題が発生しています。
ファイル名を利用してデータにアクセスするため、$data.menu.nav-main
のような指定をすると、'-'の1文字が予想していない文字列である旨のエラーが表示されます。)
i18n/ 以下には{en,ja}.tomlの2つのファイルだけを配置していて、辞書的な使い方を想定しています。当初は、headerの文言やmavメニューの文言について、config.tomlの[languages.{en,ja}.params]の中に置いていましたが、現在では、data/以下にmenu/のようなディレクトリを作成し、nav_header.yaml, nav_main.yamlのように用途毎にファイルを配置しています。
config.tomlへの追加設定
最小限の設定は、次のようになります。
baseURL = "http://localhost:1313/"
hasCJKLanguage = true
DefaultContentLanguage = "ja"
defaultContentLanguageInSubdir = true
[languages]
[languages.ja]
languageName = "日本語"
languageCode = "ja"
contentDir = "content/ja"
weight = 1
[languages.ja.params]
key1 = val1
[languages.en]
languageName = "English"
languageCode = "en"
contentDir = "content/en"
weight = 2
[languages.en.params]
key1 = val1
[languages.{ja,en}.params]に追加した項目は、処理している言語に応じて、.Site.Params以下に展開されます。
config.tomlに設定する値と、.Siteなどへの展開について
config.toml上に設定した値の多くは、.Siteのprefixを通じてアクセスが可能です。
ただし、ルールが分かりにくいように感じています。
googleAnalytics: UA-123-45
のように設定した値は、{{.Site.GoogleAnalytics }}
のようにアクセスすることができます。ここで、config.tomlに記述した内容が、 {{ .Site.xxx }}
のようにアクセスできるのだと理解すると、間違うかもしれません。この例でも先頭の小文字が大文字になっていて、実際はドキュメントを参照する必要があります。
例えば、Languageセクションが次のようになっているとします。
[languages]
[languages.ja]
languageName = "日本語(JP)"
languageCode = "JA"
それぞれ次のような方法で参照が可能です。
- .Site.Language.LanguageName → "日本語(JP)" (変更可能)
- .Site.LanguageCode → "JA" (変更可能)
また、.Page(.)経由で、.Languageにアクセスができるので、LanguageNameは次のような方法でも記述できますが、LanguageCodeはそう動かないので、次のようにエラーとなります。
- .Language.LanguageName → "日本語(JP)
- .LanguageCode → エラー
現在処理中のロケールは次のように参照できますが、config.tomlに記述する[languages.XX]
に対応するXX
が格納される仕様のようで変更はできなさそうです。
- .Site.Language.Lang → "ja"
- .Page.Lang == .Lang → "ja"
ロケールに関連する情報は、Site VariablesやPage Variableのリファレンスを確認する必要があります。
公式サイトで配布されているテーマの利用について
themes.gohugo.ioで多くのテーマが提供されていますが、それぞれ前提とする変数などが違うため、テーマ毎にconfig.tomlファイルなどの内容を書き換える必要があります。
公式discourseフォーラムへの投稿の中で、wordpressと比較して使いづらいといった意見があり、標準化が行なわれていない旨の記述も確認できます。
多くのテーマがbootstrap,w3css,font-awesomeなどを利用しつつ、独自に文書構造をデザインしています。
そのため、コピーライト表示の有無といった表示の制御では、config.tomlに共通の設定が確認できるテーマは多いものの、細かい部分ではテーマ毎にconfig.tomlに書かなければいけない内容、セクション構造が異なるため、互換性はほぼありません。
テーマの切り替えを目指すのであれば、最終的には自分で違いを吸収するための努力を払うことが必要になります。
多言語化やPagination機能を利用するメリットは大きいですが、自分のサイトを「気分によっていろいろなテーマに切り替えたい」と思う人にとってはあまり向かないツールだと思います。
ユースケース (UC)
個別のケース毎に対策をまとめていきます。
【UC】言語毎に読み込むDataファイルを切り替える
data/{ja,en}ディレクトリ以下にフィアルを配置している場合、次のようにindex関数を利用して、ターゲットとなる言語のデータ一式を取得します。ポイントは、変数を利用する際に、.Site.Data.{{Site.LanguageCode}}.activity
のような変数参照を間に挟む書式を受け付けてくれないため、分割してアクセスする必要がある点です。
一度に取得するデータを小さく保つため、data/menu/{en,ja}/data.tomlのようなアクセス方法も可能性としてはあると思いますが、私はdata/{en,ja}/
のようにトップレベルで分けています。
{{ $data := index .Site.Data .Site.LanguageCode }}
{{ range $data.menu.nav_main }}
...
{{ end }}
【UC】i18n/ディレクトリに配置した言語別ファイルの利用
i18n/ja.toml,i18n/en.tomlファイルに、直接 key = value
形式で変数を記述していくとエラーになります。
$ hugo
Building sites … panic: interface conversion: interface {} is string, not map[string]interface {}
i18nに配置するファイルでは必ずセクション([])を切って、その中のother変数(もしくはone)に内容を記述します。
[site_title]
other = "Yasuhiro ABE's Web"
そして、layout/_default/baseof.html の中などのテンプレートで参照します。
...
<title>{{ i18n "site_title" . }}</title>
...
基本的に単数・複数形を使い分ける文化圏の考え方で整備されているので、one変数とother変数を使い分ける形になっています。公式ページ、go-i18n公式
実際には、ほぼother変数に固定されているので、直感とは少し違ったイメージになっています。
【UC】config.toml に言語毎に文字列を設定する
i18n/ja.toml以外に、config.toml の中に言語毎の変数を設定する事が可能です。
[languages]
[languages.ja]
[languages.ja.params]
author = "作者"
[languages.en]
[languages.en.params]
author = "Author"
これを layout/partials/header.html などから参照するには次のようにします。
<dl>
<dt>{{ .Site.Params.author }}</dt>
...
しばらく使ってみた結果、config.tomlにはサイト全体についての情報を集約するべきだと思っています。
単純な単語の訳であれば、i18n/にあるファイルを充実させるべきだと思います。
【UC】日or英版のページへのリンクを挿入する
例えば、Using linked translations in your template など、言語間を遷移するリンクの挿入する方法はまとめられています。この例はheaderにlinkタグを埋め込むものなので、コンテンツ内のnav部分にリンクを挿入するため次のようなコードを利用しています。
{{ if .IsTranslated }}
{{ range .Translations }}
<div id="lang-selector">[ <a href="{{ .Permalink }}"
alt="Switch to {{.Language.LanguageName}}">{{ .Language.LanguageName }}</a> ]</div>
{{ end }}
{{ end }}
HugoのDiscourseをみているとkubernetesのようにPulldownメニューにする例なども掲載されています。
【UC】リストから条件に合うデータだけを表示する
range,withなどでループに入っている時に、全部を表示せずに特定の文字列を含むデータだけにアクセスしたい。
様々な実装が考えられますが、front matterに適当なキーワードをDataKey = "key"
のように記述しておくことで、そのページで読み込むデータのタイプを指定することができます。
{{ if in .date $.DataKey }}
<h3>{{ .date }}</h3>
<p>{{ .name }}</p>
{{ end }}
この例ではif in
を使うことで完全一致ではなく、部分一致を利用しています。
他には、if isset <list> <arg>
やif eq <arg1> <arg2>
といった表現があり、eqは数値でも文字列でも使えます。
【UC】ローカルに配置するPDFファイル等添付ファイルの置き場所について
PNG, JPEGなどの画像ファイルはcontent/ディレクトリに配置するとpublic/ディレクトリに適切にコピーされます。しかし、PDFは画像ファイルとは違いmediaType.goで定義されておらず、その他ファイルとして扱われるため、static/以下に上手に配置する必要があります。画像ファイル等、Hugoが理解できるファイルとして取り扱う事もできそうですが、適切とはいえないと思っています。
【2020/01/30追記】自前でビルドしたHugo Static Site Generator v0.59.1/extendedでテストしたところ、PDFファイルに限らず、contentファイルに配置されているファイルはpublicディレクトリ以下にコピーされるようになっていました。このため、css,javascript等のファイルを除き、markdownやasciidocで記述した記事に関連するファイルは全てcontentディレクトリに移動しています。
コンテンツと説明を一緒に管理したいような場合にはディレクトリが分散してしまうのは、あまりうれしくないところです。特に言語を変化させても表示させるファイルは同じ、場合によって表示するファイルが言語毎に異なる、といった場合にはPDFだけでなく添付ファイル全般の取り扱いについて試行錯誤しています。
課題はいろいろあって、例えば、次のようにstatic/ディレクトリ直下では{en,ja}/ディレクトリを別に分けておく必要があります。profile.ja.adoc等から参照する際は、profile/reference.pdfのようなディレクトリを作成し、profile.ja.adocにはlink:reference.pdf[追加情報-PDF]
のようなリンクを埋め込む事になります。
具体的には次のような構造になります。
contents/profile.en.adoc
contents/profile.ja.adoc
contents/profile/ref.ja.pdf ## そのままの名前でpublic/en/profile/ref.ja.pdfにコピーされる
contents/profile/ref.en.pdf ## そのままの名前でpublic/ja/profile/ref.en.pdfにコピーされる
contents/profile/map.pdf ## profile.{en,ja}.adocの両方から参照されるファイル
試してみると、最後のmap.pdfだけは{en,ja}のどちらか一方のディレクトリにしかコピーされません。内容が同じでもコピーする必要がある点は注意が必要で、あまり良い方法とはいえないかなと思ってしまいます。
【2020/06/25追記】Hugo v0.69.2/extendedで確認したところ、content/map.pdfファイルは、public/{ja,en}/map.pdfの両方にコピーされていましたが、サブディレクトリに配置した content/note/map.{ja,en}.adocに対応するcontent/note/map/map.pdfに配置したファイルはデフォルト言語にのみコピーされていました。
contents/ ディレクトリの中に、PDFやPNGファイルを配置することは良いアイデアだと思います。
その一方で多言語対応する場合には、テストや確認を必ず行なうようにしてください。
以上