Menu

国際化

Nexty.devはnext-intlを活用して包括的な多言語サポートを提供し、ルートレベルの言語切り替え、自動言語検出、シームレスなロケール管理を含む堅牢な国際化ソリューションを実現しています。

このガイドでは、next-intlの基本的な使い方を説明し、新しい言語と翻訳コンテンツを追加する方法を実演し、Nextyが提供する高度な国際化機能について探ります。

ファイル構造

i18n/
├── messages/                # 翻訳ファイルディレクトリ
│   ├── en/                  # 英語翻訳
│   │   └── common.json
│   ├── zh/                  # 中国語翻訳
│   │   └── common.json
│   └── ja/                  # 日本語翻訳
│       └── common.json
├── request.ts               # next-intlリクエスト設定
└── routing.ts               # ルーティングと言語設定
 
components/
├── LocaleSwitcher.tsx       # 言語切り替えコンポーネント
└── LanguageDetectionAlert.tsx  # 言語検出アラートコンポーネント
 
stores/
└── localeStore.ts           # 言語状態管理
 
middleware.ts                # ミドルウェア設定
next.config.mjs              # Next.js設定

基本的な使い方

コンポーネントでの翻訳の使用

Reactコンポーネントでは、useLocaleフックを使用して現在のロケールを取得し、useTranslationsフックを使用して翻訳コンテンツにアクセスします。

import { useLocale, useTranslations } from 'next-intl';
 
export default function MyComponent() {
  const locale = useLocale();
  const t = useTranslations('Home');
  
  return (
    <div>
      <h1>{t('title')}</h1>
      <p>{t('description')}</p>
    </div>
  );
}

注目すべき点

APP Routerモードでは、useLocaleuseTranslationsはクライアントコンポーネントとサーバーコンポーネントの両方で動作します。サーバーコンポーネントの場合、コンポーネント定義でasyncキーワードを使用しないようにするだけで、サーバーサイドレンダリングを維持しながらこれらのフックが正常に機能します。

サーバーコンポーネントでの使用

asyncサーバーコンポーネントの場合、getLocaleを使用して現在のロケールを取得し、getTranslationsを使用して翻訳コンテンツにアクセスします。

import { getLocale, getTranslations } from 'next-intl/server';
 
export default async function ServerComponent() {
  const locale = await getLocale();
  const t = await getTranslations('Home');
  
  return (
    <div>
      <h1>{t('title')}</h1>
    </div>
  );
}

パラメータ化された翻訳の使用

JSONファイル内:

{
  "welcome": "Welcome, {name}!",
  "countdown": "Closing in {countdown} seconds"
}

コンポーネント内:

const message = t('welcome', { name: 'John' });
const timer = t('countdown', { countdown: countdown.toString() });

生データの使用

配列やオブジェクトなどの複雑なデータ構造の場合、t.raw()メソッドを使用して生の翻訳データにアクセスします:

const headerLinks = t.raw('Header.links') as HeaderLink[];
const features = t.raw('Landing.Features.items');

国際化Linkコンポーネント

@/i18n/routingから公開されているLinkコンポーネントを使用します。hrefプロパティには言語プレフィックスは不要で、next-intlが現在の言語に基づいてロケールルーティングを自動的に処理します。

import { Link as I18nLink } from '@/i18n/routing';
 
<I18nLink href="/about">
  {t('aboutUs')}
</I18nLink>

国際化useRouter

プログラムによるナビゲーションには@/i18n/routinguseRouterフックを使用します。Linkコンポーネントと同様に、ルートには言語プレフィックスは不要で、next-intlがロケールルーティングを自動的に処理します。

import { useRouter } from '@/i18n/routing';
 
const router = useRouter();
 
const handleClick = () => {
  router.push('/dashboard');
};

新しい言語を追加する手順

ステップ1:言語設定の更新

i18n/routing.tsのロケール設定に新しい言語を追加します:

i18n/routing
export const LOCALES = ['en', 'zh', 'ja', 'ko'] // 韓国語を追加
export const DEFAULT_LOCALE = 'en'
export const LOCALE_NAMES: Record<string, string> = {
  'en': "English",
  'zh': "中文", 
  'ja': "日本語",
  'ko': "한국어", // 韓国語の表示名を追加
};
 
export const LOCALE_TO_HREFLANG: Record<string, string> = {
  'en': 'en-US',
  'zh': 'zh-CN',
  'ja': 'ja-JP',
  'ko': 'ko-KR', // 韓国語のhreflangマッピングを追加
};

ステップ2:翻訳ファイルの作成

言語用の新しいディレクトリを作成し、既存の翻訳ファイルをテンプレートとしてコピーします:

mkdir i18n/messages/ko
cp i18n/messages/en/common.json i18n/messages/ko/common.json

ステップ3:コンテンツの翻訳

新しい言語ファイル内のすべてのコンテンツを翻訳します。i18n/messages/ko/common.jsonを編集し、英語のコンテンツを韓国語の翻訳に置き換えます。

ステップ4:ミドルウェア設定の更新

新しいロケールを含むようにミドルウェアマッチャーを更新します:

middleware.ts
export const config = {
  matcher: [
    '/',
 
    '/(en|zh|ja|ko)/:path*', // 韓国語を追加
 
    '/((?!api|_next|_vercel|auth|.*\\.|favicon.ico).*)'
  ]
};

ステップ5:ルートリダイレクトの更新(オプション)

必要に応じて、next.config.mjsに対応するリダイレクトルールを追加します:

