fix: dates follow the UI language, not the browser's
Seven sites formatted with a bare toLocaleString()/toLocaleDateString(), so an English UI on a Hungarian machine printed Hungarian-shaped dates (and the reverse). They now go through formatDateTime/formatDate, which take the i18n language.
This commit is contained in:
@@ -4,13 +4,14 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { Info, Sparkles } from "lucide-react";
|
import { Info, Sparkles } from "lucide-react";
|
||||||
import { api } from "../lib/api";
|
import { api } from "../lib/api";
|
||||||
import { FRONTEND_VERSION, FRONTEND_SHA, FRONTEND_BUILD_DATE } from "../lib/version";
|
import { FRONTEND_VERSION, FRONTEND_SHA, FRONTEND_BUILD_DATE } from "../lib/version";
|
||||||
|
import { formatDateTime } from "../lib/format";
|
||||||
import { Button } from "./ui/Button";
|
import { Button } from "./ui/Button";
|
||||||
import Modal from "./ui/Modal";
|
import Modal from "./ui/Modal";
|
||||||
|
|
||||||
function fmtDate(d?: string | null): string {
|
function fmtDate(d?: string | null): string {
|
||||||
if (!d) return "—";
|
if (!d) return "—";
|
||||||
const t = new Date(d);
|
const t = new Date(d);
|
||||||
return isNaN(t.getTime()) ? d : t.toLocaleString();
|
return isNaN(t.getTime()) ? d : formatDateTime(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
// About dialog: app + build + schema versions (frontend/backend/database), plus a
|
// About dialog: app + build + schema versions (frontend/backend/database), plus a
|
||||||
|
|||||||
@@ -431,7 +431,7 @@ function PendingAccessRowView({
|
|||||||
{row.created_at && (
|
{row.created_at && (
|
||||||
<span>
|
<span>
|
||||||
{t("users.pendingAccess.signedUp", {
|
{t("users.pendingAccess.signedUp", {
|
||||||
time: new Date(row.created_at).toLocaleString(),
|
time: formatDateTime(row.created_at),
|
||||||
})}
|
})}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -619,7 +619,10 @@ function AdminInvites() {
|
|||||||
inv.invited_at
|
inv.invited_at
|
||||||
? "settings.invites.inviteConfirmAgain"
|
? "settings.invites.inviteConfirmAgain"
|
||||||
: "settings.invites.inviteConfirm",
|
: "settings.invites.inviteConfirm",
|
||||||
{ email: inv.email, time: new Date(inv.invited_at ?? 0).toLocaleString() },
|
{
|
||||||
|
email: inv.email,
|
||||||
|
time: formatDateTime(inv.invited_at ?? new Date(0).toISOString()),
|
||||||
|
},
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<InviteLangPicker initial={lang} onPick={(v) => (lang = v)} />
|
<InviteLangPicker initial={lang} onPick={(v) => (lang = v)} />
|
||||||
@@ -718,7 +721,7 @@ function AdminInvites() {
|
|||||||
hint={
|
hint={
|
||||||
i.invited_at
|
i.invited_at
|
||||||
? t("settings.invites.invitedAt", {
|
? t("settings.invites.invitedAt", {
|
||||||
time: new Date(i.invited_at).toLocaleString(),
|
time: formatDateTime(i.invited_at),
|
||||||
})
|
})
|
||||||
: t("settings.invites.inviteHint")
|
: t("settings.invites.inviteHint")
|
||||||
}
|
}
|
||||||
@@ -916,7 +919,7 @@ function InviteRow({
|
|||||||
{inv.requested_at && (
|
{inv.requested_at && (
|
||||||
<div className="text-2xs text-muted">
|
<div className="text-2xs text-muted">
|
||||||
{t("settings.invites.requested", {
|
{t("settings.invites.requested", {
|
||||||
time: new Date(inv.requested_at).toLocaleString(),
|
time: formatDateTime(inv.requested_at),
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { useConfirm } from "./ConfirmProvider";
|
|||||||
import { notify } from "../lib/notifications";
|
import { notify } from "../lib/notifications";
|
||||||
import { notifyActionError } from "../lib/actionErrors";
|
import { notifyActionError } from "../lib/actionErrors";
|
||||||
import { copyText } from "../lib/clipboard";
|
import { copyText } from "../lib/clipboard";
|
||||||
|
import { formatDate } from "../lib/format";
|
||||||
import { api, type DownloadJob, type ShareLink } from "../lib/api";
|
import { api, type DownloadJob, type ShareLink } from "../lib/api";
|
||||||
import { inputCls } from "./ui/form";
|
import { inputCls } from "./ui/form";
|
||||||
import { Button } from "./ui/Button";
|
import { Button } from "./ui/Button";
|
||||||
@@ -88,7 +89,7 @@ function UserShare({ job }: { job: DownloadJob }) {
|
|||||||
// --- share by public link -------------------------------------------------------------------
|
// --- share by public link -------------------------------------------------------------------
|
||||||
|
|
||||||
function LinkRow({ link, onChanged }: { link: ShareLink; onChanged: () => void }) {
|
function LinkRow({ link, onChanged }: { link: ShareLink; onChanged: () => void }) {
|
||||||
const { t } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const confirm = useConfirm();
|
const confirm = useConfirm();
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
const [editPass, setEditPass] = useState(false);
|
const [editPass, setEditPass] = useState(false);
|
||||||
@@ -130,7 +131,7 @@ function LinkRow({ link, onChanged }: { link: ShareLink; onChanged: () => void }
|
|||||||
if (link.allow_download) badges.push(t("downloads.share.downloadable"));
|
if (link.allow_download) badges.push(t("downloads.share.downloadable"));
|
||||||
if (link.expires_at)
|
if (link.expires_at)
|
||||||
badges.push(
|
badges.push(
|
||||||
t("downloads.share.expiresOn", { date: new Date(link.expires_at).toLocaleDateString() }),
|
t("downloads.share.expiresOn", { date: formatDate(link.expires_at, i18n.language) }),
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -7,7 +7,14 @@ import { useLiveQuery } from "../lib/useLiveQuery";
|
|||||||
import { api, type DownloadJob } from "../lib/api";
|
import { api, type DownloadJob } from "../lib/api";
|
||||||
import { notify } from "../lib/notifications";
|
import { notify } from "../lib/notifications";
|
||||||
import { copyText } from "../lib/clipboard";
|
import { copyText } from "../lib/clipboard";
|
||||||
import { formatBytes, formatDuration, formatEta, formatSpeed, relativeTime } from "../lib/format";
|
import {
|
||||||
|
formatBytes,
|
||||||
|
formatDateTime,
|
||||||
|
formatDuration,
|
||||||
|
formatEta,
|
||||||
|
formatSpeed,
|
||||||
|
relativeTime,
|
||||||
|
} from "../lib/format";
|
||||||
import { navItemAttrs } from "../lib/listNav";
|
import { navItemAttrs } from "../lib/listNav";
|
||||||
import { failCopyKeys } from "../lib/downloadFailure";
|
import { failCopyKeys } from "../lib/downloadFailure";
|
||||||
|
|
||||||
@@ -250,7 +257,7 @@ export function DownloadRow({
|
|||||||
{job.status === "done" && job.created_at ? (
|
{job.status === "done" && job.created_at ? (
|
||||||
<span
|
<span
|
||||||
className="text-muted inline-flex items-center gap-1"
|
className="text-muted inline-flex items-center gap-1"
|
||||||
title={new Date(job.created_at).toLocaleString()}
|
title={formatDateTime(job.created_at)}
|
||||||
>
|
>
|
||||||
· <CalendarClock className="w-3.5 h-3.5" /> {relativeTime(job.created_at)}
|
· <CalendarClock className="w-3.5 h-3.5" /> {relativeTime(job.created_at)}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user