Skip to main content

Add Japanese & Spanish to a Next.js App

Devin installs next-i18next, extracts hardcoded strings into locale JSON files, adds Japanese and Spanish translations, and verifies language switching in the browser.
AuthorCognition
CategoryFeature Development
1

(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 to investigate first:You can also start a Devin session directly from Ask Devin, and it will carry over everything it learned as context.
2

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

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:
// next-i18next.config.js
module.exports = {
  i18n: {
    defaultLocale: 'en',
    locales: ['en', 'es', 'ja'],
  },
};
// next.config.js
const { i18n } = require('./next-i18next.config');

module.exports = {
  i18n,
  // ...your existing config
};
  1. 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
  2. Builds locale files — Creates structured JSON under public/locales/:
// 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"
  }
}
// public/locales/ja/common.json
{
  "common": {
    "save": "保存",
    "cancel": "キャンセル",
    "loading": "読み込み中..."
  },
  "auth": {
    "login": "ログイン",
    "signup": "アカウント作成",
    "forgotPassword": "パスワードをお忘れですか?"
  },
  "dashboard": {
    "welcome": "おかえりなさい、{{name}}",
    "items_one": "{{count}} 件",
    "items_other": "{{count}} 件"
  }
}
// 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"
  }
}
  1. 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 })
  2. Wires up server-side loading — Adds serverSideTranslations to each page so translations are available at render time:
// pages/dashboard.tsx
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';

export const getStaticProps = async ({ locale }) => ({
  props: {
    ...(await serverSideTranslations(locale, ['common'])),
  },
});
  1. Adds the language switcher — Builds a dropdown in the navbar that uses Next.js router locale switching:
// 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>
  );
}
4

Test and iterate

Devin runs npm run dev, opens the app in its built-in browser, 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
5

Iterate from here

Once the base i18n setup is in place, follow up to extend it.
6

Update your repo setup

This task installs new dependencies (next-i18next, i18next, react-i18next). After the PR merges, update your repo setup so Devin’s environment snapshot includes these packages for future sessions:
  1. Go to Settings > Devin’s Environment > Repositories
  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.
7

Review the PR with Devin Review

Once Devin opens the PR, use Devin Review to review the i18n changes. Devin Review can catch missed hardcoded strings, inconsistent translation key naming, and missing serverSideTranslations calls across pages.