Localize sidebar dates and calendar months
This commit is contained in:
@@ -6,7 +6,7 @@ The project is under active development. Core blogging workflows are broadly ava
|
||||
|
||||
## Available Features
|
||||
|
||||
- Native Iced desktop workspace with localized menus, tabs, anchored editor popovers, automatically paged post/media sidebars, relative-dated entity lists with row deletion, dialogs, tasks, embedded Wry previews, a live Pico CSS theme editor, and per-project restart restoration of the active activity, shell visibility, and open editor tabs.
|
||||
- Native Iced desktop workspace with localized menus, tabs, anchored editor popovers, automatically paged post/media sidebars with locale-aware post dates, calendar months, and relative-dated entity lists, row deletion, dialogs, tasks, embedded Wry previews, a live Pico CSS theme editor, and per-project restart restoration of the active activity, shell visibility, and open editor tabs.
|
||||
- Post and translation authoring with change-aware draft/published/archive lifecycle, file-backed change discard, canonical draft reopening after manual translation edits, non-disruptive automatic translation, desktop archive/unarchive actions, in-place published-frontmatter updates, metadata, tags, categories, live link/backlink graphs, media, and batch gallery-image import.
|
||||
- Media import including HEIC/HEIF decoding, q80 WebP thumbnails (plus q85 AI JPEGs), metadata translations, filters, validation, post assignment, and sequential drag-and-drop insertion into post editors.
|
||||
- WordPress WXR migration with saved analyses, HTML-to-Markdown and shortcode conversion, conflict/taxonomy review, recoverable 500-item execution batches, media-parent linking, progress reporting, and optional AI-assisted taxonomy mapping.
|
||||
|
||||
@@ -3,7 +3,7 @@ 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 {
|
||||
pub(crate) fn format_locale(locale: UiLocale) -> chrono::Locale {
|
||||
match locale {
|
||||
UiLocale::En => chrono::Locale::en_US,
|
||||
UiLocale::De => chrono::Locale::de_DE,
|
||||
|
||||
@@ -9,7 +9,7 @@ use bds_core::model::{ChatConversation, ImportDefinition, Media, Post, Script, T
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::components::inputs;
|
||||
use crate::i18n::relative_date::format_relative_date;
|
||||
use crate::i18n::relative_date::{format_locale, format_relative_date};
|
||||
use crate::i18n::t;
|
||||
use crate::state::navigation::SidebarView;
|
||||
use crate::state::sidebar_filter::{CalendarYear, MediaFilter, PostFilter};
|
||||
@@ -145,12 +145,21 @@ fn post_type_icon(categories: &[String]) -> &'static str {
|
||||
"\u{1F4C4}" // 📄 document (default)
|
||||
}
|
||||
|
||||
/// Per sidebar_views.allium PostDateFormat: "Feb 10, 2026".
|
||||
fn format_post_date(unix_ms: i64) -> String {
|
||||
let secs = unix_ms / 1000;
|
||||
let dt = chrono::DateTime::from_timestamp(secs, 0)
|
||||
.unwrap_or_else(|| chrono::DateTime::from_timestamp(0, 0).unwrap());
|
||||
dt.format("%b %d, %Y").to_string()
|
||||
/// Per sidebar_views.allium PostDateFormat: locale-formatted short month,
|
||||
/// numeric day, and numeric year (for example, "Feb 10, 2026" in en-US).
|
||||
fn format_post_date(locale: UiLocale, unix_ms: i64) -> String {
|
||||
let timestamp = chrono::DateTime::from_timestamp_millis(unix_ms)
|
||||
.unwrap_or_default()
|
||||
.with_timezone(&chrono::Local);
|
||||
let pattern = match locale {
|
||||
UiLocale::En => "%b %-d, %Y",
|
||||
UiLocale::De => "%-d. %b. %Y",
|
||||
UiLocale::Fr | UiLocale::It | UiLocale::Es => "%-d %b %Y",
|
||||
};
|
||||
|
||||
timestamp
|
||||
.format_localized(pattern, format_locale(locale))
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Search input style.
|
||||
@@ -314,23 +323,14 @@ fn with_row_delete(
|
||||
)
|
||||
}
|
||||
|
||||
/// Month name abbreviation for calendar display.
|
||||
fn month_abbr(month: u32) -> &'static str {
|
||||
match month {
|
||||
1 => "Jan",
|
||||
2 => "Feb",
|
||||
3 => "Mar",
|
||||
4 => "Apr",
|
||||
5 => "May",
|
||||
6 => "Jun",
|
||||
7 => "Jul",
|
||||
8 => "Aug",
|
||||
9 => "Sep",
|
||||
10 => "Oct",
|
||||
11 => "Nov",
|
||||
12 => "Dec",
|
||||
_ => "???",
|
||||
}
|
||||
/// Locale-formatted short month name for calendar display.
|
||||
fn month_abbr(locale: UiLocale, month: u32) -> String {
|
||||
chrono::NaiveDate::from_ymd_opt(2000, month, 1)
|
||||
.map(|date| {
|
||||
date.format_localized("%b", format_locale(locale))
|
||||
.to_string()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Build the calendar archive tree widget.
|
||||
@@ -376,7 +376,7 @@ fn calendar_widget(
|
||||
if year_selected {
|
||||
for cm in &cy.months {
|
||||
let month_selected = selected_month == Some(cm.month);
|
||||
let label = format!(" {} ({})", month_abbr(cm.month), cm.count);
|
||||
let label = format!(" {} ({})", month_abbr(locale, cm.month), cm.count);
|
||||
let month_val = cm.month;
|
||||
let on_month_clone = on_month.clone();
|
||||
let style_fn = if month_selected {
|
||||
@@ -797,7 +797,7 @@ pub fn view(
|
||||
bds_core::model::PostStatus::Published => "\u{25CF}",
|
||||
bds_core::model::PostStatus::Archived => "\u{25A1}",
|
||||
};
|
||||
let date = format_post_date(p.created_at);
|
||||
let date = format_post_date(locale, p.created_at);
|
||||
let prefix_chars: usize = 5;
|
||||
let text_px = width
|
||||
- SIDEBAR_TEXT_OVERHEAD_PX
|
||||
@@ -1320,6 +1320,35 @@ pub fn view(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use chrono::{Local, TimeZone};
|
||||
|
||||
fn local_ms(year: i32, month: u32, day: u32) -> i64 {
|
||||
Local
|
||||
.with_ymd_and_hms(year, month, day, 12, 0, 0)
|
||||
.single()
|
||||
.expect("unambiguous local time")
|
||||
.timestamp_millis()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn post_date_uses_locale_order_month_name_and_punctuation() {
|
||||
let timestamp = local_ms(2026, 2, 10);
|
||||
|
||||
assert_eq!(format_post_date(UiLocale::En, timestamp), "Feb 10, 2026");
|
||||
assert_eq!(format_post_date(UiLocale::De, timestamp), "10. Feb. 2026");
|
||||
assert_eq!(format_post_date(UiLocale::Fr, timestamp), "10 févr. 2026");
|
||||
assert_eq!(format_post_date(UiLocale::It, timestamp), "10 feb 2026");
|
||||
assert_eq!(format_post_date(UiLocale::Es, timestamp), "10 feb 2026");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn calendar_month_abbreviations_use_ui_locale() {
|
||||
assert_eq!(month_abbr(UiLocale::En, 3), "Mar");
|
||||
assert_eq!(month_abbr(UiLocale::De, 3), "Mär");
|
||||
assert_eq!(month_abbr(UiLocale::Fr, 2), "févr.");
|
||||
assert_eq!(month_abbr(UiLocale::It, 1), "gen");
|
||||
assert_eq!(month_abbr(UiLocale::Es, 1), "ene");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn truncate_media_title_short() {
|
||||
|
||||
Reference in New Issue
Block a user