mirror of https://github.com/zulip/zulip.git
i18n: Localize date and time displays across the app.
Implemented date localization using native Intl object. Created special function get_localized_date_or_time_for_format Made necessary string formatting changes in 'timerender.ts'. Fixed tests and added some localization tests too. Tested on my local development server, with some random languages. Fixes #23987.
This commit is contained in:
parent
fe654b76b7
commit
a0d15f3029
|
@ -39,6 +39,7 @@ import * as settings_bots from "./settings_bots";
|
|||
import * as settings_config from "./settings_config";
|
||||
import * as settings_users from "./settings_users";
|
||||
import * as stream_popover from "./stream_popover";
|
||||
import * as timerender from "./timerender";
|
||||
import * as ui_report from "./ui_report";
|
||||
import * as user_groups from "./user_groups";
|
||||
import * as user_profile from "./user_profile";
|
||||
|
@ -247,14 +248,15 @@ function render_user_info_popover(
|
|||
|
||||
let date_joined;
|
||||
if (spectator_view) {
|
||||
const dateFormat = new Intl.DateTimeFormat("default", {dateStyle: "long"});
|
||||
date_joined = dateFormat.format(parseISO(user.date_joined));
|
||||
date_joined = timerender.get_localized_date_or_time_for_format(
|
||||
parseISO(user.date_joined),
|
||||
"dayofyear_year",
|
||||
);
|
||||
}
|
||||
// Filtering out only those profile fields that can be display in the popover and are not empty.
|
||||
const dateFormat = new Intl.DateTimeFormat("default", {dateStyle: "long"});
|
||||
const field_types = page_params.custom_profile_field_types;
|
||||
const display_profile_fields = page_params.custom_profile_fields
|
||||
.map((f) => user_profile.get_custom_profile_field_data(user, f, field_types, dateFormat))
|
||||
.map((f) => user_profile.get_custom_profile_field_data(user, f, field_types))
|
||||
.filter((f) => f.display_in_profile_summary && f.value !== undefined && f.value !== null);
|
||||
|
||||
const args = {
|
||||
|
|
|
@ -18,6 +18,7 @@ import * as settings_display from "./settings_display";
|
|||
import * as settings_panel_menu from "./settings_panel_menu";
|
||||
import * as settings_sections from "./settings_sections";
|
||||
import * as settings_toggle from "./settings_toggle";
|
||||
import * as timerender from "./timerender";
|
||||
import {user_settings} from "./user_settings";
|
||||
|
||||
export let settings_label;
|
||||
|
@ -65,8 +66,10 @@ function setup_settings_label() {
|
|||
|
||||
function get_parsed_date_of_joining() {
|
||||
const user_date_joined = people.get_by_user_id(page_params.user_id, false).date_joined;
|
||||
const dateFormat = new Intl.DateTimeFormat("default", {dateStyle: "long"});
|
||||
return dateFormat.format(parseISO(user_date_joined));
|
||||
return timerender.get_localized_date_or_time_for_format(
|
||||
parseISO(user_date_joined),
|
||||
"dayofyear_year",
|
||||
);
|
||||
}
|
||||
|
||||
export function build_page() {
|
||||
|
|
|
@ -24,6 +24,94 @@ export function clear_for_testing(): void {
|
|||
next_timerender_id = 0;
|
||||
}
|
||||
|
||||
type DateFormat = "weekday" | "dayofyear" | "weekday_dayofyear_year" | "dayofyear_year";
|
||||
type DateWithTimeFormat = "dayofyear_time" | "dayofyear_year_time" | "weekday_dayofyear_year_time";
|
||||
type TimeFormat = "time" | "time_sec";
|
||||
|
||||
type DateOrTimeFormat = DateFormat | TimeFormat | DateWithTimeFormat;
|
||||
|
||||
// Translates Zulip-specific format names, documented in the comments
|
||||
// below, into the appropriate options to pass to the Intl library
|
||||
// along with the user's locale to render date in that style of format.
|
||||
//
|
||||
// Note that because date/time formats vary with locale, the below
|
||||
// examples are what a user with English as their language will see
|
||||
// but users in other locales will see something different, especially
|
||||
// for any formats that display the name for a month/weekday, but
|
||||
// possibly in more subtle ways for languages with different
|
||||
// punctuation schemes for date and times.
|
||||
function get_format_options_for_type(type: DateOrTimeFormat): Intl.DateTimeFormatOptions {
|
||||
const is_twenty_four_hour_time = user_settings.twenty_four_hour_time;
|
||||
|
||||
const time_format_options: Intl.DateTimeFormatOptions = is_twenty_four_hour_time
|
||||
? {hourCycle: "h23", hour: "2-digit", minute: "2-digit"}
|
||||
: {
|
||||
hourCycle: "h12",
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
};
|
||||
|
||||
const weekday_format_options: Intl.DateTimeFormatOptions = {weekday: "long"};
|
||||
const full_format_options: Intl.DateTimeFormatOptions = {dateStyle: "full"};
|
||||
|
||||
const dayofyear_format_options: Intl.DateTimeFormatOptions = {day: "numeric", month: "short"};
|
||||
const dayofyear_year_format_options: Intl.DateTimeFormatOptions = {
|
||||
...dayofyear_format_options,
|
||||
year: "numeric",
|
||||
};
|
||||
const long_format_options: Intl.DateTimeFormatOptions = {
|
||||
...dayofyear_year_format_options,
|
||||
weekday: "short",
|
||||
};
|
||||
|
||||
switch (type) {
|
||||
case "time": // 01:30 PM
|
||||
return time_format_options;
|
||||
case "time_sec": // 01:30:42 PM
|
||||
return {...time_format_options, second: "2-digit"};
|
||||
case "weekday": // Wednesday
|
||||
return weekday_format_options;
|
||||
case "dayofyear": // Jul 27
|
||||
return dayofyear_format_options;
|
||||
case "dayofyear_time": // Jul 27, 01:30 PM
|
||||
return {...dayofyear_format_options, ...time_format_options};
|
||||
case "dayofyear_year": // Jul 27, 2016
|
||||
return dayofyear_year_format_options;
|
||||
case "dayofyear_year_time": // Jul 27, 2016, 01:30 PM
|
||||
return {...dayofyear_year_format_options, ...time_format_options};
|
||||
case "weekday_dayofyear_year": // Wednesday, July 27, 2016
|
||||
return full_format_options;
|
||||
case "weekday_dayofyear_year_time": // Wed, Jul 27, 2016, 13:30
|
||||
return {...long_format_options, ...time_format_options};
|
||||
default:
|
||||
throw new Error("Wrong format provided.");
|
||||
}
|
||||
}
|
||||
|
||||
function get_user_locale(): string {
|
||||
const user_default_language = user_settings.default_language;
|
||||
let locale = "";
|
||||
try {
|
||||
locale = Intl.DateTimeFormat.supportedLocalesOf(user_default_language)[0];
|
||||
} catch {
|
||||
locale = "default";
|
||||
}
|
||||
return locale;
|
||||
}
|
||||
|
||||
// Common function for all date/time rendering in the project. Handles
|
||||
// localization using the user's configured locale and the
|
||||
// twenty_four_hour_time setting.
|
||||
//
|
||||
// See get_format_options_for_type for details on the supported formats.
|
||||
export function get_localized_date_or_time_for_format(
|
||||
date: Date | number,
|
||||
format: DateOrTimeFormat,
|
||||
): string {
|
||||
const locale = get_user_locale();
|
||||
return new Intl.DateTimeFormat(locale, get_format_options_for_type(format)).format(date);
|
||||
}
|
||||
|
||||
// Exported for tests only.
|
||||
export function get_tz_with_UTC_offset(time: number | Date): string {
|
||||
const tz_offset = format(time, "xxx");
|
||||
|
@ -66,10 +154,7 @@ export function render_now(time: Date, today = new Date()): TimeRender {
|
|||
let time_str = "";
|
||||
let needs_update = false;
|
||||
// render formal time to be used for tippy tooltip
|
||||
// "\xa0" is U+00A0 NO-BREAK SPACE.
|
||||
// Can't use as that represents the literal string " ".
|
||||
const formal_time_str = format(time, "EEEE,\u00A0MMMM\u00A0d,\u00A0yyyy");
|
||||
|
||||
const formal_time_str = get_localized_date_or_time_for_format(time, "weekday_dayofyear_year");
|
||||
// How many days old is 'time'? 0 = today, 1 = yesterday, 7 = a
|
||||
// week ago, -1 = tomorrow, etc.
|
||||
|
||||
|
@ -87,12 +172,12 @@ export function render_now(time: Date, today = new Date()): TimeRender {
|
|||
} else if (time.getFullYear() !== today.getFullYear()) {
|
||||
// For long running servers, searching backlog can get ambiguous
|
||||
// without a year stamp. Only show year if message is from an older year
|
||||
time_str = format(time, "MMM\u00A0dd,\u00A0yyyy");
|
||||
time_str = get_localized_date_or_time_for_format(time, "dayofyear_year");
|
||||
needs_update = false;
|
||||
} else {
|
||||
// For now, if we get a message from tomorrow, we don't bother
|
||||
// rewriting the timestamp when it gets to be tomorrow.
|
||||
time_str = format(time, "MMM\u00A0dd");
|
||||
time_str = get_localized_date_or_time_for_format(time, "dayofyear");
|
||||
needs_update = false;
|
||||
}
|
||||
return {
|
||||
|
@ -139,12 +224,22 @@ export function last_seen_status_from_date(
|
|||
// Online more than 90 days ago, in the same year
|
||||
return $t(
|
||||
{defaultMessage: "{last_active_date}"},
|
||||
{last_active_date: format(last_active_date, "MMM\u00A0dd")},
|
||||
{
|
||||
last_active_date: get_localized_date_or_time_for_format(
|
||||
last_active_date,
|
||||
"dayofyear",
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
return $t(
|
||||
{defaultMessage: "{last_active_date}"},
|
||||
{last_active_date: format(last_active_date, "MMM\u00A0dd,\u00A0yyyy")},
|
||||
{
|
||||
last_active_date: get_localized_date_or_time_for_format(
|
||||
last_active_date,
|
||||
"dayofyear_year",
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -201,8 +296,7 @@ export function render_date(time: Date, today: Date): JQuery {
|
|||
|
||||
// Renders the timestamp returned by the <time:> Markdown syntax.
|
||||
export function format_markdown_time(time: number | Date): string {
|
||||
const hourformat = user_settings.twenty_four_hour_time ? "HH:mm" : "h:mm a";
|
||||
return format(time, "E, MMM d yyyy, " + hourformat);
|
||||
return get_localized_date_or_time_for_format(time, "weekday_dayofyear_year_time");
|
||||
}
|
||||
|
||||
export function get_markdown_time_tooltip(reference: HTMLElement): DocumentFragment | string {
|
||||
|
@ -270,10 +364,7 @@ export function get_timestamp_for_flatpickr(timestring: string): Date {
|
|||
}
|
||||
|
||||
export function stringify_time(time: number | Date): string {
|
||||
if (user_settings.twenty_four_hour_time) {
|
||||
return format(time, "HH:mm");
|
||||
}
|
||||
return format(time, "h:mm a");
|
||||
return get_localized_date_or_time_for_format(time, "time");
|
||||
}
|
||||
|
||||
export function format_time_modern(time: number | Date, today = new Date()): String {
|
||||
|
@ -282,18 +373,18 @@ export function format_time_modern(time: number | Date, today = new Date()): Str
|
|||
|
||||
if (time > today) {
|
||||
/* For timestamps in the future, we always show the year*/
|
||||
return format(time, "MMM\u00A0dd,\u00A0yyyy");
|
||||
return get_localized_date_or_time_for_format(time, "dayofyear_year");
|
||||
} else if (hours < 24) {
|
||||
return stringify_time(time);
|
||||
} else if (days_old === 1) {
|
||||
return $t({defaultMessage: "Yesterday"});
|
||||
} else if (days_old < 7) {
|
||||
return format(time, "EEEE");
|
||||
return get_localized_date_or_time_for_format(time, "weekday");
|
||||
} else if (days_old <= 180) {
|
||||
return format(time, "MMM\u00A0dd");
|
||||
return get_localized_date_or_time_for_format(time, "dayofyear");
|
||||
}
|
||||
|
||||
return format(time, "MMM\u00A0dd,\u00A0yyyy");
|
||||
return get_localized_date_or_time_for_format(time, "dayofyear_year");
|
||||
}
|
||||
|
||||
// this is for rendering absolute time based off the preferences for twenty-four
|
||||
|
@ -301,29 +392,17 @@ export function format_time_modern(time: number | Date, today = new Date()): Str
|
|||
export function absolute_time(timestamp: number, today = new Date()): string {
|
||||
const date = new Date(timestamp);
|
||||
const is_older_year = today.getFullYear() - date.getFullYear() > 0;
|
||||
const H_24 = user_settings.twenty_four_hour_time;
|
||||
|
||||
return format(
|
||||
return get_localized_date_or_time_for_format(
|
||||
date,
|
||||
is_older_year
|
||||
? H_24
|
||||
? "MMM d, yyyy HH:mm"
|
||||
: "MMM d, yyyy hh:mm a"
|
||||
: H_24
|
||||
? "MMM d HH:mm"
|
||||
: "MMM d hh:mm a",
|
||||
is_older_year ? "dayofyear_year_time" : "dayofyear_time",
|
||||
);
|
||||
}
|
||||
|
||||
export function get_full_datetime(time: Date): string {
|
||||
const time_options: Intl.DateTimeFormatOptions = {timeStyle: "medium"};
|
||||
|
||||
if (user_settings.twenty_four_hour_time) {
|
||||
time_options.hourCycle = "h24";
|
||||
}
|
||||
|
||||
const date_string = time.toLocaleDateString();
|
||||
let time_string = time.toLocaleTimeString(undefined, time_options);
|
||||
const locale = get_user_locale();
|
||||
const date_string = time.toLocaleDateString(locale);
|
||||
let time_string = get_localized_date_or_time_for_format(time, "time_sec");
|
||||
|
||||
const tz_offset_str = get_tz_with_UTC_offset(time);
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import * as settings_profile_fields from "./settings_profile_fields";
|
|||
import * as stream_data from "./stream_data";
|
||||
import * as sub_store from "./sub_store";
|
||||
import * as subscriber_api from "./subscriber_api";
|
||||
import * as timerender from "./timerender";
|
||||
import * as ui_report from "./ui_report";
|
||||
import * as user_groups from "./user_groups";
|
||||
import * as user_pill from "./user_pill";
|
||||
|
@ -108,7 +109,7 @@ function render_user_group_list(groups, user) {
|
|||
});
|
||||
}
|
||||
|
||||
export function get_custom_profile_field_data(user, field, field_types, dateFormat) {
|
||||
export function get_custom_profile_field_data(user, field, field_types) {
|
||||
const field_value = people.get_custom_profile_data(user.user_id, field.id);
|
||||
const field_type = field.type;
|
||||
const profile_field = {};
|
||||
|
@ -128,7 +129,10 @@ export function get_custom_profile_field_data(user, field, field_types, dateForm
|
|||
|
||||
switch (field_type) {
|
||||
case field_types.DATE.id:
|
||||
profile_field.value = dateFormat.format(parseISO(field_value.value));
|
||||
profile_field.value = timerender.get_localized_date_or_time_for_format(
|
||||
parseISO(field_value.value),
|
||||
"dayofyear_year",
|
||||
);
|
||||
break;
|
||||
case field_types.USER.id:
|
||||
profile_field.id = field.id;
|
||||
|
@ -179,10 +183,9 @@ function initialize_user_type_fields(user) {
|
|||
export function show_user_profile(user, default_tab_key = "profile-tab") {
|
||||
popovers.hide_all();
|
||||
|
||||
const dateFormat = new Intl.DateTimeFormat("default", {dateStyle: "long"});
|
||||
const field_types = page_params.custom_profile_field_types;
|
||||
const profile_data = page_params.custom_profile_fields
|
||||
.map((f) => get_custom_profile_field_data(user, f, field_types, dateFormat))
|
||||
.map((f) => get_custom_profile_field_data(user, f, field_types))
|
||||
.filter((f) => f.name !== undefined);
|
||||
const user_streams = stream_data.get_subscribed_streams_for_user(user.user_id);
|
||||
const groups_of_user = user_groups.get_user_groups_of_user(user.user_id);
|
||||
|
@ -194,7 +197,10 @@ export function show_user_profile(user, default_tab_key = "profile-tab") {
|
|||
user_avatar: people.medium_avatar_url_for_person(user),
|
||||
is_me: people.is_current_user(user.email),
|
||||
is_bot: user.is_bot,
|
||||
date_joined: dateFormat.format(parseISO(user.date_joined)),
|
||||
date_joined: timerender.get_localized_date_or_time_for_format(
|
||||
parseISO(user.date_joined),
|
||||
"dayofyear_year",
|
||||
),
|
||||
user_circle_class: buddy_data.get_user_circle_class(user.user_id),
|
||||
last_seen: buddy_data.user_last_seen_time_status(user.user_id),
|
||||
user_time: people.get_user_time(user.user_id),
|
||||
|
|
|
@ -520,28 +520,28 @@ test("format_drafts", ({override_rewire, mock_template}) => {
|
|||
dark_background: "",
|
||||
topic: "topic",
|
||||
raw_content: "Test stream message",
|
||||
time_stamp: "7:55 AM",
|
||||
time_stamp: "7:55 AM",
|
||||
},
|
||||
{
|
||||
draft_id: "id2",
|
||||
is_stream: false,
|
||||
recipients: "Aaron",
|
||||
raw_content: "Test private message",
|
||||
time_stamp: "Jan 30",
|
||||
time_stamp: "Jan 30",
|
||||
},
|
||||
{
|
||||
draft_id: "id5",
|
||||
is_stream: false,
|
||||
recipients: "Aaron",
|
||||
raw_content: "Test private message 3",
|
||||
time_stamp: "Jan 29",
|
||||
time_stamp: "Jan 29",
|
||||
},
|
||||
{
|
||||
draft_id: "id4",
|
||||
is_stream: false,
|
||||
recipients: "Aaron",
|
||||
raw_content: "Test private message 2",
|
||||
time_stamp: "Jan 26",
|
||||
time_stamp: "Jan 26",
|
||||
},
|
||||
{
|
||||
draft_id: "id3",
|
||||
|
@ -551,7 +551,7 @@ test("format_drafts", ({override_rewire, mock_template}) => {
|
|||
dark_background: "",
|
||||
topic: "topic",
|
||||
raw_content: "Test stream message 2",
|
||||
time_stamp: "Jan 21",
|
||||
time_stamp: "Jan 21",
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -658,21 +658,21 @@ test("filter_drafts", ({override_rewire, mock_template}) => {
|
|||
is_stream: false,
|
||||
recipients: "Aaron",
|
||||
raw_content: "Test private message",
|
||||
time_stamp: "Jan 30",
|
||||
time_stamp: "Jan 30",
|
||||
},
|
||||
{
|
||||
draft_id: "id5",
|
||||
is_stream: false,
|
||||
recipients: "Aaron",
|
||||
raw_content: "Test private message 3",
|
||||
time_stamp: "Jan 29",
|
||||
time_stamp: "Jan 29",
|
||||
},
|
||||
{
|
||||
draft_id: "id4",
|
||||
is_stream: false,
|
||||
recipients: "Aaron",
|
||||
raw_content: "Test private message 2",
|
||||
time_stamp: "Jan 26",
|
||||
time_stamp: "Jan 26",
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -685,7 +685,7 @@ test("filter_drafts", ({override_rewire, mock_template}) => {
|
|||
dark_background: "",
|
||||
topic: "topic",
|
||||
raw_content: "Test stream message",
|
||||
time_stamp: "7:55 AM",
|
||||
time_stamp: "7:55 AM",
|
||||
},
|
||||
{
|
||||
draft_id: "id3",
|
||||
|
@ -695,7 +695,7 @@ test("filter_drafts", ({override_rewire, mock_template}) => {
|
|||
dark_background: "",
|
||||
topic: "topic",
|
||||
raw_content: "Test stream message 2",
|
||||
time_stamp: "Jan 21",
|
||||
time_stamp: "Jan 21",
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -71,12 +71,12 @@ test("get_mutes", () => {
|
|||
assert.deepEqual(all_muted_users, [
|
||||
{
|
||||
date_muted: 1577836800000,
|
||||
date_muted_str: "Jan\u00A001,\u00A02020",
|
||||
date_muted_str: "Jan 1, 2020",
|
||||
id: 6,
|
||||
},
|
||||
{
|
||||
date_muted: 1577836800000,
|
||||
date_muted_str: "Jan\u00A001,\u00A02020",
|
||||
date_muted_str: "Jan 1, 2020",
|
||||
id: 4,
|
||||
},
|
||||
]);
|
||||
|
@ -95,12 +95,12 @@ test("initialize", () => {
|
|||
assert.deepEqual(muted_users.get_muted_users().sort(), [
|
||||
{
|
||||
date_muted: 1577836800000,
|
||||
date_muted_str: "Jan\u00A001,\u00A02020",
|
||||
date_muted_str: "Jan 1, 2020",
|
||||
id: 3,
|
||||
},
|
||||
{
|
||||
date_muted: 1577836800000,
|
||||
date_muted_str: "Jan\u00A001,\u00A02020",
|
||||
date_muted_str: "Jan 1, 2020",
|
||||
id: 2,
|
||||
},
|
||||
]);
|
||||
|
|
|
@ -251,7 +251,7 @@ run_test("timestamp without time", () => {
|
|||
|
||||
run_test("timestamp", ({mock_template}) => {
|
||||
mock_template("markdown_timestamp.hbs", true, (data, html) => {
|
||||
assert.deepEqual(data, {text: "Thu, Jan 1 1970, 12:00 AM"});
|
||||
assert.deepEqual(data, {text: "Thu, Jan 1, 1970, 12:00 AM"});
|
||||
return html;
|
||||
});
|
||||
|
||||
|
@ -271,14 +271,14 @@ run_test("timestamp", ({mock_template}) => {
|
|||
rm.update_elements($content);
|
||||
|
||||
// Final asserts
|
||||
assert.equal($timestamp.html(), '<i class="fa fa-clock-o"></i>\nThu, Jan 1 1970, 12:00 AM\n');
|
||||
assert.equal($timestamp.html(), '<i class="fa fa-clock-o"></i>\nThu, Jan 1, 1970, 12:00 AM\n');
|
||||
assert.equal($timestamp_invalid.text(), "never-been-set");
|
||||
});
|
||||
|
||||
run_test("timestamp-twenty-four-hour-time", ({mock_template, override}) => {
|
||||
mock_template("markdown_timestamp.hbs", true, (data, html) => {
|
||||
// sanity check incoming data
|
||||
assert.ok(data.text.startsWith("Wed, Jul 15 2020, "));
|
||||
assert.ok(data.text.startsWith("Wed, Jul 15, 2020, "));
|
||||
return html;
|
||||
});
|
||||
|
||||
|
@ -290,11 +290,11 @@ run_test("timestamp-twenty-four-hour-time", ({mock_template, override}) => {
|
|||
// We will temporarily change the 24h setting for this test.
|
||||
override(user_settings, "twenty_four_hour_time", true);
|
||||
rm.update_elements($content);
|
||||
assert.equal($timestamp.html(), '<i class="fa fa-clock-o"></i>\nWed, Jul 15 2020, 20:40\n');
|
||||
assert.equal($timestamp.html(), '<i class="fa fa-clock-o"></i>\nWed, Jul 15, 2020, 20:40\n');
|
||||
|
||||
override(user_settings, "twenty_four_hour_time", false);
|
||||
rm.update_elements($content);
|
||||
assert.equal($timestamp.html(), '<i class="fa fa-clock-o"></i>\nWed, Jul 15 2020, 8:40 PM\n');
|
||||
assert.equal($timestamp.html(), '<i class="fa fa-clock-o"></i>\nWed, Jul 15, 2020, 8:40 PM\n');
|
||||
});
|
||||
|
||||
run_test("timestamp-error", () => {
|
||||
|
|
|
@ -28,7 +28,7 @@ run_test("settings", ({override}) => {
|
|||
assert.deepEqual(list, [
|
||||
{
|
||||
date_muted: 1577836800000,
|
||||
date_muted_str: "Jan\u00A001,\u00A02020",
|
||||
date_muted_str: "Jan 1, 2020",
|
||||
stream: frontend.name,
|
||||
stream_id: frontend.stream_id,
|
||||
topic: "js",
|
||||
|
|
|
@ -22,7 +22,7 @@ run_test("settings", ({override}) => {
|
|||
override(list_widget, "create", ($container, list) => {
|
||||
assert.deepEqual(list, [
|
||||
{
|
||||
date_muted_str: "Jan\u00A001,\u00A02020",
|
||||
date_muted_str: "Jan 1, 2020",
|
||||
user_id: 5,
|
||||
user_name: "Feivel Fiverson",
|
||||
},
|
||||
|
|
|
@ -35,6 +35,135 @@ const date_2021 = get_date("2021-01-27T01:53:08.000Z", "Wednesday");
|
|||
|
||||
const date_2025 = get_date("2025-03-03T12:10:00.000Z", "Monday");
|
||||
|
||||
run_test("get_localized_date_or_time_for_format returns default date with incorrect locale", () => {
|
||||
const date = date_2019;
|
||||
const expectedDate = "Friday, April 12, 2019";
|
||||
|
||||
user_settings.default_language = "invalidLanguageCode";
|
||||
const actualDate = timerender.get_localized_date_or_time_for_format(
|
||||
date,
|
||||
"weekday_dayofyear_year",
|
||||
);
|
||||
|
||||
assert.equal(actualDate, expectedDate);
|
||||
});
|
||||
|
||||
run_test("get_localized_date_or_time_for_format returns correct format", () => {
|
||||
const date = date_2021;
|
||||
const formats = [
|
||||
{
|
||||
format: "time",
|
||||
expected: {
|
||||
date: "1:53 AM",
|
||||
},
|
||||
},
|
||||
{
|
||||
format: "time_sec",
|
||||
expected: {
|
||||
date: "1:53:08 AM",
|
||||
},
|
||||
},
|
||||
{
|
||||
format: "weekday",
|
||||
expected: {
|
||||
date: "Wednesday",
|
||||
},
|
||||
},
|
||||
{
|
||||
format: "dayofyear",
|
||||
expected: {
|
||||
date: "Jan 27",
|
||||
},
|
||||
},
|
||||
{
|
||||
format: "dayofyear_time",
|
||||
expected: {
|
||||
date: "Jan 27, 1:53 AM",
|
||||
},
|
||||
},
|
||||
{
|
||||
format: "dayofyear_year",
|
||||
expected: {
|
||||
date: "Jan 27, 2021",
|
||||
},
|
||||
},
|
||||
{
|
||||
format: "dayofyear_year_time",
|
||||
expected: {
|
||||
date: "Jan 27, 2021, 1:53 AM",
|
||||
},
|
||||
},
|
||||
{
|
||||
format: "weekday_dayofyear_year",
|
||||
expected: {
|
||||
date: "Wednesday, January 27, 2021",
|
||||
},
|
||||
},
|
||||
{
|
||||
format: "weekday_dayofyear_year_time",
|
||||
expected: {
|
||||
date: "Wed, Jan 27, 2021, 1:53 AM",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
for (const format of formats) {
|
||||
const actualDate = timerender.get_localized_date_or_time_for_format(date, format.format);
|
||||
assert.equal(actualDate, format.expected.date);
|
||||
}
|
||||
});
|
||||
|
||||
run_test("get_localized_date_or_time_for_format returns correct localized date", () => {
|
||||
const date = add(date_2019, {years: -1});
|
||||
const languages = [
|
||||
{
|
||||
language: "en",
|
||||
expected: {
|
||||
date: "Thursday, April 12, 2018",
|
||||
},
|
||||
},
|
||||
{
|
||||
language: "ru",
|
||||
expected: {
|
||||
date: "четверг, 12 апреля 2018 г.",
|
||||
},
|
||||
},
|
||||
{
|
||||
language: "fr",
|
||||
expected: {
|
||||
date: "jeudi 12 avril 2018",
|
||||
},
|
||||
},
|
||||
{
|
||||
language: "de",
|
||||
expected: {
|
||||
date: "Donnerstag, 12. April 2018",
|
||||
},
|
||||
},
|
||||
{
|
||||
language: "su",
|
||||
expected: {
|
||||
date: "Kemis, 12 April 2018",
|
||||
},
|
||||
},
|
||||
{
|
||||
language: "it",
|
||||
expected: {
|
||||
date: "giovedì 12 aprile 2018",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
for (const language of languages) {
|
||||
user_settings.default_language = language.language;
|
||||
const actualDate = timerender.get_localized_date_or_time_for_format(
|
||||
date,
|
||||
"weekday_dayofyear_year",
|
||||
);
|
||||
assert.equal(actualDate, language.expected.date);
|
||||
}
|
||||
});
|
||||
|
||||
run_test("get_tz_with_UTC_offset", () => {
|
||||
let time = date_2019;
|
||||
|
||||
|
@ -60,7 +189,7 @@ run_test("render_now_returns_today", () => {
|
|||
|
||||
const expected = {
|
||||
time_str: $t({defaultMessage: "Today"}),
|
||||
formal_time_str: "Friday, April 12, 2019",
|
||||
formal_time_str: "Friday, April 12, 2019",
|
||||
needs_update: true,
|
||||
};
|
||||
const actual = timerender.render_now(today, today);
|
||||
|
@ -75,7 +204,7 @@ run_test("render_now_returns_yesterday", () => {
|
|||
const yesterday = add(today, {days: -1});
|
||||
const expected = {
|
||||
time_str: $t({defaultMessage: "Yesterday"}),
|
||||
formal_time_str: "Thursday, April 11, 2019",
|
||||
formal_time_str: "Thursday, April 11, 2019",
|
||||
needs_update: true,
|
||||
};
|
||||
const actual = timerender.render_now(yesterday, today);
|
||||
|
@ -89,8 +218,8 @@ run_test("render_now_returns_year", () => {
|
|||
|
||||
const year_ago = add(today, {years: -1});
|
||||
const expected = {
|
||||
time_str: "Apr 12, 2018",
|
||||
formal_time_str: "Thursday, April 12, 2018",
|
||||
time_str: "Apr 12, 2018",
|
||||
formal_time_str: "Thursday, April 12, 2018",
|
||||
needs_update: false,
|
||||
};
|
||||
const actual = timerender.render_now(year_ago, today);
|
||||
|
@ -104,8 +233,8 @@ run_test("render_now_returns_month_and_day", () => {
|
|||
|
||||
const three_months_ago = add(today, {months: -3});
|
||||
const expected = {
|
||||
time_str: "Jan 12",
|
||||
formal_time_str: "Saturday, January 12, 2019",
|
||||
time_str: "Jan 12",
|
||||
formal_time_str: "Saturday, January 12, 2019",
|
||||
needs_update: false,
|
||||
};
|
||||
const actual = timerender.render_now(three_months_ago, today);
|
||||
|
@ -128,9 +257,9 @@ run_test("format_time_modern", () => {
|
|||
const more_than_6_months_ago = add(today, {months: -9});
|
||||
const previous_year_but_less_than_6_months = add(today, {months: -1});
|
||||
|
||||
assert.equal(timerender.format_time_modern(few_minutes_in_future, today), "Jan 27, 2021");
|
||||
assert.equal(timerender.format_time_modern(weeks_in_future, today), "Feb 16, 2021");
|
||||
assert.equal(timerender.format_time_modern(less_than_24_hours_ago, today), "2:53 AM");
|
||||
assert.equal(timerender.format_time_modern(few_minutes_in_future, today), "Jan 27, 2021");
|
||||
assert.equal(timerender.format_time_modern(weeks_in_future, today), "Feb 16, 2021");
|
||||
assert.equal(timerender.format_time_modern(less_than_24_hours_ago, today), "2:53 AM");
|
||||
assert.equal(
|
||||
timerender.format_time_modern(twenty_four_hours_ago, today),
|
||||
"translated: Yesterday",
|
||||
|
@ -140,13 +269,13 @@ run_test("format_time_modern", () => {
|
|||
"translated: Yesterday",
|
||||
);
|
||||
assert.equal(timerender.format_time_modern(less_than_a_week_ago, today), "Thursday");
|
||||
assert.equal(timerender.format_time_modern(one_week_ago, today), "Jan 20");
|
||||
assert.equal(timerender.format_time_modern(one_week_ago, today), "Jan 20");
|
||||
assert.equal(
|
||||
timerender.format_time_modern(previous_year_but_less_than_6_months, today),
|
||||
"Dec 27",
|
||||
"Dec 27",
|
||||
);
|
||||
assert.equal(timerender.format_time_modern(less_than_6_months_ago, today), "Oct 27");
|
||||
assert.equal(timerender.format_time_modern(more_than_6_months_ago, today), "Apr 27, 2020");
|
||||
assert.equal(timerender.format_time_modern(less_than_6_months_ago, today), "Oct 27");
|
||||
assert.equal(timerender.format_time_modern(more_than_6_months_ago, today), "Apr 27, 2020");
|
||||
});
|
||||
|
||||
run_test("format_time_modern_different_timezones", () => {
|
||||
|
@ -182,7 +311,7 @@ run_test("format_time_modern_different_timezones", () => {
|
|||
process.env.TZ = "America/Juneau";
|
||||
expected = "translated: 5/11/2017 at 11:12:53 PM AKDT (UTC-08:00)";
|
||||
assert.equal(timerender.get_full_datetime(yesterday), expected);
|
||||
assert.equal(timerender.format_time_modern(yesterday, today), "May 11");
|
||||
assert.equal(timerender.format_time_modern(yesterday, today), "May 11");
|
||||
process.env.TZ = utc_tz;
|
||||
});
|
||||
|
||||
|
@ -191,8 +320,8 @@ run_test("render_now_returns_year_with_year_boundary", () => {
|
|||
|
||||
const six_months_ago = add(today, {months: -6});
|
||||
const expected = {
|
||||
time_str: "Oct 12, 2018",
|
||||
formal_time_str: "Friday, October 12, 2018",
|
||||
time_str: "Oct 12, 2018",
|
||||
formal_time_str: "Friday, October 12, 2018",
|
||||
needs_update: false,
|
||||
};
|
||||
const actual = timerender.render_now(six_months_ago, today);
|
||||
|
@ -224,7 +353,7 @@ run_test("render_date_renders_time_html", () => {
|
|||
|
||||
const $actual = timerender.render_date(message_time, today);
|
||||
assert.equal($actual.html(), expected_html);
|
||||
assert.equal(attrs["data-tippy-content"], "Friday, April 12, 2019");
|
||||
assert.equal(attrs["data-tippy-content"], "Friday, April 12, 2019");
|
||||
assert.equal(attrs.class, "timerender0");
|
||||
});
|
||||
|
||||
|
@ -257,13 +386,13 @@ run_test("absolute_time_12_hour", () => {
|
|||
let timestamp = date_2019.getTime();
|
||||
|
||||
let today = date_2019;
|
||||
let expected = "Apr 12 05:52 PM";
|
||||
let expected = "Apr 12, 5:52 PM";
|
||||
let actual = timerender.absolute_time(timestamp, today);
|
||||
assert.equal(actual, expected);
|
||||
|
||||
// timestamp with hour > 12, different year
|
||||
let next_year = add(today, {years: 1});
|
||||
expected = "Apr 12, 2019 05:52 PM";
|
||||
expected = "Apr 12, 2019, 5:52 PM";
|
||||
actual = timerender.absolute_time(timestamp, next_year);
|
||||
assert.equal(actual, expected);
|
||||
|
||||
|
@ -271,13 +400,13 @@ run_test("absolute_time_12_hour", () => {
|
|||
timestamp = date_2017.getTime();
|
||||
|
||||
today = date_2017;
|
||||
expected = "May 18 07:12 AM";
|
||||
expected = "May 18, 7:12 AM";
|
||||
actual = timerender.absolute_time(timestamp, today);
|
||||
assert.equal(actual, expected);
|
||||
|
||||
// timestamp with hour < 12, different year
|
||||
next_year = add(today, {years: 1});
|
||||
expected = "May 18, 2017 07:12 AM";
|
||||
expected = "May 18, 2017, 7:12 AM";
|
||||
actual = timerender.absolute_time(timestamp, next_year);
|
||||
assert.equal(actual, expected);
|
||||
});
|
||||
|
@ -287,26 +416,26 @@ run_test("absolute_time_24_hour", () => {
|
|||
|
||||
// date with hour > 12, same year
|
||||
let today = date_2019;
|
||||
let expected = "Apr 12 17:52";
|
||||
let expected = "Apr 12, 17:52";
|
||||
let actual = timerender.absolute_time(date_2019.getTime(), today);
|
||||
assert.equal(actual, expected);
|
||||
|
||||
// date with hour > 12, different year
|
||||
let next_year = add(today, {years: 1});
|
||||
|
||||
expected = "Apr 12, 2019 17:52";
|
||||
expected = "Apr 12, 2019, 17:52";
|
||||
actual = timerender.absolute_time(date_2019.getTime(), next_year);
|
||||
assert.equal(actual, expected);
|
||||
|
||||
// timestamp with hour < 12, same year
|
||||
today = date_2017;
|
||||
expected = "May 18 07:12";
|
||||
expected = "May 18, 07:12";
|
||||
actual = timerender.absolute_time(date_2017.getTime(), today);
|
||||
assert.equal(actual, expected);
|
||||
|
||||
// timestamp with hour < 12, different year
|
||||
next_year = add(today, {years: 1});
|
||||
expected = "May 18, 2017 07:12";
|
||||
expected = "May 18, 2017, 07:12";
|
||||
actual = timerender.absolute_time(date_2017.getTime(), next_year);
|
||||
assert.equal(actual, expected);
|
||||
});
|
||||
|
@ -364,16 +493,16 @@ run_test("last_seen_status_from_date", () => {
|
|||
|
||||
assert_same({days: -61}, $t({defaultMessage: "61 days ago"}));
|
||||
|
||||
assert_same({days: -300}, $t({defaultMessage: "May 06,\u00A02015"}));
|
||||
assert_same({days: -300}, $t({defaultMessage: "May 6, 2015"}));
|
||||
|
||||
assert_same({days: -366}, $t({defaultMessage: "Mar 01,\u00A02015"}));
|
||||
assert_same({days: -366}, $t({defaultMessage: "Mar 1, 2015"}));
|
||||
|
||||
assert_same({years: -3}, $t({defaultMessage: "Mar 01,\u00A02013"}));
|
||||
assert_same({years: -3}, $t({defaultMessage: "Mar 1, 2013"}));
|
||||
|
||||
// Set base_date to May 1 2016 12.30 AM (months are zero based)
|
||||
base_date = new Date(2016, 4, 1, 0, 30);
|
||||
|
||||
assert_same({days: -91}, $t({defaultMessage: "Jan\u00A031"}));
|
||||
assert_same({days: -91}, $t({defaultMessage: "Jan 31"}));
|
||||
|
||||
// Set base_date to May 1 2016 10.30 PM (months are zero based)
|
||||
base_date = new Date(2016, 4, 2, 23, 30);
|
||||
|
@ -397,12 +526,12 @@ run_test("set_full_datetime", () => {
|
|||
|
||||
user_settings.twenty_four_hour_time = false;
|
||||
time_str = timerender.stringify_time(time);
|
||||
expected = "5:52 PM";
|
||||
expected = "5:52 PM";
|
||||
assert.equal(time_str, expected);
|
||||
|
||||
time = add(time, {hours: -7}); // time between 1 to 12 o'clock time.
|
||||
user_settings.twenty_four_hour_time = false;
|
||||
time_str = timerender.stringify_time(time);
|
||||
expected = "10:52 AM";
|
||||
expected = "10:52 AM";
|
||||
assert.equal(time_str, expected);
|
||||
});
|
||||
|
|
|
@ -86,14 +86,14 @@ test("get_mutes", () => {
|
|||
assert.deepEqual(all_muted_topics, [
|
||||
{
|
||||
date_muted: 1577836700000,
|
||||
date_muted_str: "Dec\u00A031,\u00A02019",
|
||||
date_muted_str: "Dec 31, 2019",
|
||||
stream: devel.name,
|
||||
stream_id: devel.stream_id,
|
||||
topic: "java",
|
||||
},
|
||||
{
|
||||
date_muted: 1577836800000,
|
||||
date_muted_str: "Jan\u00A001,\u00A02020",
|
||||
date_muted_str: "Jan 1, 2020",
|
||||
stream: office.name,
|
||||
stream_id: office.stream_id,
|
||||
topic: "gossip",
|
||||
|
@ -134,14 +134,14 @@ test("set_user_topics", () => {
|
|||
assert.deepEqual(user_topics.get_muted_topics().sort(), [
|
||||
{
|
||||
date_muted: 1577836800000,
|
||||
date_muted_str: "Jan\u00A001,\u00A02020",
|
||||
date_muted_str: "Jan 1, 2020",
|
||||
stream: social.name,
|
||||
stream_id: social.stream_id,
|
||||
topic: "breakfast",
|
||||
},
|
||||
{
|
||||
date_muted: 1577836800000,
|
||||
date_muted_str: "Jan\u00A001,\u00A02020",
|
||||
date_muted_str: "Jan 1, 2020",
|
||||
stream: design.name,
|
||||
stream_id: design.stream_id,
|
||||
topic: "typography",
|
||||
|
|
Loading…
Reference in New Issue