# Translating Your App (i18n)

App localization is zero-config end to end: write UI strings with [Lingui](https://lingui.dev/) macros, extract them into `.po` catalogs, and the compiled translations ship with the bundle and load at runtime for the Crowdin user's locale.

## Writing and extracting strings

Write strings with Lingui macros and wrap the app in `AppI18nProvider`:

```tsx
import { AppI18nProvider } from "@crowdin/serverless-apps-sdk/i18n";
import { Trans } from "@lingui/react/macro";

function Root() {
  return (
    <AppI18nProvider fallback={<p>Loading…</p>}>
      <Trans>Hello from my app!</Trans>
    </AppI18nProvider>
  );
}
```

Then run the [CLI](/serverless-apps/reference/cli-commands/#extract):

```bash
crowdin-serverless-apps extract
crowdin-serverless-apps build
```

- `extract` collects the strings into `locales/<locale>.po` catalogs - translate those (in Crowdin, naturally);
- `build` and `dev` compile the catalogs to `dist/locales/<locale>.json`, which ship inside the bundle as [runtime assets](/serverless-apps/building-app/assets/).

## Runtime behavior

At runtime, `AppI18nProvider` detects the Crowdin user's locale from the [host context](/serverless-apps/building-app/context/) and loads the matching catalog from the bundle. If no catalog exists for the user's locale, the source locale is used (default `en-US`, configurable via the `sourceLocale` prop).

## Catalog naming

Name catalogs after full Crowdin locale codes (`uk-UA.po`, `pt-BR.po`) - the CLI warns about names Crowdin will never request (such as `uk.po`), and the runtime never falls back to base languages.

The Crowdin UI ships in a fixed set of locales, so these are the only catalogs the host will ever request:

`en-US`, `ar-SA`, `be-BY`, `cs-CZ`, `da-DK`, `de-DE`, `es-ES`, `fr-FR`, `hu-HU`, `it-IT`, `ja-JP`, `pl-PL`, `pt-BR`, `pt-PT`, `ru-RU`, `sk-SK`, `tr-TR`, `uk-UA`, `xh-ZA`, `zh-CN`, `zu-ZA`

## Translating the catalogs in Crowdin

The natural loop is to translate the app in a Crowdin project of its own:

1. Create a project and upload `locales/en-US.po` as the source file.
2. Set the file's resulting-file-name pattern to produce full locale codes, e.g. `/locales/%locale%.po` - the default two-letter `%two_letters_code%` would export `uk.po`, which the runtime never requests.
3. Translate, then download the built translations into the app's `locales/` directory.
4. Re-run `build` and `publish` - the new catalogs ship with the bundle.

## Notes

- The pipeline needs no `lingui.config.ts`. If you add one, keep the PO format and the `locales/{locale}` catalog layout - see [customization](/serverless-apps/development/customization/).
- Catalogs are compiled once when `dev` starts; restart the dev server after editing `.po` files.