feat: internationalization (#43)
This commit is contained in:
		
							
								
								
									
										1
									
								
								.env.dev
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								.env.dev
									
									
									
									
									
								
							| @@ -1 +1,2 @@ | |||||||
| DATABASE_URL=postgres://app:app@db/todo_baggins | DATABASE_URL=postgres://app:app@db/todo_baggins | ||||||
|  | LANGUAGE_CODE=en-US | ||||||
|   | |||||||
							
								
								
									
										87
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										87
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -1021,6 +1021,22 @@ dependencies = [ | |||||||
|  "tracing", |  "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]] | [[package]] | ||||||
| name = "dioxus-signals" | name = "dioxus-signals" | ||||||
| version = "0.5.7" | version = "0.5.7" | ||||||
| @@ -1096,6 +1112,17 @@ dependencies = [ | |||||||
|  "syn 2.0.74", |  "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]] | [[package]] | ||||||
| name = "dotenvy" | name = "dotenvy" | ||||||
| version = "0.15.7" | version = "0.15.7" | ||||||
| @@ -1384,8 +1411,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||||||
| checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "cfg-if", |  "cfg-if", | ||||||
|  |  "js-sys", | ||||||
|  "libc", |  "libc", | ||||||
|  "wasi", |  "wasi", | ||||||
|  |  "wasm-bindgen", | ||||||
| ] | ] | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| @@ -2772,6 +2801,12 @@ dependencies = [ | |||||||
|  "lock_api", |  "lock_api", | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "stfu8" | ||||||
|  | version = "0.2.7" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "e51f1e89f093f99e7432c491c382b88a6860a5adbe6bf02574bf0a08efff1978" | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "strsim" | name = "strsim" | ||||||
| version = "0.11.1" | version = "0.11.1" | ||||||
| @@ -2872,6 +2907,15 @@ dependencies = [ | |||||||
|  "time-core", |  "time-core", | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "tinystr" | ||||||
|  | version = "0.7.6" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" | ||||||
|  | dependencies = [ | ||||||
|  |  "displaydoc", | ||||||
|  | ] | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "tinyvec" | name = "tinyvec" | ||||||
| version = "1.8.0" | version = "1.8.0" | ||||||
| @@ -2903,6 +2947,7 @@ dependencies = [ | |||||||
|  "dioxus", |  "dioxus", | ||||||
|  "dioxus-logger", |  "dioxus-logger", | ||||||
|  "dioxus-query", |  "dioxus-query", | ||||||
|  |  "dioxus-sdk", | ||||||
|  "dotenvy", |  "dotenvy", | ||||||
|  "serde", |  "serde", | ||||||
|  "serde_json", |  "serde_json", | ||||||
| @@ -2910,7 +2955,9 @@ dependencies = [ | |||||||
|  "time", |  "time", | ||||||
|  "tracing", |  "tracing", | ||||||
|  "tracing-wasm", |  "tracing-wasm", | ||||||
|  |  "unic-langid-impl", | ||||||
|  "validator", |  "validator", | ||||||
|  |  "voca_rs", | ||||||
| ] | ] | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| @@ -3138,6 +3185,25 @@ version = "1.17.0" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" | 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]] | [[package]] | ||||||
| name = "unicase" | name = "unicase" | ||||||
| version = "2.7.0" | version = "2.7.0" | ||||||
| @@ -3203,6 +3269,16 @@ version = "0.7.6" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" | 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]] | [[package]] | ||||||
| name = "validator" | name = "validator" | ||||||
| version = "0.18.1" | version = "0.18.1" | ||||||
| @@ -3257,6 +3333,17 @@ version = "0.9.5" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" | 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]] | [[package]] | ||||||
| name = "waker-fn" | name = "waker-fn" | ||||||
| version = "1.2.0" | version = "1.2.0" | ||||||
|   | |||||||
| @@ -24,6 +24,9 @@ serde_with = { version = "3.9.0", features = ["chrono_0_4"] } | |||||||
| async-std = "1.12.0" | async-std = "1.12.0" | ||||||
| dioxus-query = "0.5.1" | dioxus-query = "0.5.1" | ||||||
| time = "0.3.36" | time = "0.3.36" | ||||||
|  | dioxus-sdk = { version = "0.5.0", features = ["i18n"] } | ||||||
|  | unic-langid-impl = "0.9.5" | ||||||
|  | voca_rs = "1.15.2" | ||||||
|  |  | ||||||
| [features] | [features] | ||||||
| default = [] | default = [] | ||||||
|   | |||||||
| @@ -4,11 +4,17 @@ use dioxus::dioxus_core::Element; | |||||||
| use dioxus::prelude::*; | use dioxus::prelude::*; | ||||||
| use dioxus_query::prelude::{use_init_query_client}; | use dioxus_query::prelude::{use_init_query_client}; | ||||||
| use crate::query::{QueryErrors, QueryKey, QueryValue}; | 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] | #[component] | ||||||
| pub(crate) fn App() -> Element { | pub(crate) fn App() -> Element { | ||||||
|     use_init_query_client::<QueryValue, QueryErrors, QueryKey>(); |     use_init_query_client::<QueryValue, QueryErrors, QueryKey>(); | ||||||
|      |      | ||||||
|  |     let language_identifier = use_server_future(get_language_identifier)?.unwrap().unwrap(); | ||||||
|  |     use_init_i18n(language_identifier.clone(), language_identifier, get_languages); | ||||||
|  |      | ||||||
|     rsx! { |     rsx! { | ||||||
|         div { |         div { | ||||||
|             class: "min-h-screen text-zinc-200 bg-zinc-800 pt-4 pb-36", |             class: "min-h-screen text-zinc-200 bg-zinc-800 pt-4 pb-36", | ||||||
|   | |||||||
| @@ -1,13 +1,16 @@ | |||||||
|  | use crate::components::task_list::TaskList; | ||||||
|  | use crate::internationalization::LocaleFromLanguageIdentifier; | ||||||
| use crate::models::category::Category; | 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::core_macro::rsx; | ||||||
| use dioxus::dioxus_core::Element; | use dioxus::dioxus_core::Element; | ||||||
| use dioxus::prelude::*; | use dioxus::prelude::*; | ||||||
| use dioxus_query::prelude::QueryResult; | use dioxus_query::prelude::QueryResult; | ||||||
| use crate::components::task_list::TaskList; | use dioxus_sdk::i18n::use_i18; | ||||||
| use crate::query::QueryValue; | use dioxus_sdk::translate; | ||||||
| use crate::query::tasks::use_tasks_with_subtasks_in_category_query; |  | ||||||
| use crate::models::task::{TaskWithSubtasks}; |  | ||||||
|  |  | ||||||
| const CALENDAR_LENGTH_DAYS: usize = 366 * 3; | const CALENDAR_LENGTH_DAYS: usize = 366 * 3; | ||||||
|  |  | ||||||
| @@ -20,6 +23,8 @@ pub(crate) fn CategoryCalendarPage() -> Element { | |||||||
|     }); |     }); | ||||||
|     let tasks_query_result = tasks.result(); |     let tasks_query_result = tasks.result(); | ||||||
|  |  | ||||||
|  |     let i18 = use_i18(); | ||||||
|  |  | ||||||
|     rsx! { |     rsx! { | ||||||
|         match tasks_query_result.value() { |         match tasks_query_result.value() { | ||||||
|             QueryResult::Ok(QueryValue::TasksWithSubtasks(tasks)) |             QueryResult::Ok(QueryValue::TasksWithSubtasks(tasks)) | ||||||
| @@ -37,14 +42,17 @@ pub(crate) fn CategoryCalendarPage() -> Element { | |||||||
|                                     div { |                                     div { | ||||||
|                                         class: "pt-1", |                                         class: "pt-1", | ||||||
|                                         { |                                         { | ||||||
|                                             date_current |                                             date_current.format_localized(translate!( | ||||||
|                                             .format_localized( |                                                     i18, | ||||||
|                                                 format!( |                                                     if date_current.year() == Local::now().year() { | ||||||
|                                                     "%A %-d. %B{}",  |                                                         "formats.date_weekday_format" | ||||||
|                                                     if date_current.year() != today_date.year() |                                                     } else { | ||||||
|                                                     {" %Y"} else {""} |                                                         "formats.date_weekday_year_format" | ||||||
|  |                                                     } | ||||||
|                                                 ).as_str(), |                                                 ).as_str(), | ||||||
|                                                 Locale::en_US |                                                 LocaleFromLanguageIdentifier::from( | ||||||
|  |                                                     &(i18.selected_language)() | ||||||
|  |                                                 ).into() | ||||||
|                                             ) |                                             ) | ||||||
|                                             .to_string() |                                             .to_string() | ||||||
|                                         } |                                         } | ||||||
|   | |||||||
| @@ -1,12 +1,16 @@ | |||||||
| use crate::components::task_list::TaskList; | 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::category::Category; | ||||||
| use crate::models::task::TaskWithSubtasks; | 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 crate::query::QueryValue; | ||||||
| use chrono::{Local, Locale}; | use chrono::Local; | ||||||
| use dioxus::prelude::*; | use dioxus::prelude::*; | ||||||
| use dioxus_query::prelude::QueryResult; | 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] | #[component] | ||||||
| pub(crate) fn CategoryTodayPage() -> Element { | 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 = use_tasks_with_subtasks_in_category_query(Category::LongTerm); | ||||||
|     let long_term_tasks_query_result = long_term_tasks_query.result(); |     let long_term_tasks_query_result = long_term_tasks_query.result(); | ||||||
|  |  | ||||||
|  |     let i18 = use_i18(); | ||||||
|  |  | ||||||
|     rsx! { |     rsx! { | ||||||
|         div { |         div { | ||||||
|             class: "pt-4 flex flex-col gap-8", |             class: "pt-4 flex flex-col gap-8", | ||||||
| @@ -40,7 +46,7 @@ pub(crate) fn CategoryTodayPage() -> Element { | |||||||
|                                 } |                                 } | ||||||
|                                 div { |                                 div { | ||||||
|                                     class: "mt-1", |                                     class: "mt-1", | ||||||
|                                     "Long-term" |                                     {translate!(i18, "long_term")._upper_first()} | ||||||
|                                 } |                                 } | ||||||
|                             } |                             } | ||||||
|                             div { |                             div { | ||||||
| @@ -103,7 +109,7 @@ pub(crate) fn CategoryTodayPage() -> Element { | |||||||
|                                     } |                                     } | ||||||
|                                     div { |                                     div { | ||||||
|                                         class: "mt-1", |                                         class: "mt-1", | ||||||
|                                         "Overdue" |                                         {translate!(i18, "overdue")._upper_first()} | ||||||
|                                     } |                                     } | ||||||
|                                 } |                                 } | ||||||
|                                 TaskList { |                                 TaskList { | ||||||
| @@ -122,9 +128,23 @@ pub(crate) fn CategoryTodayPage() -> Element { | |||||||
|                                 div { |                                 div { | ||||||
|                                     class: "mt-1", |                                     class: "mt-1", | ||||||
|                                     { |                                     { | ||||||
|                                         today_date |                                         let format = translate!(i18, "formats.date_weekday_format"); | ||||||
|                                         .format_localized("Today, %A %-d. %B", Locale::en_US) |                                         let today_date = today_date.format_localized( | ||||||
|                                         .to_string() |                                             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 | ||||||
|  |                                             } | ||||||
|  |                                         ) | ||||||
|                                     } |                                     } | ||||||
|                                 } |                                 } | ||||||
|                             } |                             } | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| use crate::components::category_input::CategoryInput; | use crate::components::category_input::CategoryInput; | ||||||
| use crate::components::reoccurrence_input::ReoccurrenceIntervalInput; | use crate::components::reoccurrence_input::ReoccurrenceIntervalInput; | ||||||
|  | use crate::components::subtasks_form::SubtasksForm; | ||||||
| use crate::models::category::{CalendarTime, Category, Reoccurrence}; | use crate::models::category::{CalendarTime, Category, Reoccurrence}; | ||||||
| use crate::models::task::NewTask; | use crate::models::task::NewTask; | ||||||
| use crate::models::task::Task; | use crate::models::task::Task; | ||||||
| @@ -7,12 +8,14 @@ use crate::query::{QueryErrors, QueryKey, QueryValue}; | |||||||
| use crate::route::Route; | use crate::route::Route; | ||||||
| use crate::server::projects::get_projects; | use crate::server::projects::get_projects; | ||||||
| use crate::server::tasks::{create_task, delete_task, edit_task}; | use crate::server::tasks::{create_task, delete_task, edit_task}; | ||||||
| use chrono::{Duration}; | use chrono::Duration; | ||||||
| use dioxus::core_macro::{component, rsx}; | use dioxus::core_macro::{component, rsx}; | ||||||
| use dioxus::dioxus_core::Element; | use dioxus::dioxus_core::Element; | ||||||
| use dioxus::prelude::*; | use dioxus::prelude::*; | ||||||
| use dioxus_query::prelude::use_query_client; | 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<Duration>; 17] = [ | const REMINDER_OFFSETS: [Option<Duration>; 17] = [ | ||||||
|     None, |     None, | ||||||
| @@ -79,6 +82,8 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<() | |||||||
|     let query_client = use_query_client::<QueryValue, QueryErrors, QueryKey>(); |     let query_client = use_query_client::<QueryValue, QueryErrors, QueryKey>(); | ||||||
|     let task_for_submit = task.clone(); |     let task_for_submit = task.clone(); | ||||||
|  |  | ||||||
|  |     let i18 = use_i18(); | ||||||
|  |  | ||||||
|     rsx! { |     rsx! { | ||||||
|         div { |         div { | ||||||
|             class: "p-4 flex flex-col gap-4", |             class: "p-4 flex flex-col gap-4", | ||||||
| @@ -172,7 +177,7 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<() | |||||||
|                         id: "input_project", |                         id: "input_project", | ||||||
|                         option { |                         option { | ||||||
|                             value: 0, |                             value: 0, | ||||||
|                             "None" |                             {translate!(i18, "none")} | ||||||
|                         }, |                         }, | ||||||
|                         for project in projects { |                         for project in projects { | ||||||
|                             option { |                             option { | ||||||
| @@ -330,13 +335,14 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<() | |||||||
|                                 label { |                                 label { | ||||||
|                                     r#for: "category_calendar_reminder_offset_index", |                                     r#for: "category_calendar_reminder_offset_index", | ||||||
|                                     class: "pr-3 min-w-16 text-right", |                                     class: "pr-3 min-w-16 text-right", | ||||||
|                                     {REMINDER_OFFSETS[category_calendar_reminder_offset_index()].map( |                                     {REMINDER_OFFSETS[category_calendar_reminder_offset_index()] | ||||||
|                                         |offset| if offset.num_hours() < 1 { |                                         .map( | ||||||
|                                             format!("{} min", offset.num_minutes()) |                                             |offset| if offset.num_hours() < 1 { | ||||||
|                                         } else { |                                                 format!("{} min", offset.num_minutes()) | ||||||
|                                             format!("{} h", offset.num_hours()) |                                             } else { | ||||||
|                                         } |                                                 format!("{} h", offset.num_hours()) | ||||||
|                                     ).unwrap_or_else(|| "none".to_string())} |                                             } | ||||||
|  |                                         ).unwrap_or_else(|| translate!(i18, "none"))} | ||||||
|                                 } |                                 } | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|   | |||||||
| @@ -1,12 +1,18 @@ | |||||||
| use chrono::{Datelike, Local}; | use crate::internationalization::LocaleFromLanguageIdentifier; | ||||||
| use crate::models::category::Category; | use crate::models::category::Category; | ||||||
| use crate::models::task::TaskWithSubtasks; | use crate::models::task::TaskWithSubtasks; | ||||||
|  | use chrono::{Datelike, Local}; | ||||||
| use dioxus::core_macro::rsx; | use dioxus::core_macro::rsx; | ||||||
| use dioxus::dioxus_core::Element; | use dioxus::dioxus_core::Element; | ||||||
| use dioxus::prelude::*; | use dioxus::prelude::*; | ||||||
|  | use dioxus_sdk::i18n::use_i18; | ||||||
|  | use dioxus_sdk::translate; | ||||||
|  | use voca_rs::Voca; | ||||||
|  |  | ||||||
| #[component] | #[component] | ||||||
| pub(crate) fn TaskListItem(task: TaskWithSubtasks) -> Element { | pub(crate) fn TaskListItem(task: TaskWithSubtasks) -> Element { | ||||||
|  |     let i18 = use_i18(); | ||||||
|  |  | ||||||
|     rsx! { |     rsx! { | ||||||
|         div { |         div { | ||||||
|             class: "flex flex-col", |             class: "flex flex-col", | ||||||
| @@ -22,11 +28,47 @@ pub(crate) fn TaskListItem(task: TaskWithSubtasks) -> Element { | |||||||
|                         i { |                         i { | ||||||
|                             class: "fa-solid fa-bomb" |                             class: "fa-solid fa-bomb" | ||||||
|                         }, |                         }, | ||||||
|                         {deadline.format(if deadline.year() == Local::now().year() { |                         { | ||||||
|                             " %m. %-d." |                             let today_date = Local::now().date_naive(); | ||||||
|                         } else { |                             format!( | ||||||
|                             " %m. %-d. %Y" |                                 " {}", | ||||||
|                         }).to_string()} |                                 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() { |                 if let Category::Calendar { time, .. } = task.task().category() { | ||||||
| @@ -36,7 +78,10 @@ pub(crate) fn TaskListItem(task: TaskWithSubtasks) -> Element { | |||||||
|                             i { |                             i { | ||||||
|                                 class: "fa-solid fa-clock" |                                 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())) | ||||||
|  |                             } | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|   | |||||||
							
								
								
									
										19
									
								
								src/internationalization/cs_cz.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/internationalization/cs_cz.json
									
									
									
									
									
										Normal file
									
								
							| @@ -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" | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										19
									
								
								src/internationalization/en_us.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/internationalization/en_us.json
									
									
									
									
									
										Normal file
									
								
							| @@ -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" | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										34
									
								
								src/internationalization/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/internationalization/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -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<Language> { | ||||||
|  |     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<LocaleFromLanguageIdentifier<'a>> 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) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -6,6 +6,7 @@ mod schema; | |||||||
| mod server; | mod server; | ||||||
| mod query; | mod query; | ||||||
| mod utils; | mod utils; | ||||||
|  | mod internationalization; | ||||||
|  |  | ||||||
| use components::app::App; | use components::app::App; | ||||||
| use dioxus::prelude::*; | use dioxus::prelude::*; | ||||||
|   | |||||||
| @@ -73,7 +73,7 @@ impl Ord for Task { | |||||||
|                 .then(ReverseOrdOption::from( |                 .then(ReverseOrdOption::from( | ||||||
|                     &self_time.as_ref().map(|calendar_time| calendar_time.time()) |                     &self_time.as_ref().map(|calendar_time| calendar_time.time()) | ||||||
|                 ).cmp(&ReverseOrdOption::from( |                 ).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( |                 .then(ReverseOrdOption::from(&self.deadline()).cmp( | ||||||
|                     &ReverseOrdOption::from(&other.deadline()) |                     &ReverseOrdOption::from(&other.deadline()) | ||||||
|   | |||||||
| @@ -4,9 +4,9 @@ use dotenvy::dotenv; | |||||||
| use std::env; | use std::env; | ||||||
|  |  | ||||||
| pub(crate) fn establish_database_connection() -> ConnectionResult<PgConnection> { | pub(crate) fn establish_database_connection() -> ConnectionResult<PgConnection> { | ||||||
|     dotenv().ok(); |     dotenv().expect("Could not load environment variables."); | ||||||
|  |  | ||||||
|     let database_url = |     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) |     PgConnection::establish(&database_url) | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										14
									
								
								src/server/internationalization.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/server/internationalization.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -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<LanguageIdentifier, ServerFnError> { | ||||||
|  |     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::<LanguageIdentifier>()?) | ||||||
|  | } | ||||||
| @@ -2,3 +2,4 @@ mod database_connection; | |||||||
| pub(crate) mod projects; | pub(crate) mod projects; | ||||||
| pub(crate) mod tasks; | pub(crate) mod tasks; | ||||||
| pub(crate) mod subtasks; | pub(crate) mod subtasks; | ||||||
|  | pub(crate) mod internationalization; | ||||||
|   | |||||||
| @@ -1,10 +1,18 @@ | |||||||
| use std::cmp::Ordering; | 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 | /* The default ordering of `Option`s is `None` being less than `Some`. The purpose of this struct is | ||||||
|    to reverse that. */ |    to reverse that. */ | ||||||
| #[derive(PartialEq)] | #[derive(PartialEq)] | ||||||
| pub(crate) struct ReverseOrdOption<'a, T>(&'a Option<T>); | pub(crate) struct ReverseOrdOption<'a, T>(&'a Option<T>); | ||||||
|  |  | ||||||
|  | impl<'a, T> Deref for ReverseOrdOption<'a, T> { | ||||||
|  |     type Target = Option<T>; | ||||||
|  |  | ||||||
|  |     fn deref(&self) -> &Self::Target { | ||||||
|  |         self.0 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| impl<'a, T: Ord> Eq for ReverseOrdOption<'a, T> {} | impl<'a, T: Ord> Eq for ReverseOrdOption<'a, T> {} | ||||||
|  |  | ||||||
| impl<'a, T: Ord> PartialOrd<Self> for ReverseOrdOption<'a, T> { | impl<'a, T: Ord> PartialOrd<Self> for ReverseOrdOption<'a, T> { | ||||||
| @@ -15,7 +23,7 @@ impl<'a, T: Ord> PartialOrd<Self> for ReverseOrdOption<'a, T> { | |||||||
|  |  | ||||||
| impl<'a, T: Ord> Ord for ReverseOrdOption<'a, T> { | impl<'a, T: Ord> Ord for ReverseOrdOption<'a, T> { | ||||||
|     fn cmp(&self, other: &Self) -> Ordering { |     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, None) => Ordering::Equal, | ||||||
|             (None, Some(_)) => Ordering::Greater, |             (None, Some(_)) => Ordering::Greater, | ||||||
|             (Some(_), None) => Ordering::Less, |             (Some(_), None) => Ordering::Less, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user