173 lines
4.4 KiB
Markdown
173 lines
4.4 KiB
Markdown
|
|
# @workspace/i18n
|
||
|
|
|
||
|
|
Reusable Lingui runtime, Devtool, Vite plugin, and CLI. This package does not own
|
||
|
|
application locales or catalogs: every consuming application keeps its own
|
||
|
|
`i18n.config.json`, `lingui.config.ts`, and generated message files.
|
||
|
|
|
||
|
|
## 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",
|
||
|
|
"include": ["src"],
|
||
|
|
"exclude": ["**/*.test.{ts,tsx}"]
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Then expose it as a Lingui configuration:
|
||
|
|
|
||
|
|
```ts
|
||
|
|
import project from "./i18n.config.json" with { type: "json" }
|
||
|
|
import { createLinguiConfig } from "@workspace/i18n/lingui-config"
|
||
|
|
|
||
|
|
export default createLinguiConfig(project)
|
||
|
|
```
|
||
|
|
|
||
|
|
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
|
||
|
|
|
||
|
|
Compiled Lingui catalogs are passed to the provider synchronously, so the
|
||
|
|
first client render and SSR render use the same locale.
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
import { messages as en } from "@/locales/en/messages"
|
||
|
|
import { messages as zhHans } from "@/locales/zh-Hans/messages"
|
||
|
|
import {
|
||
|
|
I18nProvider,
|
||
|
|
Translate,
|
||
|
|
useLocale,
|
||
|
|
useLocales,
|
||
|
|
useMessage,
|
||
|
|
useTranslate,
|
||
|
|
} from "@workspace/i18n"
|
||
|
|
import { I18nDevtool } from "@workspace/i18n/devtool"
|
||
|
|
|
||
|
|
function Root({ children, locale }: React.PropsWithChildren<{ locale: string }>) {
|
||
|
|
return (
|
||
|
|
<I18nProvider
|
||
|
|
locale={locale}
|
||
|
|
locales={[
|
||
|
|
{ locale: "en", label: "English" },
|
||
|
|
{ locale: "zh-Hans", label: "简体中文" },
|
||
|
|
]}
|
||
|
|
catalogs={{ en, "zh-Hans": zhHans }}
|
||
|
|
>
|
||
|
|
{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>
|
||
|
|
</>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
`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 Lingui configuration points `runtimeConfigModule.Trans` at
|
||
|
|
`Translate`, so explicit `<Translate id="…" message="…" />` 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
|
||
|
|
<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
|
||
|
|
clip it.
|
||
|
|
|
||
|
|
Lower-level `MessagePanel`, `MessageRepositoryProvider`, and repository types
|
||
|
|
remain available from `@workspace/i18n/devtool`.
|
||
|
|
|
||
|
|
## 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
|
||
|
|
```
|
||
|
|
|
||
|
|
The application's `i18n.config.json` is the machine-editable source of truth.
|
||
|
|
Its `lingui.config.ts` maps that manifest to Lingui's configuration format.
|