first commit
This commit is contained in:
72
docs/ui/forms/react-forms/src/locales/augmentation.md
Normal file
72
docs/ui/forms/react-forms/src/locales/augmentation.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# `augmentation` — `ContributedTranslationKeys` registration
|
||||
|
||||
## Purpose
|
||||
|
||||
Side-effect-only TypeScript module that augments
|
||||
`@suaveplan/react-i18n`'s `ContributedTranslationKeys` interface with
|
||||
every key shipped by `@suaveplan/react-forms`. The augmentation runs
|
||||
purely in TypeScript's type space — there is no runtime value to
|
||||
import — but the file is included as a side-effect import from
|
||||
`src/locales/locales.ts` so consumers' `tsconfig` picks up the
|
||||
declaration when they `import "@suaveplan/react-forms"`.
|
||||
|
||||
The augmentation widens the branded `TranslationKey` union so a
|
||||
recipe's `next` button label is statically known to be a registered
|
||||
key. Misuse (`labels: { next: "typo" }`) is a TypeScript error before
|
||||
the form ever mounts.
|
||||
|
||||
## Features
|
||||
|
||||
- Pure type-space augmentation — zero bytes of JavaScript runtime cost.
|
||||
- Mirrors the keys in the generated `augmentation.d.ts` exactly so
|
||||
side-effect import paths and declaration paths stay consistent.
|
||||
- Imported as a side effect from `src/locales/locales.ts` so the
|
||||
package barrel carries the augmentation transitively.
|
||||
- Empty runtime export keeps tooling that strips type-only files from
|
||||
side-effect chains honest.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
The augmentation is automatically applied when the package is imported.
|
||||
No explicit usage is required.
|
||||
|
||||
```ts
|
||||
import "@suaveplan/react-forms";
|
||||
import type { TranslationKey } from "@suaveplan/react-i18n";
|
||||
|
||||
const k: TranslationKey = "react-forms.wizard.next"; // ✅ typechecks
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
If the consumer is building a server-side helper that does not import
|
||||
the package barrel but still wants the augmentation widened, pull the
|
||||
augmentation alone:
|
||||
|
||||
```ts
|
||||
import "@suaveplan/react-forms/locales/augmentation";
|
||||
import type { TranslationKey } from "@suaveplan/react-i18n";
|
||||
|
||||
declare const fromConfig: string;
|
||||
const key = fromConfig as TranslationKey;
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
This module exports a single empty object literal (`export {}`) so the
|
||||
TypeScript compiler treats it as a module and applies the
|
||||
`declare module` augmentation. There are no runtime exports.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The file MUST stay in sync with `augmentation.d.ts`; both are
|
||||
generated from `en-GB.json` by `bun
|
||||
packages/react/react-i18n/scripts/generate-translation-types.ts
|
||||
packages/react/react-forms --package-name @suaveplan/react-forms`.
|
||||
- Two files exist (one `.ts`, one `.d.ts`) so consumers' bundlers do
|
||||
not strip the runtime side-effect even when declaration-only files
|
||||
are filtered out by aggressive tree-shaking.
|
||||
- The unique-symbol brand on `ContributedTranslationKeys` ensures the
|
||||
augmentation surface stays mergeable across packages — never declare
|
||||
a `type ContributedTranslationKeys = { ... }`; it MUST stay an
|
||||
`interface`.
|
||||
105
docs/ui/forms/react-forms/src/locales/locales.md
Normal file
105
docs/ui/forms/react-forms/src/locales/locales.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# Locales
|
||||
|
||||
Bundled translation strings shipped with `@suaveplan/react-forms`.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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
|
||||
|
||||
```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<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.
|
||||
76
docs/ui/forms/react-forms/src/locales/translation-keys.md
Normal file
76
docs/ui/forms/react-forms/src/locales/translation-keys.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# `translation-keys` — codegen union of contributed keys
|
||||
|
||||
## Purpose
|
||||
|
||||
`translation-keys.ts` is the codegen-emitted string-literal union of
|
||||
every translation key contributed by `@suaveplan/react-forms`. It is
|
||||
regenerated from `src/locales/en-GB.json` by `bun
|
||||
packages/react/react-i18n/scripts/generate-translation-types.ts
|
||||
packages/react/react-forms --package-name @suaveplan/react-forms` and
|
||||
committed verbatim so consumers and CI can statically reference the
|
||||
union without re-running the codegen.
|
||||
|
||||
The union is useful for non-React consumers (universal helpers,
|
||||
server-side validators) that want a typed reference to the
|
||||
forms-contributed keys without taking on the full
|
||||
`@suaveplan/react-i18n` `ContributedTranslationKeys` augmentation
|
||||
pathway. React consumers should prefer the branded `TranslationKey`
|
||||
type from `@suaveplan/react-i18n` instead — that type widens
|
||||
automatically as more packages augment the interface and is the type
|
||||
the recipe and factory props use.
|
||||
|
||||
## Features
|
||||
|
||||
- Single-purpose: re-declares every key in `en-GB.json` as a string-
|
||||
literal union.
|
||||
- Sorted alphabetically via `localeCompare("en")` for diff stability.
|
||||
- Generated comment header documents the regeneration command so the
|
||||
file does not drift on hand-edit.
|
||||
- Side-effect-free — no runtime code, no imports from runtime packages.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import type { ReactFormsTranslationKey } from "@suaveplan/react-forms";
|
||||
|
||||
declare const key: ReactFormsTranslationKey;
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import type { ReactFormsTranslationKey } from "@suaveplan/react-forms";
|
||||
|
||||
const RECIPE_LABELS: Record<string, ReactFormsTranslationKey> = {
|
||||
next: "react-forms.wizard.next",
|
||||
back: "react-forms.wizard.back",
|
||||
submit: "react-forms.wizard.submit",
|
||||
add: "react-forms.repeating.add",
|
||||
remove: "react-forms.repeating.remove",
|
||||
loading: "react-forms.asyncSearch.loading",
|
||||
error: "react-forms.asyncSearch.error",
|
||||
};
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `ReactFormsTranslationKey`
|
||||
|
||||
String-literal union of every (flattened) key in
|
||||
`src/locales/en-GB.json`. Sorted alphabetically; deterministic across
|
||||
regeneration runs.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The file is regenerated by codegen — never hand-edit. Re-run `bun
|
||||
packages/react/react-i18n/scripts/generate-translation-types.ts
|
||||
packages/react/react-forms --package-name @suaveplan/react-forms`
|
||||
after every change to `en-GB.json` and commit the regenerated file.
|
||||
- The sibling `augmentation.d.ts` (also generated by the same codegen)
|
||||
extends the runtime-typed `ContributedTranslationKeys` interface that
|
||||
`TranslationKey` reads. The two files always carry the same set of
|
||||
keys; the union here is a convenience export for non-React consumers,
|
||||
the augmentation is the default type integration.
|
||||
- The output ordering uses `localeCompare("en")` so the union is byte-
|
||||
identical for identical input regardless of `en-GB.json`'s in-file
|
||||
key insertion order.
|
||||
Reference in New Issue
Block a user