use std::{borrow::Cow, str::FromStr, vec}; use anyhow::Result; use fancy_regex::Regex; use crate::{model::Language, util}; pub const LANGUAGES: [Language; 83] = [ Language::Af, Language::Am, Language::Ar, Language::As, Language::Az, Language::Be, Language::Bg, Language::Bn, Language::Bs, Language::Ca, Language::Cs, Language::Da, Language::De, Language::El, Language::En, Language::EnGb, Language::EnIn, Language::Es, Language::Es419, Language::EsUs, Language::Et, Language::Eu, Language::Fa, Language::Fi, Language::Fil, Language::Fr, Language::FrCa, Language::Gl, Language::Gu, Language::Hi, Language::Hr, Language::Hu, Language::Hy, Language::Id, Language::Is, Language::It, Language::Iw, Language::Ja, Language::Ka, Language::Kk, Language::Km, Language::Kn, Language::Ko, Language::Ky, Language::Lo, Language::Lt, Language::Lv, Language::Mk, Language::Ml, Language::Mn, Language::Mr, Language::Ms, Language::My, Language::Ne, Language::Nl, Language::No, Language::Or, Language::Pa, Language::Pl, Language::Pt, Language::PtPt, Language::Ro, Language::Ru, Language::Si, Language::Sk, Language::Sl, Language::Sq, Language::Sr, Language::SrLatn, Language::Sv, Language::Sw, Language::Ta, Language::Te, Language::Th, Language::Tr, Language::Uk, Language::Ur, Language::Uz, Language::Vi, Language::ZhCn, Language::ZhHk, Language::ZhTw, Language::Zu, ]; #[derive(Debug)] pub struct TimeagoPattern<'a> { word_separator: &'a str, seconds: Vec<&'a str>, minutes: Vec<&'a str>, hours: Vec<&'a str>, days: Vec<&'a str>, weeks: Vec<&'a str>, months: Vec<&'a str>, years: Vec<&'a str>, special_cases: Vec<(&'a str, u64)>, } impl From for TimeagoPattern<'_> { fn from(language: Language) -> Self { match language { Language::Af => TimeagoPattern { word_separator: " ", seconds: vec!["sekonde", "sekondes"], minutes: vec!["minute", "minuut"], hours: vec!["ure", "uur"], days: vec!["dae", "dag"], weeks: vec!["week", "weke"], months: vec!["maand", "maande"], years: vec!["jaar"], special_cases: vec![], }, Language::Am => TimeagoPattern { word_separator: " ", seconds: vec!["ሰኮንዶች", "ሴኮንድ"], minutes: vec!["ደቂቃ", "ደቂቃዎች"], hours: vec!["ሰዓት", "ሰዓቶች"], // INFO: add days[0] days: vec!["ቀናት", "ቀን", "ቀኖች"], weeks: vec!["ሳምንታት", "ሳምንት"], months: vec!["ወራት", "ወር"], years: vec!["ዓመታት", "ዓመት"], special_cases: vec![], }, Language::Ar => TimeagoPattern { word_separator: " ", seconds: vec!["ثانية", "ثانيتين", "ثوانٍ"], minutes: vec!["دقائق", "دقيقة", "دقيقتين"], hours: vec!["ساعات", "ساعة", "ساعتين"], days: vec!["أيام", "يوم", "يومين", "يومًا"], weeks: vec!["أسابيع", "أسبوع", "أسبوعين"], months: vec!["أشهر", "شهر", "شهرين", "شهرًا"], years: vec!["سنة", "سنتين", "سنوات"], special_cases: vec![ ("ساعتين", 2 * 3600), ("يومين", 2 * 3600 * 24), ("أسبوعين", 2 * 3600 * 24 * 7), ("شهرين", 2 * 3600 * 24 * 30), ("سنتين", 2 * 3600 * 24 * 365), ], }, // INFO: newly added Language::As => TimeagoPattern { word_separator: " ", seconds: vec!["ছেকেণ্ড"], minutes: vec!["মিনিট"], hours: vec!["ঘণ্টা"], days: vec!["দিন"], weeks: vec!["সপ্তাহ"], months: vec!["মাহ"], years: vec!["বছৰৰ"], special_cases: vec![], }, Language::Az => TimeagoPattern { word_separator: " ", seconds: vec!["saniyə"], minutes: vec!["dəqiqə"], hours: vec!["saat"], days: vec!["gün"], weeks: vec!["həftə"], months: vec!["ay"], years: vec!["il"], special_cases: vec![], }, Language::Be => TimeagoPattern { word_separator: " ", seconds: vec!["секунд", "секунду", "секунды"], minutes: vec!["хвілін", "хвіліну", "хвіліны"], hours: vec!["гадзін", "гадзіну", "гадзіны"], days: vec!["дзень", "дзён", "дня", "дні"], weeks: vec!["тыдзень", "тыдня", "тыдні"], months: vec!["месяц", "месяца", "месяцы", "месяцаў"], years: vec!["год", "года", "гады", "гадоў"], special_cases: vec![], }, Language::Bg => TimeagoPattern { word_separator: " ", seconds: vec!["секунда", "секунди"], minutes: vec!["минута", "минути"], hours: vec!["час", "часа"], days: vec!["ден", "дни"], weeks: vec!["седмица", "седмици"], months: vec!["месец", "месеца"], years: vec!["година", "години"], special_cases: vec![], }, Language::Bn => TimeagoPattern { word_separator: " ", // INFO: hours fixed seconds: vec!["সেকেন্ড"], minutes: vec!["মিনিট"], hours: vec!["ঘন্টা"], days: vec!["দিন"], weeks: vec!["সপ্তাহ"], months: vec!["মাস"], years: vec!["বছর"], special_cases: vec![], }, Language::Bs => TimeagoPattern { word_separator: " ", seconds: vec!["sekundi", "sekunde", "sekundu"], minutes: vec!["minuta", "minute", "minutu"], hours: vec!["h", "sat", "sata", "sati"], days: vec!["dan", "dana"], // INFO: fix sedmice (week plural) weeks: vec!["sedm.", "sedmice", "sedmicu"], months: vec!["mj.", "mjesec", "mjeseca", "mjeseci"], years: vec!["godina", "godine", "godinu"], special_cases: vec![], }, Language::Ca => TimeagoPattern { word_separator: " ", seconds: vec!["segon", "segons"], minutes: vec!["minut", "minuts"], hours: vec!["hora", "hores"], days: vec!["dia", "dies"], weeks: vec!["setmana", "setmanes"], months: vec!["mes", "mesos"], years: vec!["any", "anys"], special_cases: vec![], }, Language::Cs => TimeagoPattern { word_separator: " ", seconds: vec!["sekundami", "sekundou"], minutes: vec!["minutami", "minutou"], hours: vec!["hodinami", "hodinou"], days: vec!["dny", "včera"], weeks: vec!["týdnem", "týdny"], months: vec!["měsícem", "měsíci"], years: vec!["rokem", "roky", "lety"], special_cases: vec![], }, Language::Da => TimeagoPattern { word_separator: " ", seconds: vec!["sekund", "sekunder"], minutes: vec!["minut", "minutter"], hours: vec!["time", "timer"], days: vec!["dag", "dage"], weeks: vec!["uge", "uger"], months: vec!["måned", "måneder"], years: vec!["år"], special_cases: vec![], }, Language::De => TimeagoPattern { word_separator: " ", seconds: vec!["Sekunde", "Sekunden"], minutes: vec!["Minute", "Minuten"], hours: vec!["Stunde", "Stunden"], days: vec!["Tag", "Tagen"], weeks: vec!["Woche", "Wochen"], months: vec!["Monat", "Monaten"], years: vec!["Jahr", "Jahren"], special_cases: vec![], }, Language::El => TimeagoPattern { word_separator: " ", seconds: vec!["δευτερόλεπτα", "δευτερόλεπτο"], minutes: vec!["λεπτά", "λεπτό"], hours: vec!["ώρα", "ώρες"], days: vec!["ημέρα", "ημέρες"], weeks: vec!["εβδομάδα", "εβδομάδες"], months: vec!["μήνα", "μήνες"], // INFO: fixed years years: vec!["έτος", "έτη"], special_cases: vec![], }, Language::En | Language::EnGb | Language::EnIn => TimeagoPattern { word_separator: " ", seconds: vec!["second", "seconds"], minutes: vec!["minute", "minutes"], hours: vec!["hour", "hours"], days: vec!["day", "days"], weeks: vec!["week", "weeks"], months: vec!["month", "months"], years: vec!["year", "years"], special_cases: vec![], }, Language::Es | Language::EsUs | Language::Es419 => TimeagoPattern { word_separator: " ", seconds: vec!["segundo", "segundos"], minutes: vec!["minuto", "minutos"], hours: vec!["hora", "horas"], days: vec!["día", "días"], weeks: vec!["semana", "semanas"], months: vec!["mes", "meses"], years: vec!["año", "años"], special_cases: vec![], }, Language::Et => TimeagoPattern { word_separator: " ", // INFO: corrected secs/min/weeks seconds: vec!["sekund", "sekundi", "sekundit"], minutes: vec!["minut", "minuti", "minutit"], hours: vec!["tunni"], days: vec!["päev", "päeva"], weeks: vec!["nädal", "nädala", "nädalat"], months: vec!["kuu", "kuud"], years: vec!["aasta", "aastat"], special_cases: vec![], }, Language::Eu => TimeagoPattern { word_separator: " ", seconds: vec!["segundo"], minutes: vec!["minutu"], hours: vec!["ordu", "ordubete"], days: vec!["egun"], weeks: vec!["aste", "astebete"], months: vec!["hilabete"], years: vec!["urte", "urtebete"], special_cases: vec![], }, Language::Fa => TimeagoPattern { word_separator: " ", seconds: vec!["ثانیه"], minutes: vec!["دقیقه"], hours: vec!["ساعت"], days: vec!["روز"], weeks: vec!["هفته"], months: vec!["ماه"], years: vec!["سال"], special_cases: vec![], }, Language::Fi => TimeagoPattern { word_separator: " ", seconds: vec!["sekunti", "sekuntia"], minutes: vec!["minuutti", "minuuttia"], hours: vec!["tunti", "tuntia"], days: vec!["päivä", "päivää"], weeks: vec!["viikko", "viikkoa"], months: vec!["kuukausi", "kuukautta"], years: vec!["vuosi", "vuotta"], special_cases: vec![], }, Language::Fil => TimeagoPattern { word_separator: " ", seconds: vec!["segundo"], minutes: vec!["minuto"], hours: vec!["oras"], days: vec!["araw"], weeks: vec!["linggo"], months: vec!["buwan"], years: vec!["taon"], special_cases: vec![], }, Language::Fr | Language::FrCa => TimeagoPattern { word_separator: " ", seconds: vec!["seconde", "secondes"], minutes: vec!["minute", "minutes"], hours: vec!["heure", "heures"], days: vec!["jour", "jours"], weeks: vec!["semaine", "semaines"], months: vec!["mois"], years: vec!["an", "ans"], special_cases: vec![], }, Language::Gl => TimeagoPattern { word_separator: " ", seconds: vec!["segundo", "segundos"], minutes: vec!["minuto", "minutos"], hours: vec!["hora", "horas"], days: vec!["día", "días"], weeks: vec!["semana", "semanas"], months: vec!["mes", "meses"], years: vec!["ano", "anos"], special_cases: vec![], }, Language::Gu => TimeagoPattern { word_separator: " ", seconds: vec!["સેકંડ"], minutes: vec!["મિનિટ"], hours: vec!["કલાક"], days: vec!["દિવસ"], weeks: vec!["અઠવાડિયા"], months: vec!["મહિના"], years: vec!["વર્ષ"], special_cases: vec![], }, Language::Hi => TimeagoPattern { word_separator: " ", seconds: vec!["सेकंड"], minutes: vec!["मिनट"], hours: vec!["घंटा", "घंटे"], days: vec!["दिन"], weeks: vec!["सप्ताह", "हफ़्ते"], // INFO: fix months months: vec!["माह", "महीना", "महीने"], years: vec!["वर्ष"], special_cases: vec![], }, Language::Hr => TimeagoPattern { word_separator: " ", seconds: vec!["sekunde", "sekundi", "sekundu"], minutes: vec!["minuta", "minute", "minutu"], hours: vec!["sat", "sata", "sati"], days: vec!["dan", "dana"], weeks: vec!["tjedan", "tjedna"], months: vec!["mjesec", "mjeseca", "mjeseci"], years: vec!["godina", "godine", "godinu"], special_cases: vec![], }, Language::Hu => TimeagoPattern { word_separator: " ", // INFO: updated seconds: vec!["másodperce", "másodperccel"], minutes: vec!["perce", "perccel"], hours: vec!["órája", "órával"], days: vec!["napja", "nappal"], weeks: vec!["hete", "héttel"], months: vec!["hónapja", "hónappal"], years: vec!["éve", "évvel"], special_cases: vec![], }, Language::Hy => TimeagoPattern { word_separator: " ", seconds: vec!["վայրկյան"], minutes: vec!["րոպե"], hours: vec!["ժամ"], days: vec!["օր"], weeks: vec!["շաբաթ"], months: vec!["ամիս"], years: vec!["տարի"], special_cases: vec![], }, Language::Id => TimeagoPattern { word_separator: " ", seconds: vec!["detik"], minutes: vec!["menit"], hours: vec!["jam"], days: vec!["hari"], weeks: vec!["minggu"], months: vec!["bulan"], years: vec!["tahun"], special_cases: vec![], }, Language::Is => TimeagoPattern { word_separator: " ", seconds: vec!["sekúndu", "sekúndum"], minutes: vec!["mínútu", "mínútum"], hours: vec!["klukkustund", "klukkustundum"], days: vec!["degi", "dögum"], weeks: vec!["viku", "vikum"], months: vec!["mánuði", "mánuðum"], years: vec!["ári", "árum"], special_cases: vec![], }, Language::It => TimeagoPattern { word_separator: " ", seconds: vec!["secondi", "secondo"], minutes: vec!["minuti", "minuto"], hours: vec!["ora", "ore"], days: vec!["giorni", "giorno"], weeks: vec!["settimana", "settimane"], months: vec!["mese", "mesi"], years: vec!["anni", "anno"], special_cases: vec![], }, Language::Iw => TimeagoPattern { word_separator: " ", seconds: vec!["שניות", "שנייה"], minutes: vec!["דקה", "דקות"], hours: vec!["שעה", "שעות"], days: vec!["יום", "ימים"], weeks: vec!["שבוע", "שבועות"], months: vec!["חודש", "חודשים"], years: vec!["שנה", "שנים"], special_cases: vec![ ("שעתיים", 2 * 3600), ("יומיים", 2 * 3600 * 24), ("שבועיים", 2 * 3600 * 24 * 7), ("חודשיים", 2 * 3600 * 24 * 30), ("שנתיים", 2 * 3600 * 24 * 365), ], }, Language::Ja => TimeagoPattern { word_separator: "", seconds: vec!["秒"], minutes: vec!["分"], hours: vec!["時間"], days: vec!["日"], weeks: vec!["週間"], months: vec!["か月"], years: vec!["年"], special_cases: vec![], }, Language::Ka => TimeagoPattern { word_separator: " ", seconds: vec!["წამის"], minutes: vec!["წუთის"], hours: vec!["საათის"], days: vec!["დღის"], weeks: vec!["კვირის"], months: vec!["თვის"], years: vec!["წლის"], special_cases: vec![], }, Language::Kk => TimeagoPattern { word_separator: " ", seconds: vec!["секунд"], minutes: vec!["минут"], hours: vec!["сағат"], days: vec!["күн"], weeks: vec!["апта"], months: vec!["ай"], years: vec!["жыл"], special_cases: vec![], }, Language::Km => TimeagoPattern { word_separator: "", seconds: vec!["វិនាទីមុន"], minutes: vec!["នាទីមុន"], hours: vec!["ម៉ោងមុន"], days: vec!["ថ្ងៃមុន"], weeks: vec!["សប្ដាហ៍មុន"], months: vec!["ខែមុន"], years: vec!["ឆ្នាំមុន"], special_cases: vec![], }, Language::Kn => TimeagoPattern { word_separator: " ", // INFO: fix hours seconds: vec!["ಸೆಕೆಂಡುಗಳ", "ಸೆಕೆಂಡ್"], minutes: vec!["ನಿಮಿಷಗಳ", "ನಿಮಿಷದ"], hours: vec!["ಗಂಟೆ", "ಗಂಟೆಗಳ", "ಗಂಟೆಯ"], days: vec!["ದಿನಗಳ", "ದಿನದ"], weeks: vec!["ವಾರಗಳ", "ವಾರದ"], months: vec!["ತಿಂಗಳ", "ತಿಂಗಳುಗಳ"], years: vec!["ವರ್ಷಗಳ", "ವರ್ಷದ"], special_cases: vec![], }, Language::Ko => TimeagoPattern { word_separator: "", seconds: vec!["초"], minutes: vec!["분"], hours: vec!["시간"], days: vec!["일"], weeks: vec!["주"], months: vec!["개월"], years: vec!["년"], special_cases: vec![], }, Language::Ky => TimeagoPattern { word_separator: " ", seconds: vec!["секунд"], minutes: vec!["мүнөт"], hours: vec!["саат"], days: vec!["күн"], weeks: vec!["апта"], months: vec!["ай"], years: vec!["жыл"], special_cases: vec![], }, Language::Lo => TimeagoPattern { word_separator: "", seconds: vec!["ວິນາທີກ່ອນ"], minutes: vec!["ນາທີກ່ອນ"], hours: vec!["ຊົ່ວໂມງກ່ອນ"], days: vec!["ມື້ກ່ອນ"], weeks: vec!["ອາທິດກ່ອນ"], months: vec!["ເດືອນກ່ອນ"], years: vec!["ປີກ່ອນ"], special_cases: vec![], }, Language::Lt => TimeagoPattern { // INFO: fix weeks word_separator: " ", seconds: vec!["sekundes", "sekundę", "sekundžių"], minutes: vec!["minutes", "minutę", "minučių"], hours: vec!["valandas", "valandą", "valandų"], days: vec!["dienas", "dieną", "dienų"], weeks: vec!["savaites", "savaitę", "savaičių"], months: vec!["mėnesius", "mėnesių", "mėnesį"], years: vec!["metus", "metų"], special_cases: vec![], }, Language::Lv => TimeagoPattern { word_separator: " ", seconds: vec!["sekundes", "sekundēm"], minutes: vec!["minūtes", "minūtēm", "minūtes"], hours: vec!["stundas", "stundām"], days: vec!["dienas", "dienām"], weeks: vec!["nedēļas", "nedēļām"], months: vec!["mēneša", "mēnešiem"], years: vec!["gada", "gadiem"], special_cases: vec![], }, Language::Mk => TimeagoPattern { word_separator: " ", seconds: vec!["секунда", "секунди"], minutes: vec!["минута", "минути"], hours: vec!["час", "часа"], days: vec!["ден", "дена"], // INFO: fix weeks weeks: vec!["недела", "недели", "седмици"], months: vec!["месец", "месеци"], years: vec!["година", "години"], special_cases: vec![], }, Language::Ml => TimeagoPattern { word_separator: "", seconds: vec!["സെക്കന്റ്", "സെക്കൻഡ്"], minutes: vec!["മിനിറ്റ്"], hours: vec!["മണിക്കൂർ"], days: vec!["ദിവസം"], // weeks: vec!["ആഴ്ച", "ആഴ്\u{200c}ച"], weeks: vec!["ആഴ്ച"], months: vec!["മാസം"], years: vec!["വർഷം"], special_cases: vec![], }, Language::Mn => TimeagoPattern { word_separator: " ", seconds: vec!["секундын"], minutes: vec!["минутын"], hours: vec!["цагийн"], days: vec!["өдрийн"], weeks: vec!["долоо", "хоногийн"], months: vec!["сарын"], years: vec!["жилийн"], special_cases: vec![], }, Language::Mr => TimeagoPattern { word_separator: "", seconds: vec!["सेकंदांपूर्वी", "सेकंदापूर्वी"], minutes: vec!["मिनिटांपूर्वी", "मिनिटापूर्वी"], hours: vec!["तासांपूर्वी", "तासापूर्वी"], days: vec!["दिवसांपूर्वी", "दिवसापूर्वी"], weeks: vec!["आठवड्यांपूर्वी", "आठवड्यापूर्वी"], months: vec!["महिन्यांपूर्वी", "महिन्यापूर्वी"], years: vec!["वर्षांपूर्वी", "वर्षापूर्वी"], special_cases: vec![], }, Language::Ms => TimeagoPattern { word_separator: " ", seconds: vec!["saat"], minutes: vec!["minit"], hours: vec!["jam"], days: vec!["hari"], weeks: vec!["minggu"], months: vec!["bulan"], years: vec!["tahun"], special_cases: vec![], }, Language::My => TimeagoPattern { word_separator: " ", seconds: vec!["စက္ကန့်"], minutes: vec!["မိနစ်"], hours: vec!["နာရီ"], days: vec!["ရက်"], weeks: vec!["ပတ်"], months: vec!["လ"], years: vec!["နှစ်"], special_cases: vec![], }, Language::Ne => TimeagoPattern { word_separator: " ", // INFO: fix hours seconds: vec!["सेकेन्ड"], minutes: vec!["मिनेट"], hours: vec!["घण्टा"], days: vec!["दिन"], weeks: vec!["हप्ता"], months: vec!["महिना"], years: vec!["वर्ष"], special_cases: vec![], }, Language::Nl => TimeagoPattern { word_separator: " ", seconds: vec!["seconde", "seconden"], minutes: vec!["minuten", "minuut"], hours: vec!["uur"], days: vec!["dag", "dagen"], weeks: vec!["week", "weken"], months: vec!["maand", "maanden"], years: vec!["jaar"], special_cases: vec![], }, Language::No => TimeagoPattern { word_separator: " ", seconds: vec!["sekund", "sekunder"], minutes: vec!["minutt", "minutter"], hours: vec!["time", "timer"], days: vec!["dag", "dager", "døgn"], weeks: vec!["uke", "uker"], // INFO: fixed months, days months: vec!["måned", "måneder"], years: vec!["år"], special_cases: vec![], }, // INFO: newly added Language::Or => TimeagoPattern { word_separator: " ", seconds: vec!["ସେକେଣ୍ଡ"], minutes: vec!["ମିନିଟ୍"], hours: vec!["ଘଣ୍ଟା"], days: vec!["ଦିନ"], weeks: vec!["ସପ୍ତାହ"], months: vec!["ମାସ"], years: vec!["ବର୍ଷ"], special_cases: vec![], }, Language::Pa => TimeagoPattern { word_separator: " ", seconds: vec!["ਸਕਿੰਟ"], minutes: vec!["ਮਿੰਟ"], hours: vec!["ਘੰਟਾ", "ਘੰਟੇ"], days: vec!["ਦਿਨ"], weeks: vec!["ਹਫ਼ਤਾ", "ਹਫ਼ਤੇ"], months: vec!["ਮਹੀਨਾ", "ਮਹੀਨੇ"], years: vec!["ਸਾਲ"], special_cases: vec![], }, Language::Pl => TimeagoPattern { word_separator: " ", seconds: vec!["sekund", "sekundy", "sekundę"], minutes: vec!["minut", "minuty", "minutę"], hours: vec!["godzin", "godziny", "godzinę"], days: vec!["dni", "dzień"], weeks: vec!["tydzień", "tygodnie"], months: vec!["miesiąc", "miesiące", "miesięcy"], years: vec!["lat", "lata", "rok"], special_cases: vec![], }, Language::Pt | Language::PtPt => TimeagoPattern { word_separator: " ", seconds: vec!["segundo", "segundos"], minutes: vec!["minuto", "minutos"], hours: vec!["hora", "horas"], days: vec!["dia", "dias"], weeks: vec!["semana", "semanas"], months: vec!["meses", "mês"], years: vec!["ano", "anos"], special_cases: vec![], }, Language::Ro => TimeagoPattern { word_separator: " ", seconds: vec!["secunde", "secundă"], minutes: vec!["minut", "minute"], hours: vec!["ore", "oră"], days: vec!["zi", "zile"], weeks: vec!["săptămâni", "săptămână"], months: vec!["luni", "lună"], years: vec!["an", "ani"], special_cases: vec![], }, Language::Ru => TimeagoPattern { word_separator: " ", seconds: vec!["секунд", "секунду", "секунды", "только что"], minutes: vec!["минут", "минуту", "минуты"], hours: vec!["час", "часа", "часов"], days: vec!["день", "дней", "дня"], weeks: vec!["Неделю", "недели"], months: vec!["месяц", "месяца", "месяцев"], years: vec!["Год", "года", "лет"], special_cases: vec![], }, Language::Si => TimeagoPattern { word_separator: " ", seconds: vec!["තත්පර"], minutes: vec!["මිනිත්තු"], hours: vec!["පැය"], days: vec!["දින"], weeks: vec!["සති"], months: vec!["මාස"], years: vec!["වසර"], special_cases: vec![], }, Language::Sk => TimeagoPattern { word_separator: " ", seconds: vec!["sekundami", "sekundou"], minutes: vec!["minútami", "minútou"], hours: vec!["hodinami", "hodinou"], days: vec!["dňami", "dňom"], weeks: vec!["týždňami", "týždňom"], months: vec!["mesiacmi", "mesiacom"], years: vec!["rokmi", "rokom"], special_cases: vec![], }, Language::Sl => TimeagoPattern { word_separator: " ", seconds: vec!["sekundama", "sekundami", "sekundo"], minutes: vec!["minutama", "minutami", "minuto"], hours: vec!["urama", "urami", "uro"], days: vec!["dnem", "dnevi", "dnevoma"], weeks: vec!["tedni", "tednom", "tednoma"], months: vec!["mesecem", "mesecema", "meseci"], years: vec!["leti", "letom", "letoma"], special_cases: vec![], }, Language::Sq => TimeagoPattern { word_separator: " ", seconds: vec!["sekonda", "sekondë"], minutes: vec!["minuta", "minutë"], hours: vec!["orë"], days: vec!["ditë"], weeks: vec!["javë"], months: vec!["muaj"], years: vec!["vit", "vjet"], special_cases: vec![], }, Language::Sr => TimeagoPattern { word_separator: " ", seconds: vec!["секунде", "секунди"], minutes: vec!["минута"], hours: vec!["сат", "сата", "сати"], // INFO: simplified days days: vec!["дан", "дана"], weeks: vec!["недеље", "недељу"], months: vec!["месец", "месеца", "месеци"], years: vec!["година", "године", "годину"], special_cases: vec![], }, Language::SrLatn => TimeagoPattern { word_separator: " ", seconds: vec!["sekunde", "sekundi"], minutes: vec!["minuta"], hours: vec!["sat", "sati", "sata"], // INFO: simplified days days: vec!["dan", "dana"], weeks: vec!["nedelja", "nedelje", "nedelju"], months: vec!["mesec", "meseci", "meseca"], years: vec!["godine", "godina", "godinu"], special_cases: vec![], }, Language::Sv => TimeagoPattern { word_separator: " ", seconds: vec!["sekund", "sekunder"], minutes: vec!["minut", "minuter"], hours: vec!["timmar", "timme"], days: vec!["dag", "dagar"], weeks: vec!["vecka", "veckor"], months: vec!["månad", "månader"], years: vec!["år"], special_cases: vec![], }, Language::Sw => TimeagoPattern { word_separator: " ", seconds: vec!["sekunde"], minutes: vec!["dakika"], hours: vec!["saa"], days: vec!["siku"], weeks: vec!["wiki"], months: vec!["Mwezi", "miezi"], years: vec!["Miaka", "Mwaka"], special_cases: vec![], }, Language::Ta => TimeagoPattern { word_separator: " ", // INFO: fixed minutes hours months, TODO: 1 second // 2 விநாடிகளுக்கு முன் seconds: vec!["வினாடி", "வினாடிகளுக்கு"], // 1 நிமிடத்திற்கு முன் 2 நிமிடங்களுக்கு முன் minutes: vec!["நிமிடங்களுக்கு", "நிமிடத்திற்கு", "நிமிடங்கள்", "நிமிடம்"], hours: vec!["மணிநேரம்"], days: vec!["நாட்களுக்கு", "நாளுக்கு"], weeks: vec!["வாரங்களுக்கு", "வாரம்"], months: vec!["மாதத்துக்கு", "மாதங்களுக்கு"], years: vec!["ஆண்டிற்கு", "ஆண்டுகளுக்கு"], special_cases: vec![], }, Language::Te => TimeagoPattern { word_separator: " ", seconds: vec!["సెకను", "సెకన్ల"], minutes: vec!["నిమిషం", "నిమిషాల"], hours: vec!["గంట", "గంటల"], days: vec!["రోజు", "రోజుల"], weeks: vec!["వారం", "వారాల"], months: vec!["నెల", "నెలల"], years: vec!["సంవత్సరం", "సంవత్సరాల"], special_cases: vec![], }, Language::Th => TimeagoPattern { word_separator: " ", seconds: vec!["วินาทีที่ผ่านมา"], minutes: vec!["นาทีที่ผ่านมา"], hours: vec!["ชั่วโมงที่ผ่านมา"], days: vec!["วันที่ผ่านมา"], weeks: vec!["สัปดาห์ที่ผ่านมา"], months: vec!["เดือนที่ผ่านมา"], // INFO: fixed years years: vec!["ปีที่แล้ว"], special_cases: vec![], }, Language::Tr => TimeagoPattern { word_separator: " ", seconds: vec!["saniye"], minutes: vec!["dakika"], hours: vec!["saat"], days: vec!["gün"], weeks: vec!["hafta"], months: vec!["ay"], years: vec!["yıl"], special_cases: vec![], }, Language::Uk => TimeagoPattern { word_separator: " ", seconds: vec!["секунд", "секунди", "секунду"], minutes: vec!["хвилин", "хвилини", "хвилину"], hours: vec!["годин", "години", "годину"], days: vec!["день", "дні", "днів"], weeks: vec!["тиждень", "тижні"], months: vec!["місяць", "місяці", "місяців"], years: vec!["роки", "років", "рік"], special_cases: vec![], }, Language::Ur => TimeagoPattern { word_separator: " ", // INFO: fix days, months seconds: vec!["سیکنڈ", "سیکنڈز"], minutes: vec!["منٹ", "منٹس"], hours: vec!["گھنٹہ", "گھنٹے"], days: vec!["دن", "دنوں"], weeks: vec!["ہفتہ", "ہفتے"], months: vec!["مہینہ", "مہینے"], years: vec!["سال"], special_cases: vec![], }, Language::Uz => TimeagoPattern { word_separator: " ", seconds: vec!["soniya"], minutes: vec!["daqiqa"], hours: vec!["soat"], days: vec!["kun"], weeks: vec!["hafta"], months: vec!["oy"], years: vec!["yil"], special_cases: vec![], }, Language::Vi => TimeagoPattern { word_separator: " ", seconds: vec!["giây"], minutes: vec!["phút"], hours: vec!["giờ", "tiếng"], days: vec!["ngày"], weeks: vec!["tuần"], months: vec!["tháng"], years: vec!["năm"], special_cases: vec![], }, Language::ZhCn => TimeagoPattern { // INFO: remove 'ago' character word_separator: "", seconds: vec!["秒"], minutes: vec!["分钟"], hours: vec!["小时"], days: vec!["天"], weeks: vec!["周"], months: vec!["个月"], years: vec!["年"], special_cases: vec![], }, Language::ZhHk => TimeagoPattern { // INFO: fix days, remove 'ago' character word_separator: "", seconds: vec!["秒"], minutes: vec!["分鐘"], hours: vec!["小時"], days: vec!["日"], weeks: vec!["星期"], months: vec!["個月"], years: vec!["年"], special_cases: vec![], }, Language::ZhTw => TimeagoPattern { // INFO: fix days, remove 'ago' character word_separator: "", seconds: vec!["秒"], minutes: vec!["分鐘"], hours: vec!["小時"], days: vec!["天"], weeks: vec!["週"], months: vec!["個月"], years: vec!["年"], special_cases: vec![], }, Language::Zu => TimeagoPattern { word_separator: " ", // INFO: fix hours, days seconds: vec!["amasekhondi", "isekhondi"], minutes: vec!["amaminithi", "iminithi"], hours: vec!["emahoreni", "amahora", "ihora"], days: vec!["ezinsukwini", "izinsuku", "usuku"], weeks: vec!["amaviki", "iviki"], months: vec!["inyanga", "izinyanga"], years: vec!["iminyaka", "unyaka"], special_cases: vec![], }, } } } impl TryFrom<&str> for TimeagoPattern<'_> { type Error = serde_json::Error; fn try_from(s: &str) -> Result { Language::from_str(s).map(|l| Self::from(l)) } } impl TimeagoPattern<'_> { pub fn parse(&self, textual_date: &str) -> Option { self.special_cases .iter() .find_map(|case| { if self.textual_date_matches(textual_date, case.0) { Some(case.1) } else { None } }) .or_else(|| match self.parse_time_unit(textual_date) { Some(tu) => Some(util::parse_numeric::(textual_date).unwrap_or(1) * tu), None => None, }) } fn parse_time_unit(&self, textual_date: &str) -> Option { match self.is_time_unit(textual_date, &self.seconds) { true => Some(1), false => match self.is_time_unit(textual_date, &self.minutes) { true => Some(60), false => match self.is_time_unit(textual_date, &self.hours) { true => Some(3600), false => match self.is_time_unit(textual_date, &self.days) { true => Some(24 * 3600), false => match self.is_time_unit(textual_date, &self.weeks) { true => Some(7 * 24 * 3600), false => match self.is_time_unit(textual_date, &self.months) { true => Some(30 * 24 * 3600), false => match self.is_time_unit(textual_date, &self.years) { true => Some(365 * 24 * 3600), false => None, }, }, }, }, }, }, } } fn is_time_unit(&self, textual_date: &str, phrases: &Vec<&str>) -> bool { phrases .iter() .any(|p| self.textual_date_matches(textual_date, p)) } fn textual_date_matches(&self, textual_date: &str, ago_phrase: &str) -> bool { if textual_date == ago_phrase { return true; } let text_lower = textual_date.to_lowercase().replace('\u{200b}', ""); let ago_lower = ago_phrase.to_lowercase(); if self.word_separator.is_empty() { return text_lower.contains(&ago_lower); } let escaped_phrase = fancy_regex::escape(&ago_lower); let escaped_separator = match self.word_separator { " " => Cow::Borrowed("[ \\t\\xA0\\u1680\\u180e\\u2000-\\u200a\\u202f\\u205f\\u3000]"), _ => fancy_regex::escape(self.word_separator), }; let pattern = format!( "(^|{}){}($|{})", escaped_separator, escaped_phrase, escaped_separator ); ok_or_bail!(Regex::new(&pattern), false) .is_match(&text_lower) .unwrap_or_default() } } #[cfg(test)] mod tests { use std::{collections::BTreeMap, fs::File, io::BufReader, path::Path}; use rstest::rstest; use super::*; #[rstest] #[case(Language::De, "vor 1 Sekunde", Some(1))] #[case(Language::Ar, "قبل ساعة واحدة", Some(3600))] fn t_parse(#[case] lang: Language, #[case] textual_date: &str, #[case] expect: Option) { let pat = TimeagoPattern::try_from(lang).unwrap(); let secs_ago = pat.parse(textual_date); assert_eq!(secs_ago, expect); } #[test] fn t_testfile() { let json_path = Path::new("testfiles/date/timeago.json"); let expect = [ 10 * 60, 20 * 60, 1 * 3600, 2 * 3600, 7 * 3600, 8 * 3600, 9 * 3600, 10 * 3600, 11 * 3600, 12 * 3600, 13 * 3600, 14 * 3600, 15 * 3600, 3 * 3600, 4 * 3600, 4 * 3600, 5 * 3600, 6 * 3600, 6 * 3600, 20 * 3600, 2 * 3600 * 24, 3 * 3600 * 24, 5 * 3600 * 24, 6 * 3600 * 24, 8 * 3600 * 24, 10 * 3600 * 24, 12 * 3600 * 24, 2 * 3600 * 24 * 7, 3 * 3600 * 24 * 7, 4 * 3600 * 24 * 7, 1 * 3600 * 24 * 30, 8 * 3600 * 24 * 30, 11 * 3600 * 24 * 30, 1 * 3600 * 24 * 365, 2 * 3600 * 24 * 365, 3 * 3600 * 24 * 365, 4 * 3600 * 24 * 365, ]; let json_file = File::open(json_path).unwrap(); let strings_map: BTreeMap> = serde_json::from_reader(BufReader::new(json_file)).unwrap(); strings_map.iter().for_each(|(lang, strings)| { let pat = TimeagoPattern::try_from(lang.as_str()).unwrap(); assert_eq!(strings.len(), expect.len()); strings.iter().enumerate().for_each(|(n, s)| { assert_eq!( pat.parse(s), Some(expect[n]), "Language: {}, n: {}", lang, n ); }); }) } }