forked from ChelseaKR/gtfs-scorecard
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlocale.js
More file actions
103 lines (94 loc) · 3.53 KB
/
Copy pathlocale.js
File metadata and controls
103 lines (94 loc) · 3.53 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
// @ts-check
/** Locale-aware presentation primitives shared by the no-build frontend. */
const FALLBACK_LOCALE = "en";
const RTL_LANGUAGES = new Set(["ar", "ckb", "dv", "fa", "he", "ps", "sd", "ur"]);
/** @type {Map<string, Intl.DateTimeFormat>} */
const DATE_FORMATTERS = new Map();
/** @type {Map<string, Intl.NumberFormat>} */
const NUMBER_FORMATTERS = new Map();
/** @type {Map<string, Intl.Collator>} */
const COLLATORS = new Map();
/** @type {Map<string, Intl.DisplayNames>} */
const LANGUAGE_NAMES = new Map();
/** Return a valid BCP 47 locale, preferring the language declared by the page. */
export function pageLocale() {
const declared = document.documentElement.lang.trim();
const candidate = declared || navigator.languages?.[0] || navigator.language || FALLBACK_LOCALE;
try {
return new Intl.Locale(candidate).toString();
} catch {
return FALLBACK_LOCALE;
}
}
/** @param {string} locale @returns {"ltr"|"rtl"} */
export function localeDirection(locale) {
try {
const parsed = new Intl.Locale(locale);
const direction = /** @type {any} */ (parsed).textInfo?.direction;
if (direction === "ltr" || direction === "rtl") return direction;
return RTL_LANGUAGES.has(parsed.language) ? "rtl" : "ltr";
} catch {
return "ltr";
}
}
/** Keep the root direction aligned with its declared language. */
export function applyDocumentDirection() {
document.documentElement.dir = localeDirection(pageLocale());
}
/** @param {string} iso @param {string} [locale] @returns {string} */
export function formatDate(iso, locale = pageLocale()) {
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(iso));
if (!match) return String(iso);
const [, year, month, day] = match;
// Noon avoids a calendar-date shift in time zones on either side of UTC.
const date = new Date(Number(year), Number(month) - 1, Number(day), 12);
if (Number.isNaN(date.getTime())) return String(iso);
let formatter = DATE_FORMATTERS.get(locale);
if (!formatter) {
formatter = new Intl.DateTimeFormat(locale, {
year: "numeric",
month: "long",
day: "numeric",
});
DATE_FORMATTERS.set(locale, formatter);
}
return formatter.format(date);
}
/** @param {number} value @param {string} [locale] @returns {string} */
export function formatNumber(value, locale = pageLocale()) {
let formatter = NUMBER_FORMATTERS.get(locale);
if (!formatter) {
formatter = new Intl.NumberFormat(locale);
NUMBER_FORMATTERS.set(locale, formatter);
}
return formatter.format(value);
}
/** @param {string} left @param {string} right @param {string} [locale] */
export function compareText(left, right, locale = pageLocale()) {
let collator = COLLATORS.get(locale);
if (!collator) {
collator = new Intl.Collator(locale, { sensitivity: "base", numeric: true });
COLLATORS.set(locale, collator);
}
return collator.compare(left, right);
}
/** A localized language name with the published tag retained for precision.
* @param {string} code @param {string} [locale] @returns {string} */
export function formatLanguageName(code, locale = pageLocale()) {
const tag = String(code || "").trim();
if (!tag) return "";
try {
let formatter = LANGUAGE_NAMES.get(locale);
if (!formatter) {
formatter = new Intl.DisplayNames([locale], { type: "language" });
LANGUAGE_NAMES.set(locale, formatter);
}
const label = formatter.of(tag);
return label && label.toLocaleLowerCase(locale) !== tag.toLocaleLowerCase(locale)
? `${label} (${tag})`
: tag;
} catch {
return tag;
}
}
applyDocumentDirection();