【NextJS】Internationalization(i18n) – Localization

1. 概要

前回はServer Actionsの使い方についてでした。今回はLocalization(i18n)の使い方についてです。

ユーザーが明示的に言語の切り替えをする内容となります。

  • 英語
  • 日本語
  • 韓国語
  • 中国語

対象としては開発を1年程やってて自分で最初から開発してみたい方になります。そのため細かい用語などの説明はしません。

2. nodeのインストール

こちらを参考

3. プロジェクトを作成

こちらを参考

4. 必要なライブラリをインストール

こちらを参考

5. ソースコード

※前回より差分のみを記載

5-1-1. src/lib/common/i18n/i18n-config.ts

export const i18n = {
  defaultLocale: "en",
  locales: ["en", "ja", "ko", "cn"],
} as const;

export type Locale = (typeof i18n)["locales"][number];

5-1-2. src/lib/common/i18n/dictionaries.ts

import type { Locale } from "./i18n-config";

const dictionaries = {
  en: () => import("./dictionaries/en.json").then((module) => module.default),
  ja: () => import("./dictionaries/ja.json").then((module) => module.default),
  ko: () => import("./dictionaries/ko.json").then((module) => module.default),
  cn: () => import("./dictionaries/cn.json").then((module) => module.default),
};

export const getDictionary = async (locale: Locale) =>
  dictionaries[locale]?.() ?? dictionaries.en();

5-1-3. src/lib/common/i18n/dictionaries/en.json

{
  "products": {
    "cart": "Add to Cart"
  }
}

5-1-4. src/lib/common/i18n/dictionaries/ja.json

{
  "products": {
    "cart": "カートへ追加"
  }
}

5-1-5. src/lib/common/i18n/dictionaries/ko.json

{
  "products": {
    "cart": "카트에 추가"
  }
}

5-1-6. src/lib/common/i18n/dictionaries/cn.json

{
  "products": {
    "cart": "加入购物车"
  }
}

5-1-7. src/app/nextjs/nextjs05/locale-switcher.tsx

import Link from "next/link";
import { Locale, i18n } from "@/lib/common/i18n/i18n-config";

const LocaleSwitcher = () => {
  return (
    <div>
      <ul>
        <li>Locale switcher:</li>
        <ul>
          {i18n.locales.map((locale: Locale) => {
            return (
              <li key={locale}>
                <Link href={`./nextjs05?lang=${locale}`}>
                  {locale} ({"<- Click here"})
                </Link>
              </li>
            );
          })}
        </ul>
      </ul>
    </div>
  );
};

export default LocaleSwitcher;

5-1-8. src/app/nextjs/nextjs05/metadata.ts

import { Metadata } from "next";

export const metadata: Metadata = {
  title: "i18n within app directory",
  description: "How to do i18n in Next.js 13 within app directory",
  robots: {
    index: false,
    follow: true,
    nocache: true,
    googleBot: {
      index: true,
      follow: false,
      noimageindex: true,
      "max-video-preview": -1,
      "max-image-preview": "large",
      "max-snippet": -1,
    },
  },
};

5-1-9. src/app/nextjs/nextjs05/page.module.scss

.component {
  color: blue;
  & ul {
    margin-left: 20px;
    & li {
      list-style: disc;
    }
  }
}

5-1-10. src/app/nextjs/nextjs05/page.tsx

import Box from "@mui/material/Box";

import scss from "./page.module.scss";
import GoBack from "@/lib/components/go-back";
import { getDictionary } from "@/lib/common/i18n/dictionaries";
import { Locale, i18n } from "@/lib/common/i18n/i18n-config";
import LocaleSwitcher from "./locale-switcher";
import { metadata } from "./metadata";

export { metadata };

const Nextjs05 = async ({
  searchParams,
}: {
  searchParams: { [key: string]: Locale };
}) => {
  const lang: Locale = searchParams.lang ?? i18n.defaultLocale;
  const dictionary = await getDictionary(lang);

  return (
    <div className={scss.component}>
      <GoBack />
      <br />
      <br />
      <ul>
        <li>Internationalization</li>
        <ul>
          <li>Localization</li>
        </ul>
      </ul>
      <br />
      <Box
        sx={{
          width: "100%",
          p: 2,
          border: "1px dashed grey",
          borderRadius: "20px",
          "&:hover": {
            backgroundColor: "pink",
            opacity: [0.9, 0.8, 0.7],
          },
        }}
      >
        Current locale : {lang}
        <br />
        {dictionary.products.cart}
      </Box>
      <br />
      <LocaleSwitcher />
    </div>
  );
};

export default Nextjs05;

5-1-11. src/app/nextjs/page.tsx

"use client";

import React from "react";
import { Link } from "@mui/material";
import scss from "./page.module.scss";

const Nextjs = () => {
  return (
    <div className={scss.components}>
      <ul>
        <li>
          <Link href="/nextjs/nextjs01" underline="hover">
            Nextjs01
          </Link>
        </li>
        <li>
          <Link href="/nextjs/nextjs02" underline="hover">
            Nextjs02
          </Link>
        </li>
        <li>
          <Link href="/nextjs/nextjs03" underline="hover">
            Nextjs03
          </Link>
        </li>
        <li>
          <Link href="/nextjs/nextjs04" underline="hover">
            Nextjs04
          </Link>
        </li>
        <li>
          <Link href="/nextjs/nextjs05" underline="hover">
            Nextjs05
          </Link>
        </li>
      </ul>
    </div>
  );
};

export default Nextjs;

6. サーバーを起動

npm run dev

7. ブラウザで確認

  • http://localhost:3000

