Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,122 @@
# `<I18nSuspense>`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`<I18nSuspense>` is a thin convenience wrapper around React's built-in
`<Suspense>` boundary. It gives a translation-aware default fallback
that announces loading state to assistive technology via
`aria-live="polite"`, increments the
`react_i18n.suspense.fallback.total` OTel counter every time the
fallback renders, and resolves the fallback label through the active
catalog so consumers can localise the loading announcement without
forking the component. The wrapper exists so consumers do not need to
re-implement the same a11y + telemetry boilerplate around every
namespace boundary.
## Features
- Default fallback with `role="status"`, `aria-live="polite"`, and
`aria-busy="true"` for screen-reader-friendly loading state
- Visually hidden loading label so the announcement does not push DOM
layout around
- Counter `react_i18n.suspense.fallback.total{ namespace, locale }`
records every fallback render
- Loading label resolves from the active catalog with the bundled
English string (`react-i18n.suspense.loading`) as the fallback
- Custom `fallback` prop bypasses the default entirely (consumer-owned
spinner, skeleton, or loading screen)
- Zero hardcoded user-facing strings — every visible string flows
through a translation key
## Usage: Basic Example
Wrap a subtree that may suspend during a backend-driven namespace load:
```tsx
import { I18nSuspense } from "@suaveplan/react-i18n/components/i18n-suspense";
<I18nSuspense namespace="auth">
<LoginForm />
</I18nSuspense>;
```
When `LoginForm` reads from a not-yet-loaded namespace and `useTranslation`
throws the preload promise, the wrapper renders the default fallback,
records the metric sample, and re-renders the children once the load
resolves.
## Usage: Advanced Example
Custom fallback + per-namespace telemetry segmentation:
```tsx
import { I18nSuspense } from "@suaveplan/react-i18n/components/i18n-suspense";
<I18nSuspense
namespace="checkout"
fallback={
<div role="status" aria-live="polite" aria-busy="true">
<Spinner />
<span>{t("checkout.loading")}</span>
</div>
}
>
<CheckoutFlow />
</I18nSuspense>;
```
Consumers can override the bundled loading label by adding the
`react-i18n.suspense.loading` key to their catalog:
```ts
const i18n = createI18n({
defaultLocale: "de-DE",
messages: {
"de-DE": {
"react-i18n": {
suspense: { loading: "Übersetzungen werden geladen…" },
},
},
},
});
```
The default fallback will pick up the override automatically.
## API Reference
### `I18nSuspense(props: I18nSuspenseProps): ReactElement`
Props:
- `fallback?: ReactNode` — replaces the default screen-reader-friendly
fallback element. When omitted, the wrapper renders its built-in
`aria-live="polite"` div.
- `namespace?: string` — namespace label attached to the
`react_i18n.suspense.fallback.total` counter sample. Default: `""`.
- `children: ReactNode` — subtree wrapped by the underlying
`<Suspense>` boundary.
## Implementation Notes
- The default-fallback element is a child component (`DefaultFallback`)
rather than an inline `<div>` so the `useEffect` that records the
metric runs only when the fallback is actually rendered (Suspense
defers child rendering, but the fallback always renders fresh on each
suspend cycle).
- A per-instance `useRef<boolean>` guards against React's Strict-Mode
double-invoke so the counter records exactly once per logical
fallback render.
- The visually-hidden style block uses the canonical `clip:
rect(0,0,0,0)` pattern so the label is exposed to assistive tech but
invisible to sighted users.
- `resolveLoadingLabel` checks `i18n.catalog.has` before `t()` so a
missing key does not also fire a `missingKey` event for the fallback
bundle path — the bundled string is the silent default.
- `aria-busy` on the fallback container hints to assistive tech that the
region is updating; combined with `role="status"` + `aria-live="polite"`
this is the WAI-ARIA-recommended pattern for transient loading
indicators.