# One URL per language

Public pages live at /de/… and /fr/… instead of ?lang=, with a leaf config for slugs, redirects for the old URLs, a first-visit redirect at / and per-language sitemap entries.

The landing, privacy, terms, posts and profiles exist once per language at one path each: the default language at the clean URL, the others under their slug (`/privacy`, `/de/privacy`, `/fr/privacy`). Each is its own indexable page with a self canonical, hreflang for every language plus `x-default`, `og:locale`, `<html lang>` and a share image in its language; share links carry their language without a query. The route files are not duplicated: they moved under `routes/{-$locale}/`, TanStack Router's optional segment ([SEO and discovery](/docs/features/seo), [commit](https://github.com/CarlosZiegler/template-cloudflare/commit/8c3b6384)).

The languages, their URL slugs and optional translated segments are one leaf, `packages/intl/src/locale.config.ts`; a project maps `pt-BR` to `/br` there, and the pure path functions in `locale-paths.ts` (split, localize, canonical form, region-tag matching for `?lang=` and `Accept-Language`) read it. `getSSRLanguage` takes the language from the path on public pages and from the cookie everywhere else ([Internationalization](/docs/features/i18n), [commit](https://github.com/CarlosZiegler/template-cloudflare/commit/dfd319eb)).

The old URLs keep working: `?lang=de`, a region tag (`?lang=de-DE`, `?lang=de_AT`), `/en/privacy` and `/DE/privacy` answer a 301 to the path form with the rest of the query kept; an unknown language goes to the default page. A first human visit to `/` follows the locale cookie, then `Accept-Language`, with a 302 and `Vary: Accept-Language, Cookie`; crawlers, requests without `Accept-Language`, prefixed URLs and POST are never redirected (`rootLocaleRedirect` in the config). The language switcher on public pages is a list of links to the current page in each language and remembers the choice in the cookie; the header, footer, legal links and consent banner link within the page's language, and links to sign-in and the app stay unprefixed. The Markdown copies follow the same paths (`/de/privacy.md`; `Accept: text/markdown` on `/de` returns `/de/index.md`), the sitemap lists every language's URL with `xhtml:link` alternates and `llms.txt` names the per-language documents ([Agent-readable pages](/docs/features/agent-pages)).

