> ## Documentation Index
> Fetch the complete documentation index at: https://docs.avocadostudio.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Internationalization

> The editor UI and AI responses both support multiple languages. English and German ship today; adding a locale is a few-line change.

<Note>
  This page is about **the editor's own language** — the editor's chrome
  and the language the AI answers in. If you are looking for how to edit content
  that exists in several languages, that is
  [Multilingual content](/integration/multilingual). The two are unrelated: a
  German-speaking editor can work on an English-only site, and an English-speaking
  one can edit a tri-lingual site.
</Note>

Avocado Studio ships **two languages**: English (default) and German. The system is built to make adding more locales a small, mechanical change — one new dictionary file, two lines of glue, one entry in a prompt helper.

What's localized:

* **the editor UI** — most labels, buttons, tooltips and dialogs. A few
  surfaces still carry English strings in their components rather than the
  dictionary — among them the publish review dialog, the variation picker's
  heading, and parts of the chat thread — so they stay English in German.
* **AI responses** — the `summary_for_user`, `change_log`, and `suggested_next_actions` come back in the user's language

What's not (intentionally):

* **Block type names** — `Hero`, `CTA`, `FAQAccordion` are code identifiers, not user-facing copy
* **Model and provider names** — `gpt-4o`, `Claude`, `OpenAI` are vendor brands
* **Field AI suggestion pills** — these are sent as prompts to the LLM and must stay in English
* **Preview adapter overlay labels** — currently English-only (separate package, needs postMessage protocol extension)

## How it works

```mermaid theme={null}
flowchart LR
  user[User types in chat]
  editor[the editor<br/>locale = de]
  orch[Orchestrator<br/>prompts.ts]
  llm[LLM]
  user --> editor
  editor -->|POST /chat<br/>locale: 'de'| orch
  orch -->|adds a language instruction| llm
  llm -->|change_log in German| orch
  orch -->|SSE| editor
  editor -->|t('header.publish')| user
```

Two independent layers:

**Editor UI** — A custom `LocaleProvider` + `useT()` hook (no i18n library). The active locale lives in `localStorage("editor-locale")`. Translations are typed `Record<LocaleKeys, string>` so missing keys are compile errors.

**AI responses** — The editor sends `locale` on every `/chat` and `/chat/start` request. The orchestrator's `localeInstruction()` helper injects a language directive into the LLM system prompt, so the structured response fields come back in the right language.

## Switching language

In the editor: **Settings** in the top bar → **Appearance** → **Language** →
**English** / **Deutsch**. The choice persists in `localStorage` and applies to both UI strings and AI responses immediately — no reload.

## Adding a new language

Adding French as a worked example. The same recipe works for any language.

### 1. Create the dictionary

`apps/editor/src/i18n/fr.ts`:

```ts theme={null}
import type { LocaleKeys } from "./en"

const fr: Record<LocaleKeys, string> = {
  "header.publish": "Publier",
  "welcome.greeting": "Bienvenue sur {{name}}",
  // ...all other keys from en.ts
}

export default fr
```

`LocaleKeys` is derived from `en.ts`, so TypeScript will complain about every key you haven't translated yet. `pnpm typecheck` is your checklist.

### 2. Register the locale in the provider

Two edits: widen the `Locale` union and add the dictionary to `LOCALES`.
`resolveLocale()` reads membership in `LOCALES`, so it needs no change, and
`apps/editor/src/i18n/resolve-locale.test.ts` fails the build if that ever
regresses to a hardcoded locale code.

`apps/editor/src/i18n/index.tsx`:

```ts theme={null}
import fr from "./fr"

export type Locale = "en" | "de" | "fr"        // extend the union

const LOCALES = { en, de, fr }                 // add fr
const LOCALE_LABELS = {
  en: "English",
  de: "Deutsch",
  fr: "Français",                              // add label
}
```

### 3. Check the orchestrator knows the language name

`packages/orchestrator-core/src/chat/prompts.ts`:

```ts theme={null}
const LOCALE_NAMES: Record<string, string> = {
  de: "German",
  fr: "French",
  es: "Spanish",
  it: "Italian",
  pt: "Portuguese",
  nl: "Dutch",
  ja: "Japanese",
  ko: "Korean",
  zh: "Chinese",
}
```

This is the name interpolated into the prompt's instruction — *"The user's
interface is in French. Write summary\_for\_user, change\_log entries, and
suggested\_next\_actions in French…"* Note there is no
`en` entry — `localeInstruction()` returns nothing for `en` or an absent locale, because
English needs no instruction. Nine languages are already listed, so for most locales this
step is a no-op; add a row only if yours is missing.

### 4. Verify

```bash theme={null}
pnpm typecheck     # missing translation keys surface as type errors
pnpm dev           # boot the stack, switch to Français, send a chat
```

That's the full integration. No build step, no separate translation pipeline, no external service.

## Files at a glance

| Layer | File |
| - | - |
| Source of truth (English) | `apps/editor/src/i18n/en.ts` |
| Locale resolution guard | `apps/editor/src/i18n/resolve-locale.test.ts` |
| German translation | `apps/editor/src/i18n/de.ts` |
| Provider + `useT()` hook | `apps/editor/src/i18n/index.tsx` |
| Orchestrator prompt helper | `packages/orchestrator-core/src/chat/prompts.ts` (`localeInstruction()`, `LOCALE_NAMES`) |
| Request shape | `packages/orchestrator-core/src/nlp/intent-detection.ts` (`locale` on `ChatRequestBody`) |

## Using translations in code

**In React components:**

```tsx theme={null}
import { useT } from "@/i18n"

function Header() {
  const { t } = useT()
  return (
    <>
      <h1>{t("header.publish")}</h1>
      <p>{t("welcome.greeting", { name: "My Site" })}</p>
    </>
  )
}
```

The `{{name}}` interpolation syntax is built into `useT()`.

**In pure (non-React) functions** — pass `t` as a parameter rather than calling a hook:

```ts theme={null}
import type { TFunction } from "@/i18n"

function buildErrorMessage(t: TFunction): string {
  return t("errors.network")
}
```

## Why no i18n library?

The project deliberately doesn't depend on `i18next`, `react-intl`, or similar. Reasons:

* The key set is flat and stable (about a thousand strings).
* Compile-time enforcement (`Record<LocaleKeys, string>`) catches drift without ICU message format complexity.
* One fewer dependency to upgrade.
* The `{{name}}` interpolation pattern covers every actual use case the editor has.

If your fork needs plurals, gender, or ICU message format, swap in `i18next` — `useT()`'s signature is small enough to back with anything.

## See also

* [Quickstart](/quickstart) — boot the stack and try the language switcher
* [AI Providers](/ai-providers) — the chat pipeline that consumes the `locale` field


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.