diff --git a/frontend/src/components/Form/AccessField.tsx b/frontend/src/components/Form/AccessField.tsx index 16059ac3..afcbd0cf 100644 --- a/frontend/src/components/Form/AccessField.tsx +++ b/frontend/src/components/Form/AccessField.tsx @@ -3,6 +3,7 @@ import { Field, useFormikContext } from "formik"; import type { ReactNode } from "react"; import Select, { type ActionMeta, components, type OptionProps } from "react-select"; import type { AccessList } from "src/api/backend"; +import { useLocaleState } from "src/context"; import { useAccessLists } from "src/hooks"; import { formatDateTime, intl, T } from "src/locale"; @@ -32,6 +33,7 @@ interface Props { label?: string; } export function AccessField({ name = "accessListId", label = "access-list", id = "accessListId" }: Props) { + const { locale } = useLocaleState(); const { isLoading, isError, error, data } = useAccessLists(["owner", "items", "clients"]); const { setFieldValue } = useFormikContext(); @@ -48,7 +50,7 @@ export function AccessField({ name = "accessListId", label = "access-list", id = { users: item?.items?.length, rules: item?.clients?.length, - date: item?.createdOn ? formatDateTime(item?.createdOn) : "N/A", + date: item?.createdOn ? formatDateTime(item?.createdOn, locale) : "N/A", }, ), icon: , diff --git a/frontend/src/components/Form/SSLCertificateField.tsx b/frontend/src/components/Form/SSLCertificateField.tsx index 0e7ce337..6ab3ea92 100644 --- a/frontend/src/components/Form/SSLCertificateField.tsx +++ b/frontend/src/components/Form/SSLCertificateField.tsx @@ -2,6 +2,7 @@ import { IconShield } from "@tabler/icons-react"; import { Field, useFormikContext } from "formik"; import Select, { type ActionMeta, components, type OptionProps } from "react-select"; import type { Certificate } from "src/api/backend"; +import { useLocaleState } from "src/context"; import { useCertificates } from "src/hooks"; import { formatDateTime, intl, T } from "src/locale"; @@ -41,6 +42,7 @@ export function SSLCertificateField({ allowNew, forHttp = true, }: Props) { + const { locale } = useLocaleState(); const { isLoading, isError, error, data } = useCertificates(); const { values, setFieldValue } = useFormikContext(); const v: any = values || {}; @@ -75,7 +77,7 @@ export function SSLCertificateField({ data?.map((cert: Certificate) => ({ value: cert.id, label: cert.niceName, - subLabel: `${cert.provider === "letsencrypt" ? intl.formatMessage({ id: "lets-encrypt" }) : cert.provider} — ${intl.formatMessage({ id: "expires.on" }, { date: cert.expiresOn ? formatDateTime(cert.expiresOn) : "N/A" })}`, + subLabel: `${cert.provider === "letsencrypt" ? intl.formatMessage({ id: "lets-encrypt" }) : cert.provider} — ${intl.formatMessage({ id: "expires.on" }, { date: cert.expiresOn ? formatDateTime(cert.expiresOn, locale) : "N/A" })}`, icon: , })) || []; diff --git a/frontend/src/components/Table/Formatter/DateFormatter.tsx b/frontend/src/components/Table/Formatter/DateFormatter.tsx index acb96461..830b06e2 100644 --- a/frontend/src/components/Table/Formatter/DateFormatter.tsx +++ b/frontend/src/components/Table/Formatter/DateFormatter.tsx @@ -1,5 +1,6 @@ import cn from "classnames"; import { differenceInDays, isPast } from "date-fns"; +import { useLocaleState } from "src/context"; import { formatDateTime, parseDate } from "src/locale"; interface Props { @@ -8,6 +9,7 @@ interface Props { highlistNearlyExpired?: boolean; } export function DateFormatter({ value, highlightPast, highlistNearlyExpired }: Props) { + const { locale } = useLocaleState(); const d = parseDate(value); const dateIsPast = d ? isPast(d) : false; const days = d ? differenceInDays(d, new Date()) : 0; @@ -15,5 +17,5 @@ export function DateFormatter({ value, highlightPast, highlistNearlyExpired }: P "text-danger": highlightPast && dateIsPast, "text-warning": highlistNearlyExpired && !dateIsPast && days <= 30 && days >= 0, }); - return {formatDateTime(value)}; + return {formatDateTime(value, locale)}; } diff --git a/frontend/src/components/Table/Formatter/DomainsFormatter.tsx b/frontend/src/components/Table/Formatter/DomainsFormatter.tsx index fc32968c..35e8393e 100644 --- a/frontend/src/components/Table/Formatter/DomainsFormatter.tsx +++ b/frontend/src/components/Table/Formatter/DomainsFormatter.tsx @@ -1,5 +1,6 @@ import cn from "classnames"; import type { ReactNode } from "react"; +import { useLocaleState } from "src/context"; import { formatDateTime, T } from "src/locale"; interface Props { @@ -37,7 +38,9 @@ const DomainLink = ({ domain, color }: { domain?: string; color?: string }) => { }; export function DomainsFormatter({ domains, createdOn, niceName, provider, color }: Props) { + const { locale } = useLocaleState(); const elms: ReactNode[] = []; + if ((!domains || domains.length === 0) && !niceName) { elms.push( @@ -62,7 +65,7 @@ export function DomainsFormatter({ domains, createdOn, niceName, provider, color
{...elms}
{createdOn ? (
- +
) : null} diff --git a/frontend/src/components/Table/Formatter/EventFormatter.tsx b/frontend/src/components/Table/Formatter/EventFormatter.tsx index ff37ecbe..1220fa09 100644 --- a/frontend/src/components/Table/Formatter/EventFormatter.tsx +++ b/frontend/src/components/Table/Formatter/EventFormatter.tsx @@ -1,6 +1,7 @@ import { IconArrowsCross, IconBolt, IconBoltOff, IconDisc, IconLock, IconShield, IconUser } from "@tabler/icons-react"; import cn from "classnames"; import type { AuditLog } from "src/api/backend"; +import { useLocaleState } from "src/context"; import { formatDateTime, T } from "src/locale"; const getEventValue = (event: AuditLog) => { @@ -66,6 +67,7 @@ interface Props { row: AuditLog; } export function EventFormatter({ row }: Props) { + const { locale } = useLocaleState(); return (
@@ -73,7 +75,7 @@ export function EventFormatter({ row }: Props) {   — {getEventValue(row)}
-
{formatDateTime(row.createdOn)}
+
{formatDateTime(row.createdOn, locale)}
); } diff --git a/frontend/src/components/Table/Formatter/ValueWithDateFormatter.tsx b/frontend/src/components/Table/Formatter/ValueWithDateFormatter.tsx index 38352b31..d83ccbfa 100644 --- a/frontend/src/components/Table/Formatter/ValueWithDateFormatter.tsx +++ b/frontend/src/components/Table/Formatter/ValueWithDateFormatter.tsx @@ -1,3 +1,4 @@ +import { useLocaleState } from "src/context"; import { formatDateTime, T } from "src/locale"; interface Props { @@ -6,6 +7,7 @@ interface Props { disabled?: boolean; } export function ValueWithDateFormatter({ value, createdOn, disabled }: Props) { + const { locale } = useLocaleState(); return (
@@ -13,7 +15,7 @@ export function ValueWithDateFormatter({ value, createdOn, disabled }: Props) {
{createdOn ? (
- +
) : null}
diff --git a/frontend/src/locale/Utils.test.tsx b/frontend/src/locale/Utils.test.tsx index fb262501..e998dc31 100644 --- a/frontend/src/locale/Utils.test.tsx +++ b/frontend/src/locale/Utils.test.tsx @@ -39,19 +39,19 @@ describe("DateFormatter", () => { it("format date from iso date", () => { const value = "2024-01-01T00:00:00.000Z"; const text = formatDateTime(value); - expect(text).toBe("Monday, 01/01/2024, 12:00:00 am"); + expect(text).toBe("1 Jan 2024, 12:00:00 am"); }); it("format date from unix timestamp number", () => { const value = 1762476112; const text = formatDateTime(value); - expect(text).toBe("Friday, 07/11/2025, 12:41:52 am"); + expect(text).toBe("7 Nov 2025, 12:41:52 am"); }); it("format date from unix timestamp string", () => { const value = "1762476112"; const text = formatDateTime(value); - expect(text).toBe("Friday, 07/11/2025, 12:41:52 am"); + expect(text).toBe("7 Nov 2025, 12:41:52 am"); }); it("catch bad format from string", () => { diff --git a/frontend/src/locale/Utils.ts b/frontend/src/locale/Utils.ts index 018efd0b..d4224277 100644 --- a/frontend/src/locale/Utils.ts +++ b/frontend/src/locale/Utils.ts @@ -1,4 +1,9 @@ -import { fromUnixTime, intlFormat, parseISO } from "date-fns"; +import { + fromUnixTime, + type IntlFormatFormatOptions, + intlFormat, + parseISO, +} from "date-fns"; const isUnixTimestamp = (value: unknown): boolean => { if (typeof value !== "number" && typeof value !== "string") return false; @@ -20,20 +25,19 @@ const parseDate = (value: string | number): Date | null => { } }; -const formatDateTime = (value: string | number): string => { +const formatDateTime = (value: string | number, locale = "en-US"): string => { const d = parseDate(value); if (!d) return `${value}`; try { - return intlFormat(d, { - weekday: "long", - year: "numeric", - month: "numeric", - day: "numeric", - hour: "numeric", - minute: "numeric", - second: "numeric", - hour12: true, - }); + return intlFormat( + d, + { + dateStyle: "medium", + timeStyle: "medium", + hourCycle: "h12", + } as IntlFormatFormatOptions, + { locale }, + ); } catch { return `${value}`; }