定義
Locale suffix(ロケール接尾辞)とは、Markdown ファイル名の末尾に .en.md や .ja.md のように言語コードを付けて、slug は共有し locale だけが異なる文書版を区別する保存方式です。接尾辞のないファイルはデフォルト locale(例: ko)として扱います。
同じ my-article slug の下に my-article.md(ko)、my-article.en.md、my-article.ja.md が共存すれば、href・canonical・hreflang が slug 軸を共有でき、多言語サイトマップをシンプルに保てます。
なぜ必要か
言語ごとに content/en/、content/ja/ のようにフォルダを分けると、category・内部リンク・ビルドスクリプトが分岐し、保守コストが大きくなります。suffix 方式はディレクトリ構造はそのままに、locale だけをデータ軸として追加します。
ただし locale をファイル名にだけ入れ、DB の primary key や検索インデックス id を type:slug のままにすると、行の衝突・検索 hydration 0 件・MiniSearch の duplicate id 例外が静かに起きます。locale はファイル名 → 型 → SQLite id → search-index id → API クエリまで一貫して通す必要があります。
動作の仕組み
- ローダー: glob 時に
.{locale}.mdをパースしContentDocument.localeを埋めます。なければko。 - SQLite id:
type:slug:locale(例:knowledge:webpack-splitchunks:en)。list/detail API はリクエスト locale で WHERE。 - 検索インデックス: MiniSearch の document id も DB と同じフォーマット。
SearchScope.localeでフィルタ。 - 未翻訳 fallback: detail でリクエスト locale の文書がなければ
/koへ redirect(404 や空の fallback render ではなく明示的に)。 - 翻訳メタ: en/ja の機械翻訳は frontmatter
translated: machine。status を draft にして非公開にすると、その locale のリストが空に見えます。 - SEO: sitemap の hreflang はファイルが実際に存在する locale だけ出力。canonical・
og:locale・JSON-LD のinLanguageを同期。
実務での適用
build-content-db.ts、schema.ts、loader.ts、queries.ts、search-index.tsを 1 PR で locale 対応に拡張する。- PK/id フォーマット変更前に
grepで利用箇所(検索 hydration、list API、relations)を全件確認する。 - detail 4 箇所にコピペされた redirect を
getLocalizedDocumentOrRedirectのようなヘルパー 1 つに集約する。 - relations・load-more・無限スクロール API もアクティブ locale を明示引数で受け取る。
- 大量翻訳を委任する際は「Read 圧縮時に本文を再構成しない、
limitでページネーション再読み」を指示する。
トレードオフ
| 選択 | 利点 | 欠点 |
|---|---|---|
| locale suffix | slug・href 共有、hreflang 単純 | id フォーマット変更時に全パイプライン同時修正 |
locale subtree (/en/...) | フォルダを見れば言語区別が容易 | slug・リンク・ビルド規則の二重化 |
| 単一ファイル + frontmatter locale | ファイル数が少ない | 同一 slug 多言語の同時編集・diff が難しい |
| 未翻訳 404 | 厳格 | UX が荒い |
| 未翻訳 → ko redirect | 読める fallback | URL が ko に変わる(意図的な選択) |
使うべきでない場合
- 文書ごとに locale ごとに slug・構造が完全に異なる場合(翻訳ではなく別コンテンツ)。
- id に locale を入れる前に検索・DB 利用箇所を揃える計画がない場合(部分適用)。
- 機械翻訳を draft で非公開にし、その言語のリストを空にする場合(意図しない空カタログ)。
よくある間違い
- DB id だけ
type:slug:localeに変え、search-index id はtype:slugのまま → duplicate throw または 0 件 hydration。 - list ページだけ locale スコープし、relations/load-more は ko 固定。
translated: machineなのに status draft で非公開にし、en/ja ナビが「空」に見える。- サブエージェントが圧縮された Read で本文を再構成し、翻訳ファイルが原文とずれる。
関連概念
- json-ld-structured-data —
inLanguage・canonical メタ - inverted-index-full-text-search — 検索 document id の整合
- single-source-of-truth-content-metadata — frontmatter SSOT