From e547c466a8ba3c19810d6cd11d42abdd402e46a9 Mon Sep 17 00:00:00 2001 From: heaven Date: Sun, 3 May 2026 19:27:54 +0300 Subject: [PATCH] feat(settings): drop user-facing time/date format toggle and derive everything from system locale via Intl.DateTimeFormat --- docs/ai/architecture.md | 2 +- public/locales/en.json | 21 - public/locales/ru.json | 21 - src/app/components/message/Time.tsx | 19 +- src/app/components/room-intro/RoomIntro.tsx | 6 +- src/app/components/time-date/TimePicker.tsx | 21 +- .../user-profile/UserModeration.tsx | 23 +- .../features/message-search/MessageSearch.tsx | 5 - .../message-search/SearchResultGroup.tsx | 10 +- src/app/features/room-nav/DmStreamRow.tsx | 7 +- src/app/features/room/RoomTimeline.tsx | 23 - .../features/room/jump-to-time/JumpToTime.tsx | 6 +- src/app/features/room/message/Message.tsx | 13 +- .../room/room-pin-menu/RoomPinMenu.tsx | 15 +- .../features/settings/devices/DeviceTile.tsx | 8 +- src/app/features/settings/general/General.tsx | 392 +----------------- src/app/hooks/useDateFormat.ts | 38 -- src/app/state/settings.ts | 29 +- src/app/utils/time.ts | 35 +- 19 files changed, 81 insertions(+), 613 deletions(-) delete mode 100644 src/app/hooks/useDateFormat.ts diff --git a/docs/ai/architecture.md b/docs/ai/architecture.md index 1bf1e858..162da374 100644 --- a/docs/ai/architecture.md +++ b/docs/ai/architecture.md @@ -148,7 +148,7 @@ These are vojo additions on top of stock Cinny — they crossed many recent stab **Jotai** atoms in `src/app/state/`: -- `settings.ts` — User preferences (`themeId`, `useSystemTheme`, `monochromeMode`, `hideMembershipEvents`, `hideNickAvatarEvents`, …). Persisted to `localStorage['settings']`. The `MessageLayout` enum + `messageSpacing` + `legacyUsernameColor` fields were dropped in P3c; the new `dawn-p3c-cleanup` migration in `getSettings()` strips the orphan keys from existing users' persisted JSON on first load. +- `settings.ts` — User preferences (`themeId`, `useSystemTheme`, `monochromeMode`, `hideMembershipEvents`, `hideNickAvatarEvents`, …). Persisted to `localStorage['settings']`. The `MessageLayout` enum + `messageSpacing` + `legacyUsernameColor` fields were dropped in P3c; the new `dawn-p3c-cleanup` migration in `getSettings()` strips the orphan keys from existing users' persisted JSON on first load. The user-facing `hour24Clock` / `dateFormatString` fields were removed too — both now derive from the runtime locale via `Intl.DateTimeFormat` in `utils/time.ts` (24-hour locales → `HH:mm` + `DD/MM/YYYY`; AM/PM locales → `hh:mm A` + `MM/DD/YYYY`). The `system-time-format-cleanup` migration synchronously deletes those keys on first load. **Known platform limitation**: Android's manual «Use 24-hour format» toggle in Date & Time settings is invisible to JS — `Intl` reads only CLDR locale conventions. Russian-locale users with AM/PM toggle get 24-hour format on both web and Capacitor; only a native bridge to `android.text.format.DateFormat.is24HourFormat(context)` would respect that toggle. - `sessions.ts` — Active session - `upload.ts` — Upload progress (in-memory) - `room/` — `roomInputDrafts` (in-memory), `roomToParents`, `roomToUnread` diff --git a/public/locales/en.json b/public/locales/en.json index 26f604f0..e22e2952 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -99,27 +99,6 @@ "monochrome_mode": "Monochrome Mode", "twitter_emoji": "Twitter Emoji", "page_zoom": "Page Zoom", - "date_time": "Date & Time", - "hour_24": "24-Hour Time Format", - "date_format": "Date Format", - "custom": "Custom", - "formatting": "Formatting", - "year": "Year", - "two_digit_year": "Two-digit year", - "four_digit_year": "Four-digit year", - "month": "Month", - "the_month": "The month", - "two_digit_month": "Two-digit month", - "short_month_name": "Short month name", - "full_month_name": "Full month name", - "day_of_month": "Day of the Month", - "day_of_month_val": "Day of the month", - "two_digit_day": "Two-digit day of the month", - "day_of_week": "Day of the Week", - "day_of_week_sunday": "Day of the week (Sunday = 0)", - "two_letter_day": "Two-letter day name", - "short_day_name": "Short day name", - "full_day_name": "Full day name", "save": "Save", "editor": "Editor", "enter_newline": "ENTER for Newline", diff --git a/public/locales/ru.json b/public/locales/ru.json index 341dba26..e581aac2 100644 --- a/public/locales/ru.json +++ b/public/locales/ru.json @@ -99,27 +99,6 @@ "monochrome_mode": "Монохромный режим", "twitter_emoji": "Эмодзи Twitter", "page_zoom": "Масштаб страницы", - "date_time": "Дата и время", - "hour_24": "24-часовой формат", - "date_format": "Формат даты", - "custom": "Пользовательский", - "formatting": "Форматирование", - "year": "Год", - "two_digit_year": "Двузначный год", - "four_digit_year": "Четырёхзначный год", - "month": "Месяц", - "the_month": "Месяц", - "two_digit_month": "Двузначный месяц", - "short_month_name": "Краткое название месяца", - "full_month_name": "Полное название месяца", - "day_of_month": "День месяца", - "day_of_month_val": "День месяца", - "two_digit_day": "Двузначный день месяца", - "day_of_week": "День недели", - "day_of_week_sunday": "День недели (воскресенье = 0)", - "two_letter_day": "Двухбуквенное название дня", - "short_day_name": "Краткое название дня", - "full_day_name": "Полное название дня", "save": "Сохранить", "editor": "Редактор", "enter_newline": "ENTER для новой строки", diff --git a/src/app/components/message/Time.tsx b/src/app/components/message/Time.tsx index b15f8bf7..662cd5cb 100644 --- a/src/app/components/message/Time.tsx +++ b/src/app/components/message/Time.tsx @@ -6,26 +6,17 @@ import { timeDayMonYear, timeHourMinute, today, yesterday } from '../../utils/ti export type TimeProps = { compact?: boolean; ts: number; - hour24Clock: boolean; - dateFormatString: string; }; /** - * Renders a formatted timestamp, supporting compact and full display modes. + * Renders a formatted timestamp using the system locale's hour and date format. * - * Displays the time in hour:minute format if the message is from today, yesterday, or if `compact` is true. - * For older messages, it shows the date and time. - * - * @param {number} ts - The timestamp to display. - * @param {boolean} [compact=false] - If true, always show only the time. - * @param {boolean} hour24Clock - Whether to use 24-hour time format. - * @param {string} dateFormatString - Format string for the date part. - * @returns {React.ReactElement} A element with the formatted date/time. + * Today/yesterday/`compact` show time only; older messages show date + time. */ export const Time = as<'span', TimeProps & ComponentProps>( - ({ compact, hour24Clock, dateFormatString, ts, ...props }, ref) => { + ({ compact, ts, ...props }, ref) => { const { t } = useTranslation(); - const formattedTime = timeHourMinute(ts, hour24Clock); + const formattedTime = timeHourMinute(ts); let time = ''; if (compact) { @@ -35,7 +26,7 @@ export const Time = as<'span', TimeProps & ComponentProps>( } else if (yesterday(ts)) { time = `${t('Room.yesterday')} ${formattedTime}`; } else { - time = `${timeDayMonYear(ts, dateFormatString)} ${formattedTime}`; + time = `${timeDayMonYear(ts)} ${formattedTime}`; } return ( diff --git a/src/app/components/room-intro/RoomIntro.tsx b/src/app/components/room-intro/RoomIntro.tsx index 2381a75d..4de2a67e 100644 --- a/src/app/components/room-intro/RoomIntro.tsx +++ b/src/app/components/room-intro/RoomIntro.tsx @@ -14,8 +14,6 @@ import { nameInitials } from '../../utils/common'; import { useRoomAvatar, useRoomName, useRoomTopic } from '../../hooks/useRoomMeta'; import { useIsOneOnOne } from '../../hooks/useRoom'; import { useMediaAuthentication } from '../../hooks/useMediaAuthentication'; -import { useSetting } from '../../state/hooks/settings'; -import { settingsAtom } from '../../state/settings'; import { InviteUserPrompt } from '../invite-user-prompt'; export type RoomIntroProps = { @@ -51,8 +49,6 @@ export const RoomIntro = as<'div', RoomIntroProps>(({ room, ...props }, ref) => useCallback(async (roomId: string) => mx.joinRoom(roomId), [mx]) ); - const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock'); - return ( @@ -80,7 +76,7 @@ export const RoomIntro = as<'div', RoomIntroProps>(({ room, ...props }, ref) => values={{ creator: creatorName, date: timeDayMonthYear(ts), - time: timeHourMinute(ts, hour24Clock), + time: timeHourMinute(ts), }} components={{ bold: }} /> diff --git a/src/app/components/time-date/TimePicker.tsx b/src/app/components/time-date/TimePicker.tsx index c835ed09..b3110a8e 100644 --- a/src/app/components/time-date/TimePicker.tsx +++ b/src/app/components/time-date/TimePicker.tsx @@ -3,9 +3,14 @@ import { Menu, Box, Text, Chip } from 'folds'; import dayjs from 'dayjs'; import * as css from './styles.css'; import { PickerColumn } from './PickerColumn'; -import { hour12to24, hour24to12, hoursToMs, inSameDay, minutesToMs } from '../../utils/time'; -import { useSetting } from '../../state/hooks/settings'; -import { settingsAtom } from '../../state/settings'; +import { + SYSTEM_HOUR_24, + hour12to24, + hour24to12, + hoursToMs, + inSameDay, + minutesToMs, +} from '../../utils/time'; type TimePickerProps = { min: number; @@ -15,11 +20,9 @@ type TimePickerProps = { }; export const TimePicker = forwardRef( ({ min, max, value, onChange }, ref) => { - const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock'); - const hour24 = dayjs(value).hour(); - const selectedHour = hour24Clock ? hour24 : hour24to12(hour24); + const selectedHour = SYSTEM_HOUR_24 ? hour24 : hour24to12(hour24); const selectedMinute = dayjs(value).minute(); const selectedPM = hour24 >= 12; @@ -28,7 +31,7 @@ export const TimePicker = forwardRef( }; const handleHour = (hour: number) => { - const seconds = hoursToMs(hour24Clock ? hour : hour12to24(hour, selectedPM)); + const seconds = hoursToMs(SYSTEM_HOUR_24 ? hour : hour12to24(hour, selectedPM)); const lastSeconds = hoursToMs(hour24); const newValue = value + (seconds - lastSeconds); handleSubmit(newValue); @@ -63,7 +66,7 @@ export const TimePicker = forwardRef( - {hour24Clock + {SYSTEM_HOUR_24 ? Array.from(Array(24).keys()).map((hour) => ( ( ))} - {!hour24Clock && ( + {!SYSTEM_HOUR_24 && ( @@ -66,11 +61,8 @@ type UserBanAlertProps = { export function UserBanAlert({ userId, reason, canUnban, bannedBy, ts }: UserBanAlertProps) { const mx = useMatrixClient(); const room = useRoom(); - const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock'); - const [dateFormatString] = useSetting(settingsAtom, 'dateFormatString'); - - const time = ts ? timeHourMinute(ts, hour24Clock) : undefined; - const date = ts ? timeDayMonYear(ts, dateFormatString) : undefined; + const time = ts ? timeHourMinute(ts) : undefined; + const date = ts ? timeDayMonYear(ts) : undefined; const [unbanState, unban] = useAsyncCallback( useCallback(async () => { @@ -141,11 +133,8 @@ type UserInviteAlertProps = { export function UserInviteAlert({ userId, reason, canKick, invitedBy, ts }: UserInviteAlertProps) { const mx = useMatrixClient(); const room = useRoom(); - const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock'); - const [dateFormatString] = useSetting(settingsAtom, 'dateFormatString'); - - const time = ts ? timeHourMinute(ts, hour24Clock) : undefined; - const date = ts ? timeDayMonYear(ts, dateFormatString) : undefined; + const time = ts ? timeHourMinute(ts) : undefined; + const date = ts ? timeDayMonYear(ts) : undefined; const [kickState, kick] = useAsyncCallback( useCallback(async () => { diff --git a/src/app/features/message-search/MessageSearch.tsx b/src/app/features/message-search/MessageSearch.tsx index d2512c0d..0a3131ba 100644 --- a/src/app/features/message-search/MessageSearch.tsx +++ b/src/app/features/message-search/MessageSearch.tsx @@ -60,9 +60,6 @@ export function MessageSearch({ const [mediaAutoLoad] = useSetting(settingsAtom, 'mediaAutoLoad'); const [urlPreview] = useSetting(settingsAtom, 'urlPreview'); - const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock'); - const [dateFormatString] = useSetting(settingsAtom, 'dateFormatString'); - const searchInputRef = useRef(null); const scrollTopAnchorRef = useRef(null); const [searchParams, setSearchParams] = useSearchParams(); @@ -299,8 +296,6 @@ export function MessageSearch({ urlPreview={urlPreview} onOpen={navigateRoom} legacyUsernameColor={isOneOnOneRoom(groupRoom)} - hour24Clock={hour24Clock} - dateFormatString={dateFormatString} /> ); diff --git a/src/app/features/message-search/SearchResultGroup.tsx b/src/app/features/message-search/SearchResultGroup.tsx index 39a153d9..fd0567fe 100644 --- a/src/app/features/message-search/SearchResultGroup.tsx +++ b/src/app/features/message-search/SearchResultGroup.tsx @@ -61,8 +61,6 @@ type SearchResultGroupProps = { urlPreview?: boolean; onOpen: (roomId: string, eventId: string) => void; legacyUsernameColor?: boolean; - hour24Clock: boolean; - dateFormatString: string; }; export function SearchResultGroup({ room, @@ -72,8 +70,6 @@ export function SearchResultGroup({ urlPreview, onOpen, legacyUsernameColor, - hour24Clock, - dateFormatString, }: SearchResultGroupProps) { const { t } = useTranslation(); const mx = useMatrixClient(); @@ -292,11 +288,7 @@ export function SearchResultGroup({ {tagIconSrc && } -