Locales

A public URL means one locale. /login is the default locale's login page; /fr/login is French. Neither depends on the browser, so both are one cache entry each, and a link to either goes where it says.

Detection, at the root only

The exception is the site root. A visitor who typed the domain has not chosen a locale, and choosing one for them means reading Accept-Language with its weights — which a rewrite's has cannot express. So the root takes a proxy, and nothing else does.

// proxy.ts
import { createLocaleProxy } from '@fairgarden/indicators/proxy'
import { indicators } from './lib/indicators'

export const proxy = createLocaleProxy(indicators)
export const config = { matcher: ['/'] }

The matcher is written out because Next reads it from the source. A visitor the default locale suits goes on to the rewrites untouched; one another locale suits is redirected to /fr, and from there every link carries the locale. The redirect is temporary and carries Vary: Accept-Language, Cookie; the page a default-locale visitor gets is Next's own response, and a cache in front has to be told about cookies and headers itself.

The negotiation is small on purpose: ranges in order of weight, an exact match first, then the same language — en-GB finds en-US when that is all there is, and en finds it too — and * or no match means the default.

A chosen locale

A user who picked a locale should not be re-detected every time they land on the root. So a choice is remembered in a cookie — locale, unless localeCookie names another or is false — and the proxy reads it before the header. Two things write it: a Link with a locale prop, when it is followed, and useSetLocale, which also moves to the same page in the new locale.

<Link href="/" locale="es">Español</Link>

const setLocale = useSetLocale()
setLocale('es')   // cookie locale=es, then /es/<this page>

Detection never writes it. The proxy may well select a locale from Accept-Language — that is what the redirect is — but what the browser asks for is an indicator, acted on for one visit; only a choice the user makes is a preference, and only that is kept. The same goes for a link that crosses locales without being a choice: remember={false} keeps it from being taken for one.

The cookie is consulted nowhere but the root — not by the rewrites, not by the links — because /login has to stay the default locale's page whoever asks for it. The locale a user chose lives in the URLs they follow, and that is where a shared link keeps it.

The default locale's prefix

localePrefix: 'as-needed', the default, keeps the default locale out of the URL: /login, and /en/login redirects to it permanently. 'always' prefixes every locale, and an unprefixed path redirects to the default locale instead. The root is left to the proxy either way, and with 'always' the proxy redirects there for the default locale too.

Links

The Link from createNavigation reads the page's locale from its route params and prefixes hrefs accordingly, so <Link href="/settings"> is /settings on an English page and /fr/settings on a French one. To link across, <Link href="/" locale="fr">. A path that already starts with a locale, or with an excluded segment such as /api, is left alone.