「海外の取引先にも見せたいので、英語版も足せますか」。納品から半年後にこう聞かれたとき、見積もりが翻訳費だけで収まるかどうかは、最初にどう作ったかでほぼ決まります。

多言語対応は文章を流し込む作業ではなく、URLとデータの持ち方を決める設計の話。EmDash(AstroネイティブのヘッドレスCMS)のドキュメントで裏を取った仕様と、自分のサイトのAPIを叩いて確かめた結果。先に決めておくと後で効くのは、次の4点です。

多言語化の前に決める4つ:1言語1ページ、URLの付け方、訳さない項目、訳が無いときの扱い

1言語=1ページとして持つ

EmDashは「行ごとに1ロケール」というモデルを取ります。フランス語版は英語版の付属データではなく、独立した1件のコンテンツ。自前のID・slug・公開状態・リビジョン履歴を持ち、共通の translation_group で束ねられます。

id       | slug        | locale | translation_group | status
01ABC... | my-post     | en     | 01ABC...          | published
01DEF... | mon-article | fr     | 01ABC...          | draft
01GHI... | mi-entrada  | es     | 01ABC...          | published

この形だと、発注側に嬉しいことが3つ。

  • 日本語版を公開したまま、英語版だけ下書きで寝かせておける
  • URLを言語ごとに自然な文字列にできる(/blog/my-post/fr/blog/mon-article
  • 履歴もロケール別。英語版を差し戻しても日本語版は動かない

代わりに一覧の取得は1ロケール分しか返しません。「日本語と英語をまとめて新着順に並べる」画面は素直には作れない。

URLの付け方ひとつで管理画面が開かなくなる

EmDashはAstroの i18n 設定をそのまま読みます。ロケール一覧も既定ロケールもフォールバックの連鎖も、書く場所は astro.config.mjs の1か所。

i18n: {
  defaultLocale: "ja",
  locales: ["ja", "en"],
  fallback: { en: "ja" },
  // routing は書かない(既定の prefix-other-locales が安全)
}

踏みやすい地雷が、既定ロケールにもプレフィックスを付ける設定です。prefixDefaultLocale: true(または routing: "prefix-always")を入れると、/_emdash/admin とその配下がすべて404になります。全ページルートが言語プレフィックスを要求するようになり、統合が注入したルートまで巻き添えを食うため。/ja/_emdash/admin/... に読み替えても管理画面のルーターが対応しません。EmDashのドキュメントはこれをAstro側の制約として明示しています(issue #369)。/_emdash/api/* のAPIルートだけは無事です。

Astroの既定は prefixDefaultLocale: false=既定言語はプレフィックスなし、それ以外だけ /en/ が付く形。管理画面と共存できるのはこちらです。どうしても日本語側も /ja/ に置きたいなら、サイトの手前(エッジ)でリダイレクトを噛ませます。

URLルーティングの比較:既定は管理画面が開けるが、prefix-alwaysでは404になる

なお、あとから「日本語も /ja/ 配下に移したい」となると、既存URLが全部変わる話に化けます。移行の段取りはリニューアルで検索順位を落とさないための実務にまとめました。

「訳さない項目」を先に決める

フィールドごとに translatable という設定があり、既定は true。ここを false にした項目は翻訳を作った時点でコピーされ、以降はグループ内の全ロケールで同期されます。

  • 訳す:見出し、本文、要約、画像のalt、メタディスクリプション
  • 訳さない:型番、外部システムのID、並び順、価格の元データなど、言語で変わってはいけない値

「英語版だけ品番が古い」という事故は、同期させるべき値を訳せる項目のまま置いたときに起きます。逆に、訳すべきaltを非翻訳にすれば英語ページに日本語のaltが残る。どちらも公開後に気づくと直す箇所が言語の数だけ増えます。

statuspublished_at などシステム側の値は常にロケール別で、同期の対象外です。フィールドの割り方そのものはクライアントが更新しても崩れないフィールド設計に書きました。

訳が無いページに何を出すか

未翻訳のページに404を返すか、既定言語の内容を出すか。EmDashは設定したフォールバックの連鎖をたどります。fallback: { en: "ja" } なら、英語を探す→無ければ日本語→それも無ければ既定ロケール。

ここに落とし穴がひとつ。フォールバックが効くのは単一エントリの取得だけで、一覧クエリは要求したロケールの分しか返しません。記事詳細は日本語で出るのに英語版の記事一覧は空、という見え方になります。翻訳が数本の状態で英語版を公開するなら、一覧側の出し方を先に決めておきたい。

フォールバックが使われたときは応答のメタデータに fallbackLocale が入るので、「このページはまだ翻訳されていません」の注記を出す材料になります。

検索エンジン向けの配線は自動で動きます。コレクション別サイトマップは翻訳ごとに独立した <url> を出し、translation_group で束ねた兄弟を xhtml:link の代替URLとして相互に張る。x-default は既定ロケール版で、未公開の翻訳は代替URLに出ません。下書きの英語版が検索エンジンに漏れる心配は要らない、ということ。公開前の見せ方はヘッドレスCMSのプレビュー実装にまとめています。

あとから足すと、既存の記事は「en」で入っている

このサイトはi18n未設定の単一言語構成です。コンテンツAPIを叩いたら、日本語で書いた記事が locale: "en" として返ってきました。?locale=ja を付けると0件。i18n設定が無いときの実効ロケールは en(ドキュメントのタクソノミーの節にも同じ記述があります)。translationGroup には自分自身のIDが入っていました。

いま単一言語で動いているサイトに後からi18nを足して locales: ["ja", "en"] とすると、既存の日本語記事は「英語の行」として存在することになります。ドキュメントには、タクソノミーの定義と用語について、設定外のロケールが混じるとサーバーログに警告が出ること、SQLで locale を書き換えて直す手順が載っています。コンテンツ本体の行にも同じ整理が要る前提で工数を見ておくほうが安全(こちらの修復手順は見つけられませんでした)。

新規で立てるなら、単一言語のうちに i18n ブロックだけ入れて defaultLocale を実際の言語に合わせておく。それだけで、この付け替えは最初から起きません。

英語版を足すときの順番:i18n設定、訳さない項目、翻訳の作成、ロケール別の公開

多言語は「翻訳を入れる箱を足す」より「URLとフィールドの持ち方を決める」ほうが先に来ます。既存サイトへの追加でも新規でも、着手前に構成を一度見ておくと手戻りが減ります。Astro・ヘッドレスCMSの実装や既存サイトの改修のご相談はお問い合わせフォームからどうぞ。

本記事は2026年8月時点の情報です。