first commit
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user