返回 CodeWhale
detect.ts
根目录 / web / lib / i18n / detect.ts
1 /**
2 * Deterministic locale detection for the website middleware (#3091).
3 *
4 * Resolution order (first match wins, no ambient state):
5 * 1. The NEXT_LOCALE cookie (a previous explicit choice).
6 * 2. Accept-Language, in the header's preference order. Each tag matches:
7 * a. exact full tag against the routed set (pt-BR → pt-BR);
8 * b. its primary subtag against the routed set (ru-RU → ru, zh-Hant → zh);
9 * c. a declared base→variant mapping for bases we only serve as a
10 * regional variant (pt → pt-BR).
11 * 3. The default locale (en).
12 *
13 * The mapping table is deliberately tiny and explicit — no guessing that
14 * e.g. es-419 should route anywhere other than the shipped `es`.
15 */
16 import { defaultLocale, locales } from "./config";
17
18 const ROUTED = locales as readonly string[];
19
20 /** Base subtags that route to a specific regional variant. */
21 const BASE_TO_VARIANT: Record<string, string> = {
22 pt: "pt-BR",
23 };
24
25 /** Match one language tag (any case, optional region/script) to a routed locale. */
26 export function matchLocaleTag(tag: string): string | null {
27 const t = tag.trim().toLowerCase();
28 if (!t || t === "*") return null;
29
30 // Exact full-tag match (case-insensitive; routed codes are lowercase).
31 const exact = ROUTED.find((l) => l.toLowerCase() === t);
32 if (exact) return exact;
33
34 const base = t.split("-")[0];
35 if (ROUTED.includes(base)) return base;
36
37 const variant = BASE_TO_VARIANT[base];
38 if (variant && ROUTED.includes(variant)) return variant;
39
40 return null;
41 }
42
43 /** Resolve the locale for a request from its cookie and Accept-Language header. */
44 export function detectLocaleFromHeaders(
45 cookie: string | undefined,
46 acceptLanguage: string | null,
47 ): string {
48 if (cookie) {
49 const match = matchLocaleTag(cookie);
50 if (match) return match;
51 }
52
53 if (acceptLanguage) {
54 const preferred = acceptLanguage.split(",").map((s) => s.split(";")[0]);
55 for (const tag of preferred) {
56 const match = matchLocaleTag(tag);
57 if (match) return match;
58 }
59 }
60
61 return defaultLocale;
62 }
63
63 lines TYPESCRIPT