Files
suaveplan/apps/demo/src/client/i18n/i18n-instance.ts
2026-05-27 23:29:53 +01:00

68 lines
2.5 KiB
TypeScript

/**
* Build the demo's `I18nInstance`.
*
* The instance is consumed by `<ReactUiProvider>` and any
* `@suaveplan/react-ui` primitive that calls `useTranslation()` (e.g.
* `<Button>` for icon-only-button fallback, `<Input>` for placeholder
* resolution).
*
* Locale strategy: the URL carries the language subtag only (`/en/home`,
* `/zh/home`, etc.). The chassis bundles in `@suaveplan/react-ui` are
* keyed by full BCP47 — `en-GB`, `zh-CN`, `ar-AE`, `cy-GB`, `ru-RU`. We
* therefore canonicalise each demo base locale to its closest BCP47
* variant and use THAT as the i18n locale. The demo's typed `Messages`
* catalog continues to be consumed via `useLocale()` keyed by base, so
* the demo never depends on react-i18n's `t(key)` for its own copy.
*
* The chassis bundles themselves are auto-registered by
* `<ReactUiProvider>` (its internal `ReactUiBundleLoader` effect imports
* the matching JSON and merges it into the catalog on mount / language
* change), so this module deliberately does NOT preload them.
*/
import { createI18n, type I18nInstance } from "@suaveplan/i18n/plugin";
import type { BaseLocale } from "./locales.js";
export type DemoBcp47 = "en-GB" | "zh-CN" | "ar-AE" | "cy-GB" | "ru-RU";
/** Map URL base locale -> canonical BCP47 variant fed to the i18n core. */
export const BASE_TO_BCP47: Readonly<Record<BaseLocale, DemoBcp47>> = {
en: "en-GB",
zh: "zh-CN",
ar: "ar-AE",
cy: "cy-GB",
ru: "ru-RU",
};
/**
* Singleton i18n instance for the demo. Created lazily so the i18n core
* (which is module-side-effect free) does not run during static module
* graph evaluation in tests or SSR snapshots.
*/
let cached: I18nInstance | undefined;
export function getDemoI18n(): I18nInstance {
if (cached !== undefined) return cached;
// Use the default `string` generic so the cached singleton matches
// `<ReactUiProvider>`'s `i18n: I18nInstance` (without the
// narrowed locale generic).
const instance: I18nInstance = createI18n({
defaultLocale: BASE_TO_BCP47.en as string,
fallbackLocale: BASE_TO_BCP47.en as string,
});
cached = instance;
return instance;
}
/**
* Drive the i18n instance to the BCP47 variant matching the URL base.
* Safe to call from a `useEffect` — `changeLanguage(...)` short-circuits
* when the locale already matches.
*/
export async function syncDemoLocale(base: BaseLocale): Promise<void> {
const i18n = getDemoI18n();
const target = BASE_TO_BCP47[base];
if (i18n.locale !== target) {
await i18n.changeLanguage(target);
}
}