# `to-i18n-instance-options.ts` ## Purpose Translates a Zod-parsed `I18nConfig` (the React-tier shape) into the universal-core `I18nInstanceOptions` accepted by `createI18n(...)` from `@suaveplan/i18n`. This is the bridge between the React Provider's config surface (where field names favour SSR semantics, e.g. `initialMessages`) and the universal core's option bag (where field names favour catalog semantics, e.g. `messages`). The function is pure and stable across calls so the Provider can wrap it in `useMemo` without churning the universal core's instance lifecycle on every render. ## Features - Single-pass field mapping with explicit conditional forwarding so `exactOptionalPropertyTypes` (the tsconfig setting) does not require sentinel `undefined`s on the output. - Renames `initialMessages` → `messages` so the React-tier name ("initial" matches the SSR-hydration story) and the core-tier name ("messages" matches the catalog story) can each be ergonomic in their own context. - Forwards every other field as-is (`fallbackLocale`, `backend`, `backends`, `saveMissing`, `plugins`). ## Usage: Basic Example ```ts import { createI18nConfig, toI18nInstanceOptions, } from "@suaveplan/react-i18n/config"; import { createI18n } from "@suaveplan/i18n"; const cfg = createI18nConfig({ defaultLocale: "en-GB", initialMessages: { "en-GB": { hello: "Hi" } }, }); const i18n = createI18n(toI18nInstanceOptions(cfg)); ``` ## Usage: Advanced Example ```ts import { useMemo } from "react"; import { createI18n, type I18nInstance } from "@suaveplan/i18n"; import { type I18nConfig, toI18nInstanceOptions, } from "@suaveplan/react-i18n/config"; function useStableInstance(config: I18nConfig): I18nInstance { return useMemo( () => createI18n(toI18nInstanceOptions(config)), [config] ); } ``` ## API Reference ### `toI18nInstanceOptions(config: I18nConfig): I18nInstanceOptions` Pure mapping. Returns a new `I18nInstanceOptions` object on every call; fields not present on the input config are omitted from the output (so the universal-core defaults take over). | Input field | Output field | Notes | | --- | --- | --- | | `defaultLocale` | `defaultLocale` | Always present (required). | | `fallbackLocale` | `fallbackLocale` | Forwarded when defined. | | `initialMessages` | `messages` | Renamed; structurally identical. | | `backend` | `backend` | Forwarded as-is (structural). | | `backends` | `backends` | Forwarded as-is (structural). | | `saveMissing` | `saveMissing` | Forwarded when defined. | | `plugins` | `plugins` | Forwarded as-is (structural). | ## Implementation Notes - Field forwarding uses explicit `if (value !== undefined)` guards rather than spread because the tsconfig sets `exactOptionalPropertyTypes: true` — explicit guards keep the output shape free of sentinel `undefined` properties that would otherwise conflict with optional fields on `I18nInstanceOptions`. - `Backend` and `I18nPlugin` casts at the conversion boundary are unavoidable: the React-tier Zod schema validates only the structural fields (`name`, `capabilities`) of these types because their full surface includes function fields Zod cannot meaningfully validate. The Provider's runtime guard is the universal core itself, which exercises the function fields when it boots the instance. - The function is pure — calling it twice with the same input yields equivalent options (deep-equal but not necessarily reference-equal). - The function does NOT call `createI18n`. The Provider does that separately so config building and instance construction can be wired to different React lifecycles (`useMemo` on the config, `useState`/`useRef` on the instance).