Add full DE/EN UI label translations across all pages

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.
This commit is contained in:
2026-03-15 17:20:51 +01:00
parent 896b364b09
commit aa36c846d7
11 changed files with 679 additions and 270 deletions
+101 -102
View File
@@ -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};
use crate::i18n::{pick_desc, pick_name, t};
/// Convert a month-number array (1=Jan..12=Dec) to a comma-separated string of abbreviations.
fn months_display(months: &Option<Vec<i32>>) -> String {
@@ -48,18 +48,18 @@ fn opt_i32_suffix(val: Option<i32>, suffix: &str) -> String {
}
}
/// Format an Option<bool> as Yes / No / em dash.
fn opt_bool(val: Option<bool>) -> String {
/// Format an Option<bool> as Yes / No / em dash (translated).
fn opt_bool_t(val: Option<bool>, lang: &str) -> String {
match val {
Some(true) => "Yes".to_string(),
Some(false) => "No".to_string(),
Some(true) => t(lang, "yes").to_string(),
Some(false) => t(lang, "no").to_string(),
None => "\u{2014}".to_string(),
}
}
/// Bool field: always present (non-Option).
fn bool_display(val: bool) -> &'static str {
if val { "Yes" } else { "No" }
/// Bool field: always present (non-Option), translated.
fn bool_display_t(val: bool, lang: &str) -> String {
if val { t(lang, "yes").to_string() } else { t(lang, "no").to_string() }
}
/// Format an Option<Vec<String>> as comma-separated or em dash.
@@ -109,6 +109,7 @@ fn cultivar_columns() -> Vec<ColumnDef> {
#[component]
pub fn CultivarList() -> Element {
let lang = use_context::<Lang>().0;
let columns = cultivar_columns();
let mut page = use_signal(|| 1i64);
let per_page = use_signal(|| load_per_page(STORAGE_KEY_PP, 25));
@@ -146,16 +147,17 @@ pub fn CultivarList() -> Element {
});
let current_page = *page.read();
let l = lang.read().clone();
rsx! {
div { class: "page",
h1 { "Cultivars" }
h1 { "{t(&l, \"page.cultivars\")}" }
div { class: "table-toolbar",
div { class: "search-bar",
input {
r#type: "text",
placeholder: "Search cultivars...",
placeholder: "{t(&l, \"search.placeholder_cultivars\")}",
value: "{search}",
oninput: move |e| {
search.set(e.value());
@@ -177,7 +179,7 @@ pub fn CultivarList() -> Element {
}
match &*cultivars.read() {
None => rsx! { p { "Loading..." } },
None => rsx! { p { "{t(&l, \"loading\")}" } },
Some(Err(e)) => rsx! { p { class: "error", "Error: {e}" } },
Some(Ok(data)) => {
let smap = species_map.read();
@@ -188,34 +190,34 @@ pub fn CultivarList() -> Element {
};
let vis = visible_cols.read();
rsx! {
p { class: "result-count", "{data.total} cultivars" }
p { class: "result-count", "{data.total} {t(&l, \"nav.cultivars\").to_lowercase()}" }
div { class: "table-wrap",
table {
thead {
tr {
if is_col_visible(&vis, "name") {
th { "Name" }
th { "{t(&l, \"field.name\")}" }
}
if is_col_visible(&vis, "species") {
th { "Species" }
th { "{t(&l, \"field.species\")}" }
}
if is_col_visible(&vis, "organic") {
th { "Organic" }
th { "{t(&l, \"field.organic\")}" }
}
if is_col_visible(&vis, "perennial") {
th { "Perennial" }
th { "{t(&l, \"field.perennial\")}" }
}
if is_col_visible(&vis, "description") {
th { "Description" }
th { "{t(&l, \"field.description\")}" }
}
if is_col_visible(&vis, "frost_tolerance") {
th { "Frost Tol." }
th { "{t(&l, \"field.frost_tol\")}" }
}
if is_col_visible(&vis, "growing_time") {
th { "Growing Time" }
th { "{t(&l, \"field.growing_time\")}" }
}
if is_col_visible(&vis, "days_germ") {
th { "Days to Germ." }
th { "{t(&l, \"field.days_germ\")}" }
}
}
}
@@ -238,14 +240,14 @@ pub fn CultivarList() -> Element {
if is_col_visible(&vis, "organic") {
td {
if c.is_organic {
span { class: "badge organic", "Yes" }
span { class: "badge organic", "{t(&l, \"yes\")}" }
} else {
"-"
}
}
}
if is_col_visible(&vis, "perennial") {
td { if c.perennial { "Yes" } else { "Annual" } }
td { if c.perennial { "{t(&l, \"yes\")}" } else { "{t(&l, \"annual\")}" } }
}
if is_col_visible(&vis, "description") {
td { class: "cell-truncated",
@@ -283,13 +285,13 @@ pub fn CultivarList() -> Element {
button {
disabled: current_page <= 1,
onclick: move |_| page.set(current_page - 1),
"Previous"
"{t(&l, \"btn.previous\")}"
}
span { "Page {current_page} of {(data.total + data.per_page - 1) / data.per_page}" }
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),
"Next"
"{t(&l, \"btn.next\")}"
}
}
}
@@ -359,10 +361,11 @@ pub fn CultivarDetail(slug: String) -> Element {
rsx! {
div { class: "page cultivar-detail",
match &*cultivar.read() {
None => rsx! { p { "Loading..." } },
None => rsx! { p { "{t(&lang.read(), \"loading\")}" } },
Some(Err(e)) => rsx! { p { class: "error", "Error: {e}" } },
Some(Ok(c)) => {
let current_lang = lang.read().clone();
let l = &*current_lang;
// Pre-compute display strings outside of rsx
let common_name = pick_name(&current_lang, &c.name_de, &c.name_en, &c.name);
@@ -370,8 +373,8 @@ pub fn CultivarDetail(slug: String) -> Element {
let name_de = opt_str(&c.name_de);
let name_sci = opt_str(&c.name_scientific);
let desc = pick_desc(&current_lang, &c.description_de, &c.description_en, &c.description);
let organic = bool_display(c.is_organic);
let perennial = bool_display(c.perennial);
let organic = bool_display_t(c.is_organic, l);
let perennial = bool_display_t(c.perennial, l);
// Planting schedule
let indoor = months_display(&c.indoor_sowing_months);
@@ -393,9 +396,9 @@ pub fn CultivarDetail(slug: String) -> Element {
let days_germ = opt_i32_suffix(c.days_to_germination, "");
let germ_temp = opt_f64_suffix(c.germination_temp_c, " \u{00b0}C");
let light_req = opt_str(&c.light_requirement);
let strat_req = opt_bool(c.stratification_required);
let strat_req = opt_bool_t(c.stratification_required, l);
let strat_days = opt_i32_suffix(c.stratification_days, "");
let scar_req = opt_bool(c.scarification_required);
let scar_req = opt_bool_t(c.scarification_required, l);
// Climate
let min_temp = opt_f64_suffix(c.min_temp, " \u{00b0}C");
@@ -407,8 +410,6 @@ pub fn CultivarDetail(slug: String) -> Element {
let opt_light_h = opt_f64_suffix(c.optimal_light_hours_day, " h");
// --- Species fallback values ---
// When a cultivar field is empty, we show species-level
// data styled as an estimation.
let (sp_frost_fb, sp_light_fb, sp_drought_fb, sp_usda_fb) = {
let sp_read = species_data.read();
match &*sp_read {
@@ -433,7 +434,7 @@ pub fn CultivarDetail(slug: String) -> Element {
|| sp_drought_fb.is_some()
|| sp_usda_fb.is_some();
// Species image: prefer primary from images API, else primary_image_key
// Species image
let sp_primary_img: Option<crate::types::Image> = {
let imgs = species_images.read();
match &*imgs {
@@ -460,7 +461,7 @@ pub fn CultivarDetail(slug: String) -> Element {
// Week-based planting calendar (full width)
div { class: "detail-card", style: "margin-top: 1.25rem;",
div { class: "detail-card-header", "Planting Calendar" }
div { class: "detail-card-header", "{t(l, \"card.planting_calendar\")}" }
div { style: "padding: 0.75rem;",
PlantingCalendar {
indoor_sowing_months: c.indoor_sowing_months.clone(),
@@ -483,27 +484,27 @@ pub fn CultivarDetail(slug: String) -> Element {
// Card 1: Cultivar Details
div { class: "detail-card",
div { class: "detail-card-header", "Cultivar Details" }
div { class: "detail-card-header", "{t(l, \"card.cultivar_details\")}" }
table { class: "attr-table",
tbody {
tr {
th { "Name" }
th { "{t(l, \"field.name\")}" }
td { "{c.name}" }
}
tr {
th { "Common Name" }
th { "{t(l, \"field.common_name\")}" }
td { "{common_name}" }
}
tr {
th { "Name EN" }
th { "{t(l, \"field.name_en\")}" }
td { class: if name_en == "\u{2014}" { "placeholder" } else { "" }, "{name_en}" }
}
tr {
th { "Name DE" }
th { "{t(l, \"field.name_de\")}" }
td { class: if name_de == "\u{2014}" { "placeholder" } else { "" }, "{name_de}" }
}
tr {
th { "Scientific Name" }
th { "{t(l, \"field.scientific_name\")}" }
td { class: if name_sci == "\u{2014}" { "placeholder" } else { "" },
if name_sci != "\u{2014}" {
em { "{name_sci}" }
@@ -513,7 +514,7 @@ pub fn CultivarDetail(slug: String) -> Element {
}
}
tr {
th { "Species" }
th { "{t(l, \"field.species\")}" }
td {
if let Some(Some(ref sp)) = *species_data.read() {
Link { to: Route::SpeciesDetail { slug: sp.slug.clone() },
@@ -530,15 +531,15 @@ pub fn CultivarDetail(slug: String) -> Element {
}
}
tr {
th { "Description" }
th { "{t(l, \"field.description\")}" }
td { class: if desc == "\u{2014}" { "placeholder" } else { "" }, "{desc}" }
}
tr {
th { "Organic" }
th { "{t(l, \"field.organic\")}" }
td { "{organic}" }
}
tr {
th { "Perennial" }
th { "{t(l, \"field.perennial\")}" }
td { "{perennial}" }
}
}
@@ -547,35 +548,35 @@ pub fn CultivarDetail(slug: String) -> Element {
// Card 2: Planting Schedule
div { class: "detail-card",
div { class: "detail-card-header", "Planting Schedule" }
div { class: "detail-card-header", "{t(l, \"card.planting_schedule\")}" }
table { class: "attr-table",
tbody {
tr {
th { "Indoor Sowing" }
th { "{t(l, \"cal.indoor_sowing\")}" }
td { class: if indoor == "\u{2014}" { "placeholder" } else { "" }, "{indoor}" }
}
tr {
th { "Direct Sowing" }
th { "{t(l, \"cal.direct_sowing\")}" }
td { class: if direct == "\u{2014}" { "placeholder" } else { "" }, "{direct}" }
}
tr {
th { "Transplanting" }
th { "{t(l, \"cal.transplanting\")}" }
td { class: if transplant == "\u{2014}" { "placeholder" } else { "" }, "{transplant}" }
}
tr {
th { "Glasshouse" }
th { "{t(l, \"cal.glasshouse\")}" }
td { class: if glasshouse == "\u{2014}" { "placeholder" } else { "" }, "{glasshouse}" }
}
tr {
th { "Harvesting" }
th { "{t(l, \"cal.harvesting\")}" }
td { class: if harvest == "\u{2014}" { "placeholder" } else { "" }, "{harvest}" }
}
tr {
th { "Succession Planting" }
th { "{t(l, \"field.succession_planting\")}" }
td { class: if succession == "\u{2014}" { "placeholder" } else { "" }, "{succession}" }
}
tr {
th { "Planting Notes" }
th { "{t(l, \"field.planting_notes\")}" }
td { class: if planting_notes == "\u{2014}" { "placeholder" } else { "" }, "{planting_notes}" }
}
}
@@ -584,7 +585,7 @@ pub fn CultivarDetail(slug: String) -> Element {
// Card 3: Where to Buy
div { class: "detail-card",
div { class: "detail-card-header", "Where to Buy" }
div { class: "detail-card-header", "{t(l, \"card.where_to_buy\")}" }
{
let sup_links = suppliers_data.read();
let sups = all_suppliers.read();
@@ -598,10 +599,10 @@ pub fn CultivarDetail(slug: String) -> Element {
table { class: "attr-table",
thead {
tr {
th { "Supplier" }
th { "SKU" }
th { "Price" }
th { "Link" }
th { "{t(l, \"field.supplier\")}" }
th { "{t(l, \"field.sku\")}" }
th { "{t(l, \"field.price\")}" }
th { "{t(l, \"field.link\")}" }
}
}
tbody {
@@ -625,7 +626,7 @@ pub fn CultivarDetail(slug: String) -> Element {
td {
if let Some(ref u) = url {
if !u.is_empty() {
a { href: "{u}", target: "_blank", class: "external-link", "View" }
a { href: "{u}", target: "_blank", class: "external-link", "{t(l, \"btn.view\")}" }
} else {
span { class: "placeholder", "\u{2014}" }
}
@@ -642,7 +643,7 @@ pub fn CultivarDetail(slug: String) -> Element {
}
},
_ => rsx! {
p { class: "placeholder detail-card-empty", "\u{2014} No suppliers linked" }
p { class: "placeholder detail-card-empty", "\u{2014} {t(l, \"no_suppliers_linked\")}" }
},
}
}
@@ -655,7 +656,7 @@ pub fn CultivarDetail(slug: String) -> Element {
// Image card
if let Some(ref key) = sp_img_key {
div { class: "detail-card",
div { class: "detail-card-header", "Image" }
div { class: "detail-card-header", "{t(l, \"card.image\")}" }
div { class: "image-card-body",
img { class: "species-image", src: "/img/{key}", alt: "{c.name}" }
if let Some(ref img) = sp_primary_img {
@@ -673,7 +674,7 @@ pub fn CultivarDetail(slug: String) -> Element {
if img.caption.is_some() || img.license.is_some() {
" | "
}
a { href: "{url}", target: "_blank", class: "attribution-link", "Source" }
a { href: "{url}", target: "_blank", class: "attribution-link", "{t(l, \"field.source\")}" }
}
}
}
@@ -683,27 +684,27 @@ pub fn CultivarDetail(slug: String) -> Element {
// Card 4: Growing Information
div { class: "detail-card",
div { class: "detail-card-header", "Growing Information" }
div { class: "detail-card-header", "{t(l, \"card.growing_info\")}" }
table { class: "attr-table",
tbody {
tr {
th { "Growing Time" }
th { "{t(l, \"field.growing_time\")}" }
td { class: if growing_time == "\u{2014}" { "placeholder" } else { "" }, "{growing_time}" }
}
tr {
th { "Planting Depth" }
th { "{t(l, \"field.planting_depth\")}" }
td { class: if planting_depth == "\u{2014}" { "placeholder" } else { "" }, "{planting_depth}" }
}
tr {
th { "Row Spacing" }
th { "{t(l, \"field.row_spacing\")}" }
td { class: if row_spacing == "\u{2014}" { "placeholder" } else { "" }, "{row_spacing}" }
}
tr {
th { "Plant Spacing" }
th { "{t(l, \"field.plant_spacing\")}" }
td { class: if plant_spacing == "\u{2014}" { "placeholder" } else { "" }, "{plant_spacing}" }
}
tr {
th { "Propagation Methods" }
th { "{t(l, \"field.propagation\")}" }
td { class: if propagation == "\u{2014}" { "placeholder" } else { "" }, "{propagation}" }
}
}
@@ -712,19 +713,19 @@ pub fn CultivarDetail(slug: String) -> Element {
// Card 5: Germination
div { class: "detail-card",
div { class: "detail-card-header", "Germination" }
div { class: "detail-card-header", "{t(l, \"card.germination\")}" }
table { class: "attr-table",
tbody {
tr {
th { "Days to Germination" }
th { "{t(l, \"field.days_to_germ\")}" }
td { class: if days_germ == "\u{2014}" { "placeholder" } else { "" }, "{days_germ}" }
}
tr {
th { "Germination Temp" }
th { "{t(l, \"field.germ_temp\")}" }
td { class: if germ_temp == "\u{2014}" { "placeholder" } else { "" }, "{germ_temp}" }
}
tr {
th { "Light Requirement" }
th { "{t(l, \"field.light_req\")}" }
if light_req != "\u{2014}" {
td { "{light_req}" }
} else if let Some(ref fb) = sp_light_fb {
@@ -737,15 +738,15 @@ pub fn CultivarDetail(slug: String) -> Element {
}
}
tr {
th { "Stratification Required" }
th { "{t(l, \"field.stratification\")}" }
td { class: if strat_req == "\u{2014}" { "placeholder" } else { "" }, "{strat_req}" }
}
tr {
th { "Stratification Days" }
th { "{t(l, \"field.stratification_days\")}" }
td { class: if strat_days == "\u{2014}" { "placeholder" } else { "" }, "{strat_days}" }
}
tr {
th { "Scarification Required" }
th { "{t(l, \"field.scarification\")}" }
td { class: if scar_req == "\u{2014}" { "placeholder" } else { "" }, "{scar_req}" }
}
}
@@ -754,23 +755,23 @@ pub fn CultivarDetail(slug: String) -> Element {
// Card 6: Climate & Environment
div { class: "detail-card",
div { class: "detail-card-header", "Climate & Environment" }
div { class: "detail-card-header", "{t(l, \"card.climate\")}" }
table { class: "attr-table",
tbody {
tr {
th { "Min Temp" }
th { "{t(l, \"field.min_temp\")}" }
td { class: if min_temp == "\u{2014}" { "placeholder" } else { "" }, "{min_temp}" }
}
tr {
th { "Max Temp" }
th { "{t(l, \"field.max_temp\")}" }
td { class: if max_temp == "\u{2014}" { "placeholder" } else { "" }, "{max_temp}" }
}
tr {
th { "Humidity" }
th { "{t(l, \"field.humidity\")}" }
td { class: if humidity == "\u{2014}" { "placeholder" } else { "" }, "{humidity}" }
}
tr {
th { "Light" }
th { "{t(l, \"field.light\")}" }
if light != "\u{2014}" {
td { "{light}" }
} else if let Some(ref fb) = sp_light_fb {
@@ -783,7 +784,7 @@ pub fn CultivarDetail(slug: String) -> Element {
}
}
tr {
th { "Frost Tolerance" }
th { "{t(l, \"field.frost_tolerance\")}" }
if frost_tol != "\u{2014}" {
td { "{frost_tol}" }
} else if let Some(ref fb) = sp_frost_fb {
@@ -796,16 +797,16 @@ pub fn CultivarDetail(slug: String) -> Element {
}
}
tr {
th { "Min Light Hours/Day" }
th { "{t(l, \"field.min_light_hours\")}" }
td { class: if min_light_h == "\u{2014}" { "placeholder" } else { "" }, "{min_light_h}" }
}
tr {
th { "Optimal Light Hours/Day" }
th { "{t(l, \"field.opt_light_hours\")}" }
td { class: if opt_light_h == "\u{2014}" { "placeholder" } else { "" }, "{opt_light_h}" }
}
if let Some(ref fb) = sp_drought_fb {
tr {
th { "Drought Tolerance" }
th { "{t(l, \"field.drought_tolerance\")}" }
td {
span { class: "estimated-value", "{fb}" }
span { class: "estimated-tag", "~ species" }
@@ -814,7 +815,7 @@ pub fn CultivarDetail(slug: String) -> Element {
}
if let Some(ref fb) = sp_usda_fb {
tr {
th { "USDA Hardiness Zone" }
th { "{t(l, \"field.usda_hardiness_zone\")}" }
td {
span { class: "estimated-value", "{fb}" }
span { class: "estimated-tag", "~ species" }
@@ -827,7 +828,7 @@ pub fn CultivarDetail(slug: String) -> Element {
// Card 7: Species Information
div { class: "detail-card",
div { class: "detail-card-header", "Species Information" }
div { class: "detail-card-header", "{t(l, \"card.species_info\")}" }
{
let sp_read = species_data.read();
match &*sp_read {
@@ -841,8 +842,8 @@ pub fn CultivarDetail(slug: String) -> Element {
let sp_layer = opt_str(&sp.plant_layer);
let sp_drought = opt_str(&sp.drought_tolerance);
let sp_usda = opt_str(&sp.hardiness_zone_usda);
let sp_nfix = opt_bool(sp.nitrogen_fixer);
let sp_dynacc = opt_bool(sp.dynamic_accumulator);
let sp_nfix = opt_bool_t(sp.nitrogen_fixer, l);
let sp_dynacc = opt_bool_t(sp.dynamic_accumulator, l);
let sp_food = pick_desc(&current_lang, &sp.food_uses_de, &sp.food_uses_en, &sp.food_uses);
let sp_med = pick_desc(&current_lang, &sp.medicinal_uses_de, &sp.medicinal_uses_en, &sp.medicinal_uses);
let sp_other = pick_desc(&current_lang, &sp.other_uses_de, &sp.other_uses_en, &sp.other_uses);
@@ -852,47 +853,47 @@ pub fn CultivarDetail(slug: String) -> Element {
table { class: "attr-table",
tbody {
tr {
th { "Plant Layer" }
th { "{t(l, \"field.plant_layer\")}" }
td { class: if sp_layer == "\u{2014}" { "placeholder" } else { "" }, "{sp_layer}" }
}
tr {
th { "Drought Tolerance" }
th { "{t(l, \"field.drought_tolerance\")}" }
td { class: if sp_drought == "\u{2014}" { "placeholder" } else { "" }, "{sp_drought}" }
}
tr {
th { "USDA Zone" }
th { "{t(l, \"field.usda_zone\")}" }
td { class: if sp_usda == "\u{2014}" { "placeholder" } else { "" }, "{sp_usda}" }
}
tr {
th { "pH Range" }
th { "{t(l, \"field.ph_range\")}" }
td { class: if ph_range == "\u{2014}" { "placeholder" } else { "" }, "{ph_range}" }
}
tr {
th { "Nitrogen Fixer" }
th { "{t(l, \"field.nitrogen_fixer\")}" }
td { class: if sp_nfix == "\u{2014}" { "placeholder" } else { "" }, "{sp_nfix}" }
}
tr {
th { "Dynamic Accumulator" }
th { "{t(l, \"field.dynamic_acc\")}" }
td { class: if sp_dynacc == "\u{2014}" { "placeholder" } else { "" }, "{sp_dynacc}" }
}
tr {
th { "Food Uses" }
th { "{t(l, \"field.food_uses\")}" }
td { class: if sp_food == "\u{2014}" { "placeholder" } else { "" }, "{sp_food}" }
}
tr {
th { "Medicinal Uses" }
th { "{t(l, \"field.medicinal_uses\")}" }
td { class: if sp_med == "\u{2014}" { "placeholder" } else { "" }, "{sp_med}" }
}
tr {
th { "Other Uses" }
th { "{t(l, \"field.other_uses\")}" }
td { class: if sp_other == "\u{2014}" { "placeholder" } else { "" }, "{sp_other}" }
}
tr {
th { "Wildlife Value" }
th { "{t(l, \"field.wildlife_value\")}" }
td { class: if sp_wildlife == "\u{2014}" { "placeholder" } else { "" }, "{sp_wildlife}" }
}
tr {
th { "Native Range" }
th { "{t(l, \"field.native_range\")}" }
td { class: if sp_native == "\u{2014}" { "placeholder" } else { "" }, "{sp_native}" }
}
}
@@ -900,7 +901,7 @@ pub fn CultivarDetail(slug: String) -> Element {
}
},
_ => rsx! {
p { class: "placeholder detail-card-empty", "Loading species data\u{2026}" }
p { class: "placeholder detail-card-empty", "{t(l, \"loading_species_data\")}" }
},
}
}
@@ -910,9 +911,7 @@ pub fn CultivarDetail(slug: String) -> Element {
if has_any_fallback {
p { class: "species-fallback-note",
"Values marked "
span { class: "estimated-tag", "~ species" }
" are estimated from species-level data."
"{t(l, \"estimated_note\")}"
}
}
}