Next.js: 国際化 & ミドルウェア

最終更新:2026-08-26

国際化は単なる翻訳ではありません。ルーティング、ロケール検出、RTL レイアウトを含む体系的なエンジニアリングプロセスです。

1. 学習目標



2. 国際プロダクトマネージャーの実話

(1) 課題: 中東のユーザーから 47 件のネガティブレビュー

Alice の TaskFlow がローンチされた後、サウジアラビアとアラブ首長国連邦のユーザーから多数の苦情が寄せられました:

「ダッシュボードはまだ英語で、日付形式 07/06/2026 は彼らにとって意味がなく(サウジアラビアでは 06/07/2026 を使用)、数字 1,234.56 はアラブ諸国では正しく表示されません。最悪なのは、ページが右から左にスクロールし、レイアウト全体が反転していることです。」

問題 ユーザーフィードバック 影響を受けたユーザー数
アラビア語に翻訳されていない 「メニューが理解できない」 47 件のネガティブレビュー
日付形式が間違っている 「日付が逆になっている」 32 件の苦情
レイアウトが RTL に対応していない 「ボタンが右側にある」 28 件の苦情
通貨記号のエラー 「価格が USD で表示されている」 15 件の苦情

(2) next-intl + ミドルウェアの解決策

next-intl で国際化フレームワークを提供し、ミドルウェアがブラウザの言語に基づいて自動的に切り替えます。

TS
// middleware.ts — 国際化ルーティングのコア
import createMiddleware from 'next-intl/middleware'

export default createMiddleware({
  locales: ['zh', 'en', 'ja', 'ar'],
  defaultLocale: 'zh',
  localePrefix: 'always'       // URL には常に地域プレフィックスを含める
})

export const config = {
  matcher: ['/((?!_next|api|favicon.ico).*)']
}

(3) 効果

観点 再設計前 再設計後
対応言語 中国語のみ 中国語、英語、日本語、アラビア語の 4 言語
言語検出 手動選択 ブラウザ言語の自動検出
RTL サポート ❌ 非対応 ✅ 完全ミラーレイアウト
日付/数値 中国語形式をハードコード Intl API で自動フォーマット
サウジアラビアユーザー NPS 32 78


3. next-intl のインストールとプロジェクト構造

(1) インストールとディレクトリ構造

BASH
npm install next-intl
100%
graph TB
    A[next-intl] --> B[i18n/request.ts<br/>サーバー設定]
    A --> C[messages/<br/>翻訳辞書]
    C --> D[en.json<br/>英語]
    C --> E[zh.json<br/>中国語]
    C --> F[ja.json<br/>日本語]
    C --> G[ar.json<br/>アラビア語]
    A --> H[middleware.ts<br/>言語検出 + 書き換え]
    A --> I[app/[locale]/<br/>動的ルーティングセグメント]

    style A fill:#cce5ff
    style H fill:#d4edda
    style I fill:#fff3cd
ファイル 目的
i18n/request.ts サーバーサイド i18n 設定のエントリポイント
messages/en.json 英語辞書
messages/ar.json アラビア語辞書(RTL マークアップ付き)
middleware.ts 自動言語検出 + URL 書き換え
app/[locale]/ 動的地域ルーティングセグメント

(2) 初期設定

TS
// i18n/request.ts — next-intl サーバー設定
import { getRequestConfig } from 'next-intl/server'
import { hasLocale } from 'next-intl'
import { routing } from './routing'

export default getRequestConfig(async ({ requestLocale }) => {
  const requested = await requestLocale
  const locale = hasLocale(routing.locales, requested) ? requested : routing.defaultLocale

  return {
    locale,
    messages: (await import(`../messages/${locale}.json`)).default
  }
})
TS
// i18n/routing.ts — 共有ルーター設定
import { defineRouting } from 'next-intl/routing'

export const routing = defineRouting({
  locales: ['zh', 'en', 'ja', 'ar'],
  defaultLocale: 'zh',
  localeDetection: true,
  localePrefix: 'as-needed'    // デフォルト言語のプレフィックスを省略
})

▶ サンプル: 完全なミドルウェア設定

Output:

TEXT 📖 参照専用
Exports: routing.
TS
// middleware.ts — 言語検出 + Cookie 永続化
import createMiddleware from 'next-intl/middleware'
import { NextRequest, NextResponse } from 'next/server'

const intlMiddleware = createMiddleware({
  locales: ['zh', 'en', 'ja', 'ar'],
  defaultLocale: 'zh',
  localeDetection: true,
  localePrefix: 'always'
})

