User locale
A user’s preferred locale travels end-to-end, from the browser to the server, into the session JWT, and into triggered mails and background jobs : so e.g. a magic-link email renders in the sender’s locale rather than a server default.
1. Browser → server (X-Locale)
Section titled “1. Browser → server (X-Locale)”- The client sends
document.documentElement.langas anX-Localeheader (orAccept-Language) on requests. - The server exposes it as an always-present
ctx.locale; handlers can answer locale-dependently and derive mail locales from it.
2. Session persistence (SessionUser.locale in the JWT)
Section titled “2. Session persistence (SessionUser.locale in the JWT)”- The user’s chosen locale is written into the JWT at login (
localeclaim) and threaded through login / invite-accept / MFA handlers. SessionUser.localesurvives cross-device and background jobs: a job running on the user’s behalf knows their locale.ctx.localefalls back from the liveX-Localesignal toSessionUser.locale, keeping a consistent locale even when a request carries no browser locale.
3. Localized magic-link mails
Section titled “3. Localized magic-link mails”All auth mails (signup, password-reset, email-verification, invite, account-unlock) render in the requester’s locale:
appUrlmay be a function(locale) => stringto build locale-aware landing paths (e.g./de/...).- Mails derive the locale from
ctx.locale/SessionUser.locale, never a hard-coded server default.
Rule of thumb
Section titled “Rule of thumb”- New outbound mails: always derive locale from
ctx.locale(fallbackSessionUser.locale), never hard-code a server default. - Use
appUrlas(locale) => stringwhen the landing path is locale-sensitive.