diff --git a/.gitignore b/.gitignore
index 4212daa65..6d38a7388 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,10 +7,8 @@ obj/
*.swp
*.orig
*.nupkg
-*.crunchproject.local.xml
-*.crunchsolution.local.xml
-*.ncrunchsolution
-*.ncrunchproject
+*ncrunch*
+*crunch*.local.xml
*.cache
src/packages/*
PackageBuild/*
diff --git a/release_notes.md b/release_notes.md
index 1ef033e67..9ba539be0 100644
--- a/release_notes.md
+++ b/release_notes.md
@@ -1,5 +1,6 @@
###In Development
- [#166](https://github.com/MehdiK/Humanizer/pull/166): Added Dutch (NL) Number to words and ordinals
+ - [#199](https://github.com/MehdiK/Humanizer/pull/199): Added Hebrew Number to words (both genders)
[Commits](https://github.com/MehdiK/Humanizer/compare/v1.21.15...master)
diff --git a/src/Humanizer.Tests/Humanizer.Tests.csproj b/src/Humanizer.Tests/Humanizer.Tests.csproj
index 1291c6102..eb7af68d1 100644
--- a/src/Humanizer.Tests/Humanizer.Tests.csproj
+++ b/src/Humanizer.Tests/Humanizer.Tests.csproj
@@ -87,6 +87,7 @@
+
diff --git a/src/Humanizer.Tests/Localisation/he/NumberToWordsTests.cs b/src/Humanizer.Tests/Localisation/he/NumberToWordsTests.cs
new file mode 100644
index 000000000..ba54033fb
--- /dev/null
+++ b/src/Humanizer.Tests/Localisation/he/NumberToWordsTests.cs
@@ -0,0 +1,115 @@
+using Xunit;
+using Xunit.Extensions;
+
+namespace Humanizer.Tests.Localisation.he
+{
+ public class NumberToWordsTests : AmbientCulture
+ {
+ public NumberToWordsTests() : base("he") { }
+
+ [Theory]
+ [InlineData(0, "אפס")]
+ [InlineData(1, "אחת")]
+ [InlineData(2, "שתיים")]
+ [InlineData(3, "שלוש")]
+ [InlineData(4, "ארבע")]
+ [InlineData(5, "חמש")]
+ [InlineData(6, "שש")]
+ [InlineData(7, "שבע")]
+ [InlineData(8, "שמונה")]
+ [InlineData(9, "תשע")]
+ [InlineData(10, "עשר")]
+ [InlineData(11, "אחת עשרה")]
+ [InlineData(12, "שתים עשרה")]
+ [InlineData(19, "תשע עשרה")]
+ [InlineData(20, "עשרים")]
+ [InlineData(22, "עשרים ושתיים")]
+ [InlineData(50, "חמישים")]
+ [InlineData(99, "תשעים ותשע")]
+ [InlineData(100, "מאה")]
+ [InlineData(101, "מאה ואחת")]
+ [InlineData(111, "מאה ואחת עשרה")]
+ [InlineData(200, "מאתיים")]
+ [InlineData(241, "מאתיים ארבעים ואחת")]
+ [InlineData(500, "חמש מאות")]
+ [InlineData(505, "חמש מאות וחמש")]
+ [InlineData(725, "שבע מאות עשרים וחמש")]
+ [InlineData(1000, "אלף")]
+ [InlineData(1009, "אלף ותשע")]
+ [InlineData(1011, "אלף ואחת עשרה")]
+ [InlineData(1024, "אלף עשרים וארבע")]
+ [InlineData(1040, "אלף ארבעים")]
+ [InlineData(2000, "אלפיים")]
+ [InlineData(7021, "שבעת אלפים עשרים ואחת")]
+ [InlineData(20000, "עשרים אלף")]
+ [InlineData(28123, "עשרים ושמונה אלף מאה עשרים ושלוש")]
+ [InlineData(500000, "חמש מאות אלף")]
+ [InlineData(500001, "חמש מאות אלף ואחת")]
+ [InlineData(1000000, "מיליון")]
+ [InlineData(1000001, "מיליון ואחת")]
+ [InlineData(2000408, "שני מיליון ארבע מאות ושמונה")]
+ [InlineData(1000000000, "מיליארד")]
+ [InlineData(1000000001, "מיליארד ואחת")]
+ [InlineData(int.MaxValue /* 2147483647 */, "שני מיליארד מאה ארבעים ושבעה מיליון ארבע מאות שמונים ושלוש אלף שש מאות ארבעים ושבע")]
+ public void ToWords(int number, string expected)
+ {
+ Assert.Equal(expected, number.ToWords());
+ }
+
+ [Theory]
+ [InlineData(0, "אפס")]
+ [InlineData(1, "אחד")]
+ [InlineData(2, "שניים")]
+ [InlineData(3, "שלושה")]
+ [InlineData(4, "ארבעה")]
+ [InlineData(5, "חמישה")]
+ [InlineData(6, "שישה")]
+ [InlineData(7, "שבעה")]
+ [InlineData(8, "שמונה")]
+ [InlineData(9, "תשעה")]
+ [InlineData(10, "עשרה")]
+ [InlineData(11, "אחד עשר")]
+ [InlineData(12, "שנים עשר")]
+ [InlineData(19, "תשעה עשר")]
+ [InlineData(20, "עשרים")]
+ [InlineData(22, "עשרים ושניים")]
+ [InlineData(50, "חמישים")]
+ [InlineData(99, "תשעים ותשעה")]
+ [InlineData(100, "מאה")]
+ [InlineData(101, "מאה ואחד")]
+ [InlineData(111, "מאה ואחד עשר")]
+ [InlineData(200, "מאתיים")]
+ [InlineData(241, "מאתיים ארבעים ואחד")]
+ [InlineData(500, "חמש מאות")]
+ [InlineData(505, "חמש מאות וחמישה")]
+ [InlineData(725, "שבע מאות עשרים וחמישה")]
+ [InlineData(1000, "אלף")]
+ [InlineData(1009, "אלף ותשעה")]
+ [InlineData(1011, "אלף ואחד עשר")]
+ [InlineData(1024, "אלף עשרים וארבעה")]
+ [InlineData(1040, "אלף ארבעים")]
+ [InlineData(2000, "אלפיים")]
+ [InlineData(7021, "שבעת אלפים עשרים ואחד")]
+ [InlineData(20000, "עשרים אלף")]
+ [InlineData(28123, "עשרים ושמונה אלף מאה עשרים ושלושה")]
+ [InlineData(500000, "חמש מאות אלף")]
+ [InlineData(500001, "חמש מאות אלף ואחד")]
+ [InlineData(1000000, "מיליון")]
+ [InlineData(1000001, "מיליון ואחד")]
+ [InlineData(2000408, "שני מיליון ארבע מאות ושמונה")]
+ [InlineData(1000000000, "מיליארד")]
+ [InlineData(1000000001, "מיליארד ואחד")]
+ [InlineData(int.MaxValue /* 2147483647 */, "שני מיליארד מאה ארבעים ושבעה מיליון ארבע מאות שמונים ושלוש אלף שש מאות ארבעים ושבעה")]
+ public void ToWordsMasculine(int number, string expected)
+ {
+ Assert.Equal(expected, number.ToWords(GrammaticalGender.Masculine));
+ }
+
+ [Theory]
+ [InlineData(-2, "מינוס שתיים")]
+ public void NegativeToWords(int number, string expected)
+ {
+ Assert.Equal(expected, number.ToWords());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Humanizer.sln.DotSettings b/src/Humanizer.sln.DotSettings
new file mode 100644
index 000000000..59857493a
--- /dev/null
+++ b/src/Humanizer.sln.DotSettings
@@ -0,0 +1,2 @@
+
+ ONLY_FOR_MULTILINE
\ No newline at end of file
diff --git a/src/Humanizer/Humanizer.csproj b/src/Humanizer/Humanizer.csproj
index 2747348e3..1ee5c7b3d 100644
--- a/src/Humanizer/Humanizer.csproj
+++ b/src/Humanizer/Humanizer.csproj
@@ -81,6 +81,7 @@
+
diff --git a/src/Humanizer/Localisation/NumberToWords/HebrewNumberToWordsConverter.cs b/src/Humanizer/Localisation/NumberToWords/HebrewNumberToWordsConverter.cs
new file mode 100644
index 000000000..759b158de
--- /dev/null
+++ b/src/Humanizer/Localisation/NumberToWords/HebrewNumberToWordsConverter.cs
@@ -0,0 +1,149 @@
+using System;
+using System.Collections.Generic;
+
+namespace Humanizer.Localisation.NumberToWords
+{
+ internal class HebrewNumberToWordsConverter : DefaultNumberToWordsConverter
+ {
+ private static readonly string[] UnitsFeminine = { "אפס", "אחת", "שתיים", "שלוש", "ארבע", "חמש", "שש", "שבע", "שמונה", "תשע", "עשר" };
+ private static readonly string[] UnitsMasculine = { "אפס", "אחד", "שניים", "שלושה", "ארבעה", "חמישה", "שישה", "שבעה", "שמונה", "תשעה", "עשרה" };
+ private static readonly string[] TensUnit = { "עשר", "עשרים", "שלושים", "ארבעים", "חמישים", "שישים", "שבעים", "שמונים", "תשעים" };
+
+ private class DescriptionAttribute : Attribute
+ {
+ public string Description { get; set; }
+
+ public DescriptionAttribute(string description)
+ {
+ Description = description;
+ }
+ }
+
+ private enum Group
+ {
+ Hundreds = 100,
+ Thousands = 1000,
+ [Description("מיליון")]
+ Millions = 1000000,
+ [Description("מיליארד")]
+ Billions = 1000000000
+ }
+
+ public override string Convert(int number)
+ {
+ // in Hebrew, the default number gender form is feminine.
+ return Convert(number, GrammaticalGender.Feminine);
+ }
+
+ public override string Convert(int number, GrammaticalGender gender)
+ {
+ if (number < 0)
+ return string.Format("מינוס {0}", Convert(-number, gender));
+
+ if (number == 0)
+ return UnitsFeminine[0];
+
+ var parts = new List();
+ if (number >= (int)Group.Billions)
+ {
+ ToBigNumber(number, Group.Billions, parts);
+ number %= (int)Group.Billions;
+ }
+
+ if (number >= (int)Group.Millions)
+ {
+ ToBigNumber(number, Group.Millions, parts);
+ number %= (int)Group.Millions;
+ }
+
+ if (number >= (int)Group.Thousands)
+ {
+ ToThousands(number, parts);
+ number %= (int)Group.Thousands;
+ }
+
+ if (number >= (int)Group.Hundreds)
+ {
+ ToHundreds(number, parts);
+ number %= (int)Group.Hundreds;
+ }
+
+ if (number > 0)
+ {
+ bool appendAnd = parts.Count != 0;
+
+ if (number <= 10)
+ {
+ string unit = gender == GrammaticalGender.Masculine ? UnitsMasculine[number] : UnitsFeminine[number];
+ if (appendAnd)
+ unit = "ו" + unit;
+ parts.Add(unit);
+ }
+ else if (number < 20)
+ {
+ string unit = Convert(number % 10, gender);
+ unit = unit.Replace("יי", "י");
+ unit = string.Format("{0} {1}", unit, gender == GrammaticalGender.Masculine ? "עשר" : "עשרה");
+ if (appendAnd)
+ unit = "ו" + unit;
+ parts.Add(unit);
+ }
+ else
+ {
+ string tenUnit = TensUnit[number / 10 - 1];
+ if (number % 10 == 0)
+ parts.Add(tenUnit);
+ else
+ {
+ string unit = Convert(number % 10, gender);
+ parts.Add(string.Format("{0} ו{1}", tenUnit, unit));
+ }
+ }
+ }
+
+ return string.Join(" ", parts);
+ }
+
+ private void ToBigNumber(int number, Group group, List parts)
+ {
+ // Big numbers (million and above) always use the masculine form
+ // See https://www.safa-ivrit.org/dikduk/numbers.php
+
+ int digits = number / (int)@group;
+ if (digits == 2)
+ parts.Add("שני");
+ else if (digits > 2)
+ parts.Add(Convert(digits, GrammaticalGender.Masculine));
+ parts.Add(@group.Humanize());
+ }
+
+ private void ToThousands(int number, List parts)
+ {
+ int thousands = number / (int)Group.Thousands;
+
+ if (thousands == 1)
+ parts.Add("אלף");
+ else if (thousands == 2)
+ parts.Add("אלפיים");
+ else if (thousands <= 10)
+ parts.Add(UnitsFeminine[thousands] + "ת" + " אלפים");
+ else
+ parts.Add(Convert(thousands) + " אלף");
+ }
+
+ private void ToHundreds(int number, List parts)
+ {
+ // For hundreds, Hebrew is using the feminine form
+ // See https://www.safa-ivrit.org/dikduk/numbers.php
+
+ int hundreds = number / (int)Group.Hundreds;
+
+ if (hundreds == 1)
+ parts.Add("מאה");
+ else if (hundreds == 2)
+ parts.Add("מאתיים");
+ else
+ parts.Add(UnitsFeminine[hundreds] + " מאות");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Humanizer/NumberToWordsExtension.cs b/src/Humanizer/NumberToWordsExtension.cs
index eae615f55..4f6c531bb 100644
--- a/src/Humanizer/NumberToWordsExtension.cs
+++ b/src/Humanizer/NumberToWordsExtension.cs
@@ -21,7 +21,8 @@ public static class NumberToWordsExtension
{"pt-BR", () => new BrazilianPortugueseNumberToWordsConverter()},
{"ru", () => new RussianNumberToWordsConverter()},
{"fr", () => new FrenchNumberToWordsConverter()},
- {"nl", () => new DutchNumberToWordsConverter()}
+ {"nl", () => new DutchNumberToWordsConverter()},
+ {"he", () => new HebrewNumberToWordsConverter()}
};
///
@@ -35,12 +36,23 @@ public static string ToWords(this int number)
}
///
- /// for Russian locale
- /// 1.ToWords(GrammaticalGender.Masculine) -> "один"
- /// 1.ToWords(GrammaticalGender.Feminine) -> "одна"
+ /// For locales that support gender-specific forms
///
+ ///
+ /// Russian:
+ ///
+ /// 1.ToWords(GrammaticalGender.Masculine) -> "один"
+ /// 1.ToWords(GrammaticalGender.Feminine) -> "одна"
+ ///
+ /// Hebrew:
+ ///
+ /// 1.ToWords(GrammaticalGender.Masculine) -> "אחד"
+ /// 1.ToWords(GrammaticalGender.Feminine) -> "אחת"
+ ///
+ ///
+ ///
/// Number to be turned to words
- /// The grammatical gender to use for output words. Defaults to masculine.
+ /// The grammatical gender to use for output words
///
public static string ToWords(this int number, GrammaticalGender gender)
{