🫨
Next.js × next-intl で「国旗が切り替わらない」現象をデバッグ & 修正した話
TL;DR
-
next-intl
でロケールを切り替えても 国旗 Emoji が日本のまま変わらない - 原因は 「現在のロケールを誤検知 & 遅延参照していた」 こと
-
useLocale()
は context の値なので、クライアントサイド遷移直後は古い値のまま描画される - さらに国旗を 翻訳ファイル経由 で取得していたため、
ja.json
の 🇯🇵 が常に返っていた
-
-
URL パラメータ(
/ja
,/en
,/zh
)を直接読む + 固定マップで国旗を管理 で解決
事象
<span className="text-lg">🇯🇵</span>
上記のように、どの言語でも常に 🇯🇵 が表示される。
Navigation
コンポーネントでは下記のような実装にしていた。
const flagT = useTranslations("flags");
const locale = useLocale(); // <- ここで「現在のロケール」を取得
const getFlag = (code: string) => {
// 翻訳ファイルから取り出す
try {
return flagT(code); // flags.ja → 🇯🇵 など
} catch { … }
return "🌐";
};
期待
-
/en
に遷移 → 🇺🇸 -
/zh
に遷移 → 🇨🇳
現実
- どこへ行っても 🇯🇵 …
原因を分解してみる
useLocale()
だけでは最新ロケールを取れない
1. next-intl
の useLocale()
は Context 由来。
クライアントルーター (next/navigation
) で言語を切り替えると、
router.push("/en/…")
- URL は変わる
- しかし 初回描画時点では Context がまだ
ja
-
navigation.tsx
は古いja
のままレンダリング → 🇯🇵
その後再レンダリングは走るが、国旗は 翻訳ファイルの値 をキャッシュしており変わらない。
2. 国旗 Emoji を翻訳ファイルに置いたのが落とし穴
flags
name-space を ja.json / en.json / zh.json
すべてに用意。
ところが flagT(code)
を呼び出すロケールは「現在のロケール」。
- まだ
ja
だと思っている →ja.json
から 🇯🇵 を返す - 結果、国旗は固定で 🇯🇵 に
解決策
(A) URL からロケールを直接取得
import { useParams } from "next/navigation";
const params = useParams();
const localeParam =
Array.isArray(params?.locale) ? params.locale[0] : params?.locale;
const locale = localeParam ?? useLocale(); // fallback
-
/en/**
ならlocaleParam === "en"
が確実 - Context が更新される前でも 正しいロケール を先取りできる
(B) 国旗は固定マップで管理
翻訳ファイルを通さず、単純な連想配列に変更。
const getFlag = (code: string) =>
({ ja: "🇯🇵", en: "🇺🇸", zh: "🇨🇳" }[code] ?? "🌐");
- 「国旗そのもの」は翻訳ではない
- ファイル数が増えても メンテしやすい
- ガベコピ防止で fallback も一行
実際に入れたパッチ
-import { usePathname, useRouter } from "next/navigation";
+import { usePathname, useRouter, useParams } from "next/navigation";
-const flagT = useTranslations("flags");
-const locale = useLocale();
+const params = useParams();
+const localeParam = Array.isArray(params?.locale) ? params.locale[0] : params?.locale;
+const locale = localeParam || useLocale();
-const getFlag = (code: string): string => {
- const translated = flagT(code); // ← 削除
- …
-}
+const getFlag = (code: string): string =>
+ ({ ja: "🇯🇵", en: "🇺🇸", zh: "🇨🇳" }[code] ?? "🌐");
得られた学び
-
Context の値は「遅延」する 可能性がある
- ルーティング直後の「一瞬」を見逃すとバグになる
-
翻訳ファイルに入れるべきデータか?を再考
- 国旗やアイコンは「翻訳」ではなく「定数」
- バグを直す前に 「どう再現するか」を最小化
- 今回は
console.log(locale)
を貼って即発見
- 今回は
まとめ
-
useLocale()
依存のままでは、クライアント遷移直後のロケールを正しく反映できない - URL パラメータから直接ロケールを取り、国旗はシンプルな固定マップで管理すれば OK
- 「翻訳」と「定数」を分ける のが可読性&保守性のポイント
これで /en
に行けば 🇺🇸、/zh
に行けば 🇨🇳 へ即座に切り替わるようになりました。
同じようにハマった方の参考になれば幸いです 🫡
Discussion