# Internationalization

English, German and French UI with request-scoped SSR translation.

## Catalog ownership [#catalog-ownership]

Web catalogs live in `apps/web/src/locales/{en,de,fr}.ts`. `MessageKey` derives from English; the other catalogs must retain key and interpolation parity. Shared error translations come from `packages/errors/src/locales`. Mobile maintains its own app catalogs with the same supported languages.

Use `useTranslation` in components and hooks. Format dates and numbers with Intl. Do not concatenate translated fragments when the sentence needs different word order.

## Server rendering [#server-rendering]

`getSSRLanguage` checks a validated `?lang=` first, then the locale cookie, then the fallback. `getRequestI18n` clones the i18next instance for that request while sharing loaded catalogs. Mutating one global language during SSR would make concurrent requests affect each other.

The server holds every catalog (`apps/web/src/platform/i18n/catalogs.server.ts`, imported by `server.ts`). The browser starts with the one catalog the server rendered with, delivered in the root route's loader data, and `switchLanguage` fetches another from `/locales/{$lang}.json` the first time it is chosen; the client entry ships no catalog. The client keeps its shared instance and updates the document language after a switch. Public localized pages advertise locale alternates. Developer articles are English only: their article region has `lang="en"`, their chrome is translated, and their sitemap entries have no hreflang alternates.

## Extend [#extend]

Add a message to English, German and French together. Preserve placeholder names and run the locale parity tests. A new language also needs the supported-language definition, formatters, SEO mappings and native catalogs updated. Translate the domain consistently: a Todo's translated label can differ from its code identifier.


## Key files [#key-files]

- [`packages/intl/src/i18n.ts`](https://github.com/CarlosZiegler/template-cloudflare/blob/main/packages/intl/src/i18n.ts)
- [`packages/intl/src/server.ts`](https://github.com/CarlosZiegler/template-cloudflare/blob/main/packages/intl/src/server.ts)
- [`apps/web/src/platform/i18n/intl.ts`](https://github.com/CarlosZiegler/template-cloudflare/blob/main/apps/web/src/platform/i18n/intl.ts)
- [`apps/web/src/platform/i18n/catalogs.server.ts`](https://github.com/CarlosZiegler/template-cloudflare/blob/main/apps/web/src/platform/i18n/catalogs.server.ts)
- [`apps/web/src/routes/locales/{$lang}[.]json.ts`](https://github.com/CarlosZiegler/template-cloudflare/blob/main/apps/web/src/routes/locales/%7B$lang%7D%5B.%5Djson.ts)
- [`apps/web/src/locales/en.ts`](https://github.com/CarlosZiegler/template-cloudflare/blob/main/apps/web/src/locales/en.ts)
- [`apps/mobile/src/platform/i18n/i18n.ts`](https://github.com/CarlosZiegler/template-cloudflare/blob/main/apps/mobile/src/platform/i18n/i18n.ts)

