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__"
>
@@ -53,7 +53,7 @@ exports[`MessagePanel should handle lots of membership events quickly 1`] = `
aria-hidden="true"
class="mx_DateSeparator_dateHeading"
>
- Thu, Jan 1 1970
+ Thu, Jan 1, 1970
", () => {
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`] = `
>
@@ -59,7 +59,7 @@ exports[` should match the snapshot 1`] = `
aria-hidden="true"
class="mx_DateSeparator_dateHeading"
>
- Thu, Jan 1 1970
+ Thu, Jan 1, 1970
should support events with 1`] = `
>
@@ -175,7 +175,7 @@ exports[` should support events with 1`] = `
aria-hidden="true"
class="mx_DateSeparator_dateHeading"
>
- , NaN NaN
+ Thu, Jan 1, 1970
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`] = `
@@ -17,7 +17,7 @@ exports[`DateSeparator renders the date separator correctly 1`] = `
aria-hidden="true"
class="mx_DateSeparator_dateHeading"
>
- Today
+ today
@@ -50,7 +50,7 @@ exports[`DateSeparator when feature_jump_to_date is enabled renders the date sep
aria-hidden="true"
class="mx_DateSeparator_dateHeading"
>
- Fri, Dec 17 2021
+ Fri, Dec 17, 2021