# @workspace/i18n
Reusable Lingui runtime, Devtool, Vite plugin, and CLI. Every consuming
application owns its `i18n.config.json` and application catalogs. Reusable
packages may additionally ship built-in catalogs, which the application can
compose and override.
## Consumer setup
Install the package in an application and add scripts that invoke its CLI:
```json
{
"dependencies": {
"@lingui/core": "^6",
"@workspace/i18n": "workspace:*"
},
"scripts": {
"i18n": "workspace-i18n",
"i18n:compile": "workspace-i18n compile",
"i18n:extract": "workspace-i18n extract",
"i18n:ui": "workspace-i18n ui"
}
}
```
Create `i18n.config.json` in that application:
```json
{
"sourceLocale": "en",
"locales": ["en", "zh-Hans"],
"catalogPath": "src/locales/{locale}/messages",
"catalogSources": [
"@workspace/ui/locales/{locale}",
"@workspace/blocks/navigation/locales/{locale}"
],
"include": [
"src",
"../../packages/ui/src",
"../../packages/blocks/src/blocks/navigation"
],
"exclude": ["**/*.test.{ts,tsx}"]
}
```
`i18n.config.json` is the application's only persistent i18n configuration.
The package converts it to Lingui's normalized configuration in memory. CLI
commands create a temporary Lingui module only for the lifetime of the child
process, so consuming applications do not need a `lingui.config.ts`.
`catalogSources` lists reusable catalogs in merge order. The application
catalog from `catalogPath` is always merged last, so translations customized
in the application override package defaults. Keep dependency source folders
in `include` when the application should be able to edit those overrides in
Message Studio.
Mount the API plugin in the consuming application's Vite configuration:
```ts
import { i18n } from "@workspace/i18n/vite"
import { defineConfig } from "vite"
export default defineConfig({
plugins: [i18n()],
})
```
## Runtime
The Vite plugin exposes the merged catalog through
`@workspace/i18n/catalogs`. Development loads source modules directly. A
production build emits one content-hashed JSON asset per locale and embeds the
matching compact catalog in the SSR bundle.
```tsx
import { use } from "react"
import { loadMessageCatalog } from "@workspace/i18n/catalogs"
import {
I18nProvider,
Translate,
useLocale,
useLocales,
useMessage,
useTranslate,
} from "@workspace/i18n"
import { I18nDevtool } from "@workspace/i18n/devtool"
function Root({
children,
locale,
}: React.PropsWithChildren<{ locale: string }>) {
const messages = use(loadMessageCatalog(locale))
return (
{children}
{import.meta.env.DEV && }
)
}
function Greeting() {
const locale = useLocale()
const locales = useLocales()
const title = useMessage("dashboard.title")
const translate = useTranslate()
return (
<>
{title}
>
)
}
```
`@workspace/ui` and every `@workspace/blocks` block provide `de`, `en`, `es`,
`fr`, `ja`, `ko`, `zh-Hans`, and `zh-Hant` as independent locale subpaths.
Import only the blocks and languages enabled by the application:
```ts
import {
calendarLocale,
messages as uiJapanese,
} from "@workspace/ui/locales/ja"
import { messages as navigationJapanese } from "@workspace/blocks/navigation/locales/ja"
```
The UI root `locales` entry and each block-specific `locales` entry export
metadata and types only. They do not statically import catalogs. For example,
using `@workspace/blocks/navigation` and
`@workspace/blocks/navigation/locales/ja` does not import appearance, chats,
layout, notifications, or their translations. The UI locale entry also
exposes its matching `react-day-picker` `calendarLocale`.
Catalog sources are merged from left to right, so application catalogs should
be last: they can override a package translation without forking that package.
An application therefore only needs to translate its own messages unless it
intentionally customizes package copy.
## Production catalogs
During `vite build`, the plugin:
1. merges configured package catalogs and the application catalog per locale;
2. creates one deterministic shared schema from semantic message IDs;
3. replaces those IDs in production JavaScript with 10-character SHA-256
Base64URL keys;
4. emits one cacheable `assets/i18n/-.json` file for each
locale.
The private semantic-to-compact schema is written to `.i18n/schema.json` for
diagnostics and is ignored by Git. It is not shipped to the browser. Because
every locale uses the same compact key, changing languages only loads the
selected locale asset; package catalogs are not duplicated in the main
application bundle.
`Translate` is a thin wrapper around Lingui's runtime `Trans` component. It
does not add a DOM wrapper and retains `values`, `components`, `formats`,
`component`, and `render`.
The generated in-memory Lingui configuration points
`runtimeConfigModule.Trans` at `Translate`, so explicit
`` usages are found by `lingui extract`.
## Devtool
`I18nDevtool` must be mounted inside `I18nProvider`. It reads the active locale
and available locales from the provider, while the Vite `i18n()` plugin serves
catalog reads, updates, extraction, and compilation.
```tsx
{import.meta.env.DEV && }
```
The `dark` value can be a class name such as `ui\:dark`, or a CSS selector
such as `[data-theme="dark"]`. The Devtool observes document theme changes and
renders through a portal, so application overflow and stacking contexts do not
clip it. Its settings panel can override the theme with `auto`, `light`, or
`dark`, and place the floating panel at the left, center, or right of the
viewport. These preferences and the draggable trigger position persist across
page reloads.
The Devtool control-panel language is independent from the message locale.
Pass a BCP 47 language tag with `locale` to select it explicitly:
```tsx
```
When `locale` is omitted, the Devtool selects the first supported entry from
`navigator.languages` and falls back to English. Built-in control-panel
locales are English, German, Spanish, French, Japanese, Korean, Simplified
Chinese, and Traditional Chinese.
Lower-level `MessagePanel`, `MessageRepositoryProvider`, and repository types
remain available from `@workspace/i18n/devtool`.
The source locale remains available in the target-locale selector. Application
messages are read-only there because their source text belongs in source code;
messages extracted from dependency packages remain editable, and their source
locale translations are stored as application-level overrides.
## CLI
Run inside the consuming application so the CLI discovers that application's
`i18n.config.json`:
```sh
bun run i18n new ja
bun run i18n extract
bun run i18n compile
bun run i18n ui
```
Commands:
- `new ` validates and canonicalizes a BCP-47 locale, updates
`i18n.config.json`, and asks Lingui to extract its catalog.
- `extract` extracts application messages into its catalogs.
- `compile` compiles application catalogs to TypeScript modules.
- `ui` starts Message Studio. It reads and writes PO catalogs through Lingui's
catalog API, and provides Extract and Compile actions.
All commands accept `--project ` when invoked outside the application.
## Catalog workflow
```sh
cd apps/web
bun run i18n:extract
bun run i18n:compile
```
The application's `i18n.config.json` is the single machine-editable source of
truth. Runtime catalog access, Devtool actions, extraction, and compilation all
derive their Lingui configuration from it.