23
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Next.js】個人サイトを多言語対応させてみた【多言語対応】【Next-intl】

Last updated at Posted at 2025-12-13

この記事は デジタル創作サークル UniProject Advent Calendar 2025 14 日目の記事です。
私の個人サイトは「できそうなことは全部やってみる」実験場なので、最近多言語対応を入れてみました。
(おかげで Google PageSpeed Insights のパフォーマンスの部分だけスコアが壊滅的になりましたが、まあ、気にしたら負けみたいなかんじで)

使うもの

  • Next.jsの適当なプロジェクト
  • Next-intl
  • next/hearders

完成品はこちらで公開してます
https://github.com/ayane0857/next-intl-sample-qiita

next-intl のインストール

Next-intlをインストールします

# npm
npm install next-intl
# bun
bun add next-intl

翻訳ファイルをつくる

/components/locales/ 配下に言語ごとの JSON を置きました。

/components/locales/ja.json
{
    "HomePage": {
        "Name": "彩音",
        "Message": "こんにちは、私のサイトへようこそ!"
    },
    "metadata": {
        "title": "テスト",
        "description": "テストサイト"
    }
}
/components/locales/en.json
{
    "HomePage": {
        "Name": "Ayane",
        "Message": "Hello and welcome to my site!"
    },
    "metadata": {
        "title": "Test",
        "description": "test site"
    }
}

ルーティング設定

/app/i18nrouting.ts
import { defineRouting } from "next-intl/routing";

export const routing = defineRouting({
  // サポートする言語一覧
  locales: ["ja", "en"],

  // デフォルトの言語
  defaultLocale: "ja",
});

i18nconfig.tsxでどの言語を使うかを決めたりして、returnで言語と翻訳内容を返します。

/app/i18nconfig.tsx
import { getRequestConfig } from "next-intl/server";
import { hasLocale } from "next-intl";
import { routing } from "./i18nrouting";

export default getRequestConfig(async ({ requestLocale }) =>
  const requested = await requestLocale;
  const locale = hasLocale(routing.locales, requested)
    ? requested
    : routing.defaultLocale;
    
  return {
    locale,
    messages: (await import(`@/components/locales/${locale}.json`)).default, //ここにフォルダーを
  };
});

next.config.tsにnext-intlのプラグインを適応させます。
設定ファイルはcreateNextIntlPluginの中に書いておく必要があります。
もし、設定ファイルを別の場所に記載する必要があった場合はそこを書きます。

/next.config.ts
import { NextConfig } from "next";
import createNextIntlPlugin from "next-intl/plugin";

const nextConfig: NextConfig = {};

const withNextIntl = createNextIntlPlugin("./app/i18nconfig.tsx"); //設定ファイルをここ
export default withNextIntl(nextConfig);

レイアウトの部分にプロバイダーを適応する

next-intlのプロバイダーを適応させます。

/app/layout.tsx
import {NextIntlClientProvider} from 'next-intl';
 
export default async function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html>
      <body>
        <NextIntlClientProvider>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}

メタデータもgenerateMetadataを使えば簡単に実装できます。
getTranslationsHomePageという名前空間全体の翻訳関数を取得を取得できます。
("metadata")を空にすることで名前空間を指定せずに、t("metadata.title")みたいな感じで取得することも可能です

export async function generateMetadata(): Promise<Metadata> {
  const locale = await getLocale();
  const t = await getTranslations("metadata");

  return {
    title: t("title"),
    description: t("description"),
  };
}

ページ側での使い方

クライアントコンポーネントでの使い方
先ほどのgetTranslationsの使い方と同じ使い方でuseTranslationsを使用できます

/app/page.tsx
import {useTranslations} from 'next-intl';

export default function Home() {
  const t = useTranslations('HomePage');
  return (
    <div>
       <h1>{t("Name")}</h1>
       <p>{t("Message")}</p>
    </div>
  );
}
サーバーコンポーネント

サーバーコンポーネントの場合はgetTranslationsを使う必要があります。

/app/page.tsx
import {getTranslations} from 'next-intl/server';
 
export default async function HomePage() {
  const t = await getTranslations('HomePage');
  return (
    <div>
       <h1>{t("Name")}</h1>
       <p>{t("Message")}</p>
    </div>
  );
}

このままだと言語検知できないので言語検知する

