From 52180132d8a1b7a5de8549bdacce0cd4fa01d44b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Volf?= <66163112+matous-volf@users.noreply.github.com> Date: Tue, 10 Sep 2024 16:10:05 +0200 Subject: [PATCH 1/8] feat: add the `LANGUAGE_CODE` environment variable --- .env.dev | 1 + 1 file changed, 1 insertion(+) diff --git a/.env.dev b/.env.dev index e876be4..5e21dd0 100644 --- a/.env.dev +++ b/.env.dev @@ -1 +1,2 @@ DATABASE_URL=postgres://app:app@db/todo_baggins +LANGUAGE_CODE=en-US -- 2.47.1 From c8b77532568044ec71434d586b71c83f76fd841b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Volf?= <66163112+matous-volf@users.noreply.github.com> Date: Tue, 10 Sep 2024 16:10:24 +0200 Subject: [PATCH 2/8] build: add dependencies --- Cargo.lock | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 3 ++ 2 files changed, 90 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index e8632dd..4aeb4c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1021,6 +1021,22 @@ dependencies = [ "tracing", ] +[[package]] +name = "dioxus-sdk" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271adf41837fbbceb955fefd71816d4a3fbbab2829f8c0ea0364584b531ce999" +dependencies = [ + "cfg-if", + "dioxus", + "js-sys", + "serde", + "serde_json", + "tracing", + "unic-langid", + "uuid", +] + [[package]] name = "dioxus-signals" version = "0.5.7" @@ -1096,6 +1112,17 @@ dependencies = [ "syn 2.0.74", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.74", +] + [[package]] name = "dotenvy" version = "0.15.7" @@ -1384,8 +1411,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -2772,6 +2801,12 @@ dependencies = [ "lock_api", ] +[[package]] +name = "stfu8" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51f1e89f093f99e7432c491c382b88a6860a5adbe6bf02574bf0a08efff1978" + [[package]] name = "strsim" version = "0.11.1" @@ -2872,6 +2907,15 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -2903,6 +2947,7 @@ dependencies = [ "dioxus", "dioxus-logger", "dioxus-query", + "dioxus-sdk", "dotenvy", "serde", "serde_json", @@ -2910,7 +2955,9 @@ dependencies = [ "time", "tracing", "tracing-wasm", + "unic-langid-impl", "validator", + "voca_rs", ] [[package]] @@ -3138,6 +3185,25 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "unic-langid" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dd9d1e72a73b25e07123a80776aae3e7b0ec461ef94f9151eed6ec88005a44" +dependencies = [ + "unic-langid-impl", +] + +[[package]] +name = "unic-langid-impl" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a5422c1f65949306c99240b81de9f3f15929f5a8bfe05bb44b034cc8bf593e5" +dependencies = [ + "serde", + "tinystr", +] + [[package]] name = "unicase" version = "2.7.0" @@ -3203,6 +3269,16 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "uuid" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +dependencies = [ + "getrandom", + "wasm-bindgen", +] + [[package]] name = "validator" version = "0.18.1" @@ -3257,6 +3333,17 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "voca_rs" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e44efbf25e32768d5ecd22244feacc3d3b3eca72d318f5ef0a4764c2c158e18" +dependencies = [ + "regex", + "stfu8", + "unicode-segmentation", +] + [[package]] name = "waker-fn" version = "1.2.0" diff --git a/Cargo.toml b/Cargo.toml index 3fb07f6..b43ab51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,9 @@ serde_with = { version = "3.9.0", features = ["chrono_0_4"] } async-std = "1.12.0" dioxus-query = "0.5.1" time = "0.3.36" +dioxus-sdk = { version = "0.5.0", features = ["i18n"] } +unic-langid-impl = "0.9.5" +voca_rs = "1.15.2" [features] default = [] -- 2.47.1 From f7dad0bd0aed46a64e63abcc9e32bd207821837d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Volf?= <66163112+matous-volf@users.noreply.github.com> Date: Tue, 10 Sep 2024 16:11:29 +0200 Subject: [PATCH 3/8] feat: create a module for internationalization --- src/internationalization/cs_cz.json | 19 ++++++++++++++++ src/internationalization/en_us.json | 19 ++++++++++++++++ src/internationalization/mod.rs | 34 +++++++++++++++++++++++++++++ src/main.rs | 1 + 4 files changed, 73 insertions(+) create mode 100644 src/internationalization/cs_cz.json create mode 100644 src/internationalization/en_us.json create mode 100644 src/internationalization/mod.rs diff --git a/src/internationalization/cs_cz.json b/src/internationalization/cs_cz.json new file mode 100644 index 0000000..0687f79 --- /dev/null +++ b/src/internationalization/cs_cz.json @@ -0,0 +1,19 @@ +{ + "id": "cs-CZ", + "texts": { + "none": "žádný", + "long_term": "dlouhodobé", + "yesterday": "včera", + "today": "dnes", + "tomorrow": "zítra", + "overdue": "zpožděné", + "formats": { + "date_format": "%-d. %B", + "date_year_format": "%-d. %B %Y", + "date_weekday_format": "%A %-d. %B", + "date_weekday_year_format": "%A %-d. %B %Y", + "weekday_lowercase_first": "true", + "time_format": "%-H:%M" + } + } +} diff --git a/src/internationalization/en_us.json b/src/internationalization/en_us.json new file mode 100644 index 0000000..e3a6a74 --- /dev/null +++ b/src/internationalization/en_us.json @@ -0,0 +1,19 @@ +{ + "id": "en-US", + "texts": { + "none": "none", + "long_term": "long-term", + "yesterday": "yesterday", + "today": "today", + "tomorrow": "tomorrow", + "overdue": "overdue", + "formats": { + "date_format": "%B %-d", + "date_year_format": "%B %-d, %Y", + "date_weekday_format": "%A, %B %-d", + "date_weekday_year_format": "%A, %B %-d, %Y", + "weekday_lowercase_first": "false", + "time_format": "%-I:%M %P" + } + } +} diff --git a/src/internationalization/mod.rs b/src/internationalization/mod.rs new file mode 100644 index 0000000..cc2560c --- /dev/null +++ b/src/internationalization/mod.rs @@ -0,0 +1,34 @@ +use std::ops::Deref; +use std::str::FromStr; +use chrono::Locale; +use dioxus_sdk::i18n::Language; +use unic_langid_impl::LanguageIdentifier; + +const EN_US: &str = include_str!("en_us.json"); +const CS_CZ: &str = include_str!("cs_cz.json"); + +pub(crate) fn get_languages() -> Vec { + Vec::from([EN_US, CS_CZ]).into_iter().map(|texts| Language::from_str(texts).unwrap()).collect() +} + +pub(crate) struct LocaleFromLanguageIdentifier<'a>(&'a LanguageIdentifier); + +impl<'a> Deref for LocaleFromLanguageIdentifier<'a> { + type Target = LanguageIdentifier; + + fn deref(&self) -> &Self::Target { + self.0 + } +} + +impl<'a> From> for Locale { + fn from(language_identifier: LocaleFromLanguageIdentifier) -> Self { + language_identifier.to_string().replace("-", "_").parse().unwrap() + } +} + +impl<'a> From<&'a LanguageIdentifier> for LocaleFromLanguageIdentifier<'a> { + fn from(language_identifier: &'a LanguageIdentifier) -> Self { + LocaleFromLanguageIdentifier(language_identifier) + } +} diff --git a/src/main.rs b/src/main.rs index 31ce965..7324db6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ mod schema; mod server; mod query; mod utils; +mod internationalization; use components::app::App; use dioxus::prelude::*; -- 2.47.1 From 1c6d735d52a923d61f70e21f6a541554889866f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Volf?= <66163112+matous-volf@users.noreply.github.com> Date: Tue, 10 Sep 2024 16:12:14 +0200 Subject: [PATCH 4/8] refactor: implement `Deref` for `ReverseOrdOption` --- src/utils/reverse_ord_option.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/utils/reverse_ord_option.rs b/src/utils/reverse_ord_option.rs index fa5f9f3..b40f29c 100644 --- a/src/utils/reverse_ord_option.rs +++ b/src/utils/reverse_ord_option.rs @@ -1,10 +1,18 @@ use std::cmp::Ordering; - +use std::ops::Deref; /* The default ordering of `Option`s is `None` being less than `Some`. The purpose of this struct is to reverse that. */ #[derive(PartialEq)] pub(crate) struct ReverseOrdOption<'a, T>(&'a Option); +impl<'a, T> Deref for ReverseOrdOption<'a, T> { + type Target = Option; + + fn deref(&self) -> &Self::Target { + self.0 + } +} + impl<'a, T: Ord> Eq for ReverseOrdOption<'a, T> {} impl<'a, T: Ord> PartialOrd for ReverseOrdOption<'a, T> { @@ -15,7 +23,7 @@ impl<'a, T: Ord> PartialOrd for ReverseOrdOption<'a, T> { impl<'a, T: Ord> Ord for ReverseOrdOption<'a, T> { fn cmp(&self, other: &Self) -> Ordering { - match (self.0.as_ref(), other.0.as_ref()) { + match (self.as_ref(), other.as_ref()) { (None, None) => Ordering::Equal, (None, Some(_)) => Ordering::Greater, (Some(_), None) => Ordering::Less, -- 2.47.1 From 587ed9e2d765d3993c458d05f7be4d84a844df33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Volf?= <66163112+matous-volf@users.noreply.github.com> Date: Tue, 10 Sep 2024 16:13:29 +0200 Subject: [PATCH 5/8] refactor: panic on an unsuccessful .env load on a database connection --- src/server/database_connection.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/database_connection.rs b/src/server/database_connection.rs index 826f7c8..fbf5a74 100644 --- a/src/server/database_connection.rs +++ b/src/server/database_connection.rs @@ -4,9 +4,9 @@ use dotenvy::dotenv; use std::env; pub(crate) fn establish_database_connection() -> ConnectionResult { - dotenv().ok(); + dotenv().expect("Could not load environment variables."); let database_url = - env::var("DATABASE_URL").expect("The environment variable DATABASE_URL must be set."); + env::var("DATABASE_URL").expect("The environment variable DATABASE_URL has to be set."); PgConnection::establish(&database_url) } -- 2.47.1 From a8d78f63baf1161dd39eae1e755eeb6e7a8b09bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Volf?= <66163112+matous-volf@users.noreply.github.com> Date: Tue, 10 Sep 2024 16:13:51 +0200 Subject: [PATCH 6/8] style: formatting --- src/models/task.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/task.rs b/src/models/task.rs index a925b6e..98d6125 100644 --- a/src/models/task.rs +++ b/src/models/task.rs @@ -73,7 +73,7 @@ impl Ord for Task { .then(ReverseOrdOption::from( &self_time.as_ref().map(|calendar_time| calendar_time.time()) ).cmp(&ReverseOrdOption::from( - &other_time.as_ref().map(|calendar_time| calendar_time.time()) + &other_time.as_ref().map(|calendar_time| calendar_time.time()) ))) .then(ReverseOrdOption::from(&self.deadline()).cmp( &ReverseOrdOption::from(&other.deadline()) -- 2.47.1 From d2dd7c78b9ff83be2f0a3fc1e9f9d7eb77e71d9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Volf?= <66163112+matous-volf@users.noreply.github.com> Date: Tue, 10 Sep 2024 16:14:28 +0200 Subject: [PATCH 7/8] feat: create a server function for getting the language identifier --- src/server/internationalization.rs | 14 ++++++++++++++ src/server/mod.rs | 1 + 2 files changed, 15 insertions(+) create mode 100644 src/server/internationalization.rs diff --git a/src/server/internationalization.rs b/src/server/internationalization.rs new file mode 100644 index 0000000..44ccc1f --- /dev/null +++ b/src/server/internationalization.rs @@ -0,0 +1,14 @@ +use std::env; +use dioxus::prelude::ServerFnError; +use unic_langid_impl::LanguageIdentifier; +use dioxus::prelude::*; +use dotenvy::dotenv; + +#[server] +pub(crate) async fn get_language_identifier() -> Result { + dotenv().expect("Could not load environment variables from the .env file."); + + Ok(env::var("LANGUAGE_CODE") + .expect("The environment variable LANGUAGE_CODE must be set.") + .parse::()?) +} diff --git a/src/server/mod.rs b/src/server/mod.rs index 59b63ad..50ad1c8 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -2,3 +2,4 @@ mod database_connection; pub(crate) mod projects; pub(crate) mod tasks; pub(crate) mod subtasks; +pub(crate) mod internationalization; -- 2.47.1 From 94ea49b76ffd03096a03b697940cc5089ef031aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Volf?= <66163112+matous-volf@users.noreply.github.com> Date: Tue, 10 Sep 2024 16:15:47 +0200 Subject: [PATCH 8/8] feat: internationalize the app interface --- src/components/app.rs | 6 ++ .../pages/category_calendar_page.rs | 32 ++++++---- src/components/pages/category_today_page.rs | 36 ++++++++--- src/components/task_form.rs | 26 ++++---- src/components/task_list_item.rs | 59 ++++++++++++++++--- 5 files changed, 122 insertions(+), 37 deletions(-) diff --git a/src/components/app.rs b/src/components/app.rs index 708b0f5..7f74f23 100644 --- a/src/components/app.rs +++ b/src/components/app.rs @@ -4,11 +4,17 @@ use dioxus::dioxus_core::Element; use dioxus::prelude::*; use dioxus_query::prelude::{use_init_query_client}; use crate::query::{QueryErrors, QueryKey, QueryValue}; +use dioxus_sdk::i18n::{use_init_i18n}; +use crate::internationalization::get_languages; +use crate::server::internationalization::get_language_identifier; #[component] pub(crate) fn App() -> Element { use_init_query_client::(); + let language_identifier = use_server_future(get_language_identifier)?.unwrap().unwrap(); + use_init_i18n(language_identifier.clone(), language_identifier, get_languages); + rsx! { div { class: "min-h-screen text-zinc-200 bg-zinc-800 pt-4 pb-36", diff --git a/src/components/pages/category_calendar_page.rs b/src/components/pages/category_calendar_page.rs index 2a31b42..1747ea5 100644 --- a/src/components/pages/category_calendar_page.rs +++ b/src/components/pages/category_calendar_page.rs @@ -1,13 +1,16 @@ +use crate::components::task_list::TaskList; +use crate::internationalization::LocaleFromLanguageIdentifier; use crate::models::category::Category; -use chrono::{Datelike, Local, Locale}; +use crate::models::task::TaskWithSubtasks; +use crate::query::tasks::use_tasks_with_subtasks_in_category_query; +use crate::query::QueryValue; +use chrono::{Datelike, Local}; use dioxus::core_macro::rsx; use dioxus::dioxus_core::Element; use dioxus::prelude::*; use dioxus_query::prelude::QueryResult; -use crate::components::task_list::TaskList; -use crate::query::QueryValue; -use crate::query::tasks::use_tasks_with_subtasks_in_category_query; -use crate::models::task::{TaskWithSubtasks}; +use dioxus_sdk::i18n::use_i18; +use dioxus_sdk::translate; const CALENDAR_LENGTH_DAYS: usize = 366 * 3; @@ -20,6 +23,8 @@ pub(crate) fn CategoryCalendarPage() -> Element { }); let tasks_query_result = tasks.result(); + let i18 = use_i18(); + rsx! { match tasks_query_result.value() { QueryResult::Ok(QueryValue::TasksWithSubtasks(tasks)) @@ -37,14 +42,17 @@ pub(crate) fn CategoryCalendarPage() -> Element { div { class: "pt-1", { - date_current - .format_localized( - format!( - "%A %-d. %B{}", - if date_current.year() != today_date.year() - {" %Y"} else {""} + date_current.format_localized(translate!( + i18, + if date_current.year() == Local::now().year() { + "formats.date_weekday_format" + } else { + "formats.date_weekday_year_format" + } ).as_str(), - Locale::en_US + LocaleFromLanguageIdentifier::from( + &(i18.selected_language)() + ).into() ) .to_string() } diff --git a/src/components/pages/category_today_page.rs b/src/components/pages/category_today_page.rs index 7122eb3..40d77f2 100644 --- a/src/components/pages/category_today_page.rs +++ b/src/components/pages/category_today_page.rs @@ -1,12 +1,16 @@ use crate::components::task_list::TaskList; +use crate::components::task_list_item::TaskListItem; +use crate::internationalization::LocaleFromLanguageIdentifier; use crate::models::category::Category; use crate::models::task::TaskWithSubtasks; -use crate::query::tasks::{use_tasks_with_subtasks_in_category_query}; +use crate::query::tasks::use_tasks_with_subtasks_in_category_query; use crate::query::QueryValue; -use chrono::{Local, Locale}; +use chrono::Local; use dioxus::prelude::*; use dioxus_query::prelude::QueryResult; -use crate::components::task_list_item::TaskListItem; +use dioxus_sdk::i18n::use_i18; +use dioxus_sdk::translate; +use voca_rs::Voca; #[component] pub(crate) fn CategoryTodayPage() -> Element { @@ -22,6 +26,8 @@ pub(crate) fn CategoryTodayPage() -> Element { let long_term_tasks_query = use_tasks_with_subtasks_in_category_query(Category::LongTerm); let long_term_tasks_query_result = long_term_tasks_query.result(); + let i18 = use_i18(); + rsx! { div { class: "pt-4 flex flex-col gap-8", @@ -40,7 +46,7 @@ pub(crate) fn CategoryTodayPage() -> Element { } div { class: "mt-1", - "Long-term" + {translate!(i18, "long_term")._upper_first()} } } div { @@ -103,7 +109,7 @@ pub(crate) fn CategoryTodayPage() -> Element { } div { class: "mt-1", - "Overdue" + {translate!(i18, "overdue")._upper_first()} } } TaskList { @@ -122,9 +128,23 @@ pub(crate) fn CategoryTodayPage() -> Element { div { class: "mt-1", { - today_date - .format_localized("Today, %A %-d. %B", Locale::en_US) - .to_string() + let format = translate!(i18, "formats.date_weekday_format"); + let today_date = today_date.format_localized( + format.as_str(), + LocaleFromLanguageIdentifier::from( + &(i18.selected_language)() + ).into() + ).to_string(); + format!( + "{} – {}", + translate!(i18, "today")._upper_first(), + if translate!(i18, "formats.weekday_lowercase_first") + .parse().unwrap() { + today_date._lower_first() + } else { + today_date + } + ) } } } diff --git a/src/components/task_form.rs b/src/components/task_form.rs index b6e6a42..1dd3696 100644 --- a/src/components/task_form.rs +++ b/src/components/task_form.rs @@ -1,5 +1,6 @@ use crate::components::category_input::CategoryInput; use crate::components::reoccurrence_input::ReoccurrenceIntervalInput; +use crate::components::subtasks_form::SubtasksForm; use crate::models::category::{CalendarTime, Category, Reoccurrence}; use crate::models::task::NewTask; use crate::models::task::Task; @@ -7,12 +8,14 @@ use crate::query::{QueryErrors, QueryKey, QueryValue}; use crate::route::Route; use crate::server::projects::get_projects; use crate::server::tasks::{create_task, delete_task, edit_task}; -use chrono::{Duration}; +use chrono::Duration; use dioxus::core_macro::{component, rsx}; use dioxus::dioxus_core::Element; use dioxus::prelude::*; use dioxus_query::prelude::use_query_client; -use crate::components::subtasks_form::SubtasksForm; +use dioxus_sdk::i18n::use_i18; +use dioxus_sdk::translate; +use voca_rs::Voca; const REMINDER_OFFSETS: [Option; 17] = [ None, @@ -79,6 +82,8 @@ pub(crate) fn TaskForm(task: Option, on_successful_submit: EventHandler<() let query_client = use_query_client::(); let task_for_submit = task.clone(); + let i18 = use_i18(); + rsx! { div { class: "p-4 flex flex-col gap-4", @@ -172,7 +177,7 @@ pub(crate) fn TaskForm(task: Option, on_successful_submit: EventHandler<() id: "input_project", option { value: 0, - "None" + {translate!(i18, "none")} }, for project in projects { option { @@ -330,13 +335,14 @@ pub(crate) fn TaskForm(task: Option, on_successful_submit: EventHandler<() label { r#for: "category_calendar_reminder_offset_index", class: "pr-3 min-w-16 text-right", - {REMINDER_OFFSETS[category_calendar_reminder_offset_index()].map( - |offset| if offset.num_hours() < 1 { - format!("{} min", offset.num_minutes()) - } else { - format!("{} h", offset.num_hours()) - } - ).unwrap_or_else(|| "none".to_string())} + {REMINDER_OFFSETS[category_calendar_reminder_offset_index()] + .map( + |offset| if offset.num_hours() < 1 { + format!("{} min", offset.num_minutes()) + } else { + format!("{} h", offset.num_hours()) + } + ).unwrap_or_else(|| translate!(i18, "none"))} } } } diff --git a/src/components/task_list_item.rs b/src/components/task_list_item.rs index c8835b9..ed67c82 100644 --- a/src/components/task_list_item.rs +++ b/src/components/task_list_item.rs @@ -1,12 +1,18 @@ -use chrono::{Datelike, Local}; +use crate::internationalization::LocaleFromLanguageIdentifier; use crate::models::category::Category; use crate::models::task::TaskWithSubtasks; +use chrono::{Datelike, Local}; use dioxus::core_macro::rsx; use dioxus::dioxus_core::Element; use dioxus::prelude::*; +use dioxus_sdk::i18n::use_i18; +use dioxus_sdk::translate; +use voca_rs::Voca; #[component] pub(crate) fn TaskListItem(task: TaskWithSubtasks) -> Element { + let i18 = use_i18(); + rsx! { div { class: "flex flex-col", @@ -22,11 +28,47 @@ pub(crate) fn TaskListItem(task: TaskWithSubtasks) -> Element { i { class: "fa-solid fa-bomb" }, - {deadline.format(if deadline.year() == Local::now().year() { - " %m. %-d." - } else { - " %m. %-d. %Y" - }).to_string()} + { + let today_date = Local::now().date_naive(); + format!( + " {}", + if deadline == today_date - chrono::Days::new(1) { + translate!(i18, "yesterday") + } else if deadline == today_date { + translate!(i18, "today") + } else if deadline == today_date + chrono::Days::new(1) { + translate!(i18, "tomorrow") + } else if deadline > today_date + && deadline <= today_date + chrono::Days::new(7) { + let deadline = deadline.format_localized( + "%A", + LocaleFromLanguageIdentifier::from( + &(i18.selected_language)() + ).into() + ).to_string(); + if translate!(i18, "formats.weekday_lowercase_first") + .parse().unwrap() { + deadline._lower_first() + } else { + deadline + } + } else { + let format = translate!(i18, + if deadline.year() == today_date.year() { + "formats.date_format" + } else { + "formats.date_year_format" + } + ); + deadline.format_localized( + format.as_str(), + LocaleFromLanguageIdentifier::from( + &(i18.selected_language)() + ).into() + ).to_string() + } + ) + } } } if let Category::Calendar { time, .. } = task.task().category() { @@ -36,7 +78,10 @@ pub(crate) fn TaskListItem(task: TaskWithSubtasks) -> Element { i { class: "fa-solid fa-clock" }, - {calendar_time.time().format(" %k:%M").to_string()} + { + let format = translate!(i18, "formats.time_format"); + format!(" {}",calendar_time.time().format(format.as_str())) + } } } } -- 2.47.1