export default function middleware(req: NextRequest) {
  const { pathname } = req.nextUrl

  // API / _next は処理しない
  if (pathname.startsWith('/api') || pathname.startsWith('/_next') || pathname === '/favicon.ico') {
    return NextResponse.next()
  }

  return intlMiddleware(req)
}

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)']
}

Output:

TEXT 📖 参照専用
Middleware processes requests before they reach route handlers.


4. ミドルウェアルーティングポリシー

(1) 3 つのルーティングモード

100%
graph TB
    A[ユーザーアクセス] --> B{ミドルウェア検証}
    B --> C[Cookie を検査<br/>next-intl-locale]
    B --> D[Accept-Language を検査<br/>HTTP ヘッダー]
    B --> E[デフォルト言語]

    C --> F[選択済み → 使用]
    D --> G[初回訪問 → ブラウザ言語を検出]
    E --> H[一致なし → デフォルト言語]
    F --> I[URL 書き換え /{locale}/...]
    G --> I
    H --> I

    I --> J[Cookie を書き込み<br/>永続化]

    style B fill:#fff3cd
    style I fill:#d4edda
    style J fill:#cce5ff
ルーティングモード URL 形式 localePrefix 適用シナリオ
常にプレフィックス /en/about / /ar/about 'always' 全言語を均等に表示
オンデマンドプレフィックス /about (デフォルト) / /en/about 'as-needed' デフォルト言語の SEO フレンドリー
プレフィックスなし /about 'never' Cookie 駆動(非推奨)
TS
// middleware.ts — 検出ロジックの手動制御
import { NextRequest, NextResponse } from 'next/server'
import { routing } from './i18n/routing'

const COOKIE_NAME = 'NEXT_LOCALE'

export default function middleware(req: NextRequest) {
  const { pathname } = req.nextUrl
  const cookieLocale = req.cookies.get(COOKIE_NAME)?.value
  const acceptLanguage = req.headers.get('Accept-Language')?.split(',')[0]?.split('-')[0]
  const browserLocale = acceptLanguage && routing.locales.includes(acceptLanguage as any) ? acceptLanguage : null

  // 言語のマッチング
  const matchedLocale = cookieLocale || browserLocale || routing.defaultLocale

  // パスに既に言語プレフィックスがある
  const pathLocale = routing.locales.find((l) => pathname.startsWith(`/${l}/`) || pathname === `/${l}`)
  if (pathLocale) return NextResponse.next()

  // URL を書き換えて Cookie を設定
  const url = new URL(`/${matchedLocale}${pathname}`, req.url)
  const res = NextResponse.rewrite(url)
  res.cookies.set(COOKIE_NAME, matchedLocale, { maxAge: 60 * 60 * 24 * 365 })
  return res
}

export const config = { matcher: ['/((?!api|_next|favicon.ico).*)'] }
💡 ヒント: Cookie 永続化により、ユーザーの言語選択がページ間の移動時にも保持されます。ユーザーが手動で言語を切り替えた場合は、Cookie の値を更新します。

▶ サンプル: 言語切り替えコンポーネント

Output:

TEXT 📖 参照専用
Renders the middleware component UI.
TSX
// components/LocaleSwitcher.tsx
'use client'
import { useLocale } from 'next-intl'
import { usePathname, useRouter } from '@/i18n/routing'
import { useTransition } from 'react'

const locales = [
  { code: 'zh', label: '中国語' },
  { code: 'en', label: 'English' },
  { code: 'ja', label: '日本語' },
  { code: 'ar', label: 'العربية' }
]

export default function LocaleSwitcher() {
  const locale = useLocale()
  const router = useRouter()
  const pathname = usePathname()
  const [isPending, startTransition] = useTransition()

  const switchLocale = (nextLocale: string) => {
    startTransition(() => {
      router.replace(pathname, { locale: nextLocale })
    })
  }

  return (
    <select
      value={locale}
      onChange={(e) => switchLocale(e.target.value)}
      disabled={isPending}
      style={{ padding: '0.25rem 0.5rem' }}
    >
      {locales.map((l) => (
        <option key={l.code} value={l.code}>{l.label}</option>
      ))}
    </select>
  )
}

Output:

TEXT 📖 参照専用
Renders the ▶ サンプル: 言語切り替えコンポーネント component UI as described in the section.


5. 多言語辞書管理

(1) 辞書ファイル構造

