ProductReadyProductReady

国際化 (i18n)

型安全な翻訳でProductReadyアプリに多言語サポートを追加

国際化 (i18n)

ProductReadyには、typesafe-i18nを使用した組み込みのi18nサポートが付属しています。アプリに複数の言語を追加できます:

  • 型安全な翻訳 - すべての翻訳キーのオートコンプリート
  • 英語 + 中国語 + 日本語がすぐに使える
  • 新しい言語の追加が簡単 - 翻訳ファイルを追加するだけ
  • どこでも機能 - サーバーコンポーネント、クライアントコンポーネント、APIルート

ランタイムオーバーヘッドゼロ: すべての翻訳はコンパイル時にチェックされ、ツリーシェイクされます!


クイックスタート

多言語コンテンツを表示

ProductReadyのURLには言語プレフィックスが含まれます:

http://localhost:3000/en        ← 英語
http://localhost:3000/zh-CN     ← 中国語
http://localhost:3000/ja        ← 日本語

ドキュメントは完全に翻訳されています:

http://localhost:3000/en/docs
http://localhost:3000/zh-CN/docs
http://localhost:3000/ja/docs

仕組み

URL構造

/[lang]/(home)/page.tsx        ← ランディングページ(すべての言語)
/[lang]/docs/[[...slug]]/page.tsx  ← ドキュメント(すべての言語)

Next.jsは自動的に:

  1. [lang]セグメントを検出
  2. 正しい翻訳を読み込み
  3. ローカライズされたコンテンツをレンダリング

言語検出メカニズム

ProductReadyは、コンテキストに応じて2つの異なる言語検出戦略を使用します:

Webページ(マーケティング、ドキュメント、認証)

//docs/pricing/sign-inなどのURLベースのルートでは、ミドルウェアは次の優先順位を使用します:

