i18n
ルール
- Next.js App Router:
next-intl を使用 — useTranslations() フック、NextIntlClientProvider、ロケール検出用ミドルウェア
- React SPA:
react-i18next を使用 — useTranslation() フック、遅延読み込み用 i18next-http-backend を備えた i18n.init()
- ロケール認識ルーティング:
/en/about、/fr/about — ミドルウェアを使用して Accept-Language ヘッダーを検出しリダイレクト
- 翻訳ファイル: ロケールごと、名前空間ごとの JSON —
messages/en/common.json、messages/fr/common.json
- 名前空間分割: 機能/ページごとに1つの名前空間 —
auth.json、dashboard.json、settings.json — ページが必要とするものだけを読み込む
- 複数形: ICU メッセージ形式を使用 —
{count, plural, one {# アイテム} other {# アイテム}}
- 補間:
t('greeting', { name }) — 翻訳された文字列を決して連結しない
- 日付/数値/通貨:
Intl.DateTimeFormat、Intl.NumberFormat を使用 — フォーマットをハードコードしない
- RTL サポート:
<html> に dir="rtl" を使用、CSS 論理プロパティ(margin-left ではなく margin-inline-start)を使用、アラビア語/ヘブライ語でテスト
パターン
import createMiddleware from "next-intl/middleware";
export default createMiddleware({
locales: ["en", "fr", "de", "ar"],
defaultLocale: "en",
});
import { useTranslations } from "next-intl";
function ProductCard({ price, date }: { price: number; date: Date }) {
const t = useTranslations("products");
const format = useFormatter();
return (
<div>
<h2>{t("title")}</h2>
<p>{format.number(price, { style: "currency", currency: "USD" })}</p>
<p>{format.dateTime(date, { dateStyle: "medium" })}</p>
</div>
);
}
避けるべきこと
- ユーザー向け文字列のハードコーディング — すべての文字列は
t() を経由する必要があります
- 翻訳テキストの文字列連結 — 補間または ICU 形式を使用します
- すべてのロケールを先読み込み — ロケールと名前空間ごとに遅延読み込みします
- レイアウトに CSS の
left/right を使用 — RTL 互換性のために論理プロパティを使用します
- ロケールを
localStorage のみに保存 — SEO と共有可能性のために URL ベースのロケールを使用します