From b24038cb572039802eeadbd4b8a35ac9daf6a6fd Mon Sep 17 00:00:00 2001 From: Florian Berthold Date: Sun, 15 Mar 2026 21:50:41 +0100 Subject: [PATCH] Add German translations for species enum values (plant_layer, drought_tolerance, pollination_type, succession_stage, native_status) --- herbapi-ui/src/i18n.rs | 41 ++++++++++++++++++++++++++ herbapi-ui/src/pages/cultivars.rs | 11 +++---- herbapi-ui/src/pages/species.rs | 48 ++++++++++++++++--------------- 3 files changed, 72 insertions(+), 28 deletions(-) diff --git a/herbapi-ui/src/i18n.rs b/herbapi-ui/src/i18n.rs index 17f9534..9379635 100644 --- a/herbapi-ui/src/i18n.rs +++ b/herbapi-ui/src/i18n.rs @@ -205,6 +205,8 @@ pub fn t(lang: &str, key: &str) -> &'static str { ("de", "sources.intro") => "HerbAPI aggregiert Pflanzendaten aus mehreren offenen Quellen. Wir danken diesen Projekten, dass sie botanisches Wissen frei zug\u{00e4}nglich machen.", ("de", "species_species") => "Arten", ("de", "loading_species_data") => "Lade Artdaten\u{2026}", + ("de", "specialists") => "Spezialisten", + ("de", "caterpillar_hosts") => "Raupen-Wirtspflanzen", // ── English defaults ────────────────────────────────────── @@ -385,7 +387,46 @@ pub fn t(lang: &str, key: &str) -> &'static str { (_, "sources.intro") => "HerbAPI aggregates plant data from multiple open sources. We are grateful to these projects for making botanical knowledge freely available.", (_, "species_species") => "species", (_, "loading_species_data") => "Loading species data\u{2026}", + (_, "specialists") => "specialists", + (_, "caterpillar_hosts") => "caterpillar hosts", _ => "???", } } + +/// Translate a stored enum/tag value to the display language. +/// Falls back to the raw value for unknown combinations. +pub fn t_val<'a>(lang: &str, value: &'a str) -> &'a str { + match (lang, value) { + // plant_layer + ("de", "canopy") => "Baumschicht", + ("de", "understory") => "Unterholz", + ("de", "shrub") => "Strauch", + ("de", "herbaceous") => "Krautig", + ("de", "ground_cover") => "Bodendecker", + ("de", "vine") => "Kletterpflanze", + ("de", "root") => "Wurzelschicht", + // drought_tolerance + ("de", "none") => "keine", + ("de", "low") => "gering", + ("de", "moderate") => "mittel", + ("de", "high") => "hoch", + ("de", "very_high") => "sehr hoch", + // pollination_type + ("de", "insect") => "Insekten", + ("de", "wind") => "Wind", + ("de", "self") => "Selbstbest\u{00e4}ubung", + ("de", "water") => "Wasser", + ("de", "Insect-pollinated") => "Insektenbest\u{00e4}ubt", + ("de", "Wind-pollinated") => "Windbest\u{00e4}ubt", + // succession_stage + ("de", "pioneer") => "Pionierstadium", + ("de", "secondary") => "Sekund\u{00e4}rstadium", + ("de", "climax") => "Klimaxstadium", + // native_status + ("de", "heimische Wildform") => "Heimische Wildform", + ("de", "Arch\u{00e4}ophyt") => "Arch\u{00e4}ophyt", + ("de", "Neophyt") => "Neophyt", + _ => value, + } +} diff --git a/herbapi-ui/src/pages/cultivars.rs b/herbapi-ui/src/pages/cultivars.rs index 3551452..ee01e4a 100644 --- a/herbapi-ui/src/pages/cultivars.rs +++ b/herbapi-ui/src/pages/cultivars.rs @@ -6,7 +6,7 @@ use crate::api; use crate::app::{Lang, Route}; use crate::components::planting_calendar::PlantingCalendar; use crate::components::table_controls::*; -use crate::i18n::{pick_desc, pick_name, t}; +use crate::i18n::{pick_desc, pick_name, t, t_val}; /// Convert a month-number array (1=Jan..12=Dec) to a comma-separated string of abbreviations. fn months_display(months: &Option>) -> String { @@ -418,8 +418,9 @@ pub fn CultivarDetail(slug: String) -> Element { .filter(|s| !s.is_empty()) .map(|z| format!("USDA Zone {z}")); let light = infer_light_from_layer(&sp.plant_layer); - let drought = sp.drought_tolerance.clone() - .filter(|s| !s.is_empty()); + let drought = sp.drought_tolerance.as_deref() + .filter(|s| !s.is_empty()) + .map(|v| t_val(l, v).to_string()); let usda = sp.hardiness_zone_usda.clone() .filter(|s| !s.is_empty()); (frost, light, drought, usda) @@ -839,8 +840,8 @@ pub fn CultivarDetail(slug: String) -> Element { (None, Some(mx)) => format!("< {mx}"), _ => "\u{2014}".to_string(), }; - let sp_layer = opt_str(&sp.plant_layer); - let sp_drought = opt_str(&sp.drought_tolerance); + let sp_layer = sp.plant_layer.as_deref().map(|v| t_val(l, v).to_string()).unwrap_or_else(|| "\u{2014}".to_string()); + let sp_drought = sp.drought_tolerance.as_deref().map(|v| t_val(l, v).to_string()).unwrap_or_else(|| "\u{2014}".to_string()); let sp_usda = opt_str(&sp.hardiness_zone_usda); let sp_nfix = opt_bool_t(sp.nitrogen_fixer, l); let sp_dynacc = opt_bool_t(sp.dynamic_accumulator, l); diff --git a/herbapi-ui/src/pages/species.rs b/herbapi-ui/src/pages/species.rs index 13291a3..1c4843a 100644 --- a/herbapi-ui/src/pages/species.rs +++ b/herbapi-ui/src/pages/species.rs @@ -7,7 +7,7 @@ use crate::api; use crate::app::{Lang, Route}; use crate::components::plant_card::PlantCard; use crate::components::table_controls::*; -use crate::i18n::{pick_desc, pick_name, t}; +use crate::i18n::{pick_desc, pick_name, t, t_val}; const STORAGE_KEY_COLS: &str = "herbapi_species_cols"; const STORAGE_KEY_PP: &str = "herbapi_species_pp"; @@ -140,13 +140,13 @@ pub fn SpeciesList() -> Element { page.set(1); }, option { value: "", "{t(&l, \"filter.all\")}" } - option { value: "canopy", "Canopy" } - option { value: "understory", "Understory" } - option { value: "shrub", "Shrub" } - option { value: "herbaceous", "Herbaceous" } - option { value: "ground_cover", "Ground Cover" } - option { value: "vine", "Vine" } - option { value: "root", "Root" } + option { value: "canopy", "{t_val(&l, \"canopy\")}" } + option { value: "understory", "{t_val(&l, \"understory\")}" } + option { value: "shrub", "{t_val(&l, \"shrub\")}" } + option { value: "herbaceous", "{t_val(&l, \"herbaceous\")}" } + option { value: "ground_cover", "{t_val(&l, \"ground_cover\")}" } + option { value: "vine", "{t_val(&l, \"vine\")}" } + option { value: "root", "{t_val(&l, \"root\")}" } } } div { class: "filter-group", @@ -158,11 +158,11 @@ pub fn SpeciesList() -> Element { page.set(1); }, option { value: "", "{t(&l, \"filter.all\")}" } - option { value: "none", "None" } - option { value: "low", "Low" } - option { value: "moderate", "Moderate" } - option { value: "high", "High" } - option { value: "very_high", "Very High" } + option { value: "none", "{t_val(&l, \"none\")}" } + option { value: "low", "{t_val(&l, \"low\")}" } + option { value: "moderate", "{t_val(&l, \"moderate\")}" } + option { value: "high", "{t_val(&l, \"high\")}" } + option { value: "very_high", "{t_val(&l, \"very_high\")}" } } } div { class: "filter-group filter-checkbox", @@ -297,7 +297,7 @@ pub fn SpeciesList() -> Element { td { class: "species-name", em { "{family_name}" } } } if is_col_visible(&vis, "plant_layer") { - td { "{s.plant_layer.as_deref().unwrap_or(\"-\")}" } + td { "{s.plant_layer.as_deref().map(|v| t_val(&l, v)).unwrap_or(\"-\")}" } } if is_col_visible(&vis, "nitrogen_fixer") { td { @@ -331,7 +331,7 @@ pub fn SpeciesList() -> Element { } } if is_col_visible(&vis, "drought_tolerance") { - td { "{s.drought_tolerance.as_deref().unwrap_or(\"-\")}" } + td { "{s.drought_tolerance.as_deref().map(|v| t_val(&l, v)).unwrap_or(\"-\")}" } } if is_col_visible(&vis, "hardiness_zone_usda") { td { "{s.hardiness_zone_usda.as_deref().unwrap_or(\"-\")}" } @@ -362,7 +362,8 @@ pub fn SpeciesList() -> Element { s if s.contains("eophyt") => "native-badge native-badge-neophyt", _ => "native-badge", }; - rsx! { span { class: "{badge_class}", "{ns}" } } + let translated = t_val(&l, ns); + rsx! { span { class: "{badge_class}", "{translated}" } } }, _ => rsx! { "-" }, } @@ -507,11 +508,11 @@ pub fn SpeciesDetail(slug: String) -> Element { }; // Ecology - let layer = os(&s.plant_layer); - let succession = os(&s.succession_stage); + let layer = s.plant_layer.as_deref().map(|v| t_val(l, v).to_string()).unwrap_or_else(|| em.to_string()); + let succession = s.succession_stage.as_deref().map(|v| t_val(l, v).to_string()).unwrap_or_else(|| em.to_string()); let wildlife = os(&s.wildlife_value); let native = os(&s.native_range); - let pollination = os(&s.pollination_type); + let pollination = s.pollination_type.as_deref().map(|v| t_val(l, v).to_string()).unwrap_or_else(|| em.to_string()); // Growing requirements let soil_moist = os(&s.soil_moisture); @@ -521,7 +522,7 @@ pub fn SpeciesDetail(slug: String) -> Element { (None, Some(mx)) => format!("< {mx}"), _ => em.to_string(), }; - let drought = os(&s.drought_tolerance); + let drought = s.drought_tolerance.as_deref().map(|v| t_val(l, v).to_string()).unwrap_or_else(|| em.to_string()); let salt = os(&s.salt_tolerance); let usda = os(&s.hardiness_zone_usda); let at_zone = os(&s.hardiness_zone_at); @@ -810,7 +811,7 @@ pub fn SpeciesDetail(slug: String) -> Element { th { "{t(l, \"field.wild_bees\")}" } td { match (s.wild_bee_count, s.wild_bee_specialist_count) { - (Some(total), Some(spec)) => rsx! { "{total} {t(l, \"species_species\")} ({spec} specialists)" }, + (Some(total), Some(spec)) => rsx! { "{total} {t(l, \"species_species\")} ({spec} {t(l, \"specialists\")})" }, (Some(total), None) => rsx! { "{total} {t(l, \"species_species\")}" }, _ => rsx! { span { class: "placeholder", "\u{2014}" } }, } @@ -820,7 +821,7 @@ pub fn SpeciesDetail(slug: String) -> Element { th { "{t(l, \"field.butterflies\")}" } td { match (s.butterfly_moth_count, s.caterpillar_host_count) { - (Some(bm), Some(ch)) => rsx! { "{bm} {t(l, \"species_species\")} ({ch} caterpillar hosts)" }, + (Some(bm), Some(ch)) => rsx! { "{bm} {t(l, \"species_species\")} ({ch} {t(l, \"caterpillar_hosts\")})" }, (Some(bm), None) => rsx! { "{bm} {t(l, \"species_species\")}" }, _ => rsx! { span { class: "placeholder", "\u{2014}" } }, } @@ -879,7 +880,8 @@ pub fn SpeciesDetail(slug: String) -> Element { s if s.contains("eophyt") => "native-badge native-badge-neophyt", _ => "native-badge", }; - rsx! { span { class: "{badge_class}", "{ns}" } } + let translated = t_val(l, ns); + rsx! { span { class: "{badge_class}", "{translated}" } } }, _ => rsx! { span { class: "placeholder", "\u{2014}" } }, }