多言語サイトの設計 ― テキストを 3 種類に分けて持つ
シリーズ・第 3 回 — 「個人で出版ツールを作って配るまで」(全 5 回予定)。前回は出力を契約する話でした。一覧は シリーズの記事一覧 から。今回のテーマは「多言語対応をどう設計するか」です。
「サイトを多言語にする」と聞くと、まず本文の翻訳を思い浮かべます。でも実際にやってみると、翻訳が要るテキストは本文だけではありませんでした。ナビのラベル、プロフィール、設定値。それぞれ性質が違い、同じ入れ物に押し込むと無理が出ます。
うまくいったのは、テキストを種類ごとに分けて、それぞれに合った持ち方をする設計でした。今回はその話です。例として Hugo(crofty が内部で使うもの)を挙げます。
テキストは 3 種類に分かれる
多言語サイトのテキストを整理すると、だいたい次の 3 種類になります。住む場所も、翻訳の仕組みも別です。
| 種類 | 例 | 持ち方 |
|---|---|---|
| 本文 | 記事 | 言語ごとのファイル |
| 固定文言 | ナビ、ボタン、「Support」 | 言語 → 文字列の翻訳テーブル |
| 設定・データ | サイト設定、プロフィール | フィールド単位で「共通」か「言語ごと」 |
ひとつずつ見ます。
本文 ― 言語ごとのファイル
記事は、ひとつのフォルダ(ページバンドル)に言語別のファイルを並べます。
content/posts/my-article/
index.md … 日本語(既定言語)
index.en.md … 英語
photo.avif … 同梱する画像
ビルドすると /posts/my-article/ と /en/posts/my-article/ の両方ができ、言語の切り替えはページ同士のリンクで済みます。JavaScript は要りません。
ひとつだけ小さな罠があります。画像(ページではないファイル)は既定言語のパスにしか出力されないので、英語版から相対参照すると 404 になります。英語版だけ、絶対パスで既定言語側を指せば直ります。
 <!-- 日本語:相対でよい -->
 <!-- 英語:絶対パスで指す -->
固定文言 ― 翻訳テーブル
ナビゲーションやボタンのラベルのような、記事に依らない固定の文言は、本文に混ぜません。言語から文字列を引く「翻訳テーブル」でまとめて持ちます。
crofty では、こうした固定文言を言語ごとに用意してあり、言語名(「日本語」「English」)や支援リンクの「サポートする」「Support」を出し分けます。記事を 1 本も書いていなくても、サイトの枠は両言語で揃う、ということです。
設定・データ ― フィールド単位で選ぶ
設定ファイルや、プロフィールのような構造化データも、多言語の対象です。ただし値によって、全言語で同じものと、言語ごとに変えたいものが混在します。そこで、フィールド単位で「ただの文字列」か「言語ごとの値」かを選べるようにします。
設定ファイル(hugo.yaml)なら、言語ごとの設定ブロックに分けて持ちます。サイトのタイトルのように、言語で変わる値はここに置きます。
# hugo.yaml — 設定も言語ごとに値を持てる
languages:
ja:
title: "わたしのブログ"
en:
title: "My Blog"
データファイル(data/profile.yml)なら、フィールドごとに文字列かマップかを使い分けます。
support:
# 文言は言語ごと → マップで持つ
message:
ja: "記事が役に立ったら、活動を応援していただけると励みになります。"
en: "If these posts helped, a little support keeps the work going."
# URL は全言語で同じ → ただの文字列
stripe: "https://donate.stripe.com/..."
message は言語ごとなのでマップ、stripe は共通なので文字列。どちらの場合も「言語で変わる値だけ分ける」という同じ発想です。
配信は、全部ビルドして静的のまま
この 3 種類を分けておくと、配信はシンプルです。ビルド時に全言語のページを生成し、あとは静的ファイルを配るだけ。アクセスごとの判定も、クライアントの JavaScript も要りません。
唯一の別問題は「ブラウザの言語で自動的に振り分けたい」とき。これはビルド時には決められないので、やるならエッジ(配信時)かクライアント(JavaScript)に一手間を足すことになります。JavaScript を増やしたくないなら、自動振り分けは持たず、言語スイッチャで読者に選んでもらうのが素直です。crofty も当面はこの方針です。
まとめ
多言語化は、本文を翻訳することだけではありませんでした。テキストを 本文・固定文言・設定/データ の 3 種類に分け、それぞれに合う入れ物を選ぶ。そこを最初に設計しておけば、あとはビルドが全言語を出してくれます。
そして、どの層も静的なまま完結するので、表示の速さと no-JS はそのまま保てます。
← 前の記事:出力を契約する | 次の記事:自作 CLI を brew と scoop で配る →