優先順位ソース説明
1URLプレフィックス/zh-CN/docs → 中国語
2Cookie(webpage_langリピーター訪問者
3Accept-Languageヘッダー初回訪問者(ブラウザ設定)
4デフォルトロケールenへのフォールバック

ユーザーがページを訪問すると:

  • URLに言語プレフィックスがある場合 → その言語を使用し、Cookieを設定
  • プレフィックスはないがCookieが存在する場合 → 優先言語にリダイレクト
  • 初回訪問者の場合 → ブラウザのAccept-Languageヘッダーから検出
  • それ以外 → デフォルトロケール(英語)を使用

Cookieは1年間保持されるため、リピーター訪問者は優先言語が表示されます。

SPAページ(ダッシュボード、エージェント、スペース)

/dashboard/agent/spaces/*などのダッシュボードルートでは、LocaleProviderは次の優先順位を使用します:

優先順位ソース説明
1ユーザーDB設定データベースのuser.locale
2localStorage(app_localeクライアント側の設定
3ブラウザ言語navigator.languages検出
4デフォルトロケールenへのフォールバック

Webページとの主な違い:

  • URLプレフィックスなし - ダッシュボードルートに/zh-CN/dashboardはありません
  • ユーザー設定が最優先 - ログインユーザーのDB設定が優先
  • localStorage永続化 - SPAナビゲーションではCookieより高速
  • クライアント側検出 - @formatjs/intl-localematcherを使用してブラウザ言語マッチング

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

クライアントコンポーネント

'use client';

import { useI18nContext } from '~/i18n/i18n-react';

export function WelcomeMessage() {
  const { LL } = useI18nContext();
  
  return (
    <div>
      <h1>{LL.common.appName()}</h1>
      <p>{LL.common.tagline()}</p>
    </div>
  );
}

サーバーコンポーネント

import { loadLocale } from '~/i18n/i18n-util.sync';
import type { Locales } from '~/i18n/i18n-types';

export default async function ServerPage({ 
  params 
}: { 
  params: { lang: Locales } 
}) {
  const LL = loadLocale(params.lang);
  
  return (
    <div>
      <h1>{LL.common.appName()}</h1>
      <p>{LL.common.tagline()}</p>
    </div>
  );
}

新しい言語を追加

ステップ 1: サポートされる言語を追加

src/lib/i18n/config.tsを編集:

export const SUPPORTED_LOCALES = ["en", "zh-CN", "ja", "ko"] as const;
//                                                            ^^^^ 新しい言語

export const LOCALE_DISPLAY_NAMES: Record<Locale, string> = {
  en: "English",
  "zh-CN": "简体中文",
  ja: "日本語",
  ko: "한국어",  // 追加
};

export const AUTO_LABEL: Record<Locale, string> = {
  en: "Auto",
  "zh-CN": "自动",
  ja: "自動",
  ko: "자동",  // 追加
};

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

src/i18n/ko/index.tsを作成:

import type { Translation } from '../i18n-types';

const ko: Translation = {
  common: {
    appName: "ProductReady",
    tagline: "장난감 코드를 판매 가능한 제품으로 전환",
    loading: "로딩 중...",
    error: "오류",
    success: "성공",
    // ... 他のすべての翻訳
  },
  // ... 他のすべてのセクション
};

export default ko;

ステップ 3: ドキュメントを追加(オプション)

content/docs/ko/にドキュメントを作成:

content/docs/ko/
├── index.mdx
├── quick-start.mdx
└── meta.json

ステップ 4: テスト

pnpm dev

http://localhost:3000/koを訪問


翻訳キーを追加

ステップ 1: 英語翻訳を追加

src/i18n/en/index.tsを編集:

const en = {
  common: {
    // ... 既存の翻訳
    newKey: "New Translation",
  },
};

ステップ 2: 他の言語を更新

src/i18n/zh-CN/index.tssrc/i18n/ja/index.tsを同じキーで更新:

const zhCN = {
  common: {
    // ... 既存の翻訳
    newKey: "新的翻译",
  },
};

const ja = {
  common: {
    // ... 既存の翻訳
    newKey: "新しい翻訳",
  },
};

ステップ 3: 型を生成

pnpm i18n:typegen

ステップ 4: 使用

const { LL } = useI18nContext();
<p>{LL.common.newKey()}</p>

動的な翻訳

変数付きの翻訳

// 翻訳ファイル
const en = {
  greeting: "Hello, {name}!",
};

// コンポーネント内
<p>{LL.greeting({ name: "Alice" })}</p>
// → "Hello, Alice!"

複数形の処理

const en = {
  itemCount: "{count|plural|one:1 item|other:{count} items}",
};

<p>{LL.itemCount({ count: 1 })}</p>  // → "1 item"
<p>{LL.itemCount({ count: 5 })}</p>  // → "5 items"

ベストプラクティス

翻訳の整理

  • 機能でグループ化auth.signInauth.signUp
  • 一貫した命名:動詞-名詞パターンを使用
  • キーを短く保つcommon.savecommon.saveButtonではなく)
  • 深くネストしすぎない:3レベルまで

翻訳の品質

  • ネイティブスピーカーを使用:機械翻訳を避ける
  • コンテキストを提供:翻訳者がキーの使用場所を理解できるようにする
  • 定期的にレビュー:翻訳を最新の状態に保つ
  • テスト:すべての言語が正しく表示されることを確認

トラブルシューティング

翻訳が表示されない

問題:翻訳キーがそのまま表示される。

解決策

  1. pnpm i18n:typegenを実行して型を生成
  2. すべての言語で翻訳キーが定義されていることを確認
  3. サーバーを再起動

型エラー

問題:TypeScriptが翻訳キーを認識しない。

解決策

  1. pnpm i18n:typegenを実行
  2. TypeScriptサーバーを再起動(VSCode: Cmd+Shift+P → "Reload Window")

次のステップ

i18nを設定したので:

  1. ドキュメントシステム - 多言語ドキュメントを追加
  2. デザインシステム - RTL言語のスタイル
  3. SEO - 多言語SEOを最適化

参考資料

ProductReadyで国際化を楽しんでください! 🌍

On this page