English in the code: fixtures, identifiers, and one phrase table

The fallback mail phrases move into a `Phrases` table, the one place a
language other than English appears in this code; the `navn` special
case goes (a form names the person's field `name`). Test fixtures
speak English: traffic, board, hazards, report, fixed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Bendik Aagaard Lynghaug
2026-09-23 22:09:04 +02:00
co-authored by Claude Fable 5.1
parent 713f8ff3a0
commit e3dae063d4
2 changed files with 93 additions and 62 deletions
+92 -61
View File
@@ -741,38 +741,29 @@ pub fn fallback_words(plan: &Plan) -> BTreeMap<String, Value> {
continue; continue;
} }
// The fallback is true in the plan's language before a model // The fallback is true in the plan's language before a model
// has written a word: Norwegian or English, the two the plans // has written a word; the phrases live in one table below.
// come in. let t = Phrases::for_language(&plan.language);
let no = plan.language.starts_with("no") || plan.language.starts_with("nb") || plan.language.starts_with("nn"); let greeting = match need.collects.iter().find(|f| f.name == "name") {
let name_field = need.collects.iter().find(|f| f.name == "name" || f.name == "navn").map(|f| f.name.clone()); Some(f) => format!("{} {{{}}},\n\n", t.hello, f.name),
let greeting = match (&name_field, no) { None => format!("{},\n\n", t.hello),
(Some(f), true) => format!("Hei {{{f}}},\n\n"),
(None, true) => "Hei,\n\n".to_string(),
(Some(f), false) => format!("Hello {{{f}}},\n\n"),
(None, false) => "Hello,\n\n".to_string(),
}; };
let mut per_stage: Map<String, Value> = Map::new(); let mut per_stage: Map<String, Value> = Map::new();
for (stage, to) in &need.mails { for (stage, to) in &need.mails {
let stage_word = humanize(stage).to_lowercase(); let stage_word = humanize(stage).to_lowercase();
let what = match (stage == &need.stages[0], no) { let what = if stage == &need.stages[0] {
(true, true) => format!("Vi har mottatt det du sendte om {}.", need.wants), format!("{} {}.", t.received_what_you_sent_about, need.wants)
(false, true) => format!("Det du sendte om {} er nå {stage_word}.", need.wants), } else {
(true, false) => format!("We have received what you sent about {}.", need.wants), format!("{} {} {} {stage_word}.", t.what_you_sent_about, need.wants, t.is_now)
(false, false) => format!("What you sent about {} is now {stage_word}.", need.wants),
}; };
let mut body = if to == SUBMITTER { let mut body = if to == SUBMITTER {
format!("{greeting}{what}") format!("{greeting}{what}")
} else if no {
format!("En sak i {} er nå {stage_word}: {{chain}}.", humanize(bucket).to_lowercase())
} else { } else {
format!("A case in {} is now {stage_word}: {{chain}}.", humanize(bucket).to_lowercase()) format!("{} {} {} {stage_word}: {{chain}}.", t.a_case_in, humanize(bucket).to_lowercase(), t.is_now)
}; };
if to == SUBMITTER && moves_of(need, SUBMITTER).iter().any(|(from, _)| from == stage) { if to == SUBMITTER && moves_of(need, SUBMITTER).iter().any(|(from, _)| from == stage) {
let lead = if no { "Det du kan gjøre nå" } else { "What you can do next" }; body.push_str(&format!("\n\n{}: {{site}}/decide/{}?chain={{chain}}&email={{email}}", t.what_you_can_do_next, need.id));
body.push_str(&format!("\n\n{lead}: {{site}}/decide/{}?chain={{chain}}&email={{email}}", need.id));
} }
let at = if no { "på" } else { "at" }; body.push_str(&format!("\n\n{} {} {}", plan.contact.name, t.at, plan.contact.email));
body.push_str(&format!("\n\n{} {at} {}", plan.contact.name, plan.contact.email));
per_stage.insert(stage.clone(), json!({ "subject": format!("{}: {}", plan.business, humanize(stage)), "body": body })); per_stage.insert(stage.clone(), json!({ "subject": format!("{}: {}", plan.business, humanize(stage)), "body": body }));
} }
mails.insert(bucket.to_string(), Value::Object(per_stage)); mails.insert(bucket.to_string(), Value::Object(per_stage));
@@ -813,6 +804,46 @@ fn decide_path(need: &PlanNeed) -> String {
const SUBMITTER: &str = "submitter"; const SUBMITTER: &str = "submitter";
/// The words of the fallback mails, per language the plans come in.
/// The only place a language other than English appears in this
/// code: everything else the site says in its own language is a word
/// slot the model or a person fills.
struct Phrases {
hello: &'static str,
received_what_you_sent_about: &'static str,
what_you_sent_about: &'static str,
is_now: &'static str,
a_case_in: &'static str,
what_you_can_do_next: &'static str,
at: &'static str,
}
impl Phrases {
fn for_language(lang: &str) -> Phrases {
if lang.starts_with("no") || lang.starts_with("nb") || lang.starts_with("nn") {
Phrases {
hello: "Hei",
received_what_you_sent_about: "Vi har mottatt det du sendte om",
what_you_sent_about: "Det du sendte om",
is_now: "er nå",
a_case_in: "En sak i",
what_you_can_do_next: "Det du kan gjøre nå",
at: "på",
}
} else {
Phrases {
hello: "Hello",
received_what_you_sent_about: "We have received what you sent about",
what_you_sent_about: "What you sent about",
is_now: "is now",
a_case_in: "A case in",
what_you_can_do_next: "What you can do next",
at: "at",
}
}
}
}
/// Alternatives on one page before a visitor stops reading them. /// Alternatives on one page before a visitor stops reading them.
const MAX_FRONT_DOOR: usize = 7; const MAX_FRONT_DOOR: usize = 7;
@@ -1599,9 +1630,9 @@ mod tests {
#[test] #[test]
fn words_take_the_models_strings_and_never_its_structure() { fn words_take_the_models_strings_and_never_its_structure() {
let fallback = json!({"question": "What?", "needs": {"a": {"heading": "A", "labels": {"email": "Email"}}}}); let fallback = json!({"question": "What?", "needs": {"a": {"heading": "A", "labels": {"email": "Email"}}}});
let given = json!({"question": " Hva trenger du? ", "needs": {"a": {"heading": "", "labels": {"email": 7, "extra": "x"}}, "b": {"heading": "B"}}, "injected": "y"}); let given = json!({"question": " What do you need? ", "needs": {"a": {"heading": "", "labels": {"email": 7, "extra": "x"}}, "b": {"heading": "B"}}, "injected": "y"});
let merged = merge_words(&fallback, &given); let merged = merge_words(&fallback, &given);
assert_eq!(merged, json!({"question": "Hva trenger du?", "needs": {"a": {"heading": "A", "labels": {"email": "Email"}}}})); assert_eq!(merged, json!({"question": "What do you need?", "needs": {"a": {"heading": "A", "labels": {"email": "Email"}}}}));
} }
const PLAN: &str = "business: Nordvik Bygg\ncontact: { name: Ingrid, email: i@n.example }\nabout: We fix old buildings.\nneeds:\n - id: survey\n who: owners\n wants: Someone to look at the building\n lands_in: enquiries\n handled_by: project_managers\n stages: [open, surveyed, quoted, won, lost]\n done_when: [won, lost]\n success: [won]\n max_fields: 4\n collects:\n - { name: email, type: email }\n - { name: problem, type: textarea }\n - { name: address, optional: true }\n - id: report\n who: crew\n wants: Report a hazard\n enters_as: crew\n lands_in: site_issues\n handled_by: hse\n stages: [open, in_hand, closed]\n done_when: [closed]\n success: [closed]\n max_fields: 3\n collects:\n - { name: kind, type: select, options: [Hazard, Near miss] }\n"; const PLAN: &str = "business: Nordvik Bygg\ncontact: { name: Ingrid, email: i@n.example }\nabout: We fix old buildings.\nneeds:\n - id: survey\n who: owners\n wants: Someone to look at the building\n lands_in: enquiries\n handled_by: project_managers\n stages: [open, surveyed, quoted, won, lost]\n done_when: [won, lost]\n success: [won]\n max_fields: 4\n collects:\n - { name: email, type: email }\n - { name: problem, type: textarea }\n - { name: address, optional: true }\n - id: report\n who: crew\n wants: Report a hazard\n enters_as: crew\n lands_in: site_issues\n handled_by: hse\n stages: [open, in_hand, closed]\n done_when: [closed]\n success: [closed]\n max_fields: 3\n collects:\n - { name: kind, type: select, options: [Hazard, Near miss] }\n";
@@ -1731,42 +1762,42 @@ mod tests {
#[test] #[test]
fn selects_can_take_many_and_draw_their_options_from_the_labelled_groups() { fn selects_can_take_many_and_draw_their_options_from_the_labelled_groups() {
let yaml = "business: B\ncontact: { name: I, email: i@b.example }\ngroups:\n - { id: trafikk, label: Trafikkforhold }\n - { id: elvesti, label: Elvesti }\nneeds:\n - id: komite\n who: en som vil sitte i en komité\n wants: en plass\n lands_in: soknader\n handled_by: styret\n stages: [open, plassert, avslatt]\n done_when: [plassert, avslatt]\n max_fields: 3\n collects:\n - { name: navn }\n - { name: komite, type: select, options_from: groups, multiple: true }\n"; let yaml = "business: B\ncontact: { name: I, email: i@b.example }\ngroups:\n - { id: traffic, label: Traffic }\n - { id: elvesti, label: Riverside }\nneeds:\n - id: committee\n who: someone who wants a seat on a committee\n wants: a seat\n lands_in: applications\n handled_by: board\n stages: [open, placed, declined]\n done_when: [placed, declined]\n max_fields: 3\n collects:\n - { name: name }\n - { name: committee, type: select, options_from: groups, multiple: true }\n";
let plan: Plan = serde_yaml::from_str(yaml).unwrap(); let plan: Plan = serde_yaml::from_str(yaml).unwrap();
plan.validate().unwrap(); plan.validate().unwrap();
let files = render(&plan, &fallback_words(&plan)).unwrap(); let files = render(&plan, &fallback_words(&plan)).unwrap();
let page: Value = serde_yaml::from_str(&files["questions/index.yaml"]).unwrap(); let page: Value = serde_yaml::from_str(&files["questions/index.yaml"]).unwrap();
let field = &page["alternatives"][1]["features"][2]["requirements"][1]; let field = &page["alternatives"][1]["features"][2]["requirements"][1];
assert_eq!(field["options"], json!(["Trafikkforhold", "Elvesti"])); assert_eq!(field["options"], json!(["Traffic", "Riverside"]));
assert_eq!(field["multiple"], true); assert_eq!(field["multiple"], true);
// Without labelled groups there is nothing to draw from. // Without labelled groups there is nothing to draw from.
let no_groups: Plan = serde_yaml::from_str(&yaml.replace("groups:\n - { id: trafikk, label: Trafikkforhold }\n - { id: elvesti, label: Elvesti }\n", "")).unwrap(); let no_groups: Plan = serde_yaml::from_str(&yaml.replace("groups:\n - { id: traffic, label: Traffic }\n - { id: elvesti, label: Riverside }\n", "")).unwrap();
assert!(no_groups.validate().unwrap_err().to_string().contains("no labelled")); assert!(no_groups.validate().unwrap_err().to_string().contains("no labelled"));
// `multiple` on a text field is a mistake, not a no-op. // `multiple` on a text field is a mistake, not a no-op.
let bad: Plan = serde_yaml::from_str(&yaml.replace("{ name: navn }", "{ name: navn, multiple: true }")).unwrap(); let bad: Plan = serde_yaml::from_str(&yaml.replace("{ name: name }", "{ name: name, multiple: true }")).unwrap();
assert!(bad.validate().unwrap_err().to_string().contains("multiple is for select")); assert!(bad.validate().unwrap_err().to_string().contains("multiple is for select"));
} }
#[test] #[test]
fn a_public_watcher_gets_a_read_only_page_without_contact_details() { fn a_public_watcher_gets_a_read_only_page_without_contact_details() {
let yaml = "business: B\ncontact: { name: I, email: i@b.example }\nneeds:\n - id: trafikk\n who: en som har sett noe farlig\n wants: at det tas opp\n lands_in: meldinger\n handled_by: trafikk\n stages: [open, ordnet]\n done_when: [ordnet]\n watched_by: [styret, public]\n max_fields: 2\n collects:\n - { name: hvor }\n - { name: hva, type: textarea }\n - { name: epost, type: email, optional: true }\n - { name: navn, optional: true, private: true }\n"; let yaml = "business: B\ncontact: { name: I, email: i@b.example }\nneeds:\n - id: traffic\n who: someone who saw a hazard\n wants: at det tas opp\n lands_in: meldinger\n handled_by: traffic\n stages: [open, fixed]\n done_when: [fixed]\n watched_by: [board, public]\n max_fields: 2\n collects:\n - { name: where }\n - { name: what, type: textarea }\n - { name: epost, type: email, optional: true }\n - { name: name, optional: true, private: true }\n";
let plan: Plan = serde_yaml::from_str(yaml).unwrap(); let plan: Plan = serde_yaml::from_str(yaml).unwrap();
plan.validate().unwrap(); plan.validate().unwrap();
let files = render(&plan, &fallback_words(&plan)).unwrap(); let files = render(&plan, &fallback_words(&plan)).unwrap();
// `public` is not a group: no desk directory for it. // `public` is not a group: no desk directory for it.
assert!(!files.contains_key("questions/public/index.yaml")); assert!(!files.contains_key("questions/public/index.yaml"));
assert!(files.contains_key("questions/styret/index.yaml")); assert!(files.contains_key("questions/board/index.yaml"));
let view: Value = serde_yaml::from_str(&files["questions/status/meldinger/index.yaml"]).unwrap(); let view: Value = serde_yaml::from_str(&files["questions/status/meldinger/index.yaml"]).unwrap();
let resource = &view["alternatives"][0]["features"][0]["resource"]; let resource = &view["alternatives"][0]["features"][0]["resource"];
assert_eq!(resource["public"], true); assert_eq!(resource["public"], true);
assert!(resource.get("requires_group").is_none()); assert!(resource.get("requires_group").is_none());
assert_eq!(resource["jq"], ".[] | .responses |= {hva: .hva, hvor: .hvor}"); assert_eq!(resource["jq"], ".[] | .responses |= {what: .what, where: .where}");
assert!(view.get("qualifies").is_none()); assert!(view.get("qualifies").is_none());
// The followup page points people at the list. // The followup page points people at the list.
let followup: Value = serde_yaml::from_str(&files["questions/received/trafikk.yaml"]).unwrap(); let followup: Value = serde_yaml::from_str(&files["questions/received/traffic.yaml"]).unwrap();
assert_eq!(followup["alternatives"][1]["action"], "/status/meldinger"); assert_eq!(followup["alternatives"][1]["action"], "/status/meldinger");
// An internal need cannot be public. // An internal need cannot be public.
let internal: Plan = serde_yaml::from_str(&yaml.replace(" lands_in:", " enters_as: styret\n lands_in:")).unwrap(); let internal: Plan = serde_yaml::from_str(&yaml.replace(" lands_in:", " enters_as: board\n lands_in:")).unwrap();
assert!(internal.validate().unwrap_err().to_string().contains("public view")); assert!(internal.validate().unwrap_err().to_string().contains("public view"));
} }
@@ -2004,26 +2035,26 @@ fn collect_words(a: &serde_yaml::Value, b: &serde_yaml::Value, found: &mut std::
mod invite_tests { mod invite_tests {
use super::*; use super::*;
const PLAN: &str = "business: Vel\ncontact: { name: S, email: s@v.example }\ngroups:\n - { id: trafikk, label: Trafikk }\ninvites_by: [styret]\nneeds:\n - id: melde\n who: a neighbour\n wants: a hazard seen to\n lands_in: farer\n handled_by: trafikk\n stages: [open, ordnet]\n done_when: [ordnet]\n max_fields: 3\n collects:\n - { name: hvor }\n - id: utlegg\n who: a committee member\n wants: money back\n enters_as: trafikk\n lands_in: utlegg\n handled_by: styret\n stages: [open, betalt, avvist]\n done_when: [betalt, avvist]\n max_fields: 3\n collects:\n - { name: belop }\n"; const PLAN: &str = "business: Vel\ncontact: { name: S, email: s@v.example }\ngroups:\n - { id: traffic, label: Trafikk }\ninvites_by: [board]\nneeds:\n - id: report\n who: a neighbour\n wants: a hazard seen to\n lands_in: hazards\n handled_by: traffic\n stages: [open, fixed]\n done_when: [fixed]\n max_fields: 3\n collects:\n - { name: where }\n - id: expenses\n who: a committee member\n wants: money back\n enters_as: traffic\n lands_in: expenses\n handled_by: board\n stages: [open, betalt, avvist]\n done_when: [betalt, avvist]\n max_fields: 3\n collects:\n - { name: amount }\n";
#[test] #[test]
fn words_follow_a_card_that_moves_to_another_page() { fn words_follow_a_card_that_moves_to_another_page() {
// Built once behind a door and hand-worded; rebuilt flat, the // Built once behind a door and hand-worded; rebuilt flat, the
// form's words come along even though its page is gone. // form's words come along even though its page is gone.
let doored = "business: Vel\ncontact: { name: S, email: s@v.example }\ndoors:\n - { id: melde-fra, who: someone who saw something }\nneeds:\n - id: melde\n who: a neighbour\n wants: a hazard seen to\n door: melde-fra\n lands_in: farer\n handled_by: trafikk\n stages: [open, ordnet]\n done_when: [ordnet]\n max_fields: 3\n collects:\n - { name: hvor }\n"; let doored = "business: Vel\ncontact: { name: S, email: s@v.example }\ndoors:\n - { id: report-something, who: someone who saw something }\nneeds:\n - id: report\n who: a neighbour\n wants: a hazard seen to\n door: report-something\n lands_in: hazards\n handled_by: traffic\n stages: [open, fixed]\n done_when: [fixed]\n max_fields: 3\n collects:\n - { name: where }\n";
let plan: Plan = serde_yaml::from_str(doored).unwrap(); let plan: Plan = serde_yaml::from_str(doored).unwrap();
let mut words = fallback_words(&plan); let mut words = fallback_words(&plan);
let door_page = words.get_mut("questions/melde-fra/index.yaml").unwrap(); let door_page = words.get_mut("questions/report-something/index.yaml").unwrap();
door_page["needs"]["melde"]["heading"] = json!("Meld fra om en trafikkfare"); door_page["needs"]["report"]["heading"] = json!("Report a traffic hazard");
door_page["needs"]["melde"]["hints"]["hvor"] = json!("Svingen ved skolen"); door_page["needs"]["report"]["hints"]["where"] = json!("The bend by the school");
let repo = std::env::temp_dir().join(format!("trainer-lift-{}", std::process::id())); let repo = std::env::temp_dir().join(format!("trainer-lift-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&repo); let _ = std::fs::remove_dir_all(&repo);
write_repo(&repo, render(&plan, &words).unwrap()).unwrap(); write_repo(&repo, render(&plan, &words).unwrap()).unwrap();
let flat: Plan = serde_yaml::from_str(&doored.replace("doors:\n - { id: melde-fra, who: someone who saw something }\n", "").replace(" door: melde-fra\n", "")).unwrap(); let flat: Plan = serde_yaml::from_str(&doored.replace("doors:\n - { id: report-something, who: someone who saw something }\n", "").replace(" door: report-something\n", "")).unwrap();
let lifted = lift_words(&flat, &repo).unwrap(); let lifted = lift_words(&flat, &repo).unwrap();
assert_eq!(lifted["questions/index.yaml"]["needs"]["melde"]["heading"], "Meld fra om en trafikkfare"); assert_eq!(lifted["questions/index.yaml"]["needs"]["report"]["heading"], "Report a traffic hazard");
assert_eq!(lifted["questions/index.yaml"]["needs"]["melde"]["hints"]["hvor"], "Svingen ved skolen"); assert_eq!(lifted["questions/index.yaml"]["needs"]["report"]["hints"]["where"], "The bend by the school");
let _ = std::fs::remove_dir_all(&repo); let _ = std::fs::remove_dir_all(&repo);
} }
@@ -2032,15 +2063,15 @@ mod invite_tests {
let plan: Plan = serde_yaml::from_str(PLAN).unwrap(); let plan: Plan = serde_yaml::from_str(PLAN).unwrap();
plan.validate().unwrap(); plan.validate().unwrap();
let files = render(&plan, &fallback_words(&plan)).unwrap(); let files = render(&plan, &fallback_words(&plan)).unwrap();
// The board invites into both groups; trafikk only into itself. // The board invites into both groups; traffic only into itself.
let styret = &files["questions/styret/index.yaml"]; let board = &files["questions/board/index.yaml"];
assert_eq!(styret.matches("invite:").count(), 2, "{styret}"); assert_eq!(board.matches("invite:").count(), 2, "{board}");
assert!(styret.contains("bucket: portal_invites")); assert!(board.contains("bucket: portal_invites"));
let trafikk = &files["questions/trafikk/index.yaml"]; let traffic = &files["questions/traffic/index.yaml"];
assert_eq!(trafikk.matches("invite:").count(), 1); assert_eq!(traffic.matches("invite:").count(), 1);
assert!(trafikk.contains("group: trafikk") && !trafikk.contains("group: styret")); assert!(traffic.contains("group: traffic") && !traffic.contains("group: board"));
// The plan is refused when an inviter has no desk. // The plan is refused when an inviter has no desk.
let bad: Plan = serde_yaml::from_str(&PLAN.replace("invites_by: [styret]", "invites_by: [nobody]")).unwrap(); let bad: Plan = serde_yaml::from_str(&PLAN.replace("invites_by: [board]", "invites_by: [nobody]")).unwrap();
assert!(bad.validate().unwrap_err().to_string().contains("no desk")); assert!(bad.validate().unwrap_err().to_string().contains("no desk"));
} }
@@ -2049,23 +2080,23 @@ mod invite_tests {
let plan: Plan = serde_yaml::from_str(PLAN).unwrap(); let plan: Plan = serde_yaml::from_str(PLAN).unwrap();
let mut words = fallback_words(&plan); let mut words = fallback_words(&plan);
// A hand-edited desk: Norwegian words in the live repo. // A hand-edited desk: Norwegian words in the live repo.
let desk = words.get_mut("questions/trafikk/index.yaml").unwrap(); let desk = words.get_mut("questions/traffic/index.yaml").unwrap();
desk["question"] = json!("Hva må gjøres?"); desk["question"] = json!("What is waiting?");
desk["buckets"]["farer"]["heading"] = json!("Meldte farer"); desk["buckets"]["hazards"]["heading"] = json!("Reported hazards");
desk["buckets"]["farer"]["moves"]["open->ordnet"] = json!("Ordnet"); desk["buckets"]["hazards"]["moves"]["open->fixed"] = json!("Fixed");
let dir = std::env::temp_dir().join(format!("lift-{}", std::process::id())); let dir = std::env::temp_dir().join(format!("lift-{}", std::process::id()));
write_repo(&dir, render(&plan, &words).unwrap()).unwrap(); write_repo(&dir, render(&plan, &words).unwrap()).unwrap();
let lifted = lift_words(&plan, &dir).unwrap(); let lifted = lift_words(&plan, &dir).unwrap();
let desk = &lifted["questions/trafikk/index.yaml"]; let desk = &lifted["questions/traffic/index.yaml"];
assert_eq!(desk["question"], "Hva må gjøres?"); assert_eq!(desk["question"], "What is waiting?");
assert_eq!(desk["buckets"]["farer"]["heading"], "Meldte farer"); assert_eq!(desk["buckets"]["hazards"]["heading"], "Reported hazards");
assert_eq!(desk["buckets"]["farer"]["moves"]["open->ordnet"], "Ordnet"); assert_eq!(desk["buckets"]["hazards"]["moves"]["open->fixed"], "Fixed");
// A slot the repo never had keeps the fallback. // A slot the repo never had keeps the fallback.
let plan2: Plan = serde_yaml::from_str(&PLAN.replace("invites_by: [styret]", "invites_by: [styret, trafikk]")).unwrap(); let plan2: Plan = serde_yaml::from_str(&PLAN.replace("invites_by: [board]", "invites_by: [board, traffic]")).unwrap();
let lifted2 = lift_words(&plan2, &dir).unwrap(); let lifted2 = lift_words(&plan2, &dir).unwrap();
assert_eq!(lifted2["questions/trafikk/index.yaml"]["question"], "Hva må gjøres?"); assert_eq!(lifted2["questions/traffic/index.yaml"]["question"], "What is waiting?");
assert!(lifted2["questions/trafikk/index.yaml"]["invites"]["styret"]["heading"].as_str().unwrap().starts_with("Invite someone")); assert!(lifted2["questions/traffic/index.yaml"]["invites"]["board"]["heading"].as_str().unwrap().starts_with("Invite someone"));
let _ = std::fs::remove_dir_all(&dir); let _ = std::fs::remove_dir_all(&dir);
} }
@@ -2073,7 +2104,7 @@ mod invite_tests {
fn words_follow_their_card_when_the_live_page_is_ordered_differently() { fn words_follow_their_card_when_the_live_page_is_ordered_differently() {
let plan: Plan = serde_yaml::from_str(PLAN).unwrap(); let plan: Plan = serde_yaml::from_str(PLAN).unwrap();
let mut words = fallback_words(&plan); let mut words = fallback_words(&plan);
words.get_mut("questions/index.yaml").unwrap()["needs"]["melde"]["heading"] = json!("Meld fra om en fare"); words.get_mut("questions/index.yaml").unwrap()["needs"]["report"]["heading"] = json!("Report a hazard");
let dir = std::env::temp_dir().join(format!("lift-order-{}", std::process::id())); let dir = std::env::temp_dir().join(format!("lift-order-{}", std::process::id()));
write_repo(&dir, render(&plan, &words).unwrap()).unwrap(); write_repo(&dir, render(&plan, &words).unwrap()).unwrap();
// The live front door has an extra first card the plan knows // The live front door has an extra first card the plan knows
@@ -2086,7 +2117,7 @@ mod invite_tests {
std::fs::write(&front, serde_yaml::to_string(&page).unwrap()).unwrap(); std::fs::write(&front, serde_yaml::to_string(&page).unwrap()).unwrap();
let lifted = lift_words(&plan, &dir).unwrap(); let lifted = lift_words(&plan, &dir).unwrap();
assert_eq!(lifted["questions/index.yaml"]["needs"]["melde"]["heading"], "Meld fra om en fare"); assert_eq!(lifted["questions/index.yaml"]["needs"]["report"]["heading"], "Report a hazard");
let _ = std::fs::remove_dir_all(&dir); let _ = std::fs::remove_dir_all(&dir);
} }
} }
+1 -1
View File
@@ -37,7 +37,7 @@ doors: # a first question before the forms, when more tha
who: who comes through it # per kind of visitor, two or three needs behind each who: who comes through it # per kind of visitor, two or three needs behind each
groups: # groups with a label the public may see: the committees groups: # groups with a label the public may see: the committees
- { id: trafikk, label: Trafikkforhold } # a person can ask to join (optional) - { id: traffic, label: Traffic } # a person can ask to join (optional)
needs: needs:
- id: kebab-case # ASCII only - id: kebab-case # ASCII only