Middleware
Automatic locale detection with TanStack Start middleware
Configure automatic locale detection using TanStack Start middleware.
Basic Setup
Create a middleware using the built-in helper:
import { createBetterI18nMiddleware } from "@better-i18n/use-intl/middleware"
import { i18nConfig } from "../i18n.config"
export const i18nMiddleware = createBetterI18nMiddleware({
project: i18nConfig.project,
defaultLocale: i18nConfig.defaultLocale,
}) Register Middleware
Add the middleware to your TanStack Start configuration:
import { createStart } from "@tanstack/react-start/server"
import { i18nMiddleware } from "./middleware/i18n"
export default createStart({
middleware: [i18nMiddleware],
})Configuration Options
export const i18nMiddleware = createBetterI18nMiddleware({
// Required
project: "org/project",
defaultLocale: "en",
// Optional detection settings
detection: {
// Use Accept-Language header
browserLanguage: true,
// Use locale cookie
cookie: true,
// Cookie name (default: "locale")
cookieName: "locale",
// Cookie max age in seconds (default: 1 year)
cookieMaxAge: 60 * 60 * 24 * 365,
},
})Detection Order
The middleware detects locale in this order:
- URL Path -
/tr/about→tr - Cookie -
locale=tr→tr - Accept-Language Header -
tr-TR,tr;q=0.9→tr - Default - Falls back to
defaultLocale
How It Works
Request: GET /about
Headers: Accept-Language: tr-TR,tr;q=0.9,en;q=0.8
1. Check URL path → No locale prefix
2. Check cookie → No cookie
3. Check Accept-Language → "tr" detected
4. Set context.locale = "tr"
5. Set-Cookie: locale=trCookie Persistence
The middleware sets a cookie to persist the user's preference:
// First visit (Accept-Language: tr)
// → Cookie set: locale=tr; path=/; max-age=31536000
// Second visit (cookie exists)
// → Locale from cookie, no header checkDisable Cookie
To disable cookie-based persistence:
export const i18nMiddleware = createBetterI18nMiddleware({
project: "org/project",
defaultLocale: "en",
detection: {
cookie: false,
browserLanguage: true,
},
})Disable Browser Detection
To ignore Accept-Language header:
export const i18nMiddleware = createBetterI18nMiddleware({
project: "org/project",
defaultLocale: "en",
detection: {
cookie: true,
browserLanguage: false,
},
})Custom Middleware
For advanced use cases, use detectLocale from @better-i18n/use-intl/server to handle locale detection:
import { createMiddleware, getRequest } from "@tanstack/react-start/server"
import { detectLocale } from "@better-i18n/use-intl/server"
export const i18nMiddleware = createMiddleware().server(async ({ next }) => {
const request = getRequest()
const locale = detectLocale({
request,
availableLocales: ["en", "tr", "de"],
defaultLocale: "en",
})
return next({ context: { locale } })
})detectLocale handles Accept-Language header parsing, quality factor (q-value) sorting, and best-match selection — no boilerplate needed.
detectLocale Parameters
| Parameter | Description |
|---|---|
request | Server Request object — Accept-Language header is read from here |
availableLocales | List of supported locales to match against |
defaultLocale | Fallback locale when no match is found |
Server-side, request is used to read the Accept-Language header. On client-side navigation, navigator.languages is used automatically — the same detectLocale function works in both environments.
Advanced: Manual Parsing
For custom logic, you can use the lower-level utilities directly:
import { parseAcceptLanguage, matchLocale } from "@better-i18n/use-intl/server"
const languages = parseAcceptLanguage(request.headers.get("accept-language") ?? "")
// → [{ locale: "tr", quality: 1 }, { locale: "en", quality: 0.8 }]
const locale = matchLocale(languages, ["en", "tr", "de"], "en")
// → "tr"Accessing Locale in Routes
The middleware sets context.locale which is accessible in all routes:
export const Route = createRootRouteWithContext<{ locale: string }>()({
loader: async ({ context }) => {
console.log("Detected locale:", context.locale)
const messages = await getMessages({
project: "org/project",
locale: context.locale,
})
return { messages, locale: context.locale }
},
})Debugging
Enable logging to debug locale detection:
export const i18nMiddleware = createMiddleware().server(async ({ next, request }) => {
const pathLocale = new URL(request.url).pathname.split("/")[1]
const cookieLocale = getCookie(request, "locale")
const browserLocale = request.headers.get("accept-language")
console.log("Locale detection:", {
path: pathLocale,
cookie: cookieLocale,
browser: browserLocale,
url: request.url,
})
// ... rest of middleware
})Common Issues
Locale Not Persisting
Symptom: User's language preference resets on each visit.
Solution: Ensure cookie is set correctly:
detection: {
cookie: true,
cookieMaxAge: 60 * 60 * 24 * 365, // 1 year
}Wrong Locale Detected
Symptom: Browser in Turkish but app shows English.
Solution: Check detection order and cookie:
// Debug by logging
console.log({
cookie: request.headers.get("cookie"),
acceptLanguage: request.headers.get("accept-language"),
})