2026-07-29 21:23:00 +08:00
|
|
|
# @workspace/i18n
|
|
|
|
|
|
2026-07-30 14:47:42 +08:00
|
|
|
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.
|
2026-07-29 21:23:00 +08:00
|
|
|
|
|
|
|
|
## 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",
|
2026-07-30 14:47:42 +08:00
|
|
|
"catalogSources": [
|
|
|
|
|
"@workspace/ui/locales/{locale}",
|
|
|
|
|
"@workspace/blocks/navigation/locales/{locale}"
|
|
|
|
|
],
|
|
|
|
|
"include": [
|
|
|
|
|
"src",
|
|
|
|
|
"../../packages/ui/src",
|
|
|
|
|
"../../packages/blocks/src/blocks/navigation"
|
|
|
|
|
],
|
2026-07-29 21:23:00 +08:00
|
|
|
"exclude": ["**/*.test.{ts,tsx}"]
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2026-07-30 14:47:42 +08:00
|
|
|
`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`.
|
2026-07-29 21:23:00 +08:00
|
|
|
|
2026-07-30 14:47:42 +08:00
|
|
|
`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.
|
2026-07-29 21:23:00 +08:00
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
2026-07-30 14:47:42 +08:00
|
|
|
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.
|
2026-07-29 21:23:00 +08:00
|
|
|
|
|
|
|
|
```tsx
|
2026-07-30 14:47:42 +08:00
|
|
|
import { use } from "react"
|
|
|
|
|
import { loadMessageCatalog } from "@workspace/i18n/catalogs"
|
2026-07-29 21:23:00 +08:00
|
|
|
import {
|
|
|
|
|
I18nProvider,
|
|
|
|
|
Translate,
|
|
|
|
|
useLocale,
|
|
|
|
|
useLocales,
|
|
|
|
|
useMessage,
|
|
|
|
|
useTranslate,
|
|
|
|
|
} from "@workspace/i18n"
|
|
|
|
|
import { I18nDevtool } from "@workspace/i18n/devtool"
|
|
|
|
|
|
2026-07-30 14:47:42 +08:00
|
|
|
function Root({
|
|
|
|
|
children,
|
|
|
|
|
locale,
|
|
|
|
|
}: React.PropsWithChildren<{ locale: string }>) {
|
|
|
|
|
const messages = use(loadMessageCatalog(locale))
|
|
|
|
|
|
2026-07-29 21:23:00 +08:00
|
|
|
return (
|
|
|
|
|
<I18nProvider
|
|
|
|
|
locale={locale}
|
|
|
|
|
locales={[
|
|
|
|
|
{ locale: "en", label: "English" },
|
|
|
|
|
{ locale: "zh-Hans", label: "简体中文" },
|
|
|
|
|
]}
|
2026-07-30 14:47:42 +08:00
|
|
|
catalogs={{ [locale]: messages }}
|
2026-07-29 21:23:00 +08:00
|
|
|
>
|
|
|
|
|
{children}
|
|
|
|
|
{import.meta.env.DEV && <I18nDevtool dark={"ui\\:dark"} />}
|
|
|
|
|
</I18nProvider>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Greeting() {
|
|
|
|
|
const locale = useLocale()
|
|
|
|
|
const locales = useLocales()
|
|
|
|
|
const title = useMessage("dashboard.title")
|
|
|
|
|
const translate = useTranslate()
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<Translate
|
|
|
|
|
id="welcome"
|
|
|
|
|
message="Welcome, {name}"
|
|
|
|
|
values={{ name: "Ada" }}
|
|
|
|
|
/>
|
|
|
|
|
<span>{title}</span>
|
|
|
|
|
</>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2026-07-30 14:47:42 +08:00
|
|
|
`@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/<locale>-<content-hash>.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.
|
|
|
|
|
|
2026-07-29 21:23:00 +08:00
|
|
|
`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`.
|
|
|
|
|
|
2026-07-30 14:47:42 +08:00
|
|
|
The generated in-memory Lingui configuration points
|
|
|
|
|
`runtimeConfigModule.Trans` at `Translate`, so explicit
|
|
|
|
|
`<Translate id="…" message="…" />` usages are found by `lingui extract`.
|
2026-07-29 21:23:00 +08:00
|
|
|
|
|
|
|
|
## 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
|
|
|
|
|
<I18nProvider locale="zh-Hans" locales={["en", "zh-Hans"]}>
|
|
|
|
|
<App />
|
|
|
|
|
{import.meta.env.DEV && <I18nDevtool dark={"ui\\:dark"} />}
|
|
|
|
|
</I18nProvider>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
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
|
2026-07-30 14:47:42 +08:00
|
|
|
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
|
|
|
|
|
<I18nDevtool locale="zh-Hans" dark={"ui\\:dark"} />
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
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.
|
2026-07-29 21:23:00 +08:00
|
|
|
|
|
|
|
|
Lower-level `MessagePanel`, `MessageRepositoryProvider`, and repository types
|
|
|
|
|
remain available from `@workspace/i18n/devtool`.
|
|
|
|
|
|
2026-07-30 14:47:42 +08:00
|
|
|
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.
|
|
|
|
|
|
2026-07-29 21:23:00 +08:00
|
|
|
## 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 <locale>` 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 <path>` when invoked outside the application.
|
|
|
|
|
|
|
|
|
|
## Catalog workflow
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
cd apps/web
|
|
|
|
|
bun run i18n:extract
|
|
|
|
|
bun run i18n:compile
|
|
|
|
|
```
|
|
|
|
|
|
2026-07-30 14:47:42 +08:00
|
|
|
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.
|