What the agent does
The agent reads the issue and then the app's own conventions. In an app shaped like realworld-apps/vue-realworld-example-app, package.json declares Vue 3.5, Vue Router 5, Pinia 3 and Vite 8, with Bun as the package manager and bun.lock as the lockfile, and scripts for serve, build, lint, format:check and test:e2e, which runs the shared RealWorld Playwright suite out of the realworld git submodule against bun run serve on port 8080. eslint.config.mjs applies eslint-plugin-vue's essential rules with Prettier, and vite.config.js resolves @ to src and lets single-file components be imported without their .vue extension. src/main.js creates the app, installs Pinia and the router, checks auth in a beforeEach guard and exposes a window.__conduit_debug__ object the end-to-end tests read.
The strings are in the templates. src/components/TheHeader.vue carries Home, Sign in, Sign up and New Article; src/views/Login.vue has the Sign in heading, Need an account? and the Email and Password placeholders; src/views/Settings.vue has Your Settings and its placeholders; src/components/ArticleList.vue has Loading articles... and No articles are here... yet.; and src/views/Home.vue carries the conduit banner, A place to share your knowledge. and Popular Tags. src/common/format.js formats dates with date-fns, which the agent reads so dates can follow the chosen locale as well.
The wiring is vue-i18n, whose current 11 line asks only for Vue 3. src/i18n/index.js calls createI18n in composition mode with en as the fallback, messages from src/locales/en.json and the second locale the issue names, and the starting locale from localStorage, then the browser language, then en. main.js installs it next to Pinia and the router. Each template string becomes a $t() call with a key, placeholders use :placeholder="$t(...)", and a switcher in TheFooter.vue writes the choice to localStorage and to <html lang>. The English copy in en.json is kept character for character, because the shared Playwright specs assert that text and the selectors around it.
There is no unit test runner in this repository, so the proof is the suite it has. The agent runs git submodule update --init as the README instructs, bun install, bun run lint, bun run format:check and bun run build, then bunx playwright install --with-deps chromium and bun run test:e2e, which is what .github/workflows/playwright.yml runs, and reads the report. It then switches the locale in the sandbox browser and walks the login, settings and home pages to confirm every key resolves and no raw key shows.
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. Move the user-facing strings in the header, footer, login, register, settings, home and article list into locale files, add a second locale, add a language switcher in the footer that remembers the choice, and make dates follow the locale.\" Read package.json, eslint.config.mjs, vite.config.js, playwright.config.ts, src/main.js, src/common/format.js, src/components/TheHeader.vue, src/views/Login.vue, src/views/Settings.vue, src/components/ArticleList.vue and src/views/Home.vue first. Use vue-i18n in composition mode with English as the fallback and keep the English strings exactly as they are so the RealWorld Playwright suite still passes. Initialise the realworld submodule, run bun run lint, bun run format:check, bun run build and bun run test:e2e, check the second locale in the browser, and open a pull request that links the issue.",
"resources": [
{
"url": "https://github.com/your-org/your-app"
}
]
}- 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.
- 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.
- 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. The diff adds src/i18n/index.js and src/locales/ with one JSON file per locale, adds vue-i18n to package.json and bun.lock, installs the plugin in src/main.js, replaces literals with $t() calls in the header, footer, auth views, settings, home and article list, routes date formatting in src/common/format.js through the active locale, and adds the switcher. The description quotes the issue, lists each template whose strings moved, and names any string left in place because a selector or a test depends on it.
The checks are the proof. The Format Check workflow runs Prettier and ESLint, and the Playwright Tests workflow installs Chromium and runs the RealWorld suite against the dev server, uploading playwright-report/ as an artifact. The session's trajectory at GET /o/{org}/sessions/{id}/messages keeps every bun command with its output and the browser pass through the second locale, so a reviewer sees the suite green and the translated pages rather than a claim.
Before merging, a reviewer checks that en.json matches the previous copy exactly, since the end-to-end specs read it, that the second locale has every key the first has, and that the lang attribute and the stored choice survive a reload. 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 and, if only some teams may hand work off, their keys in config.teams. The trigger adds the issue after the prompt and fills in nothing, so the agent reads 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 https://linear.app/<workspace>/issue/ENG-42 in resources, which puts the issue text in the prompt through your Linear connection, with a request_id derived from the issue id so a redelivered webhook returns 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?
Moving every string in seven templates into keys is the kind of edit where one slip shows up as a raw key on the login page, and the only honest check here is the shared end-to-end suite, which needs the submodule, Chromium and the dev server. On a laptop that is a long loop beside other work. On Rush the agent runs that loop in its own sandbox with its own browser, after each pass, and pushes only when the suite and the lint are clean.
The result is a pull request with its own evidence: the diff shows every string that moved, the locale files show every key, the Playwright report shows the suite passing with English intact, and the trajectory shows the second locale rendered. Several labelled issues become several sandboxes and several pull requests at once.
Frequently asked questions
Will the RealWorld end-to-end suite still pass?
It should, because English stays the default and the fallback, and en.json carries the previous copy unchanged. The specs under realworld/specs/e2e assert that text and the selectors around it, and the agent runs bun run test:e2e before the pull request opens. Anything it had to leave untranslated to keep a selector stable is named in the description.
Why composition mode?
Because the views and components use <script setup>, so useI18n() fits beside useRouter() and the Pinia stores, and $t() is still available in templates. vue-i18n 11 supports Vue 3 only, which this app is, and the legacy API would add an older style for no gain.
How is the chosen language remembered?
In localStorage, read by src/i18n/index.js at startup before the browser language and the en fallback. The footer switcher writes the choice, updates the i18n locale and sets <html lang>, so a reload keeps it and screen readers get the right language. No server call is involved.
Does this change the API or the stores?
No. src/common/api.service.js, the Pinia stores and the router are untouched; the change is in templates, one new plugin, the locale files and src/common/format.js for dates. Error messages that come from the RealWorld API are shown as the API returns them.