108 lines
3.0 KiB
TypeScript
108 lines
3.0 KiB
TypeScript
import dayjs, { type Dayjs } from "dayjs"
|
||
import localizedFormat from "dayjs/plugin/localizedFormat"
|
||
import relativeTime from "dayjs/plugin/relativeTime"
|
||
import "dayjs/locale/fr"
|
||
import "dayjs/locale/en"
|
||
|
||
dayjs.extend(localizedFormat)
|
||
dayjs.extend(relativeTime)
|
||
|
||
const SUPPORTED_LOCALES = new Set(["fr", "en", "de", "es", "it", "pt", "nl", "pl", "ja", "zh"])
|
||
|
||
let activeLocale: string | null = null
|
||
|
||
/** Browser locale for dayjs (client); stable fallback on server. */
|
||
export function resolveMailDateLocale(): string {
|
||
if (typeof navigator === "undefined") return "fr"
|
||
const base = navigator.language.split("-")[0]?.toLowerCase() ?? "fr"
|
||
return SUPPORTED_LOCALES.has(base) ? base : "en"
|
||
}
|
||
|
||
export function ensureMailDateLocale(): void {
|
||
const next = resolveMailDateLocale()
|
||
if (activeLocale === next) return
|
||
dayjs.locale(next)
|
||
activeLocale = next
|
||
}
|
||
|
||
export function parseMailDate(iso: string): Dayjs | null {
|
||
if (!iso?.trim()) return null
|
||
const d = dayjs(iso)
|
||
return d.isValid() ? d : null
|
||
}
|
||
|
||
export type MailDateDisplayVariant = "list" | "preview" | "previewShort" | "detail"
|
||
|
||
const TWO_WEEKS_MS = 14 * 24 * 60 * 60 * 1000
|
||
|
||
function relativeSuffix(d: Dayjs, now: Dayjs): string {
|
||
if (d.isAfter(now)) return ""
|
||
return ` (${d.fromNow()})`
|
||
}
|
||
|
||
/** Colonne date de la liste (fuseau navigateur). */
|
||
export function formatMailListDate(iso: string, now: Dayjs = dayjs()): string {
|
||
ensureMailDateLocale()
|
||
const d = parseMailDate(iso)
|
||
if (!d) return "—"
|
||
|
||
if (d.isSame(now, "day")) {
|
||
return d.format("LT")
|
||
}
|
||
if (d.isSame(now, "year")) {
|
||
return d.format("D MMM")
|
||
}
|
||
return d.format("L")
|
||
}
|
||
|
||
/** En-tête / aperçu d’un message (fuseau navigateur). */
|
||
export function formatMailPreviewDate(iso: string, now: Dayjs = dayjs()): string {
|
||
ensureMailDateLocale()
|
||
const d = parseMailDate(iso)
|
||
if (!d) return "—"
|
||
|
||
const time = d.format("LT")
|
||
|
||
if (d.isSame(now, "day")) {
|
||
return `${time}${relativeSuffix(d, now)}`
|
||
}
|
||
|
||
const msAgo = now.valueOf() - d.valueOf()
|
||
const withinTwoWeeks = msAgo >= 0 && msAgo < TWO_WEEKS_MS
|
||
const datePart = d.format("ddd D MMM")
|
||
|
||
if (withinTwoWeeks) {
|
||
return `${datePart} ${time}${relativeSuffix(d, now)}`
|
||
}
|
||
if (d.isSame(now, "year")) {
|
||
return `${datePart} ${time}`
|
||
}
|
||
return `${d.format("ddd D MMM YYYY")} ${time}`
|
||
}
|
||
|
||
/** Citations, impression, panneau « détails » (sans relatif). */
|
||
export function formatMailDetailDate(iso: string, now: Dayjs = dayjs()): string {
|
||
ensureMailDateLocale()
|
||
const d = parseMailDate(iso)
|
||
if (!d) return "—"
|
||
|
||
const time = d.format("LT")
|
||
const datePart = d.format("ddd D MMM")
|
||
if (d.isSame(now, "year")) {
|
||
return `${datePart} ${time}`
|
||
}
|
||
return `${d.format("ddd D MMM YYYY")} ${time}`
|
||
}
|
||
|
||
export function formatMailDate(iso: string, variant: MailDateDisplayVariant): string {
|
||
switch (variant) {
|
||
case "list":
|
||
case "previewShort":
|
||
return formatMailListDate(iso)
|
||
case "preview":
|
||
return formatMailPreviewDate(iso)
|
||
case "detail":
|
||
return formatMailDetailDate(iso)
|
||
}
|
||
}
|