All jobs

Add i18n to a React App from a Linear Issue

Yes. An agent reads the labelled Linear issue, wires i18next into a Vite React app, moves strings into locale files, adds a switcher and tests, and opens a PR.

Run this on RushIn a Project that has your repository.

What the agent does

The agent reads the issue and then the repository before it changes anything. In a repository shaped like alan2207/bulletproof-react there are three apps, and the issue points at apps/react-vite. Its package.json declares React 18.3.1, react-router 7, Vite 5, Vitest 2, Zod 3 and TypeScript 5.4, with Yarn scripts for test, lint, check-types, build and test-e2e. The ESLint config in .eslintrc.cjs enforces kebab-case file and folder names through eslint-plugin-check-file, and src/config/env.ts reads only VITE_APP_ variables through a Zod schema, so the locale default is read there rather than from a loose import.meta.env call.

The strings live where the app composes itself. src/app/provider.tsx wraps the tree in Suspense, an error boundary, HelmetProvider and the React Query client; src/components/layouts/dashboard-layout.tsx names the side navigation Dashboard, Discussions and Users and the menu items Your Profile and Sign Out; src/features/auth/components/login-form.tsx labels its inputs Email Address and Password; and src/app/routes/landing.tsx carries the Bulletproof React, Get started and Github Repo copy. The agent installs i18next, react-i18next and i18next-browser-languagedetector, after checking that react-i18next 17 asks for React 16.8 or newer and TypeScript 5, 6 or 7, both of which this app satisfies.

The wiring is one module and one directory. src/lib/i18n.ts initialises i18next with the browser language detector, English as fallbackLng, and resources imported from src/locales/en/translation.json and the second locale the issue names, with the kebab-case paths the lint rule requires. provider.tsx imports that module once so every route renders through it, and a useTranslation hook replaces the literal strings in the layouts, the auth forms and the landing page with keys. A language menu is added to the user dropdown in dashboard-layout.tsx, and the choice persists through the detector's localStorage cache so a reload keeps it. English stays the default, which matters because the Playwright specs under e2e/tests and the Vitest tests under src/app/routes/app/discussions/__tests__ find elements by their English text.

Verification uses the repository's own tools. src/testing/test-utils.tsx renders under AppProvider with a memory router, so a new test renders the dashboard layout, changes the language through i18next, and asserts the navigation reads in the second locale. The agent runs yarn test, yarn lint, yarn check-types and yarn build, which is the same sequence .github/workflows/react-vite-ci.yml runs in its all-cli-checks job, and it runs yarn test-e2e against the mock server the e2e job uses, so the smoke, profile and auth setup specs still pass with English as the default.

Start it with one API call

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

