diff --git a/release_notes.md b/release_notes.md index 18274410a..41ca0e28e 100644 --- a/release_notes.md +++ b/release_notes.md @@ -1,6 +1,7 @@ ###In Development [Commits](https://github.com/MehdiK/Humanizer/compare/v1.35.0...master) + - [#414](https://github.com/MehdiK/Humanizer/pull/414): Add Ukraininan language ###v1.35.0 - 2015-03-29 - [#399](https://github.com/MehdiK/Humanizer/pull/399): Added support for humanizing DateTimeOffset diff --git a/src/Humanizer.Tests/Humanizer.Tests.csproj b/src/Humanizer.Tests/Humanizer.Tests.csproj index 2e9aa81c0..9e869e501 100644 --- a/src/Humanizer.Tests/Humanizer.Tests.csproj +++ b/src/Humanizer.Tests/Humanizer.Tests.csproj @@ -140,6 +140,10 @@ + + + + diff --git a/src/Humanizer.Tests/Localisation/uk-UA/DateHumanizeTests.cs b/src/Humanizer.Tests/Localisation/uk-UA/DateHumanizeTests.cs new file mode 100644 index 000000000..78145369b --- /dev/null +++ b/src/Humanizer.Tests/Localisation/uk-UA/DateHumanizeTests.cs @@ -0,0 +1,258 @@ +using Humanizer.Localisation; +using Xunit; +using Xunit.Extensions; + +namespace Humanizer.Tests.Localisation.ukUA +{ + public class DateHumanizeTests : AmbientCulture + { + public DateHumanizeTests() + : base("uk-UA") + { + } + + [Theory] + [InlineData(1, "секунду тому")] + [InlineData(2, "2 секунди тому")] + [InlineData(3, "3 секунди тому")] + [InlineData(4, "4 секунди тому")] + [InlineData(5, "5 секунд тому")] + [InlineData(6, "6 секунд тому")] + [InlineData(10, "10 секунд тому")] + [InlineData(11, "11 секунд тому")] + [InlineData(19, "19 секунд тому")] + [InlineData(20, "20 секунд тому")] + [InlineData(21, "21 секунду тому")] + [InlineData(22, "22 секунди тому")] + [InlineData(23, "23 секунди тому")] + [InlineData(24, "24 секунди тому")] + [InlineData(25, "25 секунд тому")] + [InlineData(40, "40 секунд тому")] + public void SecondsAgo(int seconds, string expected) + { + DateHumanize.Verify(expected, seconds, TimeUnit.Second, Tense.Past); + } + + [Theory] + [InlineData(1, "через секунду")] + [InlineData(2, "через 2 секунди")] + [InlineData(3, "через 3 секунди")] + [InlineData(4, "через 4 секунди")] + [InlineData(5, "через 5 секунд")] + [InlineData(6, "через 6 секунд")] + [InlineData(10, "через 10 секунд")] + [InlineData(11, "через 11 секунд")] + [InlineData(19, "через 19 секунд")] + [InlineData(20, "через 20 секунд")] + [InlineData(21, "через 21 секунду")] + [InlineData(22, "через 22 секунди")] + [InlineData(23, "через 23 секунди")] + [InlineData(24, "через 24 секунди")] + [InlineData(25, "через 25 секунд")] + [InlineData(40, "через 40 секунд")] + public void SecondsFromNow(int seconds, string expected) + { + DateHumanize.Verify(expected, seconds, TimeUnit.Second, Tense.Future); + } + + [Theory] + [InlineData(1, "хвилину тому")] + [InlineData(2, "2 хвилини тому")] + [InlineData(3, "3 хвилини тому")] + [InlineData(4, "4 хвилини тому")] + [InlineData(5, "5 хвилин тому")] + [InlineData(6, "6 хвилин тому")] + [InlineData(10, "10 хвилин тому")] + [InlineData(11, "11 хвилин тому")] + [InlineData(19, "19 хвилин тому")] + [InlineData(20, "20 хвилин тому")] + [InlineData(21, "21 хвилину тому")] + [InlineData(22, "22 хвилини тому")] + [InlineData(23, "23 хвилини тому")] + [InlineData(24, "24 хвилини тому")] + [InlineData(25, "25 хвилин тому")] + [InlineData(40, "40 хвилин тому")] + [InlineData(60, "годину тому")] + public void MinutesAgo(int minutes, string expected) + { + DateHumanize.Verify(expected, minutes, TimeUnit.Minute, Tense.Past); + } + + [Theory] + [InlineData(1, "через хвилину")] + [InlineData(2, "через 2 хвилини")] + [InlineData(3, "через 3 хвилини")] + [InlineData(4, "через 4 хвилини")] + [InlineData(5, "через 5 хвилин")] + [InlineData(6, "через 6 хвилин")] + [InlineData(10, "через 10 хвилин")] + [InlineData(11, "через 11 хвилин")] + [InlineData(19, "через 19 хвилин")] + [InlineData(20, "через 20 хвилин")] + [InlineData(21, "через 21 хвилину")] + [InlineData(22, "через 22 хвилини")] + [InlineData(23, "через 23 хвилини")] + [InlineData(24, "через 24 хвилини")] + [InlineData(25, "через 25 хвилин")] + [InlineData(40, "через 40 хвилин")] + public void MinutesFromNow(int minutes, string expected) + { + DateHumanize.Verify(expected, minutes, TimeUnit.Minute, Tense.Future); + } + + [Theory] + [InlineData(1, "годину тому")] + [InlineData(2, "2 години тому")] + [InlineData(3, "3 години тому")] + [InlineData(4, "4 години тому")] + [InlineData(5, "5 годин тому")] + [InlineData(6, "6 годин тому")] + [InlineData(10, "10 годин тому")] + [InlineData(11, "11 годин тому")] + [InlineData(19, "19 годин тому")] + [InlineData(20, "20 годин тому")] + [InlineData(21, "21 годину тому")] + [InlineData(22, "22 години тому")] + [InlineData(23, "23 години тому")] + public void HoursAgo(int hours, string expected) + { + DateHumanize.Verify(expected, hours, TimeUnit.Hour, Tense.Past); + } + + [Theory] + [InlineData(1, "через годину")] + [InlineData(2, "через 2 години")] + [InlineData(3, "через 3 години")] + [InlineData(4, "через 4 години")] + [InlineData(5, "через 5 годин")] + [InlineData(6, "через 6 годин")] + [InlineData(10, "через 10 годин")] + [InlineData(11, "через 11 годин")] + [InlineData(19, "через 19 годин")] + [InlineData(20, "через 20 годин")] + [InlineData(21, "через 21 годину")] + [InlineData(22, "через 22 години")] + [InlineData(23, "через 23 години")] + public void HoursFromNow(int hours, string expected) + { + DateHumanize.Verify(expected, hours, TimeUnit.Hour, Tense.Future); + } + + [Theory] + [InlineData(1, "вчора")] + [InlineData(2, "2 дні тому")] + [InlineData(3, "3 дні тому")] + [InlineData(4, "4 дні тому")] + [InlineData(5, "5 днів тому")] + [InlineData(6, "6 днів тому")] + [InlineData(10, "10 днів тому")] + [InlineData(11, "11 днів тому")] + [InlineData(19, "19 днів тому")] + [InlineData(20, "20 днів тому")] + [InlineData(21, "21 день тому")] + [InlineData(22, "22 дні тому")] + [InlineData(23, "23 дні тому")] + [InlineData(24, "24 дні тому")] + [InlineData(25, "25 днів тому")] + public void DaysAgo(int days, string expected) + { + DateHumanize.Verify(expected, days, TimeUnit.Day, Tense.Past); + } + + [Theory] + [InlineData(1, "завтра")] + [InlineData(2, "через 2 дні")] + [InlineData(3, "через 3 дні")] + [InlineData(4, "через 4 дні")] + [InlineData(5, "через 5 днів")] + [InlineData(6, "через 6 днів")] + [InlineData(10, "через 10 днів")] + [InlineData(11, "через 11 днів")] + [InlineData(19, "через 19 днів")] + [InlineData(20, "через 20 днів")] + [InlineData(21, "через 21 день")] + [InlineData(22, "через 22 дні")] + [InlineData(23, "через 23 дні")] + [InlineData(24, "через 24 дні")] + [InlineData(25, "через 25 днів")] + public void DaysFromNow(int days, string expected) + { + DateHumanize.Verify(expected, days, TimeUnit.Day, Tense.Future); + } + + [Theory] + [InlineData(1, "місяць тому")] + [InlineData(2, "2 місяці тому")] + [InlineData(3, "3 місяці тому")] + [InlineData(4, "4 місяці тому")] + [InlineData(5, "5 місяців тому")] + [InlineData(6, "6 місяців тому")] + [InlineData(10, "10 місяців тому")] + [InlineData(11, "11 місяців тому")] + public void MonthsAgo(int months, string expected) + { + DateHumanize.Verify(expected, months, TimeUnit.Month, Tense.Past); + } + + [Theory] + [InlineData(1, "через місяць")] + [InlineData(2, "через 2 місяці")] + [InlineData(3, "через 3 місяці")] + [InlineData(4, "через 4 місяці")] + [InlineData(5, "через 5 місяців")] + [InlineData(6, "через 6 місяців")] + [InlineData(10, "через 10 місяців")] + [InlineData(11, "через 11 місяців")] + public void MonthsFromNow(int months, string expected) + { + DateHumanize.Verify(expected, months, TimeUnit.Month, Tense.Future); + } + + [Theory] + [InlineData(1, "рік тому")] + [InlineData(2, "2 роки тому")] + [InlineData(3, "3 роки тому")] + [InlineData(4, "4 роки тому")] + [InlineData(5, "5 років тому")] + [InlineData(6, "6 років тому")] + [InlineData(10, "10 років тому")] + [InlineData(11, "11 років тому")] + [InlineData(19, "19 років тому")] + [InlineData(21, "21 рік тому")] + [InlineData(111, "111 років тому")] + [InlineData(121, "121 рік тому")] + [InlineData(222, "222 роки тому")] + [InlineData(325, "325 років тому")] + public void YearsAgo(int years, string expected) + { + DateHumanize.Verify(expected, years, TimeUnit.Year, Tense.Past); + } + + [Theory] + [InlineData(1, "через рік")] + [InlineData(2, "через 2 роки")] + [InlineData(3, "через 3 роки")] + [InlineData(4, "через 4 роки")] + [InlineData(5, "через 5 років")] + [InlineData(6, "через 6 років")] + [InlineData(10, "через 10 років")] + [InlineData(11, "через 11 років")] + [InlineData(19, "через 19 років")] + [InlineData(20, "через 20 років")] + [InlineData(21, "через 21 рік")] + [InlineData(111, "через 111 років")] + [InlineData(121, "через 121 рік")] + [InlineData(222, "через 222 роки")] + [InlineData(325, "через 325 років")] + public void YearsFromNow(int years, string expected) + { + DateHumanize.Verify(expected, years, TimeUnit.Year, Tense.Future); + } + + [Fact] + public void Now() + { + DateHumanize.Verify("зараз", 0, TimeUnit.Day, Tense.Past); + } + } +} diff --git a/src/Humanizer.Tests/Localisation/uk-UA/NumberToWordsTests.cs b/src/Humanizer.Tests/Localisation/uk-UA/NumberToWordsTests.cs new file mode 100644 index 000000000..011b1c75b --- /dev/null +++ b/src/Humanizer.Tests/Localisation/uk-UA/NumberToWordsTests.cs @@ -0,0 +1,241 @@ +using Xunit; +using Xunit.Extensions; + +namespace Humanizer.Tests.Localisation.ukUA +{ + public class NumberToWordsTests : AmbientCulture + { + public NumberToWordsTests() + : base("uk-UA") + { + } + + [Theory] + [InlineData(0, "нуль")] + [InlineData(1, "один")] + [InlineData(10, "десять")] + [InlineData(11, "одинадцять")] + [InlineData(12, "дванадцять")] + [InlineData(13, "тринадцять")] + [InlineData(14, "чотирнадцять")] + [InlineData(15, "п'ятнадцять")] + [InlineData(16, "шістнадцять")] + [InlineData(17, "сімнадцять")] + [InlineData(18, "вісімнадцять")] + [InlineData(19, "дев'ятнадцять")] + [InlineData(20, "двадцять")] + [InlineData(30, "тридцять")] + [InlineData(40, "сорок")] + [InlineData(50, "п'ятдесят")] + [InlineData(60, "шістдесят")] + [InlineData(70, "сімдесят")] + [InlineData(80, "вісімдесят")] + [InlineData(90, "дев'яносто")] + [InlineData(100, "сто")] + [InlineData(200, "двісті")] + [InlineData(300, "триста")] + [InlineData(400, "чотириста")] + [InlineData(500, "п'ятсот")] + [InlineData(600, "шістсот")] + [InlineData(700, "сімсот")] + [InlineData(800, "вісімсот")] + [InlineData(900, "дев'ятсот")] + [InlineData(1000, "одна тисяча")] + [InlineData(2000, "дві тисячі")] + [InlineData(3000, "три тисячі")] + [InlineData(4000, "чотири тисячі")] + [InlineData(5000, "п'ять тисяч")] + [InlineData(10000, "десять тисяч")] + [InlineData(100000, "сто тисяч")] + [InlineData(1000000, "один мільйон")] + [InlineData(2000000, "два мільйона")] + [InlineData(10000000, "десять мільйонів")] + [InlineData(100000000, "сто мільйонів")] + [InlineData(1000000000, "один мільярд")] + [InlineData(2000000000, "два мільярда")] + [InlineData(122, "сто двадцять два")] + [InlineData(3501, "три тисячі п'ятсот один")] + [InlineData(111, "сто одинадцять")] + [InlineData(1112, "одна тисяча сто дванадцять")] + [InlineData(11213, "одинадцять тисяч двісті тринадцять")] + [InlineData(121314, "сто двадцять одна тисяча триста чотирнадцять")] + [InlineData(2132415, "два мільйона сто тридцять дві тисячі чотириста п'ятнадцять")] + [InlineData(12345516, "дванадцять мільйонів триста сорок п'ять тисяч п'ятсот шістнадцять")] + [InlineData(751633617, "сімсот п'ятдесят один мільйон шістсот тридцять три тисячі шістсот сімнадцять")] + [InlineData(1111111118, "один мільярд сто одинадцять мільйонів сто одинадцять тисяч сто вісімнадцять")] + [InlineData(-751633617, "мінус сімсот п'ятдесят один мільйон шістсот тридцять три тисячі шістсот сімнадцять")] + public void ToWords(int number, string expected) + { + Assert.Equal(expected, number.ToWords()); + } + + [Theory] + [InlineData(122, "сто двадцять дві", GrammaticalGender.Feminine)] + [InlineData(3501, "три тисячі п'ятсот одна", GrammaticalGender.Feminine)] + [InlineData(3501, "три тисячі п'ятсот одне", GrammaticalGender.Neuter)] + public void ToWordsWithGender(int number, string expected, GrammaticalGender gender) + { + Assert.Equal(expected, number.ToWords(gender)); + } + + [Theory] + [InlineData(0, "нульовий")] + [InlineData(1, "перший")] + [InlineData(2, "другий")] + [InlineData(3, "третій")] + [InlineData(4, "четвертий")] + [InlineData(10, "десятий")] + [InlineData(11, "одинадцятий")] + [InlineData(12, "дванадцятий")] + [InlineData(13, "тринадцятий")] + [InlineData(14, "чотирнадцятий")] + [InlineData(15, "п'ятнадцятий")] + [InlineData(16, "шістнадцятий")] + [InlineData(17, "сімнадцятий")] + [InlineData(18, "вісімнадцятий")] + [InlineData(19, "дев'ятнадцятий")] + [InlineData(20, "двадцятий")] + [InlineData(30, "тридцятий")] + [InlineData(40, "сороковий")] + [InlineData(50, "п'ятдесятий")] + [InlineData(60, "шістдесятий")] + [InlineData(70, "сімдесятий")] + [InlineData(80, "вісімдесятий")] + [InlineData(90, "дев'яностий")] + [InlineData(100, "сотий")] + [InlineData(101, "сто перший")] + [InlineData(140, "сто сороковий")] + [InlineData(200, "двохсотий")] + [InlineData(300, "трьохсотий")] + [InlineData(400, "чотирьохсотий")] + [InlineData(500, "п'ятисотий")] + [InlineData(600, "шестисотий")] + [InlineData(700, "семисотий")] + [InlineData(800, "восьмисотий")] + [InlineData(900, "дев'ятисотий")] + [InlineData(1000, "тисячний")] + [InlineData(1001, "одна тисяча перший")] + [InlineData(1040, "одна тисяча сороковий")] + [InlineData(2000, "двохтисячний")] + [InlineData(3000, "трьохтисячний")] + [InlineData(4000, "чотирьохтисячний")] + [InlineData(5000, "п'ятитисячний")] + [InlineData(10000, "десятитисячний")] + [InlineData(21000, "двадцятиоднотисячний")] + [InlineData(100000, "стотисячний")] + [InlineData(101000, "стооднотисячний")] + [InlineData(121000, "стодвадцятиоднотисячний")] + [InlineData(200000, "двохсоттисячний")] + [InlineData(1000000, "мільйонний")] + [InlineData(2000000, "двохмільйонний")] + [InlineData(10000000, "десятимільйонний")] + [InlineData(21000000, "двадцятиодномільйонний")] + [InlineData(100000000, "стомільйонний")] + [InlineData(230000000, "двохсоттридцятимільйонний")] + [InlineData(1000000000, "мільярдний")] + [InlineData(2000000000, "двохмільярдний")] + [InlineData(122, "сто двадцять другий")] + [InlineData(3501, "три тисячі п'ятсот перший")] + [InlineData(111, "сто одинадцятий")] + [InlineData(1112, "одна тисяча сто дванадцятий")] + [InlineData(11213, "одинадцять тисяч двісті тринадцятий")] + [InlineData(121314, "сто двадцять одна тисяча триста чотирнадцятий")] + [InlineData(2132415, "два мільйона сто тридцять дві тисячі чотириста п'ятнадцятий")] + [InlineData(12345516, "дванадцять мільйонів триста сорок п'ять тисяч п'ятсот шістнадцятий")] + [InlineData(751633617, "сімсот п'ятдесят один мільйон шістсот тридцять три тисячі шістсот сімнадцятий")] + [InlineData(1111111118, "один мільярд сто одинадцять мільйонів сто одинадцять тисяч сто вісімнадцятий")] + [InlineData(1111111000, "один мільярд сто одинадцять мільйонів стоодинадцятитисячний")] + [InlineData(1234567000, "один мільярд двісті тридцять чотири мільйона п'ятисотшістдесятисемитисячний")] + [InlineData(2000000000, "двохмільярдний")] + [InlineData(-751633617, "мінус сімсот п'ятдесят один мільйон шістсот тридцять три тисячі шістсот сімнадцятий")] + public void ToOrdinalWords(int number, string expected) + { + Assert.Equal(expected, number.ToOrdinalWords()); + } + + [Theory] + [InlineData(0, "нульова")] + [InlineData(1, "перша")] + [InlineData(2, "друга")] + [InlineData(3, "третя")] + [InlineData(4, "четверта")] + [InlineData(10, "десята")] + [InlineData(11, "одинадцята")] + [InlineData(12, "дванадцята")] + [InlineData(13, "тринадцята")] + [InlineData(14, "чотирнадцята")] + [InlineData(15, "п'ятнадцята")] + [InlineData(16, "шістнадцята")] + [InlineData(17, "сімнадцята")] + [InlineData(18, "вісімнадцята")] + [InlineData(19, "дев'ятнадцята")] + [InlineData(20, "двадцята")] + [InlineData(30, "тридцята")] + [InlineData(40, "сорокова")] + [InlineData(50, "п'ятдесята")] + [InlineData(60, "шістдесята")] + [InlineData(70, "сімдесята")] + [InlineData(80, "вісімдесята")] + [InlineData(90, "дев'яноста")] + [InlineData(100, "сота")] + [InlineData(200, "двохсота")] + [InlineData(300, "трьохсота")] + [InlineData(400, "чотирьохсота")] + [InlineData(500, "п'ятисота")] + [InlineData(600, "шестисота")] + [InlineData(700, "семисота")] + [InlineData(800, "восьмисота")] + [InlineData(900, "дев'ятисота")] + [InlineData(1000, "тисячна")] + [InlineData(2000, "двохтисячна")] + [InlineData(3000, "трьохтисячна")] + [InlineData(4000, "чотирьохтисячна")] + [InlineData(5000, "п'ятитисячна")] + [InlineData(10000, "десятитисячна")] + [InlineData(90000, "дев'яностотисячна")] + [InlineData(100000, "стотисячна")] + [InlineData(990000, "дев'ятисотдев'яностотисячна")] + [InlineData(990001, "дев'ятсот дев'яносто тисяч перша")] + [InlineData(1000000, "мільйонна")] + [InlineData(2000000, "двохмільйонна")] + [InlineData(10000000, "десятимільйонна")] + [InlineData(100000000, "стомільйонна")] + [InlineData(1000000000, "мільярдна")] + [InlineData(2000000000, "двохмільярдна")] + [InlineData(122, "сто двадцять друга")] + [InlineData(3501, "три тисячі п'ятсот перша")] + [InlineData(111, "сто одинадцята")] + [InlineData(1112, "одна тисяча сто дванадцята")] + [InlineData(11000, "одинадцятитисячна")] + [InlineData(11001, "одинадцять тисяч перша")] + [InlineData(11213, "одинадцять тисяч двісті тринадцята")] + [InlineData(15000, "п'ятнадцятитисячна")] + [InlineData(20000, "двадцятитисячна")] + [InlineData(121314, "сто двадцять одна тисяча триста чотирнадцята")] + [InlineData(2132415, "два мільйона сто тридцять дві тисячі чотириста п'ятнадцята")] + [InlineData(12345516, "дванадцять мільйонів триста сорок п'ять тисяч п'ятсот шістнадцята")] + [InlineData(751633617, "сімсот п'ятдесят один мільйон шістсот тридцять три тисячі шістсот сімнадцята")] + [InlineData(1111111118, "один мільярд сто одинадцять мільйонів сто одинадцять тисяч сто вісімнадцята")] + [InlineData(-751633617, "мінус сімсот п'ятдесят один мільйон шістсот тридцять три тисячі шістсот сімнадцята")] + public void ToOrdinalWordsFeminine(int number, string expected) + { + Assert.Equal(expected, number.ToOrdinalWords(GrammaticalGender.Feminine)); + } + + [Theory] + [InlineData(3, "третє")] + [InlineData(111, "сто одинадцяте")] + [InlineData(1112, "одна тисяча сто дванадцяте")] + [InlineData(11213, "одинадцять тисяч двісті тринадцяте")] + [InlineData(121314, "сто двадцять одна тисяча триста чотирнадцяте")] + [InlineData(2132415, "два мільйона сто тридцять дві тисячі чотириста п'ятнадцяте")] + [InlineData(12345516, "дванадцять мільйонів триста сорок п'ять тисяч п'ятсот шістнадцяте")] + [InlineData(751633617, "сімсот п'ятдесят один мільйон шістсот тридцять три тисячі шістсот сімнадцяте")] + [InlineData(1111111118, "один мільярд сто одинадцять мільйонів сто одинадцять тисяч сто вісімнадцяте")] + [InlineData(-751633617, "мінус сімсот п'ятдесят один мільйон шістсот тридцять три тисячі шістсот сімнадцяте")] + public void ToOrdinalWordsNeuter(int number, string expected) + { + Assert.Equal(expected, number.ToOrdinalWords(GrammaticalGender.Neuter)); + } + } +} diff --git a/src/Humanizer.Tests/Localisation/uk-UA/OrdinalizeTests.cs b/src/Humanizer.Tests/Localisation/uk-UA/OrdinalizeTests.cs new file mode 100644 index 000000000..cac60d7d3 --- /dev/null +++ b/src/Humanizer.Tests/Localisation/uk-UA/OrdinalizeTests.cs @@ -0,0 +1,129 @@ +using Xunit; +using Xunit.Extensions; + +namespace Humanizer.Tests.Localisation.ukUA +{ + public class OrdinalizeTests : AmbientCulture + { + public OrdinalizeTests() + : base("uk-UA") + { + } + + [Theory] + [InlineData("0", "0-й")] + [InlineData("1", "1-й")] + [InlineData("2", "2-й")] + [InlineData("3", "3-й")] + [InlineData("4", "4-й")] + [InlineData("5", "5-й")] + [InlineData("6", "6-й")] + [InlineData("23", "23-й")] + [InlineData("100", "100-й")] + [InlineData("101", "101-й")] + [InlineData("102", "102-й")] + [InlineData("103", "103-й")] + [InlineData("1001", "1001-й")] + public void OrdinalizeString(string number, string ordinalized) + { + Assert.Equal(number.Ordinalize(GrammaticalGender.Masculine), ordinalized); + } + + [Theory] + [InlineData("0", "0-а")] + [InlineData("1", "1-а")] + [InlineData("2", "2-а")] + [InlineData("3", "3-я")] + [InlineData("4", "4-а")] + [InlineData("5", "5-а")] + [InlineData("6", "6-а")] + [InlineData("23", "23-я")] + [InlineData("100", "100-а")] + [InlineData("101", "101-а")] + [InlineData("102", "102-а")] + [InlineData("103", "103-я")] + [InlineData("1001", "1001-а")] + public void OrdinalizeStringFeminine(string number, string ordinalized) + { + Assert.Equal(number.Ordinalize(GrammaticalGender.Feminine), ordinalized); + } + + [Theory] + [InlineData("0", "0-е")] + [InlineData("1", "1-е")] + [InlineData("2", "2-е")] + [InlineData("3", "3-є")] + [InlineData("4", "4-е")] + [InlineData("5", "5-е")] + [InlineData("6", "6-е")] + [InlineData("23", "23-є")] + [InlineData("100", "100-е")] + [InlineData("101", "101-е")] + [InlineData("102", "102-е")] + [InlineData("103", "103-є")] + [InlineData("1001", "1001-е")] + public void OrdinalizeStringNeuter(string number, string ordinalized) + { + Assert.Equal(number.Ordinalize(GrammaticalGender.Neuter), ordinalized); + } + + [Theory] + [InlineData(0, "0-й")] + [InlineData(1, "1-й")] + [InlineData(2, "2-й")] + [InlineData(3, "3-й")] + [InlineData(4, "4-й")] + [InlineData(5, "5-й")] + [InlineData(6, "6-й")] + [InlineData(10, "10-й")] + [InlineData(23, "23-й")] + [InlineData(100, "100-й")] + [InlineData(101, "101-й")] + [InlineData(102, "102-й")] + [InlineData(103, "103-й")] + [InlineData(1001, "1001-й")] + public void OrdinalizeNumber(int number, string ordinalized) + { + Assert.Equal(number.Ordinalize(GrammaticalGender.Masculine), ordinalized); + } + + [Theory] + [InlineData(0, "0-а")] + [InlineData(1, "1-а")] + [InlineData(2, "2-а")] + [InlineData(3, "3-я")] + [InlineData(4, "4-а")] + [InlineData(5, "5-а")] + [InlineData(6, "6-а")] + [InlineData(10, "10-а")] + [InlineData(23, "23-я")] + [InlineData(100, "100-а")] + [InlineData(101, "101-а")] + [InlineData(102, "102-а")] + [InlineData(103, "103-я")] + [InlineData(1001, "1001-а")] + public void OrdinalizeNumberFeminine(int number, string ordinalized) + { + Assert.Equal(number.Ordinalize(GrammaticalGender.Feminine), ordinalized); + } + + [Theory] + [InlineData(0, "0-е")] + [InlineData(1, "1-е")] + [InlineData(2, "2-е")] + [InlineData(3, "3-є")] + [InlineData(4, "4-е")] + [InlineData(5, "5-е")] + [InlineData(6, "6-е")] + [InlineData(23, "23-є")] + [InlineData(100, "100-е")] + [InlineData(101, "101-е")] + [InlineData(102, "102-е")] + [InlineData(103, "103-є")] + [InlineData(1001, "1001-е")] + public void OrdinalizeNumberNeuter(int number, string ordinalized) + { + Assert.Equal(number.Ordinalize(GrammaticalGender.Neuter), ordinalized); + } + } +} \ No newline at end of file diff --git a/src/Humanizer.Tests/Localisation/uk-UA/TimeSpanHumanizeTests.cs b/src/Humanizer.Tests/Localisation/uk-UA/TimeSpanHumanizeTests.cs new file mode 100644 index 000000000..cf9806bc3 --- /dev/null +++ b/src/Humanizer.Tests/Localisation/uk-UA/TimeSpanHumanizeTests.cs @@ -0,0 +1,129 @@ +using System; +using Xunit; +using Xunit.Extensions; + +namespace Humanizer.Tests.Localisation.ukUA +{ + public class TimeSpanHumanizeTests : AmbientCulture + { + public TimeSpanHumanizeTests() + : base("uk-UA") + { + } + + [Theory] + [InlineData(7, "один тиждень")] + [InlineData(14, "2 тижні")] + [InlineData(21, "3 тижні")] + [InlineData(28, "4 тижні")] + [InlineData(35, "5 тижнів")] + [InlineData(77, "11 тижнів")] + public void Weeks(int days, string expected) + { + Assert.Equal(expected, TimeSpan.FromDays(days).Humanize()); + } + + [Theory] + [InlineData(1, "один день")] + [InlineData(2, "2 дні")] + [InlineData(3, "3 дні")] + [InlineData(4, "4 дні")] + [InlineData(5, "5 днів")] + [InlineData(6, "6 днів")] + public void Days(int days, string expected) + { + Assert.Equal(expected, TimeSpan.FromDays(days).Humanize()); + } + + [Theory] + [InlineData(1, "одна година")] + [InlineData(2, "2 години")] + [InlineData(3, "3 години")] + [InlineData(4, "4 години")] + [InlineData(5, "5 годин")] + [InlineData(6, "6 годин")] + [InlineData(10, "10 годин")] + [InlineData(11, "11 годин")] + [InlineData(19, "19 годин")] + [InlineData(20, "20 годин")] + [InlineData(21, "21 година")] + [InlineData(22, "22 години")] + [InlineData(23, "23 години")] + public void Hours(int hours, string expected) + { + Assert.Equal(expected, TimeSpan.FromHours(hours).Humanize()); + } + + [Theory] + [InlineData(1, "одна хвилина")] + [InlineData(2, "2 хвилини")] + [InlineData(3, "3 хвилини")] + [InlineData(4, "4 хвилини")] + [InlineData(5, "5 хвилин")] + [InlineData(6, "6 хвилин")] + [InlineData(10, "10 хвилин")] + [InlineData(11, "11 хвилин")] + [InlineData(19, "19 хвилин")] + [InlineData(20, "20 хвилин")] + [InlineData(21, "21 хвилина")] + [InlineData(22, "22 хвилини")] + [InlineData(23, "23 хвилини")] + [InlineData(24, "24 хвилини")] + [InlineData(25, "25 хвилин")] + [InlineData(40, "40 хвилин")] + public void Minutes(int minutes, string expected) + { + Assert.Equal(expected, TimeSpan.FromMinutes(minutes).Humanize()); + } + + [Theory] + [InlineData(1, "одна секунда")] + [InlineData(2, "2 секунди")] + [InlineData(3, "3 секунди")] + [InlineData(4, "4 секунди")] + [InlineData(5, "5 секунд")] + [InlineData(6, "6 секунд")] + [InlineData(10, "10 секунд")] + [InlineData(11, "11 секунд")] + [InlineData(19, "19 секунд")] + [InlineData(20, "20 секунд")] + [InlineData(21, "21 секунда")] + [InlineData(22, "22 секунди")] + [InlineData(23, "23 секунди")] + [InlineData(24, "24 секунди")] + [InlineData(25, "25 секунд")] + [InlineData(40, "40 секунд")] + public void Seconds(int seconds, string expected) + { + Assert.Equal(expected, TimeSpan.FromSeconds(seconds).Humanize()); + } + + [Theory] + [InlineData(1, "одна мілісекунда")] + [InlineData(2, "2 мілісекунди")] + [InlineData(3, "3 мілісекунди")] + [InlineData(4, "4 мілісекунди")] + [InlineData(5, "5 мілісекунд")] + [InlineData(6, "6 мілісекунд")] + [InlineData(10, "10 мілісекунд")] + [InlineData(11, "11 мілісекунд")] + [InlineData(19, "19 мілісекунд")] + [InlineData(20, "20 мілісекунд")] + [InlineData(21, "21 мілісекунда")] + [InlineData(22, "22 мілісекунди")] + [InlineData(23, "23 мілісекунди")] + [InlineData(24, "24 мілісекунди")] + [InlineData(25, "25 мілісекунд")] + [InlineData(40, "40 мілісекунд")] + public void Milliseconds(int milliseconds, string expected) + { + Assert.Equal(expected, TimeSpan.FromMilliseconds(milliseconds).Humanize()); + } + + [Fact] + public void NoTime() + { + Assert.Equal("без часу", TimeSpan.Zero.Humanize()); + } + } +} diff --git a/src/Humanizer/Configuration/FormatterRegistry.cs b/src/Humanizer/Configuration/FormatterRegistry.cs index 59f296d81..4c0552eb3 100644 --- a/src/Humanizer/Configuration/FormatterRegistry.cs +++ b/src/Humanizer/Configuration/FormatterRegistry.cs @@ -13,6 +13,7 @@ public FormatterRegistry() : base(new DefaultFormatter("en-US")) Register("sl", new SlovenianFormatter()); Register("sr", new SerbianFormatter("sr")); Register("sr-Latn", new SerbianFormatter("sr-Latn")); + Register("uk", new UkrainianFormatter()); RegisterCzechSlovakPolishFormatter("cs"); RegisterCzechSlovakPolishFormatter("pl"); RegisterCzechSlovakPolishFormatter("sk"); diff --git a/src/Humanizer/Configuration/NumberToWordsConverterRegistry.cs b/src/Humanizer/Configuration/NumberToWordsConverterRegistry.cs index 16e0e9a7b..bd45d0271 100644 --- a/src/Humanizer/Configuration/NumberToWordsConverterRegistry.cs +++ b/src/Humanizer/Configuration/NumberToWordsConverterRegistry.cs @@ -21,6 +21,7 @@ internal class NumberToWordsConverterRegistry : LocaliserRegistry + @@ -98,6 +99,7 @@ + @@ -110,6 +112,7 @@ + @@ -185,6 +188,7 @@ + diff --git a/src/Humanizer/Localisation/Formatters/UkrainianFormatter.cs b/src/Humanizer/Localisation/Formatters/UkrainianFormatter.cs new file mode 100644 index 000000000..23d2e102b --- /dev/null +++ b/src/Humanizer/Localisation/Formatters/UkrainianFormatter.cs @@ -0,0 +1,28 @@ +using Humanizer.Localisation.GrammaticalNumber; + +namespace Humanizer.Localisation.Formatters +{ + internal class UkrainianFormatter : DefaultFormatter + { + public UkrainianFormatter() + : base("uk") + { + } + + protected override string GetResourceKey(string resourceKey, int number) + { + var grammaticalNumber = RussianGrammaticalNumberDetector.Detect(number); + var suffix = GetSuffix(grammaticalNumber); + return resourceKey + suffix; + } + + private string GetSuffix(RussianGrammaticalNumber grammaticalNumber) + { + if (grammaticalNumber == RussianGrammaticalNumber.Singular) + return "_Singular"; + if (grammaticalNumber == RussianGrammaticalNumber.Paucal) + return "_Paucal"; + return ""; + } + } +} \ No newline at end of file diff --git a/src/Humanizer/Localisation/NumberToWords/UkrainianNumberToWordsConverter.cs b/src/Humanizer/Localisation/NumberToWords/UkrainianNumberToWordsConverter.cs new file mode 100644 index 000000000..8473421a9 --- /dev/null +++ b/src/Humanizer/Localisation/NumberToWords/UkrainianNumberToWordsConverter.cs @@ -0,0 +1,209 @@ +using System; +using System.Collections.Generic; +using Humanizer.Localisation.GrammaticalNumber; + +namespace Humanizer.Localisation.NumberToWords +{ + internal class UkrainianNumberToWordsConverter : GenderedNumberToWordsConverter + { + private static readonly string[] HundredsMap = { "нуль", "сто", "двісті", "триста", "чотириста", "п'ятсот", "шістсот", "сімсот", "вісімсот", "дев'ятсот" }; + private static readonly string[] TensMap = { "нуль", "десять", "двадцять", "тридцять", "сорок", "п'ятдесят", "шістдесят", "сімдесят", "вісімдесят", "дев'яносто" }; + private static readonly string[] UnitsMap = { "нуль", "один", "два", "три", "чотири", "п'ять", "шість", "сім", "вісім", "дев'ять", "десять", "одинадцять", "дванадцять", "тринадцять", "чотирнадцять", "п'ятнадцять", "шістнадцять", "сімнадцять", "вісімнадцять", "дев'ятнадцять" }; + private static readonly string[] UnitsOrdinalPrefixes = { string.Empty, string.Empty, "двох", "трьох", "чотирьох", "п'яти", "шести", "семи", "восьми", "дев'яти", "десяти", "одинадцяти", "дванадцяти", "тринадцяти", "чотирнадцяти", "п'ятнадцяти", "шістнадцяти", "сімнадцяти", "вісімнадцяти", "дев'ятнадцяти", "двадцяти" }; + private static readonly string[] TensOrdinalPrefixes = { string.Empty, "десяти", "двадцяти", "тридцяти", "сорока", "п'ятдесяти", "шістдесяти", "сімдесяти", "вісімдесяти", "дев'яносто" }; + private static readonly string[] TensOrdinal = { string.Empty, "десят", "двадцят", "тридцят", "сороков", "п'ятдесят", "шістдесят", "сімдесят", "вісімдесят", "дев'яност" }; + private static readonly string[] UnitsOrdinal = { "нульов", "перш", "друг", "трет", "четверт", "п'ят", "шост", "сьом", "восьм", "дев'ят", "десят", "одинадцят", "дванадцят", "тринадцят", "чотирнадцят", "п'ятнадцят", "шістнадцят", "сімнадцят", "вісімнадцят", "дев'ятнадцят" }; + + public override string Convert(int number, GrammaticalGender gender) + { + if (number == 0) + return "нуль"; + + var parts = new List(); + + if (number < 0) + { + parts.Add("мінус"); + number = -number; + } + + CollectParts(parts, ref number, 1000000000, GrammaticalGender.Masculine, "мільярд", "мільярда", "мільярдів"); + CollectParts(parts, ref number, 1000000, GrammaticalGender.Masculine, "мільйон", "мільйона", "мільйонів"); + CollectParts(parts, ref number, 1000, GrammaticalGender.Feminine, "тисяча", "тисячі", "тисяч"); + + if (number > 0) + CollectPartsUnderOneThousand(parts, number, gender); + + return string.Join(" ", parts); + } + + public override string ConvertToOrdinal(int number, GrammaticalGender gender) + { + if (number == 0) + return "нульов" + GetEndingForGender(gender, number); + + var parts = new List(); + + if (number < 0) + { + parts.Add("мінус"); + number = -number; + } + + CollectOrdinalParts(parts, ref number, 1000000000, GrammaticalGender.Masculine, "мільярдн" + GetEndingForGender(gender, number), "мільярд", "мільярда", "мільярдів"); + CollectOrdinalParts(parts, ref number, 1000000, GrammaticalGender.Masculine, "мільйонн" + GetEndingForGender(gender, number), "мільйон", "мільйона", "мільйонів"); + CollectOrdinalParts(parts, ref number, 1000, GrammaticalGender.Feminine, "тисячн" + GetEndingForGender(gender, number), "тисяча", "тисячі", "тисяч"); + + if (number >= 100) + { + var ending = GetEndingForGender(gender, number); + var hundreds = number/100; + number %= 100; + if (number == 0) + parts.Add(UnitsOrdinalPrefixes[hundreds] + "сот" + ending); + else + parts.Add(HundredsMap[hundreds]); + } + + if (number >= 20) + { + var ending = GetEndingForGender(gender, number); + var tens = number/10; + number %= 10; + if (number == 0) + parts.Add(TensOrdinal[tens] + ending); + else + parts.Add(TensMap[tens]); + } + + if (number > 0) + parts.Add(UnitsOrdinal[number] + GetEndingForGender(gender, number)); + + return string.Join(" ", parts); + } + + private static void CollectPartsUnderOneThousand(ICollection parts, int number, GrammaticalGender gender) + { + if (number >= 100) + { + var hundreds = number/100; + number %= 100; + parts.Add(HundredsMap[hundreds]); + } + + if (number >= 20) + { + var tens = number/10; + parts.Add(TensMap[tens]); + number %= 10; + } + + if (number > 0) + { + if (number == 1 && gender == GrammaticalGender.Feminine) + parts.Add("одна"); + else if (number == 1 && gender == GrammaticalGender.Neuter) + parts.Add("одне"); + else if (number == 2 && gender == GrammaticalGender.Feminine) + parts.Add("дві"); + else if (number < 20) + parts.Add(UnitsMap[number]); + } + } + + private static string GetPrefix(int number) + { + var parts = new List(); + + if (number >= 100) + { + var hundreds = number/100; + number %= 100; + if (hundreds != 1) + parts.Add(UnitsOrdinalPrefixes[hundreds] + "сот"); + else + parts.Add("сто"); + } + + if (number >= 20) + { + var tens = number/10; + number %= 10; + parts.Add(TensOrdinalPrefixes[tens]); + } + + if (number > 0) + { + parts.Add(number == 1 ? "одно" : UnitsOrdinalPrefixes[number]); + } + + return string.Join("", parts); + } + + private static void CollectParts(ICollection parts, ref int number, int divisor, GrammaticalGender gender, params string[] forms) + { + if (number < divisor) return; + var result = number/divisor; + number %= divisor; + + CollectPartsUnderOneThousand(parts, result, gender); + parts.Add(ChooseOneForGrammaticalNumber(result, forms)); + } + + private static void CollectOrdinalParts(ICollection parts, ref int number, int divisor, GrammaticalGender gender, string prefixedForm, params string[] forms) + { + if (number < divisor) return; + var result = number/divisor; + number %= divisor; + if (number == 0) + { + if (result == 1) + parts.Add(prefixedForm); + else + parts.Add(GetPrefix(result) + prefixedForm); + } + else + { + CollectPartsUnderOneThousand(parts, result, gender); + parts.Add(ChooseOneForGrammaticalNumber(result, forms)); + } + } + + private static int GetIndex(RussianGrammaticalNumber number) + { + if (number == RussianGrammaticalNumber.Singular) + return 0; + + if (number == RussianGrammaticalNumber.Paucal) + return 1; + + return 2; + } + + private static string ChooseOneForGrammaticalNumber(int number, string[] forms) + { + return forms[GetIndex(RussianGrammaticalNumberDetector.Detect(number))]; + } + + private static string GetEndingForGender(GrammaticalGender gender, int number) + { + switch (gender) + { + case GrammaticalGender.Masculine: + if (number == 3) + return "ій"; + return "ий"; + case GrammaticalGender.Feminine: + if (number == 3) + return "я"; + return "а"; + case GrammaticalGender.Neuter: + if (number == 3) + return "є"; + return "е"; + default: + throw new ArgumentOutOfRangeException("gender"); + } + } + } +} diff --git a/src/Humanizer/Localisation/Ordinalizers/UkrainianOrdinalizer.cs b/src/Humanizer/Localisation/Ordinalizers/UkrainianOrdinalizer.cs new file mode 100644 index 000000000..64990ea9c --- /dev/null +++ b/src/Humanizer/Localisation/Ordinalizers/UkrainianOrdinalizer.cs @@ -0,0 +1,30 @@ +namespace Humanizer.Localisation.Ordinalizers +{ + internal class UkrainianOrdinalizer : DefaultOrdinalizer + { + public override string Convert(int number, string numberString) + { + return Convert(number, numberString, GrammaticalGender.Masculine); + } + + public override string Convert(int number, string numberString, GrammaticalGender gender) + { + + if (gender == GrammaticalGender.Masculine) + return numberString + "-й"; + + if (gender == GrammaticalGender.Feminine) + { + if (number % 10 == 3) + return numberString + "-я"; + return numberString + "-а"; + } + + if (gender == GrammaticalGender.Neuter) + if (number % 10 == 3) + return numberString + "-є"; + + return numberString + "-е"; + } + } +} diff --git a/src/Humanizer/Properties/Resources.uk.resx b/src/Humanizer/Properties/Resources.uk.resx new file mode 100644 index 000000000..dc3481968 --- /dev/null +++ b/src/Humanizer/Properties/Resources.uk.resx @@ -0,0 +1,342 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + секунду тому + + + хвилину тому + + + годину тому + + + вчора + + + місяць тому + + + рік тому + + + {0} день тому + + + {0} годин тому + + + {0} хвилин тому + + + {0} місяців тому + + + {0} секунд тому + + + {0} років тому + + + {0} днів тому + + + {0} дні тому + + + {0} години тому + + + {0} годину тому + + + {0} хвилини тому + + + {0} хвилину тому + + + {0} місяці тому + + + {0} місяць тому + + + {0} секунди тому + + + {0} секунду тому + + + {0} роки тому + + + {0} рік тому + + + зараз + + + {0} днів + + + {0} дні + + + {0} день + + + {0} годин + + + {0} години + + + {0} година + + + {0} мілісекунд + + + {0} мілісекунди + + + {0} мілісекунда + + + {0} хвилин + + + {0} хвилини + + + {0} хвилина + + + {0} секунд + + + {0} секунди + + + {0} секунда + + + {0} тижнів + + + {0} тижні + + + {0} тиждень + + + один день + + + одна година + + + одна мілісекунда + + + одна хвилина + + + одна секунда + + + один тиждень + + + без часу + + + через {0} днів + + + через {0} дні + + + через {0} день + + + через {0} годин + + + через {0} години + + + через {0} годину + + + через {0} хвилин + + + через {0} хвилини + + + через {0} хвилину + + + через {0} місяців + + + через {0} місяці + + + через {0} місяць + + + через {0} секунд + + + через {0} секунди + + + через {0} секунду + + + через {0} років + + + через {0} роки + + + через {0} рік + + + завтра + + + через годину + + + через хвилину + + + через місяць + + + через секунду + + + через рік + +