fix: small findings with regard to deletes from sidebar
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
pub mod relative_date;
|
||||
|
||||
use bds_core::i18n::{UiLocale, translate, translate_with};
|
||||
|
||||
/// Shorthand for translate in view code.
|
||||
|
||||
163
crates/bds-ui/src/i18n/relative_date.rs
Normal file
163
crates/bds-ui/src/i18n/relative_date.rs
Normal file
@@ -0,0 +1,163 @@
|
||||
use bds_core::i18n::{UiLocale, translate, translate_with};
|
||||
use chrono::{DateTime, Datelike, Local, NaiveDate};
|
||||
|
||||
/// chrono format locale per sidebar_views.allium LocaleMapping
|
||||
/// (ui_locale → format_locale, e.g. "de" → "de-DE").
|
||||
fn format_locale(locale: UiLocale) -> chrono::Locale {
|
||||
match locale {
|
||||
UiLocale::En => chrono::Locale::en_US,
|
||||
UiLocale::De => chrono::Locale::de_DE,
|
||||
UiLocale::Fr => chrono::Locale::fr_FR,
|
||||
UiLocale::It => chrono::Locale::it_IT,
|
||||
UiLocale::Es => chrono::Locale::es_ES,
|
||||
}
|
||||
}
|
||||
|
||||
/// Per sidebar_views.allium RelativeDateFormat, with
|
||||
/// diff_days = (today - timestamp.date).days on local calendar dates:
|
||||
/// diff_days = 0 → locale time string
|
||||
/// diff_days = 1 → localized "Yesterday"
|
||||
/// diff_days < 7 → short weekday name
|
||||
/// otherwise → short month name + numeric day
|
||||
pub fn format_relative_date(locale: UiLocale, unix_ms: i64, today: NaiveDate) -> String {
|
||||
let timestamp = DateTime::from_timestamp_millis(unix_ms)
|
||||
.unwrap_or_default()
|
||||
.with_timezone(&Local);
|
||||
let chrono_locale = format_locale(locale);
|
||||
let diff_days = (today - timestamp.date_naive()).num_days();
|
||||
|
||||
if diff_days == 0 {
|
||||
timestamp.format_localized("%X", chrono_locale).to_string()
|
||||
} else if diff_days == 1 {
|
||||
translate(locale, "sidebar.chatYesterday")
|
||||
} else if diff_days < 7 {
|
||||
timestamp.format_localized("%a", chrono_locale).to_string()
|
||||
} else {
|
||||
let month = timestamp.format_localized("%b", chrono_locale).to_string();
|
||||
let day = timestamp.day().to_string();
|
||||
translate_with(
|
||||
locale,
|
||||
"sidebar.relativeDateMonthDay",
|
||||
&[("month", &month), ("day", &day)],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use chrono::TimeZone;
|
||||
|
||||
fn local_ms(y: i32, mo: u32, d: u32, h: u32, mi: u32, s: u32) -> i64 {
|
||||
Local
|
||||
.with_ymd_and_hms(y, mo, d, h, mi, s)
|
||||
.single()
|
||||
.expect("unambiguous local time")
|
||||
.timestamp_millis()
|
||||
}
|
||||
|
||||
fn day(y: i32, mo: u32, d: u32) -> NaiveDate {
|
||||
NaiveDate::from_ymd_opt(y, mo, d).expect("valid date")
|
||||
}
|
||||
|
||||
// Reference "today" throughout: Tuesday, 2026-02-17.
|
||||
const TODAY: (i32, u32, u32) = (2026, 2, 17);
|
||||
|
||||
fn today() -> NaiveDate {
|
||||
day(TODAY.0, TODAY.1, TODAY.2)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_day_shows_locale_time() {
|
||||
let ts = local_ms(2026, 2, 17, 14, 5, 7);
|
||||
let en = format_relative_date(UiLocale::En, ts, today());
|
||||
assert!(en.contains("05:07"), "expected time string, got {en}");
|
||||
assert!(en.contains("PM"), "en-US uses 12-hour clock, got {en}");
|
||||
let de = format_relative_date(UiLocale::De, ts, today());
|
||||
assert!(de.contains("14:05"), "de-DE uses 24-hour clock, got {de}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_day_ago_is_yesterday_even_under_24_hours() {
|
||||
// 23:59 yesterday vs. any time today: calendar-day diff is 1.
|
||||
let ts = local_ms(2026, 2, 16, 23, 59, 0);
|
||||
assert_eq!(format_relative_date(UiLocale::En, ts, today()), "Yesterday");
|
||||
assert_eq!(format_relative_date(UiLocale::De, ts, today()), "Gestern");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_days_ago_shows_short_weekday() {
|
||||
// 2026-02-15 is a Sunday.
|
||||
let ts = local_ms(2026, 2, 15, 12, 0, 0);
|
||||
assert_eq!(format_relative_date(UiLocale::En, ts, today()), "Sun");
|
||||
assert_eq!(format_relative_date(UiLocale::De, ts, today()), "So");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn six_days_ago_still_shows_weekday() {
|
||||
// 2026-02-11 is a Wednesday: diff_days = 6 is the last weekday case.
|
||||
let ts = local_ms(2026, 2, 11, 12, 0, 0);
|
||||
assert_eq!(format_relative_date(UiLocale::En, ts, today()), "Wed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seven_days_ago_shows_month_and_day() {
|
||||
// diff_days = 7 is the first month+day case.
|
||||
let ts = local_ms(2026, 2, 10, 12, 0, 0);
|
||||
assert_eq!(format_relative_date(UiLocale::En, ts, today()), "Feb 10");
|
||||
assert_eq!(format_relative_date(UiLocale::De, ts, today()), "10. Feb");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn month_day_order_is_day_first_in_romance_locales() {
|
||||
let ts = local_ms(2026, 2, 10, 12, 0, 0);
|
||||
for locale in [UiLocale::Fr, UiLocale::It, UiLocale::Es] {
|
||||
let result = format_relative_date(locale, ts, today());
|
||||
assert!(
|
||||
result.starts_with("10 "),
|
||||
"expected day-first order for {locale}, got {result}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn distant_past_shows_month_and_day() {
|
||||
let ts = local_ms(2025, 6, 3, 12, 0, 0);
|
||||
assert_eq!(format_relative_date(UiLocale::En, ts, today()), "Jun 3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn future_timestamp_uses_weekday_branch() {
|
||||
// Spec formula: diff_days < 7 → weekday; a future date has a
|
||||
// negative diff and therefore falls into the weekday case.
|
||||
// 2026-02-20 is a Friday.
|
||||
let ts = local_ms(2026, 2, 20, 12, 0, 0);
|
||||
assert_eq!(format_relative_date(UiLocale::En, ts, today()), "Fri");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn yesterday_is_translated_in_all_locales() {
|
||||
let ts = local_ms(2026, 2, 16, 12, 0, 0);
|
||||
for locale in UiLocale::all() {
|
||||
let result = format_relative_date(*locale, ts, today());
|
||||
assert_ne!(
|
||||
result, "sidebar.chatYesterday",
|
||||
"missing yesterday translation for {locale}"
|
||||
);
|
||||
assert!(!result.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn month_day_pattern_exists_in_all_locales() {
|
||||
let ts = local_ms(2025, 6, 3, 12, 0, 0);
|
||||
for locale in UiLocale::all() {
|
||||
let result = format_relative_date(*locale, ts, today());
|
||||
assert_ne!(
|
||||
result, "sidebar.relativeDateMonthDay",
|
||||
"missing month-day pattern for {locale}"
|
||||
);
|
||||
assert!(result.contains('3'), "day missing for {locale}: {result}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user