ukraine 1.1.0

Glory to Ukraine. Library for transliterating Ukrainian Cyrillic text into Latin script representation
Documentation
use super::common::{HUNDREDS, TENS};

/// Grammatical gender for ordinal number words in the nominative case.
#[derive(Copy, Clone)]
pub enum OrdinalGender {
    Masculine,
    Feminine,
    Neuter,
}

struct OrdinalForms {
    masculine: &'static str,
    feminine: &'static str,
    neuter: &'static str,
}

impl OrdinalForms {
    fn get(&self, gender: OrdinalGender) -> &'static str {
        match gender {
            OrdinalGender::Masculine => self.masculine,
            OrdinalGender::Feminine => self.feminine,
            OrdinalGender::Neuter => self.neuter,
        }
    }
}

const ORDINAL_UNDER_TWENTY: [OrdinalForms; 20] = [
    OrdinalForms {
        masculine: "нульовий",
        feminine: "нульова",
        neuter: "нульове",
    },
    OrdinalForms {
        masculine: "перший",
        feminine: "перша",
        neuter: "перше",
    },
    OrdinalForms {
        masculine: "другий",
        feminine: "друга",
        neuter: "друге",
    },
    OrdinalForms {
        masculine: "третій",
        feminine: "третя",
        neuter: "третє",
    },
    OrdinalForms {
        masculine: "четвертий",
        feminine: "четверта",
        neuter: "четверте",
    },
    OrdinalForms {
        masculine: "п'ятий",
        feminine: "п'ята",
        neuter: "п'яте",
    },
    OrdinalForms {
        masculine: "шостий",
        feminine: "шоста",
        neuter: "шосте",
    },
    OrdinalForms {
        masculine: "сьомий",
        feminine: "сьома",
        neuter: "сьоме",
    },
    OrdinalForms {
        masculine: "восьмий",
        feminine: "восьма",
        neuter: "восьме",
    },
    OrdinalForms {
        masculine: "дев'ятий",
        feminine: "дев'ята",
        neuter: "дев'яте",
    },
    OrdinalForms {
        masculine: "десятий",
        feminine: "десята",
        neuter: "десяте",
    },
    OrdinalForms {
        masculine: "одинадцятий",
        feminine: "одинадцята",
        neuter: "одинадцяте",
    },
    OrdinalForms {
        masculine: "дванадцятий",
        feminine: "дванадцята",
        neuter: "дванадцяте",
    },
    OrdinalForms {
        masculine: "тринадцятий",
        feminine: "тринадцята",
        neuter: "тринадцяте",
    },
    OrdinalForms {
        masculine: "чотирнадцятий",
        feminine: "чотирнадцята",
        neuter: "чотирнадцяте",
    },
    OrdinalForms {
        masculine: "п'ятнадцятий",
        feminine: "п'ятнадцята",
        neuter: "п'ятнадцяте",
    },
    OrdinalForms {
        masculine: "шістнадцятий",
        feminine: "шістнадцята",
        neuter: "шістнадцяте",
    },
    OrdinalForms {
        masculine: "сімнадцятий",
        feminine: "сімнадцята",
        neuter: "сімнадцяте",
    },
    OrdinalForms {
        masculine: "вісімнадцятий",
        feminine: "вісімнадцята",
        neuter: "вісімнадцяте",
    },
    OrdinalForms {
        masculine: "дев'ятнадцятий",
        feminine: "дев'ятнадцята",
        neuter: "дев'ятнадцяте",
    },
];

const ORDINAL_TENS: [Option<OrdinalForms>; 10] = [
    None,
    None,
    Some(OrdinalForms {
        masculine: "двадцятий",
        feminine: "двадцята",
        neuter: "двадцяте",
    }),
    Some(OrdinalForms {
        masculine: "тридцятий",
        feminine: "тридцята",
        neuter: "тридцяте",
    }),
    Some(OrdinalForms {
        masculine: "сороковий",
        feminine: "сорокова",
        neuter: "сорокове",
    }),
    Some(OrdinalForms {
        masculine: "п'ятдесятий",
        feminine: "п'ятдесята",
        neuter: "п'ятдесяте",
    }),
    Some(OrdinalForms {
        masculine: "шістдесятий",
        feminine: "шістдесята",
        neuter: "шістдесяте",
    }),
    Some(OrdinalForms {
        masculine: "сімдесятий",
        feminine: "сімдесята",
        neuter: "сімдесяте",
    }),
    Some(OrdinalForms {
        masculine: "вісімдесятий",
        feminine: "вісімдесята",
        neuter: "вісімдесяте",
    }),
    Some(OrdinalForms {
        masculine: "дев'яностий",
        feminine: "дев'яноста",
        neuter: "дев'яносте",
    }),
];

