# Add a Second Language to an Astro Site

Yes. An agent adds a locale to your Astro site's i18n config, writes the UI strings, creates the localised routes and switcher, builds it and opens a pull request.

[Run this on Rush](https://cloud.getrush.ai/run/add-i18n/astro/), in a Project that has your repository.

## What the agent does

The agent starts from the Linear issue, which names the language, then reads how far the site already goes. In a blog like satnaing/astro-paper, `astro.config.ts` already has an `i18n` block with `locales: ["en"]`, `defaultLocale: "en"` and `routing.prefixDefaultLocale: false`, and `astro-paper.config.ts` sets `site.lang` to `en`, `site.dir` to `ltr` and a `timezone`. There is a `src/i18n` directory: `types.ts` declares a `UIStrings` interface with `nav`, `post`, `pagination`, `home`, `footer`, `pages` and `a11y` groups, `lang/en.ts` is the only implementation, and `index.ts` loads every file under `lang/` with `import.meta.glob` and exports `useTranslations(locale)`, which falls back to English. `CHANGELOG.md` records this as the i18n layer added in #631. So the shape exists; what is missing is a second locale and the routes that serve it.

The agent then reads where the locale is used, so the new one reaches every place. `src/layouts/Layout.astro` sets `<html lang>` from `Astro.currentLocale ?? site.lang` and builds the RSS link with `getRelativeLocaleUrl`; `src/components/Header.astro` calls `useTranslations(locale)`, strips the locale prefix with `stripLocale` from `src/utils/withBase.ts` to compute the active nav item, and links home with `getRelativeLocaleUrl(locale, "")`; `src/utils/getPostPaths.ts` has `getPostUrl(id, filePath, locale)` that prefixes post URLs for a non-default locale; `src/pages/index.astro` reads `t.home.featured` and `t.home.recentPosts`. Posts live in `src/content/posts` as a `glob` collection whose schema in `src/content.config.ts` has `title`, `description`, `pubDatetime`, `tags` and no language field, and `src/content/pages/about.md` is the About page.

The change is in four parts. First, config: the new code goes into `locales` in `astro.config.ts`, and `src/i18n/lang/<code>.ts` implements `UIStrings` in full, so `astro check` fails the build if a key is missed rather than letting English leak through at runtime. Second, routes: with `prefixDefaultLocale` off, Astro serves a non-default locale from `src/pages/<code>/`, so the agent adds that directory with pages that import the same layouts and components as the English ones, for the index, `posts/[...page]`, `posts/[...slug]`, `tags`, `archives`, `search`, `about` and `404`, each reading `Astro.currentLocale`. Third, content: it adds an optional `lang` field to the posts schema defaulting to `site.lang`, filters each localised listing to posts in its language, and adds a localised `about.md` so the pages collection has a translated entry. Fourth, discovery: a language switcher in `Header.astro` built from `getRelativeLocaleUrl` and the stripped current path, and the `@astrojs/sitemap` integration given `i18n` with the default locale and the locale map, so the sitemap carries alternate links.

Verification follows `.github/workflows/ci.yml`, which runs `pnpm run lint`, `pnpm run format:check` and `pnpm run build` on Node 24 with pnpm. `build` here is `astro check && astro build && pagefind --site dist && cp -r dist/pagefind public/`, so the type check, the static build of every localised page, the search index and its copy into `public/` all have to pass. The agent opens `dist/` in the sandbox browser through `astro preview`, loads the new locale's home page, a post and the About page, confirms `<html lang>` and the switcher, then opens a pull request that links the Linear issue.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "Add Spanish (es) as a second language to this Astro site, as the linked issue asks. Read astro.config.ts, astro-paper.config.ts, src/i18n/types.ts, src/i18n/index.ts, src/i18n/lang/en.ts, src/layouts/Layout.astro, src/components/Header.astro, src/utils/getPostPaths.ts, src/content.config.ts and the pages under src/pages first. Add es to i18n.locales, write src/i18n/lang/es.ts implementing every key of UIStrings, add src/pages/es/ with localised versions of the index, posts, tags, archives, search, about and 404 pages that reuse the existing layouts and components, add an optional lang field to the posts schema that defaults to the site language and filter each localised listing by it, add a Spanish about page to the pages collection, add a language switcher to Header.astro using getRelativeLocaleUrl, and pass the locale map to the sitemap integration. Run pnpm run lint, pnpm run format:check and pnpm run build, which includes astro check and pagefind, then open the preview in the browser and load the Spanish home, a post and the about page. Open a pull request that links the issue and lists every file added or changed.",
  "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 that links the Linear issue. It adds `src/i18n/lang/es.ts`, the `src/pages/es/` directory and a Spanish About page, and changes `astro.config.ts`, `src/content.config.ts`, `src/components/Header.astro` and whichever listing pages gained the language filter. The description lists each file, shows the `pnpm run build` output with `astro check` reporting no errors, and includes screenshots from the sandbox browser of the Spanish home page, a post and the About page with the switcher visible.

The session read names the repository and the pull request, and the screenshots are artifacts with download links that last an hour per read. The transcript at `GET /o/{org}/sessions/{id}/messages` keeps the lint, format and build runs and every page the browser loaded, so a reviewer who does not read Spanish can still see that every route renders and that `<html lang="es">` is set.

Before merging, a reviewer checks the translated strings in `es.ts` for tone and terminology, since the agent writes them and a native speaker should read them, checks that the English site is unchanged at its unprefixed URLs, and confirms the sitemap now lists both locales. A post that should appear in both languages needs a translated copy with `lang: es`; the pull request says which posts are English-only after the change.

## Run it on every Linear issue labelled i18n

Add an automation to the Project with this prompt and a `linear` trigger on `linear.issue.labeled` with `i18n` in its labels. Labelling an issue that names the language starts one run in the Project's repository; the trigger adds the issue after the prompt, so the agent reads the language code from it and replaces nothing in the prompt text. A change made by a Linear app integration never fires the trigger, and an automation starts at most `max_runs_per_hour` runs in an hour.

From your own server, the same run starts with this request and the issue's URL added to `resources` as `https://linear.app/<workspace>/issue/<ID>`, which puts the issue in the prompt through your Linear connection. Give the key a webhook URL and Rush POSTs `run.completed` with `data.session_url` when the turn ends, so your handler can post the pull request link back on the issue.

## Why run it on Rush?

Adding a locale to an Astro site touches config, strings, a parallel page tree and content, and the only honest check is the full build with `astro check` and a browser on the output. The agent runs that loop in its own sandbox with Node 24 and pnpm as CI uses them, and loads every new route in a real browser before the pull request exists, which is more than most laptop attempts do before pushing.

What it did is kept with the result. The transcript holds each build, including any `astro check` error that catches a missing `UIStrings` key, and the screenshots are on the session. Several issues, one per language, run at once in separate sandboxes and arrive as separate pull requests.

## Frequently asked questions

### Does the English site change?

No. `prefixDefaultLocale` stays off, so English keeps its unprefixed URLs and the new locale lives under its own prefix. The existing `src/pages` files are untouched apart from the Header switcher and the listing filter, which defaults to the site language and so shows what it showed before.

### Who writes the translations?

The agent writes `src/i18n/lang/es.ts` and the Spanish About page, and the pull request marks them as needing a native speaker's review. `astro check` guarantees the file is complete against `UIStrings`; it cannot judge the wording, which is why the description points a reviewer at that file first.

### Do existing posts appear in the new locale?

Only posts with `lang` set to the new code appear in its listings. The schema field defaults to the site language so every existing post stays English, and the pull request lists the posts that have no Spanish copy. Translating a post is a copy with `lang: es`, which can be a follow-up on the same session.

### What about search and the RSS feed?

Pagefind indexes the built `dist/`, so the Spanish pages are searchable once `pnpm run build` has run; the agent keeps the search page per locale. The RSS link in `Layout.astro` already goes through `getRelativeLocaleUrl`, and a localised `rss.xml.ts` under `src/pages/es/` is added in the same pull request if the issue asks for a per-language feed.
