> ## Documentation Index
> Fetch the complete documentation index at: https://docs.devin.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Next.js アプリに日本語とスペイン語対応を追加する

export const UseCaseHero = ({title, description, prompt, category, features, devinUrl, agent, intent, playbookId, type}) => {
  const encodedPrompt = encodeURIComponent(prompt || '');
  const tag = 'docs-use-case-gallery';
  const utm = 'utm_source=docs&utm_medium=use-case-gallery&utm_campaign=hero-cta';
  const agentParams = (agent ? '&agent=' + agent : '') + (intent ? '&intent=' + intent : '') + (playbookId ? '&playbookId=' + playbookId : '');
  const devinHref = type === 'schedule' ? 'https://app.devin.ai/settings/schedules/create?' + utm + agentParams + (prompt ? '&prompt=' + encodedPrompt : '') : type === 'review' ? 'https://app.devin.ai/review?' + utm : agent === 'ada' ? 'https://app.devin.ai/search?' + utm + '&noSubmit=true' + (prompt ? '&prompt=' + encodedPrompt : '') : devinUrl ? devinUrl.includes('?') ? devinUrl + '&' + utm + agentParams : devinUrl + '?' + utm + agentParams : prompt ? 'https://app.devin.ai/?tags=' + tag + '&' + utm + agentParams + '&prompt=' + encodedPrompt : 'https://app.devin.ai/?' + utm + agentParams;
  const buttonLabel = type === 'schedule' ? 'Schedule in Devin ↗' : type === 'review' ? 'Set Up Devin Review ↗' : agent === 'advanced' ? 'Try in Devin ↗' : agent === 'dana' ? 'Try in Dana ↗' : agent === 'ada' ? 'Try in Ask Devin ↗' : 'Try in Devin ↗';
  const featureList = features ? features.split(',').map(f => f.trim()) : [];
  return <div className="uc-hero">
      <div className="uc-hero-inner">
        <div className="uc-hero-left">
          <h1 className="uc-hero-title">{title}</h1>
          <p className="uc-hero-desc">{description}</p>
          <div>
            <a href={devinHref} target="_blank" rel="noopener noreferrer" className="try-in-devin-btn">
              {buttonLabel}
            </a>
          </div>
        </div>
        <div className="uc-hero-meta">
          <div className="uc-meta-item">
            <span className="uc-meta-label">Author</span>
            <span className="uc-meta-value">Cognition</span>
          </div>
          <div className="uc-meta-item">
            <span className="uc-meta-label">Category</span>
            <span className="uc-meta-value">{category}</span>
          </div>
          {featureList.length > 0 && <div className="uc-meta-item">
              <span className="uc-meta-label">Features</span>
              <span className="uc-meta-value">{featureList.join(', ')}</span>
            </div>}
        </div>
      </div>
    </div>;
};

