From 94f8c92590e327e447f22edd9e7e3748db7b4ada Mon Sep 17 00:00:00 2001 From: Thomas Radioyes Date: Sat, 12 Apr 2014 00:02:01 +0200 Subject: [PATCH] Add french localisation for NumberToWords Added french (fr-FR) localisation & tests for NumberToWords Localized NumberToOrdinalWordsExtension, NumberToOrdinalWordsTests and DateTimeHumanizePrecisionStrategyTests --- .../DateTimeHumanizePrecisionStrategyTests.cs | 4 +- src/Humanizer.Tests/Humanizer.Tests.csproj | 2 + .../fr/NumberToOrdinalWordsTests.cs | 59 +++++++++ .../Localisation/fr/NumberToWordsTests.cs | 62 ++++++++++ .../NumberToOrdinalWordsTests.cs | 4 +- src/Humanizer/Humanizer.csproj | 1 + .../FrenchNumberToWordsConverter.cs | 116 ++++++++++++++++++ src/Humanizer/NumberToWordsExtension.cs | 3 +- 8 files changed, 248 insertions(+), 3 deletions(-) create mode 100644 src/Humanizer.Tests/Localisation/fr/NumberToOrdinalWordsTests.cs create mode 100644 src/Humanizer.Tests/Localisation/fr/NumberToWordsTests.cs create mode 100644 src/Humanizer/Localisation/NumberToWords/FrenchNumberToWordsConverter.cs diff --git a/src/Humanizer.Tests/DateTimeHumanizePrecisionStrategyTests.cs b/src/Humanizer.Tests/DateTimeHumanizePrecisionStrategyTests.cs index 1a6e99224..1aa3acbd2 100644 --- a/src/Humanizer.Tests/DateTimeHumanizePrecisionStrategyTests.cs +++ b/src/Humanizer.Tests/DateTimeHumanizePrecisionStrategyTests.cs @@ -3,8 +3,10 @@ namespace Humanizer.Tests { - public class DateTimeHumanizePrecisionStrategyTests + public class DateTimeHumanizePrecisionStrategyTests : AmbientCulture { + public DateTimeHumanizePrecisionStrategyTests() : base("en-US") { } + private const double defaultPrecision = .75; [Theory] diff --git a/src/Humanizer.Tests/Humanizer.Tests.csproj b/src/Humanizer.Tests/Humanizer.Tests.csproj index b36032eae..2898df18b 100644 --- a/src/Humanizer.Tests/Humanizer.Tests.csproj +++ b/src/Humanizer.Tests/Humanizer.Tests.csproj @@ -83,6 +83,8 @@ + + diff --git a/src/Humanizer.Tests/Localisation/fr/NumberToOrdinalWordsTests.cs b/src/Humanizer.Tests/Localisation/fr/NumberToOrdinalWordsTests.cs new file mode 100644 index 000000000..0942020a4 --- /dev/null +++ b/src/Humanizer.Tests/Localisation/fr/NumberToOrdinalWordsTests.cs @@ -0,0 +1,59 @@ +using Xunit; +using Xunit.Extensions; + +namespace Humanizer.Tests.Localisation.fr +{ + public class NumberToOrdinalWordsTests : AmbientCulture + { + public NumberToOrdinalWordsTests() : base("fr-FR") { } + + [Theory] + [InlineData(0, "zéroième")] + [InlineData(1, "premier")] + [InlineData(2, "deuxième")] + [InlineData(3, "troisième")] + [InlineData(4, "quatrième")] + [InlineData(5, "cinquième")] + [InlineData(6, "sixième")] + [InlineData(7, "septième")] + [InlineData(8, "huitième")] + [InlineData(9, "neuvième")] + [InlineData(10, "dixième")] + [InlineData(11, "onzième")] + [InlineData(12, "douzième")] + [InlineData(13, "treizième")] + [InlineData(14, "quatorzième")] + [InlineData(15, "quinzième")] + [InlineData(16, "seizième")] + [InlineData(17, "dix-septième")] + [InlineData(18, "dix-huitième")] + [InlineData(19, "dix-neuvième")] + [InlineData(20, "vingtième")] + [InlineData(21, "vingt et unième")] + [InlineData(22, "vingt-deuxième")] + [InlineData(30, "trentième")] + [InlineData(40, "quarantième")] + [InlineData(50, "cinquantième")] + [InlineData(60, "soixantième")] + [InlineData(70, "soixante-dixième")] + [InlineData(80, "quatre-vingtième")] + [InlineData(90, "quatre-vingt-dixième")] + [InlineData(95, "quatre-vingt-quinzième")] + [InlineData(96, "quatre-vingt-seizième")] + [InlineData(100, "centième")] + [InlineData(120, "cent vingtième")] + [InlineData(121, "cent vingt et unième")] + [InlineData(1000, "millième")] + [InlineData(1001, "mille unième")] + [InlineData(1021, "mille vingt et unième")] + [InlineData(10000, "dix millième")] + [InlineData(10121, "dix mille cent vingt et unième")] + [InlineData(100000, "cent millième")] + [InlineData(1000000, "millionième")] + [InlineData(1000000000, "milliardième")] + public void ToOrdinalWords(int number, string words) + { + Assert.Equal(words, number.ToOrdinalWords()); + } + } +} diff --git a/src/Humanizer.Tests/Localisation/fr/NumberToWordsTests.cs b/src/Humanizer.Tests/Localisation/fr/NumberToWordsTests.cs new file mode 100644 index 000000000..385af527c --- /dev/null +++ b/src/Humanizer.Tests/Localisation/fr/NumberToWordsTests.cs @@ -0,0 +1,62 @@ +using Xunit; +using Xunit.Extensions; + +namespace Humanizer.Tests.Localisation.fr +{ + public class NumberToWordsTests : AmbientCulture + { + public NumberToWordsTests() : base("fr-FR") { } + + [Theory] + [InlineData(0, "zéro")] + [InlineData(1, "un")] + [InlineData(10, "dix")] + [InlineData(11, "onze")] + [InlineData(15, "quinze")] + [InlineData(17, "dix-sept")] + [InlineData(25, "vingt-cinq")] + [InlineData(31, "trente et un")] + [InlineData(71, "soixante et onze")] + [InlineData(81, "quatre-vingt-un")] + [InlineData(122, "cent vingt-deux")] + [InlineData(3501, "trois mille cinq cent un")] + [InlineData(100, "cent")] + [InlineData(1000, "mille")] + [InlineData(100000, "cent mille")] + [InlineData(1000000, "un million")] + [InlineData(10000000, "dix millions")] + [InlineData(100000000, "cent millions")] + [InlineData(1000000000, "un milliard")] + [InlineData(111, "cent onze")] + [InlineData(1111, "mille cent onze")] + [InlineData(111111, "cent onze mille cent onze")] + [InlineData(1111111, "un million cent onze mille cent onze")] + [InlineData(11111111, "onze millions cent onze mille cent onze")] + [InlineData(111111111, "cent onze millions cent onze mille cent onze")] + [InlineData(1111111111, "un milliard cent onze millions cent onze mille cent onze")] + [InlineData(123, "cent vingt-trois")] + [InlineData(1234, "mille deux cent trente-quatre")] + [InlineData(12345, "douze mille trois cent quarante-cinq")] + [InlineData(123456, "cent vingt-trois mille quatre cent cinquante-six")] + [InlineData(1234567, "un million deux cent trente-quatre mille cinq cent soixante-sept")] + [InlineData(12345678, "douze millions trois cent quarante-cinq mille six cent soixante-dix-huit")] + [InlineData(123456789, "cent vingt-trois millions quatre cent cinquante-six mille sept cent quatre-vingt-neuf")] + [InlineData(1234567890, "un milliard deux cent trente-quatre millions cinq cent soixante-sept mille huit cent quatre-vingt-dix")] + [InlineData(1234567899, "un milliard deux cent trente-quatre millions cinq cent soixante-sept mille huit cent quatre-vingt-dix-neuf")] + [InlineData(223, "deux cent vingt-trois")] + [InlineData(2234, "deux mille deux cent trente-quatre")] + [InlineData(22345, "vingt-deux mille trois cent quarante-cinq")] + [InlineData(223456, "deux cent vingt-trois mille quatre cent cinquante-six")] + [InlineData(2234567, "deux millions deux cent trente-quatre mille cinq cent soixante-sept")] + [InlineData(22345678, "vingt-deux millions trois cent quarante-cinq mille six cent soixante-dix-huit")] + [InlineData(223456789, "deux cent vingt-trois millions quatre cent cinquante-six mille sept cent quatre-vingt-neuf")] + [InlineData(2147483646, "deux milliards cent quarante-sept millions quatre cent quatre-vingt-trois mille six cent quarante-six")] + [InlineData(1999, "mille neuf cent quatre-vingt-dix-neuf")] + [InlineData(2014, "deux mille quatorze")] + [InlineData(2048, "deux mille quarante-huit")] + public void ToWordsFrench(int number, string expected) + { + Assert.Equal(expected, number.ToWords()); + } + } +} diff --git a/src/Humanizer.Tests/NumberToOrdinalWordsTests.cs b/src/Humanizer.Tests/NumberToOrdinalWordsTests.cs index 079af043e..0cb830d8b 100644 --- a/src/Humanizer.Tests/NumberToOrdinalWordsTests.cs +++ b/src/Humanizer.Tests/NumberToOrdinalWordsTests.cs @@ -3,8 +3,10 @@ namespace Humanizer.Tests { - public class NumberToOrdinalWordsTests + public class NumberToOrdinalWordsTests : AmbientCulture { + public NumberToOrdinalWordsTests(): base("en-US") { } + [Theory] [InlineData(0, "zeroth")] [InlineData(1, "first")] diff --git a/src/Humanizer/Humanizer.csproj b/src/Humanizer/Humanizer.csproj index 42f8ae754..80f2ea6dc 100644 --- a/src/Humanizer/Humanizer.csproj +++ b/src/Humanizer/Humanizer.csproj @@ -74,6 +74,7 @@ + diff --git a/src/Humanizer/Localisation/NumberToWords/FrenchNumberToWordsConverter.cs b/src/Humanizer/Localisation/NumberToWords/FrenchNumberToWordsConverter.cs new file mode 100644 index 000000000..17039f4d6 --- /dev/null +++ b/src/Humanizer/Localisation/NumberToWords/FrenchNumberToWordsConverter.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; + +namespace Humanizer.Localisation.NumberToWords +{ + internal class FrenchNumberToWordsConverter : INumberToWordsConverter + { + private static readonly string[] UnitsMap = { "zéro", "un", "deux", "trois", "quatre", "cinq", "six", "sept", "huit", "neuf", "dix", "onze", "douze", "treize", "quatorze", "quinze", "seize", "dix-sept", "dix-huit", "dix-neuf" }; + private static readonly string[] TensMap = { "zéro", "dix", "vingt", "trente", "quarante", "cinquante", "soixante", "soixante-dix", "quatre-vingt", "quatre-vingt-dix"}; + + private static readonly Dictionary NumberExceptions = new Dictionary + { + {71, "soixante et onze"}, + {81, "quatre-vingt-un"}, + {91, "quatre-vingt-onze"} + }; + + public string Convert(int number) + { + if (number == 0) + return UnitsMap[0]; + if (number < 0) + return string.Format("moins {0}", Convert(Math.Abs(number))); + + var parts = new List(); + + if ((number / 1000000000) > 0) + { + parts.Add(string.Format("{0} milliard{1}", + Convert(number/1000000000), + number/1000000000 == 1 ? "" : "s")); + + number %= 1000000000; + } + + if ((number / 1000000) > 0) + { + parts.Add(string.Format("{0} million{1}", + Convert(number/1000000), + number/1000000 == 1 ? "" : "s")); + + number %= 1000000; + } + + if ((number / 1000) > 0) + { + parts.Add(number/1000 == 1 + ? string.Format("mille") + : string.Format("{0} mille", Convert(number/1000))); + + number %= 1000; + } + + if ((number / 100) > 0) + { + parts.Add(number < 200 ? "cent" : string.Format("{0} cent", Convert(number/100))); + number %= 100; + } + + if (number > 0) + { + if (NumberExceptions.ContainsKey(number)) + parts.Add(NumberExceptions[number]); + else if (number < 20) + parts.Add(UnitsMap[number]); + else + { + string lastPart; + if (number >= 70 && (number < 80 || number >= 90)) + { + int baseNumber = number < 80 ? 60 : 80; + lastPart = string.Format("{0}-{1}", TensMap[baseNumber/10], Convert(number - baseNumber)); + } + else + { + lastPart = TensMap[number/10]; + if ((number%10) > 0) + { + if ((number - 1) % 10 == 0) + lastPart += " et un"; + else + lastPart += string.Format("-{0}", UnitsMap[number%10]); + } + } + parts.Add(lastPart); + } + } + + return string.Join(" ", parts.ToArray()); + } + + public string ConvertToOrdinal(int number) + { + if (number == 1) + return "premier"; + + var convertedNumber = Convert(number); + + if (convertedNumber.EndsWith("s") && !convertedNumber.EndsWith("trois")) + convertedNumber = convertedNumber.TrimEnd('s'); + else if (convertedNumber.EndsWith("cinq")) + convertedNumber += "u"; + else if (convertedNumber.EndsWith("neuf")) + convertedNumber = convertedNumber.TrimEnd('f') + "v"; + + if (convertedNumber.StartsWith("un ")) + { + convertedNumber = convertedNumber.Remove(0, 3); + } + + convertedNumber = convertedNumber.TrimEnd('e'); + convertedNumber += "ième"; + return convertedNumber; + } + } +} diff --git a/src/Humanizer/NumberToWordsExtension.cs b/src/Humanizer/NumberToWordsExtension.cs index 3c980fdf4..37d49d27e 100644 --- a/src/Humanizer/NumberToWordsExtension.cs +++ b/src/Humanizer/NumberToWordsExtension.cs @@ -15,7 +15,8 @@ public static class NumberToWordsExtension { { "ar", () => new ArabicNumberToWordsConverter() }, { "fa", () => new FarsiNumberToWordsConverter() }, { "es", () => new SpanishNumberToWordsConverter() }, - { "pl", () => new PolishNumberToWordsConverter() } + { "pl", () => new PolishNumberToWordsConverter() }, + { "fr", () => new FrenchNumberToWordsConverter() } }; ///