ヘッダーからフッターまでの文言は @k8ordo/i18n の message から出ています。下の挨拶は引数を取る文言です。
こんにちは。
判定中greeting(name) の引数は、ja に書いた関数の引数から型が付きます。en 側を別の引数で書くとコンパイルが通りません。
ロケール集合を定義して Register に載せ、文言を message で1つずつ書く。あとは呼ぶだけ。
defineLocales(['ja', 'en']) を書くのは1回だけ。既定値・所属判定・交渉・URL区間・[locale] のスキーマは全部そこから出るので、一覧を複製する場所が無い。localize / delocalize が区間の付け外しを、paramsSchema が /fr/… を本物の404にする。pathname の残りは @k8ordo/router のもの。negotiate(navigator.languages) も negotiate(parseAcceptLanguage(header)) も同じ関数。要求の順に、完全一致→同じ言語→既定値で決める。message({ ja, en }) が1つの文言。全ロケールが揃わないと型が通らず、引数の型は書いた関数から流れる。キーの一覧も辞書オブジェクトも無い。[locale] がその描画のロケールになり、ブラウザでは URL がロケール。nav.home() はどちらでも同じに描ける。設計ガイドは npm パッケージに同梱されています。AIコーディングエージェントは node_modules/@k8ordo/i18n/docs/ からインストールした版そのものを読みます。
// i18n.ts — 一覧はここにしか書かない
export const locales = defineLocales(['ja', 'en']);
declare module '@k8ordo/i18n' {
interface Register { locale: LocaleOf<typeof locales> }
}
// messages/nav.ts — 文言は 1 つずつ関数。全ロケールが揃わないと通らない
export const home = message({ ja: 'ホーム', en: 'Home' });
export const greeting = message({
ja: (name: string) => `こんにちは、${name}さん`,
en: (name) => `Hello, ${name}`, // 引数の型は ja から流れる
});
// routes/[locale]/layout.tsx — 受理したロケールがこの描画のロケールになる
export const paramsSchema = locales.paramsSchema; // /fr/… は 404
// Server Component でも Client Component でも、同じ 1 行
<h1>{nav.home()}</h1>
<p>{nav.greeting(name)}</p>