Browse docs

React

Setup & Boot Modes

ReactVanilla

React adapter for @secundus-studio/stift-core@0.4.0. Core stays headless; this package is the useSyncExternalStore-backed surface (via @tanstack/react-store) on top of the same store, pack manager, and config the vanilla API reads — every hook is a thin wrapper over an existing headless method.

TanStack-style unified surface: @secundus-studio/stift-react@0.4.0 re-exports @secundus-studio/stift-core@0.4.0, so a React app installs this one package and imports everything from it — createStift, the Stift type, RegisteredLocale, PersisterAdapter, hooks, providers, all of it.

pnpm add @secundus-studio/stift-react@0.4.0 @secundus-studio/stift-vite-plugin@0.4.0

Provider

import { createStift, StiftProvider, BootFallback, useTranslation } from '@secundus-studio/stift-react'
import { createStiftRuntime } from 'virtual:stift/runtime'

const Stift = createStift({ ...createStiftRuntime(), persister })
const locale = await Stift.detectLocale()

<StiftProvider Stift={Stift} boot={() => Stift.initialize({ locale })}>
  <BootFallback fallback={<Spinner />}>
    <App />
  </BootFallback>
</StiftProvider>

function Cart() {
  const { t, ready } = useTranslation('checkout')
  return <h1>{t('title')}</h1>
}

Instance wiring (detectors, localePersistence, persister): Quickstart. Generated artifacts: Build.

Boot modes

Three supported modes — pick one per app (or per subtree):

  • fallback (splash): boot prop + <BootFallback fallback={…}>. Renders the fallback until boot settles, then children. The fallback must not require translation. A rejected boot still releases the gate and reports through onBootError (defaults to console.error).
  • suspense (per-component): default. useTranslation(ns) suspends until its namespaces load — wrap in <Suspense>. Nothing suspends for cached or seeded namespaces.
  • none (immediate): omit boot/BootFallback, pass { suspense: false }, gate on ready. Renders instantly with raw-key fallback while loads stream in — suits hybrid client/server pages and client islands.

Boot imperatively in the client entry (as the examples do) by omitting boot and calling initialize() yourself.

SSR hydration

No SSR boot mode — the server renders translated HTML and the client adopts it:

// client
const Stift = createStift({ ...runtime, hydrate: window.__STIFT_STATE__ })
<StiftProvider Stift={Stift}> {/* no boot prop, no BootFallback */}
  <App /> {/* seeded namespaces never suspend */}
</StiftProvider>

The server renders t() results directly (@secundus-studio/stift-core/server: resolveLocale + FsJsonSource), then serializes { locale, namespaces } into the HTML (e.g. window.__STIFT_STATE__). Set <html lang> from the server-resolved locale; the client never re-detects.

Hooks by concern

The hooks are deliberately split by internationalization domain:

Hook Domain Returns Headless equivalent
useStift() i18n (system root) Stift instance createStift()
useTranslation(ns?) i18n (messages) { t, ready } (t.rich / t.markdown on t) Stift.scope / Stift.t
usePackStatus() i18n (resources) { pendingLocale, isBooting, packInstalls, getStatus } Stift.state + subscribe
useLocale() l10n (locale) { locale, direction, setLocale, context } Stift.getLocale/getDirection/setLocale
useFormatter() l10n (conventions) formatter functions only @secundus-studio/stift-format@0.4.0 / Intl
useConfig() g11n (system config) Readonly<StiftRuntimeConfig> Stift.config

The three-letter words

  • g11n (globalization) is the umbrella — i18n + l10n together. In this adapter it's the Stift instance and useConfig(): system configuration and strategy (supported locales, currencies, numbering systems, params/context declarations, memory, loading strategy, detection, security tier).
  • i18n (internationalization) is the enabling engineering: message keys, t, ICU, RTL support, detection, and the translation resources (packs). useStift, useTranslation, usePackStatus.
  • l10n (localization) is the adaptation for one locale: the active locale, switching, and national conventions (date/number formatting). useLocale, useFormatter.

A "pack" is translation files — i18n resources, not a locale concern — so usePackStatus reports pack download/install state, not a locale state.

Strict per-concern returns

Hooks never return overlapping meta:

  • useTranslation returns only { t, ready }. ready is true once every requested namespace is cached for the active locale (i18next-style flag); pair it with { suspense: false } to gate instead of suspending.
  • useLocale returns locale state only — the instance belongs to useStift(), config to useConfig().
  • useFormatter returns formatting functions only; locale/direction come from useLocale().

Runtime config

Stift.config (headless) and useConfig() (React) expose the effective runtime config — normalized from the generated virtual:stift/runtime config object plus any loose createStift overrides, shallow-frozen for the instance lifetime. security.staticKey never crosses the runtime boundary. Declaring it: stift.config.ts.

Next