Browse docs

Runtime

Offline & Persistence

ReactVanilla

Packs are per-locale+namespace compiled blobs (Uint8Array), kept in an in-memory cache and optionally an async PersisterAdapter so they survive reloads without the network.

Persister

import { createBestEffortPersister } from '@secundus-studio/stift-persisters'

export const persister = await createBestEffortPersister()
// OPFS → IndexedDB → Cache Storage → memory: first supported wins, never throws.

Pin one backend instead of the chain:

Import Backend Notes
@secundus-studio/stift-persisters/opfs Origin Private FS Fastest; needs secure context
@secundus-studio/stift-persisters/idb-keyval IndexedDB Broad fallback
@secundus-studio/stift-persisters/cache-api Cache Storage Shares the browser cache quota

All implement PersisterAdapter = { get, set, del, keys } over Uint8Array — custom backends implement the same interface directly, no registration needed. Wire via createStift({ persister }). With no persister, packs are memory-only (re-fetched each boot). Stift.pack.durability reports 'durable' | 'memory' | 'none' — gate install/wipe UI on it: a memory persister looks healthy but evaporates on reload.

Install → activate lifecycle

await Stift.installLocale('ar') // fetch + cache the pack(s); returns boolean
await Stift.activateLocale('ar') // load into memory + make it the active locale
Stift.isLocaleInstalled('ar') // → boolean
const size = await Stift.getPackSize('ar') // PackSizeInfo: byte budget vs. estimate

activateLocale is the reactive switch behind useLocale().setLocale for offline-resident locales. Use getPackSize to warn before installing a large pack on metered storage. Wipes are dialect-safe (wiping ar never wipes ar-SY): Dialects.

Boot offline-first

const locale = await Stift.detectLocale() // custom (saved) > navigator > fallback
Stift.setLocale(locale)
void Stift.initialize({ locale }) // unawaited: first paint stays off the network
void Stift.pack.checkForUpdates(locale) // revalidate in the background

initialize fast-paths returning visitors: installed packs verify + activate from the persister with zero network, and the reactive packInstalls status rehydrates from disk so it reads installed from the first notification. Strategy choice: Loading Strategies.

Packs never need a service worker. Locale packs, detection, and catalog/update checks all work without one. A SW only buys app-shell precaching (a fully offline reload of the shell); if you ship one, activate new versions with skipWaiting + clients.claim() so refreshes pull freshly-published packs without a hard reload.

Wiring checklist

  • Suspense boundary: components that suspend on a namespace load (useTranslation) must sit under a <Suspense> boundary, or the first render before anything is cached drops the tree. Boot modes: Setup & Boot Modes.
  • Error surfacing: wire hooks.onError to console.error (or inline UI) so failed pack downloads show the real error instead of failing silently.
  • Reset: Stift.pack.wipePersisted({ except: locale }) clears all downloaded packs except the active locale.
  • OPFS requires a secure context (localhost fine; prod needs HTTPS); if unavailable, fall back to IndexedDB.

Next