docs(ui): expand bilingual component guides

This commit is contained in:
Maofeng
2026-09-21 17:27:45 +08:00
parent fb985edb20
commit 4c61e79d0d
22 changed files with 1636 additions and 192 deletions
+76 -9
View File
@@ -1,31 +1,98 @@
---
title: Add responsive behavior
description: Use shared breakpoint and interaction hooks when CSS alone cannot express the behavior.
title: Responsive and interaction hooks
description: Synchronize breakpoints when behavior must follow the viewport and add ripple feedback to custom controls.
order: 20
toc:
- id: breakpoints
title: Breakpoints
- id: breakpoint-contract
title: Breakpoint contract
- id: reading-breakpoints
title: Read breakpoints
- id: root-classes
title: Root classes
- id: server-rendering
title: Server rendering
- id: ripple
title: Ripple interaction
---
## Breakpoints {#breakpoints}
## Breakpoint contract {#breakpoint-contract}
The shared breakpoint splits the viewport into three ranges:
| Value | Range |
| --------- | -------------------- |
| `mobile` | Below 768px |
| `tablet` | 768px through 1023px |
| `desktop` | 1024px and above |
Prefer CSS for visual changes. Read a breakpoint during rendering only when mounted content, interaction behavior, or data volume must change.
## Read breakpoints {#reading-breakpoints}
```tsx
import { useBreakpoint, useIsMobile } from "@workspace/ui/hooks/use-breakpoint"
import {
useBreakpoint,
useIsDesktop,
useIsMobile,
useIsTablet,
useOnBreakpoint,
} from "@workspace/ui/hooks/use-breakpoint"
const breakpoint = useBreakpoint()
const isMobile = useIsMobile()
```
Prefer CSS responsive variants for visual changes. Use these hooks only when component behavior or mounted content must change.
Every hook subscribes to the same pair of `matchMedia` queries through `useSyncExternalStore`. `useOnBreakpoint(listener)` runs only after the breakpoint changes; it does not report the initial value as a change.
## Root classes {#root-classes}
After mounting `BreakpointReactor`, the document root has exactly one of `.ui:mobile`, `.ui:tablet`, or `.ui:desktop`:
```tsx
import { BreakpointReactor } from "@workspace/ui/hooks/use-breakpoint"
;<>
<BreakpointReactor />
<App />
</>
```
`globals.css` registers these as the custom `mobile:`, `tablet:`, and `desktop:` variants, allowing CSS and behavioral code to share the same three-range definition.
## Server rendering {#server-rendering}
The breakpoint store uses a deterministic desktop server snapshot. `getBreakpointInitializationScript` can assign the matching root class before hydration when the application needs CSS and behavioral breakpoints to agree immediately.
The server snapshot is fixed to `desktop` so SSR remains deterministic. To write the real class before hydration, place the result of `getBreakpointInitializationScript()` in an inline script in the document head:
```tsx
import { getBreakpointInitializationScript } from "@workspace/ui/hooks/use-breakpoint"
;<script
dangerouslySetInnerHTML={{ __html: getBreakpointInitializationScript() }}
/>
```
Pass the application's nonce under a strict CSP. The script only synchronizes the initial class; it does not replace `BreakpointReactor` for later viewport changes.
## Ripple interaction {#ripple}
`useRipple` returns a ref callback that adds pointer-driven feedback to an interactive element. The element should establish a positioned containing block so the generated overlay uses the correct bounds.
`Button` already uses `useRippleRef`, so ordinary buttons need no extra setup. Use the hook directly for a custom interactive surface:
```tsx
import { useRipple } from "@workspace/ui/hooks/use-ripple"
export function Tile() {
const ref = useRipple<HTMLButtonElement>({
duration: 500,
opacity: 0.2,
rippleClassName: "bg-primary",
})
return (
<button ref={ref} className="relative">
Open project
</button>
)
}
```
The element must establish a positioned containing block, normally with `relative`. The hook ignores disabled controls, `aria-disabled="true"`, and non-primary mouse buttons, and removes listeners and animation nodes when the ref changes or unmounts. Use `useRippleRef(ref, options)` when merging an external ref.
@@ -1,19 +1,29 @@
---
title: Localize UI
description: Load only the UI catalog and calendar locale required by the active language.
description: Bridge the application translator into UI components and load the active calendar locale.
order: 21
toc:
- id: catalog-source
title: Catalog source
- id: direct-imports
title: Direct imports
- id: supported-locales
title: Supported locales
- id: two-layers
title: Two responsibilities
- id: catalogs
title: Merge catalogs
- id: bridge-provider
title: Bridge the provider
- id: fallback
title: Fallback behavior
- id: direction
title: Text direction
---
## Catalog source {#catalog-source}
## Two responsibilities {#two-layers}
Add the UI locale source to the application's internationalization configuration:
`@workspace/i18n` manages the application locale, catalog loading, formatting, and switching. `@workspace/ui/components/i18n` is a small adapter that keeps UI primitives independent of an internationalization framework. It accepts a compatible translator, a language tag, and a `react-day-picker` locale.
This separation keeps the UI package reusable while allowing applications to use the full workspace internationalization runtime.
## Merge catalogs {#catalogs}
Add the UI catalog source to the application's `i18n.config.json`:
```json
{
@@ -21,16 +31,50 @@ Add the UI locale source to the application's internationalization configuration
}
```
The locales root contains metadata and types. It does not import every language catalog.
The `@workspace/ui/locales` root exports only `uiCatalogLocales`, `uiMessageIds`, and types; it does not statically import every language. The current supported set is `en-US` and `zh-Hans`.
## Direct imports {#direct-imports}
## Bridge the provider {#bridge-provider}
```ts
import { calendarLocale, messages } from "@workspace/ui/locales/en-US"
Read the translator from the current application provider and pass it to the UI provider:
```tsx
import { useLocaleDefinition, useTranslate } from "@workspace/i18n"
import {
I18nProvider as UiI18nProvider,
type CalendarLocale,
} from "@workspace/ui/components/i18n"
import { calendarLocale as enUS } from "@workspace/ui/locales/en-US"
import { calendarLocale as zhHans } from "@workspace/ui/locales/zh-Hans"
const calendarLocales: Record<string, CalendarLocale> = {
"en-US": enUS,
"zh-Hans": zhHans,
}
export function AppUiI18nProvider({ children }: React.PropsWithChildren) {
const locale = useLocaleDefinition()
const t = useTranslate()
return (
<UiI18nProvider
calendarLocale={calendarLocales[locale.locale]}
locale={locale.languageTag ?? locale.locale}
t={t}
>
{children}
</UiI18nProvider>
)
}
```
Each language entry also exports its matching `react-day-picker` locale.
`Calendar` prefers its own `locale` prop, then the provider's `calendarLocale`. Month formatting and the default `lang` use the provider language tag.
## Supported locales {#supported-locales}
## Fallback behavior {#fallback}
The UI catalogs include American English (`en-US`) and Simplified Chinese (`zh-Hans`).
Without a provider, the UI layer uses `en-US` and the English source text from each descriptor. Isolated tests and stories therefore remain usable, but production applications should bridge the provider so accessible names and visible text use the same locale.
Custom components may use `uiMessages`, `useMessage`, `useTranslate`, `LocalizedText`, `useLocale`, and `useCalendarLocale`. Product copy belongs in the application or domain package, not in the UI catalog.
## Text direction {#direction}
Translation locale and layout direction are separate concerns. When switching locales, the application should synchronize `document.documentElement.lang` and `dir`, and use `DirectionProvider` when needed. Local components use logical utilities such as `start`, `end`, `ps`, and `pe`; `components.json` also enables `rtl` generation.
@@ -0,0 +1,67 @@
---
title: Workspace adaptations
description: Record the implementation differences that must survive updates from the shadcn/ui Base UI source.
order: 22
toc:
- id: baseline
title: Upstream baseline
- id: icons-and-ripple
title: Icons and ripple
- id: localization
title: Localization
- id: popover
title: Popover extensions
- id: select
title: Select correction
- id: upgrade-checklist
title: Upgrade checklist
---
## Upstream baseline {#baseline}
The local components began from shadcn/ui's Base UI registry with the `base-vega` style. Use the upstream [component documentation](https://ui.shadcn.com/docs/components/base) and [GitHub source directory](https://github.com/shadcn-ui/ui/tree/main/apps/v4/registry/bases/base/ui) as references, but do not assume the latest upstream version is identical to the workspace snapshot.
When determining behavior, prefer the local source, then this documentation, then local `components.json`, and only then the latest upstream example.
## Icons and ripple {#icons-and-ripple}
`components/icon.tsx` maps Lucide icon names to `@icones/react` components and preserves a set of upstream-compatible export names. Existing components import through that entry point. Convert any `lucide-react` import introduced by the CLI back to the local module.
`Button` merges its incoming ref with `useRippleRef`. Preserve relative positioning, ref merging, and disabled behavior when updating Button.
## Localization {#localization}
Breadcrumb, Calendar, Carousel, Dialog, Pagination, Sheet, Sidebar, Spinner, Toast, and related components replace some hard-coded English with descriptors from `components/i18n.tsx`. When updating these files, inspect accessible labels, screen-reader-only text, and default descriptions in addition to visible UI.
Only `en-US` and `zh-Hans` catalogs are published. Adding a message requires synchronized changes to:
1. The `uiMessages` descriptor.
2. The `uiMessageIds` type list.
3. `messages` in both locale modules.
4. The component that consumes the message.
## Popover extensions {#popover}
The local `PopoverContent` adds these capabilities beyond the upstream wrapper:
- Forwarded `anchor` and `positionMethod` options.
- `showArrow`, accepting an `AnchoredSurface` configuration object.
- `viewportClassName`, separating scrollable content from the overflow-visible outer surface.
- An anchor-sized default side offset and continuous curved tail.
- `createPopoverHandle` and `PopoverHandle` exports.
Do not replace the file wholesale with upstream source. In particular, test long-content scrolling, every side, collision flips, narrow triggers, and RTL.
## Select correction {#select}
`SelectTrigger` renders `ChevronDownIcon` as a child of `SelectPrimitive.Icon` instead of passing the icon component through `render`. This correction is intentional for the current Base UI and Icones combination. After a Select update, verify that the trigger shows exactly one icon and test SSR and hydration.
## Upgrade checklist {#upgrade-checklist}
1. Review the shadcn docs and matching registry source diff before replacing anything.
2. Generate through the CLI on a temporary branch or stage files individually, preserving local public exports.
3. Convert direct icon dependencies back to `components/icon`.
4. Restore translated messages, logical-direction utilities, and local theme classes.
5. Give Button, Popover, Select, Calendar, Toast, and Sidebar focused regression coverage.
6. Run the UI typecheck, repository lint and format checks, then build a real consuming application.
7. Manually inspect light, dark, LTR, RTL, mobile, tablet, and desktop behavior.