Locale Routing
How URL-based locale detection and routing works.
Raypx uses URL-based locale routing where the locale code appears as the first segment of every user-facing path. This approach ensures each language has its own distinct URL, which is beneficial for SEO and sharing.
URL Structure
/{locale}/{page-path}Examples:
/en-US -- Home page (English)
/en-US/dashboard -- Dashboard (English)
/zh-CN -- Home page (Chinese)
/zh-CN/dashboard -- Dashboard (Chinese)Ignored Paths
Certain path prefixes skip locale processing entirely:
| Prefix | Description |
|---|---|
/api | API endpoints (e.g., /api/rpc, /api/auth) |
/__ | Internal utilities (e.g., TanStack Start internals) |
/llms | LLM.txt and related files |
Paths containing a dot (.) in the filename are also ignored (e.g., /favicon.ico, /robots.txt).
Router Rewrite
Locale handling is implemented in src/router.tsx using TanStack Router's route configuration. The router rewrites URLs by stripping or adding the locale prefix:
deLocalizeUrl
Strips the locale prefix from a URL so the router can match against the actual page path:
import { deLocalizeUrl } from "@raypx/i18n"
deLocalizeUrl(new URL("http://localhost/en-US/dashboard"))
// => URL with pathname "/dashboard"localizeUrl
Ensures a URL has the appropriate locale prefix:
import { localizeUrl } from "@raypx/i18n"
localizeUrl(new URL("http://localhost/dashboard"), "en-US")
// => URL with pathname "/en-US/dashboard"For ignored paths, localizeUrl returns the URL unchanged.
Middleware
The middleware in src/start.ts handles locale detection and redirects for incoming requests:
- The request URL is parsed and the locale is extracted from the first path segment.
- If the path is in the ignored list, processing is skipped.
- If the path has no locale prefix and is not ignored, the user is redirected to the locale-prefixed version (using the default locale or the cookie value).
Redirect Behavior
- Paths without a locale prefix that are not in the ignore list are redirected to the same path with the locale prefix added.
- The locale prefix is determined by the
raypx-localecookie, oren-USif no cookie is set. - Redirects use the same HTTP method and preserve query parameters.
Server Functions Locale Preservation
When server functions are called, the locale context is preserved through the request headers. The resolveRequestLocale() function in @raypx/i18n extracts the locale from the request:
import { resolveRequestLocale } from "@raypx/i18n"
export async function myServerFunction(request: Request) {
const locale = resolveRequestLocale(request)
const messages = getMessages(locale)
// ...
}Server function requests (those hitting /api or containing the server function marker) skip i18n middleware to avoid unnecessary overhead.
Client-Side Navigation
On the client side, getCurrentLocale() reads the locale from the current URL path or falls back to the cookie:
import { getCurrentLocale, switchLocalePath } from "@raypx/i18n"
// Get current locale
const locale = getCurrentLocale() // "en-US" or "zh-CN"
// Generate a path for switching locale
const newPath = switchLocalePath("/en-US/dashboard", "zh-CN")
// => "/zh-CN/dashboard"