All jobs

Translate a Next.js App into Another Language

Yes. An agent moves your Next.js app's hard-coded strings into message files, adds Spanish and a locale switcher, and screenshots the pages in Spanish.

Run this on RushIn a Project that has your repository.

What the agent does

The agent starts by finding out how the app renders text today. In a Next.js app like nextjs/saas-starter it reads package.json and sees next 15.6.0-canary.59, React 19.1 and the App Router, with no next-intl, i18next, react-intl or Lingui anywhere. app/layout.tsx sets lang="en" and a metadata title of 'Next.js SaaS Starter', and every label is a literal in JSX: 'Build Your SaaS' on the landing page in app/(dashboard)/page.tsx, 'Get Started' in pricing/submit-button.tsx, 'Sign in to your account' and the Enter your email placeholder in app/(login)/login.tsx, 'Team Settings' in the dashboard and 'Page Not Found' in app/not-found.tsx.

Since the App Router has no translation layer of its own, the agent adds one library (next-intl is the usual choice for server components) and creates messages/en.json with keys grouped by page, then replaces each literal with a lookup. It also covers text that is not in JSX: the error strings the server actions in app/(login)/actions.ts return, such as 'Invalid email or password. Please try again.' and 'Current password is incorrect.', the pricing features 'Unlimited Usage' and '24/7 Support + Slack Access', and interpolated text like 'with {trialDays} day free trial', which becomes a message with a placeholder instead of a concatenated string.

Locale selection has to fit the app's existing middleware.ts, which already redirects /dashboard to /sign-in without a session cookie and refreshes that cookie on GET. The agent composes the locale step with that logic rather than replacing it, or reads the locale from a cookie so the existing routes keep their paths. It writes messages/es.json with Spanish for every key, sets lang from the active locale, and adds a small switcher in the dashboard header that writes the choice.

Then it runs the app in its sandbox: pnpm install against the pnpm-lock.yaml (lockfile version 9.0), a Postgres for Drizzle, pnpm db:migrate, and pnpm dev. It opens the landing page, /pricing and /sign-in in the sandbox's browser with Spanish selected, looks for strings it missed (an English word in a Spanish page means a literal it did not extract), fixes them, and takes the three screenshots. The pricing page reads products and prices from Stripe through lib/payments/stripe.ts; without a Stripe key it shows the code's fallback plans, and the agent says which one the screenshot shows.

Start it with one API call

POST https://api.prix.dev/o/{org}/sessions with this body:

{
  "agent": "claude",
  "input": "Extract every hard-coded user-facing string in this Next.js app into i18n message files: JSX text, placeholders, metadata titles and descriptions, and error messages returned by server actions. Use one i18n library suited to the App Router and keep interpolated values as message placeholders. Add a Spanish translation for every key, set the html lang from the active locale, and add a locale switcher. Keep the existing middleware behavior for sessions and protected routes. Run the app, open the landing page, the pricing page and the sign-in page in Spanish in the browser, fix any English left on them, and save a screenshot of each. Open a pull request that lists the screenshots, any string you left untranslated and why.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ]
}
  1. Get an API key. In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
  2. Send the request. POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
  3. Read what came back. Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

What comes back

What comes back is a pull request. In a repository shaped like nextjs/saas-starter it adds the i18n library to package.json and pnpm-lock.yaml, adds messages/en.json and messages/es.json with the same keys, changes middleware.ts to add locale selection beside the session check, and touches each page and component that rendered a literal: the landing page, pricing, the sign-in and sign-up forms, the dashboard pages, not-found.tsx and the server actions' error strings. The switcher is one new component. The three screenshots come back as files on the session and are listed in the pull request description.

The description also lists strings the agent chose not to translate, such as product names from Stripe, which come from your Stripe account rather than the code, and the brand name. The session's trajectory keeps the commands it ran and each page it opened, so a reviewer can see that the app actually started and which strings were missing on the first pass.

Before merging, a reviewer checks the Spanish with a fluent speaker (tone, the formal or informal 'you', length on narrow buttons), confirms that sign-in and the /dashboard redirect still work with the new middleware, and that messages/es.json has no key missing from en.json. Translations of legal text, if the app has any, deserve their own review.

Run it on every customer app to localize

A localization service can run this once per customer repository. When a customer connects a repository and picks target languages, your backend sends this request with their repository in resources and the language named in input, one run per repository. Each run clones into its own sandbox and opens its own pull request on the customer's repository, so customers never share a workspace.

Set a webhook URL on your API key and Rush POSTs run.completed or run.failed when each run's turn ends, with the session URL to read the pull request link from. Adding a second language later is a follow-up message to the same session (POST /o/{org}/sessions/{id}/messages), which wakes the parked sandbox with the extracted keys already in place.

The use case this job follows, with its automation and trigger: Run an agent per customer task.

Why run it on Rush?

Extracting strings is mechanical but wide: dozens of files, server actions, metadata and middleware, and the only way to know it is complete is to run the app and look at every page in the new language. On Rush the agent does that in a sandbox with a browser, starts the real app against its own Postgres, and takes the screenshots itself, so the pull request arrives with visual evidence instead of a diff someone has to check out and boot.

The run is also easy to multiply. A localization product can start one run per customer repository and per language, each isolated from the others, each ending in a pull request a person reviews. The trajectory keeps every page load and command, which matters when a customer asks why a string was left in English.

Frequently asked questions

Which i18n library will it use?

If your app already has one (next-intl, i18next, react-intl, Lingui) it uses that and its existing message files. If there is none, as in nextjs/saas-starter, it adds one suited to the App Router and server components and says which in the pull request. Name a library in the prompt if your team has a preference.

Will the URLs change to /es/...?

Only if you ask for locale-prefixed routes. By default the request keeps existing routes and the middleware's session behavior, so the locale is usually read from a cookie or the Accept-Language header. Prefixed routes are better for search indexing of public pages; say so in the prompt and the agent restructures app/ under a locale segment.

How good is the Spanish?

It is a first translation written with the context of each screen, which is better than translating a flat list of keys, but it is not a substitute for a fluent reviewer. The pull request keeps English and Spanish side by side in two JSON files, which is the format translators and translation tools already work with.

Does it translate content from the database or Stripe?

No. It extracts strings that live in the code. Product names and prices on the pricing page come from Stripe, and user-generated content comes from Postgres; the pull request lists those as out of scope so you can decide how to localize them.