{
  "agent": "claude",
  "input": "A Linear issue labeled agent says: \"Add internationalisation to the React Vite app. Move the visible strings in the layouts, the auth forms and the landing page into locale files, add a second locale, and add a language switcher in the user menu that remembers the choice.\" Work in apps/react-vite. Read package.json, .eslintrc.cjs, src/config/env.ts, src/app/provider.tsx, src/components/layouts/dashboard-layout.tsx, src/features/auth/components/login-form.tsx and src/app/routes/landing.tsx first. Use i18next with react-i18next and the browser language detector, keep English as the fallback so the existing Vitest and Playwright tests still pass, keep file and folder names in kebab-case, add a Vitest test that renders in the second locale, run yarn test, yarn lint, yarn check-types, yarn build and yarn test-e2e, and open a pull request that links the issue.",
  "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 on its own branch, scoped to apps/react-vite. The diff adds src/lib/i18n.ts and the src/locales/ directory with one JSON file per locale, adds three dependencies to package.json and yarn.lock, imports the i18n module in src/app/provider.tsx, replaces literals with t() calls in the layout, auth and landing files, and adds the language menu and its test. The description quotes the issue, lists every file whose strings moved, and names any string the agent left in place because a test or an accessibility label depends on it.

The checks are the proof. react-vite-ci.yml runs on the pull request: the all-cli-checks job builds, tests, lints and type-checks, and the e2e job installs Playwright browsers and runs the specs against the mock API, uploading playwright-report/ as an artifact. The session's trajectory at GET /o/{org}/sessions/{id}/messages keeps every yarn command with its output, so a reviewer sees the suites pass rather than reading a claim.

Before merging, a reviewer checks three things: that no key is missing from the second locale file, which the new test exercises for the navigation but not for every string; that src/config/env.ts still validates the environment with the new optional default-locale variable; and that the English copy in the locale file matches what the Playwright specs look for. The two Next.js apps in the monorepo are untouched, and nothing reaches main until the pull request merges.

Run it on every Linear issue labelled agent

Sign in to Linear once with POST /me/connections {"type": "app", "name": "linear"} and finish the authorize URL in the console. Then add an automation to the Project with this prompt and a linear trigger on linear.issue.labeled, naming the label in config.labels. The trigger adds the issue after the prompt and fills in nothing, so the agent reads which app and which locales the issue asks for from the event. The automation's max_runs_per_hour, between 1 and 30, caps how many labelled issues it takes on in an hour, each in its own sandbox with its own pull request.

From your own server instead, a handler that receives Linear's webhook for the label sends POST /o/{org}/sessions with the repository and the issue URL in resources: https://linear.app/<workspace>/issue/ENG-42 puts the issue text in the prompt through your Linear connection. A request_id derived from the issue id makes a redelivered webhook return the run it already started. Give that key a webhook_url and Rush POSTs run.completed when the turn finishes, with data.session_url to read for the pull request link.

The use case this job follows, with its automation and trigger: Turn a Linear label into a PR.

Why run it on Rush?

Internationalising an app is mostly a long, careful edit: every literal in a dozen files becomes a key, every key needs an entry in every locale, and one missed string shows up as a raw key in production. On a laptop that work competes with everything else open in the editor. On Rush the agent has its own sandbox and its own clone, runs yarn test, yarn lint and yarn check-types after each pass, and pushes only when the sequence the CI workflow runs is clean.

The result is a pull request you can judge, not a branch you have to finish. The diff shows every string that moved, the locale files show every key, the new Vitest test shows the switch working, and the repository's own CI runs the unit and Playwright suites on it. Several labelled issues mean several sandboxes at once, and the trajectory keeps every command and its output for each.

Frequently asked questions

Will the existing tests break when strings become keys?

They should not, because English stays the default and the fallback. The Vitest tests in src/app/routes/app/discussions/__tests__ and the Playwright specs under e2e/tests find elements by English text, and that text now comes from src/locales/en/translation.json unchanged. The agent runs both suites before opening the pull request, and any string it had to leave alone is named in the description.

Which library does it use?

i18next with react-i18next and i18next-browser-languagedetector. The agent checks the peer requirements first: react-i18next 17 asks for React 16.8 or newer, i18next 26.2 or newer and TypeScript 5, 6 or 7, and this app has React 18.3.1 and TypeScript 5.4. If your issue names another library, say so and the agent follows it.

Does it touch the Next.js apps too?

No. The issue names apps/react-vite, and the pull request stays inside it. The Next.js App Router and Pages Router apps have their own CI workflows and would need their own routing-aware setup, so they are a separate issue rather than a side effect of this one.

How is the chosen language remembered?

Through the browser language detector's localStorage cache, which is its default order: a stored choice wins, then the browser language, then English. The switcher in the user dropdown writes the choice through i18next's changeLanguage, so the detector stores it and a reload keeps it. No server round trip is involved.

Will the ESLint file-name rule reject the new files?

No. .eslintrc.cjs requires kebab-case names for files and folders under src/, and the agent names the new module src/lib/i18n.ts and the locale directory src/locales/en/ to match. yarn lint runs before the pull request opens, and the CI workflow runs it again.