# Locales Bundled translation strings shipped with `@suaveplan/react-forms`. _(no submodules)_ ## Purpose The package owns a small set of user-facing strings used by the recipes (`Next`, `Back`, `Submit`, `Add`, `Remove`, `Loading…`, `Failed to load`). The strings ship as flat-key JSON bundles in five locales (`en-GB`, `zh-CN`, `ar-AE`, `cy-GB`, `ru-RU`) and are merged into the consumer's `I18nInstance.catalog` at provider mount. Consumers MAY override any key via ``'s catalog merging or via the recipe factory's per-recipe `labels` prop. ## Features - Flat-key bundles addressable by `react-forms..` - Lazy loaders (one dynamic import per locale) so only the active locale's JSON ships in the consumer's main bundle - Type-narrowed via the generated `augmentation.d.ts` so misuse fails at compile time - Sorted key list (`REACT_FORMS_BUNDLE_KEYS`) for parity tests - One source of truth (`en-GB.json`); other locales are kept parity- complete by `scripts/check-locale-completeness.ts` ## Usage: Basic Example ```ts import { REACT_FORMS_BUNDLE_KEYS, reactFormsBundles, } from "@suaveplan/react-forms"; const en = await reactFormsBundles["en-GB"](); console.log(Object.keys(en).length); // 7 console.log(REACT_FORMS_BUNDLE_KEYS[0]); // "react-forms.asyncSearch.error" ``` ## Usage: Advanced Example Wire the bundles into the active `I18nInstance` so recipe defaults resolve correctly. The pattern mirrors `react-ui`'s `ReactUiProvider` flat-to-nested merging step: ```ts import { reactFormsBundles } from "@suaveplan/react-forms"; import type { I18nInstance } from "@suaveplan/i18n/plugin"; function nestFlat(bag: Record): Record { const out: Record = {}; for (const [k, v] of Object.entries(bag)) { const parts = k.split("."); let cur: Record = out; for (let i = 0; i < parts.length - 1; i += 1) { const seg = parts[i] as string; if (typeof cur[seg] !== "object" || cur[seg] === null) { cur[seg] = {}; } cur = cur[seg] as Record; } cur[parts[parts.length - 1] as string] = v; } return out; } export async function registerReactFormsLocales( i18n: I18nInstance ): Promise { for (const [locale, loader] of Object.entries(reactFormsBundles)) { const bundle = await loader(); i18n.catalog.mergeMessages( locale, nestFlat(bundle as Record) ); } } ``` ## API Reference - `SupportedFormsLocale` — `"en-GB" | "zh-CN" | "ar-AE" | "cy-GB" | "ru-RU"`. The five locales the package ships translations for. - `reactFormsBundles` — `Readonly Promise>>`. Frozen map of locale → async loader returning the flat key/value bag for that locale. Loaders are lazy so consumers only chunk the active locale. - `FormsLocaleBundle` — `Readonly>`. Shape of a single locale's bundle. - `REACT_FORMS_BUNDLE_KEYS` — `ReadonlyArray`. Sorted list of every key in `en-GB.json` for parity tests. ## Implementation Notes - The bundles are imported via dynamic JSON imports so the bundler can tree-shake unused locales when consumers split per language route. - The augmentation lives in two files (`augmentation.ts` + the generated `augmentation.d.ts`) so a side-effect runtime import carries the augmentation through the package barrel even when declaration-only files are pruned by aggressive bundlers. - `Object.freeze` plus `Readonly<...>` annotations prevent accidental mutation by consumers — the bundles MUST be treated as immutable. - Adding a key requires updating ALL FIVE locale bundles + re-running the codegen (`bun packages/react/react-i18n/scripts/generate-translation-types.ts packages/react/react-forms --package-name @suaveplan/react-forms`) - duplicating the entry into `augmentation.ts`. The `check-locale-completeness.ts` CI gate fails the build if any locale drifts.