> ## 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.

# Add Japanese & Spanish to a Next.js App

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="Add Japanese & Spanish to a Next.js App" description="Devin installs next-i18next, extracts hardcoded strings into locale JSON files, adds Japanese and Spanish translations, and verifies language switching in the browser." prompt="Add internationalization to our Next.js app using next-i18next. Install next-i18next, i18next, and react-i18next. Configure locale routing with defaultLocale 'en' and locales ['en', 'es', 'ja']. Scan components/ and pages/ for all hardcoded English strings (JSX text, placeholders, aria-labels, error messages, tooltips). Create structured locale files under public/locales/{en,es,ja}/common.json with namespaced keys. Replace every hardcoded string with a t() call. Add serverSideTranslations to every page. Add a language switcher to the navbar. Use placeholder translations for Spanish and Japanese. Start the dev server and test every page in all three languages — check for leftover English strings, layout overflow, and broken routing. Keep iterating until everything works. Do not open a PR until all three languages render correctly on every page." category="Feature Development" features="" />

<div className="uc-detail-wrapper">
  <Tip>Don't want to set this up manually? Paste a link to this page into a Devin session and ask it to set everything up for you.</Tip>

  <Steps>
    <Step title="(Optional) Scope the codebase with Ask Devin">
      If you're not sure how your Next.js app is structured or which components contain hardcoded strings, use [Ask Devin](https://app.devin.ai/search?utm_source=docs\&utm_medium=use-case-gallery) to investigate first:

      <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>

      You can also start a Devin session directly from Ask Devin, and it will carry over everything it learned as context.
    </Step>

    <Step title="Kick off the i18n setup">
      Adding internationalization to a Next.js app means wiring up locale routing, configuring middleware, extracting every hardcoded string, and creating translation files — all before you can test a single language switch. Devin handles the entire pipeline: installing `next-i18next`, configuring Next.js locale routing, extracting strings from dozens of components, and verifying the result in the browser.

      <PromptBlock>
        ```txt Add Japanese and Spanish to our Next.js app theme={null}
        Add internationalization to our Next.js app using next-i18next.

        - Install next-i18next, i18next, and react-i18next
        - Configure next-i18next.config.js with defaultLocale "en" and
          locales ["en", "es", "ja"]
        - Update next.config.js to include the i18n config
        - Scan components/ and pages/ for all hardcoded English strings
          (JSX text, placeholder attributes, aria-labels, error messages, tooltips)
        - Create structured locale files under public/locales/{en,es,ja}/common.json
          with namespaced keys (e.g. common.save, auth.loginButton, errors.notFound)
        - Replace every hardcoded string with a t() call using useTranslation('common')
        - Add serverSideTranslations to every page's getStaticProps / getServerSideProps
        - Add a language switcher dropdown to components/Navbar.tsx using next/router locale switching
        - Use placeholder translations for Spanish and Japanese — we'll swap in
          professional translations later
        - Run the app and switch between all three languages to confirm nothing breaks

        ## Testing & verification
        - Start the dev server and switch between /en, /es, and /ja routes
        - Check every page for leftover untranslated English strings
        - Verify the language switcher toggles correctly
        - Test that interpolated values (names, counts) render in each locale
        - Check that layouts don't break with longer Spanish strings or
          wider Japanese characters
        - If anything is broken — missing translations, layout overflow,
          routing errors — fix it and re-test. Keep iterating until all
          three languages render correctly on every page.
        - Do not open a PR until everything works end-to-end
        ```
      </PromptBlock>

      Use the **`/plan`** slash command at the start of the session so Devin investigates your codebase first — it will figure out whether you're using the Pages Router or App Router, identify the right i18n library, and outline its approach before writing any code. Review the plan and suggest changes before it starts.
    </Step>

    <Step title="How Devin handles the setup">
      Devin works through your Next.js codebase methodically — configuring the i18n pipeline, extracting strings, and building locale files. Here's what that looks like:

      1. **Installs dependencies** — Runs `npm install next-i18next i18next react-i18next` and creates the config files:

      ```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,
        // ...your existing config
      };
      ```

      2. **Scans every component** — Reads each file in `components/` and `pages/`, identifying hardcoded text in JSX, attributes like `placeholder` and `aria-label`, template literals, and error strings

      3. **Builds locale files** — Creates structured JSON under `public/locales/`:

      ```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. **Replaces strings in-place** — Wraps each component with `useTranslation('common')` and swaps hardcoded text for `t()` calls, handling interpolation for dynamic values like `t('dashboard.welcome', { name: user.name })`

      5. **Wires up server-side loading** — Adds `serverSideTranslations` to each page so translations are available at render time:

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

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

      6. **Adds the language switcher** — Builds a dropdown in the navbar that uses Next.js router locale switching:

      ```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="Test and iterate">
      Devin runs `npm run dev`, opens the app in its [built-in browser](/work-with-devin/devin-session-tools), and navigates between `/en`, `/es`, and `/ja` routes to verify everything works. If something is broken — a missing translation, a layout that overflows, a route that 404s — Devin fixes it and re-tests automatically.

      Use the **`/test`** slash command at any point to tell Devin to re-run all tests and re-verify in the browser. Use **`/review`** before the final PR to have Devin review its own code for missed strings, inconsistent key naming, or missing `serverSideTranslations` calls.

      Devin checks:

      * All visible text is translated (no leftover English strings in Spanish/Japanese mode)
      * Layouts don't break with longer Spanish strings or wider Japanese characters
      * The language switcher toggles between all three locales
      * Interpolated values (usernames, counts) render correctly in each locale
      * Pluralization rules work (e.g., "1 item" → "1 件" in Japanese, "1 elemento" in Spanish)
      * Next.js locale routing works — `/ja/dashboard` shows the Japanese version
    </Step>

    <Step title="Iterate from here">
      Once the base i18n setup is in place, follow up to extend it.

      <PromptBlock>
        ```txt Swap in professional translations theme={null}
        Replace the placeholder Spanish and Japanese translations with these
        professional translations from our vendor. [Attach the translated JSON files.]
        Make sure all keys in public/locales/en/common.json are covered and
        no placeholders remain in the es and ja files.
        ```
      </PromptBlock>

      <PromptBlock>
        ```txt Add Korean as a fourth locale theme={null}
        Add Korean (ko) as a new locale. Update next-i18next.config.js to include
        "ko" in the locales array, create public/locales/ko/common.json with
        placeholder translations, add the option to the LanguageSwitcher, and
        verify /ko routes work in the browser.
        ```
      </PromptBlock>

      <PromptBlock>
        ```txt Split translations by page namespace theme={null}
        Our common.json is getting large. Split it into separate namespace files:
        public/locales/{en,es,ja}/auth.json, dashboard.json, and settings.json.
        Update each page's serverSideTranslations call to load only the
        namespaces it needs.
        ```
      </PromptBlock>
    </Step>

    <Step title="Update your repo setup">
      This task installs new dependencies (`next-i18next`, `i18next`, `react-i18next`). After the PR merges, update your [environment configuration](/onboard-devin/environment) so Devin's environment snapshot includes these packages for future sessions:

      1. Go to [**Settings > Devin's Environment > Repositories**](https://app.devin.ai/settings/machine?utm_source=docs\&utm_medium=use-case-gallery)
      2. Select your repo and click **Configure**
      3. Under **Upkeep > Maintain dependencies**, make sure your install command (e.g., `npm ci`) is set
      4. Click **Save** to update the snapshot

      You can also set an **Upkeep Frequency** so Devin automatically re-runs dependency installation on a schedule — this keeps the snapshot current as your `package.json` evolves, so future sessions don't waste time installing packages at startup.
    </Step>

    <Step title="Review the PR with Devin Review">
      Once Devin opens the PR, use [Devin Review](https://app.devin.ai/review?utm_source=docs\&utm_medium=use-case-gallery) to review the i18n changes. Devin Review can catch missed hardcoded strings, inconsistent translation key naming, and missing `serverSideTranslations` calls across pages.
    </Step>
  </Steps>
</div>
