静的に書き出す
@k8ordo/staticは、パラメータを持つページのpathnameを、ビルドの時点で知る必要があります。ロケールの区間はどのページでも同じ値を取るので、その一覧はロケールの集合が作ります。このページでは、locales.pathsの渡し方と、ほかのパラメータがあるときの書き方、404ページの言語を説明します。
このページの内容
ロケールの数だけページを書き出す
frameworkのpathsオプションにlocales.pathsを渡します。/:localeの区間を持つパターンが、ロケールの数だけのpathnameになります。
vite.config.tsimport { framework } from '@k8ordo/static';
import { defineConfig } from 'vite';
import { locales } from './src/i18n';
export default defineConfig({
plugins: [framework({ paths: locales.paths })],
});locales.paths(['/:locale', '/:locale/about']);
// ['/ja', '/en', '/ja/about', '/en/about']置き換えは区間ごとに行います。そのため、/:localeCodeのように名前がlocaleで始まるだけのパラメータには触れません。
書き出すpathnameは、1つずつ別のリクエストとして描かれます。ページごとにスキーマがロケールを受け付けるので、文言はそのページのロケールで書かれます。ビルドは複数のページを同時に描きますが、あるページのロケールがほかのページに漏れることはありません。
ほかのパラメータもあるとき
/:locale/blog/:slugのように、ほかのパラメータも持つパターンは、locales.pathsを通しても/ja/blog/:slugのように:slugが残ります。
ビルドはパラメータの残ったpathnameを書き出せないので、static build needs pathnames for /:locale/blog/:slug — supply them with the "paths" optionというエラーで止まります。
vite.config.tsimport { framework } from '@k8ordo/static';
import { defineConfig } from 'vite';
import { locales } from './src/i18n';
import { readSlugs } from './src/posts';
const expandSlugs = async (paths: string[]) => {
const slugs = await readSlugs();
return paths.flatMap((path) =>
path.includes('/:slug')
? slugs.map((slug) => path.replace('/:slug', `/${slug}`))
: [path],
);
};
export default defineConfig({
plugins: [
framework({
paths: (patterns) => expandSlugs(locales.paths(patterns)),
}),
],
});先にlocales.pathsでロケールを展開し、残ったパラメータを同じ関数の中で展開します。pathsにはPromiseを返す関数も渡せるので、記事の一覧を読み込んでから答えられます。
404ページの言語
静的なホストは、知らないURLすべてに1つの404.htmlを返します。このファイルはビルドの番兵の区間で1回だけ描かれるので、文言は既定のロケールで書かれます。
番兵の区間はスキーマが受け付けないため、ビルドがそれまでにどのロケールのページを描いていても、404.htmlは既定のロケールになります。1つのファイルを、訪問者ごとの言語に合わせることはできません。
ブラウザは、このファイルをハイドレーションしません。別のURLのために描かれたものなので、ハイドレーション中に読んだ文言と食い違うからです。代わりに訪問者のURLで描き直すので、Client Componentはそこで訪問者のロケールになります。
そのため、not-found.tsxはClient Componentにして文言を描きます。JavaScriptが動かない訪問者には、既定のロケールのまま届きます。
routes/[locale]/not-found.tsx'use client';
import * as m from '../../messages';
export default function NotFound() {
return (
<main>
<h1>{m.notFound.title()}</h1>
<p>{m.notFound.description()}</p>
</main>
);
}<html lang>は、ルートレイアウトがサーバーで書いた既定のロケールのまま残ります。このサイトでは、[locale]のレイアウトが描くClient Componentが、effectの中でdocument.documentElement.langを訪問者のロケールに直しています。
メモ
開発サーバーと@k8ordo/serverでは、404は訪問者のURLで描かれます。URLの区間が集合のロケールなら、サーバーが書いたHTMLの時点でそのロケールです。