const IRREGULAR_PLURALS: &[(&str, &str)] = &[
("Mann", "Männer"),
("Kind", "Kinder"),
("Haus", "Häuser"),
("Wort", "Wörter"),
("Buch", "Bücher"),
("Mensch", "Menschen"),
("Frau", "Frauen"),
("Apfel", "Äpfel"),
("Vater", "Väter"),
("Mutter", "Mütter"),
("Bruder", "Brüder"),
("Land", "Länder"),
("Stadt", "Städte"),
("Nacht", "Nächte"),
("Hand", "Hände"),
("Auto", "Autos"),
("Tag", "Tage"),
("Jahr", "Jahre"),
("Zeit", "Zeiten"),
("Klasse", "Klassen"),
];
pub fn pluralize_de(word: &str) -> String {
if let Some(pl) = irregular_plural(word) {
return pl.to_string();
}
let lower = word.to_lowercase();
if has_fem_derivational_suffix(&lower) {
return add_en(word);
}
if lower.ends_with("chen") || lower.ends_with("lein") {
return word.to_string();
}
if lower.ends_with('e') {
return format!("{word}n");
}
if lower.ends_with("er") || lower.ends_with("el") || lower.ends_with("en") {
return word.to_string();
}
if lower.ends_with('o') || lower.ends_with('y') {
return format!("{word}s");
}
format!("{word}e")
}
pub fn singularize_de(word: &str) -> String {
if let Some(sg) = irregular_singular(word) {
return sg.to_string();
}
if word.ends_with("en") && word.len() > 3 {
return word[..word.len() - 2].to_string();
}
if word.ends_with('n') && word.len() > 2 {
return word[..word.len() - 1].to_string();
}
if word.ends_with('e') && word.len() > 2 {
return word[..word.len() - 1].to_string();
}
if word.ends_with('s') && word.len() > 2 {
return word[..word.len() - 1].to_string();
}
word.to_string()
}
fn has_fem_derivational_suffix(lower: &str) -> bool {
lower.ends_with("ung")
|| lower.ends_with("heit")
|| lower.ends_with("keit")
|| lower.ends_with("schaft")
|| lower.ends_with("ion")
|| lower.ends_with("ei")
}
fn add_en(word: &str) -> String {
format!("{word}en")
}
fn irregular_plural(word: &str) -> Option<&'static str> {
for &(sg, pl) in IRREGULAR_PLURALS {
if sg == word {
return Some(pl);
}
}
let lower = word.to_lowercase();
for &(sg, pl) in IRREGULAR_PLURALS {
if sg.to_lowercase() == lower {
return Some(pl);
}
}
None
}
fn irregular_singular(word: &str) -> Option<&'static str> {
for &(sg, pl) in IRREGULAR_PLURALS {
if pl == word {
return Some(sg);
}
}
let lower = word.to_lowercase();
for &(sg, pl) in IRREGULAR_PLURALS {
if pl.to_lowercase() == lower {
return Some(sg);
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pluralize_fem_derivational_ung_adds_en() {
assert_eq!(pluralize_de("Meinung"), "Meinungen");
assert_eq!(pluralize_de("Zeitung"), "Zeitungen");
}
#[test]
fn pluralize_fem_derivational_ei_adds_en() {
assert_eq!(pluralize_de("Datei"), "Dateien");
assert_eq!(pluralize_de("Polizei"), "Polizeien");
assert_eq!(pluralize_de("Bäckerei"), "Bäckereien");
}
#[test]
fn pluralize_diminutive_chen_unchanged() {
assert_eq!(pluralize_de("Hündchen"), "Hündchen");
}
#[test]
fn pluralize_bare_e_adds_n() {
assert_eq!(pluralize_de("Katze"), "Katzen");
assert_eq!(pluralize_de("Flasche"), "Flaschen");
}
#[test]
fn pluralize_er_el_en_unchanged() {
assert_eq!(pluralize_de("Lehrer"), "Lehrer");
assert_eq!(pluralize_de("Schlüssel"), "Schlüssel");
}
#[test]
fn pluralize_loanword_o_adds_s() {
assert_eq!(pluralize_de("Radio"), "Radios");
assert_eq!(pluralize_de("Sofa"), "Sofae"); }
#[test]
fn pluralize_irregular_mann() {
assert_eq!(pluralize_de("Mann"), "Männer");
}
#[test]
fn pluralize_irregular_kind() {
assert_eq!(pluralize_de("Kind"), "Kinder");
}
#[test]
fn pluralize_irregular_haus() {
assert_eq!(pluralize_de("Haus"), "Häuser");
}
#[test]
fn pluralize_irregular_auto() {
assert_eq!(pluralize_de("Auto"), "Autos");
}
#[test]
fn pluralize_irregular_klasse() {
assert_eq!(pluralize_de("Klasse"), "Klassen");
}
#[test]
fn pluralize_preserves_capitalization_irregular() {
let pl = pluralize_de("Mann");
assert_eq!(&pl[..1], "M", "First char should be uppercase");
assert_eq!(pl, "Männer");
}
#[test]
fn pluralize_preserves_capitalization_regular() {
let pl = pluralize_de("Meinung");
assert_eq!(&pl[..1], "M");
assert_eq!(pl, "Meinungen");
}
#[test]
fn pluralize_lowercase_input_stays_lowercase() {
let pl = pluralize_de("meinung");
assert!(pl.starts_with('m'));
}
#[test]
fn singularize_irregular_männer_to_mann() {
assert_eq!(singularize_de("Männer"), "Mann");
}
#[test]
fn singularize_en_suffix_stripped() {
assert_eq!(singularize_de("Zeitungen"), "Zeitung");
}
#[test]
fn singularize_e_suffix_stripped() {
assert_eq!(singularize_de("Tische"), "Tisch");
}
#[test]
fn singularize_s_suffix_stripped() {
assert_eq!(singularize_de("Radios"), "Radio");
}
#[test]
fn singularize_irregular_kinder_to_kind() {
assert_eq!(singularize_de("Kinder"), "Kind");
}
}