aa36c846d7
Add t() translation function to i18n.rs with ~200 key/value pairs covering navigation, card headers, field labels, buttons, filters, planting calendar rows, and general UI text in both German and English. Updated all 11 source files to use t(&lang, "key") instead of hardcoded English strings: app.rs (sidebar nav), all 7 page files (species, cultivars, families, suppliers, home, search, sources), and all 3 component files (planting_calendar, table_controls, plant_card). Boolean values (Yes/No/Annual) are also translated.
220 lines
9.8 KiB
Rust
220 lines
9.8 KiB
Rust
use dioxus::prelude::*;
|
|
|
|
use crate::api;
|
|
use crate::app::{Lang, Route};
|
|
use crate::components::table_controls::*;
|
|
use crate::i18n::{pick_name, t};
|
|
|
|
const STORAGE_KEY_COLS: &str = "herbapi_families_cols";
|
|
const STORAGE_KEY_PP: &str = "herbapi_families_pp";
|
|
|
|
fn family_columns() -> Vec<ColumnDef> {
|
|
vec![
|
|
ColumnDef { key: "name_scientific", label: "Scientific Name", default_visible: true },
|
|
ColumnDef { key: "common_name", label: "Common Name", default_visible: true },
|
|
ColumnDef { key: "name_en", label: "English", default_visible: false },
|
|
ColumnDef { key: "name_de", label: "German", default_visible: false },
|
|
ColumnDef { key: "description", label: "Description", default_visible: false },
|
|
]
|
|
}
|
|
|
|
#[component]
|
|
pub fn FamilyList() -> Element {
|
|
let lang = use_context::<Lang>().0;
|
|
let columns = family_columns();
|
|
let mut page = use_signal(|| 1i64);
|
|
let per_page = use_signal(|| load_per_page(STORAGE_KEY_PP, 25));
|
|
let mut search = use_signal(|| String::new());
|
|
let visible_cols = use_signal(|| load_visible_columns(STORAGE_KEY_COLS, &family_columns()));
|
|
|
|
let families = use_resource(move || {
|
|
let s = search.read().clone();
|
|
let p = *page.read();
|
|
let pp = *per_page.read();
|
|
async move {
|
|
let q = if s.is_empty() { None } else { Some(s.as_str()) };
|
|
api::list_families(p, pp, q).await
|
|
}
|
|
});
|
|
|
|
let current_page = *page.read();
|
|
let l = lang.read().clone();
|
|
|
|
rsx! {
|
|
div { class: "page",
|
|
h1 { "{t(&l, \"page.families\")}" }
|
|
|
|
div { class: "table-toolbar",
|
|
div { class: "search-bar",
|
|
input {
|
|
r#type: "text",
|
|
placeholder: "{t(&l, \"search.placeholder_families\")}",
|
|
value: "{search}",
|
|
oninput: move |e| {
|
|
search.set(e.value());
|
|
page.set(1);
|
|
},
|
|
}
|
|
}
|
|
PerPageSelector {
|
|
per_page: per_page,
|
|
page: page,
|
|
storage_key: STORAGE_KEY_PP.to_string(),
|
|
}
|
|
}
|
|
|
|
ColumnToggle {
|
|
columns: columns.clone(),
|
|
visible: visible_cols,
|
|
storage_key: STORAGE_KEY_COLS.to_string(),
|
|
}
|
|
|
|
match &*families.read() {
|
|
None => rsx! { p { "{t(&l, \"loading\")}" } },
|
|
Some(Err(e)) => rsx! { p { class: "error", "Error: {e}" } },
|
|
Some(Ok(data)) => {
|
|
let vis = visible_cols.read();
|
|
rsx! {
|
|
p { class: "result-count", "{data.total} {t(&l, \"nav.families\").to_lowercase()}" }
|
|
div { class: "table-wrap",
|
|
table {
|
|
thead {
|
|
tr {
|
|
if is_col_visible(&vis, "name_scientific") {
|
|
th { "{t(&l, \"field.scientific_name\")}" }
|
|
}
|
|
if is_col_visible(&vis, "common_name") {
|
|
th { "{t(&l, \"field.common_name\")}" }
|
|
}
|
|
if is_col_visible(&vis, "name_en") {
|
|
th { "{t(&l, \"field.name_en\")}" }
|
|
}
|
|
if is_col_visible(&vis, "name_de") {
|
|
th { "{t(&l, \"field.name_de\")}" }
|
|
}
|
|
if is_col_visible(&vis, "description") {
|
|
th { "{t(&l, \"field.description\")}" }
|
|
}
|
|
}
|
|
}
|
|
tbody {
|
|
for f in data.data.iter() {
|
|
{
|
|
let common = pick_name(&lang.read(), &f.name_de, &f.name_en, &f.name_scientific);
|
|
rsx! {
|
|
tr {
|
|
if is_col_visible(&vis, "name_scientific") {
|
|
td {
|
|
Link { to: Route::FamilyDetail { slug: f.slug.clone() },
|
|
em { "{f.name_scientific}" }
|
|
}
|
|
}
|
|
}
|
|
if is_col_visible(&vis, "common_name") {
|
|
td { "{common}" }
|
|
}
|
|
if is_col_visible(&vis, "name_en") {
|
|
td { "{f.name_en.as_deref().unwrap_or(\"-\")}" }
|
|
}
|
|
if is_col_visible(&vis, "name_de") {
|
|
td { "{f.name_de.as_deref().unwrap_or(\"-\")}" }
|
|
}
|
|
if is_col_visible(&vis, "description") {
|
|
td { class: "cell-truncated",
|
|
"{f.description.as_deref().map(|d| truncate(d, 80)).unwrap_or_else(|| \"-\".to_string())}"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if data.total > data.per_page {
|
|
div { class: "pagination",
|
|
button {
|
|
disabled: current_page <= 1,
|
|
onclick: move |_| page.set(current_page - 1),
|
|
"{t(&l, \"btn.previous\")}"
|
|
}
|
|
span { "{t(&l, \"page\")} {current_page} {t(&l, \"of\")} {(data.total + data.per_page - 1) / data.per_page}" }
|
|
button {
|
|
disabled: current_page * data.per_page >= data.total,
|
|
onclick: move |_| page.set(current_page + 1),
|
|
"{t(&l, \"btn.next\")}"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[component]
|
|
pub fn FamilyDetail(slug: String) -> Element {
|
|
let lang = use_context::<Lang>().0;
|
|
let slug_clone = slug.clone();
|
|
let family = use_resource(move || {
|
|
let s = slug_clone.clone();
|
|
async move { api::get_family(&s).await }
|
|
});
|
|
|
|
let slug_for_species = slug.clone();
|
|
let species = use_resource(move || {
|
|
let s = slug_for_species.clone();
|
|
async move { api::list_species(1, 100, Some(&s), None).await }
|
|
});
|
|
|
|
let l = lang.read().clone();
|
|
|
|
rsx! {
|
|
div { class: "page",
|
|
match &*family.read() {
|
|
None => rsx! { p { "{t(&l, \"loading\")}" } },
|
|
Some(Err(e)) => rsx! { p { class: "error", "Error: {e}" } },
|
|
Some(Ok(f)) => {
|
|
let common = pick_name(&lang.read(), &f.name_de, &f.name_en, &f.name_scientific);
|
|
rsx! {
|
|
h1 { em { "{f.name_scientific}" } }
|
|
if common != f.name_scientific {
|
|
p { class: "name-common", "{common}" }
|
|
}
|
|
if let Some(ref desc) = f.description {
|
|
p { "{desc}" }
|
|
}
|
|
}
|
|
},
|
|
}
|
|
|
|
h2 { "{t(&l, \"page.species_in_family\")}" }
|
|
match &*species.read() {
|
|
None => rsx! { p { "{t(&l, \"loading\")}" } },
|
|
Some(Err(e)) => rsx! { p { class: "error", "Error: {e}" } },
|
|
Some(Ok(data)) => rsx! {
|
|
div { class: "card-grid",
|
|
for s in data.data.iter() {
|
|
{
|
|
let sp_common = pick_name(&lang.read(), &s.name_de, &s.name_en, &s.name_scientific);
|
|
let show_common = if sp_common == s.name_scientific { None } else { Some(sp_common) };
|
|
rsx! {
|
|
div { class: "plant-card",
|
|
Link { to: Route::SpeciesDetail { slug: s.slug.clone() },
|
|
em { "{s.name_scientific}" }
|
|
}
|
|
if let Some(ref common) = show_common {
|
|
p { class: "card-common", "{common}" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
}
|