export const PromptBlock = ({children, type, agent, intent, playbookId}) => {
  var utm = 'utm_source=docs&utm_medium=use-case-gallery&utm_campaign=prompt-block';
  var tag = 'docs-use-case-gallery';
  var agentParams = (agent ? '&agent=' + agent : '') + (intent ? '&intent=' + intent : '') + (playbookId ? '&playbookId=' + playbookId : '');
  var label = type === 'schedule' ? 'Schedule in Devin' : type === 'playbook' ? 'Create Playbook' : type === 'knowledge' ? 'Add to Knowledge' : agent === 'advanced' ? 'Try in Devin' : agent === 'dana' ? 'Try in Dana' : agent === 'ada' ? 'Try in Ask Devin' : 'Try in Devin';
  var buildUrl = function (text) {
    var encoded = encodeURIComponent(text);
    if (type === 'schedule') return 'https://app.devin.ai/settings/schedules/create?' + utm + agentParams + '&prompt=' + encoded;
    if (type === 'playbook') return 'https://app.devin.ai/settings/playbooks/create?' + utm + '&body=' + encoded;
    if (type === 'knowledge') return 'https://app.devin.ai/knowledge?' + utm + '&body=' + encoded;
    if (agent === 'ada') return 'https://app.devin.ai/search?' + utm + '&noSubmit=true&prompt=' + encoded;
    return 'https://app.devin.ai/?tags=' + tag + '&' + utm + agentParams + '&prompt=' + encoded;
  };
  const ref = React.useRef(null);
  const [href, setHref] = React.useState('#');
  React.useEffect(() => {
    if (!ref.current) return;
    var codeEl = ref.current.querySelector('pre code');
    if (codeEl) {
      var text = codeEl.textContent.trim();
      if (text) setHref(buildUrl(text));
    }
    var header = ref.current.querySelector('[data-component-part="code-block-header"]');
    if (header && !header.querySelector('.prompt-block-devin-link')) {
      var link = document.createElement('a');
      link.href = href;
      link.target = '_blank';
      link.rel = 'noopener noreferrer';
      link.className = 'prompt-block-devin-link';
      link.style.cssText = 'display:inline-flex;align-items:center;gap:6px;text-decoration:none;color:#fff;font-size:11px;font-weight:500;padding:4px 10px;border-radius:6px;white-space:nowrap;background:#317CFF;transition:background 0.2s;margin-left:8px;';
      link.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg> ' + label;
      link.onmouseenter = function () {
        link.style.background = '#2968D9';
      };
      link.onmouseleave = function () {
        link.style.background = '#317CFF';
      };
      header.appendChild(link);
    }
    var existingLink = ref.current.querySelector('.prompt-block-devin-link');
    if (existingLink && href !== '#') existingLink.href = href;
  });
  return <div className="prompt-block" ref={ref}>{children}</div>;
};

<UseCaseHero title="Next.js アプリに日本語とスペイン語を追加する" description="Devin が next-i18next をインストールし、ハードコードされた文字列をロケール JSON ファイルに抽出し、日本語とスペイン語の翻訳を追加し、ブラウザでの言語切り替えを検証します。" prompt="next-i18next を使って Next.js アプリに国際化対応を追加してください。next-i18next、i18next、react-i18next をインストールします。defaultLocale 'en' と locales ['en', 'es', 'ja'] を使ってロケールルーティングを設定します。components/ と pages/ をスキャンし、すべてのハードコードされた英語の文字列（JSX テキスト、placeholder、aria-label、エラーメッセージ、ツールチップ）を探します。public/locales/{en,es,ja}/common.json 配下に、名前空間付きキーを持つ構造化されたロケールファイルを作成します。すべてのハードコードされた文字列を t() 呼び出しに置き換えます。すべてのページに serverSideTranslations を追加します。ナビゲーションバーに言語スイッチャーを追加します。スペイン語と日本語には仮の翻訳を使用します。開発サーバーを起動し、3 つすべての言語で全ページをテストして、英語の文字列の取り残し、レイアウトのはみ出し、ルーティングの不具合がないか確認します。すべてが正常に動作するまで、この作業を繰り返してください。3 つすべての言語がすべてのページで正しく表示されるまで PR を開かないでください。" category="機能開発" features="" />

