From e3b1d5ff6db97e7df2c082e97464dff9260589fb Mon Sep 17 00:00:00 2001 From: Florian Berthold Date: Sun, 15 Mar 2026 21:57:23 +0100 Subject: [PATCH] Add bilingual native_range and wildlife_value fields (DE/EN) to species MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migration 011 adds native_range_de, native_range_en, wildlife_value_de, wildlife_value_en columns. Backend INSERT/UPDATE queries updated ($64→$68). Frontend species and cultivar detail pages use pick_desc() for language selection. --- .../011_species_bilingual_range_wildlife.sql | 4 ++ herbapi-api/src/db/models.rs | 8 +++ herbapi-api/src/db/species.rs | 49 +++++++++++-------- herbapi-ui/src/pages/cultivars.rs | 4 +- herbapi-ui/src/pages/species.rs | 4 +- herbapi-ui/src/types.rs | 4 ++ 6 files changed, 49 insertions(+), 24 deletions(-) create mode 100644 herbapi-api/migrations/011_species_bilingual_range_wildlife.sql diff --git a/herbapi-api/migrations/011_species_bilingual_range_wildlife.sql b/herbapi-api/migrations/011_species_bilingual_range_wildlife.sql new file mode 100644 index 0000000..2a7cb1b --- /dev/null +++ b/herbapi-api/migrations/011_species_bilingual_range_wildlife.sql @@ -0,0 +1,4 @@ +ALTER TABLE species ADD COLUMN IF NOT EXISTS native_range_de TEXT; +ALTER TABLE species ADD COLUMN IF NOT EXISTS native_range_en TEXT; +ALTER TABLE species ADD COLUMN IF NOT EXISTS wildlife_value_de TEXT; +ALTER TABLE species ADD COLUMN IF NOT EXISTS wildlife_value_en TEXT; diff --git a/herbapi-api/src/db/models.rs b/herbapi-api/src/db/models.rs index e66b1c1..6e23aa0 100644 --- a/herbapi-api/src/db/models.rs +++ b/herbapi-api/src/db/models.rs @@ -110,6 +110,8 @@ pub struct Species { pub other_uses_de: Option, pub other_uses_en: Option, pub native_range: Option, + pub native_range_de: Option, + pub native_range_en: Option, pub invasiveness: Option, pub pollination_type: Option, pub plant_layer: Option, @@ -119,6 +121,8 @@ pub struct Species { pub attracts_pollinators: Option, pub attracts_beneficial_insects: Option, pub wildlife_value: Option, + pub wildlife_value_de: Option, + pub wildlife_value_en: Option, pub mulch_plant: Option, pub ground_cover_quality: Option, pub allelopathic: Option, @@ -179,6 +183,8 @@ pub struct CreateSpecies { pub other_uses_de: Option, pub other_uses_en: Option, pub native_range: Option, + pub native_range_de: Option, + pub native_range_en: Option, pub invasiveness: Option, pub pollination_type: Option, pub plant_layer: Option, @@ -188,6 +194,8 @@ pub struct CreateSpecies { pub attracts_pollinators: Option, pub attracts_beneficial_insects: Option, pub wildlife_value: Option, + pub wildlife_value_de: Option, + pub wildlife_value_en: Option, pub mulch_plant: Option, pub ground_cover_quality: Option, pub allelopathic: Option, diff --git a/herbapi-api/src/db/species.rs b/herbapi-api/src/db/species.rs index ecd127d..228bb38 100644 --- a/herbapi-api/src/db/species.rs +++ b/herbapi-api/src/db/species.rs @@ -109,9 +109,12 @@ pub async fn create(pool: &PgPool, req: &CreateSpecies) -> Result { food_uses, food_uses_de, food_uses_en, medicinal_uses, medicinal_uses_de, medicinal_uses_en, other_uses, other_uses_de, other_uses_en, - native_range, invasiveness, pollination_type, + native_range, native_range_de, native_range_en, + invasiveness, pollination_type, plant_layer, nitrogen_fixer, dynamic_accumulator, dynamic_accumulator_nutrients, - attracts_pollinators, attracts_beneficial_insects, wildlife_value, mulch_plant, + attracts_pollinators, attracts_beneficial_insects, + wildlife_value, wildlife_value_de, wildlife_value_en, + mulch_plant, ground_cover_quality, allelopathic, guild_role, succession_stage, heavy_metal_tolerance, wikidata_qid, gbif_id, eppo_code, pfaf_url, source_urls, nectar_value, pollen_value, wild_bee_count, wild_bee_specialist_count, @@ -119,10 +122,10 @@ pub async fn create(pool: &PgPool, req: &CreateSpecies) -> Result { hoverfly_count, beetle_count, bird_count, mammal_count, native_status, naturadb_tags) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18, - $19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31, - $32,$33,$34,$35,$36,$37,$38,$39,$40,$41,$42,$43,$44, - $45,$46,$47,$48,$49,$50,$51,$52,$53,$54,$55,$56,$57, - $58,$59,$60,$61,$62,$63,$64) + $19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32,$33, + $34,$35,$36,$37,$38,$39,$40,$41,$42,$43,$44,$45,$46, + $47,$48,$49,$50,$51,$52,$53,$54,$55,$56,$57,$58,$59, + $60,$61,$62,$63,$64,$65,$66,$67,$68) RETURNING *" ) .bind(id).bind(&slug).bind(req.family_id).bind(&req.name_scientific) @@ -136,11 +139,13 @@ pub async fn create(pool: &PgPool, req: &CreateSpecies) -> Result { .bind(&req.food_uses).bind(&req.food_uses_de).bind(&req.food_uses_en) .bind(&req.medicinal_uses).bind(&req.medicinal_uses_de).bind(&req.medicinal_uses_en) .bind(&req.other_uses).bind(&req.other_uses_de).bind(&req.other_uses_en) - .bind(&req.native_range).bind(&req.invasiveness).bind(&req.pollination_type) + .bind(&req.native_range).bind(&req.native_range_de).bind(&req.native_range_en) + .bind(&req.invasiveness).bind(&req.pollination_type) .bind(&req.plant_layer).bind(req.nitrogen_fixer).bind(req.dynamic_accumulator) .bind(&req.dynamic_accumulator_nutrients) .bind(req.attracts_pollinators).bind(req.attracts_beneficial_insects) - .bind(&req.wildlife_value).bind(req.mulch_plant) + .bind(&req.wildlife_value).bind(&req.wildlife_value_de).bind(&req.wildlife_value_en) + .bind(req.mulch_plant) .bind(&req.ground_cover_quality).bind(req.allelopathic).bind(&req.guild_role) .bind(&req.succession_stage).bind(req.heavy_metal_tolerance) .bind(&req.wikidata_qid).bind(&req.gbif_id).bind(&req.eppo_code).bind(&req.pfaf_url) @@ -174,16 +179,18 @@ pub async fn update(pool: &PgPool, id: Uuid, req: &CreateSpecies) -> Result Result Element { let sp_food = pick_desc(¤t_lang, &sp.food_uses_de, &sp.food_uses_en, &sp.food_uses); let sp_med = pick_desc(¤t_lang, &sp.medicinal_uses_de, &sp.medicinal_uses_en, &sp.medicinal_uses); let sp_other = pick_desc(¤t_lang, &sp.other_uses_de, &sp.other_uses_en, &sp.other_uses); - let sp_wildlife = opt_str(&sp.wildlife_value); - let sp_native = opt_str(&sp.native_range); + let sp_wildlife = pick_desc(¤t_lang, &sp.wildlife_value_de, &sp.wildlife_value_en, &sp.wildlife_value); + let sp_native = pick_desc(¤t_lang, &sp.native_range_de, &sp.native_range_en, &sp.native_range); rsx! { table { class: "attr-table", tbody { diff --git a/herbapi-ui/src/pages/species.rs b/herbapi-ui/src/pages/species.rs index 1c4843a..361fee4 100644 --- a/herbapi-ui/src/pages/species.rs +++ b/herbapi-ui/src/pages/species.rs @@ -510,8 +510,8 @@ pub fn SpeciesDetail(slug: String) -> Element { // Ecology 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 wildlife = pick_desc(¤t_lang, &s.wildlife_value_de, &s.wildlife_value_en, &s.wildlife_value); + let native = pick_desc(¤t_lang, &s.native_range_de, &s.native_range_en, &s.native_range); let pollination = s.pollination_type.as_deref().map(|v| t_val(l, v).to_string()).unwrap_or_else(|| em.to_string()); // Growing requirements diff --git a/herbapi-ui/src/types.rs b/herbapi-ui/src/types.rs index 934c26c..620e062 100644 --- a/herbapi-ui/src/types.rs +++ b/herbapi-ui/src/types.rs @@ -52,12 +52,16 @@ pub struct Species { pub other_uses_de: Option, pub other_uses_en: Option, pub native_range: Option, + pub native_range_de: Option, + pub native_range_en: Option, pub plant_layer: Option, pub nitrogen_fixer: Option, pub dynamic_accumulator: Option, pub attracts_pollinators: Option, pub attracts_beneficial_insects: Option, pub wildlife_value: Option, + pub wildlife_value_de: Option, + pub wildlife_value_en: Option, pub mulch_plant: Option, pub ground_cover_quality: Option, pub allelopathic: Option,