From 3c52ba0c92dc4be617c2d11403bd596efde74100 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 21 Aug 2023 20:38:59 +0100 Subject: [PATCH] Use Intl to localise dates and times (#11422) * Use Intl to generate better internationalised date formats * Get `Yesterday` and `Today` from Intl also * Correct capitalisation blunder * Fix formatTime include weekday * Iterate * Fix tests * use jest setSystemTime * Discard changes to cypress/e2e/settings/general-user-settings-tab.spec.ts * Discard changes to res/css/_components.pcss * Discard changes to res/css/views/elements/_LanguageDropdown.pcss * Discard changes to src/components/views/elements/LanguageDropdown.tsx * Add docs & tests for getDaysArray & getMonthsArray * Discard changes to test/components/structures/__snapshots__/MatrixChat-test.tsx.snap * Consolidate consts * Improve testing & documentation * Update snapshot * Apply suggestions from code review Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Iterate * Clarify comments * Update src/DateUtils.ts Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Specify hourCycle * Discard changes to test/components/views/settings/devices/DeviceDetails-test.tsx * Update comments --------- Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> --- cypress/e2e/editing/editing.spec.ts | 4 +- res/css/views/messages/_DateSeparator.pcss | 1 + src/DateUtils.ts | 268 +++++++++++------- .../views/dialogs/ForwardDialog.tsx | 1 + .../views/messages/DateSeparator.tsx | 17 +- src/i18n/strings/en_EN.json | 34 --- src/languageHandler.tsx | 2 +- .../structures/TimelinePanel-test.tsx | 16 +- .../__snapshots__/MessagePanel-test.tsx.snap | 4 +- .../dialogs/MessageEditHistoryDialog-test.tsx | 2 +- .../MessageEditHistoryDialog-test.tsx.snap | 14 +- .../views/messages/DateSeparator-test.tsx | 8 +- .../__snapshots__/DateSeparator-test.tsx.snap | 8 +- .../views/rooms/SearchResultTile-test.tsx | 2 +- .../PinnedEventTile-test.tsx.snap | 2 +- test/test-utils/beacon.ts | 1 + test/test-utils/location.ts | 2 + test/test-utils/test-utils.ts | 1 + test/utils/DateUtils-test.ts | 244 +++++++++++++++- .../utils/exportUtils/PlainTextExport-test.ts | 4 +- .../__snapshots__/HTMLExport-test.ts.snap | 4 +- 21 files changed, 446 insertions(+), 193 deletions(-) diff --git a/cypress/e2e/editing/editing.spec.ts b/cypress/e2e/editing/editing.spec.ts index dafe15c885..b7dacf8603 100644 --- a/cypress/e2e/editing/editing.spec.ts +++ b/cypress/e2e/editing/editing.spec.ts @@ -119,7 +119,7 @@ describe("Editing", () => { // Assert that the date separator is rendered at the top cy.get("li:nth-child(1) .mx_DateSeparator").within(() => { cy.get("h2").within(() => { - cy.findByText("Today"); + cy.findByText("today").should("have.css", "text-transform", "capitalize"); }); }); @@ -184,7 +184,7 @@ describe("Editing", () => { // Assert that the date is rendered cy.get("li:nth-child(1) .mx_DateSeparator").within(() => { cy.get("h2").within(() => { - cy.findByText("Today"); + cy.findByText("today").should("have.css", "text-transform", "capitalize"); }); }); diff --git a/res/css/views/messages/_DateSeparator.pcss b/res/css/views/messages/_DateSeparator.pcss index 0a25cccaaf..52d263f688 100644 --- a/res/css/views/messages/_DateSeparator.pcss +++ b/res/css/views/messages/_DateSeparator.pcss @@ -40,6 +40,7 @@ limitations under the License. font-size: inherit; font-weight: inherit; color: inherit; + text-transform: capitalize; } .mx_DateSeparator_jumpToDateMenu { diff --git a/src/DateUtils.ts b/src/DateUtils.ts index e743b3feea..78b390a4aa 100644 --- a/src/DateUtils.ts +++ b/src/DateUtils.ts @@ -18,95 +18,121 @@ limitations under the License. import { Optional } from "matrix-events-sdk"; -import { _t } from "./languageHandler"; +import { _t, getUserLanguage } from "./languageHandler"; -function getDaysArray(): string[] { - return [_t("Sun"), _t("Mon"), _t("Tue"), _t("Wed"), _t("Thu"), _t("Fri"), _t("Sat")]; +export const MINUTE_MS = 60000; +export const HOUR_MS = MINUTE_MS * 60; +export const DAY_MS = HOUR_MS * 24; + +/** + * Returns array of 7 weekday names, from Sunday to Saturday, internationalised to the user's language. + * @param weekday - format desired "short" | "long" | "narrow" + */ +export function getDaysArray(weekday: Intl.DateTimeFormatOptions["weekday"] = "short"): string[] { + const sunday = 1672574400000; // 2023-01-01 12:00 UTC + const { format } = new Intl.DateTimeFormat(getUserLanguage(), { weekday, timeZone: "UTC" }); + return [...Array(7).keys()].map((day) => format(sunday + day * DAY_MS)); } -function getMonthsArray(): string[] { - return [ - _t("Jan"), - _t("Feb"), - _t("Mar"), - _t("Apr"), - _t("May"), - _t("Jun"), - _t("Jul"), - _t("Aug"), - _t("Sep"), - _t("Oct"), - _t("Nov"), - _t("Dec"), - ]; +/** + * Returns array of 12 month names, from January to December, internationalised to the user's language. + * @param month - format desired "numeric" | "2-digit" | "long" | "short" | "narrow" + */ +export function getMonthsArray(month: Intl.DateTimeFormatOptions["month"] = "short"): string[] { + const { format } = new Intl.DateTimeFormat(getUserLanguage(), { month, timeZone: "UTC" }); + return [...Array(12).keys()].map((m) => format(Date.UTC(2021, m))); } -function pad(n: number): string { - return (n < 10 ? "0" : "") + n; +// XXX: Ideally we could just specify `hour12: boolean` but it has issues on Chrome in the `en` locale +// https://support.google.com/chrome/thread/29828561?hl=en +function getTwelveHourOptions(showTwelveHour: boolean): Intl.DateTimeFormatOptions { + return { + hourCycle: showTwelveHour ? "h12" : "h23", + }; } -function twelveHourTime(date: Date, showSeconds = false): string { - let hours = date.getHours() % 12; - const minutes = pad(date.getMinutes()); - const ampm = date.getHours() >= 12 ? _t("PM") : _t("AM"); - hours = hours ? hours : 12; // convert 0 -> 12 - if (showSeconds) { - const seconds = pad(date.getSeconds()); - return `${hours}:${minutes}:${seconds}${ampm}`; - } - return `${hours}:${minutes}${ampm}`; -} - -export function formatDate(date: Date, showTwelveHour = false): string { +/** + * Formats a given date to a date & time string. + * + * The output format depends on how far away the given date is from now. + * Will use the browser's default time zone. + * If the date is today it will return a time string excluding seconds. See {@formatTime}. + * If the date is within the last 6 days it will return the name of the weekday along with the time string excluding seconds. + * If the date is within the same year then it will return the weekday, month and day of the month along with the time string excluding seconds. + * Otherwise, it will return a string representing the full date & time in a human friendly manner. See {@formatFullDate}. + * @param date - date object to format + * @param showTwelveHour - whether to use 12-hour rather than 24-hour time. Defaults to `false` (24 hour mode). + * Overrides the default from the locale, whether `true` or `false`. + * @param locale - the locale string to use, in BCP 47 format, defaulting to user's selected application locale + */ +export function formatDate(date: Date, showTwelveHour = false, locale?: string): string { + const _locale = locale ?? getUserLanguage(); const now = new Date(); - const days = getDaysArray(); - const months = getMonthsArray(); if (date.toDateString() === now.toDateString()) { - return formatTime(date, showTwelveHour); - } else if (now.getTime() - date.getTime() < 6 * 24 * 60 * 60 * 1000) { - // TODO: use standard date localize function provided in counterpart - return _t("%(weekDayName)s %(time)s", { - weekDayName: days[date.getDay()], - time: formatTime(date, showTwelveHour), - }); + return formatTime(date, showTwelveHour, _locale); + } else if (now.getTime() - date.getTime() < 6 * DAY_MS) { + // Time is within the last 6 days (or in the future) + return new Intl.DateTimeFormat(_locale, { + ...getTwelveHourOptions(showTwelveHour), + weekday: "short", + hour: "numeric", + minute: "2-digit", + }).format(date); } else if (now.getFullYear() === date.getFullYear()) { - // TODO: use standard date localize function provided in counterpart - return _t("%(weekDayName)s, %(monthName)s %(day)s %(time)s", { - weekDayName: days[date.getDay()], - monthName: months[date.getMonth()], - day: date.getDate(), - time: formatTime(date, showTwelveHour), - }); + return new Intl.DateTimeFormat(_locale, { + ...getTwelveHourOptions(showTwelveHour), + weekday: "short", + month: "short", + day: "numeric", + hour: "numeric", + minute: "2-digit", + }).format(date); } - return formatFullDate(date, showTwelveHour); + return formatFullDate(date, showTwelveHour, false, _locale); } -export function formatFullDateNoTime(date: Date): string { - const days = getDaysArray(); - const months = getMonthsArray(); - return _t("%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s", { - weekDayName: days[date.getDay()], - monthName: months[date.getMonth()], - day: date.getDate(), - fullYear: date.getFullYear(), - }); +/** + * Formats a given date to a human-friendly string with short weekday. + * Will use the browser's default time zone. + * @example "Thu, 17 Nov 2022" in en-GB locale + * @param date - date object to format + * @param locale - the locale string to use, in BCP 47 format, defaulting to user's selected application locale + */ +export function formatFullDateNoTime(date: Date, locale?: string): string { + return new Intl.DateTimeFormat(locale ?? getUserLanguage(), { + weekday: "short", + month: "short", + day: "numeric", + year: "numeric", + }).format(date); } -export function formatFullDate(date: Date, showTwelveHour = false, showSeconds = true): string { - const days = getDaysArray(); - const months = getMonthsArray(); - return _t("%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s", { - weekDayName: days[date.getDay()], - monthName: months[date.getMonth()], - day: date.getDate(), - fullYear: date.getFullYear(), - time: showSeconds ? formatFullTime(date, showTwelveHour) : formatTime(date, showTwelveHour), - }); +/** + * Formats a given date to a date & time string, optionally including seconds. + * Will use the browser's default time zone. + * @example "Thu, 17 Nov 2022, 4:58:32 pm" in en-GB locale with showTwelveHour=true and showSeconds=true + * @param date - date object to format + * @param showTwelveHour - whether to use 12-hour rather than 24-hour time. Defaults to `false` (24 hour mode). + * Overrides the default from the locale, whether `true` or `false`. + * @param showSeconds - whether to include seconds in the time portion of the string + * @param locale - the locale string to use, in BCP 47 format, defaulting to user's selected application locale + */ +export function formatFullDate(date: Date, showTwelveHour = false, showSeconds = true, locale?: string): string { + return new Intl.DateTimeFormat(locale ?? getUserLanguage(), { + ...getTwelveHourOptions(showTwelveHour), + weekday: "short", + month: "short", + day: "numeric", + year: "numeric", + hour: "numeric", + minute: "2-digit", + second: showSeconds ? "2-digit" : undefined, + }).format(date); } /** * Formats dates to be compatible with attributes of a ``. Dates - * should be formatted like "2020-06-23" (formatted according to ISO8601) + * should be formatted like "2020-06-23" (formatted according to ISO8601). * * @param date The date to format. * @returns The date string in ISO8601 format ready to be used with an `` @@ -115,22 +141,44 @@ export function formatDateForInput(date: Date): string { const year = `${date.getFullYear()}`.padStart(4, "0"); const month = `${date.getMonth() + 1}`.padStart(2, "0"); const day = `${date.getDate()}`.padStart(2, "0"); - const dateInputValue = `${year}-${month}-${day}`; - return dateInputValue; + return `${year}-${month}-${day}`; } -export function formatFullTime(date: Date, showTwelveHour = false): string { - if (showTwelveHour) { - return twelveHourTime(date, true); - } - return pad(date.getHours()) + ":" + pad(date.getMinutes()) + ":" + pad(date.getSeconds()); +/** + * Formats a given date to a time string including seconds. + * Will use the browser's default time zone. + * @example "4:58:32 PM" in en-GB locale with showTwelveHour=true + * @example "16:58:32" in en-GB locale with showTwelveHour=false + * @param date - date object to format + * @param showTwelveHour - whether to use 12-hour rather than 24-hour time. Defaults to `false` (24 hour mode). + * Overrides the default from the locale, whether `true` or `false`. + * @param locale - the locale string to use, in BCP 47 format, defaulting to user's selected application locale + */ +export function formatFullTime(date: Date, showTwelveHour = false, locale?: string): string { + return new Intl.DateTimeFormat(locale ?? getUserLanguage(), { + ...getTwelveHourOptions(showTwelveHour), + hour: "numeric", + minute: "2-digit", + second: "2-digit", + }).format(date); } -export function formatTime(date: Date, showTwelveHour = false): string { - if (showTwelveHour) { - return twelveHourTime(date); - } - return pad(date.getHours()) + ":" + pad(date.getMinutes()); +/** + * Formats a given date to a time string excluding seconds. + * Will use the browser's default time zone. + * @example "4:58 PM" in en-GB locale with showTwelveHour=true + * @example "16:58" in en-GB locale with showTwelveHour=false + * @param date - date object to format + * @param showTwelveHour - whether to use 12-hour rather than 24-hour time. Defaults to `false` (24 hour mode). + * Overrides the default from the locale, whether `true` or `false`. + * @param locale - the locale string to use, in BCP 47 format, defaulting to user's selected application locale + */ +export function formatTime(date: Date, showTwelveHour = false, locale?: string): string { + return new Intl.DateTimeFormat(locale ?? getUserLanguage(), { + ...getTwelveHourOptions(showTwelveHour), + hour: "numeric", + minute: "2-digit", + }).format(date); } export function formatSeconds(inSeconds: number): string { @@ -183,9 +231,8 @@ export function formatTimeLeft(inSeconds: number): string { }); } -const MILLIS_IN_DAY = 86400000; function withinPast24Hours(prevDate: Date, nextDate: Date): boolean { - return Math.abs(prevDate.getTime() - nextDate.getTime()) <= MILLIS_IN_DAY; + return Math.abs(prevDate.getTime() - nextDate.getTime()) <= DAY_MS; } function withinCurrentDay(prevDate: Date, nextDate: Date): boolean { @@ -210,15 +257,15 @@ export function wantsDateSeparator(prevEventDate: Optional, nextEventDate: } export function formatFullDateNoDay(date: Date): string { + const locale = getUserLanguage(); return _t("%(date)s at %(time)s", { - date: date.toLocaleDateString().replace(/\//g, "-"), - time: date.toLocaleTimeString().replace(/:/g, "-"), + date: date.toLocaleDateString(locale).replace(/\//g, "-"), + time: date.toLocaleTimeString(locale).replace(/:/g, "-"), }); } /** - * Returns an ISO date string without textual description of the date (ie: no "Wednesday" or - * similar) + * Returns an ISO date string without textual description of the date (ie: no "Wednesday" or similar) * @param date The date to format. * @returns The date string in ISO format. */ @@ -226,12 +273,23 @@ export function formatFullDateNoDayISO(date: Date): string { return date.toISOString(); } -export function formatFullDateNoDayNoTime(date: Date): string { - return date.getFullYear() + "/" + pad(date.getMonth() + 1) + "/" + pad(date.getDate()); +/** + * Formats a given date to a string. + * Will use the browser's default time zone. + * @example 17/11/2022 in en-GB locale + * @param date - date object to format + * @param locale - the locale string to use, in BCP 47 format, defaulting to user's selected application locale + */ +export function formatFullDateNoDayNoTime(date: Date, locale?: string): string { + return new Intl.DateTimeFormat(locale ?? getUserLanguage(), { + year: "numeric", + month: "numeric", + day: "numeric", + }).format(date); } export function formatRelativeTime(date: Date, showTwelveHour = false): string { - const now = new Date(Date.now()); + const now = new Date(); if (withinCurrentDay(date, now)) { return formatTime(date, showTwelveHour); } else { @@ -245,15 +303,11 @@ export function formatRelativeTime(date: Date, showTwelveHour = false): string { } } -const MINUTE_MS = 60000; -const HOUR_MS = MINUTE_MS * 60; -const DAY_MS = HOUR_MS * 24; - /** - * Formats duration in ms to human readable string - * Returns value in biggest possible unit (day, hour, min, second) + * Formats duration in ms to human-readable string + * Returns value in the biggest possible unit (day, hour, min, second) * Rounds values up until unit threshold - * ie. 23:13:57 -> 23h, 24:13:57 -> 1d, 44:56:56 -> 2d + * i.e. 23:13:57 -> 23h, 24:13:57 -> 1d, 44:56:56 -> 2d */ export function formatDuration(durationMs: number): string { if (durationMs >= DAY_MS) { @@ -269,9 +323,9 @@ export function formatDuration(durationMs: number): string { } /** - * Formats duration in ms to human readable string + * Formats duration in ms to human-readable string * Returns precise value down to the nearest second - * ie. 23:13:57 -> 23h 13m 57s, 44:56:56 -> 1d 20h 56m 56s + * i.e. 23:13:57 -> 23h 13m 57s, 44:56:56 -> 1d 20h 56m 56s */ export function formatPreciseDuration(durationMs: number): string { const days = Math.floor(durationMs / DAY_MS); @@ -293,13 +347,13 @@ export function formatPreciseDuration(durationMs: number): string { /** * Formats a timestamp to a short date - * (eg 25/12/22 in uk locale) - * localised by system locale + * Similar to {@formatFullDateNoDayNoTime} but with 2-digit on day, month, year. + * @example 25/12/22 in en-GB locale * @param timestamp - epoch timestamp + * @param locale - the locale string to use, in BCP 47 format, defaulting to user's selected application locale * @returns {string} formattedDate */ -export const formatLocalDateShort = (timestamp: number): string => - new Intl.DateTimeFormat( - undefined, // locales - { day: "2-digit", month: "2-digit", year: "2-digit" }, - ).format(timestamp); +export const formatLocalDateShort = (timestamp: number, locale?: string): string => + new Intl.DateTimeFormat(locale ?? getUserLanguage(), { day: "2-digit", month: "2-digit", year: "2-digit" }).format( + timestamp, + ); diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx index 13d84b99e7..a9b351be30 100644 --- a/src/components/views/dialogs/ForwardDialog.tsx +++ b/src/components/views/dialogs/ForwardDialog.tsx @@ -216,6 +216,7 @@ const ForwardDialog: React.FC = ({ matrixClient: cli, event, permalinkCr }, event_id: "$9999999999999999999999999999999999999999999", room_id: event.getRoomId(), + origin_server_ts: event.getTs(), }); mockEvent.sender = { name: profileInfo.displayname || userId, diff --git a/src/components/views/messages/DateSeparator.tsx b/src/components/views/messages/DateSeparator.tsx index 0e6815ed36..f9609c4c60 100644 --- a/src/components/views/messages/DateSeparator.tsx +++ b/src/components/views/messages/DateSeparator.tsx @@ -19,8 +19,8 @@ import React from "react"; import { Direction, ConnectionError, MatrixError, HTTPError } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; -import { _t } from "../../../languageHandler"; -import { formatFullDateNoDay, formatFullDateNoTime } from "../../../DateUtils"; +import { _t, getUserLanguage } from "../../../languageHandler"; +import { formatFullDateNoDay, formatFullDateNoTime, getDaysArray } from "../../../DateUtils"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import dispatcher from "../../../dispatcher/dispatcher"; import { Action } from "../../../dispatcher/actions"; @@ -40,10 +40,6 @@ import JumpToDatePicker from "./JumpToDatePicker"; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; import { SdkContextClass } from "../../../contexts/SDKContext"; -function getDaysArray(): string[] { - return [_t("Sunday"), _t("Monday"), _t("Tuesday"), _t("Wednesday"), _t("Thursday"), _t("Friday"), _t("Saturday")]; -} - interface IProps { roomId: string; ts: number; @@ -105,15 +101,16 @@ export default class DateSeparator extends React.Component { const today = new Date(); const yesterday = new Date(); - const days = getDaysArray(); + const days = getDaysArray("long"); yesterday.setDate(today.getDate() - 1); + const relativeTimeFormat = new Intl.RelativeTimeFormat(getUserLanguage(), { style: "long", numeric: "auto" }); if (date.toDateString() === today.toDateString()) { - return _t("Today"); + return relativeTimeFormat.format(0, "day"); // Today } else if (date.toDateString() === yesterday.toDateString()) { - return _t("Yesterday"); + return relativeTimeFormat.format(-1, "day"); // Yesterday } else if (today.getTime() - date.getTime() < 6 * 24 * 60 * 60 * 1000) { - return days[date.getDay()]; + return days[date.getDay()]; // Sunday-Saturday } else { return formatFullDateNoTime(date); } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9fb7f99f7f..170feb0e64 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -27,31 +27,6 @@ "Server may be unavailable, overloaded, or you hit a bug.": "Server may be unavailable, overloaded, or you hit a bug.", "The server does not support the room version specified.": "The server does not support the room version specified.", "Failure to create room": "Failure to create room", - "Sun": "Sun", - "Mon": "Mon", - "Tue": "Tue", - "Wed": "Wed", - "Thu": "Thu", - "Fri": "Fri", - "Sat": "Sat", - "Jan": "Jan", - "Feb": "Feb", - "Mar": "Mar", - "Apr": "Apr", - "May": "May", - "Jun": "Jun", - "Jul": "Jul", - "Aug": "Aug", - "Sep": "Sep", - "Oct": "Oct", - "Nov": "Nov", - "Dec": "Dec", - "PM": "PM", - "AM": "AM", - "%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s", - "%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(time)s", - "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s", - "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s", "%(hours)sh %(minutes)sm %(seconds)ss left": "%(hours)sh %(minutes)sm %(seconds)ss left", "%(minutes)sm %(seconds)ss left": "%(minutes)sm %(seconds)ss left", "%(seconds)ss left": "%(seconds)ss left", @@ -2401,15 +2376,6 @@ }, "%(name)s started a video call": "%(name)s started a video call", "Video call ended": "Video call ended", - "Sunday": "Sunday", - "Monday": "Monday", - "Tuesday": "Tuesday", - "Wednesday": "Wednesday", - "Thursday": "Thursday", - "Friday": "Friday", - "Saturday": "Saturday", - "Today": "Today", - "Yesterday": "Yesterday", "A network error occurred while trying to find and jump to the given date. Your homeserver might be down or there was just a temporary problem with your internet connection. Please try again. If this continues, please contact your homeserver administrator.": "A network error occurred while trying to find and jump to the given date. Your homeserver might be down or there was just a temporary problem with your internet connection. Please try again. If this continues, please contact your homeserver administrator.", "We were unable to find an event looking forwards from %(dateString)s. Try choosing an earlier date.": "We were unable to find an event looking forwards from %(dateString)s. Try choosing an earlier date.", "Server returned %(statusCode)s with error code %(errorCode)s": "Server returned %(statusCode)s with error code %(errorCode)s", diff --git a/src/languageHandler.tsx b/src/languageHandler.tsx index 0829282b6c..33d6bdd6c7 100644 --- a/src/languageHandler.tsx +++ b/src/languageHandler.tsx @@ -93,7 +93,7 @@ export class UserFriendlyError extends Error { export function getUserLanguage(): string { const language = SettingsStore.getValue("language", null, /*excludeDefault:*/ true); - if (language) { + if (typeof language === "string" && language !== "") { return language; } else { return normalizeLanguageKey(getLanguageFromBrowser()); diff --git a/test/components/structures/TimelinePanel-test.tsx b/test/components/structures/TimelinePanel-test.tsx index 8fd36c9d5a..97c9b52ab1 100644 --- a/test/components/structures/TimelinePanel-test.tsx +++ b/test/components/structures/TimelinePanel-test.tsx @@ -96,6 +96,7 @@ const mockEvents = (room: Room, count = 2): MatrixEvent[] => { type: EventType.RoomMessage, sender: "userId", content: createMessageEventContent("`Event${index}`"), + origin_server_ts: index, }), ); } @@ -447,7 +448,7 @@ describe("TimelinePanel", () => { render(); - const event = new MatrixEvent({ type: RoomEvent.Timeline }); + const event = new MatrixEvent({ type: RoomEvent.Timeline, origin_server_ts: 0 }); const data = { timeline: otherTimeline, liveEvent: true }; client.emit(RoomEvent.Timeline, event, room, false, false, data); @@ -463,7 +464,7 @@ describe("TimelinePanel", () => { render(); - const event = new MatrixEvent({ type: RoomEvent.Timeline }); + const event = new MatrixEvent({ type: RoomEvent.Timeline, origin_server_ts: 0 }); const data = { timeline: props.timelineSet.getLiveTimeline(), liveEvent: false }; client.emit(RoomEvent.Timeline, event, room, false, false, data); @@ -479,7 +480,7 @@ describe("TimelinePanel", () => { render(); - const event = new MatrixEvent({ type: RoomEvent.Timeline }); + const event = new MatrixEvent({ type: RoomEvent.Timeline, origin_server_ts: 0 }); const data = { timeline: props.timelineSet.getLiveTimeline(), liveEvent: false }; const toStartOfTimeline = true; client.emit(RoomEvent.Timeline, event, room, toStartOfTimeline, false, data); @@ -496,7 +497,7 @@ describe("TimelinePanel", () => { render(); - const event = new MatrixEvent({ type: RoomEvent.Timeline }); + const event = new MatrixEvent({ type: RoomEvent.Timeline, origin_server_ts: 0 }); const data = { timeline: props.timelineSet.getLiveTimeline(), liveEvent: true }; client.emit(RoomEvent.Timeline, event, room, false, false, data); @@ -521,7 +522,7 @@ describe("TimelinePanel", () => { await flushPromises(); - const event = new MatrixEvent({ type: RoomEvent.Timeline }); + const event = new MatrixEvent({ type: RoomEvent.Timeline, origin_server_ts: 0 }); const data = { timeline: props.timelineSet.getLiveTimeline(), liveEvent: true }; client.emit(RoomEvent.Timeline, event, room, false, false, data); @@ -539,11 +540,13 @@ describe("TimelinePanel", () => { type: "m.call.invite", room_id: virtualRoom.roomId, event_id: `virtualCallEvent1`, + origin_server_ts: 0, }); const virtualCallMetaEvent = new MatrixEvent({ type: "org.matrix.call.sdp_stream_metadata_changed", room_id: virtualRoom.roomId, event_id: `virtualCallEvent2`, + origin_server_ts: 0, }); const virtualEvents = [virtualCallInvite, ...mockEvents(virtualRoom), virtualCallMetaEvent]; const { timelineSet: overlayTimelineSet } = getProps(virtualRoom, virtualEvents); @@ -819,6 +822,7 @@ describe("TimelinePanel", () => { type: EventType.RoomMessage, sender: "userId", content: createMessageEventContent("ReplyEvent1"), + origin_server_ts: 0, }); reply2 = new MatrixEvent({ @@ -827,6 +831,7 @@ describe("TimelinePanel", () => { type: EventType.RoomMessage, sender: "userId", content: createMessageEventContent("ReplyEvent2"), + origin_server_ts: 0, }); root = new MatrixEvent({ @@ -835,6 +840,7 @@ describe("TimelinePanel", () => { type: EventType.RoomMessage, sender: "userId", content: createMessageEventContent("RootEvent"), + origin_server_ts: 0, }); const eventMap: { [key: string]: MatrixEvent } = { diff --git a/test/components/structures/__snapshots__/MessagePanel-test.tsx.snap b/test/components/structures/__snapshots__/MessagePanel-test.tsx.snap index c22cee69d1..0586b57fe1 100644 --- a/test/components/structures/__snapshots__/MessagePanel-test.tsx.snap +++ b/test/components/structures/__snapshots__/MessagePanel-test.tsx.snap @@ -39,7 +39,7 @@ exports[`MessagePanel should handle lots of membership events quickly 1`] = ` data-testid="__testid__" >
", () => { new MatrixEvent({ type: EventType.RoomMessage, room_id: roomId, - origin_server_ts: e.ts, + origin_server_ts: e.ts ?? 0, content: { body: e.msg, }, diff --git a/test/components/views/dialogs/__snapshots__/MessageEditHistoryDialog-test.tsx.snap b/test/components/views/dialogs/__snapshots__/MessageEditHistoryDialog-test.tsx.snap index 8c4dac800e..ed7efb2e2d 100644 --- a/test/components/views/dialogs/__snapshots__/MessageEditHistoryDialog-test.tsx.snap +++ b/test/components/views/dialogs/__snapshots__/MessageEditHistoryDialog-test.tsx.snap @@ -45,7 +45,7 @@ exports[` should match the snapshot 1`] = ` >

  • should support events with 1`] = ` >

  • should support events with 1`] = ` - NaN:NaN + 00:00
    should support events with 1`] = ` - NaN:NaN + 00:00
    should support events with 1`] = ` - NaN:NaN + 00:00
    { type TestCase = [string, number, string]; const testCases: TestCase[] = [ - ["the exact same moment", nowDate.getTime(), "Today"], - ["same day as current day", nowDate.getTime() - HOUR_MS, "Today"], - ["day before the current day", nowDate.getTime() - HOUR_MS * 12, "Yesterday"], + ["the exact same moment", nowDate.getTime(), "today"], + ["same day as current day", nowDate.getTime() - HOUR_MS, "today"], + ["day before the current day", nowDate.getTime() - HOUR_MS * 12, "yesterday"], ["2 days ago", nowDate.getTime() - DAY_MS * 2, "Wednesday"], - ["144 hours ago", nowDate.getTime() - HOUR_MS * 144, "Sat, Dec 11 2021"], + ["144 hours ago", nowDate.getTime() - HOUR_MS * 144, "Sat, Dec 11, 2021"], [ "6 days ago, but less than 144h", new Date("Saturday Dec 11 2021 23:59:00 GMT+0100 (Central European Standard Time)").getTime(), diff --git a/test/components/views/messages/__snapshots__/DateSeparator-test.tsx.snap b/test/components/views/messages/__snapshots__/DateSeparator-test.tsx.snap index d6823fbfa5..c3b6501973 100644 --- a/test/components/views/messages/__snapshots__/DateSeparator-test.tsx.snap +++ b/test/components/views/messages/__snapshots__/DateSeparator-test.tsx.snap @@ -3,7 +3,7 @@ exports[`DateSeparator renders the date separator correctly 1`] = `
  • @user49:example.com
    Message #49
  • @user48:example.com
    Message #48
  • @user47:example.com
    Message #47
  • @user46:example.com
    Message #46
  • @user45:example.com
    Message #45
  • @user44:example.com
    Message #44
  • @user43:example.com
    Message #43
  • @user42:example.com
    Message #42
  • @user41:example.com
    Message #41
  • @user40:example.com
    Message #40
  • @user39:example.com
    Message #39
  • @user38:example.com
    Message #38
  • @user37:example.com
    Message #37
  • @user36:example.com
    Message #36
  • @user35:example.com
    Message #35
  • @user34:example.com
    Message #34
  • @user33:example.com
    Message #33
  • @user32:example.com
    Message #32
  • @user31:example.com
    Message #31
  • @user30:example.com
    Message #30
  • @user29:example.com
    Message #29
  • @user28:example.com
    Message #28
  • @user27:example.com
    Message #27
  • @user26:example.com
    Message #26
  • @user25:example.com
    Message #25
  • @user24:example.com
    Message #24
  • @user23:example.com
    Message #23
  • @user22:example.com
    Message #22
  • @user21:example.com
    Message #21
  • @user20:example.com
    Message #20
  • @user19:example.com
    Message #19
  • @user18:example.com
    Message #18
  • @user17:example.com
    Message #17
  • @user16:example.com
    Message #16
  • @user15:example.com
    Message #15
  • @user14:example.com
    Message #14
  • @user13:example.com
    Message #13
  • @user12:example.com
    Message #12
  • @user11:example.com
    Message #11
  • @user10:example.com
    Message #10
  • @user9:example.com
    Message #9
  • @user8:example.com
    Message #8
  • @user7:example.com
    Message #7
  • @user6:example.com
    Message #6
  • @user5:example.com
    Message #5
  • @user4:example.com
    Message #4
  • @user3:example.com
    Message #3
  • @user2:example.com
    Message #2
  • @user1:example.com
    Message #1
  • @user0:example.com
    Message #0
  • +
  • @user49:example.com
    Message #49
  • @user48:example.com
    Message #48
  • @user47:example.com
    Message #47
  • @user46:example.com
    Message #46
  • @user45:example.com
    Message #45
  • @user44:example.com
    Message #44
  • @user43:example.com
    Message #43
  • @user42:example.com
    Message #42
  • @user41:example.com
    Message #41
  • @user40:example.com
    Message #40
  • @user39:example.com
    Message #39
  • @user38:example.com
    Message #38
  • @user37:example.com
    Message #37
  • @user36:example.com
    Message #36
  • @user35:example.com
    Message #35
  • @user34:example.com
    Message #34
  • @user33:example.com
    Message #33
  • @user32:example.com
    Message #32
  • @user31:example.com
    Message #31
  • @user30:example.com
    Message #30
  • @user29:example.com
    Message #29
  • @user28:example.com
    Message #28
  • @user27:example.com
    Message #27
  • @user26:example.com
    Message #26
  • @user25:example.com
    Message #25
  • @user24:example.com
    Message #24
  • @user23:example.com
    Message #23
  • @user22:example.com
    Message #22
  • @user21:example.com
    Message #21
  • @user20:example.com
    Message #20
  • @user19:example.com
    Message #19
  • @user18:example.com
    Message #18
  • @user17:example.com
    Message #17
  • @user16:example.com
    Message #16
  • @user15:example.com
    Message #15
  • @user14:example.com
    Message #14
  • @user13:example.com
    Message #13
  • @user12:example.com
    Message #12
  • @user11:example.com
    Message #11
  • @user10:example.com
    Message #10
  • @user9:example.com
    Message #9
  • @user8:example.com
    Message #8
  • @user7:example.com
    Message #7
  • @user6:example.com
    Message #6
  • @user5:example.com
    Message #5
  • @user4:example.com
    Message #4
  • @user3:example.com
    Message #3
  • @user2:example.com
    Message #2
  • @user1:example.com
    Message #1
  • @user0:example.com
    Message #0