From 563f5075079a1b067a1b2a20ec43569e37f5267c Mon Sep 17 00:00:00 2001 From: Igal Tabachnik Date: Sat, 12 Apr 2014 16:37:38 +0300 Subject: [PATCH 1/7] More aggressive NCrunch ignore patterns for .gitignore --- .gitignore | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) 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/* From ccabf355de058d2b6ee7d8258556c4aebacdcca5 Mon Sep 17 00:00:00 2001 From: Igal Tabachnik Date: Sat, 12 Apr 2014 20:58:23 +0300 Subject: [PATCH 2/7] ToWords Hebrew implementation --- src/Humanizer.Tests/Humanizer.Tests.csproj | 1 + .../Localisation/he/NumberToWordsTests.cs | 53 +++++++++++++ src/Humanizer/Humanizer.csproj | 2 + .../HebrewNumberToWordsConverter.cs | 74 +++++++++++++++++++ src/Humanizer/NumberToWordsExtension.cs | 22 ++++-- 5 files changed, 147 insertions(+), 5 deletions(-) create mode 100644 src/Humanizer.Tests/Localisation/he/NumberToWordsTests.cs create mode 100644 src/Humanizer/Localisation/NumberToWords/HebrewNumberToWordsConverter.cs 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..5d958aefe --- /dev/null +++ b/src/Humanizer.Tests/Localisation/he/NumberToWordsTests.cs @@ -0,0 +1,53 @@ +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(1024, "אלף עשרים וארבע")] + //[InlineData(1040, "אלף ארבעים")] + //[InlineData(20000, "עשרים אלף")] + //[InlineData(500000, "חמש מאוד אלף")] + //[InlineData(500001, "חמש מאות אלף ואחת")] + //[InlineData(1000000, "מיליון")] + //[InlineData(1000001, "מיליון ואחת")] + //[InlineData(1000000000, "מיליארד")] + //[InlineData(1000000001, "מיליארד ואחת")] + //[InlineData(9876543210, "תשע מיליארד שמונה מאות שבעים ושש מיליון חמש מאות ארבעים ושלוש אלף מאתיים ועשר")] + public void ToWords(int number, string expected) + { + Assert.Equal(expected, number.ToWords()); + } + } +} \ No newline at end of file diff --git a/src/Humanizer/Humanizer.csproj b/src/Humanizer/Humanizer.csproj index 2747348e3..fb4640660 100644 --- a/src/Humanizer/Humanizer.csproj +++ b/src/Humanizer/Humanizer.csproj @@ -81,6 +81,8 @@ + + diff --git a/src/Humanizer/Localisation/NumberToWords/HebrewNumberToWordsConverter.cs b/src/Humanizer/Localisation/NumberToWords/HebrewNumberToWordsConverter.cs new file mode 100644 index 000000000..3ac4d01d6 --- /dev/null +++ b/src/Humanizer/Localisation/NumberToWords/HebrewNumberToWordsConverter.cs @@ -0,0 +1,74 @@ +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 = { "עשר", "עשרים", "שלושים", "ארבעים", "חמישים", "שישים", "שבעים", "שמונים", "תשעים" }; + + 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 "אפס"; + + if (number <= 10) + return gender == GrammaticalGender.Masculine ? UnitsMasculine[number - 1] : UnitsFeminine[number - 1]; + + if (number < 20) + { + string unit = Convert(number % 10, gender); + unit = unit.Replace("יי", "י"); + return string.Format("{0} {1}", unit, gender == GrammaticalGender.Masculine ? "עשר" : "עשרה"); + } + if (number < 100) + { + if (number % 10 == 0) + return TensUnit[number / 10 - 1]; + + string unit = Convert(number % 10, gender); + return string.Format("{0} ו{1}", TensUnit[number / 10 - 1], unit); + } + if (number < 1000) + { + if (number == 100) return "מאה"; + if (number == 200) return "מאתיים"; + + int hundredsDigit = number / 100; + if ((hundredsDigit * 100) == number) + return UnitsFeminine[hundredsDigit - 1] + " מאות"; + + if (hundredsDigit == 1) + return ToHundredsString("מאה", number, gender); + if (hundredsDigit == 2) + return ToHundredsString("מאתיים", number, gender); + + return ToHundredsString(UnitsFeminine[hundredsDigit - 1] + " מאות", number, gender); + + } + + return number.ToString(); + } + + private string ToHundredsString(string prefix, int number, GrammaticalGender gender) + { + int tens = number % 100; + return string.Format("{0} {1}", + prefix, + tens < 20 + ? "ו" + Convert(tens, gender) + : Convert(tens, gender)); + } + } +} \ 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) { From e5a426aa81e66c3e2442b49cb65e02f851a4633b Mon Sep 17 00:00:00 2001 From: Igal Tabachnik Date: Sat, 12 Apr 2014 23:54:38 +0300 Subject: [PATCH 3/7] Adding ReSharper team-shared settings file to prevent wrapping single-line if-else statemes with braces (per Humanizer's convention) --- src/Humanizer.sln.DotSettings | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 src/Humanizer.sln.DotSettings 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 From 8bc53772b24c86edd0acd896f515e5395ebccfa2 Mon Sep 17 00:00:00 2001 From: Igal Tabachnik Date: Sun, 13 Apr 2014 01:51:22 +0300 Subject: [PATCH 4/7] Adding NumberToWords in Hebrew --- .../Localisation/he/NumberToWordsTests.cs | 35 +++-- .../HebrewNumberToWordsConverter.cs | 147 +++++++++++++----- 2 files changed, 135 insertions(+), 47 deletions(-) diff --git a/src/Humanizer.Tests/Localisation/he/NumberToWordsTests.cs b/src/Humanizer.Tests/Localisation/he/NumberToWordsTests.cs index 5d958aefe..7415211e5 100644 --- a/src/Humanizer.Tests/Localisation/he/NumberToWordsTests.cs +++ b/src/Humanizer.Tests/Localisation/he/NumberToWordsTests.cs @@ -34,20 +34,33 @@ public NumberToWordsTests() : base("he") { } [InlineData(500, "חמש מאות")] [InlineData(505, "חמש מאות וחמש")] [InlineData(725, "שבע מאות עשרים וחמש")] - //[InlineData(1000, "אלף")] - //[InlineData(1024, "אלף עשרים וארבע")] - //[InlineData(1040, "אלף ארבעים")] - //[InlineData(20000, "עשרים אלף")] - //[InlineData(500000, "חמש מאוד אלף")] - //[InlineData(500001, "חמש מאות אלף ואחת")] - //[InlineData(1000000, "מיליון")] - //[InlineData(1000001, "מיליון ואחת")] - //[InlineData(1000000000, "מיליארד")] - //[InlineData(1000000001, "מיליארד ואחת")] - //[InlineData(9876543210, "תשע מיליארד שמונה מאות שבעים ושש מיליון חמש מאות ארבעים ושלוש אלף מאתיים ועשר")] + [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(-2, "מינוס שתיים")] + public void NegativeToWords(int number, string expected) + { + Assert.Equal(expected, number.ToWords()); + } } } \ No newline at end of file diff --git a/src/Humanizer/Localisation/NumberToWords/HebrewNumberToWordsConverter.cs b/src/Humanizer/Localisation/NumberToWords/HebrewNumberToWordsConverter.cs index 3ac4d01d6..759b158de 100644 --- a/src/Humanizer/Localisation/NumberToWords/HebrewNumberToWordsConverter.cs +++ b/src/Humanizer/Localisation/NumberToWords/HebrewNumberToWordsConverter.cs @@ -5,9 +5,29 @@ namespace Humanizer.Localisation.NumberToWords { internal class HebrewNumberToWordsConverter : DefaultNumberToWordsConverter { - private static readonly string[] UnitsFeminine = { "אחת", "שתיים", "שלוש", "ארבע", "חמש", "שש", "שבע", "שמונה", "תשע", "עשר" }; - private static readonly string[] UnitsMasculine = { "אחד", "שניים", "שלושה", "ארבעה", "חמישה", "שישה", "שבעה", "שמונה", "תשעה", "עשרה" }; - private static readonly string[] TensUnit = { "עשר", "עשרים", "שלושים", "ארבעים", "חמישים", "שישים", "שבעים", "שמונים", "תשעים" }; + 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) { @@ -19,56 +39,111 @@ public override string Convert(int number, GrammaticalGender gender) { if (number < 0) return string.Format("מינוס {0}", Convert(-number, gender)); - + if (number == 0) - return "אפס"; + return UnitsFeminine[0]; - if (number <= 10) - return gender == GrammaticalGender.Masculine ? UnitsMasculine[number - 1] : UnitsFeminine[number - 1]; + var parts = new List(); + if (number >= (int)Group.Billions) + { + ToBigNumber(number, Group.Billions, parts); + number %= (int)Group.Billions; + } - if (number < 20) + if (number >= (int)Group.Millions) { - string unit = Convert(number % 10, gender); - unit = unit.Replace("יי", "י"); - return string.Format("{0} {1}", unit, gender == GrammaticalGender.Masculine ? "עשר" : "עשרה"); + ToBigNumber(number, Group.Millions, parts); + number %= (int)Group.Millions; } - if (number < 100) + + if (number >= (int)Group.Thousands) { - if (number % 10 == 0) - return TensUnit[number / 10 - 1]; + ToThousands(number, parts); + number %= (int)Group.Thousands; + } - string unit = Convert(number % 10, gender); - return string.Format("{0} ו{1}", TensUnit[number / 10 - 1], unit); + if (number >= (int)Group.Hundreds) + { + ToHundreds(number, parts); + number %= (int)Group.Hundreds; } - if (number < 1000) + + if (number > 0) { - if (number == 100) return "מאה"; - if (number == 200) return "מאתיים"; + bool appendAnd = parts.Count != 0; - int hundredsDigit = number / 100; - if ((hundredsDigit * 100) == number) - return UnitsFeminine[hundredsDigit - 1] + " מאות"; + 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); + } - if (hundredsDigit == 1) - return ToHundredsString("מאה", number, gender); - if (hundredsDigit == 2) - return ToHundredsString("מאתיים", number, gender); + 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 - return ToHundredsString(UnitsFeminine[hundredsDigit - 1] + " מאות", number, gender); + 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; - return number.ToString(); + 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 string ToHundredsString(string prefix, int number, GrammaticalGender gender) + private void ToHundreds(int number, List parts) { - int tens = number % 100; - return string.Format("{0} {1}", - prefix, - tens < 20 - ? "ו" + Convert(tens, gender) - : Convert(tens, gender)); + // 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 From f0998da53b446cbc758cb8d61cd389859f5ca930 Mon Sep 17 00:00:00 2001 From: Igal Tabachnik Date: Sun, 13 Apr 2014 03:13:09 +0300 Subject: [PATCH 5/7] Added tests for Masculine ToWords form --- .../Localisation/he/NumberToWordsTests.cs | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/Humanizer.Tests/Localisation/he/NumberToWordsTests.cs b/src/Humanizer.Tests/Localisation/he/NumberToWordsTests.cs index 7415211e5..ba54033fb 100644 --- a/src/Humanizer.Tests/Localisation/he/NumberToWordsTests.cs +++ b/src/Humanizer.Tests/Localisation/he/NumberToWordsTests.cs @@ -56,6 +56,55 @@ 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) From e75b2e4dbecfe9e0886187520856599ae8864ba2 Mon Sep 17 00:00:00 2001 From: Igal Tabachnik Date: Sun, 13 Apr 2014 03:30:11 +0300 Subject: [PATCH 6/7] Adding release notes --- release_notes.md | 1 + 1 file changed, 1 insertion(+) 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) From 68f2f218c07bfc951bb05e31aa8485d2126b9099 Mon Sep 17 00:00:00 2001 From: Igal Tabachnik Date: Sun, 13 Apr 2014 03:41:02 +0300 Subject: [PATCH 7/7] Removing a non-existing file from .csproj (incorrect merge??) --- src/Humanizer/Humanizer.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Humanizer/Humanizer.csproj b/src/Humanizer/Humanizer.csproj index fb4640660..1ee5c7b3d 100644 --- a/src/Humanizer/Humanizer.csproj +++ b/src/Humanizer/Humanizer.csproj @@ -82,7 +82,6 @@ -