User locale
In plain words
Section titled “In plain words”Whatever language a user picked, everything they get back comes in that language. The browser tells the server which one it is, the server keeps it in the session, and anything sent out later — a magic-link mail, a background job — reads it from there instead of falling back to 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.