<div className="uc-detail-wrapper">
  <Tip>手動でのセットアップを省略したい場合は、このページのリンクを Devin のセッションに貼り付けて、すべてのセットアップを依頼してください。</Tip>

  <Steps>
    <Step title="（オプション）Ask Devin でコードベースの範囲を指定する">
      Next.js アプリの構成や、どのコンポーネントにハードコードされた文字列が含まれているかがわからない場合は、まず [Ask Devin](https://app.devin.ai/search?utm_source=docs\&utm_medium=use-case-gallery) を使って調査してください：

      <PromptBlock agent="ada">
        ```txt Scope the i18n work theme={null}
        Help me understand the scope of adding i18n to our Next.js app:
        1. Are we using the Pages Router or App Router?
        2. How many components and pages have hardcoded English strings?
        3. Do we already have any i18n setup or locale files?
        4. Which shared components (buttons, forms, modals) contain text?
        5. Are there any strings that should stay in English (brand names, API codes)?
        ```
      </PromptBlock>

      また、Ask Devin から直接 Devin セッションを開始することもでき、そのセッションでは Ask Devin が取得したすべての情報がコンテキストとして引き継がれます。
    </Step>

    <Step title="i18nのセットアップを開始する">
      Next.js アプリに国際化対応を追加するということは、ロケールルーティングの設定、middleware の構成、ハードコードされたすべての文字列の抽出、翻訳ファイルの作成を、言語切り替えを 1 回テストする前にすべて終わらせる必要があるということです。Devin はこのパイプライン全体を処理します。`next-i18next` のインストール、Next.js のロケールルーティングの設定、数十個のコンポーネントからの文字列抽出、そしてブラウザでの結果検証まで対応します。

      <PromptBlock>
        ```txt Add Japanese and Spanish to our Next.js app theme={null}
        next-i18next を使って、私たちの Next.js アプリに日本語とスペイン語を含む国際化対応を追加してください。

        - next-i18next、i18next、react-i18next をインストールする
        - next-i18next.config.js を、defaultLocale "en" と
          locales ["en", "es", "ja"] で設定する
        - next.config.js を更新し、i18n の設定を含める
        - components/ と pages/ をスキャンして、ハードコードされた英語の文字列をすべて探す
          （JSX テキスト、placeholder 属性、aria-label、エラーメッセージ、ツールチップ）
        - public/locales/{en,es,ja}/common.json 配下に構造化されたロケールファイルを作成し、
          名前空間付きキー（例: common.save, auth.loginButton, errors.notFound）を使う
        - すべてのハードコードされた文字列を、useTranslation('common') を使った t() 呼び出しに置き換える
        - すべてのページの getStaticProps / getServerSideProps に serverSideTranslations を追加する
        - next/router の locale switching を使って、components/Navbar.tsx に言語切り替え用ドロップダウンを追加する
        - スペイン語と日本語には一旦プレースホルダー翻訳を使う — 後で
          プロフェッショナルな翻訳に差し替える
        - アプリを実行し、3 つの言語すべてを切り替えて、何も壊れていないことを確認する

        ## Testing & verification
        - 開発サーバーを起動し、/en、/es、/ja の各ルートを切り替える
        - すべてのページで、翻訳されずに残っている英語の文字列がないか確認する
        - 言語切り替えスイッチが正しくトグルすることを確認する
        - 補間された値（名前、件数）が各ロケールで正しく表示されることをテストする
        - スペイン語の長めの文字列や、
          日本語の幅の広い文字でレイアウトが崩れないか確認する
        - 何か壊れている場合 — 翻訳抜け、レイアウトのはみ出し、
          ルーティングエラーなどがあれば — 修正して再テストする。3 つすべての
          言語が、すべてのページで正しく表示されるまで繰り返す。
        - すべてがエンドツーエンドで動作するまで PR を作成しないこと
        ```
      </PromptBlock>

      セッションの最初に **`/plan`** スラッシュコマンドを使って、まず Devin にコードベースを調査させてください。Pages Router か App Router のどちらを使っているかを判別し、適切な i18n ライブラリを特定し、コードを書き始める前に進め方の概要を示させます。計画を確認し、着手前に変更を提案してください。
    </Step>

    <Step title="Devin によるセットアップの仕組み">
      Devin は Next.js のコードベースに対して、i18n パイプラインの構成、文字列の抽出、ロケールファイルの作成を順序立てて処理します。実際の流れは次のとおりです。

      1. **依存関係をインストール** — `npm install next-i18next i18next react-i18next` を実行し、設定ファイルを作成します。

      ```js theme={null}
      // next-i18next.config.js
      module.exports = {
        i18n: {
          defaultLocale: 'en',
          locales: ['en', 'es', 'ja'],
        },
      };
      ```

      ```js theme={null}
      // next.config.js
      const { i18n } = require('./next-i18next.config');

      module.exports = {
        i18n,
        // ...既存の設定
      };
      ```

      2. **すべてのコンポーネントをスキャン** — `components/` と `pages/` 内の各ファイルを読み取り、JSX 内のハードコードされたテキストや、`placeholder`・`aria-label` といった属性、テンプレートリテラル、エラー文字列を検出します

      3. **ロケールファイルを構築** — `public/locales/` 配下に構造化された JSON を生成します:

      ```json theme={null}
      // public/locales/en/common.json
      {
        "common": {
          "save": "Save",
          "cancel": "Cancel",
          "loading": "Loading..."
        },
        "auth": {
          "login": "Log in",
          "signup": "Create account",
          "forgotPassword": "Forgot your password?"
        },
        "dashboard": {
          "welcome": "Welcome back, {{name}}",
          "items_one": "{{count}} item",
          "items_other": "{{count}} items"
        }
      }
      ```

      ```json theme={null}
      // public/locales/ja/common.json
      {
        "common": {
          "save": "保存",
          "cancel": "キャンセル",
          "loading": "読み込み中..."
        },
        "auth": {
          "login": "ログイン",
          "signup": "アカウント作成",
          "forgotPassword": "パスワードをお忘れですか？"
        },
        "dashboard": {
          "welcome": "おかえりなさい、{{name}}",
          "items_one": "{{count}} 件",
          "items_other": "{{count}} 件"
        }
      }
      ```

      ```json theme={null}
      // public/locales/es/common.json
      {
        "common": {
          "save": "Guardar",
          "cancel": "Cancelar",
          "loading": "Cargando..."
        },
        "auth": {
          "login": "Iniciar sesión",
          "signup": "Crear cuenta",
          "forgotPassword": "¿Olvidaste tu contraseña?"
        },
        "dashboard": {
          "welcome": "Bienvenido de nuevo, {{name}}",
          "items_one": "{{count}} elemento",
          "items_other": "{{count}} elementos"
        }
      }
      ```

      4. **文字列をその場で置き換え** — 各コンポーネントを `useTranslation('common')` でラップし、ハードコードされたテキストを `t()` 呼び出しに置き換えます。`t('dashboard.welcome', { name: user.name })` のような動的値の補間にも対応します。

      5. **サーバーサイドでの読み込みを設定** — 各ページに `serverSideTranslations` を追加し、レンダリング時点で翻訳が利用できるようにします:

      ```tsx theme={null}
      // pages/dashboard.tsx
      import { serverSideTranslations } from 'next-i18next/serverSideTranslations';

      export const getStaticProps = async ({ locale }) => ({
        props: {
          ...(await serverSideTranslations(locale, ['common'])),
        },
      });
      ```

      6. **言語スイッチャーを追加します** — Next.js の router のロケール切り替え機能を利用するドロップダウンをナビゲーションバーに追加します:

      ```tsx theme={null}
      // components/LanguageSwitcher.tsx
      import { useRouter } from 'next/router';

      export default function LanguageSwitcher() {
        const router = useRouter();

        const switchLocale = (locale: string) => {
          router.push(router.pathname, router.asPath, { locale });
        };

        return (
          <select
            value={router.locale}
            onChange={(e) => switchLocale(e.target.value)}
          >
            <option value="en">English</option>
            <option value="es">Español</option>
            <option value="ja">日本語</option>
          </select>
        );
      }
      ```
    </Step>

    <Step title="テストと改善を繰り返す">
      Devin は `npm run dev` を実行し、[組み込みブラウザ](/ja/work-with-devin/devin-session-tools) でアプリを開き、`/en`、`/es`、`/ja` の各ルートを行き来してすべてが正しく動作することを確認します。欠落している翻訳、レイアウトのはみ出し、404 になるルートなど、何か問題があれば Devin が修正し、自動的に再テストします。

      任意のタイミングで **`/test`** スラッシュコマンドを使うと、Devin にすべてのテストを再実行させ、ブラウザで再検証させることができます。最終的な PR の前に **`/review`** を使うと、Devin が自分のコードをレビューし、翻訳漏れの文字列、キー名の不整合、`serverSideTranslations` 呼び出しの抜けなどをチェックします。

      Devin は次を確認します:

      * すべての表示テキストが翻訳されていること (スペイン語/日本語モードで英語の文字列が残っていないこと)
      * スペイン語の長い文字列や日本語の幅の広い文字でもレイアウトが崩れないこと
      * 言語スイッチャーで 3 つのロケールをすべて切り替えられること
      * 差し込まれる値 (ユーザー名、件数など) が各ロケールで正しくレンダリングされること
      * 複数形ルールが機能していること (例: "1 item" → 日本語では "1 件"、スペイン語では "1 elemento")
      * Next.js のロケールルーティングが機能していること — `/ja/dashboard` で日本語版が表示されること
    </Step>

    <Step title="ここから反復を開始する">
      ベースとなる i18n のセットアップが完了したら、次のステップで機能を拡張します。

      <PromptBlock>
        ```txt プロフェッショナルな翻訳に差し替える theme={null}
        プレースホルダーのスペイン語および日本語訳を、ベンダー提供の
        これらのプロフェッショナルな翻訳に置き換えてください。
        [翻訳済みの JSON ファイルを添付してください。]
        public/locales/en/common.json にあるすべてのキーがカバーされており、
        es および ja のファイルにプレースホルダーが残っていないことを確認してください。
        ```
      </PromptBlock>

      <PromptBlock>
        ```txt 4 つ目のロケールとして韓国語を追加する theme={null}
        新しいロケールとして韓国語 (ko) を追加してください。next-i18next.config.js を更新して
        locales 配列に "ko" を追加し、public/locales/ko/common.json をプレースホルダーの
        翻訳付きで作成し、LanguageSwitcher にそのオプションを追加したうえで、
        /ko ルートがブラウザで正しく動作することを検証してください。
        ```
      </PromptBlock>

      <PromptBlock>
        ```txt ページごとの namespace に翻訳を分割する theme={null}
        common.json が大きくなってきています。以下の個別 namespace ファイルに分割してください:
        public/locales/{en,es,ja}/auth.json、dashboard.json、settings.json。
        各ページの serverSideTranslations 呼び出しを更新し、
        必要な namespace のみを読み込むようにしてください。
        ```
      </PromptBlock>
    </Step>

    <Step title="リポジトリ設定を更新する">
      このタスクでは、新しい依存関係 (`next-i18next`、`i18next`、`react-i18next`) をインストールします。PR がマージされたら、今後のセッションで Devin の環境スナップショットにこれらのパッケージが含まれるように、[環境設定](/ja/onboard-devin/environment) を更新してください。

      1. [**Settings > Devin's Environment > Repositories**](https://app.devin.ai/settings/machine?utm_source=docs\&utm_medium=use-case-gallery) に移動します
      2. 対象のリポジトリを選択し、**Configure** をクリックします
      3. **Upkeep > Maintain dependencies** で、インストールコマンド (例: `npm ci`) が設定されていることを確認します
      4. **Save** をクリックしてスナップショットを更新します

      また、**Upkeep Frequency** を設定して、Devin が依存関係のインストールをスケジュールに従って自動的に再実行するようにすることもできます。これにより、`package.json` の変更に合わせてスナップショットを最新の状態に保てるため、将来のセッションで起動時にパッケージのインストールに時間を無駄にせずに済みます。
    </Step>

    <Step title="Devin Review で PR をレビューする">
      Devin が PR を作成したら、[Devin Review](https://app.devin.ai/review?utm_source=docs\&utm_medium=use-case-gallery) を使って i18n の変更をレビューします。Devin Review は、見落とされたハードコードされた文字列、一貫性のない翻訳キー名、各ページで抜けている `serverSideTranslations` 呼び出しなどを検出できます。
    </Step>
  </Steps>
</div>