8. ディレクトリの構造

.
├── README.md
├── next-env.d.ts
├── next.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
│   ├── js
│   │   └── script.js
│   ├── next.svg
│   └── vercel.svg
├── src
│   ├── app
│   │   ├── components
│   │   │   ├── component01
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── component02
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── component03
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── component04
│   │   │   │   ├── checkbox-demo.tsx
│   │   │   │   ├── page.module.scss
│   │   │   │   ├── page.tsx
│   │   │   │   ├── radio-demo.tsx
│   │   │   │   └── select-demo.tsx
│   │   │   ├── page.module.scss
│   │   │   └── page.tsx
│   │   ├── events
│   │   │   ├── event01
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── page.module.scss
│   │   │   └── page.tsx
│   │   ├── favicon.ico
│   │   ├── globals.css
│   │   ├── globals.scss
│   │   ├── hooks
│   │   │   ├── hook01
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── hook02
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── hook03
│   │   │   │   ├── child.tsx
│   │   │   │   ├── counter-provider.tsx
│   │   │   │   ├── grandchild.tsx
│   │   │   │   ├── myself.tsx
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── hook04
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── hook05
│   │   │   │   ├── child.tsx
│   │   │   │   ├── counter-provider.tsx
│   │   │   │   ├── grandchild.tsx
│   │   │   │   ├── myself.tsx
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── hook06
│   │   │   │   ├── child.tsx
│   │   │   │   ├── page.module.scss
│   │   │   │   ├── page.tsx
│   │   │   │   ├── play-provider.tsx
│   │   │   │   ├── text-box.tsx
│   │   │   │   └── video-player.tsx
│   │   │   ├── page.module.scss
│   │   │   └── page.tsx
│   │   ├── layout.module.scss
│   │   ├── layout.tsx
│   │   ├── nextjs
│   │   │   ├── nextjs01
│   │   │   │   ├── child
│   │   │   │   │   ├── client-page.tsx
│   │   │   │   │   ├── metadata.ts
│   │   │   │   │   └── page.tsx
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── nextjs02
│   │   │   │   ├── [...slug]
│   │   │   │   │   └── page.tsx
│   │   │   │   ├── page.module.scss
│   │   │   │   ├── page.tsx
│   │   │   │   └── shop
│   │   │   │       └── [id]
│   │   │   │           └── page.tsx
│   │   │   ├── nextjs03
│   │   │   │   ├── fetch-image.tsx
│   │   │   │   ├── get-image.tsx
│   │   │   │   ├── loading.tsx
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── nextjs04
│   │   │   │   ├── actions.ts
│   │   │   │   ├── list.tsx
│   │   │   │   ├── page.module.scss
│   │   │   │   ├── page.tsx
│   │   │   │   └── register-form.tsx
│   │   │   ├── nextjs05
│   │   │   │   ├── locale-switcher.tsx
│   │   │   │   ├── metadata.ts
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── page.module.scss
│   │   │   └── page.tsx
│   │   ├── page.module.scss
│   │   ├── page.tsx
│   │   └── redux
│   │       ├── page.module.scss
│   │       ├── page.tsx
│   │       ├── redux01
│   │       │   ├── child.tsx
│   │       │   ├── counter-slice.ts
│   │       │   ├── grandchild.tsx
│   │       │   ├── hooks.ts
│   │       │   ├── myself.tsx
│   │       │   ├── page.module.scss
│   │       │   ├── page.tsx
│   │       │   └── store.ts
│   │       └── redux02
│   │           ├── child.tsx
│   │           ├── hooks.ts
│   │           ├── image-box.tsx
│   │           ├── image-slice.ts
│   │           ├── page.module.scss
│   │           ├── page.tsx
│   │           ├── store.ts
│   │           ├── text-box.tsx
│   │           └── text-slice.ts
│   ├── lib
│   │   ├── common
│   │   │   ├── db
│   │   │   │   ├── pool-config.ts
│   │   │   │   └── pool.ts
│   │   │   ├── definitions.ts
│   │   │   ├── i18n
│   │   │   │   ├── dictionaries
│   │   │   │   │   ├── cn.json
│   │   │   │   │   ├── en.json
│   │   │   │   │   ├── ja.json
│   │   │   │   │   └── ko.json
│   │   │   │   ├── dictionaries.ts
│   │   │   │   └── i18n-config.ts
│   │   │   └── sidebar-links.tsx
│   │   ├── components
│   │   │   ├── alert-snackbar.tsx
│   │   │   ├── go-back.tsx
│   │   │   └── spacer.tsx
│   │   ├── footer.tsx
│   │   ├── header.tsx
│   │   ├── sidebar.tsx
│   │   └── utils
│   │       └── util.ts
│   └── scss
│       └── common
│           ├── _index.scss
│           ├── _mixin.scss
│           ├── _mq.scss
│           └── _variables.scss
├── tailwind.config.ts
└── tsconfig.json

40 directories, 126 files

9. 備考

今回はLocalization(i18n)の使い方についてでした。

10. 参考

投稿者プロフィール

Sondon
開発好きなシステムエンジニアです。
卓球にハマってます。

関連記事

  1. 【NextJS】UI Design using Stitch with…

  2. 【NextJS】NextJS・TypeScript・Apollo Cl…

  3. 【NextJS】Pankoにオンラインビンゴが追加されました

  4. 【NextJS】Firestore

  5. 【NextJS】Zoom in on an image during …

  6. 【NextJS】Gemini CLIを使ってシンプルプロジェクトを開発…

最近の記事

制作実績一覧

  1. Checkeys