# @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.