| author | |
| committer | |
| log | e1cbb705273a261d26b489f35a7ddca6567d9663 |
| tree | 64ef3d9903abdce8d2cad2a3e5a0a496422ae149 |
| parent | 80d9c181da41ee3890791c9b65018ce093e7bc9e |
| signature | Signed by PGP key B5690EEEBB952194 |
3 files changed, 57 insertions(+), 0 deletions(-)
apps/web/src/components/CustomSnoozeDialog.tsx+2| ... | ... | @@ -10,6 +10,7 @@ import { |
| 10 | 10 | import { Button } from "./ui/button"; |
| 11 | 11 | import { CalendarIcon } from "lucide-react"; |
| 12 | 12 | import { Calendar } from "./ui/calendar"; |
| 13 | import { weekStartsOn } from "../timestampFormat"; | |
| 13 | 14 | import { Popover, PopoverTrigger, PopoverPopup } from "./ui/popover"; |
| 14 | 15 | import { Input } from "./ui/input"; |
| 15 | 16 | import { Label } from "./ui/label"; |
| ... | ... | @@ -149,6 +150,7 @@ function CustomSnoozeDialog() { |
| 149 | 150 | required |
| 150 | 151 | selected={date} |
| 151 | 152 | defaultMonth={date} |
| 153 | {...(weekStartsOn === undefined ? {} : { weekStartsOn })} | |
| 152 | 154 | disabled={{ before: new Date(new Date().setHours(0, 0, 0, 0)) }} |
| 153 | 155 | onSelect={(selected) => { |
| 154 | 156 | setDate(selected); |
apps/web/src/timestampFormat.test.ts+26| ... | ... | @@ -52,6 +52,32 @@ describe("formatShortTimestamp", () => { |
| 52 | 52 | }); |
| 53 | 53 | }); |
| 54 | 54 | |
| 55 | describe("resolveWeekStartsOn", () => { | |
| 56 | it.each([ | |
| 57 | ["en-US", 0], | |
| 58 | ["en-GB", 1], | |
| 59 | ["pl-PL", 1], | |
| 60 | ["ar-EG", 6], | |
| 61 | ])("starts the %s week on weekday %i", async (locale, weekday) => { | |
| 62 | const { resolveWeekStartsOn } = await import("./timestampFormat"); | |
| 63 | expect(resolveWeekStartsOn(locale)).toBe(weekday); | |
| 64 | }); | |
| 65 | ||
| 66 | it("leaves the default to the caller for a malformed locale", async () => { | |
| 67 | const { resolveWeekStartsOn } = await import("./timestampFormat"); | |
| 68 | expect(resolveWeekStartsOn("not a locale")).toBeUndefined(); | |
| 69 | }); | |
| 70 | ||
| 71 | it("follows the locale the desktop host reports", async () => { | |
| 72 | vi.stubGlobal("window", { desktopBridge: { getSystemLocale: () => "en-GB" } }); | |
| 73 | vi.resetModules(); | |
| 74 | const { weekStartsOn } = await import("./timestampFormat"); | |
| 75 | expect(weekStartsOn).toBe(1); | |
| 76 | vi.unstubAllGlobals(); | |
| 77 | vi.resetModules(); | |
| 78 | }); | |
| 79 | }); | |
| 80 | ||
| 55 | 81 | describe("formatChatTimestampTooltip", () => { |
| 56 | 82 | afterEach(() => { |
| 57 | 83 | vi.restoreAllMocks(); |
apps/web/src/timestampFormat.ts+29| ... | ... | @@ -53,6 +53,35 @@ function readHostSystemLocale(): string | null { |
| 53 | 53 | |
| 54 | 54 | const timestampLocale = resolveTimestampLocale(readHostSystemLocale()); |
| 55 | 55 | |
| 56 | const WEEKDAY_INDEXES = [0, 1, 2, 3, 4, 5, 6] as const; | |
| 57 | type WeekdayIndex = (typeof WEEKDAY_INDEXES)[number]; | |
| 58 | ||
| 59 | type LocaleWithWeekInfo = Intl.Locale & { | |
| 60 | readonly weekInfo?: { readonly firstDay: number }; | |
| 61 | getWeekInfo?: () => { readonly firstDay: number }; | |
| 62 | }; | |
| 63 | ||
| 64 | /** | |
| 65 | * First weekday of a locale as a `Date#getDay` index (0 is Sunday), or | |
| 66 | * `undefined` when the runtime has no week data, so callers keep their own | |
| 67 | * default. Without a locale it reads the runtime's. | |
| 68 | */ | |
| 69 | export function resolveWeekStartsOn(locale: string | undefined): WeekdayIndex | undefined { | |
| 70 | try { | |
| 71 | const resolved: LocaleWithWeekInfo = new Intl.Locale( | |
| 72 | locale ?? Intl.DateTimeFormat().resolvedOptions().locale, | |
| 73 | ); | |
| 74 | // Week info counts Monday as 1 and Sunday as 7. | |
| 75 | const firstDay = resolved.getWeekInfo?.().firstDay ?? resolved.weekInfo?.firstDay; | |
| 76 | return firstDay === undefined ? undefined : WEEKDAY_INDEXES[firstDay % 7]; | |
| 77 | } catch { | |
| 78 | return undefined; | |
| 79 | } | |
| 80 | } | |
| 81 | ||
| 82 | /** Week start for calendars, from the same locale timestamps are shown in. */ | |
| 83 | export const weekStartsOn = resolveWeekStartsOn(timestampLocale); | |
| 84 | ||
| 56 | 85 | const timestampFormatterCache = new Map<string, Intl.DateTimeFormat>(); |
| 57 | 86 | |
| 58 | 87 | function getTimestampFormatter( |