Files
skills/docs/localization/i18n/react-i18n/src/registry/use-registry.md
2026-05-29 08:20:56 +01:00

3.2 KiB

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

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:

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.