/[lang]/を使って、params経由で言語を検知してもいいのですが、それだと余計な処理がふえたりするので、HTTPのヘッダーから取得します。
ヘッダーにはAccept-Languageというユーザーのブラウザの言語が乗っている部分があるので、それを使わせていただきます。
英語の場合はen-USみたいな感じでenだけほしいのですが、ほかの部分もあるので、その余計な部分はsplitで消しておきます。
もしほしい場合はaccept_langを直で使えば大丈夫です

/app/i18nconfig.tsx
import { getRequestConfig } from "next-intl/server";
import { routing } from "./i18nrouting";
+ import { headers } from "next/headers";
- import { hasLocale } from "next-intl";

- export default getRequestConfig(async ({ requestLocale }) =>
+ export default getRequestConfig(async () => {
-  const requested = await requestLocale;
+  const headersList = await headers();
+  const accept_lang = headersList.get("Accept-Language") || "";
+  const browserLocale = accept_lang.split(",")[0]?.trim(); // e.g. "en-US"
  const locale = hasLocale(routing.locales, requested)
   ? requested
   : routing.defaultLocale;
  return {
    locale,
    messages: (await import(`@/components/locales/${locale}.json`)).default,
  };
});

HTMLのlang属性も自動で正しい言語に変える

htmlの言語もgetLocaleを使うことで変更できます。
getLocaleを使うと使用している言語が取得できます。

/app/layout.tsx
- import {NextIntlClientProvider} from 'next-intl';
+ import { getLocale } from "next-intl/server";
 
export default async function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
+ const locale = await getLocale();
  return (
+   <html lang={lang}>
      <body>
-       <NextIntlClientProvider>
          {children}
-       </NextIntlClientProvider>
      </body>
    </html>
  );

結果

ウェブでアクセスしてみたらわかりますが、デフォルトではデフォルトに設定した言語が、ほかにブラウザの言語を変更したら、対応している場合はその言語が、対応していない場合はほかの言語が表示されると思います。
ほかの言語検知方式などはi18nconfig.tsxをいじれば可能だと思います

フルコード
/components/locales/ja.json
{
    "HomePage": {
        "Name": "彩音",
        "Message": "こんにちは、私のサイトへようこそ!"
    }
}
/components/locales/en.json
{
    "HomePage": {
        "Name": "Ayane",
        "Message": "Hello and welcome to my site!"
    }
}
/app/layout.tsx
import { getLocale } from "next-intl/server";
import type { Metadata } from "next";
import "./globals.css";

export const metadata: Metadata = {
  title: "Next-intl sample",
  description: "A sample project for next-intl",
};

export default async function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  const locale = await getLocale();
  return (
    <html lang={locale}>
      <body>{children}</body>
    </html>
  );
}

/app/page.tsx
import {useTranslations} from 'next-intl';

export default function Home() {
  const t = useTranslations('HomePage');
  return (
    <div>
       <h1>{t("Name")}</h1>
       <p>{t("Message")}</p>
    </div>
  );
}
/app/i18nconfig.tsx
import { getRequestConfig } from "next-intl/server";
import { headers } from "next/headers";
import { routing } from "./i18nrouting";

export default getRequestConfig(async () => {
  const headersList = await headers();
  const accept_lang = headersList.get("Accept-Language") || "";
  const firstTag = accept_lang.split(",")[0]?.trim(); // e.g. "en-US"
  const browserLocale = firstTag.split(/[-;]/)[0]; // e.g. "en"
  const locale = routing.locales.includes(
    browserLocale as (typeof routing.locales)[number]
  )
    ? browserLocale
    : routing.defaultLocale;
  return {
    locale,
    messages: (await import(`@/components/locales/${locale}.json`)).default,
  };
});

/app/i18nrouting.ts
import { defineRouting } from "next-intl/routing";

export const routing = defineRouting({
  // サポートする言語一覧
  locales: ["ja", "en"],

  // デフォルトの言語
  defaultLocale: "ja",
});
/next.config.ts
import { NextConfig } from "next";
import createNextIntlPlugin from "next-intl/plugin";

const nextConfig: NextConfig = {};

const withNextIntl = createNextIntlPlugin("./app/i18nconfig.tsx"); //設定ファイルをここ
export default withNextIntl(nextConfig);

結果

調べたらnext-i18n純正を使う記事がでてきますが、こっちで対応させたほうが早いです。
ボタンを使って変更する方法などは各自自分で調べてみてください。
まあ、私のホームページ多言語対応させたところで、外国人利用者0に近しいとおもいますし、わざわざ公式翻訳しなくても、ブラウザ標準機能でうちは足りるところがあるので...
まあ、作った意味はわかりませんが、たのしかったのでOKです

23
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?