Files
skills/docs/ui/forms/react-forms/src/locales/locales.md
2026-05-29 08:20:56 +01:00

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.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

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 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.