i18n(多言語対応)

概要

gohan はディレクトリベースのロケール構造で多言語サイトをサポートする。各ロケールのコンテンツはコンテンツディレクトリ配下の名前付きサブディレクトリに格納する(例: content/en/content/ja/)。デフォルトロケールはルートURLで提供され、その他のロケールはURLプレフィックスが付与される。

既存の単一言語サイトは設定変更なしでそのまま動作する(完全後方互換)。

設定

config.yamli18n ブロックを追加する:

site:
  language: en          # default_locale のフォールバックとして使用

i18n:
  locales: [en, ja]     # ロケールコードのリスト(順序あり)
  default_locale: en    # URLプレフィックスなしで提供するロケール。省略時は site.language
フィールド デフォルト 説明
locales []string [] (無効) コンテンツディレクトリ配下のロケールコード
default_locale string site.language URLプレフィックスなしで提供するロケール

locales が空のとき、i18n は無効となり gohan は単一言語サイトとして動作する。

コンテンツ構造

content/
  en/
    posts/
      hello-world.md    → /posts/hello-world/
  ja/
    posts/
      hello-world.md    → /ja/posts/hello-world/

content/ 直下の最初のパスセグメントのみがロケールコードとして扱われる。i18n.locales に含まれる値と一致する必要がある。

翻訳リンク

同じ記事の翻訳を関連付けるには、フロントマターに translation_key を追加する。全バリアントで同じ値を指定すること。

---
title: Hello World
translation_key: hello-world
---

gohan build 中に BuildTranslationMap が実行されると、各 ProcessedArticle.Translations に兄弟ロケールの LocaleRef エントリが設定される。テンプレートでこれを使って言語切替リンクを描画できる。

データモデル

新規型

// I18nConfig holds multi-language content configuration.
type I18nConfig struct {
    Locales       []string `yaml:"locales"`
    DefaultLocale string   `yaml:"default_locale"`
}

// LocaleRef holds a locale code and the canonical URL for a translated article.
type LocaleRef struct {
    Locale string
    URL    string
}

拡張された型

// FrontMatter — 追加フィールド
TranslationKey string `yaml:"translation_key"`

// ProcessedArticle — 追加フィールド
Locale       string       // コンテンツパスから検出(例: "en"、"ja")
URL          string       // 正規URLパス(例: "/posts/hello/" または "/ja/posts/hello/")
Translations []LocaleRef  // BuildTranslationMap 後に設定される

// Config — 追加フィールド
I18n I18nConfig `yaml:"i18n"`

URLスキーム

コンテンツファイル ロケール 出力 URL
content/en/posts/hello.md en(デフォルト) public/posts/hello/index.html /posts/hello/
content/ja/posts/hello.md ja public/ja/posts/hello/index.html /ja/posts/hello/

インデックスページも同じルール:

ロケール 出力 URL
en(デフォルト) public/index.html /
ja public/ja/index.html /ja/

テンプレートでの使い方

言語切替リンク

記事ページでは .Article.Translations を使って翻訳版へのリンクを生成します:

{{if .Article.Translations}}
<nav aria-label="Language">
  <a href="{{.Article.URL}}">{{.Config.Site.Language}}</a>
  {{range .Article.Translations}}
  <a href="{{.URL}}">{{.Locale}}</a>
  {{end}}
</nav>
{{end}}

一覧ページの言語切替リンク

タグ・カテゴリ一覧ページには .Article.Translations がありません。代わりに .CurrentTaxonomy.Translations(レンダー時に設定される map[locale]URL)を 使って対応するタクソノミーページへ直接ジャンプします:

{{/* tag.html / category.html */}}
{{- $altURL := "/ja/"}}
{{- if eq .CurrentLocale "ja"}}{{$altURL = "/"}}{{end}}
{{- if .CurrentTaxonomy}}
  {{- $targetLocale := "ja"}}
  {{- if eq .CurrentLocale "ja"}}{{$targetLocale = "en"}}{{end}}
  {{- with index .CurrentTaxonomy.Translations $targetLocale}}
    {{- $altURL = .}}
  {{- end}}
{{- end}}
<a href="{{$altURL}}">言語を切り替える</a>

ページネーションがあるタクソノミーページでは、常に対向ロケールの 1 ページ目 に遷移します。ロケールごとにページ数が異なる可能性があるため、現在のページ 番号をそのまま引き継ぐとリンク切れを引き起こします。

アーカイブ一覧ページでは .CurrentArchivePath を同様に使います:

{{/* archive.html */}}
{{- $altURL := "/ja/"}}
{{- if eq .CurrentLocale "ja"}}{{$altURL = "/"}}{{end}}
{{- if .CurrentArchivePath}}
  {{- if eq .CurrentLocale "ja"}}
    {{- $altURL = slice .CurrentArchivePath 3}}
  {{- else}}
    {{- $altURL = printf "/ja%s" .CurrentArchivePath}}
  {{- end}}
{{- end}}
<a href="{{$altURL}}">言語を切り替える</a>

