diff --git a/components/files/__tests__/file-item-parts.test.tsx b/components/files/__tests__/file-item-parts.test.tsx
new file mode 100644
index 000000000..09c76a75a
--- /dev/null
+++ b/components/files/__tests__/file-item-parts.test.tsx
@@ -0,0 +1,27 @@
+import { render } from '@testing-library/react';
+import { describe, it, expect, vi, afterEach } from 'vitest';
+
+let appLocale = 'ar';
+vi.mock('@/i18n/detect-locale', () => ({ getEffectiveLocale: () => appLocale }));
+
+import { FileSize, formatFullDate, formatShortDate } from '../file-item-parts';
+
+// The browser here is en-US; the app's language must win.
+describe('Files list dates and sizes', () => {
+ afterEach(() => { appLocale = 'ar'; });
+
+ it('writes dates in the app language, not the browser one', () => {
+ const now = new Date('2026-10-27T12:00:00Z');
+ expect(formatShortDate('2026-03-05T10:00:00Z', now)).toMatch(/مارس/);
+ expect(formatFullDate('2026-03-05T10:00:00Z')).toMatch(/مارس/);
+ appLocale = 'de';
+ expect(formatShortDate('2026-03-05T10:00:00Z', now)).toMatch(/März/);
+ });
+
+ it('keeps the number before the unit in a right-to-left layout', () => {
+ const { container } = render(
{label}
diff --git a/components/files/file-item-parts.tsx b/components/files/file-item-parts.tsx
index 2b201fc3e..37a33b43e 100644
--- a/components/files/file-item-parts.tsx
+++ b/components/files/file-item-parts.tsx
@@ -3,8 +3,10 @@
import { useEffect, useRef, useState } from "react";
import { useTranslations } from "next-intl";
import { Share2, Users } from "@/components/icons";
-import { cn } from "@/lib/utils";
+import { cn, formatFileSize } from "@/lib/utils";
import { getEffectiveTimeZone } from "@/lib/timezone";
+import { getEffectiveLocale } from "@/i18n/detect-locale";
+import { useSettingsStore } from "@/stores/settings-store";
import type { FileResource } from "@/stores/file-store";
import { getFileIconByName, getFolderIcon, getSymlinkIcon, isImageFile, type FileIconSize } from "@/components/files/file-icons";
@@ -124,17 +126,33 @@ export function ShareBadge({ resource, t, className }: {
return null;
}
+/**
+ * A size that keeps its number before its unit ("6.7 KB") in a right-to-left
+ * layout too, where the bidi algorithm would otherwise show "KB 6.7".
+ */
+export function FileSize({ bytes }: { bytes: number }) {
+ return
{formatFileSize(bytes)};
+}
+
+/** Dates follow the app's language and 12/24-hour setting, as Mail's list does, not the browser's. */
+function dateLocale(): { locale: string; hour12: boolean } {
+ const locale = getEffectiveLocale();
+ return { locale: locale === "en" ? "en-US" : locale, hour12: useSettingsStore.getState().timeFormat === "12h" };
+}
+
/** Full date and time, for tooltips and the details panel. */
export function formatFullDate(value: string): string {
if (!value) return "";
try {
- return new Date(value).toLocaleString(undefined, {
+ const { locale, hour12 } = dateLocale();
+ return new Date(value).toLocaleString(locale, {
timeZone: getEffectiveTimeZone(),
year: "numeric",
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
+ hour12,
});
} catch {
return value;
@@ -149,12 +167,13 @@ export function formatShortDate(value: string, now = new Date()): string {
const timeZone = getEffectiveTimeZone();
const dayKey = (d: Date) => d.toLocaleDateString("en-CA", { timeZone });
try {
+ const { locale, hour12 } = dateLocale();
if (dayKey(date) === dayKey(now)) {
- return date.toLocaleTimeString(undefined, { timeZone, hour: "2-digit", minute: "2-digit" });
+ return date.toLocaleTimeString(locale, { timeZone, hour: "2-digit", minute: "2-digit", hour12 });
}
const sameYear = date.toLocaleDateString("en-CA", { timeZone, year: "numeric" })
=== now.toLocaleDateString("en-CA", { timeZone, year: "numeric" });
- return date.toLocaleDateString(undefined, {
+ return date.toLocaleDateString(locale, {
timeZone,
day: "numeric",
month: "short",
diff --git a/components/files/file-picker-dialog.tsx b/components/files/file-picker-dialog.tsx
index 55a94e275..e49320ca0 100644
--- a/components/files/file-picker-dialog.tsx
+++ b/components/files/file-picker-dialog.tsx
@@ -175,7 +175,7 @@ export function FilePickerDialog({ client, onClose, onPick }: FilePickerDialogPr
{node.name}
{detail &&
{detail}}
-
{formatFileSize(node.size)}
+
{formatFileSize(node.size)}
);
};
diff --git a/components/files/file-views.tsx b/components/files/file-views.tsx
index f3d23336d..ce627400a 100644
--- a/components/files/file-views.tsx
+++ b/components/files/file-views.tsx
@@ -3,11 +3,11 @@
import { useRef } from "react";
import { useTranslations } from "next-intl";
import { ArrowUp, ArrowDown, MoreVertical, Download, Share2, Star, Check } from "@/components/icons";
-import { cn, formatFileSize } from "@/lib/utils";
+import { cn } from "@/lib/utils";
import { IconButton } from "@/components/ui/icon-button";
import type { FileResource } from "@/stores/file-store";
import {
- ResourceIcon, ShareBadge, Thumbnail, canShowThumbnail, formatFullDate, formatShortDate,
+ FileSize, ResourceIcon, ShareBadge, Thumbnail, canShowThumbnail, formatFullDate, formatShortDate,
type IconOptions,
} from "@/components/files/file-item-parts";
import type { SortDir, SortKey } from "@/components/files/files-header";
@@ -271,7 +271,7 @@ function FileListRow({ resource, isSelected, isFocused, isDropTarget, isCut, isS
{note && <>{note} · >}
{formatShortDate(resource.lastModified)}
- {!resource.isDirectory && !resource.isSymlink && <> · {formatFileSize(resource.contentLength)}>}
+ {!resource.isDirectory && !resource.isSymlink && <> · >}
)}
@@ -280,7 +280,7 @@ function FileListRow({ resource, isSelected, isFocused, isDropTarget, isCut, isS
{!touch && formatShortDate(resource.lastModified)}
- {!touch && (resource.isDirectory || resource.isSymlink ? "—" : formatFileSize(resource.contentLength))}
+ {!touch && (resource.isDirectory || resource.isSymlink ? "—" : )}