This document describes the i18n infrastructure for the Goose Desktop UI (`ui/desktop/`).
## Overview
The i18n system is built on [react-intl](https://formatjs.io/docs/react-intl/) (part of the FormatJS suite). It uses the **ICU MessageFormat** standard for translations, which provides full support for pluralization, gender/select, number/date formatting, and nested messages — all governed by CLDR rules.
**Key design decisions:**
- English strings live in source code as `defaultMessage` values — no duplication between code and catalog.
- The `@formatjs/cli` tool extracts messages automatically from source into translation catalogs.
- Date, time, and number formatting use the same locale as text translations (single source of truth via `IntlProvider`).
- No build pipeline changes required — react-intl is a pure runtime library.
## Marking strings for translation
### In React components
```tsx
import{defineMessages,useIntl}from'react-intl';
constmessages=defineMessages({
greeting:{
id:'myComponent.greeting',
defaultMessage:'Hello, {name}!',
},
itemCount:{
id:'myComponent.itemCount',
defaultMessage:'{count, plural, one {# item} other {# items}}',
The `#` symbol inside plural/selectordinal is replaced with the formatted number.
For full syntax details, see the [ICU MessageFormat specification](https://unicode-org.github.io/icu/userguide/format_parse/messages/).
## Extracting messages
After adding or modifying `defineMessages` calls, regenerate the English catalog:
```bash
cd ui/desktop
pnpm i18n:extract
```
This scans all `src/**/*.{ts,tsx}` files and writes the canonical English catalog to `src/i18n/messages/en.json`. Commit this file — it serves as the reference for translators.
### Keeping en.json in sync (automated check)
The `lint:check` script includes `i18n:check`, which re-runs extraction and verifies the output matches what's committed:
```bash
pnpm i18n:check
```
This runs as part of `pnpm lint:check` (and therefore CI). If a developer changes a `defaultMessage` in source but forgets to run `pnpm i18n:extract`, the check fails with a diff showing exactly what's out of date.
To compile messages into an optimized AST format (optional, for production performance):
```bash
pnpm i18n:compile
```
Compiled files go to `src/i18n/compiled/` (gitignored).
## Locale detection
The locale is resolved at startup in the following order:
Use `intl.formatDate()`, `intl.formatNumber()`, `intl.formatRelativeTime()` from the `useIntl()` hook. These automatically use the same locale as text translations: