Locale Routing
Carry the locale in the URL (/de/blocks/button) and switch it from the built-in LocaleSwitcher. The package deliberately doesn't own routing — it gives you the onLocaleChange seam; SvelteKit's reroute hook does the rest.
Provider & SSR switches the locale in place — no URL change. That is enough for an app whose locale
is a user preference. When the locale should be addressable — shareable /de/… links, distinct pages for crawlers, a browser back-button that walks language
history — it belongs in the URL. That is a routing concern, and routing is a framework decision,
not a component one.
The package owns state, not routes
setLocale mutates the request-scoped locale and fires the provider's onLocaleChange. That callback is the seam: it is where your app
decides what a locale switch means — write a cookie, call another i18n, or (here) navigate.
The path prefix vs. query-param vs. subdomain choice, with its hreflang and canonical
implications, stays yours. This guide wires the recommended path-prefix strategy end to end.
1. Map the URL to a route
SvelteKit's reroute hook runs before handle and turns the visible
URL into the route used for matching. Strip the locale segment there, and your route tree
never needs a [lang] param — /de/blocks/button and /en/blocks/button both render src/routes/blocks/button.
reroute does not change the address bar or event.url — it only picks
the route. Because it is cached per URL, keep it pure (no I/O, no Date.now()).
2. Read the locale per request
Since event.url keeps the prefix, the root server load reads the locale straight
from it and feeds the provider — SSR and the first client render agree, with no navigator.language guess. A bare path (first visit, or a legacy unprefixed link)
has no locale to read, so redirect it to the resolveLocale choice — the single point where cookie and Accept-Language still decide.
3. Switch from the LocaleSwitcher
Nothing about the switcher changes — the built-in LocaleSwitcher calls setLocale as always. You only translate the resulting onLocaleChange into a navigation to the prefixed URL. That one handler is the whole
routing layer.
After the goto lands, the new data.locale flows into the provider as
a controlled value — but it already equals the state setLocale set, so no onLocaleChange re-fires. Locale changes triggered by a plain link (not the
switcher) do fire it, and resolve to the same URL — the target !== current guard makes
that a no-op.
4. Locale-aware links
Internal links must carry the active locale, or a click drops the visitor back to the bare path (and a redirect). Centralize the URL shape in one helper so a strategy change touches one file.
5. hreflang & canonical
Addressable locales exist for crawlers — so tell them. Emit an alternate link per
locale and a canonical for the current one. This is the payoff path-prefix routing
buys you over an in-place switch.
Variants
The recipe above always prefixes, including the default — unambiguous, but it gives up a clean
default URL. Two common alternatives, both wired through the same onLocaleChange seam:
Default locale unprefixed
Leave the base locale at the bare path and prefix only the others. Nicer URLs at the cost of
one ambiguity: a crawler can't tell an explicit en page from an undecided one, so
the canonical tag from the previous step does real work here.
Query parameter
If you don't need SEO-distinct pages, ?lang=de is the lightest option: skip reroute entirely, read url.searchParams.get('lang') in the load, and
in onLocaleChange goto the same path with the param set. Same seam, no route
tree changes — but search engines may treat the variants as one page.