開発記録
3 言語サイトで URL を言語の情報源にする理由
繁体字中国語、英語、日本語のルートの役割から、URL がページの言語、文言、metadata、言語切り替え先を決める仕組みを説明します。
- 多言語サイト
- Astro
- ルーティング
- 情報設計
この翻訳は、2026 年 10 月 2 日の原稿に記録されたルートの契約を説明しています。以下の英語版・日本語版ノート一覧の空状態は、当時の記録を指します。現在も記事がないという意味ではありません。言語プレフィックスがないルート例は繁体字中国語版のルートで、元のコード例のラベルも変更せずに残しています。
多言語サイトは、最初にブラウザーの言語設定を調べる必要があるとは限りません。言語をグローバルな状態の中に隠す必要もありません。ここで扱うサイトでは、URL 自体が言語の情報源です。繁体字中国語版は /、英語版は /en、日本語版は /ja を使います。
これはルートの命名規則に見えますが、どの文言を読み込むか、html lang に何を出力するか、metadata にどの言語を使うか、言語切り替え先のリンクが実際に存在するかにも影響します。ここでの判断は、言語が公開 URL の一部であるなら、ビルドもリンクも同じ locale の情報源から導き、別のブラウザー側の状態で上書きしないということです。
デフォルト言語とほかの言語を区別します
記録された言語の集合は、zh-TW、en、ja の 3 つです。繁体字中国語がデフォルト言語なので、固定ページは元のパスをそのまま使います。英語と日本語は、先頭に言語プレフィックスを付けます。
次のルート例は、元の中国語ラベルをそのまま残しています。「繁中」は繁体字中国語、「英文」は英語、「日文」は日本語です。プレフィックスのない /about は繁体字中国語版のルートです。
繁中:/about
英文:/en/about
日文:/ja/about
コードの localizedPath() の役割はこれだけです。固定ページとその言語を受け取り、対応する URL を返します。ブラウザーの言語検出も、cookie の読み取りも、利用者の好む言語の判断もしません。小さな役割ですが、URL の生成規則を 1 か所に保てます。
URL 生成関数は、デフォルト言語の例外も維持しています。zh-TW には /zh-TW が不要なので、/、/notes、/about などのパスが繁体字中国語版になります。プレフィックスを付けるのは英語と日本語だけです。3 言語すべてに一律でプレフィックスを付けると、既存の繁体字中国語版の URL が変わり、古いリンクや canonical の規則にも追加の移行問題が生じます。
固定ページの言語は route props から受け取ります
固定ページは、読み込み後にブラウザーが空の外枠を別の言語へ切り替える仕組みではありません。英語と日本語の動的ルートは、ビルド時に固定ページの集合を列挙し、lang とページの役割を同じ Astro コンポーネント群へ渡します。
/en/about と /ja/about は AboutPage を共有していますが、実際には別々に出力される 2 つのページです。コンポーネントが受け取る lang は、次を決めます。
- タイトルや段落をどの言語の辞書から読み込むか。
- ナビゲーションをどの言語の固定ページ群へリンクするか。
html要素のlang属性に何を出力するか。- ページの説明、Open Graph の文言、構造化データにどの言語を使うか。
「コンポーネントを共有すること」は、「ページの状態を 1 つに共有すること」ではありません。共有するのはページの構造です。実際の言語の値は引き続き route props から渡され、ビルド時点で確定しています。
リンクにも同じ言語の情報源を使います
主要ナビゲーションのリンクは、現在の URL 文字列を部分的に切り取ってつなぎ直すものではありません。現在のページと移動先の言語を、同じパス生成関数に渡します。たとえば、英語の Notes ページから日本語へ切り替えるなら、移動先は /ja/notes です。繁体字中国語の製品ページから英語へ切り替えるなら、/en/products/daily-order になります。
この方法なら、言語切り替えに通常のリンクとしての性質を保てます。
- ブラウザーで新しいタブに開けます。
- JavaScript が動かなくても切り替えられます。
- クローラーや支援技術がリンク自体を理解できます。
- 各言語のページが、それぞれ canonical と
hreflangを持てます。
「現在の言語」を保持する別の client store は必要ありません。ページは URL から自分の言語を把握しているため、言語切り替えでは別の実在する URL を生成すればよいのです。
固定ページとコンテンツページでは多言語化の問題が異なります
固定ページは、ルートと文言辞書をサイト自身が管理しているため、3 言語すべてに対応するページがあることを事前に把握できます。ノート記事は異なります。中国語の記事に、まだ英語版や日本語版がない場合もあります。
したがって、記事ページの言語リンクをグローバルな言語一覧だけで作ることはできません。記事テンプレートは、同じ stableId を使って、実際に存在し、正式な公開条件も満たす翻訳を探してから対応するリンクを生成します。翻訳のない言語について、存在するように見える記事 URL を無理に組み立てることはしません。
この 2 つは分けて考えられます。
| コンテンツの種類 | 言語別 URL の生成方法 | 翻訳がない場合の扱い |
|---|---|---|
| 固定ページ | 固定ルートと言語の集合から構築します | 対応するすべての言語にページがあります |
| ノート記事 | 実在する記事と stableId から構築します |
実在しない翻訳記事のページを生成しません |
「多言語サイトに英語のページがあること」と「すべての記事に英語の翻訳があること」は、同じ意味ではありません。前者は固定ページのルート契約で、後者はコンテンツの提供状況です。
URL が言語を決めても、すべての内容が翻訳済みとは限りません
原稿に記録された時点では、英語版と日本語版のノート一覧は、一時的に正式な記事がなくても通常のページとして存在していました。一覧はその言語の空状態を表示し、繁体字中国語の記事をコピーしたり、読者を繁体字中国語版のホームへリダイレクトしたりしませんでした。これは当時の空状態の例であり、現在も一覧が空であるという主張ではありません。
この空状態は、次の 2 つを分けるために重要です。
- その言語のサイトへの入口がすでに存在すること。
- その言語で公開閲覧できる記事が何件あるかということ。
記事がなければ、ページは記事がないと伝えます。記事があれば、初めてその記事のルートが現れます。URL はコンテンツへの入口を先に用意できますが、実在しない翻訳をコンテンツの代わりに補うことはできません。
この方法が意図的に扱わないこと
URL を言語の情報源にしても、すべての多言語対応の問題を解決したわけではありません。記録された方法では、次のことを意図的に行いません。
- ブラウザーの言語に応じて、読者を別の locale に自動で移動させません。
- 存在しない翻訳を原文の代替ページとして扱いません。
- ルートに
/enや/jaがあるだけで、その言語の内容がすべて揃っているとは主張しません。 - 言語切り替えを、JavaScript がなければ動かないボタンにはしません。
- 各ページ自身の URL と metadata を、グローバルな言語状態で置き換えません。
将来、実際の自動リダイレクト、言語設定の記憶、さらなる翻訳を追加する場合も、公開 URL、canonical、過去のリンクと衝突しないかを先に確認する必要があります。これらは、ここで扱うルート契約によって存在が証明された機能ではありません。
多言語ページを書くときに、まず確認できること
- すべての言語にプレフィックスを付けるのではなく、どの言語がデフォルト URL を使うか決めます。
- 固定ページのルート、ナビゲーションのリンク、
lang、metadata に同じ locale の集合を使います。 - 固定ページの翻訳と記事の翻訳を別々にモデル化します。
- 翻訳ファイルと公開状態の両方が条件を満たす場合にだけ、記事の言語リンクを作ります。
- コンテンツがなければ実際の空状態を表示し、画面を埋めるための架空の記事は使いません。
- コンポーネントのファイルに
lang変数があるかを見るだけでなく、出力検証で実際の URL、canonical、言語指定を確認します。
記録されたサイトの方法は、「まず言語切り替えを作り、すべての内容をそこへ押し込む」ものではありません。公開 URL、ページのデータ、実在するコンテンツを一致させる方法です。固定ページは構造を共有し、記事はそれぞれ翻訳を待てます。どちらも URL から始まりますが、コンテンツの提供状況まで同じであるかのように見せる必要はありません。
この記録の範囲
この記事は、原稿に記録された 3 言語の固定ページ、ナビゲーションのリンク、ノート記事のルートの実装契約を整理したものです。URL が言語の情報源になる仕組みと、固定ページと翻訳記事の違いを説明しています。自動言語検出、翻訳の完全性、検索流入、読者の言語の好みを測定したとは主張していません。