JSON
// messages/zh.json
{
  "nav": {
    "home": "ホーム",
    "dashboard": "ダッシュボード",
    "projects": "プロジェクト",
    "settings": "設定",
    "signIn": "ログイン",
    "signOut": "終了"
  },
  "home": {
    "title": "TaskFlow - チームコラボレーションプラットフォーム",
    "subtitle": "10,000 以上のチームの効率的なコラボレーションを支援します",
    "cta": "無料で始める"
  },
  "dashboard": {
    "welcome": "おかえりなさい、{name}",
    "stats": {
      "members": "{count} 名のメンバー",
      "projects": "{count} プロジェクト",
      "tasks": "{count} タスク"
    },
    "activity": "最近のアクティビティ"
  },
  "common": {
    "loading": "読み込み中...",
    "error": "エラーが発生しました",
    "save": "保存",
    "cancel": "キャンセル",
    "delete": "削除",
    "confirm": "確認"
  }
}
JSON
// messages/ar.json
{
  "nav": {
    "home": "الرئيسية",
    "dashboard": "لوحة التحكم",
    "projects": "المشاريع",
    "settings": "الإعدادات",
    "signIn": "تسجيل الدخول",
    "signOut": "تسجيل الخروج"
  },
  "home": {
    "title": "TaskFlow - منصة التعاون الجماعي",
    "subtitle": "نساعد أكثر من 10,000 فريق على العمل بكفاءة",
    "cta": "ابدأ مجاناً"
  },
  "dashboard": {
    "welcome": "مرحباً بعودتك، {name}",
    "stats": {
      "members": "{count} أعضاء",
      "projects": "{count} مشروعاً",
      "tasks": "{count} مهمة"
    },
    "activity": "النشاطات الأخيرة"
  },
  "common": {
    "loading": "جارٍ التحميل...",
    "error": "حدث خطأ",
    "save": "حفظ",
    "cancel": "إلغاء",
    "delete": "حذف",
    "confirm": "تأكيد"
  }
}

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

TSX
// サーバーコンポーネント: t() 関数を使用
import { getTranslations } from 'next-intl/server'

export default async function HomePage() {
  const t = await getTranslations('home')

  return (
    <div>
      <h1>{t('title')}</h1>
      <p>{t('subtitle')}</p>
      <button>{t('cta')}</button>
    </div>
  )
}
TSX
// クライアントコンポーネント: useTranslations フック
'use client'
import { useTranslations } from 'next-intl'

export default function DashboardHeader({ name }: { name: string }) {
  const t = useTranslations('dashboard')

  return (
    <h1>{t('welcome', { name })}</h1>
  )
}
💻 出力 (中国語): おかえりなさい、Alice 💻 出力 (アラビア語): مرحباً بعودتك، Alice

▶ サンプル: 複数形を含む翻訳

TSX
// 辞書
{
  "tasks": {
    "remaining": "{count, plural, =0 {タスクなし} one {# 件のタスク} other {# 件のタスク}}"
  }
}
TSX
import { useTranslations } from 'next-intl'

export default function TaskCount({ count }: { count: number }) {
  const t = useTranslations('tasks')

  return <span>{t('remaining', { count })}</span>
}

Output:

TEXT 📖 参照専用
Renders the TaskCount component UI.
💻 出力 (count=0): タスクなし 💻 出力 (count=1): 1 件のタスク 💻 出力 (count=5): 5 件のタスク

💡 ヒント: next-intl は ICU MessageFormat 構文を使用し、複数形、選択肢、数値フォーマットなどの高度な機能をサポートしています。



6. generateStaticParams: 多言語ページの事前生成

(1) 静的事前生成の原理

100%
graph LR
    A[generateStaticParams] --> B[ロケールリストを返却]
    B --> C[zh: /zh/blog/post-1]
    B --> D[en: /en/blog/post-1]
    B --> E[ja: /ja/blog/post-1]
    B --> F[ar: /ar/blog/post-1]
    C --> G[ビルド時に生成: 4 つの HTML ファイル]
    D --> G
    E --> G
    F --> G

    style A fill:#cce5ff
    style G fill:#d4edda
言語 HTML ファイル パス
中国語 out/zh/blog/post-1.html /zh/blog/post-1
英語 out/en/blog/post-1.html /en/blog/post-1
日本語 out/ja/blog/post-1.html /ja/blog/post-1
アラビア語 out/ar/blog/post-1.html /ar/blog/post-1

