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() }
};
///