diff --git a/src/Humanizer.Tests/Humanizer.Tests.csproj b/src/Humanizer.Tests/Humanizer.Tests.csproj
index f425164d4..afcad6e05 100644
--- a/src/Humanizer.Tests/Humanizer.Tests.csproj
+++ b/src/Humanizer.Tests/Humanizer.Tests.csproj
@@ -71,6 +71,9 @@
+
+
+
diff --git a/src/Humanizer.Tests/Localisation/af/DateHumanizeTests.cs b/src/Humanizer.Tests/Localisation/af/DateHumanizeTests.cs
new file mode 100644
index 000000000..5bad15fb2
--- /dev/null
+++ b/src/Humanizer.Tests/Localisation/af/DateHumanizeTests.cs
@@ -0,0 +1,117 @@
+using System;
+using Humanizer.Localisation;
+using Xunit;
+using Xunit.Extensions;
+
+namespace Humanizer.Tests.Localisation.af
+{
+ public class DateHumanizeTests : AmbientCulture
+ {
+ public DateHumanizeTests() : base("af") { }
+
+ [Theory]
+ [InlineData(2, "2 dae gelede")]
+ [InlineData(1, "gister")]
+ public void DaysAgo(int days, string expected)
+ {
+ DateHumanize.Verify(expected, days, TimeUnit.Day, Tense.Past);
+ }
+
+ [Theory]
+ [InlineData(2, "2 ure gelede")]
+ [InlineData(1, "1 uur terug")]
+ public void HoursAgo(int hours, string expected)
+ {
+ DateHumanize.Verify(expected, hours, TimeUnit.Hour, Tense.Past);
+ }
+
+ [Theory]
+ [InlineData(2, "2 minute terug")]
+ [InlineData(1, "1 minuut terug")]
+ [InlineData(60, "1 uur terug")]
+ public void MinutesAgo(int minutes, string expected)
+ {
+ DateHumanize.Verify(expected, minutes, TimeUnit.Minute, Tense.Past);
+ }
+
+ [Theory]
+ [InlineData(2, "2 maande gelede")]
+ [InlineData(1, "1 maand gelede")]
+ public void MonthsAgo(int months, string expected)
+ {
+ DateHumanize.Verify(expected, months, TimeUnit.Month, Tense.Past);
+ }
+
+ [Theory]
+ [InlineData(2, "2 sekondes terug")]
+ [InlineData(1, "1 sekonde terug")]
+ public void SecondsAgo(int seconds, string expected)
+ {
+ DateHumanize.Verify(expected, seconds, TimeUnit.Second, Tense.Past);
+ }
+
+ [Theory]
+ [InlineData(2, "2 jaar gelede")]
+ [InlineData(1, "1 jaar gelede")]
+ public void YearsAgo(int years, string expected)
+ {
+ DateHumanize.Verify(expected, years, TimeUnit.Year, Tense.Past);
+ }
+
+ [Theory]
+ [InlineData(2, "oor 2 dae")]
+ [InlineData(1, "môre")]
+ public void DaysFromNow(int days, string expected)
+ {
+ DateHumanize.Verify(expected, days, TimeUnit.Day, Tense.Future);
+ }
+
+ [Theory]
+ [InlineData(2, "oor 2 ure")]
+ [InlineData(1, "oor 1 uur")]
+ public void HoursFromNow(int hours, string expected)
+ {
+ DateHumanize.Verify(expected, hours, TimeUnit.Hour, Tense.Future);
+ }
+
+ [Theory]
+ [InlineData(2, "oor 2 minute")]
+ [InlineData(1, "oor 1 minuut")]
+ [InlineData(60, "oor 1 uur")]
+ public void MinutesFromNow(int minutes, string expected)
+ {
+ DateHumanize.Verify(expected, minutes, TimeUnit.Minute, Tense.Future);
+ }
+
+ [Theory]
+ [InlineData(2, "oor 2 maande")]
+ [InlineData(1, "oor 1 maand")]
+ public void MonthsFromNow(int months, string expected)
+ {
+ DateHumanize.Verify(expected, months, TimeUnit.Month, Tense.Future);
+ }
+
+ [Theory]
+ [InlineData(2, "oor 2 sekondes")]
+ [InlineData(1, "oor 1 sekonde")]
+ public void SecondsFromNow(int seconds, string expected)
+ {
+ DateHumanize.Verify(expected, seconds, TimeUnit.Second, Tense.Future);
+ }
+
+ [Theory]
+ [InlineData(2, "oor 2 jaar")]
+ [InlineData(1, "oor 1 jaar")]
+ public void YearsFromNow(int years, string expected)
+ {
+ DateHumanize.Verify(expected, years, TimeUnit.Year, Tense.Future);
+ }
+
+ [Theory]
+ [InlineData(0, "nou")]
+ public void RightNow(int seconds, string expected)
+ {
+ DateHumanize.Verify(expected, seconds, TimeUnit.Second, Tense.Future);
+ }
+ }
+}
diff --git a/src/Humanizer.Tests/Localisation/af/NumberToWordsTests.cs b/src/Humanizer.Tests/Localisation/af/NumberToWordsTests.cs
new file mode 100644
index 000000000..51e915f17
--- /dev/null
+++ b/src/Humanizer.Tests/Localisation/af/NumberToWordsTests.cs
@@ -0,0 +1,115 @@
+using System.Globalization;
+using Xunit;
+using Xunit.Extensions;
+
+namespace Humanizer.Tests.Localisation.af
+{
+ public class AfrikaansNumberToWordsTests : AmbientCulture
+ {
+ public AfrikaansNumberToWordsTests()
+ : base("af")
+ {
+ }
+
+ [InlineData(1, "een")]
+ [InlineData(10, "tien")]
+ [InlineData(11, "elf")]
+ [InlineData(20, "twintig")]
+ [InlineData(122, "een honderd twee en twintig")]
+ [InlineData(3501, "drie duisend vyf honderd en een")]
+ [InlineData(100, "een honderd")]
+ [InlineData(1000, "een duisend")]
+ [InlineData(100000, "een honderd duisend")]
+ [InlineData(1000000, "een miljoen")]
+ [InlineData(10000000, "tien miljoen")]
+ [InlineData(100000000, "een honderd miljoen")]
+ [InlineData(1000000000, "een miljard")]
+ [InlineData(111, "een honderd en elf")]
+ [InlineData(1111, "een duisend een honderd en elf")]
+ [InlineData(111111, "een honderd en elf duisend een honderd en elf")]
+ [InlineData(1111111, "een miljoen een honderd en elf duisend een honderd en elf")]
+ [InlineData(11111111, "elf miljoen een honderd en elf duisend een honderd en elf")]
+ [InlineData(111111111, "een honderd en elf miljoen een honderd en elf duisend een honderd en elf")]
+ [InlineData(1111111111, "een miljard een honderd en elf miljoen een honderd en elf duisend een honderd en elf")]
+ [InlineData(123, "een honderd drie en twintig")]
+ [InlineData(1234, "een duisend twee honderd vier en dertig")]
+ [InlineData(12345, "twaalf duisend drie honderd vyf en veertig")]
+ [InlineData(123456, "een honderd drie en twintig duisend vier honderd ses en vyftig")]
+ [InlineData(1234567, "een miljoen twee honderd vier en dertig duisend vyf honderd sewe en sestig")]
+ [InlineData(12345678, "twaalf miljoen drie honderd vyf en veertig duisend ses honderd agt en sewentig")]
+ [InlineData(123456789, "een honderd drie en twintig miljoen vier honderd ses en vyftig duisend sewe honderd nege en tagtig")]
+ [InlineData(1234567890, "een miljard twee honderd vier en dertig miljoen vyf honderd sewe en sestig duisend agt honderd en negentig")]
+ [Theory]
+ public void ToWords(int number, string expected)
+ {
+ Assert.Equal(expected, number.ToWords());
+ }
+
+ [Theory]
+ [InlineData(0, "nulste")]
+ [InlineData(1, "eerste")]
+ [InlineData(2, "tweede")]
+ [InlineData(3, "derde")]
+ [InlineData(4, "vierde")]
+ [InlineData(5, "vyfde")]
+ [InlineData(6, "sesde")]
+ [InlineData(7, "sewende")]
+ [InlineData(8, "agste")]
+ [InlineData(9, "negende")]
+ [InlineData(10, "tiende")]
+ [InlineData(11, "elfde")]
+ [InlineData(12, "twaalfde")]
+ [InlineData(13, "dertiende")]
+ [InlineData(14, "veertiende")]
+ [InlineData(15, "vyftiende")]
+ [InlineData(16, "sestiende")]
+ [InlineData(17, "sewentiende")]
+ [InlineData(18, "agtiende")]
+ [InlineData(19, "negentiende")]
+ [InlineData(20, "twintigste")]
+ [InlineData(21, "een en twintigste")]
+ [InlineData(22, "twee en twintigste")]
+ [InlineData(30, "dertigste")]
+ [InlineData(40, "veertigste")]
+ [InlineData(50, "vyftigste")]
+ [InlineData(60, "sestigste")]
+ [InlineData(70, "sewentigste")]
+ [InlineData(80, "tagtigste")]
+ [InlineData(90, "negentigste")]
+ [InlineData(95, "vyf en negentigste")]
+ [InlineData(96, "ses en negentigste")]
+ [InlineData(100, "honderdste")]
+ [InlineData(112, "honderd en twaalfde")]
+ [InlineData(120, "honderd en twintigste")]
+ [InlineData(121, "honderd een en twintigste")]
+ [InlineData(1000, "duisendste")]
+ [InlineData(1001, "duisend en eerste")]
+ [InlineData(1021, "duisend een en twintigste")]
+ [InlineData(10000, "tien duisendste")]
+ [InlineData(10121, "tien duisend een honderd een en twintigste")]
+ [InlineData(100000, "honderd duisendste")]
+ [InlineData(1000000, "miljoenste")]
+ public void ToOrdinalWords(int number, string words)
+ {
+ Assert.Equal(words, number.ToOrdinalWords());
+ }
+
+ [Theory]
+ [InlineData(11, "en-US", "eleven")]
+ [InlineData(22, "ar", "اثنان و عشرون")]
+ [InlineData(40, "ru", "сорок")]
+ public void ToWords_CanSpecifyCultureExplicitly(int number, string culture, string expected)
+ {
+ Assert.Equal(expected, number.ToWords(new CultureInfo(culture)));
+ }
+
+ [Theory]
+ [InlineData(1021, "en-US", "thousand and twenty-first")]
+ [InlineData(21, "ar", "الحادي و العشرون")]
+ [InlineData(1112, "ru", "одна тысяча сто двенадцатый")]
+ public void ToOrdinalWords_CanSpecifyCultureExplicitly(int number, string culture, string expected)
+ {
+ Assert.Equal(expected, number.ToOrdinalWords(new CultureInfo(culture)));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Humanizer.Tests/Localisation/af/TimeSpanHumanizeTests.cs b/src/Humanizer.Tests/Localisation/af/TimeSpanHumanizeTests.cs
new file mode 100644
index 000000000..5a6f1dca9
--- /dev/null
+++ b/src/Humanizer.Tests/Localisation/af/TimeSpanHumanizeTests.cs
@@ -0,0 +1,94 @@
+using System;
+using Xunit;
+
+namespace Humanizer.Tests.Localisation.af
+{
+ public class TimeSpanHumanizeTests : AmbientCulture
+ {
+ public TimeSpanHumanizeTests() : base("af") { }
+
+ [Fact]
+ public void TwoWeeks()
+ {
+ Assert.Equal("2 weke", TimeSpan.FromDays(14).Humanize());
+ }
+
+ [Fact]
+ public void OneWeek()
+ {
+ Assert.Equal("1 week", TimeSpan.FromDays(7).Humanize());
+ }
+
+ [Fact]
+ public void SixDays()
+ {
+ Assert.Equal("6 dae", TimeSpan.FromDays(6).Humanize());
+ }
+
+ [Fact]
+ public void TwoDays()
+ {
+ Assert.Equal("2 dae", TimeSpan.FromDays(2).Humanize());
+ }
+
+ [Fact]
+ public void OneDay()
+ {
+ Assert.Equal("1 dag", TimeSpan.FromDays(1).Humanize());
+ }
+
+ [Fact]
+ public void TwoHours()
+ {
+ Assert.Equal("2 ure", TimeSpan.FromHours(2).Humanize());
+ }
+
+ [Fact]
+ public void OneHour()
+ {
+ Assert.Equal("1 uur", TimeSpan.FromHours(1).Humanize());
+ }
+
+ [Fact]
+ public void TwoMinutes()
+ {
+ Assert.Equal("2 minute", TimeSpan.FromMinutes(2).Humanize());
+ }
+
+ [Fact]
+ public void OneMinute()
+ {
+ Assert.Equal("1 minuut", TimeSpan.FromMinutes(1).Humanize());
+ }
+
+ [Fact]
+ public void TwoSeconds()
+ {
+ Assert.Equal("2 sekondes", TimeSpan.FromSeconds(2).Humanize());
+ }
+
+ [Fact]
+ public void OneSecond()
+ {
+ Assert.Equal("1 sekond", TimeSpan.FromSeconds(1).Humanize());
+ }
+
+ [Fact]
+ public void TwoMilliseconds()
+ {
+ Assert.Equal("2 millisekondes", TimeSpan.FromMilliseconds(2).Humanize());
+ }
+
+ [Fact]
+ public void OneMillisecond()
+ {
+ Assert.Equal("1 millisekond", TimeSpan.FromMilliseconds(1).Humanize());
+ }
+
+ [Fact]
+ public void NoTime()
+ {
+ Assert.Equal("geen tyd", TimeSpan.Zero.Humanize());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Humanizer/Configuration/NumberToWordsConverterRegistry.cs b/src/Humanizer/Configuration/NumberToWordsConverterRegistry.cs
index 0245e9b82..cc09a39dc 100644
--- a/src/Humanizer/Configuration/NumberToWordsConverterRegistry.cs
+++ b/src/Humanizer/Configuration/NumberToWordsConverterRegistry.cs
@@ -7,6 +7,7 @@ internal class NumberToWordsConverterRegistry : LocaliserRegistry new DefaultNumberToWordsConverter(culture))
{
+ Register("af", new AfrikaansNumberToWordsConverter());
Register("en", new EnglishNumberToWordsConverter());
Register("ar", new ArabicNumberToWordsConverter());
Register("fa", new FarsiNumberToWordsConverter());
diff --git a/src/Humanizer/Humanizer.csproj b/src/Humanizer/Humanizer.csproj
index 1d91d3479..69b101dfd 100644
--- a/src/Humanizer/Humanizer.csproj
+++ b/src/Humanizer/Humanizer.csproj
@@ -75,6 +75,7 @@
+
@@ -243,7 +244,7 @@
-
+
diff --git a/src/Humanizer/Localisation/NumberToWords/AfrikaansNumberToWordsConverter.cs b/src/Humanizer/Localisation/NumberToWords/AfrikaansNumberToWordsConverter.cs
new file mode 100644
index 000000000..af924efca
--- /dev/null
+++ b/src/Humanizer/Localisation/NumberToWords/AfrikaansNumberToWordsConverter.cs
@@ -0,0 +1,136 @@
+using System;
+using System.Collections.Generic;
+
+namespace Humanizer.Localisation.NumberToWords
+{
+ internal class AfrikaansNumberToWordsConverter : GenderlessNumberToWordsConverter
+ {
+ private static readonly string[] UnitsMap = { "nul", "een", "twee", "drie", "vier", "vyf", "ses", "sewe", "agt", "nege", "tien", "elf", "twaalf", "dertien", "veertien", "vyftien", "sestien", "sewentien", "agtien", "negentien" };
+ private static readonly string[] TensMap = { "nul", "tien", "twintig", "dertig", "veertig", "vyftig", "sestig", "sewentig", "tagtig", "negentig" };
+
+ private static readonly Dictionary OrdinalExceptions = new Dictionary
+ {
+ {0, "nulste"},
+ {1, "eerste"},
+ {3, "derde"},
+ {7, "sewende"},
+ {8, "agste"},
+ {9, "negende"},
+ {10, "tiende"},
+ {14, "veertiende"},
+ {17, "sewentiende"},
+ {19, "negentiende"}
+ };
+
+ public override string Convert(int number)
+ {
+ return Convert(number, false);
+ }
+
+ public override string ConvertToOrdinal(int number)
+ {
+ return Convert(number, true);
+ }
+
+ private string Convert(int number, bool isOrdinal)
+ {
+ if (number == 0)
+ return GetUnitValue(0, isOrdinal);
+
+ if (number < 0)
+ return string.Format("minus {0}", Convert(-number));
+
+ var parts = new List();
+
+ if ((number / 1000000000) > 0)
+ {
+ parts.Add(string.Format("{0} miljard", Convert(number / 1000000000)));
+ number %= 1000000000;
+ }
+
+ if ((number / 1000000) > 0)
+ {
+ parts.Add(string.Format("{0} miljoen", Convert(number / 1000000)));
+ number %= 1000000;
+ }
+
+ if ((number / 1000) > 0)
+ {
+ parts.Add(string.Format("{0} duisend", Convert(number / 1000)));
+ number %= 1000;
+ }
+
+ if ((number / 100) > 0)
+ {
+ parts.Add(string.Format("{0} honderd", Convert(number / 100)));
+ number %= 100;
+ }
+
+ if (number > 0)
+ {
+ //if (parts.Count != 0)
+ // parts.Add("en");
+
+ if (number < 20)
+ {
+ if (parts.Count > 0)
+ parts.Add("en");
+ parts.Add(GetUnitValue(number, isOrdinal));
+ }
+ else
+ {
+ var lastPartValue = (number/10)*10;
+ var lastPart = TensMap[number/10];
+ if ((number%10) > 0)
+ lastPart = string.Format("{0} en {1}",GetUnitValue(number % 10, false), isOrdinal ? GetUnitValue(lastPartValue, isOrdinal) : lastPart);
+ else if ((number%10) == 0)
+ lastPart = string.Format("{0}{1}{2}", parts.Count > 0 ? "en " : "", lastPart, isOrdinal ? "ste" : "");
+ else if (isOrdinal)
+ lastPart = lastPart.TrimEnd('~') + "ste";
+
+ parts.Add(lastPart);
+ }
+ }
+ else if (isOrdinal)
+ parts[parts.Count - 1] += "ste";
+
+ string toWords = string.Join(" ", parts.ToArray());
+
+ if (isOrdinal)
+ toWords = RemoveOnePrefix(toWords);
+
+ return toWords;
+ }
+
+ private static string GetUnitValue(int number, bool isOrdinal)
+ {
+ if (isOrdinal)
+ {
+ string exceptionString;
+ if (ExceptionNumbersToWords(number, out exceptionString))
+ return exceptionString;
+ else if (number > 19)
+ return TensMap[number/10] + "ste";
+ else
+ return UnitsMap[number] + "de";
+ }
+ else
+ return UnitsMap[number];
+ }
+
+ private static string RemoveOnePrefix(string toWords)
+ {
+ // one hundred => hundredth
+ if (toWords.IndexOf("een", StringComparison.Ordinal) == 0)
+ if (toWords.IndexOf("een en", StringComparison.Ordinal) != 0)
+ toWords = toWords.Remove(0, 4);
+
+ return toWords;
+ }
+
+ private static bool ExceptionNumbersToWords(int number, out string words)
+ {
+ return OrdinalExceptions.TryGetValue(number, out words);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Humanizer/Properties/Resources.af.resx b/src/Humanizer/Properties/Resources.af.resx
index 9197486c6..955479a5a 100644
--- a/src/Humanizer/Properties/Resources.af.resx
+++ b/src/Humanizer/Properties/Resources.af.resx
@@ -118,39 +118,117 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
- 'n sekonde gelede
+ 1 sekonde terug
{0} sekondes terug
- 'n minuut gelede
+ 1 minuut terug
{0} minute terug
- 'n uur gelede
+ 1 uur terug
- {0} ure terug
+ {0} ure gelede
gister
- {0} dae terug
+ {0} dae gelede
- 'n maand gelede
+ 1 maand gelede
- {0} maande terug
+ {0} maande gelede
- 'n jaar gelede
+ 1 jaar gelede
- {0} jare terug
+ {0} jaar gelede
+
+
+ {0} dae
+
+
+ {0} ure
+
+
+ {0} millisekondes
+
+
+ {0} minute
+
+
+ {0} sekondes
+
+
+ {0} weke
+
+
+ 1 dag
+
+
+ 1 uur
+
+
+ 1 millisekond
+
+
+ 1 minuut
+
+
+ 1 sekond
+
+
+ 1 week
+
+
+ geen tyd
+
+
+ oor {0} dae
+
+
+ oor {0} ure
+
+
+ oor {0} minute
+
+
+ oor {0} maande
+
+
+ oor {0} sekondes
+
+
+ oor {0} jaar
+
+
+ nou
+
+
+ môre
+
+
+ oor 1 uur
+
+
+ oor 1 minuut
+
+
+ oor 1 maand
+
+
+ oor 1 sekonde
+
+
+ oor 1 jaar
\ No newline at end of file