(2) ルートレイアウトの "locale" パラメータ

TSX
// app/[locale]/layout.tsx — ルートレイアウトが locale パラメータを受け取る
import { NextIntlClientProvider } from 'next-intl'
import { getMessages, getTranslations } from 'next-intl/server'
import { notFound } from 'next/navigation'
import { routing } from '@/i18n/routing'

type Props = { children: React.ReactNode; params: Promise<{ locale: string }> }

export default async function LocaleLayout({ children, params }: Props) {
  const { locale } = await params

  // locale が有効か検証
  if (!routing.locales.includes(locale as any)) notFound()

  const messages = await getMessages()

  return (
    <html lang={locale} dir={locale === 'ar' ? 'rtl' : 'ltr'}>
      <body>
        <NextIntlClientProvider messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  )
}

▶ サンプル: 多言語ブログ記事の静的生成

Output:

TEXT 📖 参照専用
Renders the LocaleLayout component UI.
TSX
// app/[locale]/blog/[slug]/page.tsx — 多言語 SSG
import { getTranslations } from 'next-intl/server'
import { routing } from '@/i18n/routing'

type Props = { params: Promise<{ locale: string; slug: string }> }

// 全言語の記事ページを事前生成
export async function generateStaticParams() {
  const posts = await fetch('https://api.taskflow.io/blog/posts').then(r => r.json())

  return routing.locales.flatMap((locale) =>
    posts.map((post: { slug: string }) => ({ locale, slug: post.slug }))
  )
}

export async function generateMetadata({ params }: Props) {
  const { locale, slug } = await params
  const post = await fetch(`https://api.taskflow.io/blog/${slug}`).then(r => r.json())

  return {
    title: locale === 'en' ? post.title : post.titleLocalized[locale],
    alternates: {
      languages: Object.fromEntries(
        routing.locales.map((l) => [l, `https://taskflow.io/${l}/blog/${slug}`])
      )
    }
  }
}

export default async function BlogPostPage({ params }: Props) {
  const { locale, slug } = await params
  const t = await getTranslations('blog')

  return (
    <article>
      <h1>{t('title')}</h1>
      <p>Locale: {locale} | Slug: {slug}</p>
    </article>
  )
}

Output:

TEXT 📖 参照専用
Renders: Page with "{t('title')}" heading and associated content.


7. RTL レイアウトサポート

(1) RTL と LTR の違い

100%
graph TB
    subgraph LTR レイアウト
        A1[ロゴ<br/>左揃え] --> A2[ナビゲーションバー<br/>左から右]
        A2 --> A3[サイドバー<br/>左揃え]
        A3 --> A4[コンテンツ<br/>右側に配置]
    end
    subgraph RTL レイアウト
        B1[ロゴ<br/>右揃え] --> B2[ナビゲーションバー<br/>右から左]
        B2 --> B3[サイドバー<br/>右揃え]
        B3 --> B4[コンテンツ<br/>左側に配置]
    end

    style B1 fill:#d4edda
    style B2 fill:#d4edda
    style B3 fill:#d4edda
観点 LTR (中国語/英語/日本語) RTL (アラビア語)
テキスト方向 左から右 右から左
ナビゲーションバー ロゴ左 → メニュー右 ロゴ右 → メニュー左
サイドバー
矢印アイコン 進む 進む
カードマージン margin-left: auto margin-right: auto
フォーム送信ボタン 右下 左下

(2) Tailwind CSS RTL サポート

TSX
// app/[locale]/layout.tsx — RTL 検出と CSS クラス名
export default async function LocaleLayout({ children, params }: {
  children: React.ReactNode
  params: Promise<{ locale: string }>
}) {
  const { locale } = await params
  const isRtl = locale === 'ar'

  return (
    <html lang={locale} dir={isRtl ? 'rtl' : 'ltr'}>
      <body className={isRtl ? 'rtl' : 'ltr'}>
        {children}
      </body>
    </html>
  )
}
CSS
/* app/globals.css — RTL 基本スタイル */
.ltr { direction: ltr; }
.rtl { direction: rtl; }

/* ナビゲーションバーのミラーリング */
.rtl .nav-items { flex-direction: row-reverse; }
.rtl .sidebar { right: 0; left: auto; }
.rtl .main-content { margin-right: 260px; margin-left: 0; }

/* アイコンのミラーリング */
.rtl .icon-arrow::before { content: '←'; }
.ltr .icon-arrow::before { content: '→'; }

