Lazy Loading
By default a package registers all its locale bundles eagerly. Opt into code-splitting to keep non-base languages out of the initial bundle until they're activated.
When to use it
Eager registration ships every locale's strings in the initial bundle. That's simplest and
right for a handful of languages (en/de). Past that, the inactive
locales are dead weight on first paint — register them as dynamic-import loaders so only the
active language is in the initial bundle, and the rest load on activation.
Registering loaders
Pass options.loaders to createPackageI18n. The bundle in translations (typically en) stays eager as the base/fallback; each
listed locale becomes a dynamic import that Vite/Rollup splits into its own chunk.
Compile-time key parity is not checked for lazy locales — the chunk isn't
visible to the type-checker. Guard parity at runtime with validatePackageTranslations in a test.
Load lifecycle
The provider drives loading automatically:
- On mount it loads the active locale and the fallback. (Effects don't run during SSR, so a lazy non-base initial locale renders the fallback on the server and the first client paint, then re-resolves reactively once its chunk lands.)
- On switch,
setLocaletriggers the target's loader (not awaited) and flips the locale; the$derivedreads re-resolve when the chunk arrives. - If a load fails and no data exists for that
locale,
load-failed-no-fallbackis reported to the error sink — the loud signal that "the language you switched to can't be rendered."
useI18n().isLoading is true while a lazy load is in flight — wire it to
a spinner if a switch is perceptible:
Trade-offs
Eager (default)
- Simplest — no extra config.
- Compile-time key parity across all locales.
- No loading state, no flash of fallback.
- Best for a few locales (
en/de).
Lazy (opt-in)
- Smaller initial bundle — inactive locales excluded.
- Parity is a runtime/CI check, not compile-time.
- A lazy non-base initial locale flashes the fallback first.
- Worth it past a handful of locales.