@secundus-studio/stift-tanstack-router-plugin
v0.4.32
Published
TanStack Router integration: rewrite.input/output, shouldRedirect, persist-vs-infer locale adapters.
Maintainers
Readme
@secundus-studio/stift-tanstack-router-plugin
TanStack Router integration for @secundus-studio/stift-core: strategy-driven URL
localization plus the persist/infer locale-adapter split.
Install
pnpm add @secundus-studio/stift-tanstack-router-pluginTwo layers
1. createUrlRewriter(config) — URL math
Built on the headless core's URL helpers (tested once, in core). Slot the
first two into TanStack Router's rewrite.input / rewrite.output, and call
shouldRedirect from a beforeLoad:
import { createUrlRewriter } from '@secundus-studio/stift-tanstack-router-plugin'
const rewriter = createUrlRewriter({
url: { strategy: 'path-prefix' },
supported: ['en', 'fr'],
default: 'en',
})
rewriter.localizeUrl('/docs/setup', 'fr') // '/fr/docs/setup'
rewriter.localizeUrl('/docs/setup', 'en') // '/docs/setup' (default = unprefixed)
rewriter.deLocalizeUrl('/fr/docs/setup') // '/docs/setup'
rewriter.parseUrlLocale('/fr/docs/setup') // { locale: 'fr', source: 'path-prefix' }
rewriter.shouldRedirect({ url: '/fr/docs/setup', preferredLocale: 'en' })
// { redirectUrl: '/docs/setup' } — throw redirect({ href }) in beforeLoadStrategies: 'path-prefix' | 'subdomain' | 'domain' | 'query-param'. All
follow an as-needed policy — the default locale is expressed by nothing, so
deLocalizeUrl(localizeUrl(url, default)) is the identity and rewrite
round-trips cleanly.
2. createLocaleRuntime(config) — persist vs infer
A locale persist adapter stores an explicit user choice (cookie, localStorage) and is written on every switch. An infer adapter is a first-visit-only guess (Accept-Language) — a browser header can never silently override a stored preference.
import { acceptLanguage, cookiePersister, createLocaleRuntime, localStoragePersister } from '@secundus-studio/stift-tanstack-router-plugin'
export const localeRuntime = createLocaleRuntime({
supported: ['en', 'fr'],
default: 'en',
url: { strategy: 'path-prefix' },
persist: [localStoragePersister()], // default key `__stift_locale` — shared with the Stift state backend
infer: [acceptLanguage()],
})
// Resolve for the current request/visit:
const locale = await localeRuntime.getLocale(
{ request: { headers: { 'accept-language': 'fr' }, url } },
{ firstVisit: true },
)
// Record an explicit switch (writes every persist adapter):
await localeRuntime.setLocale('fr')
// Re-exports for TanStack Router rewrite hooks:
localeRuntime.localizeUrl / deLocalizeUrl / parseUrlLocale / shouldRedirect
// SSR cookie sync from persist adapters that support it:
localeRuntime.serializePersist(locale) // Set-Cookie fragmentsResolution order in getLocale: URL locale (authoritative) → persist chain →
infer chain (only when firstVisit) → configured default.
Adapters
| Export | Kind | Notes |
|---|---|---|
| localStoragePersister({ key?, storage? }) | persist | Pure client/SPA persistence |
| cookiePersister({ name?, maxAge?, document? }) | persist | Client read/write; serialize(locale) yields the Set-Cookie fragment for SSR |
| acceptLanguage() | infer | Best-match Accept-Language against supported |
Custom adapters implement PersistAdapter / InferAdapter directly —
read/write receive an AdapterContext (request, cookies, plus
supported/default for validation).
Types
All public types are re-exported: LocaleRuntime, LocaleRuntimeConfig,
UrlRewriter, UrlRewriterConfig, PersistAdapter, InferAdapter,
AdapterContext, plus LocaleTag, UrlDetectionConfig, UrlLocaleSource
from core.