const ORDINAL_HUNDREDS: [Option<OrdinalForms>; 10] = [
    None,
    Some(OrdinalForms {
        masculine: "сотий",
        feminine: "сота",
        neuter: "соте",
    }),
    Some(OrdinalForms {
        masculine: "двохсотий",
        feminine: "двохсота",
        neuter: "двохсоте",
    }),
    Some(OrdinalForms {
        masculine: "трьохсотий",
        feminine: "трьохсота",
        neuter: "трьохсоте",
    }),
    Some(OrdinalForms {
        masculine: "чотирьохсотий",
        feminine: "чотирьохсота",
        neuter: "чотирьохсоте",
    }),
    Some(OrdinalForms {
        masculine: "п'ятисотий",
        feminine: "п'ятисота",
        neuter: "п'ятисоте",
    }),
    Some(OrdinalForms {
        masculine: "шістсотий",
        feminine: "шістсота",
        neuter: "шістсоте",
    }),
    Some(OrdinalForms {
        masculine: "сімсотий",
        feminine: "сімсота",
        neuter: "сімсоте",
    }),
    Some(OrdinalForms {
        masculine: "вісімсотий",
        feminine: "вісімсота",
        neuter: "вісімсоте",
    }),
    Some(OrdinalForms {
        masculine: "дев'ятсотий",
        feminine: "дев'ятсота",
        neuter: "дев'ятсоте",
    }),
];

/// Convert a non-negative integer (0..=999) into its ordinal word form.
/// The result is in the nominative case for the specified grammatical gender.
pub fn to_ordinal_words(number: u64, gender: OrdinalGender) -> String {
    assert!(
        number <= 999,
        "Ordinal conversion currently supports numbers up to 999"
    );
    ordinal_under_thousand(number as u16, gender)
}

fn ordinal_under_thousand(number: u16, gender: OrdinalGender) -> String {
    if number < 20 {
        return ORDINAL_UNDER_TWENTY[number as usize]
            .get(gender)
            .to_string();
    }

    if number < 100 {
        let tens = number / 10;
        let units = number % 10;

        if units == 0 {
            if let Some(forms) = &ORDINAL_TENS[tens as usize] {
                return forms.get(gender).to_string();
            }
        }

        let mut parts: Vec<&str> = Vec::new();
        if tens > 0 {
            let tens_word = TENS[tens as usize];
            if !tens_word.is_empty() {
                parts.push(tens_word);
            }
        }

        if units > 0 {
            parts.push(ORDINAL_UNDER_TWENTY[units as usize].get(gender));
        }

        return parts.join(" ");
    }

    let hundreds = number / 100;
    let remainder = number % 100;

    if remainder == 0 {
        if let Some(forms) = &ORDINAL_HUNDREDS[hundreds as usize] {
            return forms.get(gender).to_string();
        }
    }

    let mut parts: Vec<&str> = Vec::new();

    if hundreds > 0 {
        let hundreds_word = HUNDREDS[hundreds as usize];
        if !hundreds_word.is_empty() {
            parts.push(hundreds_word);
        }
    }

    if remainder < 20 {
        parts.push(ORDINAL_UNDER_TWENTY[remainder as usize].get(gender));
    } else {
        let tens = remainder / 10;
        let units = remainder % 10;

        if units == 0 {
            if let Some(forms) = &ORDINAL_TENS[tens as usize] {
                parts.push(forms.get(gender));
            }
        } else {
            if tens > 0 {
                let tens_word = TENS[tens as usize];
                if !tens_word.is_empty() {
                    parts.push(tens_word);
                }
            }

            parts.push(ORDINAL_UNDER_TWENTY[units as usize].get(gender));
        }
    }

    parts.join(" ")
}