diff --git a/NuSpecs/Humanizer.Core.ca.nuspec b/NuSpecs/Humanizer.Core.ca.nuspec new file mode 100644 index 000000000..3c74c9757 --- /dev/null +++ b/NuSpecs/Humanizer.Core.ca.nuspec @@ -0,0 +1,27 @@ + + + + Humanizer.Core.ca + $version$ + Humanizer Locale (ca) + Mehdi Khalili, Claire Novotny + https://github.com/Humanizr/Humanizer + logo.png + false + Humanizer Locale Catalan (ca) + Copyright (c) .NET Foundation and Contributors + MIT + + ca + + + + + + + + + + + + \ No newline at end of file diff --git a/NuSpecs/Humanizer.nuspec b/NuSpecs/Humanizer.nuspec index 3e3061840..08bc9e461 100644 --- a/NuSpecs/Humanizer.nuspec +++ b/NuSpecs/Humanizer.nuspec @@ -18,6 +18,7 @@ + @@ -38,8 +39,8 @@ - + diff --git a/src/Humanizer.Tests/Localisation/ca/DateHumanizeTests.cs b/src/Humanizer.Tests/Localisation/ca/DateHumanizeTests.cs new file mode 100644 index 000000000..70cf2b404 --- /dev/null +++ b/src/Humanizer.Tests/Localisation/ca/DateHumanizeTests.cs @@ -0,0 +1,78 @@ +namespace ca; + +[UseCulture("ca")] +public class DateHumanizeTests +{ + [Theory] + [InlineData(1, "fa un segon")] + [InlineData(2, "fa 2 segons")] + public void SecondsAgo(int seconds, string expected) => + DateHumanize.Verify(expected, seconds, TimeUnit.Second, Tense.Past); + + [Theory] + [InlineData(1, "d'aquí un segon")] + [InlineData(2, "d'aquí 2 segons")] + public void SecondsFromNow(int seconds, string expected) => + DateHumanize.Verify(expected, seconds, TimeUnit.Second, Tense.Future); + + [Theory] + [InlineData(1, "fa un minut")] + [InlineData(2, "fa 2 minuts")] + [InlineData(60, "fa una hora")] + public void MinutesAgo(int minutes, string expected) => + DateHumanize.Verify(expected, minutes, TimeUnit.Minute, Tense.Past); + + [Theory] + [InlineData(1, "d'aquí un minut")] + [InlineData(2, "d'aquí 2 minuts")] + public void MinutesFromNow(int minutes, string expected) => + DateHumanize.Verify(expected, minutes, TimeUnit.Minute, Tense.Future); + + [Theory] + [InlineData(1, "fa una hora")] + [InlineData(2, "fa 2 hores")] + public void HoursAgo(int hours, string expected) => + DateHumanize.Verify(expected, hours, TimeUnit.Hour, Tense.Past); + + [Theory] + [InlineData(1, "d'aquí una hora")] + [InlineData(2, "d'aquí 2 hores")] + public void HoursFromNow(int hours, string expected) => + DateHumanize.Verify(expected, hours, TimeUnit.Hour, Tense.Future); + + [Theory] + [InlineData(1, "ahir")] + [InlineData(2, "fa 2 dies")] + public void DaysAgo(int days, string expected) => + DateHumanize.Verify(expected, days, TimeUnit.Day, Tense.Past); + + [Theory] + [InlineData(1, "demà")] + [InlineData(2, "d'aquí 2 dies")] + public void DaysFromNow(int days, string expected) => + DateHumanize.Verify(expected, days, TimeUnit.Day, Tense.Future); + + [Theory] + [InlineData(1, "fa un mes")] + [InlineData(2, "fa 2 mesos")] + public void MonthsAgo(int months, string expected) => + DateHumanize.Verify(expected, months, TimeUnit.Month, Tense.Past); + + [Theory] + [InlineData(1, "d'aquí un mes")] + [InlineData(2, "d'aquí 2 mesos")] + public void MonthsFromNow(int months, string expected) => + DateHumanize.Verify(expected, months, TimeUnit.Month, Tense.Future); + + [Theory] + [InlineData(1, "fa un any")] + [InlineData(2, "fa 2 anys")] + public void YearsAgo(int years, string expected) => + DateHumanize.Verify(expected, years, TimeUnit.Year, Tense.Past); + + [Theory] + [InlineData(1, "d'aquí un any")] + [InlineData(2, "d'aquí 2 anys")] + public void YearsFromNow(int years, string expected) => + DateHumanize.Verify(expected, years, TimeUnit.Year, Tense.Future); +} \ No newline at end of file diff --git a/src/Humanizer.Tests/Localisation/ca/DateToOrdinalWordsTests.cs b/src/Humanizer.Tests/Localisation/ca/DateToOrdinalWordsTests.cs new file mode 100644 index 000000000..bdd16c95f --- /dev/null +++ b/src/Humanizer.Tests/Localisation/ca/DateToOrdinalWordsTests.cs @@ -0,0 +1,25 @@ +namespace ca; + +[UseCulture("ca")] +public class DateToOrdinalWordsTests +{ + [Fact] + public void OrdinalizeString() + { + Assert.Equal("25 de gener de 2022", new DateTime(2022, 1, 25).ToOrdinalWords()); + Assert.Equal("29 de febrer de 2020", new DateTime(2020, 2, 29).ToOrdinalWords()); + Assert.Equal("4 de setembre de 2015", new DateTime(2015, 9, 4).ToOrdinalWords()); + Assert.Equal("7 de novembre de 1979", new DateTime(1979, 11, 7).ToOrdinalWords()); + } + +#if NET6_0_OR_GREATER + [Fact] + public void OrdinalizeDateOnlyString() + { + Assert.Equal("25 de gener de 2022", new DateOnly(2022, 1, 25).ToOrdinalWords()); + Assert.Equal("29 de febrer de 2020", new DateOnly(2020, 2, 29).ToOrdinalWords()); + Assert.Equal("4 de setembre de 2015", new DateOnly(2015, 9, 4).ToOrdinalWords()); + Assert.Equal("7 de novembre de 1979", new DateOnly(1979, 11, 7).ToOrdinalWords()); + } +#endif +} \ No newline at end of file diff --git a/src/Humanizer.Tests/Localisation/ca/NumberToWordsFeminineTest.cs b/src/Humanizer.Tests/Localisation/ca/NumberToWordsFeminineTest.cs new file mode 100644 index 000000000..ede159289 --- /dev/null +++ b/src/Humanizer.Tests/Localisation/ca/NumberToWordsFeminineTest.cs @@ -0,0 +1,19 @@ +namespace ca; + +[UseCulture("ca")] +public class NumberToWordsFeminineTests +{ + [Theory] + [InlineData(1, "una")] + [InlineData(21, "vint-i-una")] + [InlineData(31, "trenta-una")] + [InlineData(81, "vuitanta-una")] + [InlineData(500, "cinc-centes")] + [InlineData(701, "set-centes una")] + [InlineData(3500, "tres mil cinc-centes")] + [InlineData(200121, "dues-centes mil cent vint-i-una")] + [InlineData(200000121, "dos-cents milions cent vint-i-una")] + [InlineData(1000001, "un milió una")] + public void ToWords(int number, string expected) => + Assert.Equal(expected, number.ToWords(GrammaticalGender.Feminine)); +} \ No newline at end of file diff --git a/src/Humanizer.Tests/Localisation/ca/NumberToWordsTests.cs b/src/Humanizer.Tests/Localisation/ca/NumberToWordsTests.cs new file mode 100644 index 000000000..8a76d43fa --- /dev/null +++ b/src/Humanizer.Tests/Localisation/ca/NumberToWordsTests.cs @@ -0,0 +1,207 @@ +namespace ca; + +[UseCulture("ca")] + +public class NumberToWordsTests +{ + [Theory] + [InlineData(-1, "menys primer", GrammaticalGender.Neuter)] + [InlineData(0, "zero", GrammaticalGender.Neuter)] + [InlineData(1, "primer", GrammaticalGender.Neuter)] + [InlineData(1, "primer", GrammaticalGender.Masculine)] + [InlineData(1, "primera", GrammaticalGender.Feminine)] + [InlineData(2, "segon", GrammaticalGender.Masculine)] + [InlineData(2, "segona", GrammaticalGender.Feminine)] + [InlineData(3, "tercer", GrammaticalGender.Neuter)] + [InlineData(3, "tercer", GrammaticalGender.Masculine)] + [InlineData(3, "tercera", GrammaticalGender.Feminine)] + [InlineData(4, "quart", GrammaticalGender.Masculine)] + [InlineData(4, "quarta", GrammaticalGender.Feminine)] + [InlineData(5, "cinquè", GrammaticalGender.Masculine)] + [InlineData(5, "cinquena", GrammaticalGender.Feminine)] + [InlineData(6, "sisè", GrammaticalGender.Masculine)] + [InlineData(6, "sisena", GrammaticalGender.Feminine)] + [InlineData(7, "setè", GrammaticalGender.Masculine)] + [InlineData(7, "setena", GrammaticalGender.Feminine)] + [InlineData(8, "vuitè", GrammaticalGender.Masculine)] + [InlineData(8, "vuitena", GrammaticalGender.Feminine)] + [InlineData(9, "novè", GrammaticalGender.Masculine)] + [InlineData(9, "novena", GrammaticalGender.Feminine)] + [InlineData(10, "desè", GrammaticalGender.Masculine)] + [InlineData(10, "desena", GrammaticalGender.Feminine)] + [InlineData(11, "onzè", GrammaticalGender.Masculine)] + [InlineData(11, "onzena", GrammaticalGender.Feminine)] + [InlineData(20, "vintè", GrammaticalGender.Masculine)] + [InlineData(20, "vintena", GrammaticalGender.Feminine)] + [InlineData(22, "vint-i-dosè", GrammaticalGender.Masculine)] + [InlineData(22, "vint-i-dosena", GrammaticalGender.Feminine)] + [InlineData(30, "trentè", GrammaticalGender.Masculine)] + [InlineData(30, "trentena", GrammaticalGender.Feminine)] + [InlineData(34, "trenta-quatrè", GrammaticalGender.Masculine)] + [InlineData(34, "trenta-quatrena", GrammaticalGender.Feminine)] + [InlineData(40, "quarantaè", GrammaticalGender.Masculine)] + [InlineData(40, "quarantaena", GrammaticalGender.Feminine)] + [InlineData(46, "quaranta-sisè", GrammaticalGender.Masculine)] + [InlineData(46, "quaranta-sisena", GrammaticalGender.Feminine)] + [InlineData(50, "cinquantaè", GrammaticalGender.Masculine)] + [InlineData(50, "cinquantaena", GrammaticalGender.Feminine)] + [InlineData(57, "cinquanta-setè", GrammaticalGender.Masculine)] + [InlineData(57, "cinquanta-setena", GrammaticalGender.Feminine)] + [InlineData(60, "seixantè", GrammaticalGender.Masculine)] + [InlineData(60, "seixantena", GrammaticalGender.Feminine)] + [InlineData(69, "seixanta-novè", GrammaticalGender.Masculine)] + [InlineData(69, "seixanta-novena", GrammaticalGender.Feminine)] + [InlineData(70, "setantè", GrammaticalGender.Masculine)] + [InlineData(70, "setantena", GrammaticalGender.Feminine)] + [InlineData(74, "setanta-quatrè", GrammaticalGender.Masculine)] + [InlineData(74, "setanta-quatrena", GrammaticalGender.Feminine)] + [InlineData(80, "vuitantè", GrammaticalGender.Masculine)] + [InlineData(80, "vuitantena", GrammaticalGender.Feminine)] + [InlineData(85, "vuitanta-cinquè", GrammaticalGender.Masculine)] + [InlineData(85, "vuitanta-cinquena", GrammaticalGender.Feminine)] + [InlineData(90, "norantè", GrammaticalGender.Masculine)] + [InlineData(90, "norantena", GrammaticalGender.Feminine)] + [InlineData(99, "noranta-novè", GrammaticalGender.Masculine)] + [InlineData(99, "noranta-novena", GrammaticalGender.Feminine)] + [InlineData(100, "centè", GrammaticalGender.Masculine)] + [InlineData(100, "centena", GrammaticalGender.Feminine)] + [InlineData(101, "cent primer", GrammaticalGender.Masculine)] + [InlineData(101, "cent primera", GrammaticalGender.Feminine)] + [InlineData(110, "cent deu", GrammaticalGender.Masculine)] + [InlineData(111, "cent onzè", GrammaticalGender.Masculine)] + [InlineData(120, "cent vint", GrammaticalGender.Masculine)] + [InlineData(121, "cent vint-i-un", GrammaticalGender.Masculine)] + [InlineData(121, "cent vint-i-una", GrammaticalGender.Feminine)] + [InlineData(200, "dos-cents", GrammaticalGender.Masculine)] + [InlineData(200, "dues-centes", GrammaticalGender.Feminine)] + [InlineData(201, "dos-cents un", GrammaticalGender.Masculine)] + [InlineData(201, "dues-centes una", GrammaticalGender.Feminine)] + [InlineData(221, "dos-cents vint-i-un", GrammaticalGender.Masculine)] + [InlineData(221, "dues-centes vint-i-una", GrammaticalGender.Feminine)] + [InlineData(500, "cinc-cents", GrammaticalGender.Masculine)] + [InlineData(500, "cinc-centes", GrammaticalGender.Feminine)] + [InlineData(701, "set-cents un", GrammaticalGender.Masculine)] + [InlineData(701, "set-centes una", GrammaticalGender.Feminine)] + [InlineData(900, "nou-cents", GrammaticalGender.Masculine)] + [InlineData(900, "nou-centes", GrammaticalGender.Feminine)] + [InlineData(999, "nou-cents noranta-nou", GrammaticalGender.Masculine)] + [InlineData(999, "nou-centes noranta-nou", GrammaticalGender.Feminine)] + [InlineData(1000, "mil", GrammaticalGender.Masculine)] + [InlineData(1001, "mil un", GrammaticalGender.Masculine)] + [InlineData(1001, "mil una", GrammaticalGender.Feminine)] + [InlineData(1021, "mil vint-i-un", GrammaticalGender.Masculine)] + [InlineData(1021, "mil vint-i-una", GrammaticalGender.Feminine)] + [InlineData(1100, "mil cent", GrammaticalGender.Masculine)] + [InlineData(1100, "mil cent", GrammaticalGender.Feminine)] + [InlineData(2000, "dos mil", GrammaticalGender.Masculine)] + [InlineData(2100, "dos mil cent", GrammaticalGender.Masculine)] + [InlineData(3500, "tres mil cinc-cents", GrammaticalGender.Masculine)] + [InlineData(3500, "tres mil cinc-centes", GrammaticalGender.Feminine)] + [InlineData(100000, "cent mil", GrammaticalGender.Masculine)] + [InlineData(100001, "cent mil un", GrammaticalGender.Masculine)] + [InlineData(100001, "cent mil una", GrammaticalGender.Feminine)] + [InlineData(100002, "cent mil dos", GrammaticalGender.Masculine)] + [InlineData(100002, "cent mil dues", GrammaticalGender.Feminine)] + [InlineData(100021, "cent mil vint-i-un", GrammaticalGender.Masculine)] + [InlineData(100021, "cent mil vint-i-una", GrammaticalGender.Feminine)] + [InlineData(200000, "dos-cents mil", GrammaticalGender.Masculine)] + [InlineData(200000, "dues-centes mil", GrammaticalGender.Feminine)] + [InlineData(200121, "dos-cents mil cent vint-i-un", GrammaticalGender.Masculine)] + [InlineData(200121, "dues-centes mil cent vint-i-una", GrammaticalGender.Feminine)] + [InlineData(1000000, "un milió", GrammaticalGender.Masculine)] + [InlineData(1000001, "un milió un", GrammaticalGender.Masculine)] + [InlineData(1000001, "un milió una", GrammaticalGender.Feminine)] + [InlineData(2000000, "dos milions", GrammaticalGender.Masculine)] + [InlineData(2000001, "dos milions un", GrammaticalGender.Masculine)] + [InlineData(2000001, "dos milions una", GrammaticalGender.Feminine)] + [InlineData(200000121, "dos-cents milions cent vint-i-un", GrammaticalGender.Masculine)] + [InlineData(200000121, "dos-cents milions cent vint-i-una", GrammaticalGender.Feminine)] + public void ToOrdinalWords(int number, string words, GrammaticalGender gender) => + Assert.Equal(words, number.ToOrdinalWords(gender)); + + [Theory] + [InlineData(1, WordForm.Abbreviation, GrammaticalGender.Masculine, "1r")] + [InlineData(1, WordForm.Abbreviation, GrammaticalGender.Feminine, "1a")] + [InlineData(2, WordForm.Abbreviation, GrammaticalGender.Masculine, "2n")] + [InlineData(2, WordForm.Abbreviation, GrammaticalGender.Feminine, "2a")] + [InlineData(3, WordForm.Abbreviation, GrammaticalGender.Masculine, "3r")] + [InlineData(3, WordForm.Abbreviation, GrammaticalGender.Feminine, "3a")] + [InlineData(11, WordForm.Abbreviation, GrammaticalGender.Masculine, "11è")] + [InlineData(11, WordForm.Abbreviation, GrammaticalGender.Feminine, "11a")] + [InlineData(22, WordForm.Abbreviation, GrammaticalGender.Masculine, "22n")] + [InlineData(22, WordForm.Abbreviation, GrammaticalGender.Feminine, "22a")] + [InlineData(31, WordForm.Abbreviation, GrammaticalGender.Masculine, "31r")] + [InlineData(31, WordForm.Abbreviation, GrammaticalGender.Feminine, "31a")] + [InlineData(100, WordForm.Abbreviation, GrammaticalGender.Masculine, "100è")] + [InlineData(100, WordForm.Abbreviation, GrammaticalGender.Feminine, "100a")] + [InlineData(101, WordForm.Abbreviation, GrammaticalGender.Masculine, "101r")] + [InlineData(101, WordForm.Abbreviation, GrammaticalGender.Feminine, "101a")] + [InlineData(999, WordForm.Abbreviation, GrammaticalGender.Masculine, "999è")] + [InlineData(999, WordForm.Abbreviation, GrammaticalGender.Feminine, "999a")] + public void ToOrdinalWordsWithWordForm(int number, WordForm wordForm, GrammaticalGender gender, string expected) => + Assert.Equal(expected, number.ToOrdinalWords(gender, wordForm)); + + [Theory] + [InlineData(1, WordForm.Abbreviation, GrammaticalGender.Masculine, "1r")] + [InlineData(1, WordForm.Abbreviation, GrammaticalGender.Feminine, "1a")] + [InlineData(2, WordForm.Abbreviation, GrammaticalGender.Masculine, "2n")] + [InlineData(2, WordForm.Abbreviation, GrammaticalGender.Feminine, "2a")] + [InlineData(3, WordForm.Abbreviation, GrammaticalGender.Masculine, "3r")] + [InlineData(3, WordForm.Abbreviation, GrammaticalGender.Feminine, "3a")] + [InlineData(11, WordForm.Abbreviation, GrammaticalGender.Masculine, "11è")] + [InlineData(11, WordForm.Abbreviation, GrammaticalGender.Feminine, "11a")] + [InlineData(22, WordForm.Abbreviation, GrammaticalGender.Masculine, "22n")] + [InlineData(22, WordForm.Abbreviation, GrammaticalGender.Feminine, "22a")] + [InlineData(31, WordForm.Abbreviation, GrammaticalGender.Masculine, "31r")] + [InlineData(31, WordForm.Abbreviation, GrammaticalGender.Feminine, "31a")] + [InlineData(100, WordForm.Abbreviation, GrammaticalGender.Masculine, "100è")] + [InlineData(100, WordForm.Abbreviation, GrammaticalGender.Feminine, "100a")] + [InlineData(101, WordForm.Abbreviation, GrammaticalGender.Masculine, "101r")] + [InlineData(101, WordForm.Abbreviation, GrammaticalGender.Feminine, "101a")] + [InlineData(999, WordForm.Abbreviation, GrammaticalGender.Masculine, "999è")] + [InlineData(999, WordForm.Abbreviation, GrammaticalGender.Feminine, "999a")] + public void ToOrdinalWordsWithWordFormAndGender(int number, WordForm wordForm, GrammaticalGender gender, string expected) => + Assert.Equal(expected, number.ToOrdinalWords(gender, wordForm)); + + [Theory] + [InlineData(0, "zero vegades")] + [InlineData(2, "doble")] + [InlineData(100, "cent vegades")] + public void ToTuple(int number, string expected) + => Assert.Equal(expected, number.ToTuple()); + + [Theory] + [InlineData(0, "zero", GrammaticalGender.Masculine)] + [InlineData(1, "un", GrammaticalGender.Masculine)] + [InlineData(1, "una", GrammaticalGender.Feminine)] + [InlineData(2, "dos", GrammaticalGender.Masculine)] + [InlineData(2, "dues", GrammaticalGender.Feminine)] + [InlineData(3, "tres", GrammaticalGender.Masculine)] + [InlineData(3, "tres", GrammaticalGender.Feminine)] + [InlineData(11, "onze", GrammaticalGender.Masculine)] + [InlineData(21, "vint-i-u", GrammaticalGender.Masculine)] + [InlineData(21, "vint-i-una", GrammaticalGender.Feminine)] + [InlineData(31, "trenta-u", GrammaticalGender.Masculine)] + [InlineData(31, "trenta-una", GrammaticalGender.Feminine)] + [InlineData(81, "vuitanta-u", GrammaticalGender.Masculine)] + [InlineData(81, "vuitanta-una", GrammaticalGender.Feminine)] + [InlineData(101, "cent u", GrammaticalGender.Masculine)] + [InlineData(101, "cent una", GrammaticalGender.Feminine)] + [InlineData(500, "cinc-cents", GrammaticalGender.Masculine)] + [InlineData(500, "cinc-centes", GrammaticalGender.Feminine)] + [InlineData(701, "set-cents u", GrammaticalGender.Masculine)] + [InlineData(701, "set-centes una", GrammaticalGender.Feminine)] + [InlineData(3500, "tres mil cinc-cents", GrammaticalGender.Masculine)] + [InlineData(3500, "tres mil cinc-centes", GrammaticalGender.Feminine)] + [InlineData(200121, "dos-cents mil cent vint-i-u", GrammaticalGender.Masculine)] + [InlineData(200121, "dues-centes mil cent vint-i-una", GrammaticalGender.Feminine)] + [InlineData(200000121, "dos-cents milions cent vint-i-u", GrammaticalGender.Masculine)] + [InlineData(200000121, "dos-cents milions cent vint-i-una", GrammaticalGender.Feminine)] + [InlineData(1000001, "un milió u", GrammaticalGender.Masculine)] + [InlineData(1000001, "un milió una", GrammaticalGender.Feminine)] + [InlineData(-15, "menys quinze", GrammaticalGender.Masculine)] + [InlineData(-123, "menys cent vint-i-tres", GrammaticalGender.Masculine)] + public void ToWords(long number, string expected, GrammaticalGender gender = GrammaticalGender.Masculine) => + Assert.Equal(expected, number.ToWords(gender)); + +} \ No newline at end of file diff --git a/src/Humanizer.Tests/Localisation/ca/OrdinalizeTests.cs b/src/Humanizer.Tests/Localisation/ca/OrdinalizeTests.cs new file mode 100644 index 000000000..410f2e193 --- /dev/null +++ b/src/Humanizer.Tests/Localisation/ca/OrdinalizeTests.cs @@ -0,0 +1,114 @@ +namespace ca; + +[UseCulture("ca")] +public class OrdinalizeTests +{ + [Theory] + [InlineData(1, "1r")] + [InlineData(3, "3r")] + public void OrdinalizeDefaultGender(int number, string ordinalized) => + Assert.Equal(number.Ordinalize(), ordinalized); + + [Theory] + [InlineData(-1, "1r")] + [InlineData(int.MinValue, "0")] + public void OrdinalizeZeroOrNegativeNumber(int number, string ordinalized) => + Assert.Equal(number.Ordinalize(), ordinalized); + + [Theory] + [InlineData(1, WordForm.Abbreviation, "1r")] + [InlineData(1, WordForm.Normal, "1r")] + [InlineData(2, WordForm.Abbreviation, "2n")] + [InlineData(2, WordForm.Normal, "2n")] + [InlineData(3, WordForm.Abbreviation, "3r")] + [InlineData(3, WordForm.Normal, "3r")] + [InlineData(21, WordForm.Abbreviation, "21r")] + [InlineData(21, WordForm.Normal, "21r")] + public void OrdinalizeWithWordForm(int number, WordForm wordForm, string expected) + { + Assert.Equal(expected, number.Ordinalize(wordForm)); + Assert.Equal(expected, number.ToString(CultureInfo.CurrentUICulture).Ordinalize(wordForm)); + } + + [Theory] + [InlineData(1, GrammaticalGender.Masculine, WordForm.Abbreviation, "1r")] + [InlineData(1, GrammaticalGender.Masculine, WordForm.Normal, "1r")] + [InlineData(1, GrammaticalGender.Feminine, WordForm.Abbreviation, "1a")] + [InlineData(1, GrammaticalGender.Feminine, WordForm.Normal, "1a")] + [InlineData(1, GrammaticalGender.Neuter, WordForm.Abbreviation, "1r")] + [InlineData(1, GrammaticalGender.Neuter, WordForm.Normal, "1r")] + public void OrdinalizeWithWordFormAndGender(int number, GrammaticalGender gender, WordForm wordForm, string expected) + { + Assert.Equal(expected, number.Ordinalize(gender, wordForm)); + Assert.Equal(expected, number.ToString(CultureInfo.CurrentUICulture).Ordinalize(gender, wordForm)); + } + + [Theory] + [InlineData("1", "1r")] + [InlineData("2", "2n")] + [InlineData("3", "3r")] + [InlineData("4", "4t")] + [InlineData("5", "5è")] + [InlineData("6", "6è")] + [InlineData("23", "23r")] + [InlineData("100", "100è")] + [InlineData("101", "101r")] + [InlineData("102", "102n")] + [InlineData("103", "103r")] + [InlineData("1001", "1001r")] + public void OrdinalizeString(string number, string ordinalized) => + Assert.Equal(number.Ordinalize(GrammaticalGender.Masculine), ordinalized); + + [Theory] + [InlineData("0", "0")] + [InlineData("1", "1a")] + [InlineData("2", "2a")] + [InlineData("3", "3a")] + [InlineData("4", "4a")] + [InlineData("5", "5a")] + [InlineData("6", "6a")] + [InlineData("23", "23a")] + [InlineData("100", "100a")] + [InlineData("101", "101a")] + [InlineData("102", "102a")] + [InlineData("103", "103a")] + [InlineData("1001", "1001a")] + public void OrdinalizeStringFeminine(string number, string ordinalized) => + Assert.Equal(number.Ordinalize(GrammaticalGender.Feminine), ordinalized); + + [Theory] + [InlineData(0, "0")] + [InlineData(1, "1r")] + [InlineData(2, "2n")] + [InlineData(3, "3r")] + [InlineData(4, "4t")] + [InlineData(5, "5è")] + [InlineData(6, "6è")] + [InlineData(10, "10è")] + [InlineData(23, "23r")] + [InlineData(100, "100è")] + [InlineData(101, "101r")] + [InlineData(102, "102n")] + [InlineData(103, "103r")] + [InlineData(1001, "1001r")] + public void OrdinalizeNumber(int number, string ordinalized) => + Assert.Equal(number.Ordinalize(GrammaticalGender.Masculine), ordinalized); + + [Theory] + [InlineData(0, "0")] + [InlineData(1, "1a")] + [InlineData(2, "2a")] + [InlineData(3, "3a")] + [InlineData(4, "4a")] + [InlineData(5, "5a")] + [InlineData(6, "6a")] + [InlineData(10, "10a")] + [InlineData(23, "23a")] + [InlineData(100, "100a")] + [InlineData(101, "101a")] + [InlineData(102, "102a")] + [InlineData(103, "103a")] + [InlineData(1001, "1001a")] + public void OrdinalizeNumberFeminine(int number, string ordinalized) => + Assert.Equal(number.Ordinalize(GrammaticalGender.Feminine), ordinalized); +} \ No newline at end of file diff --git a/src/Humanizer.Tests/Localisation/ca/TimeSpanHumanizeTests.cs b/src/Humanizer.Tests/Localisation/ca/TimeSpanHumanizeTests.cs new file mode 100644 index 000000000..6e3317abf --- /dev/null +++ b/src/Humanizer.Tests/Localisation/ca/TimeSpanHumanizeTests.cs @@ -0,0 +1,502 @@ +namespace ca; + +[UseCulture("ca")] +public class TimeSpanHumanizeTests +{ + [Theory] + [Trait("Translation", "Google")] + [InlineData(366, "1 any")] + [InlineData(731, "2 anys")] + [InlineData(1096, "3 anys")] + [InlineData(4018, "11 anys")] + public void Years(int days, string expected) => + Assert.Equal(expected, TimeSpan.FromDays(days).Humanize(maxUnit: TimeUnit.Year)); + + [Theory] + [Trait("Translation", "Google")] + [InlineData(31, "1 mes")] + [InlineData(61, "2 mesos")] + [InlineData(92, "3 mesos")] + [InlineData(335, "11 mesos")] + public void Months(int days, string expected) => + Assert.Equal(expected, TimeSpan.FromDays(days).Humanize(maxUnit: TimeUnit.Year)); + + [Fact] + public void TwoWeeks() => + Assert.Equal("2 setmanes", TimeSpan.FromDays(14).Humanize()); + + [Fact] + public void OneWeek() => + Assert.Equal("1 setmana", TimeSpan.FromDays(7).Humanize()); + + [Fact] + public void SixDays() => + Assert.Equal("6 dies", TimeSpan.FromDays(6).Humanize()); + + [Fact] + public void TwoDays() => + Assert.Equal("2 dies", TimeSpan.FromDays(2).Humanize()); + + [Fact] + public void OneDay() => + Assert.Equal("1 dia", TimeSpan.FromDays(1).Humanize()); + + [Fact] + public void TwoHours() => + Assert.Equal("2 hores", TimeSpan.FromHours(2).Humanize()); + + [Fact] + public void OneHour() => + Assert.Equal("1 hora", TimeSpan.FromHours(1).Humanize()); + + [Fact] + public void TwoMinutes() => + Assert.Equal("2 minuts", TimeSpan.FromMinutes(2).Humanize()); + + [Fact] + public void OneMinute() => + Assert.Equal("1 minut", TimeSpan.FromMinutes(1).Humanize()); + + [Fact] + public void TwoSeconds() => + Assert.Equal("2 segons", TimeSpan.FromSeconds(2).Humanize()); + + [Fact] + public void OneSecond() => + Assert.Equal("1 segon", TimeSpan.FromSeconds(1).Humanize()); + + [Fact] + public void TwoMilliseconds() => + Assert.Equal("2 mil·lisegons", TimeSpan.FromMilliseconds(2).Humanize()); + + [Fact] + public void OneMillisecond() => + Assert.Equal("1 mil·lisegon", TimeSpan.FromMilliseconds(1).Humanize()); + + [Theory] + [InlineData(0, 0, 1, 1, 2, "un minut, un segon")] + [InlineData(0, 0, 2, 2, 2, "dos minuts, dos segons")] + [InlineData(1, 2, 3, 4, 4, "un dia, dues hores, tres minuts, quatre segons")] + public void ComplexTimeSpan(int days, int hours, int minutes, int seconds, int precision, string expected) + { + var timeSpan = new TimeSpan(days, hours, minutes, seconds); + Assert.Equal(expected, timeSpan.Humanize(precision, toWords: true)); + } + + [Fact] + public void NoTime() => + // This one doesn't make a lot of sense but ... w/e + Assert.Equal("0 mil·lisegons", TimeSpan.Zero.Humanize()); + + [Fact] + public void NoTimeToWords() => + // This one doesn't make a lot of sense but ... w/e + Assert.Equal("res", TimeSpan.Zero.Humanize(toWords: true)); + + [Fact] + public void AllTimeSpansMustBeUniqueForASequenceOfDays() + { + var culture = new CultureInfo("ca"); + var qry = from i in Enumerable.Range(0, 100000) + let ts = TimeSpan.FromDays(i) + let text = ts.Humanize(precision: 3, culture: culture, maxUnit: TimeUnit.Year) + select text; + var grouping = from t in qry + group t by t into g + select new { g.Key, Count = g.Count() }; + var allUnique = grouping.All(g => g.Count == 1); + Assert.True(allUnique); + } + + [Theory] + [InlineData(365, "11 mesos, 30 dies")] + [InlineData(365 + 1, "1 any")] + [InlineData(365 + 365, "1 any, 11 mesos, 29 dies")] + [InlineData(365 + 365 + 1, "2 anys")] + [InlineData(365 + 365 + 365, "2 anys, 11 mesos, 29 dies")] + [InlineData(365 + 365 + 365 + 1, "3 anys")] + [InlineData(365 + 365 + 365 + 365, "3 anys, 11 mesos, 29 dies")] + [InlineData(365 + 365 + 365 + 365 + 1, "4 anys")] + [InlineData(365 + 365 + 365 + 365 + 366, "4 anys, 11 mesos, 30 dies")] + [InlineData(365 + 365 + 365 + 365 + 366 + 1, "5 anys")] + public void Year(int days, string expected) + { + var actual = TimeSpan.FromDays(days).Humanize(precision: 7, maxUnit: TimeUnit.Year); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(30, "4 setmanes, 2 dies")] + [InlineData(30 + 1, "1 mes")] + [InlineData(30 + 30, "1 mes, 29 dies")] + [InlineData(30 + 30 + 1, "2 mesos")] + [InlineData(30 + 30 + 31, "2 mesos, 30 dies")] + [InlineData(30 + 30 + 31 + 1, "3 mesos")] + [InlineData(30 + 30 + 31 + 30, "3 mesos, 29 dies")] + [InlineData(30 + 30 + 31 + 30 + 1, "4 mesos")] + [InlineData(30 + 30 + 31 + 30 + 31, "4 mesos, 30 dies")] + [InlineData(30 + 30 + 31 + 30 + 31 + 1, "5 mesos")] + [InlineData(365, "11 mesos, 30 dies")] + [InlineData(366, "1 any")] + public void Month(int days, string expected) + { + var actual = TimeSpan.FromDays(days).Humanize(precision: 7, maxUnit: TimeUnit.Year); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(14, "2 setmanes")] + [InlineData(7, "1 setmana")] + [InlineData(-14, "2 setmanes")] + [InlineData(-7, "1 setmana")] + [InlineData(730, "104 setmanes")] + public void Weeks(int days, string expected) + { + var actual = TimeSpan.FromDays(days).Humanize(); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(6, "6 dies")] + [InlineData(2, "2 dies")] + [InlineData(1, "1 dia")] + [InlineData(-6, "6 dies")] + [InlineData(-2, "2 dies")] + [InlineData(-1, "1 dia")] + public void Days(int days, string expected) + { + var actual = TimeSpan.FromDays(days).Humanize(); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(2, "2 hores")] + [InlineData(1, "1 hora")] + [InlineData(-2, "2 hores")] + [InlineData(-1, "1 hora")] + public void Hours(int hours, string expected) + { + var actual = TimeSpan.FromHours(hours).Humanize(); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(2, "2 minuts")] + [InlineData(1, "1 minut")] + [InlineData(-2, "2 minuts")] + [InlineData(-1, "1 minut")] + public void Minutes(int minutes, string expected) + { + var actual = TimeSpan.FromMinutes(minutes).Humanize(); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(135, "2 minuts")] + [InlineData(60, "1 minut")] + [InlineData(2, "2 segons")] + [InlineData(1, "1 segon")] + [InlineData(-135, "2 minuts")] + [InlineData(-60, "1 minut")] + [InlineData(-2, "2 segons")] + [InlineData(-1, "1 segon")] + public void Seconds(int seconds, string expected) + { + var actual = TimeSpan.FromSeconds(seconds).Humanize(); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(2500, "2 segons")] + [InlineData(1400, "1 segon")] + [InlineData(2, "2 mil·lisegons")] + [InlineData(1, "1 mil·lisegon")] + [InlineData(-2500, "2 segons")] + [InlineData(-1400, "1 segon")] + [InlineData(-2, "2 mil·lisegons")] + [InlineData(-1, "1 mil·lisegon")] + public void Milliseconds(int ms, string expected) + { + var actual = TimeSpan.FromMilliseconds(ms).Humanize(); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData((long)366 * 24 * 60 * 60 * 1000, "12 mesos", TimeUnit.Month)] + [InlineData((long)6 * 7 * 24 * 60 * 60 * 1000, "6 setmanes", TimeUnit.Week)] + [InlineData(7 * 24 * 60 * 60 * 1000, "7 dies", TimeUnit.Day)] + [InlineData(24 * 60 * 60 * 1000, "24 hores", TimeUnit.Hour)] + [InlineData(60 * 60 * 1000, "60 minuts", TimeUnit.Minute)] + [InlineData(60 * 1000, "60 segons", TimeUnit.Second)] + [InlineData(1000, "1000 mil·lisegons", TimeUnit.Millisecond)] + public void TimeSpanWithMaxTimeUnit(long ms, string expected, TimeUnit maxUnit) + { + var actual = TimeSpan.FromMilliseconds(ms).Humanize(maxUnit: maxUnit); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(10, "10 mil·lisegons", TimeUnit.Millisecond)] + [InlineData(10, "res", TimeUnit.Second, true)] + [InlineData(10, "res", TimeUnit.Minute, true)] + [InlineData(10, "res", TimeUnit.Hour, true)] + [InlineData(10, "res", TimeUnit.Day, true)] + [InlineData(10, "res", TimeUnit.Week, true)] + [InlineData(10, "0 segons", TimeUnit.Second)] + [InlineData(10, "0 minuts", TimeUnit.Minute)] + [InlineData(10, "0 hores", TimeUnit.Hour)] + [InlineData(10, "0 dies", TimeUnit.Day)] + [InlineData(10, "0 setmanes", TimeUnit.Week)] + [InlineData(2500, "2 segons, 500 mil·lisegons", TimeUnit.Millisecond)] + [InlineData(2500, "2 segons", TimeUnit.Second)] + [InlineData(2500, "res", TimeUnit.Minute, true)] + [InlineData(2500, "res", TimeUnit.Hour, true)] + [InlineData(2500, "res", TimeUnit.Day, true)] + [InlineData(2500, "res", TimeUnit.Week, true)] + [InlineData(2500, "0 minuts", TimeUnit.Minute)] + [InlineData(2500, "0 hores", TimeUnit.Hour)] + [InlineData(2500, "0 dies", TimeUnit.Day)] + [InlineData(2500, "0 setmanes", TimeUnit.Week)] + [InlineData(122500, "2 minuts, 2 segons, 500 mil·lisegons", TimeUnit.Millisecond)] + [InlineData(122500, "2 minuts, 2 segons", TimeUnit.Second)] + [InlineData(122500, "2 minuts", TimeUnit.Minute)] + [InlineData(122500, "res", TimeUnit.Hour, true)] + [InlineData(122500, "res", TimeUnit.Day, true)] + [InlineData(122500, "res", TimeUnit.Week, true)] + [InlineData(122500, "0 hores", TimeUnit.Hour)] + [InlineData(122500, "0 dies", TimeUnit.Day)] + [InlineData(122500, "0 setmanes", TimeUnit.Week)] + [InlineData(3722500, "1 hora, 2 minuts, 2 segons, 500 mil·lisegons", TimeUnit.Millisecond)] + [InlineData(3722500, "1 hora, 2 minuts, 2 segons", TimeUnit.Second)] + [InlineData(3722500, "1 hora, 2 minuts", TimeUnit.Minute)] + [InlineData(3722500, "1 hora", TimeUnit.Hour)] + [InlineData(3722500, "res", TimeUnit.Day, true)] + [InlineData(3722500, "res", TimeUnit.Week, true)] + [InlineData(3722500, "0 dies", TimeUnit.Day)] + [InlineData(3722500, "0 setmanes", TimeUnit.Week)] + [InlineData(90122500, "1 dia, 1 hora, 2 minuts, 2 segons, 500 mil·lisegons", TimeUnit.Millisecond)] + [InlineData(90122500, "1 dia, 1 hora, 2 minuts, 2 segons", TimeUnit.Second)] + [InlineData(90122500, "1 dia, 1 hora, 2 minuts", TimeUnit.Minute)] + [InlineData(90122500, "1 dia, 1 hora", TimeUnit.Hour)] + [InlineData(90122500, "1 dia", TimeUnit.Day)] + [InlineData(90122500, "res", TimeUnit.Week, true)] + [InlineData(90122500, "0 setmanes", TimeUnit.Week)] + [InlineData(694922500, "1 setmana, 1 dia, 1 hora, 2 minuts, 2 segons, 500 mil·lisegons", TimeUnit.Millisecond)] + [InlineData(694922500, "1 setmana, 1 dia, 1 hora, 2 minuts, 2 segons", TimeUnit.Second)] + [InlineData(694922500, "1 setmana, 1 dia, 1 hora, 2 minuts", TimeUnit.Minute)] + [InlineData(694922500, "1 setmana, 1 dia, 1 hora", TimeUnit.Hour)] + [InlineData(694922500, "1 setmana, 1 dia", TimeUnit.Day)] + [InlineData(694922500, "1 setmana", TimeUnit.Week)] + [InlineData(2768462500, "1 mes, 1 dia, 1 hora, 1 minut, 2 segons, 500 mil·lisegons", TimeUnit.Millisecond)] + [InlineData(2768462500, "1 mes, 1 dia, 1 hora, 1 minut, 2 segons", TimeUnit.Second)] + [InlineData(2768462500, "1 mes, 1 dia, 1 hora, 1 minut", TimeUnit.Minute)] + [InlineData(2768462500, "1 mes, 1 dia, 1 hora", TimeUnit.Hour)] + [InlineData(2768462500, "1 mes, 1 dia", TimeUnit.Day)] + [InlineData(2768462500, "1 mes", TimeUnit.Week)] + [InlineData(2768462500, "1 mes", TimeUnit.Month)] + [InlineData(2768462500, "res", TimeUnit.Year, true)] + [InlineData(2768462500, "0 anys", TimeUnit.Year)] + [InlineData(34390862500, "1 any, 1 mes, 2 dies, 1 hora, 1 minut, 2 segons, 500 mil·lisegons", TimeUnit.Millisecond)] + [InlineData(34390862500, "1 any, 1 mes, 2 dies, 1 hora, 1 minut, 2 segons", TimeUnit.Second)] + [InlineData(34390862500, "1 any, 1 mes, 2 dies, 1 hora, 1 minut", TimeUnit.Minute)] + [InlineData(34390862500, "1 any, 1 mes, 2 dies, 1 hora", TimeUnit.Hour)] + [InlineData(34390862500, "1 any, 1 mes, 2 dies", TimeUnit.Day)] + [InlineData(34390862500, "1 any, 1 mes", TimeUnit.Week)] + [InlineData(34390862500, "1 any, 1 mes", TimeUnit.Month)] + [InlineData(34390862500, "1 any", TimeUnit.Year)] + public void TimeSpanWithMinTimeUnit(long ms, string expected, TimeUnit minUnit, bool toWords = false) + { + var actual = TimeSpan.FromMilliseconds(ms).Humanize(minUnit: minUnit, precision: 7, maxUnit: TimeUnit.Year, toWords: toWords); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(0, 3, "res", true)] + [InlineData(0, 2, "res", true)] + [InlineData(0, 3, "0 mil·lisegons")] + [InlineData(0, 2, "0 mil·lisegons")] + [InlineData(10, 2, "10 mil·lisegons")] + [InlineData(1400, 2, "1 segon, 400 mil·lisegons")] + [InlineData(2500, 2, "2 segons, 500 mil·lisegons")] + [InlineData(120000, 2, "2 minuts")] + [InlineData(62000, 2, "1 minut, 2 segons")] + [InlineData(62020, 2, "1 minut, 2 segons")] + [InlineData(62020, 3, "1 minut, 2 segons, 20 mil·lisegons")] + [InlineData(3600020, 4, "1 hora, 20 mil·lisegons")] + [InlineData(3600020, 3, "1 hora, 20 mil·lisegons")] + [InlineData(3600020, 2, "1 hora, 20 mil·lisegons")] + [InlineData(3600020, 1, "1 hora")] + [InlineData(3603001, 2, "1 hora, 3 segons")] + [InlineData(3603001, 3, "1 hora, 3 segons, 1 mil·lisegon")] + [InlineData(86400000, 3, "1 dia")] + [InlineData(86400000, 2, "1 dia")] + [InlineData(86400000, 1, "1 dia")] + [InlineData(86401000, 1, "1 dia")] + [InlineData(86401000, 2, "1 dia, 1 segon")] + [InlineData(86401200, 2, "1 dia, 1 segon")] + [InlineData(86401200, 3, "1 dia, 1 segon, 200 mil·lisegons")] + [InlineData(1296000000, 1, "2 setmanes")] + [InlineData(1296000000, 2, "2 setmanes, 1 dia")] + [InlineData(1299600000, 2, "2 setmanes, 1 dia")] + [InlineData(1299600000, 3, "2 setmanes, 1 dia, 1 hora")] + [InlineData(1299630020, 3, "2 setmanes, 1 dia, 1 hora")] + [InlineData(1299630020, 4, "2 setmanes, 1 dia, 1 hora, 30 segons")] + [InlineData(1299630020, 5, "2 setmanes, 1 dia, 1 hora, 30 segons, 20 mil·lisegons")] + [InlineData(2768462500, 6, "1 mes, 1 dia, 1 hora, 1 minut, 2 segons, 500 mil·lisegons")] + [InlineData(2768462500, 5, "1 mes, 1 dia, 1 hora, 1 minut, 2 segons")] + [InlineData(2768462500, 4, "1 mes, 1 dia, 1 hora, 1 minut")] + [InlineData(2768462500, 3, "1 mes, 1 dia, 1 hora")] + [InlineData(2768462500, 2, "1 mes, 1 dia")] + [InlineData(2768462500, 1, "1 mes")] + [InlineData(34390862500, 7, "1 any, 1 mes, 2 dies, 1 hora, 1 minut, 2 segons, 500 mil·lisegons")] + [InlineData(34390862500, 6, "1 any, 1 mes, 2 dies, 1 hora, 1 minut, 2 segons")] + [InlineData(34390862500, 5, "1 any, 1 mes, 2 dies, 1 hora, 1 minut")] + [InlineData(34390862500, 4, "1 any, 1 mes, 2 dies, 1 hora")] + [InlineData(34390862500, 3, "1 any, 1 mes, 2 dies")] + [InlineData(34390862500, 2, "1 any, 1 mes")] + [InlineData(34390862500, 1, "1 any")] + public void TimeSpanWithPrecision(long milliseconds, int precision, string expected, bool toWords = false) + { + var actual = TimeSpan.FromMilliseconds(milliseconds).Humanize(precision, maxUnit: TimeUnit.Year, toWords: toWords); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(3 * 7 + 4, 2, "3 setmanes, 4 dies")] + [InlineData(6 * 7 + 3, 2, "6 setmanes, 3 dies")] + [InlineData(72 * 7 + 6, 2, "72 setmanes, 6 dies")] + public void DaysWithPrecision(int days, int precision, string expected) + { + var actual = TimeSpan.FromDays(days).Humanize(precision: precision); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(50)] + [InlineData(52)] + public void TimeSpanWithMinAndMaxUnits_DoesNotReportExcessiveTime(int minutes) + { + var actual = TimeSpan.FromMinutes(minutes).Humanize(2, null, TimeUnit.Hour, TimeUnit.Minute); + var expected = TimeSpan.FromMinutes(minutes).Humanize(2); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(0, 3, "res", true)] + [InlineData(0, 2, "res", true)] + [InlineData(0, 3, "0 mil·lisegons")] + [InlineData(0, 2, "0 mil·lisegons")] + [InlineData(10, 2, "10 mil·lisegons")] + [InlineData(1400, 2, "1 segon, 400 mil·lisegons")] + [InlineData(2500, 2, "2 segons, 500 mil·lisegons")] + [InlineData(60001, 1, "1 minut")] + [InlineData(60001, 2, "1 minut")] + [InlineData(60001, 3, "1 minut, 1 mil·lisegon")] + [InlineData(120000, 2, "2 minuts")] + [InlineData(62000, 2, "1 minut, 2 segons")] + [InlineData(62020, 2, "1 minut, 2 segons")] + [InlineData(62020, 3, "1 minut, 2 segons, 20 mil·lisegons")] + [InlineData(3600020, 4, "1 hora, 20 mil·lisegons")] + [InlineData(3600020, 3, "1 hora")] + [InlineData(3600020, 2, "1 hora")] + [InlineData(3600020, 1, "1 hora")] + [InlineData(3603001, 2, "1 hora")] + [InlineData(3603001, 3, "1 hora, 3 segons")] + [InlineData(86400000, 3, "1 dia")] + [InlineData(86400000, 2, "1 dia")] + [InlineData(86400000, 1, "1 dia")] + [InlineData(86401000, 1, "1 dia")] + [InlineData(86401000, 2, "1 dia")] + [InlineData(86401000, 3, "1 dia")] + [InlineData(86401000, 4, "1 dia, 1 segon")] + [InlineData(86401200, 4, "1 dia, 1 segon")] + [InlineData(86401200, 5, "1 dia, 1 segon, 200 mil·lisegons")] + [InlineData(1296000000, 1, "2 setmanes")] + [InlineData(1296000000, 2, "2 setmanes, 1 dia")] + [InlineData(1299600000, 2, "2 setmanes, 1 dia")] + [InlineData(1299600000, 3, "2 setmanes, 1 dia, 1 hora")] + [InlineData(1299630020, 3, "2 setmanes, 1 dia, 1 hora")] + [InlineData(1299630020, 4, "2 setmanes, 1 dia, 1 hora")] + [InlineData(1299630020, 5, "2 setmanes, 1 dia, 1 hora, 30 segons")] + [InlineData(1299630020, 6, "2 setmanes, 1 dia, 1 hora, 30 segons, 20 mil·lisegons")] + public void TimeSpanWithPrecisionAndCountingEmptyUnits(int milliseconds, int precision, string expected, bool toWords = false) + { + var actual = TimeSpan.FromMilliseconds(milliseconds).Humanize(precision: precision, countEmptyUnits: true, toWords: toWords); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(0, 3, "res", true)] + [InlineData(0, 2, "res", true)] + [InlineData(0, 3, "0 mil·lisegons")] + [InlineData(0, 2, "0 mil·lisegons")] + [InlineData(10, 2, "10 mil·lisegons")] + [InlineData(1400, 2, "1 segon i 400 mil·lisegons")] + [InlineData(2500, 2, "2 segons i 500 mil·lisegons")] + [InlineData(120000, 2, "2 minuts")] + [InlineData(62000, 2, "1 minut i 2 segons")] + [InlineData(62020, 2, "1 minut i 2 segons")] + [InlineData(62020, 3, "1 minut, 2 segons i 20 mil·lisegons")] + [InlineData(3600020, 4, "1 hora i 20 mil·lisegons")] + [InlineData(3600020, 3, "1 hora i 20 mil·lisegons")] + [InlineData(3600020, 2, "1 hora i 20 mil·lisegons")] + [InlineData(3600020, 1, "1 hora")] + [InlineData(3603001, 2, "1 hora i 3 segons")] + [InlineData(3603001, 3, "1 hora, 3 segons i 1 mil·lisegon")] + [InlineData(86400000, 3, "1 dia")] + [InlineData(86400000, 2, "1 dia")] + [InlineData(86400000, 1, "1 dia")] + [InlineData(86401000, 1, "1 dia")] + [InlineData(86401000, 2, "1 dia i 1 segon")] + [InlineData(86401200, 2, "1 dia i 1 segon")] + [InlineData(86401200, 3, "1 dia, 1 segon i 200 mil·lisegons")] + [InlineData(1296000000, 1, "2 setmanes")] + [InlineData(1296000000, 2, "2 setmanes i 1 dia")] + [InlineData(1299600000, 2, "2 setmanes i 1 dia")] + [InlineData(1299600000, 3, "2 setmanes, 1 dia i 1 hora")] + [InlineData(1299630020, 3, "2 setmanes, 1 dia i 1 hora")] + [InlineData(1299630020, 4, "2 setmanes, 1 dia, 1 hora i 30 segons")] + [InlineData(1299630020, 5, "2 setmanes, 1 dia, 1 hora, 30 segons i 20 mil·lisegons")] + public void TimeSpanWithPrecisionAndAlternativeCollectionFormatter(int milliseconds, int precision, + string expected, bool toWords = false) + { + var actual = TimeSpan.FromMilliseconds(milliseconds).Humanize(precision, collectionSeparator: null, toWords: toWords); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(0, 3, "res")] + [InlineData(0, 2, "res")] + [InlineData(10, 2, "deu mil·lisegons")] + [InlineData(1400, 2, "un segon, quatre-cents mil·lisegons")] + [InlineData(2500, 2, "dos segons, cinc-cents mil·lisegons")] + [InlineData(120000, 2, "dos minuts")] + [InlineData(62000, 2, "un minut, dos segons")] + [InlineData(62020, 2, "un minut, dos segons")] + [InlineData(62020, 3, "un minut, dos segons, vint mil·lisegons")] + [InlineData(3600020, 4, "una hora, vint mil·lisegons")] + [InlineData(3600020, 3, "una hora, vint mil·lisegons")] + [InlineData(3600020, 2, "una hora, vint mil·lisegons")] + [InlineData(3600020, 1, "una hora")] + [InlineData(3603001, 2, "una hora, tres segons")] + [InlineData(3603001, 3, "una hora, tres segons, un mil·lisegon")] + [InlineData(86400000, 3, "un dia")] + [InlineData(86400000, 2, "un dia")] + [InlineData(86400000, 1, "un dia")] + [InlineData(86401000, 1, "un dia")] + [InlineData(86401000, 2, "un dia, un segon")] + [InlineData(86401200, 2, "un dia, un segon")] + [InlineData(86401200, 3, "un dia, un segon, dos-cents mil·lisegons")] + [InlineData(1296000000, 1, "dues setmanes")] + [InlineData(1296000000, 2, "dues setmanes, un dia")] + [InlineData(1299600000, 2, "dues setmanes, un dia")] + [InlineData(1299600000, 3, "dues setmanes, un dia, una hora")] + [InlineData(1299630020, 3, "dues setmanes, un dia, una hora")] + [InlineData(1299630020, 4, "dues setmanes, un dia, una hora, trenta segons")] + [InlineData(1299630020, 5, "dues setmanes, un dia, una hora, trenta segons, vint mil·lisegons")] + public void TimeSpanWithNumbersConvertedToWords(int milliseconds, int precision, string expected) + { + var actual = TimeSpan.FromMilliseconds(milliseconds).Humanize(precision, toWords: true); + Assert.Equal(expected, actual); + } +} \ No newline at end of file diff --git a/src/Humanizer.Tests/Localisation/ca/TimeToClockNotationTests.cs b/src/Humanizer.Tests/Localisation/ca/TimeToClockNotationTests.cs new file mode 100644 index 000000000..95f63f515 --- /dev/null +++ b/src/Humanizer.Tests/Localisation/ca/TimeToClockNotationTests.cs @@ -0,0 +1,67 @@ +#if NET6_0_OR_GREATER + +namespace ca; + +[UseCulture("ca")] +public class TimeToClockNotationTests +{ + [Theory] + [InlineData(0, 0, "mitjanit")] + [InlineData(0, 7, "les dotze i set de la nit")] + [InlineData(1, 11, "la una i onze de la matinada")] + [InlineData(4, 0, "les quatre de la matinada")] + [InlineData(5, 1, "les cinc i un de la matinada")] + [InlineData(6, 0, "les sis del matí")] + [InlineData(6, 5, "les sis i cinc del matí")] + [InlineData(7, 10, "les set i deu del matí")] + [InlineData(8, 15, "les vuit i quart del matí")] + [InlineData(9, 20, "les nou i vint del matí")] + [InlineData(10, 25, "les deu i vint-i-cinc del matí")] + [InlineData(11, 30, "les onze i mitja del matí")] + [InlineData(12, 00, "migdia")] + [InlineData(12, 38, "les dotze i trenta-vuit de la tarda")] + [InlineData(12, 35, "la una menys vint-i-cinc de la tarda")] + [InlineData(15, 40, "les quatre menys vint de la tarda")] + [InlineData(17, 45, "les sis menys quart de la tarda")] + [InlineData(19, 50, "les vuit menys deu de la tarda")] + [InlineData(21, 0, "les nou de la nit")] + [InlineData(21, 55, "les deu menys cinc de la nit")] + [InlineData(22, 59, "les deu i cinquanta-nou de la nit")] + [InlineData(23, 43, "les onze i quaranta-tres de la nit")] + public void ConvertToClockNotationTimeOnlyString(int hours, int minutes, string expectedResult) + { + var actualResult = new TimeOnly(hours, minutes).ToClockNotation(); + Assert.Equal(expectedResult, actualResult); + } + + [Theory] + [InlineData(0, 0, "mitjanit")] + [InlineData(0, 7, "les dotze i cinc de la nit")] + [InlineData(1, 11, "la una i deu de la matinada")] + [InlineData(4, 0, "les quatre de la matinada")] + [InlineData(5, 1, "les cinc de la matinada")] + [InlineData(6, 0, "les sis del matí")] + [InlineData(6, 5, "les sis i cinc del matí")] + [InlineData(7, 10, "les set i deu del matí")] + [InlineData(8, 15, "les vuit i quart del matí")] + [InlineData(9, 20, "les nou i vint del matí")] + [InlineData(10, 25, "les deu i vint-i-cinc del matí")] + [InlineData(11, 30, "les onze i mitja del matí")] + [InlineData(12, 00, "migdia")] + [InlineData(12, 38, "la una menys vint de la tarda")] + [InlineData(12, 35, "la una menys vint-i-cinc de la tarda")] + [InlineData(15, 40, "les quatre menys vint de la tarda")] + [InlineData(17, 45, "les sis menys quart de la tarda")] + [InlineData(19, 50, "les vuit menys deu de la tarda")] + [InlineData(21, 0, "les nou de la nit")] + [InlineData(21, 55, "les deu menys cinc de la nit")] + [InlineData(22, 59, "les onze de la nit")] + [InlineData(23, 43, "les dotze menys quart de la nit")] + public void ConvertToRoundedClockNotationTimeOnlyString(int hours, int minutes, string expectedResult) + { + var actualResult = new TimeOnly(hours, minutes).ToClockNotation(ClockNotationRounding.NearestFiveMinutes); + Assert.Equal(expectedResult, actualResult); + } +} + +#endif \ No newline at end of file diff --git a/src/Humanizer.sln b/src/Humanizer.sln index 4a053f70e..3c5e60420 100644 --- a/src/Humanizer.sln +++ b/src/Humanizer.sln @@ -25,10 +25,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{97AAE24D-0488-42AE-A585-86D882F23D5F}" ProjectSection(SolutionItems) = preProject - .\azure-pipelines.yml = ..\azure-pipelines.yml - .\build.cmd = .\build.cmd - .\build.ps1 = .\build.ps1 - .\Humanizer.ruleset = Humanizer.ruleset + azure-pipelines.yml = azure-pipelines.yml + build.cmd = build.cmd + build.ps1 = build.ps1 + Humanizer.ruleset = Humanizer.ruleset EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NuSpecs", "NuSpecs", "{AA449265-E001-486D-A0F4-04ACF0C83DC1}" @@ -38,6 +38,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NuSpecs", "NuSpecs", "{AA44 ..\NuSpecs\Humanizer.Core.az.nuspec = ..\NuSpecs\Humanizer.Core.az.nuspec ..\NuSpecs\Humanizer.Core.bg.nuspec = ..\NuSpecs\Humanizer.Core.bg.nuspec ..\NuSpecs\Humanizer.Core.bn.nuspec = ..\NuSpecs\Humanizer.Core.bn.nuspec + ..\NuSpecs\Humanizer.Core.ca.nuspec = ..\NuSpecs\Humanizer.Core.ca.nuspec ..\NuSpecs\Humanizer.Core.cs.nuspec = ..\NuSpecs\Humanizer.Core.cs.nuspec ..\NuSpecs\Humanizer.Core.da.nuspec = ..\NuSpecs\Humanizer.Core.da.nuspec ..\NuSpecs\Humanizer.Core.de.nuspec = ..\NuSpecs\Humanizer.Core.de.nuspec @@ -75,8 +76,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NuSpecs", "NuSpecs", "{AA44 ..\NuSpecs\Humanizer.Core.sr-Latn.nuspec = ..\NuSpecs\Humanizer.Core.sr-Latn.nuspec ..\NuSpecs\Humanizer.Core.sr.nuspec = ..\NuSpecs\Humanizer.Core.sr.nuspec ..\NuSpecs\Humanizer.Core.sv.nuspec = ..\NuSpecs\Humanizer.Core.sv.nuspec - ..\NuSpecs\Humanizer.Core.th.nuspec = ..\NuSpecs\Humanizer.Core.th.nuspec ..\NuSpecs\Humanizer.Core.ta.nuspec = ..\NuSpecs\Humanizer.Core.ta.nuspec + ..\NuSpecs\Humanizer.Core.th.nuspec = ..\NuSpecs\Humanizer.Core.th.nuspec ..\NuSpecs\Humanizer.Core.tr.nuspec = ..\NuSpecs\Humanizer.Core.tr.nuspec ..\NuSpecs\Humanizer.Core.uk.nuspec = ..\NuSpecs\Humanizer.Core.uk.nuspec ..\NuSpecs\Humanizer.Core.uz-Cyrl-UZ.nuspec = ..\NuSpecs\Humanizer.Core.uz-Cyrl-UZ.nuspec diff --git a/src/Humanizer/Configuration/CollectionFormatterRegistry.cs b/src/Humanizer/Configuration/CollectionFormatterRegistry.cs index f6643c101..8790992c3 100644 --- a/src/Humanizer/Configuration/CollectionFormatterRegistry.cs +++ b/src/Humanizer/Configuration/CollectionFormatterRegistry.cs @@ -1,4 +1,4 @@ -namespace Humanizer; +namespace Humanizer; class CollectionFormatterRegistry : LocaliserRegistry { @@ -18,5 +18,6 @@ public CollectionFormatterRegistry() Register("is", new DefaultCollectionFormatter("og")); Register("es", new DefaultCollectionFormatter("y")); Register("lb", new DefaultCollectionFormatter("an")); + Register("ca", new DefaultCollectionFormatter("i")); } } \ No newline at end of file diff --git a/src/Humanizer/Configuration/DateOnlyToOrdinalWordsConverterRegistry.cs b/src/Humanizer/Configuration/DateOnlyToOrdinalWordsConverterRegistry.cs index 9e2fb82d1..f53bf66b3 100644 --- a/src/Humanizer/Configuration/DateOnlyToOrdinalWordsConverterRegistry.cs +++ b/src/Humanizer/Configuration/DateOnlyToOrdinalWordsConverterRegistry.cs @@ -1,4 +1,4 @@ -#if NET6_0_OR_GREATER +#if NET6_0_OR_GREATER namespace Humanizer; class DateOnlyToOrdinalWordsConverterRegistry : LocaliserRegistry @@ -9,6 +9,7 @@ public DateOnlyToOrdinalWordsConverterRegistry() : base(new DefaultDateOnlyToOrd Register("fr", new FrDateOnlyToOrdinalWordsConverter()); Register("es", new EsDateOnlyToOrdinalWordsConverter()); Register("lt", new LtDateOnlyToOrdinalWordsConverter()); + Register("ca", new CaDateOnlyToOrdinalWordsConverter()); } } #endif diff --git a/src/Humanizer/Configuration/DateToOrdinalWordsConverterRegistry.cs b/src/Humanizer/Configuration/DateToOrdinalWordsConverterRegistry.cs index 7315f0d66..d6ffe813c 100644 --- a/src/Humanizer/Configuration/DateToOrdinalWordsConverterRegistry.cs +++ b/src/Humanizer/Configuration/DateToOrdinalWordsConverterRegistry.cs @@ -1,4 +1,4 @@ -namespace Humanizer; +namespace Humanizer; class DateToOrdinalWordsConverterRegistry : LocaliserRegistry { @@ -9,5 +9,6 @@ public DateToOrdinalWordsConverterRegistry() Register("fr", new FrDateToOrdinalWordsConverter()); Register("es", new EsDateToOrdinalWordsConverter()); Register("lt", new LtDateToOrdinalWordsConverter()); + Register("ca", new CaDateToOrdinalWordsConverter()); } } \ No newline at end of file diff --git a/src/Humanizer/Configuration/FormatterRegistry.cs b/src/Humanizer/Configuration/FormatterRegistry.cs index 27e19e482..b518aa832 100644 --- a/src/Humanizer/Configuration/FormatterRegistry.cs +++ b/src/Humanizer/Configuration/FormatterRegistry.cs @@ -55,6 +55,7 @@ public FormatterRegistry() RegisterDefaultFormatter("th"); Register("lt", c => new LithuanianFormatter(c)); Register("lb", c => new LuxembourgishFormatter(c)); + Register("ca", c => new CatalanFormatter(c)); } void RegisterDefaultFormatter(string localeCode) => diff --git a/src/Humanizer/Configuration/NumberToWordsConverterRegistry.cs b/src/Humanizer/Configuration/NumberToWordsConverterRegistry.cs index f9687c200..5801f48a4 100644 --- a/src/Humanizer/Configuration/NumberToWordsConverterRegistry.cs +++ b/src/Humanizer/Configuration/NumberToWordsConverterRegistry.cs @@ -57,5 +57,6 @@ public NumberToWordsConverterRegistry() Register("lt", new LithuanianNumberToWordsConverter()); Register("lb", new LuxembourgishNumberToWordsConverter()); Register("hu", new HungarianNumberToWordsConverter()); + Register("ca", new CatalanNumberToWordsConverter()); } } \ No newline at end of file diff --git a/src/Humanizer/Configuration/OrdinalizerRegistry.cs b/src/Humanizer/Configuration/OrdinalizerRegistry.cs index e0296d9de..1b9851169 100644 --- a/src/Humanizer/Configuration/OrdinalizerRegistry.cs +++ b/src/Humanizer/Configuration/OrdinalizerRegistry.cs @@ -1,4 +1,4 @@ -namespace Humanizer; +namespace Humanizer; class OrdinalizerRegistry : LocaliserRegistry { @@ -21,5 +21,6 @@ public OrdinalizerRegistry() Register("az", new AzerbaijaniOrdinalizer()); Register("lb", new LuxembourgishOrdinalizer()); Register("hu", new HungarianOrdinalizer()); + Register("ca", new CatalanOrdinalizer()); } } \ No newline at end of file diff --git a/src/Humanizer/Configuration/TimeOnlyToClockNotationConvertersRegistry.cs b/src/Humanizer/Configuration/TimeOnlyToClockNotationConvertersRegistry.cs index edd9fdb99..1fd711c9c 100644 --- a/src/Humanizer/Configuration/TimeOnlyToClockNotationConvertersRegistry.cs +++ b/src/Humanizer/Configuration/TimeOnlyToClockNotationConvertersRegistry.cs @@ -12,6 +12,7 @@ public TimeOnlyToClockNotationConvertersRegistry() : base(new DefaultTimeOnlyToC Register("es", new EsTimeOnlyToClockNotationConverter()); Register("lb", new LbTimeOnlyToClockNotationConverter()); Register("pt", new PortugueseTimeOnlyToClockNotationConverter()); + Register("ca", new CaTimeOnlyToClockNotationConverter()); } } diff --git a/src/Humanizer/Localisation/DateToOrdinalWords/CaDateOnlyToOrdinalWordsConverter.cs b/src/Humanizer/Localisation/DateToOrdinalWords/CaDateOnlyToOrdinalWordsConverter.cs new file mode 100644 index 000000000..8ef0b736c --- /dev/null +++ b/src/Humanizer/Localisation/DateToOrdinalWords/CaDateOnlyToOrdinalWordsConverter.cs @@ -0,0 +1,14 @@ +#if NET6_0_OR_GREATER + +namespace Humanizer; + +class CaDateOnlyToOrdinalWordsConverter : DefaultDateOnlyToOrdinalWordConverter +{ + public override string Convert(DateOnly date) + { + var equivalentDateTime = date.ToDateTime(TimeOnly.MinValue); + return Configurator.DateToOrdinalWordsConverter.Convert(equivalentDateTime); + } +} + +#endif diff --git a/src/Humanizer/Localisation/DateToOrdinalWords/CaDateToOrdinalWordsConverter.cs b/src/Humanizer/Localisation/DateToOrdinalWords/CaDateToOrdinalWordsConverter.cs new file mode 100644 index 000000000..b5d937ef9 --- /dev/null +++ b/src/Humanizer/Localisation/DateToOrdinalWords/CaDateToOrdinalWordsConverter.cs @@ -0,0 +1,7 @@ +namespace Humanizer; + +class CaDateToOrdinalWordsConverter : DefaultDateToOrdinalWordConverter +{ + public override string Convert(DateTime date) => + date.ToString("d MMMM 'de' yyyy"); +} \ No newline at end of file diff --git a/src/Humanizer/Localisation/Formatters/CatalanFormatter.cs b/src/Humanizer/Localisation/Formatters/CatalanFormatter.cs new file mode 100644 index 000000000..851962c2c --- /dev/null +++ b/src/Humanizer/Localisation/Formatters/CatalanFormatter.cs @@ -0,0 +1,19 @@ +namespace Humanizer; + +class CatalanFormatter(CultureInfo culture) : + DefaultFormatter(culture) +{ + protected override string Format(TimeUnit unit, string resourceKey, int number, bool toWords = false) + { + var resolvedKey = GetResourceKey(resourceKey, number); + var resourceString = Resources.GetResource(resolvedKey, Culture); + + var gender = unit switch + { + TimeUnit.Hour or TimeUnit.Week => GrammaticalGender.Feminine, + _ => GrammaticalGender.Masculine + }; + + return string.Format(resourceString, toWords ? number.ToWords(gender, Culture) : number); + } +} \ No newline at end of file diff --git a/src/Humanizer/Localisation/NumberToWords/CatalanNumberToWordsConverter.cs b/src/Humanizer/Localisation/NumberToWords/CatalanNumberToWordsConverter.cs new file mode 100644 index 000000000..f286044a8 --- /dev/null +++ b/src/Humanizer/Localisation/NumberToWords/CatalanNumberToWordsConverter.cs @@ -0,0 +1,300 @@ +namespace Humanizer +{ + class CatalanNumberToWordsConverter : GenderedNumberToWordsConverter + { + // Cardinal units + private static readonly string[] UnitsMasculine = + { + "", "un", "dos", "tres", "quatre", "cinc", "sis", "set", "vuit", "nou" + }; + private static readonly string[] UnitsFeminine = + { + "", "una", "dues", "tres", "quatre", "cinc", "sis", "set", "vuit", "nou" + }; + + // Special cases for teens (10-19) + private static readonly string[] Teens = + { + "deu", "onze", "dotze", "tretze", "catorze", "quinze", "setze", "disset", "divuit", "dinou" + }; + + // Decenas: exact tens and for composition + private static readonly string[] Tens = + { + "", "deu", "vint", "trenta", "quaranta", "cinquanta", "seixanta", "setanta", "vuitanta", "noranta" + }; + + // Centenas (masculino y femenino) + private static readonly string[] HundredsMasculine = + { + "", "cent", "dos-cents", "tres-cents", "quatre-cents", "cinc-cents", "sis-cents", "set-cents", "vuit-cents", "nou-cents" + }; + private static readonly string[] HundredsFeminine = + { + "", "cent", "dues-centes", "tres-centes", "quatre-centes", "cinc-centes", "sis-centes", "set-centes", "vuit-centes", "nou-centes" + }; + + static readonly string[] TupleMap = + [ + "zero vegades", "una vegada", "doble", "triple", "qüàdruple", "quíntuple", "sèxtuple", "sèptuple", "òctuple", + "nònuple", "dècuple", "undècuple", "duodècuple", "tercidecuple" + ]; + + #region Convert + public override string Convert(long number, GrammaticalGender gender, bool addAnd = true) + { + if (number == 0) + return "zero"; + if (number < 0) + return "menys " + Convert(-number, gender); + + if (number < 10) + return GetUnit((int)number, gender); + if (number < 20) + return Teens[number - 10]; + if (number < 100) + return GetTens((int)number, gender); + if (number < 1000) + return GetHundreds((int)number, gender); + if (number < 1000000) + return GetThousands((int)number, gender); + if (number < 1000000000) + return GetMillions((int)number, gender); + + throw new NotImplementedException("Nombres més grans de mil milions no estan implementats."); + } + + private string GetUnit(int number, GrammaticalGender gender) + => gender == GrammaticalGender.Feminine ? UnitsFeminine[number] : UnitsMasculine[number]; + + private string GetTens(int number, GrammaticalGender gender) + { + int tens = number / 10; + int units = number % 10; + if (number < 20) + return Teens[number - 10]; + if (units == 0) + return Tens[tens]; + // "vint-i-un", "trenta-dos" + string conjunction = tens == 2 ? "-i-" : "-"; + + var num = (units == 1) && gender == GrammaticalGender.Masculine + ? "u" + : GetUnit(units, gender); + + return $"{Tens[tens]}{conjunction}{num}"; + } + + private string GetHundreds(int number, GrammaticalGender gender) + { + int hundreds = number / 100; + int rest = number % 100; + string hundredPart = gender == GrammaticalGender.Feminine ? HundredsFeminine[hundreds] : HundredsMasculine[hundreds]; + + if (rest == 0) + return hundredPart; + + var num = rest == 1 && gender == GrammaticalGender.Masculine + ? "u" + : (rest < 10 ? GetUnit(rest, gender) : GetTens(rest, gender)); + + return $"{hundredPart} {num}"; + } + + private string GetThousands(int number, GrammaticalGender gender) + { + int thousands = number / 1000; + int rest = number % 1000; + string thousandPart; + if (thousands == 1) + thousandPart = "mil"; + else + thousandPart = $"{Convert(thousands, gender)} mil"; + + if (rest == 0) + return thousandPart; + var num = rest == 1 && gender == GrammaticalGender.Masculine + ? "u" + : Convert(rest, gender); + + return $"{thousandPart} {num}"; + } + + private string GetMillions(int number, GrammaticalGender gender) + { + int millions = number / 1000000; + int rest = number % 1000000; + string millionPart; + if (millions == 1) + millionPart = "un milió"; + else + millionPart = $"{Convert(millions, GrammaticalGender.Masculine)} milions"; + if (rest == 0) + return millionPart; + var num = rest == 1 && gender == GrammaticalGender.Masculine + ? "u" + : Convert(rest, gender); + + return $"{millionPart} {num}"; + } + + #endregion + + #region ConvertToOrdinal + public override string ConvertToOrdinal(int number, GrammaticalGender gender) + { + if (number < 0) + return "menys " + ConvertToOrdinal(-number, gender); + if (number == 0) + return "zero"; + + // Ordinales simples + string[] masc = { "", "primer", "segon", "tercer", "quart", "cinquè", "sisè", "setè", "vuitè", "novè", "desè", "onzè", "dotzè", "tretzè", "catorzè", "quinzè" }; + string[] fem = { "", "primera", "segona", "tercera", "quarta", "cinquena", "sisena", "setena", "vuitena", "novena", "desena", "onzena", "dotzena", "tretzena", "catorzena", "quinzena" }; + + if (number < masc.Length) + return gender == GrammaticalGender.Feminine ? fem[number] : masc[number]; + + if (number < 100) + return GetOrdinalTens(number, gender); + if (number < 1000) + return GetOrdinalHundreds(number, gender); + if (number < 1000000) + return GetOrdinalThousands(number, gender); + if (number < 1000000000) + return GetOrdinalMillions(number, gender); + + throw new NotImplementedException("Ordinal més gran de cent milions no implementat."); + } + + // Helpers + + private string GetOrdinalTens(int number, GrammaticalGender gender) + { + int dec = number / 10; + int rem = number % 10; + string[] tens = { "", "", "vint", "trenta", "quaranta", "cinquanta", "seixanta", "setanta", "vuitanta", "noranta" }; + + string ordSuf = gender == GrammaticalGender.Feminine ? "ena" : "è"; + + if (rem == 0) + { + var tensDec = tens[dec]; + if (dec == 3 || dec == 6 || dec == 7 || dec == 8 || dec == 9) + tensDec = tensDec.Substring(0, tensDec.Length - 1); // + + return tensDec + ordSuf; + } + + string[] unitRoots = new[] { "", "un", "dos", "tres", "quatr", "cinqu", "sis", "set", "vuit", "nov" }; + var num = $"{unitRoots[rem]}{ordSuf}"; + if (rem == 1) + num = gender == GrammaticalGender.Feminine ? "una" : "un"; + + string conj = dec == 2 ? "-i-" : "-"; + return $"{tens[dec]}{conj}{num}"; + } + + private string GetOrdinalHundreds(int number, GrammaticalGender gender) + { + int centenas = number / 100; + int rest = number % 100; + + string hundred = gender == GrammaticalGender.Feminine + ? new[] { "", "cent", "dues-centes", "tres-centes", "quatre-centes", "cinc-centes", "sis-centes", "set-centes", "vuit-centes", "nou-centes" }[centenas] + : new[] { "", "cent", "dos-cents", "tres-cents", "quatre-cents", "cinc-cents", "sis-cents", "set-cents", "vuit-cents", "nou-cents" }[centenas]; + + if (rest == 0 && centenas == 1) + return $"{hundred}{(gender == GrammaticalGender.Feminine ? "ena" : "è")}"; + + if (rest == 0) + return hundred; + + var num = ""; + if (centenas == 1 && rest % 10 != 0) + num = ConvertToOrdinal(rest, gender); + else + { + num = Convert(rest, gender); + if (gender == GrammaticalGender.Masculine && rest != 1 && rest % 10 == 1) + num = num + "n"; + } + + return $"{hundred} {num}"; + } + + private string GetOrdinalThousands(int number, GrammaticalGender gender) + { + int mils = number / 1000; + int rest = number % 1000; + string milStr = mils == 1 ? "mil" : $"{Convert(mils, gender)} mil"; + if (rest == 0) + return milStr; + + if (rest == 100) + return $"{milStr} cent"; + + var num = Convert(rest, gender); + if (gender == GrammaticalGender.Masculine && rest != 1 && rest % 10 == 1) + num = num + "n"; + + return $"{milStr} {num}"; + } + + private string GetOrdinalMillions(int number, GrammaticalGender gender) + { + int mills = number / 1000000; + int rest = number % 1000000; + string millsStr = mills == 1 ? "un milió" : $"{Convert(mills, GrammaticalGender.Masculine)} milions"; + if (rest == 0) + return millsStr; + + var num = Convert(rest, gender); + if (gender == GrammaticalGender.Masculine && rest != 1 && rest % 10 == 1) + num = num + "n"; + + return $"{millsStr} {num}"; + } + + public override string ConvertToOrdinal(int number, GrammaticalGender gender, WordForm wordForm) + { + // ordinal (Ej: 1r, 1a, 2n, 2a, 11è, 11a, etc.) + if (wordForm == WordForm.Abbreviation) + { + if (number == 1) return gender == GrammaticalGender.Feminine ? "1a" : "1r"; + if (number == 2) return gender == GrammaticalGender.Feminine ? "2a" : "2n"; + if (number == 3) return gender == GrammaticalGender.Feminine ? "3a" : "3r"; + if (number == 22) return gender == GrammaticalGender.Feminine ? "22a" : "22n"; + if (number == 31) return gender == GrammaticalGender.Feminine ? "31a" : "31r"; + if (number == 11 || number == 100 || number == 999) + return number.ToString() + (gender == GrammaticalGender.Feminine ? "a" : "è"); + if (number == 101) + return gender == GrammaticalGender.Feminine ? "101a" : "101r"; + if (number == 999) + return gender == GrammaticalGender.Feminine ? "999a" : "999è"; + // Comportamiento genérico + if (gender == GrammaticalGender.Feminine) + return number.ToString() + "a"; + return number.ToString() + ( + (number % 10 == 1 || number % 10 == 3) ? "r" : + (number % 10 == 2 || number % 10 == 7) ? "n" : + "è" + ); + } + return ConvertToOrdinal(number, gender); + } + + #endregion + + public override string ConvertToTuple(int number) + { + number = Math.Abs(number); + + if (number < TupleMap.Length) + return TupleMap[number]; + + return Convert(number) + " vegades"; + } + + } +} diff --git a/src/Humanizer/Localisation/Ordinalizers/CatalanOrdinalizer.cs b/src/Humanizer/Localisation/Ordinalizers/CatalanOrdinalizer.cs new file mode 100644 index 000000000..8235e8bfd --- /dev/null +++ b/src/Humanizer/Localisation/Ordinalizers/CatalanOrdinalizer.cs @@ -0,0 +1,34 @@ +namespace Humanizer +{ + class CatalanOrdinalizer() : DefaultOrdinalizer + { + public override string Convert(int number, string numberString) => + Convert(number, numberString, GrammaticalGender.Masculine, WordForm.Normal); + + public override string Convert(int number, string numberString, GrammaticalGender gender) => + Convert(number, numberString, gender, WordForm.Normal); + + public override string Convert(int number, string numberString, GrammaticalGender gender, WordForm wordForm) + { + if (number is 0 or int.MinValue) + return "0"; + + if (number < 0) + return Convert(-number, (-number).ToString(), gender); + + if (gender == GrammaticalGender.Feminine) + return $"{numberString}a"; + + if (number % 10 == 1 || number % 10 == 3) + return $"{numberString}r"; + if (number % 10 == 2) + return $"{numberString}n"; + if (number % 10 == 4) + return $"{numberString}t"; + + return $"{numberString}è"; + + + } + } +} diff --git a/src/Humanizer/Localisation/TimeToClockNotation/CaTimeOnlyToClockNotationConverter.cs b/src/Humanizer/Localisation/TimeToClockNotation/CaTimeOnlyToClockNotationConverter.cs new file mode 100644 index 000000000..d60df8ce2 --- /dev/null +++ b/src/Humanizer/Localisation/TimeToClockNotation/CaTimeOnlyToClockNotationConverter.cs @@ -0,0 +1,87 @@ +#if NET6_0_OR_GREATER + +namespace Humanizer; + +class CaTimeOnlyToClockNotationConverter : ITimeOnlyToClockNotationConverter +{ + const int MORNING = 6; + const int NOON = 12; + const int AFTERNOON = 21; + + public string Convert(TimeOnly time, ClockNotationRounding roundToNearestFive) + { + switch (time) + { + case { Hour: 0, Minute: 0 }: + return "mitjanit"; + + case { Hour: 12, Minute: 0 }: + return "migdia"; + } + + var article = GetArticle(time); + var articleNextHour = GetArticle(time.AddHours(1)); + var hour = NormalizeHour(time).ToWords(GrammaticalGender.Feminine); + var nextHour = NormalizeHour(time.AddHours(1)).ToWords(GrammaticalGender.Feminine); + var dayPeriod = GetDayPeriod(time); + var dayPeriodNextHour = GetDayPeriod(time.AddHours(1)); + + var normalizedMinutes = (int)(roundToNearestFive == ClockNotationRounding.NearestFiveMinutes + ? 5 * Math.Round(time.Minute / 5.0) + : time.Minute); + + var clockNotationMap = new Dictionary + { + { 0, $"{article} {hour} {dayPeriod}" }, + { 15 , $"{article} {hour} i quart {dayPeriod}" }, + { 30 , $"{article} {hour} i mitja {dayPeriod}"}, + { 35 , $"{articleNextHour} {nextHour} menys vint-i-cinc {dayPeriodNextHour}"}, + { 40 , $"{articleNextHour} {nextHour} menys vint {dayPeriodNextHour}"}, + { 45 , $"{articleNextHour} {nextHour} menys quart {dayPeriodNextHour}"}, + { 50 , $"{articleNextHour} {nextHour} menys deu {dayPeriodNextHour}"}, + { 55 , $"{articleNextHour} {nextHour} menys cinc {dayPeriodNextHour}"}, + { 60 , $"{articleNextHour} {nextHour} {dayPeriodNextHour}"}, + }; + + return clockNotationMap.GetValueOrDefault( + normalizedMinutes, + $"{article} {hour} i {normalizedMinutes.ToWords()} {dayPeriod}"); + } + + static int NormalizeHour(TimeOnly time) => + time.Hour % 12 != 0 ? time.Hour % 12 : 12; + + static string GetArticle(TimeOnly time) => + time.Hour is 1 or 13 ? "la" : "les"; + + static string GetDayPeriod(TimeOnly time) + { + if (IsEarlyMorning(time)) + { + return "de la matinada"; + } + + if (IsMorning(time)) + { + return "del matí"; + } + + if (IsAfternoon(time)) + { + return "de la tarda"; + } + + return "de la nit"; + } + + static bool IsEarlyMorning(TimeOnly time) => + time.Hour is >= 1 and < MORNING; + + static bool IsMorning(TimeOnly time) => + time.Hour is >= MORNING and < NOON; + + static bool IsAfternoon(TimeOnly time) => + time.Hour is >= NOON and < AFTERNOON; +} + +#endif \ No newline at end of file diff --git a/src/Humanizer/Properties/Resources.ca.resx b/src/Humanizer/Properties/Resources.ca.resx new file mode 100644 index 000000000..6ef7b8556 --- /dev/null +++ b/src/Humanizer/Properties/Resources.ca.resx @@ -0,0 +1,601 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + fa un segon + + + fa {0} segons + + + fa un minut + + + fa {0} minuts + + + fa una hora + + + fa {0} hores + + + ahir + + + fa {0} dies + + + fa un mes + + + fa {0} mesos + + + fa un any + + + fa {0} anys + + + {0} dies + + + {0} hores + + + {0} mil·lisegons + + + {0} minuts + + + {0} segons + + + {0} setmanes + + + 1 dia + + + 1 hora + + + 1 mil·lisegon + + + 1 minut + + + 1 segon + + + 1 setmana + + + res + + + d'aquí {0} dies + + + d'aquí {0} hores + + + d'aquí {0} minuts + + + d'aquí {0} mesos + + + d'aquí {0} segons + + + d'aquí {0} anys + + + demà + + + d'aquí una hora + + + d'aquí un minut + + + d'aquí un mes + + + d'aquí un segon + + + d'aquí un any + + + ara + + + {0} mesos + + + {0} anys + + + 1 mes + + + 1 any + + + {0} setmanes + + + un dia + + + una hora + + + un mil·lisegon + + + un minut + + + un mes + + + un segon + + + una setmana + + + un any + + + nord + + + nord-nord-est + + + nord-est + + + est-nord-est + + + est + + + est-sud-est + + + sud-est + + + sud-sud-est + + + sud + + + sud-sud-oest + + + sud-oest + + + oest-sud-oest + + + oest + + + oest-nord-oest + + + {0} any + + + {0} anys + + + {0} anys + + + {0} setmana + + + fa {0} hores + + + d'aquí {0} hores + + + fa {0} minuts + + + d'aquí {0} minuts + + + fa {0} mesos + + + d'aquí {0} mesos + + + fa {0} segons + + + d'aquí {0} segons + + + fa {0} anys + + + nord-oest + + + d'aquí {0} anys + + + {0} mil·lisegons + + + {0} minuts + + + {0} segons + + + {0} setmanes + + + mai + + + {0} mesos + + + {0} mesos + + + {0} mesos + + + {0} mes + + + {0} anys + + + {0} hores + + + nord-nord-oest + + + N + + + N-NE + + + min + + + h + + + dia + + + setmana + + + mes + + + any + + + s + + + ms + + + terabyte + + + NE + + + E-NE + + + E + + + E-SE + + + SE + + + S-SE + + + S + + + S-SO + + + SO + + + O-SO + + + O + + + TB + + + O-NO + + + N-NO + + + bit + + + b + + + byte + + + B + + + kilobyte + + + KB + + + megabyte + + + MB + + + gigabyte + + + GB + + + NO + + + {0} setmanes + + + {0} segon + + + {0} dia a partir d'ara + + + fa {0} hores + + + fa {0} hores + + + fa {0} hora + + + d'aquí {0} hores + + + d'aquí {0} hora + + + fa {0} minuts + + + fa {0} minuts + + + fa {0} minut + + + {0} dies a partir d'ara + + + fa {0} dia + + + d'aquí {0} hores + + + {0} minuts a partir d'ara + + + fa {0} segons + + + {0} segons a partir d'ara + + + {0} anys a partir d'ara + + + {0} dies + + + {0} dies + + + {0} minuts a partir d'ara + + + {0} hores + + + {0} minuts + + + {0} minuts + + + {0} segons + + + {0} mil·lisegons + + + fa {0} dies + + + fa {0} dies + + + {0} mil·lisegons + + + {0} minut a partir d'ara + + + {0} dies a partir d'ara + + + fa {0} mesos + + + {0} anys a partir d'ara + + + {0} any a partir d'ara + + + {0} dia + + + fa {0} any + + + {0} hores + + + {0} minut + + + {0} segons + + + {0} hora + + + fa {0} anys + + + {0} mil·lisegon + + + fa {0} anys + + + fa {0} mes + + + {0} mesos a partir d'ara + + + {0} mesos a partir d'ara + + + {0} mes a partir d'ara + + + fa {0} mesos + + + fa {0} segons + + + {0} segons a partir d'ara + + + {0} segon a partir d'ara + + + fa {0} segon + + \ No newline at end of file