4.1 KiB
4.1 KiB
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 <TranslationProvider>'s catalog
merging or via the recipe factory's per-recipe labels prop.
Features
- Flat-key bundles addressable by
react-forms.<recipe>.<key> - 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.tsso 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 byscripts/check-locale-completeness.ts
Usage: Basic Example
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:
import { reactFormsBundles } from "@suaveplan/react-forms";
import type { I18nInstance } from "@suaveplan/i18n/plugin";
function nestFlat(bag: Record<string, string>): Record<string, unknown> {
const out: Record<string, unknown> = {};
for (const [k, v] of Object.entries(bag)) {
const parts = k.split(".");
let cur: Record<string, unknown> = 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<string, unknown>;
}
cur[parts[parts.length - 1] as string] = v;
}
return out;
}
export async function registerReactFormsLocales(
i18n: I18nInstance
): Promise<void> {
for (const [locale, loader] of Object.entries(reactFormsBundles)) {
const bundle = await loader();
i18n.catalog.mergeMessages(
locale,
nestFlat(bundle as Record<string, string>)
);
}
}
API Reference
SupportedFormsLocale—"en-GB" | "zh-CN" | "ar-AE" | "cy-GB" | "ru-RU". The five locales the package ships translations for.reactFormsBundles—Readonly<Record<SupportedFormsLocale, () => Promise<FormsLocaleBundle>>>. 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<Record<string, string>>. Shape of a single locale's bundle.REACT_FORMS_BUNDLE_KEYS—ReadonlyArray<string>. Sorted list of every key inen-GB.jsonfor 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 generatedaugmentation.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.freezeplusReadonly<...>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. Thecheck-locale-completeness.tsCI gate fails the build if any locale drifts.
- duplicating the entry into