Add DE/EN language toggle with bilingual descriptions
- Language switcher in sidebar (DE/EN buttons, persists to localStorage) - i18n module with pick_desc/pick_name helpers for language-aware fallback - All detail/list pages use language context for names and descriptions - Species/Cultivar types updated with description_de/description_en - Common Name column added to species/families lists
This commit is contained in:
@@ -120,8 +120,51 @@ em {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.sidebar-user {
|
||||
/* Language toggle */
|
||||
|
||||
.sidebar-lang {
|
||||
margin-top: auto;
|
||||
padding: 0.75rem 1.25rem;
|
||||
border-top: 1px solid rgba(255,255,255,0.1);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.lang-toggle {
|
||||
display: flex;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(255,255,255,0.2);
|
||||
}
|
||||
|
||||
.lang-btn {
|
||||
padding: 0.3rem 0.75rem;
|
||||
background: transparent;
|
||||
color: rgba(255,255,255,0.5);
|
||||
border: none;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.lang-btn:hover {
|
||||
color: rgba(255,255,255,0.8);
|
||||
background: rgba(255,255,255,0.05);
|
||||
}
|
||||
|
||||
.lang-btn-active {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.lang-btn-active:hover {
|
||||
background: var(--accent-hover);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.sidebar-user {
|
||||
padding: 1rem 1.25rem;
|
||||
border-top: 1px solid rgba(255,255,255,0.1);
|
||||
display: flex;
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
use dioxus::prelude::*;
|
||||
use gloo_storage::{LocalStorage, Storage};
|
||||
|
||||
use crate::api;
|
||||
use crate::types::MeResponse;
|
||||
|
||||
/// Global language signal shared via context. Values: "de" or "en".
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Lang(pub Signal<String>);
|
||||
|
||||
#[derive(Routable, Clone, Debug, PartialEq)]
|
||||
#[rustfmt::skip]
|
||||
pub enum Route {
|
||||
@@ -43,6 +48,15 @@ pub fn App() -> Element {
|
||||
|
||||
#[component]
|
||||
fn Layout() -> Element {
|
||||
// Language state: load from localStorage, default "de"
|
||||
let lang_signal = use_signal(|| {
|
||||
LocalStorage::get::<String>("herbapi_lang").unwrap_or_else(|_| "de".to_string())
|
||||
});
|
||||
use_context_provider(|| Lang(lang_signal));
|
||||
|
||||
let mut lang = use_context::<Lang>().0;
|
||||
let current_lang = lang.read().clone();
|
||||
|
||||
// Try to get current user (may be None for public access)
|
||||
let auth = use_resource(|| async { api::get_current_user().await.ok() });
|
||||
let user: Option<MeResponse> = auth.read().as_ref().and_then(|r| r.clone());
|
||||
@@ -66,6 +80,26 @@ fn Layout() -> Element {
|
||||
NavLink { to: Route::SearchPage {}, label: "Search" }
|
||||
NavLink { to: Route::Sources {}, label: "Sources" }
|
||||
}
|
||||
div { class: "sidebar-lang",
|
||||
div { class: "lang-toggle",
|
||||
button {
|
||||
class: if current_lang == "de" { "lang-btn lang-btn-active" } else { "lang-btn" },
|
||||
onclick: move |_| {
|
||||
lang.set("de".to_string());
|
||||
let _ = LocalStorage::set("herbapi_lang", "de".to_string());
|
||||
},
|
||||
"DE"
|
||||
}
|
||||
button {
|
||||
class: if current_lang == "en" { "lang-btn lang-btn-active" } else { "lang-btn" },
|
||||
onclick: move |_| {
|
||||
lang.set("en".to_string());
|
||||
let _ = LocalStorage::set("herbapi_lang", "en".to_string());
|
||||
},
|
||||
"EN"
|
||||
}
|
||||
}
|
||||
}
|
||||
div { class: "sidebar-user",
|
||||
if let Some(ref u) = user {
|
||||
span { class: "user-name", "{u.nickname.as_deref().or(u.name.as_deref()).unwrap_or(&u.email)}" }
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/// Pick the right description based on language, falling back to the other.
|
||||
pub fn pick_desc(lang: &str, de: &Option<String>, en: &Option<String>, fallback: &Option<String>) -> String {
|
||||
match lang {
|
||||
"de" => de.as_deref()
|
||||
.or(fallback.as_deref())
|
||||
.or(en.as_deref())
|
||||
.unwrap_or("\u{2014}").to_string(),
|
||||
_ => en.as_deref()
|
||||
.or(fallback.as_deref())
|
||||
.or(de.as_deref())
|
||||
.unwrap_or("\u{2014}").to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Pick the right name based on language
|
||||
pub fn pick_name(lang: &str, de: &Option<String>, en: &Option<String>, scientific: &str) -> String {
|
||||
match lang {
|
||||
"de" => de.as_deref().filter(|s| !s.is_empty())
|
||||
.or(en.as_deref().filter(|s| !s.is_empty()))
|
||||
.unwrap_or(scientific).to_string(),
|
||||
_ => en.as_deref().filter(|s| !s.is_empty())
|
||||
.or(de.as_deref().filter(|s| !s.is_empty()))
|
||||
.unwrap_or(scientific).to_string(),
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
mod api;
|
||||
mod app;
|
||||
mod components;
|
||||
mod i18n;
|
||||
mod pages;
|
||||
mod types;
|
||||
|
||||
|
||||
@@ -3,8 +3,9 @@ use std::collections::HashMap;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::api;
|
||||
use crate::app::Route;
|
||||
use crate::app::{Lang, Route};
|
||||
use crate::components::table_controls::*;
|
||||
use crate::i18n::{pick_desc, pick_name};
|
||||
|
||||
/// Convert a month-number array (1=Jan..12=Dec) to a comma-separated string of abbreviations.
|
||||
fn months_display(months: &Option<Vec<i32>>) -> String {
|
||||
@@ -279,6 +280,7 @@ pub fn CultivarList() -> Element {
|
||||
|
||||
#[component]
|
||||
pub fn CultivarDetail(slug: String) -> Element {
|
||||
let lang = use_context::<Lang>().0;
|
||||
let slug_clone = slug.clone();
|
||||
let cultivar = use_resource(move || {
|
||||
let s = slug_clone.clone();
|
||||
@@ -326,11 +328,14 @@ pub fn CultivarDetail(slug: String) -> Element {
|
||||
None => rsx! { p { "Loading..." } },
|
||||
Some(Err(e)) => rsx! { p { class: "error", "Error: {e}" } },
|
||||
Some(Ok(c)) => {
|
||||
let current_lang = lang.read().clone();
|
||||
|
||||
// Pre-compute display strings outside of rsx
|
||||
let common_name = pick_name(¤t_lang, &c.name_de, &c.name_en, &c.name);
|
||||
let name_en = opt_str(&c.name_en);
|
||||
let name_de = opt_str(&c.name_de);
|
||||
let name_sci = opt_str(&c.name_scientific);
|
||||
let desc = opt_str(&c.description);
|
||||
let desc = pick_desc(¤t_lang, &c.description_de, &c.description_en, &c.description);
|
||||
let organic = bool_display(c.is_organic);
|
||||
let perennial = bool_display(c.perennial);
|
||||
|
||||
@@ -369,6 +374,9 @@ pub fn CultivarDetail(slug: String) -> Element {
|
||||
|
||||
rsx! {
|
||||
h1 { "{c.name}" }
|
||||
if common_name != c.name {
|
||||
p { class: "name-common", "{common_name}" }
|
||||
}
|
||||
|
||||
div { class: "detail-row",
|
||||
// === LEFT COLUMN ===
|
||||
@@ -383,6 +391,10 @@ pub fn CultivarDetail(slug: String) -> Element {
|
||||
th { "Name" }
|
||||
td { "{c.name}" }
|
||||
}
|
||||
tr {
|
||||
th { "Common Name" }
|
||||
td { "{common_name}" }
|
||||
}
|
||||
tr {
|
||||
th { "Name EN" }
|
||||
td { class: if name_en == "\u{2014}" { "placeholder" } else { "" }, "{name_en}" }
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::api;
|
||||
use crate::app::Route;
|
||||
use crate::app::{Lang, Route};
|
||||
use crate::components::table_controls::*;
|
||||
use crate::i18n::pick_name;
|
||||
|
||||
const STORAGE_KEY_COLS: &str = "herbapi_families_cols";
|
||||
const STORAGE_KEY_PP: &str = "herbapi_families_pp";
|
||||
@@ -10,14 +11,16 @@ 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: "name_en", label: "English", default_visible: true },
|
||||
ColumnDef { key: "name_de", label: "German", 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));
|
||||
@@ -79,6 +82,9 @@ pub fn FamilyList() -> Element {
|
||||
if is_col_visible(&vis, "name_scientific") {
|
||||
th { "Scientific Name" }
|
||||
}
|
||||
if is_col_visible(&vis, "common_name") {
|
||||
th { "Common Name" }
|
||||
}
|
||||
if is_col_visible(&vis, "name_en") {
|
||||
th { "English" }
|
||||
}
|
||||
@@ -92,23 +98,31 @@ pub fn FamilyList() -> Element {
|
||||
}
|
||||
tbody {
|
||||
for f in data.data.iter() {
|
||||
tr {
|
||||
if is_col_visible(&vis, "name_scientific") {
|
||||
td {
|
||||
Link { to: Route::FamilyDetail { slug: f.slug.clone() },
|
||||
em { "{f.name_scientific}" }
|
||||
{
|
||||
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 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())}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,6 +154,7 @@ pub fn FamilyList() -> Element {
|
||||
|
||||
#[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();
|
||||
@@ -157,16 +172,16 @@ pub fn FamilyDetail(slug: String) -> Element {
|
||||
match &*family.read() {
|
||||
None => rsx! { p { "Loading..." } },
|
||||
Some(Err(e)) => rsx! { p { class: "error", "Error: {e}" } },
|
||||
Some(Ok(f)) => rsx! {
|
||||
h1 { em { "{f.name_scientific}" } }
|
||||
if let Some(ref en) = f.name_en {
|
||||
p { class: "name-common", "{en}" }
|
||||
}
|
||||
if let Some(ref de) = f.name_de {
|
||||
p { class: "name-common", "{de}" }
|
||||
}
|
||||
if let Some(ref desc) = f.description {
|
||||
p { "{desc}" }
|
||||
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}" }
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -178,12 +193,18 @@ pub fn FamilyDetail(slug: String) -> Element {
|
||||
Some(Ok(data)) => rsx! {
|
||||
div { class: "card-grid",
|
||||
for s in data.data.iter() {
|
||||
div { class: "plant-card",
|
||||
Link { to: Route::SpeciesDetail { slug: s.slug.clone() },
|
||||
em { "{s.name_scientific}" }
|
||||
}
|
||||
if let Some(ref en) = s.name_en {
|
||||
p { class: "card-common", "{en}" }
|
||||
{
|
||||
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}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::api;
|
||||
use crate::app::Route;
|
||||
use crate::app::{Lang, Route};
|
||||
use crate::components::plant_card::PlantCard;
|
||||
use crate::i18n::pick_name;
|
||||
|
||||
#[component]
|
||||
pub fn Home() -> Element {
|
||||
let lang = use_context::<Lang>().0;
|
||||
let mut search_query = use_signal(|| String::new());
|
||||
let species = use_resource(|| async { api::list_species(1, 12, None, None).await });
|
||||
|
||||
@@ -36,12 +38,18 @@ pub fn Home() -> Element {
|
||||
Some(Ok(data)) => rsx! {
|
||||
div { class: "card-grid",
|
||||
for s in data.data.iter().take(12) {
|
||||
PlantCard {
|
||||
key: "{s.id}",
|
||||
slug: s.slug.clone(),
|
||||
name: s.name_scientific.clone(),
|
||||
name_common: s.name_en.clone(),
|
||||
entity_type: "species".to_string(),
|
||||
{
|
||||
let card_common = pick_name(&lang.read(), &s.name_de, &s.name_en, &s.name_scientific);
|
||||
let show_common = if card_common == s.name_scientific { None } else { Some(card_common) };
|
||||
rsx! {
|
||||
PlantCard {
|
||||
key: "{s.id}",
|
||||
slug: s.slug.clone(),
|
||||
name: s.name_scientific.clone(),
|
||||
name_common: show_common,
|
||||
entity_type: "species".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@ use std::collections::HashMap;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::api;
|
||||
use crate::app::Route;
|
||||
use crate::app::{Lang, Route};
|
||||
use crate::components::plant_card::PlantCard;
|
||||
use crate::components::table_controls::*;
|
||||
use crate::i18n::{pick_desc, pick_name};
|
||||
|
||||
const STORAGE_KEY_COLS: &str = "herbapi_species_cols";
|
||||
const STORAGE_KEY_PP: &str = "herbapi_species_pp";
|
||||
@@ -15,7 +16,8 @@ const STORAGE_KEY_VIEW: &str = "herbapi_species_view";
|
||||
fn species_columns() -> Vec<ColumnDef> {
|
||||
vec![
|
||||
ColumnDef { key: "name_scientific", label: "Scientific Name", default_visible: true },
|
||||
ColumnDef { key: "name_de", label: "German", default_visible: true },
|
||||
ColumnDef { key: "common_name", label: "Common Name", default_visible: true },
|
||||
ColumnDef { key: "name_de", label: "German", default_visible: false },
|
||||
ColumnDef { key: "name_en", label: "English", default_visible: false },
|
||||
ColumnDef { key: "family", label: "Family", default_visible: true },
|
||||
ColumnDef { key: "plant_layer", label: "Layer", default_visible: true },
|
||||
@@ -33,6 +35,7 @@ fn species_columns() -> Vec<ColumnDef> {
|
||||
|
||||
#[component]
|
||||
pub fn SpeciesList() -> Element {
|
||||
let lang = use_context::<Lang>().0;
|
||||
let columns = species_columns();
|
||||
let mut page = use_signal(|| 1i64);
|
||||
let per_page = use_signal(|| load_per_page(STORAGE_KEY_PP, 25));
|
||||
@@ -222,6 +225,9 @@ pub fn SpeciesList() -> Element {
|
||||
if is_col_visible(&vis, "name_scientific") {
|
||||
th { "Scientific Name" }
|
||||
}
|
||||
if is_col_visible(&vis, "common_name") {
|
||||
th { "Common Name" }
|
||||
}
|
||||
if is_col_visible(&vis, "name_de") {
|
||||
th { "German" }
|
||||
}
|
||||
@@ -267,6 +273,7 @@ pub fn SpeciesList() -> Element {
|
||||
for s in data.data.iter() {
|
||||
{
|
||||
let family_name: &str = fm.get(&s.family_id).map(String::as_str).unwrap_or("-");
|
||||
let common = pick_name(&lang.read(), &s.name_de, &s.name_en, &s.name_scientific);
|
||||
rsx! {
|
||||
tr {
|
||||
if is_col_visible(&vis, "name_scientific") {
|
||||
@@ -276,6 +283,9 @@ pub fn SpeciesList() -> Element {
|
||||
}
|
||||
}
|
||||
}
|
||||
if is_col_visible(&vis, "common_name") {
|
||||
td { "{common}" }
|
||||
}
|
||||
if is_col_visible(&vis, "name_de") {
|
||||
td { "{s.name_de.as_deref().unwrap_or(\"-\")}" }
|
||||
}
|
||||
@@ -369,12 +379,18 @@ pub fn SpeciesList() -> Element {
|
||||
} else {
|
||||
div { class: "card-grid",
|
||||
for s in data.data.iter() {
|
||||
PlantCard {
|
||||
key: "{s.id}",
|
||||
slug: s.slug.clone(),
|
||||
name: s.name_scientific.clone(),
|
||||
name_common: s.name_en.clone(),
|
||||
entity_type: "species".to_string(),
|
||||
{
|
||||
let card_common = pick_name(&lang.read(), &s.name_de, &s.name_en, &s.name_scientific);
|
||||
let show_common = if card_common == s.name_scientific { None } else { Some(card_common) };
|
||||
rsx! {
|
||||
PlantCard {
|
||||
key: "{s.id}",
|
||||
slug: s.slug.clone(),
|
||||
name: s.name_scientific.clone(),
|
||||
name_common: show_common,
|
||||
entity_type: "species".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -404,6 +420,7 @@ pub fn SpeciesList() -> Element {
|
||||
|
||||
#[component]
|
||||
pub fn SpeciesDetail(slug: String) -> Element {
|
||||
let lang = use_context::<Lang>().0;
|
||||
let slug_clone = slug.clone();
|
||||
let species = use_resource(move || {
|
||||
let s = slug_clone.clone();
|
||||
@@ -436,6 +453,7 @@ pub fn SpeciesDetail(slug: String) -> Element {
|
||||
Some(Err(e)) => rsx! { p { class: "error", "Error: {e}" } },
|
||||
Some(Ok(s)) => {
|
||||
let species_slug = s.slug.clone();
|
||||
let current_lang = lang.read().clone();
|
||||
|
||||
// Helper closures to format fields
|
||||
let os = |v: &Option<String>| -> String {
|
||||
@@ -460,9 +478,10 @@ pub fn SpeciesDetail(slug: String) -> Element {
|
||||
|
||||
let em = "\u{2014}";
|
||||
|
||||
let common_name = pick_name(¤t_lang, &s.name_de, &s.name_en, &s.name_scientific);
|
||||
let name_en = os(&s.name_en);
|
||||
let name_de = os(&s.name_de);
|
||||
let desc = os(&s.description);
|
||||
let desc = pick_desc(¤t_lang, &s.description_de, &s.description_en, &s.description);
|
||||
|
||||
// Uses
|
||||
let food = os(&s.food_uses);
|
||||
@@ -511,6 +530,9 @@ pub fn SpeciesDetail(slug: String) -> Element {
|
||||
|
||||
rsx! {
|
||||
h1 { em { "{s.name_scientific}" } }
|
||||
if common_name != s.name_scientific {
|
||||
p { class: "name-common", "{common_name}" }
|
||||
}
|
||||
|
||||
div { class: "detail-row",
|
||||
// === LEFT COLUMN ===
|
||||
@@ -525,6 +547,10 @@ pub fn SpeciesDetail(slug: String) -> Element {
|
||||
th { "Scientific Name" }
|
||||
td { em { "{s.name_scientific}" } }
|
||||
}
|
||||
tr {
|
||||
th { "Common Name" }
|
||||
td { "{common_name}" }
|
||||
}
|
||||
tr {
|
||||
th { "Name EN" }
|
||||
td { class: if name_en == em { "placeholder" } else { "" }, "{name_en}" }
|
||||
@@ -894,6 +920,7 @@ pub fn SpeciesDetail(slug: String) -> Element {
|
||||
|
||||
#[component]
|
||||
fn CultivarListForSpecies(species_slug: String) -> Element {
|
||||
let lang = use_context::<Lang>().0;
|
||||
let slug = species_slug.clone();
|
||||
let cultivars = use_resource(move || {
|
||||
let s = slug.clone();
|
||||
@@ -911,15 +938,21 @@ fn CultivarListForSpecies(species_slug: String) -> Element {
|
||||
rsx! {
|
||||
div { class: "card-grid",
|
||||
for c in data.data.iter() {
|
||||
div { class: "plant-card",
|
||||
Link { to: Route::CultivarDetail { slug: c.slug.clone() },
|
||||
strong { "{c.name}" }
|
||||
}
|
||||
if let Some(ref en) = c.name_en {
|
||||
p { class: "card-common", "{en}" }
|
||||
}
|
||||
if c.is_organic {
|
||||
span { class: "badge organic", "Organic" }
|
||||
{
|
||||
let cv_common = pick_name(&lang.read(), &c.name_de, &c.name_en, &c.name);
|
||||
let show_common = if cv_common == c.name { None } else { Some(cv_common) };
|
||||
rsx! {
|
||||
div { class: "plant-card",
|
||||
Link { to: Route::CultivarDetail { slug: c.slug.clone() },
|
||||
strong { "{c.name}" }
|
||||
}
|
||||
if let Some(ref common) = show_common {
|
||||
p { class: "card-common", "{common}" }
|
||||
}
|
||||
if c.is_organic {
|
||||
span { class: "badge organic", "Organic" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ pub struct Species {
|
||||
pub name_en: Option<String>,
|
||||
pub name_de: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub description_en: Option<String>,
|
||||
pub description_de: Option<String>,
|
||||
pub soil_moisture: Option<String>,
|
||||
pub ph_min: Option<f64>,
|
||||
pub ph_max: Option<f64>,
|
||||
@@ -89,6 +91,8 @@ pub struct Cultivar {
|
||||
pub name_de: Option<String>,
|
||||
pub name_scientific: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub description_en: Option<String>,
|
||||
pub description_de: Option<String>,
|
||||
pub is_organic: bool,
|
||||
pub perennial: bool,
|
||||
pub growing_time_days: Option<i32>,
|
||||
|
||||
Reference in New Issue
Block a user