どちらのフィールドも gohan がすべての一覧ページで設定します:

テンプレート フィールド 値の例(EN) 値の例(JA)
tag.htmlcategory.html .CurrentTaxonomy.URL /tags/go/ /ja/tags/go/
archive.html(年) .CurrentArchivePath /archives/2024/ /ja/archives/2024/
archive.html(月) .CurrentArchivePath /archives/2024/01/ /ja/archives/2024/01/

ロケール条件分岐

{{if eq .Article.Locale "ja"}}
<p lang="ja">日本語版の記事です。</p>
{{end}}

サイトマップ(hreflang)

記事に Translations が設定されている場合、sitemap.xml にGoogleが推奨する xhtml:link hreflang alternateが自動的に追加される:

<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
        xmlns:xhtml="http://www.w3.org/1999/xhtml">
  <url>
    <loc>https://example.com/posts/hello-world/</loc>
    <lastmod>2024-01-01</lastmod>
    <xhtml:link rel="alternate" hreflang="en" href="https://example.com/posts/hello-world/"/>
    <xhtml:link rel="alternate" hreflang="ja" href="https://example.com/ja/posts/hello-world/"/>
  </url>
</urlset>

実装メモ

  • ロケール検出 (detectLocale in internal/processor/processor_impl.go): content/ 以降の最初のパスセグメントを I18n.Locales と照合する。
  • 出力パス (computeOutputPath): コンテンツパスからロケールセグメントを取り除き、デフォルト以外のロケールにはロケールコードを再付与する。
  • 翻訳マップ (BuildTranslationMap): build.goProcess() の後に一度だけ呼び出す。TranslationKey で記事をグループ化し、各記事の Translations フィールドを設定する。
  • ジェネレーター (internal/generator/html.go): ロケールごとのインデックスページと記事ページをロケール対応パスで生成する。
  • 後方互換性: I18n.Locales が空の場合、新しいヘルパーはすべて空文字を返し、動作変更はない。

ロケール別タクソノミー

i18n が有効な場合、gohan はロケールごとに個別のタクソノミーレジストリを読み込みます。 各ロケールごとにロケール固有のファイルを優先し、存在しない場合はグローバルファイルへフォールバックします:

content/
  tags.yaml              # グローバル / フォールバック
  categories.yaml        # グローバル / フォールバック
  en/
    tags.yaml            # EN 固有(任意)
    categories.yaml      # EN 固有(任意)
    posts/
  ja/
    tags.yaml            # JA 固有(任意)
    categories.yaml      # JA 固有(任意)
    posts/

バリデーション(gohan build)はロケールスコープで行われます:EN 記事は EN レジストリに対して、JA 記事は JA レジストリに対して検証されます。 ロケール固有のファイルが存在しない場合はグローバルファイルがフォールバックとして使用されるため、既存のシングルランゲージサイトは変更不要です。

site.Tagssite.Categories(テンプレートで使用)は全ロケールレジストリの重複除去済みユニオンを含むため、全タグ・カテゴリに常にアクセス可能です。

ロケール間タクソノミー連携

同じタグ・カテゴリをロケール間で連携させる(タグ・カテゴリページ上の言語 切替リンクから対向ロケールの同等ページへ遷移できるようにする)には、 各タクソノミーエントリに translation_key を追加します。同じキーを持つ エントリは互いに翻訳として扱われます。

# content/en/categories.yaml
categories:
  - name: Application
    translation_key: application

# content/ja/categories.yaml
categories:
  - name: アプリケーション
    translation_key: application

レンダリング時、gohan は .CurrentTaxonomy.Translations に同じ translation_key を持つ他ロケールの URL を map[locale]URL として設定します。 ページネーション対象ページでは、ロケールごとのページ数差異による リンク切れを防ぐため、常に対向ロケールの 1 ページ目を指します。

暗黙のフォールバック — 名前一致。 translation_key が省略された場合、 gohan は Name フィールド(大文字小文字無視)による一致にフォールバック します。これにより godockeraws のようにロケール間で同名の ASCII 識別子は設定なしで自動的にリンクされます。明示的な translation_key が常に優先されます。

ケース 必要な設定 動作
全ロケールで同名(go, docker なし Name フォールバックで自動連携
ロケールごとに名前が異なる(Applicationアプリケーション 各ロケールファイルで同じ translation_key を設定 キーで連携
対向ロケールに対応するものがない なし Translations が空 → 切替リンクはロケールルートへフォールバック

現バージョンの制限事項

  • atom.xml / feed.xml フィードは全ロケールの記事を含む。
  • ユーザーの優先言語に基づく / からの自動リダイレクトは未実装。

関連ページ