/* スペーシングの反転 */
.rtl .ml-auto { margin-right: auto; margin-left: 0; }
.rtl .mr-2 { margin-left: 0.5rem; margin-right: 0; }

▶ サンプル: RTL 最適化ダッシュボードレイアウト

Output:

TEXT 📖 参照専用
CSS styles applied for: .nav-items, .mr-2, .sidebar, .rtl, before, .main-content
TSX
// components/DashboardLayout.tsx — RTL 互換レイアウト
export default function DashboardLayout({
  sidebar,
  children,
  locale
}: {
  sidebar: React.ReactNode
  children: React.ReactNode
  locale: string
}) {
  const isRtl = locale === 'ar'

  return (
    <div style={{
      display: 'flex',
      flexDirection: isRtl ? 'row-reverse' : 'row',
      minHeight: '100vh'
    }}>
      {/* サイドバー: LTR では左、RTL では右 */}
      <aside style={{
        width: 260,
        borderRight: isRtl ? 'none' : '1px solid #e5e7eb',
        borderLeft: isRtl ? '1px solid #e5e7eb' : 'none'
      }}>
        {sidebar}
      </aside>

      {/* メインコンテンツ領域 */}
      <main style={{
        flex: 1,
        padding: '2rem',
        textAlign: isRtl ? 'right' : 'left'
      }}>
        {children}
      </main>
    </div>
  )
}

Output:

TEXT 📖 参照専用
Renders: Dashboard layout with dark sidebar (Dashboard/Projects/Settings links) + main content area.
⚠️ 注意: RTL は単なる direction: rtl だけではありません。すべての flex 方向、マージン/パディング、アイコン矢印、テキスト配置もミラーリングする必要があります。



8. 完全なサンプル: 4 言語対応公式サイト + RTL ダッシュボード

TSX
// app/[locale]/layout.tsx — 国際化ルートレイアウト
import { NextIntlClientProvider } from 'next-intl'
import { getMessages } from 'next-intl/server'
import { routing } from '@/i18n/routing'
import { notFound } from 'next/navigation'
import LocaleSwitcher from '@/components/LocaleSwitcher'
import type { Metadata } from 'next'

type Props = { children: React.ReactNode; params: Promise<{ locale: string }> }

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { locale } = await params
  return {
    title: 'TaskFlow',
    alternates: {
      canonical: `https://taskflow.io/${locale}`,
      languages: Object.fromEntries(
        routing.locales.map((l) => [l, `https://taskflow.io/${l}`])
      )
    }
  }
}

export default async function LocaleLayout({ children, params }: Props) {
  const { locale } = await params

  if (!routing.locales.includes(locale as any)) notFound()

  const messages = await getMessages()
  const isRtl = locale === 'ar'

  return (
    <html lang={locale} dir={isRtl ? 'rtl' : 'ltr'}>
      <head>
        {/* RTL CSS の読み込み */}
        {isRtl && <link rel="stylesheet" href="/rtl.css" />}
      </head>
      <body style={{ margin: 0, fontFamily: 'system-ui, sans-serif' }}>
        <NextIntlClientProvider messages={messages}>
          <nav style={{
            display: 'flex',
            justifyContent: 'space-between',
            alignItems: 'center',
            padding: '1rem 2rem',
            borderBottom: '1px solid #e5e7eb',
            flexDirection: isRtl ? 'row-reverse' : 'row'
          }}>
            <strong>TaskFlow</strong>
            <LocaleSwitcher />
          </nav>
          <main style={{ padding: '2rem' }}>{children}</main>
        </NextIntlClientProvider>
      </body>
    </html>
  )
}
TSX
// app/[locale]/dashboard/page.tsx — RTL ダッシュボード
import { getTranslations } from 'next-intl/server'

type Props = { params: Promise<{ locale: string }> }

