first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,59 @@
# `useReactI18nRegistry` & `useOptionalReactI18nRegistry`
## Purpose
These hooks read the active `ReactI18nRegistry` from React context. The strict variant `useReactI18nRegistry()` throws `REACT_I18N.REGISTRY_NOT_PROVIDED` when called outside a `<RegistryProvider>` ancestor, which is the right behavior for code that genuinely requires the registry (e.g. plugin-aware components). The tolerant variant `useOptionalReactI18nRegistry()` returns `null` outside a provider, which is the right behavior for code that wants to opt into the registry when present but fall back to a non-registry implementation when absent — built-in hooks like `useNumber` use this so existing call sites without `<RegistryProvider>` keep working.
## Features
- Strict and tolerant variants with the same context source.
- Stable error code (`REACT_I18N.REGISTRY_NOT_PROVIDED`) on the strict variant for programmatic handling.
- Zero overhead — both hooks are a single `useContext` lookup.
## Usage: Basic Example
```tsx
import { useReactI18nRegistry } from "@suaveplan/react-i18n/registry";
function MyComponent() {
const registry = useReactI18nRegistry();
const formatter = registry.getFormatter<{ ms: number }, string>("duration");
return <span>{formatter?.format("en-GB", { ms: 1500 })}</span>;
}
```
## Usage: Advanced Example
The tolerant variant is the right choice for any hook or component that should work both inside and outside a `<RegistryProvider>`. The built-in hooks (`useNumber`, `useDate`, …) use it internally:
```ts
import { useOptionalReactI18nRegistry } from "@suaveplan/react-i18n/registry";
function useNumber(value: number, options?: NumberOptions): string {
const registry = useOptionalReactI18nRegistry();
if (registry) {
const plugin = registry.getFormatter<NumberArgs, string>("number");
if (plugin) return plugin.format(useLocale(), { value, ...options });
}
// Fallback path — direct call to @suaveplan/i18n-formatter-number
return formatNumber(useLocale(), value, options);
}
```
For test fixtures that mount a registry per-case, the strict hook is appropriate — a missing provider in a test is a wiring bug and should fail loudly.
## API Reference
### `useReactI18nRegistry(): ReactI18nRegistry`
Reads the registry from `RegistryContext`. Throws `ReactI18nError(REACT_I18N.REGISTRY_NOT_PROVIDED)` when no `<RegistryProvider>` is in scope.
### `useOptionalReactI18nRegistry(): ReactI18nRegistry | null`
Reads the registry from `RegistryContext`. Returns `null` when no `<RegistryProvider>` is in scope.
## Implementation Notes
- Both hooks are bound to a single module-scoped `RegistryContext` so the brand identity of the context is preserved across SSR / hot-reload / dev-bundle scenarios.
- Two `<RegistryProvider>` ancestors in the same tree result in the inner one winning — React context resolution is closest-ancestor wins. This is the desired behavior for layered apps where a sub-tree wants its own registry overlay; if you do not want that, mount only one provider.
- The tolerant variant returns `null` rather than an "empty" registry sentinel. A null check at the call site is more honest than papering over the missing-provider case with a no-op registry.