next.config.mjs
{
  source: "/ko/dashboard", 
  destination: "/ko/dashboard/settings",
  permanent: true,
}

ステップ6:新しい言語用のOG画像の追加

public/ディレクトリにog_ko.pngを追加します。

OG画像の作成と使用に関する情報については、以下のドキュメントをお読みください:

注目すべき点

言語を削除するには、これらの手順を逆に実行します:設定からロケールを削除し、翻訳ファイルを削除し、ミドルウェアマッチャーを更新します。

新しい翻訳名前空間の追加

新しいページや機能を追加する際は、翻訳を論理的な名前空間に整理するために、別のJSONファイルを作成することをお勧めします。

例えば、既存のcommon.jsonと並んで、ランディングページの翻訳用に新しいLanding.jsonを追加できます:

i18n/messages/en/
├── common.json
├── Landing.json         # 新しいLandingの追加

リクエスト設定の更新:

i18n/request.ts
import { getRequestConfig } from 'next-intl/server';
import { routing } from './routing';
 
export default getRequestConfig(async ({ requestLocale }) => {
  let locale = await requestLocale;
 
  if (!locale || !routing.locales.includes(locale as any)) {
    locale = routing.defaultLocale;
  }
 
  const common = (await import(`./messages/${locale}/common.json`)).default;
  const Landing = (await import(`./messages/${locale}/Landing.json`)).default; // Landingをインポート
 
  return {
    locale,
    messages: {
      ...common,
      Landing, // Landingをインポート
    }
  };
});

新しい名前空間の使用

const t = useTranslations('Landing');

SSG(静的サイト生成)のベストプラクティス

注目すべき点

  • v2.3.0以前は、レイアウト内の動的メソッドが真のSSG実装を妨げていました。ページは依然としてサーバーサイドレンダリング(SSR)されており、SEOの利点は維持されていましたが、静的生成によるパフォーマンス向上は得られませんでした。
  • v2.3.0以降については、以下の実装アプローチに従ってSSGを実現してください。

ブログ詳細ページ、プライバシーポリシー、利用規約ページなどの静的コンテンツページには、SSGを使用してサーバーCPU消費を削減し、ページ読み込みパフォーマンスを向上させることをお勧めします。

シナリオ1:localeのみが動的パラメータのページ

適用ページ/privacy-policy/zh/privacy-policyのようなURL。動的パラメータはロケールプレフィックスのみです。

実装アプローチ

  1. paramsからlocaleパラメータを抽出(SSG互換性のためにgetLocale()を使用しない)
  2. generateStaticParamsを実装して、サポートされているすべてのロケールのページを事前生成
app/[locale]/privacy-policy/page.tsx
export default async function Page({ params }: { params: Params }) {
  // ✅ SSG互換性のためにparamsからlocaleを抽出
  // ❌ getLocale()を使用しない(静的生成を妨げる)
  const { locale } = await params;
 
  // ... その他のコード ...
 
  return (
    // ... ページコンテンツ ...
  );
}
 
export async function generateStaticParams() {
  return LOCALES.map((locale) => ({
    locale,
  }));
}

シナリオ2:マルチレベルの動的ルートを持つページ

適用ページ/blogs/nexty-dev-stand-out/zh/blogs/nexty-dev-stand-outのようなURL。動的パラメータには言語プレフィックスとコンテンツ識別子(ブログスラッグなど)の両方が含まれます。

実装アプローチ:同じ原則に従いますが、generateStaticParamsはより複雑なパラメータの組み合わせを処理する必要があります。

app/[locale]/blogs/[slug]/page.tsx
export default async function BlogPage({ params }: { params: Params }) {
  const { locale, slug } = await params;
  
  // ... その他のコード ...
 
  return (
    // ... ページコンテンツ ...
  );
}
 
export async function generateStaticParams() {
  // localeとslugのすべての可能な組み合わせを生成
  const allParams: { locale: string; slug: string }[] = [];
 
  for (const locale of LOCALES) {
    // データソースから現在のロケールのすべてのブログスラッグを取得
    const blogSlugs = await getBlogSlugs(locale); // データ取得ロジック
    
    for (const slug of blogSlugs) {
      allParams.push({ locale, slug });
    }
  }
 
  return allParams;
}

SSG実装の検証

npm run buildを実行した後、.next/server/app/enディレクトリに静的HTMLファイルが生成されていることを確認します:

ssg result

.htmlファイルの存在により、ページがビルド時に静的に生成されていることが確認できます。

詳細情報:追加の詳細については、動的ルートに関するNext.jsドキュメントをご確認ください。

Nextyの高度な機能

自動言語検出とユーザープロンプト

システムは、ユーザーの初回訪問時にブラウザの言語を自動的に検出します。検出された言語が現在のウェブサイトの言語と異なる場合、以下の動作を伴う言語切り替えプロンプトが表示されます:

  • 自動非表示:10秒後に自動的に閉じます
  • 手動制御:ユーザーは手動でプロンプトを閉じることができます
  • 永続的な設定:非表示状態はCookieを使用して30日間保存されます

SEO最適化

各言語バージョンは独立したURLとローカライズされたメタデータを生成し、最適な検索エンジンインデックスを確保します:

import { constructMetadata } from "@/lib/metadata";
 
export async function generateMetadata({ params }: MetadataProps): Promise<Metadata> {
  const { locale } = await params;
  const t = await getTranslations({ locale, namespace: "Home" });
 
  return constructMetadata({
    page: "Home",
    title: t("title"),
    description: t("description"),
    locale: locale as Locale,
    path: `/`,
  });
}