export default async function DashboardPage({ params }: Props) {
  const { locale } = await params
  const t = await getTranslations('dashboard')
  const isRtl = locale === 'ar'

  return (
    <div>
      <h1 style={{ textAlign: isRtl ? 'right' : 'left' }}>{t('welcome', { name: 'Alice' })}</h1>

      <div style={{
        display: 'flex',
        gap: '1rem',
        flexDirection: isRtl ? 'row-reverse' : 'row'
      }}>
        <div style={{ flex: 1, padding: '1.5rem', background: '#f0f9ff', borderRadius: 8 }}>
          <h3>{t('stats.members', { count: 12 })}</h3>
        </div>
        <div style={{ flex: 1, padding: '1.5rem', background: '#fef3c7', borderRadius: 8 }}>
          <h3>{t('stats.projects', { count: 45 })}</h3>
        </div>
        <div style={{ flex: 1, padding: '1.5rem', background: '#ecfdf5', borderRadius: 8 }}>
          <h3>{t('stats.tasks', { count: 230 })}</h3>
        </div>
      </div>

      <h2 style={{ textAlign: isRtl ? 'right' : 'left' }}>{t('activity')}</h2>
      <div style={{
        padding: '1rem',
        border: '1px solid #e5e7eb',
        borderRadius: 8,
        textAlign: isRtl ? 'right' : 'left'
      }}>
        <p>Alice がプロジェクト「ウェブサイトリデザイン」を作成しました</p>
        <p>Bob がタスク #47 を完了しました</p>
        <p>Charlie が組織に参加しました</p>
      </div>
    </div>
  )
}
TEXT 📖 参照専用
# 出力結果 (中国語):
TaskFlow
[言語を選択: 中国語 ▼]

おかえりなさい、Alice
[12 名のメンバー] [45 プロジェクト] [230 タスク]

最近のアクティビティ
Alice がプロジェクト「ウェブサイトリデザイン」を作成しました
Bob がタスク #47 を完了しました
Charlie が組織に参加しました
TEXT 📖 参照専用
# 出力結果 (アラビア語):
TaskFlow
[اختيار اللغة: العربية ▼]

مرحباً بعودتك، Alice
[12 أعضاء] [45 مشروعاً] [230 مهمة]

النشاطات الأخيرة
أليس قامت بإنشاء مشروع "إعادة تصميم الموقع"
بوب أكمل المهمة رقم 47
تشارلي انضم إلى المنظمة

❓ よくある質問

Q next-intlnext-international のどちらを選ぶべきですか?
A next-intl はより大きなコミュニティ(5k⭐)とより多くの機能(複数形、選択肢、数値フォーマット、RTL 検出)を持っています。next-international はより軽量です。このチュートリアルでは next-intl を使用します。
Q ミドルウェアでリダイレクトではなく URL 書き換えを使用するのはなぜですか?
A rewrite はブラウザのアドレスバーの内容を変更せずに内部的に /{locale}/path にマッピングするため、SEO とユーザー体験に優れています。redirect は URL を変更するため、ユーザーが共有したリンクが壊れる可能性があります。
Q アラビア語の RTL レイアウトに必要な追加作業は何ですか?
A direction: rtl に加えて、flex 方向のミラーリング(flex-direction: row-reverse)、マージンとパディングの方向調整、矢印アイコンの反転、left/right の代わりに text-align: start/end を使用する必要があります。globals.css[dir="rtl"] セレクタを使用して統一的に処理することをお勧めします。
Q generateStaticParams は各言語に個別の HTML ファイルを生成しますか?
A はい。例えば /zh/blog/post-1/en/blog/post-1/ar/blog/post-1 は 3 つの個別の HTML ファイルです。各ファイルには対応する言語のコンテンツのみが含まれ、最速の読み込み速度を保証します。
Q 翻訳辞書の可変パラメータはどのように処理すべきですか?
A ICU MessageFormat 構文を使用します: {name} で変数を挿入、{count, plural, =0 {なし} one {# 件のタスク} other {# 件のタスク}} で複数形を処理します。next-intlt() および useTranslations() フックがこれらのテンプレートを自動的に解析します。

📖 まとめ


📝 練習問題

  1. 基本問題 (⭐): 既存のプロジェクトに next-intl をインストールし、中国語と英語の辞書を設定し、言語切り替えコンポーネントを実装し、t() を使用してページにナビゲーションバーのテキストをレンダリングしてください。

  2. 応用問題 (⭐⭐): 完全な多言語 SSG ブログを実装してください: generateStaticParams で 4 言語の記事ページを事前生成し、各記事がビルドプロセス中に対応する言語の辞書を読み込むようにし、生成された HTML ファイルの構造を検証します。

  3. 発展問題 (⭐⭐⭐): RTL をサポートするダッシュボードページを構築してください: アラビア語と英語の両方に対応し、Tailwind CSS の rtl: プレフィックスと flex-direction ミラーリングを使用して、サイドバー、ナビゲーション、統計カードの完全なレイアウト適応を実装します。dir="rtl" ですべての要素が正しくミラーリングされることを検証してください。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%