import type { TimeUnit } from '../../types/timeAgoT' import { getI18n } from '../../i18n' export { fmtDate, formatDate, getDatePartsMap, handleTimezone, timeDelta, utc, local } from '../date' export type { DateTimeAcceptedFormats, FormatDateOptions } from '../date' type TimeAgoLang = 'en' | 'es' | 'fr' | 'he' | 'it' | 'ru' interface TimeAgoTranslations { year: string | TimeUnit month: string | TimeUnit week: string | TimeUnit day: string | TimeUnit hour: string | TimeUnit minute: string | TimeUnit second: string | TimeUnit ago: string in: string justNow: string } function getTimeAgoTranslations(lang: TimeAgoLang): TimeAgoTranslations { const fallback: TimeAgoTranslations = { year: 'year', month: 'month', week: 'week', day: 'day', hour: 'hour', minute: 'minute', second: 'second', ago: 'ago', in: 'in', justNow: 'Just now', } try { const i18n = getI18n() const messages = i18n.global.messages as Record }> const localeMessages = messages[lang] || messages.en const fromLocale = localeMessages?.timeAgo return fromLocale ? { ...fallback, ...fromLocale } : fallback } catch { return fallback } } function unitLabel(unit: string | TimeUnit | undefined, count: number): string { if (unit == null) return '' if (typeof unit === 'string') { if (count === 1) return unit if (unit.endsWith('s')) return unit return `${unit}s` } return count === 1 ? (unit.singular ?? '') : (unit.plural ?? unit.singular ?? '') } function getCurrentLocale(): TimeAgoLang { try { const i18n = getI18n() const { locale } = i18n.global const lang = typeof locale === 'string' ? locale : locale.value const supported: TimeAgoLang[] = ['en', 'es', 'fr', 'he', 'it', 'ru'] return supported.includes(lang as TimeAgoLang) ? lang as TimeAgoLang : 'en' } catch { return 'en' } } export function timeAgo( date: string | Date, langOrConfig?: TimeAgoLang | { lang?: TimeAgoLang, mode?: 'relative' | 'absolute' } ) { if (!date) return '' if (typeof date === 'string') { // Treat bare ISO strings (no timezone offset / Z) as UTC if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test(date) && !/[Z+-]\d*$/.test(date)) date = new Date(`${date}Z`) else date = new Date(date) } const lang = typeof langOrConfig === 'string' ? langOrConfig : (langOrConfig?.lang || getCurrentLocale()) const mode = typeof langOrConfig === 'object' ? (langOrConfig.mode || 'relative') : 'relative' const isRelative = mode === 'relative' const seconds = Math.floor((date.getTime() - Date.now()) / 1000) const intervals = [ { label: 'year', seconds: 31536000 }, { label: 'month', seconds: 2592000 }, { label: 'week', seconds: 604800 }, { label: 'day', seconds: 86400 }, { label: 'hour', seconds: 3600 }, { label: 'minute', seconds: 60 }, { label: 'second', seconds: 1 }, ] const t = getTimeAgoTranslations(lang) const getUnit = (label: string): string | TimeUnit | undefined => t[label as keyof TimeAgoTranslations] as string | TimeUnit | undefined for (const interval of intervals) { const count = Math.floor(Math.abs(seconds) / interval.seconds) if (count >= 1) { const suffix = isRelative && seconds < 0 ? ` ${t.ago}` : '' const prefix = isRelative && seconds > 0 && t.in !== 'in' ? `${t.in} ` : '' if (lang === 'he') { const form = unitLabel(getUnit(interval.label), count) const hePrefix = isRelative ? (seconds < 0 ? `${t.ago} ` : (seconds > 0 ? `${t.in} ` : '')) : '' if (interval.label === 'day' && seconds > 0) { const hours = Math.floor((Math.abs(seconds) % 86400) / 3600) const hourForm = unitLabel(t.hour, hours) const main = count === 1 ? form : `${count} ${form}` const hourPart = hours > 0 ? (hours === 1 ? ` ${hourForm}` : ` ${hours} ${hourForm}`) : '' return `${hePrefix}${main}${hourPart}` } return `${hePrefix}${count === 1 ? form : `${count} ${form}`}` } const label = unitLabel(getUnit(interval.label), count) if (interval.label === 'day' && seconds > 0) { const hours = Math.floor((Math.abs(seconds) % 86400) / 3600) const hourLabel = unitLabel(t.hour, hours) return `${prefix}${count} ${label}${hours > 0 ? ` ${hours} ${hourLabel}` : ''}${suffix}` } return `${prefix}${count} ${label}${suffix}` } } return t.justNow as string }