feat(i18n): expand catalog runtime and developer tooling
- add Intl-backed locale definitions, formatters, catalog loaders, and runtime hooks\n- compile hashed production catalogs from package-scoped catalog sources\n- drive Lingui extraction and compilation directly from project JSON config\n- redesign the devtool with localized controls, draggable persistence, settings, navigation, copy actions, ANSI output, and fullscreen support\n- extend repository APIs, CLI coverage, documentation, and runtime tests
This commit is contained in:
+103
-23
@@ -1,8 +1,9 @@
|
||||
# @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.
|
||||
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
|
||||
|
||||
@@ -30,19 +31,29 @@ Create `i18n.config.json` in that application:
|
||||
"sourceLocale": "en",
|
||||
"locales": ["en", "zh-Hans"],
|
||||
"catalogPath": "src/locales/{locale}/messages",
|
||||
"include": ["src"],
|
||||
"catalogSources": [
|
||||
"@workspace/ui/locales/{locale}",
|
||||
"@workspace/blocks/navigation/locales/{locale}"
|
||||
],
|
||||
"include": [
|
||||
"src",
|
||||
"../../packages/ui/src",
|
||||
"../../packages/blocks/src/blocks/navigation"
|
||||
],
|
||||
"exclude": ["**/*.test.{ts,tsx}"]
|
||||
}
|
||||
```
|
||||
|
||||
Then expose it as a Lingui configuration:
|
||||
`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`.
|
||||
|
||||
```ts
|
||||
import project from "./i18n.config.json" with { type: "json" }
|
||||
import { createLinguiConfig } from "@workspace/i18n/lingui-config"
|
||||
|
||||
export default createLinguiConfig(project)
|
||||
```
|
||||
`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:
|
||||
|
||||
@@ -57,12 +68,14 @@ export default defineConfig({
|
||||
|
||||
## Runtime
|
||||
|
||||
Compiled Lingui catalogs are passed to the provider synchronously, so the
|
||||
first client render and SSR render use the same locale.
|
||||
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 { messages as en } from "@/locales/en/messages"
|
||||
import { messages as zhHans } from "@/locales/zh-Hans/messages"
|
||||
import { use } from "react"
|
||||
import { loadMessageCatalog } from "@workspace/i18n/catalogs"
|
||||
import {
|
||||
I18nProvider,
|
||||
Translate,
|
||||
@@ -73,7 +86,12 @@ import {
|
||||
} from "@workspace/i18n"
|
||||
import { I18nDevtool } from "@workspace/i18n/devtool"
|
||||
|
||||
function Root({ children, locale }: React.PropsWithChildren<{ locale: string }>) {
|
||||
function Root({
|
||||
children,
|
||||
locale,
|
||||
}: React.PropsWithChildren<{ locale: string }>) {
|
||||
const messages = use(loadMessageCatalog(locale))
|
||||
|
||||
return (
|
||||
<I18nProvider
|
||||
locale={locale}
|
||||
@@ -81,7 +99,7 @@ function Root({ children, locale }: React.PropsWithChildren<{ locale: string }>)
|
||||
{ locale: "en", label: "English" },
|
||||
{ locale: "zh-Hans", label: "简体中文" },
|
||||
]}
|
||||
catalogs={{ en, "zh-Hans": zhHans }}
|
||||
catalogs={{ [locale]: messages }}
|
||||
>
|
||||
{children}
|
||||
{import.meta.env.DEV && <I18nDevtool dark={"ui\\:dark"} />}
|
||||
@@ -108,13 +126,54 @@ function Greeting() {
|
||||
}
|
||||
```
|
||||
|
||||
`@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.
|
||||
|
||||
`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`.
|
||||
The generated in-memory Lingui configuration points
|
||||
`runtimeConfigModule.Trans` at `Translate`, so explicit
|
||||
`<Translate id="…" message="…" />` usages are found by `lingui extract`.
|
||||
|
||||
## Devtool
|
||||
|
||||
@@ -132,11 +191,31 @@ catalog reads, updates, extraction, and compilation.
|
||||
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.
|
||||
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.
|
||||
|
||||
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
|
||||
@@ -168,5 +247,6 @@ 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.
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user