多言語サイトを作る — locale で言語別に取得
有料オプション「多言語オプション」を有効にしたサイトでは、公開APIのコンテンツ取得系メソッドに言語コードを渡すだけで、言語別の公開版を取得できます。このガイドでは、対応言語と
locale の渡し方、未翻訳コンテンツの原文フォールバック(translation_available)、そして静的サイト(SSG)で言語別ページを焼く設計までをまとめます。
isBadRequest・E001008)を返します。locale は
@sharedan/cms-sdk 0.7 以上で渡せます。
1. 多言語オプションと対応言語
取得できる言語は en(英語)・zh-Hans(中国語簡体)・zh-Hant(中国語繁体)・ko(韓国語)・vi(ベトナム語)・fr(フランス語)・de(ドイツ語)です。原文は日本語(ja)で、原文を取得するときは
locale を指定しません(省略します)。実際に出せる言語は、運営側でサイトごとに設定した言語に限られます。
翻訳対象は、投稿・FAQ・固定コンテンツの本文、タイトル、カテゴリ名、FAQの質問/回答、メタタグ/OGPのテキスト、フォーム定義(項目ラベル等)、メンテナンス状態(title/message)です。slug・key などの識別子、投稿の published_at、メンテナンスの resumes_at は言語共通(翻訳しません)。
2. locale を渡して取得する
取得したい言語コードを locale に渡すだけです。posts.list/posts.get/posts.categories/faqs.list/faqs.categories/contents.get/forms.get/maintenance.get が対応します。
import { cms } from '../../lib/cms';
import { renderProseMirror } from '@sharedan/cms-sdk';
// 投稿一覧・個別(本文も指定言語で返る)
const { items } = await cms.posts.list('news', { locale: 'en', perPage: 20 });
const post = await cms.posts.get('news', slug, { locale: 'en' });
const html = post.body ? renderProseMirror(post.body) : '';
// FAQ・カテゴリ・固定コンテンツ・フォーム定義・メンテナンス状態も同じ要領
const faqs = await cms.faqs.list({ locale: 'ko' });
const cats = await cms.posts.categories('news', { locale: 'ko' }); // slug は言語共通
const company = await cms.contents.get('company-message', { locale: 'zh-Hant' });
const form = await cms.forms.get('contact', { locale: 'vi' });
const maint = await cms.maintenance.get({ locale: 'en' }); 3. 未翻訳のフォールバック(translation_available)
公開APIは常に各言語の公開版だけを返します。指定した言語の公開版がまだ無いコンテンツは、エラーにはせず原文(日本語)を返し、レスポンスに translation_available: false(訳を返したときは
true)を含めます。このフラグは localeを指定したときだけ付きます(原文取得時は省略=undefined)。
const { items } = await cms.posts.list('news', { locale: 'en' });
for (const p of items) {
if (p.translation_available === false) {
// 英語版がまだ無い投稿。原文(日本語)が入っている。
// 「翻訳準備中」の注記を添えて原文を見せる、などの出し分けに使える。
}
}
// locale を指定しない(=原文取得)と translation_available は付かない(undefined)。 これにより、まだ訳が無い記事も空にならず原文で表示できます。原文のまま見せるか、「翻訳準備中」の注記を添えるかを、このフラグで出し分けてください。
4. 言語別ページを焼く(SSG)
静的サイトでは、言語切替をビルド時に言語別の静的ページとして焼くのが基本です(/=原文 ja、/en/、/zh-Hant/ …)。切替はページ遷移(<a> リンク)で行い、実行時の再取得を挟まないので、静的ビルドの速さをそのまま保てます。
まず、ビルド時に「生成してよい言語」を見極めます。多言語の設定はサイトごとに異なり、まだ有効化していない言語を指定すると
400(E001008)になるため、各候補言語を軽く問い合わせて、有効な言語だけを残します。これで設定差(未有効化の言語)でビルドを落とさずに済みます。
// src/lib/locales.ts — ビルド時に「生成してよい言語」を見極める
import { ShareDanCmsApiError } from '@sharedan/cms-sdk';
import { cms } from './cms';
// 出したい候補(APIに渡す locale コード)。原文 ja はルートで別途生成するので含めない。
const CANDIDATES = ['en', 'zh-Hant'];
// 多言語が有効で、かつ運営管理画面で設定済みの言語だけを残す。
export async function resolveExtraLocales() {
const enabled = [];
for (const locale of CANDIDATES) {
try {
const { items } = await cms.posts.list('news', { perPage: 1, locale });
// 多言語が有効な公開APIは locale 指定時のレスポンスに translation_available を必ず含める。
// 付いていなければ(未有効化・旧API)その言語はスキップする。
if (items[0] && 'translation_available' in items[0]) enabled.push(locale);
} catch (e) {
// 無効/未対応の言語は 400(E001008)。その言語は生成しない。
if (e instanceof ShareDanCmsApiError && e.isBadRequest) continue;
throw e; // それ以外の実エラーはビルドを止める(壊れたサイトを配信しない)
}
}
return enabled; // 例: ['en', 'zh-Hant'](原文 ja は常にルートで生成)
}
原文(ja)はルート直下のページ(src/pages/posts/news/[slug].astro・localeなしの get())で焼き、原文以外は src/pages/[locale]/… 配下で言語ごとに焼きます。getStaticPaths で「有効言語 × 投稿スラッグ」を列挙するだけです。
---
// src/pages/[locale]/posts/news/[slug].astro(原文以外の言語ページ)
import { cms } from '../../../../lib/cms';
import { renderProseMirror } from '@sharedan/cms-sdk';
import { resolveExtraLocales } from '../../../../lib/locales';
export async function getStaticPaths() {
const locales = await resolveExtraLocales(); // ['en', 'zh-Hant'] など(原文 ja は含まない)
const paths = [];
for (const locale of locales) {
const { items } = await cms.posts.list('news', { perPage: 100, locale });
for (const p of items) {
paths.push({ params: { locale, slug: p.slug }, props: { locale } });
}
}
return paths;
}
const { locale } = Astro.props;
const post = await cms.posts.get('news', Astro.params.slug, { locale });
const untranslated = post.translation_available === false;
const html = post.body ? renderProseMirror(post.body) : '';
---
<article>
<h1>{post.title}</h1>
{untranslated && <p class="untranslated">この記事はまだ翻訳されていません。原文を表示しています。</p>}
<div set:html={html} />
</article>
言語スイッチャは、同じ記事の各言語版URLへのリンクを並べるだけです(原文はルート、他言語は
/{locale}/… のプレフィックス付き)。
// 原文 ja はルート(/…)、他言語は /{locale}/… に置く。
// 同じ記事の各言語版へのリンクを組み立てる。
const localeLink = (code, slug) =>
code === 'ja' ? '/posts/news/' + slug : '/' + code + '/posts/news/' + slug;
const langs = [
{ code: 'ja', label: '日本語' },
{ code: 'en', label: 'EN' },
{ code: 'zh-Hant', label: '繁中' },
]; apps/samples/html)が、この「ビルド時に言語別ページを焼く」構成(/=ja・/en/・/zh-Hant/)と、上記の有効言語プローブをそのまま実装しています。多言語オプションが無効なサイトでは、自動的に日本語のみのサイトとして成立します(graceful
degradation)。
5. 注意点
- パスワード保護された本文も locale で訳される:
cms.posts.unlock()にも任意のlocale(第4引数)を渡せます。個別取得と同じ言語を渡すと、ゲートされた本文も指定言語の公開版で返り、タイトルと本文の言語が揃います。未訳の本文は原文(日本語)を返しtranslation_available: falseを含めます(パスワード保護された投稿を表示する)。 -
localeはキャッシュキーに含まれます:公開APIはAccept-Languageでは出し分けません。必ずクエリのlocaleで指定してください。言語別ページは別URLとして焼かれるため、CDNキャッシュ/パージも言語ごとに独立します。 - 識別子は訳さない:
slug・key・カテゴリのslugは言語共通です。言語別ページでも、ルーティングやカテゴリ絞り込みのキーは原文と同じ値を使えます。 - 無効・未対応の言語は 400(E001008):多言語オプションが無効なサイトで
localeを指定した場合や、設定していない言語を指定した場合に返ります。SSGでは上記のプローブで事前に弾き、実行時に直接叩く構成ではisBadRequestを捕捉してください。
このページはお役に立ちましたか?
改善要望・不具合・ドキュメントの分かりにくい点など、お気軽にお寄せください。いただいた内容は今後の改善に役立てます。
お寄せいただきありがとうございます。今後の改善に役立てます。