-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Expand file tree
/
Copy pathconfig.ts
More file actions
135 lines (111 loc) · 4.48 KB
/
Copy pathconfig.ts
File metadata and controls
135 lines (111 loc) · 4.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/**
* Locale configuration for the admin UI runtime.
*
* Locale definitions are in `./locales.ts` -- the single source of truth
* shared by this file, lingui.config.ts and lunaria.config.ts.
*/
import { ENABLED_LOCALES, LOCALES, SOURCE_LOCALE } from "./locales.js";
export type { LocaleDefinition as SupportedLocale } from "./locales.js";
function isValidLocale(code: string): boolean {
try {
const locale = new Intl.Locale(code);
return locale.baseName !== "";
} catch {
if (import.meta.env.DEV) {
throw new Error(`Invalid locale code: "${code}"`);
}
return false;
}
}
// Injected by the EmDash Vite integration from process.env.EMDASH_PSEUDO_LOCALE.
// Only true in dev when EMDASH_PSEUDO_LOCALE=1 is set.
declare const __EMDASH_PSEUDO_LOCALE__: boolean;
/**
* The pseudo locale, injected into the supported list only when
* EMDASH_PSEUDO_LOCALE=1 is set. Never available in production.
*/
const PSEUDO_LOCALE =
typeof __EMDASH_PSEUDO_LOCALE__ !== "undefined" && __EMDASH_PSEUDO_LOCALE__
? LOCALES.find((l) => l.code === "pseudo")
: undefined;
/** Available locales at runtime, validated against BCP 47. */
export const SUPPORTED_LOCALES = [
...ENABLED_LOCALES.filter((l) => isValidLocale(l.code)),
...(PSEUDO_LOCALE ? [PSEUDO_LOCALE] : []),
];
export const SUPPORTED_LOCALE_CODES = new Set(SUPPORTED_LOCALES.map((l) => l.code));
export const DEFAULT_LOCALE = SOURCE_LOCALE.code;
/** Maps base language codes to supported locales (e.g. "pt" -> "pt-BR"). */
const BASE_LANGUAGE_MAP = new Map<string, string>();
/** Maps script codes to supported locales (e.g. "zh-Hant" -> "zh-TW", "zh-Hans" -> "zh-CN"). */
const SCRIPT_LANGUAGE_MAP = new Map<string, string>();
for (const l of SUPPORTED_LOCALES) {
const base = l.code.split("-")[0]!.toLowerCase();
// First match wins -- if we have both "pt" and "pt-BR", exact wins via direct lookup.
if (!BASE_LANGUAGE_MAP.has(base)) {
BASE_LANGUAGE_MAP.set(base, l.code);
}
const maximized = new Intl.Locale(l.code).maximize();
if (maximized.script) {
const scriptKey = `${maximized.language}-${maximized.script}`.toLowerCase();
if (!SCRIPT_LANGUAGE_MAP.has(scriptKey)) {
SCRIPT_LANGUAGE_MAP.set(scriptKey, l.code);
}
}
}
/**
* Find the best matching supported locale for a BCP 47 tag.
* Canonicalizes via Intl.Locale so case differences (e.g. "pt-br" vs "pt-BR")
* don't prevent matching. Supports script codes (zh-Hant -> zh-TW, zh-Hans -> zh-CN)
* and falls back to base language (pt-PT -> pt-BR).
*/
export function matchLocale(tag: string): string | undefined {
const trimmed = tag.trim();
if (!trimmed) return undefined;
let canonical: string;
try {
canonical = new Intl.Locale(trimmed).baseName;
} catch {
return undefined;
}
// Exact match (case-insensitive via Intl.Locale)
if (SUPPORTED_LOCALE_CODES.has(canonical)) return canonical;
// Try script-based matching (zh-Hant -> zh-TW, zh-Hans -> zh-CN)
const locale = new Intl.Locale(trimmed);
if (locale.script) {
const scriptKey = `${locale.language}-${locale.script}`.toLowerCase();
const scriptMatch = SCRIPT_LANGUAGE_MAP.get(scriptKey);
if (scriptMatch) return scriptMatch;
}
// Fallback to base language (pt-PT -> pt-BR)
const base = canonical.split("-")[0]!.toLowerCase();
return BASE_LANGUAGE_MAP.get(base);
}
const LOCALE_LABELS = new Map(SUPPORTED_LOCALES.map((l) => [l.code, l.label]));
/** Get a display label for a locale code, falling back to uppercase code. */
export function getLocaleLabel(code: string): string {
return LOCALE_LABELS.get(code) ?? code.toUpperCase();
}
const LOCALE_DIRS = new Map(SUPPORTED_LOCALES.map((l) => [l.code, l.dir]));
/** Get the text direction for a locale code. Defaults to "ltr" if not specified. */
export function getLocaleDir(code: string): "ltr" | "rtl" {
return LOCALE_DIRS.get(code) ?? "ltr";
}
const LOCALE_COOKIE_RE = /(?:^|;\s*)emdash-locale=([^;]+)/;
/**
* Resolve the admin locale from a Request.
* Priority: emdash-locale cookie -> Accept-Language -> DEFAULT_LOCALE.
*/
export function resolveLocale(request: Request): string {
const cookieHeader = request.headers.get("cookie") ?? "";
const cookieMatch = cookieHeader.match(LOCALE_COOKIE_RE);
const cookieLocale = cookieMatch?.[1]?.trim() ?? "";
if (SUPPORTED_LOCALE_CODES.has(cookieLocale)) return cookieLocale;
const acceptLang = request.headers.get("accept-language") ?? "";
for (const entry of acceptLang.split(",")) {
const tag = entry.split(";")[0]!.trim();
const matched = matchLocale(tag);
if (matched) return matched;
}
return DEFAULT_LOCALE;
}