Zenn
💱

Next.jsのSSG(Static Exports)にて、自作コードで簡易的に多言語対応する方法

2025/04/12に公開

成果物

ソースコード

GitHub

デモ

デモページ
フッターから言語を選択すれば、URLやtitleが変更されることがわかると思います。

やりたいこと

  • 静的なページを出力しつつ、多言語対応したい。
  • SEOの都合上、言語ごとにページを作成し、メタタグを設定したい。
  • ドメインは統一し、サブディレクトリで言語を分けたい。

なかなか上手いこといくライブラリが無かったので自分で実装してみました。
今回は日本語と英語のみの実装となります。

環境

Next.js 15.3.0
React 19.0.0

実装

ディレクトリイメージ

[lang] ┬ page.tsx
   └ demo - page.tsx

分かりにくいかと思いますが、全てのページは[lang]以下に配置され、langのパラメータによってテキストが変更されるというイメージです。
言語ファイルはLanguage.tsxにまとめて書きます。

トップページ

トップページからは各言語のトップページからの遷移のみを行います。
つまり言語がjaならば、トップページは/ja/になります。

page.tsx
'use client';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';

export default function Home() {
  const router = useRouter();
  useEffect(() => {
    const language: string =
      window.navigator.language || window.navigator.language[0];
    if (language.includes('ja')) {
      router.push('/ja/');
    } else {
      router.push('/en/');
    }
  }, [router]);
  return <></>;
}

Language.tsx

言語ごとに文章をまとめたファイルです。
これはページごとに管理してもいいかもしれません。

Language.tsx
const langType = ['ja', 'en'];

type LangType = (typeof langType)[number];

type Meta = {
  title: string;
  description: string;
};
type I18nText = {
  home: Meta & {
    head: string;
    body: string;
    demo: string;
  };
  demo: Meta & {
    head: string;
    body: string;
    home: string;
  };
  footer: {
    footer: string;
  };
};

const i18nText: Record<LangType, I18nText> = {
  ja: {
    home: {
      title: 'サイトタイトル',
      description: 'サイト説明文。',
      head: 'トップページ',
      body: 'ここはトップページです。',
      demo: 'デモページ',
    },
    demo: {
      title: 'ページ',
      description: 'ページ説明文。',
      head: 'デモページ',
      body: 'ここはデモページです。',
      home: 'ホームページ',
    },
    footer: {
      footer: 'フッター',
    },
  },
  en: {
    home: {
      title: 'Site Title',
      description: 'Site description.',
      head: 'Home Page',
      body: 'Here is Home Page',
      demo: 'Demo Page',
    },
    demo: {
      title: 'Page',
      description: 'Page description.',
      head: 'Demo Page',
      body: 'Here is Demo Page',
      home: 'Home Page',
    },
    footer: {
      footer: 'footer',
    },
  },
};

export { i18nText, langType };
export type { LangType };

/[lang]/layout.tsx

メタタグはlayout.tsxで書きます。

layout.tsx
import { ReactNode } from 'react';
import type { Metadata } from 'next';
import { i18nText, LangType } from '@/components/Language';

type Params = {
  lang: LangType;
};

export async function generateMetadata({
  params,
}: {
  params: Promise<Params>;
}): Promise<Metadata> {
  const { lang } = await params;
  const t = i18nText[lang];

  const title = t.home.title;
  const description = t.home.description;

  return {
    title,
    description,
  };
}

export const dynamicParams = false;

export function generateStaticParams() {
  return [{ lang: 'ja' }, { lang: 'en' }];
}

export default function Layout({ children }: { children: ReactNode }) {
  return <>{children}</>;
}

/[lang]/page.tsx

languseParams()により取得します。

page.tsx
'use client';
import { useParams } from 'next/navigation';
import { i18nText, LangType } from '@/components/Language';
import Container from '@/components/Container';
import Link from 'next/link';

export default function Page() {
  const params = useParams<{ lang: LangType }>();
  const { lang } = params;
  const t = i18nText[lang];

  return (
    <Container lang={lang}>
      <div className='bg-white p-2'>
        <h1 className='text-center text-2xl'>{t.home.head}</h1>
        <p>{t.home.body}</p>
        <Link href={`/${lang}/demo/`} className='text-sky-600'>
          {t.home.demo}
        </Link>
      </div>
    </Container>
  );
}

まとめ

小規模のサイトやwebアプリでは使えるのではないでしょうか。

Discussion

ログインするとコメントできます