🏊♀️
Next.js + i18n + hooks で作る言語切り替えコンポーネント
前提
- 下記の記事1.のように多言語対応を実現し、記事2.のように言語情報の Redux 化を行った
- 言語切り替えコンポーネントについては、記事2. で作っていたがベースのみ
- よって、この記事で言語切り替えコンポーネントの完成までのフローをまとめます
記事
環境
- Next.js
- TypeScript
- App Router
- Redux
- Redux Toolkit
- i18n
- 日・英の言語対応(デフォルトは日)
言語切り替えコンポーネント本体
結論から、言語切り替えコンポーネントは以下。
- 現在の言語(
currentLocale
) - 言語を切り替えるための関数(
toggleLocale
)
を返してくれるカスタムフックuseLanguage
を作成した。
src/components/localeSwitcher/localeSwitcher.tsx
"use client";
import { i18n, type Locale } from "@/i18n/i18n-config";
import { useLanguage } from "@/hooks/useLanguage"; //⭐️
import styles from "./localeSwitcher.module.scss";
export default function LocaleSwitcher() {
const { currentLocale, toggleLocale } = useLanguage(); //⭐️
return (
<ul className={styles.localeSwitcher}>
{i18n.locales.map((item: Locale) => {
let isActive = false;
isActive = currentLocale === item;
return (
<li key={item} className={styles.localItem}>
{isActive ? (
<span className={styles.current}>{item}</span>
) : (
<button onClick={toggleLocale}>{item}</button>
)}
</li>
);
})}
</ul>
);
}
useLanguage
カスタムフックカスタムフックuseLanguage
の中身の全貌は以下。
src/hooks/useLanguage.ts
src/hooks/useLanguage.ts
import { useEffect } from "react";
import { useRouter, usePathname } from "next/navigation";
// hooks
import { RootState, AppDispatch } from "@/store/store";
import { useAppSelector, useAppDispatch } from "@/hooks/hooks";
import { setLanguage, setPathName } from "@/features/language/languageSlice"; // ⭐️
// i18n
import { i18n, type Locale } from "@/i18n/i18n-config";
export const useLanguage = () => {
const router = useRouter();
const pathName = usePathname();
// 1. Reduxで管理している言語情報
const dispatch = useAppDispatch<AppDispatch>();
const currentLocale = useAppSelector(
(state: RootState) => state.language.language
);
// 2. URLのパス名か dispatchが変更されるたびに実行
useEffect(() => {
const segments = pathName?.split("/");
if (segments && i18n.locales.includes(segments[1] as Locale)) {
dispatch(setLanguage(segments[1] as Locale));
}
dispatch(setPathName(pathName || "/"));
}, [pathName, dispatch]);
// 3. 言語を切り替えるための関数
const toggleLocale = () => {
const newLocale: Locale = currentLocale === "en" ? "ja" : "en";
const redirectedPathName = (locale: Locale) => {
if (!pathName) return "/";
const segments = pathName.split("/");
segments[1] = locale;
return segments.join("/");
};
router.push(redirectedPathName(newLocale));
dispatch(setLanguage(newLocale));
};
return { currentLocale, toggleLocale };
};
上記src/hooks/useLanguage.ts
内でインポートしている(下記引用)languageSlice(⭐️)の中身は以下。
import { setLanguage, setPathName } from "@/features/language/languageSlice"; // ⭐️
src/features/language/languageSlice.ts
src/features/language/languageSlice.ts
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
// Define a type for the slice state
export interface LanguageState {
language: "en" | "ja";
pathName: string | null;
}
// Define the initial state using that type
const initialState: LanguageState = {
language: "ja",
pathName: null,
};
export const languageSlice = createSlice({
name: "language",
initialState,
reducers: {
setLanguage: (state, action: PayloadAction<"en" | "ja">) => {
state.language = action.payload;
console.log("current locale:", state.language);
},
setPathName: (state, action: PayloadAction<string>) => {
state.pathName = action.payload;
},
},
});
export const { setLanguage, setPathName } = languageSlice.actions;
export default languageSlice.reducer;
状態の取得と初期化
const router = useRouter();
const pathName = usePathname();
// 1. Redux 内で管理している言語情報の dispatch と 現在の言語 を取得
const dispatch = useAppDispatch<AppDispatch>();
const currentLocale = useAppSelector(
(state: RootState) => state.language.language
);
useEffect
useEffect() 内の処理は、URLのパス名か dispatch が変更されるたびに実行される。
やっていること:
-
- URLを "/" で分割した2番目の値に有効な言語コードが入っているかをチェック
- YES => 現在の言語として、Reduxストア内の言語設定を更新する
-
- Reduxストアに現在のパス名を保存する
useEffect(() => {
const segments = pathName?.split("/");
// 1. URLを "/" で分割した2番目の値に有効な言語コードが入っているかをチェック
if (segments && i18n.locales.includes(segments[1] as Locale)) {
// 1-2. setLanguage アクションをディスパッチし、Reduxストア内の言語設定を更新する
dispatch(setLanguage(segments[1] as Locale));
}
// 2. setPathName アクションをディスパッチし、Reduxストアに現在のパス名を保存する
dispatch(setPathName(pathName || "/"));
}, [pathName, dispatch]);
toggleLocale関数
言語を切り替えるための関数。
やっていること:
-
- 新しいロケールの定義
-
- 新しいロケールのURLを再構築し、そのパスに遷移させる
- 2-0. 上記のために、引数に入るロケールに基づいてURLを再構築する関数を作成
-
- 新しいロケールを現在の言語として Redux ストア内の言語設定を更新する
const toggleLocale = () => {
// 1. Redux 内で管理している言語が en だったら、新しいロケールを ja とする。
const newLocale: Locale = currentLocale === "en" ? "ja" : "en";
// 2-0. 引数に入るロケールに基づいてURLを再構築する関数
const redirectedPathName = (locale: Locale) => {
if (!pathName) return "/";
const segments = pathName.split("/");
segments[1] = locale;
return segments.join("/");
};
// 2. 新しいロケールのURLを再構築し、そのパスに遷移させる
router.push(redirectedPathName(newLocale));
// 3. setLanguage アクションをディスパッチし、Reduxストア内の言語設定を更新する
dispatch(setLanguage(newLocale));
};
戻り値
return { currentLocale, toggleLocale };
Discussion