Files
grimoire/src/hooks/useLocale.ts
2025-12-11 13:00:25 +01:00

115 lines
3.1 KiB
TypeScript

import { useMemo } from "react";
export interface LocaleConfig {
/** Browser's detected locale (e.g., 'en-US', 'pt-BR', 'ja-JP') */
locale: string;
/** Language code (e.g., 'en', 'pt', 'ja') */
language: string;
/** Region code (e.g., 'US', 'BR', 'JP') */
region?: string;
/** Timezone (e.g., 'America/New_York') */
timezone: string;
/** 12h or 24h time preference */
timeFormat: "12h" | "24h";
}
/**
* Hook to get user's locale preferences from browser
* Falls back to en-US if detection fails
*/
export function useLocale(): LocaleConfig {
return useMemo(() => {
// Get browser locale
const browserLocale =
navigator.language || navigator.languages?.[0] || "en-US";
// Parse locale into language and region
const [language, region] = browserLocale.split("-");
// Detect timezone
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
// Detect 12h vs 24h preference by formatting a test date
const testDate = new Date(2000, 0, 1, 13, 0); // 1PM
const formatted = testDate.toLocaleTimeString(browserLocale, {
hour: "numeric",
});
const timeFormat =
formatted.includes("PM") || formatted.includes("AM") ? "12h" : "24h";
return {
locale: browserLocale,
language,
region,
timezone,
timeFormat,
};
}, []);
}
/**
* Format a timestamp according to locale preferences
* @param timestamp - Unix timestamp in seconds
* @param style - 'relative' for "2h ago", 'absolute' for full date/time, 'date' for date only, 'time' for time only
*/
export function formatTimestamp(
timestamp: number,
style: "relative" | "absolute" | "date" | "time" = "relative",
locale?: string,
): string {
const browserLocale = locale || navigator.language || "en-US";
const date = new Date(timestamp * 1000);
if (style === "relative") {
const now = Date.now();
const diff = now - timestamp * 1000;
const seconds = Math.floor(diff / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
const weeks = Math.floor(days / 7);
const months = Math.floor(days / 30);
const years = Math.floor(days / 365);
if (seconds < 60) return `${seconds}s ago`;
if (minutes < 60) return `${minutes}m ago`;
if (hours < 24) return `${hours}h ago`;
if (days < 7) return `${days}d ago`;
if (weeks < 4) return `${weeks}w ago`;
if (months < 12) return `${months}mo ago`;
return `${years}y ago`;
}
if (style === "absolute") {
// ISO-8601 style: 2025-12-10 23:42
return date
.toLocaleString(browserLocale, {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
hour12: false,
})
.replace(",", "");
}
if (style === "date") {
return date.toLocaleDateString(browserLocale, {
year: "numeric",
month: "2-digit",
day: "2-digit",
});
}
if (style === "time") {
return date.toLocaleTimeString(browserLocale, {
hour: "2-digit",
minute: "2-digit",
hour12: false,
});
}
return date.toLocaleString(browserLocale);
}