| 1 | /** |
| 2 | * Locale path helpers for the website router. |
| 3 | * |
| 4 | * Middleware, the locale switcher, and the docs-only theme toggle all have |
| 5 | * to agree on what the first path segment means. `pt-BR` is one segment, |
| 6 | * not two; a bare `/install` is not yet localized. One implementation keeps |
| 7 | * those call sites from drifting into a regex that only matches `[a-z]{2}`. |
| 8 | */ |
| 9 | import { locales } from "./config"; |
| 10 | |
| 11 | const ROUTED = locales as readonly string[]; |
| 12 | |
| 13 | /** |
| 14 | * The routed locale a first path segment names, in canonical casing. |
| 15 | * |
| 16 | * Matching ignores case so `/pt-br/install` resolves to the same route as |
| 17 | * `/pt-BR/install`. Only the regional tag has a case to get wrong, and an |
| 18 | * external link that lowercases it used to fall through to the bare-path |
| 19 | * branch and land on `/en/pt-br/install` — a 404. |
| 20 | */ |
| 21 | function routedLocale(segment: string | undefined): string | null { |
| 22 | if (!segment) return null; |
| 23 | const lower = segment.toLowerCase(); |
| 24 | return ROUTED.find((l) => l.toLowerCase() === lower) ?? null; |
| 25 | } |
| 26 | |
| 27 | /** The routed locale already in `pathname`, or null if the path is bare. */ |
| 28 | export function pathLocale(pathname: string): string | null { |
| 29 | return routedLocale(pathname.split("/")[1]); |
| 30 | } |
| 31 | |
| 32 | /** |
| 33 | * Swap or insert the locale prefix. Used by the switcher so a click on |
| 34 | * `/pt-BR/docs/guide` lands on `/ja/docs/guide` rather than a nested |
| 35 | * `/ja/pt-BR/docs/guide` that the compact nav then treats as a miss, and by |
| 36 | * the middleware to fold a miscased prefix onto its canonical spelling. |
| 37 | */ |
| 38 | export function replacePathLocale(pathname: string, locale: string): string { |
| 39 | const segments = pathname.split("/"); |
| 40 | if (routedLocale(segments[1])) { |
| 41 | segments[1] = locale; |
| 42 | return segments.join("/"); |
| 43 | } |
| 44 | // `"/"` splits to `["", ""]`, so splicing would join to `/<locale>/` — a |
| 45 | // URL Next.js only serves after a trailing-slash redirect. Every other |
| 46 | // path keeps whatever trailing slash it arrived with. |
| 47 | if (pathname === "" || pathname === "/") return `/${locale}`; |
| 48 | segments.splice(1, 0, locale); |
| 49 | return segments.join("/"); |
| 50 | } |
| 51 | |
| 52 | /** True when the path is a docs route, with or without a locale prefix. */ |
| 53 | export function isDocsPath(pathname: string): boolean { |
| 54 | const segs = pathname.split("/").filter(Boolean); |
| 55 | if (routedLocale(segs[0])) { |
| 56 | return segs[1] === "docs"; |
| 57 | } |
| 58 | return segs[0] === "docs"; |
| 59 | } |
| 60 |