diff --git a/readme.md b/readme.md index d3846b2b7..fe15f78f5 100644 --- a/readme.md +++ b/readme.md @@ -560,6 +560,13 @@ The possible values are `GrammaticalGender.Masculine`, `GrammaticalGender.Femini Obviously this only applies to some cultures. For others passing gender in doesn't make any difference in the result. +Also, culture to use can be specified explicitly. If it is not, current thread's current UI culture is used. Here's an example: + +```C# +11.ToWords(new CultureInfo("en")) => "eleven" +1.ToWords(GrammaticalGender.Masculine, new CultureInfo("ru")) => "один" +``` + ###Number to ordinal words This is kind of mixing `ToWords` with `Ordinalize`. You can call `ToOrdinalWords` on a number to get an ordinal representation of the number in words! For example: @@ -592,6 +599,13 @@ The possible values are `GrammaticalGender.Masculine`, `GrammaticalGender.Femini Obviously this only applies to some cultures. For others passing gender in doesn't make any difference in the result. +Also, culture to use can be specified explicitly. If it is not, current thread's current UI culture is used. Here's an example: + +```C# +10.ToOrdinalWords(new CultureInfo("en-US")) => "tenth" +1.ToOrdinalWords(GrammaticalGender.Masculine, new CulureInfo("pt-BR")) => "primeiro" +``` + ###Roman numerals Humanizer can change numbers to Roman numerals using the `ToRoman` extension. The numbers 1 to 10 can be expressed in Roman numerals as follows: diff --git a/release_notes.md b/release_notes.md index 9a7730feb..711190fb8 100644 --- a/release_notes.md +++ b/release_notes.md @@ -5,6 +5,7 @@ - [#278](https://github.com/MehdiK/Humanizer/pull/278): Changed DefaultDateTimeHumanizeStrategy to turn 60 min to one hour not 45 - [#283](https://github.com/MehdiK/Humanizer/pull/283): Added Neutral nb support for DateTime and TimeSpan Humanize - [#286](https://github.com/MehdiK/Humanizer/pull/286): Added optional Culture parameter to DateTime.Humanize & TimeSpan.Humanize + - [#295](https://github.com/MehdiK/Humanizer/pull/295): Added optional Culture parameter to NumberToWords [Commits](https://github.com/MehdiK/Humanizer/compare/v1.26.1...master) diff --git a/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt b/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt index 3a2bd3dd3..eba13a1c6 100644 --- a/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt +++ b/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt @@ -84,7 +84,9 @@ public class Configurator public class LocaliserRegistry`1 { public LocaliserRegistry`1(TLocaliser defaultLocaliser) { } + public LocaliserRegistry`1(System.Func<, > defaultLocaliser) { } public void Register(string localeCode, TLocaliser localiser) { } + public void Register(string localeCode, System.Func<, > localiser) { } public TLocaliser ResolveForCulture(System.Globalization.CultureInfo culture) { } public TLocaliser ResolveForUiCulture() { } } @@ -291,10 +293,10 @@ public class NumberToTimeSpanExtensions public class NumberToWordsExtension { - public string ToOrdinalWords(int number) { } - public string ToOrdinalWords(int number, Humanizer.GrammaticalGender gender) { } - public string ToWords(int number) { } - public string ToWords(int number, Humanizer.GrammaticalGender gender) { } + public string ToOrdinalWords(int number, System.Globalization.CultureInfo culture) { } + public string ToOrdinalWords(int number, Humanizer.GrammaticalGender gender, System.Globalization.CultureInfo culture) { } + public string ToWords(int number, System.Globalization.CultureInfo culture) { } + public string ToWords(int number, Humanizer.GrammaticalGender gender, System.Globalization.CultureInfo culture) { } } public class On diff --git a/src/Humanizer.Tests/NumberToWordsTests.cs b/src/Humanizer.Tests/NumberToWordsTests.cs index d77dddf3c..fc8787c87 100644 --- a/src/Humanizer.Tests/NumberToWordsTests.cs +++ b/src/Humanizer.Tests/NumberToWordsTests.cs @@ -1,4 +1,5 @@ -using Xunit; +using System.Globalization; +using Xunit; using Xunit.Extensions; namespace Humanizer.Tests @@ -92,5 +93,23 @@ 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/Configuration/Configurator.cs b/src/Humanizer/Configuration/Configurator.cs index afadff4f5..c3605537b 100644 --- a/src/Humanizer/Configuration/Configurator.cs +++ b/src/Humanizer/Configuration/Configurator.cs @@ -72,12 +72,10 @@ internal static IFormatter GetFormatter(CultureInfo culture) /// /// The converter to be used /// - internal static INumberToWordsConverter NumberToWordsConverter + /// The culture to retrieve number to words converter for. Null means that current thread's UI culture should be used. + internal static INumberToWordsConverter GetNumberToWordsConverter(CultureInfo culture) { - get - { - return NumberToWordsConverters.ResolveForUiCulture(); - } + return NumberToWordsConverters.ResolveForCulture(culture); } /// diff --git a/src/Humanizer/Configuration/LocaliserRegistry.cs b/src/Humanizer/Configuration/LocaliserRegistry.cs index 4492dbfa4..a7e4880f3 100644 --- a/src/Humanizer/Configuration/LocaliserRegistry.cs +++ b/src/Humanizer/Configuration/LocaliserRegistry.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Globalization; namespace Humanizer.Configuration @@ -7,16 +8,26 @@ namespace Humanizer.Configuration /// A registry of localised system components with their associated locales /// /// - public class LocaliserRegistry + public class LocaliserRegistry + where TLocaliser : class { - private readonly IDictionary _localisers = new Dictionary(); - private readonly TLocaliser _defaultLocaliser; + private readonly IDictionary> _localisers = new Dictionary>(); + private readonly Func _defaultLocaliser; /// /// Creates a localiser registry with the default localiser set to the provided value /// /// public LocaliserRegistry(TLocaliser defaultLocaliser) + { + _defaultLocaliser = (culture) => defaultLocaliser; + } + + /// + /// Creates a localiser registry with the default localiser factory set to the provided value + /// + /// + public LocaliserRegistry(Func defaultLocaliser) { _defaultLocaliser = defaultLocaliser; } @@ -35,9 +46,28 @@ public TLocaliser ResolveForUiCulture() /// The culture to retrieve localiser for. If not specified, current thread's UI culture is used. public TLocaliser ResolveForCulture(CultureInfo culture) { - culture = culture ?? CultureInfo.CurrentUICulture; + return FindLocaliser(culture ?? CultureInfo.CurrentUICulture)(culture); + } - TLocaliser localiser; + /// + /// Registers the localiser for the culture provided + /// + public void Register(string localeCode, TLocaliser localiser) + { + _localisers[localeCode] = (culture) => localiser; + } + + /// + /// Registers the localiser factory for the culture provided + /// + public void Register(string localeCode, Func localiser) + { + _localisers[localeCode] = localiser; + } + + private Func FindLocaliser(CultureInfo culture) + { + Func localiser; if (_localisers.TryGetValue(culture.Name, out localiser)) return localiser; @@ -47,13 +77,5 @@ public TLocaliser ResolveForCulture(CultureInfo culture) return _defaultLocaliser; } - - /// - /// Registers the localiser for the culture provided - /// - public void Register(string localeCode, TLocaliser localiser) - { - _localisers[localeCode] = localiser; - } } } diff --git a/src/Humanizer/Configuration/NumberToWordsConverterRegistry.cs b/src/Humanizer/Configuration/NumberToWordsConverterRegistry.cs index 45c829a14..b22be4615 100644 --- a/src/Humanizer/Configuration/NumberToWordsConverterRegistry.cs +++ b/src/Humanizer/Configuration/NumberToWordsConverterRegistry.cs @@ -4,19 +4,19 @@ namespace Humanizer.Configuration { internal class NumberToWordsConverterRegistry : LocaliserRegistry { - public NumberToWordsConverterRegistry() : base(new DefaultNumberToWordsConverter()) + public NumberToWordsConverterRegistry() : base((culture) => new DefaultNumberToWordsConverter(culture)) { Register("en", new EnglishNumberToWordsConverter()); Register("ar", new ArabicNumberToWordsConverter()); Register("fa", new FarsiNumberToWordsConverter()); Register("es", new SpanishNumberToWordsConverter()); - Register("pl", new PolishNumberToWordsConverter()); + Register("pl", (culture) => new PolishNumberToWordsConverter(culture)); Register("pt-BR", new BrazilianPortugueseNumberToWordsConverter()); Register("ru", new RussianNumberToWordsConverter()); Register("fr", new FrenchNumberToWordsConverter()); Register("nl", new DutchNumberToWordsConverter()); - Register("he", new HebrewNumberToWordsConverter()); - Register("sl", new SlovenianNumberToWordsConverter()); + Register("he", (culture) => new HebrewNumberToWordsConverter(culture)); + Register("sl", (culture) => new SlovenianNumberToWordsConverter(culture)); Register("de", new GermanNumberToWordsConverter()); } } diff --git a/src/Humanizer/Humanizer.csproj b/src/Humanizer/Humanizer.csproj index de6eaa08e..f23d6d0d5 100644 --- a/src/Humanizer/Humanizer.csproj +++ b/src/Humanizer/Humanizer.csproj @@ -65,6 +65,8 @@ + + diff --git a/src/Humanizer/Localisation/NumberToWords/ArabicNumberToWordsConverter.cs b/src/Humanizer/Localisation/NumberToWords/ArabicNumberToWordsConverter.cs index e47f7c199..6cb2cd9c4 100644 --- a/src/Humanizer/Localisation/NumberToWords/ArabicNumberToWordsConverter.cs +++ b/src/Humanizer/Localisation/NumberToWords/ArabicNumberToWordsConverter.cs @@ -4,7 +4,7 @@ namespace Humanizer.Localisation.NumberToWords { - internal class ArabicNumberToWordsConverter : DefaultNumberToWordsConverter + internal class ArabicNumberToWordsConverter : GenderlessNumberToWordsConverter { private static readonly string[] Groups = { "مئة", "ألف", "مليون", "مليار", "تريليون", "كوادريليون", "كوينتليون", "سكستيليون" }; private static readonly string[] AppendedGroups = { "", "ألفاً", "مليوناً", "ملياراً", "تريليوناً", "كوادريليوناً", "كوينتليوناً", "سكستيليوناً" }; diff --git a/src/Humanizer/Localisation/NumberToWords/BrazilianPortugueseNumberToWordsConverter.cs b/src/Humanizer/Localisation/NumberToWords/BrazilianPortugueseNumberToWordsConverter.cs index 00efdf2e1..8b646a44d 100644 --- a/src/Humanizer/Localisation/NumberToWords/BrazilianPortugueseNumberToWordsConverter.cs +++ b/src/Humanizer/Localisation/NumberToWords/BrazilianPortugueseNumberToWordsConverter.cs @@ -3,7 +3,7 @@ namespace Humanizer.Localisation.NumberToWords { - internal class BrazilianPortugueseNumberToWordsConverter : DefaultNumberToWordsConverter + internal class BrazilianPortugueseNumberToWordsConverter : GenderedNumberToWordsConverter { private static readonly string[] PortugueseUnitsMap = { "zero", "um", "dois", "três", "quatro", "cinco", "seis", "sete", "oito", "nove", "dez", "onze", "doze", "treze", "quatorze", "quinze", "dezesseis", "dezessete", "dezoito", "dezenove" }; private static readonly string[] PortugueseTensMap = { "zero", "dez", "vinte", "trinta", "quarenta", "cinquenta", "sessenta", "setenta", "oitenta", "noventa" }; @@ -81,11 +81,6 @@ public override string Convert(int number, GrammaticalGender gender) return string.Join(" ", parts.ToArray()); } - public override string Convert(int number) - { - return Convert(number, GrammaticalGender.Masculine); - } - public override string ConvertToOrdinal(int number, GrammaticalGender gender) { // N/A in Portuguese ordinal @@ -139,12 +134,7 @@ public override string ConvertToOrdinal(int number, GrammaticalGender gender) return string.Join(" ", parts.ToArray()); } - public override string ConvertToOrdinal(int number) - { - return ConvertToOrdinal(number, GrammaticalGender.Masculine); - } - - private string ApplyGender(string toWords, GrammaticalGender gender) + private static string ApplyGender(string toWords, GrammaticalGender gender) { if (gender != GrammaticalGender.Feminine) return toWords; @@ -161,11 +151,11 @@ private string ApplyGender(string toWords, GrammaticalGender gender) return toWords; } - private string ApplyOrdinalGender(string toWords, GrammaticalGender gender) + private static string ApplyOrdinalGender(string toWords, GrammaticalGender gender) { if (gender == GrammaticalGender.Feminine) return toWords.TrimEnd('o') + 'a'; - + return toWords; } } diff --git a/src/Humanizer/Localisation/NumberToWords/DefaultNumberToWordsConverter.cs b/src/Humanizer/Localisation/NumberToWords/DefaultNumberToWordsConverter.cs index 35c34adaf..ece6e9b0c 100644 --- a/src/Humanizer/Localisation/NumberToWords/DefaultNumberToWordsConverter.cs +++ b/src/Humanizer/Localisation/NumberToWords/DefaultNumberToWordsConverter.cs @@ -1,41 +1,28 @@ -namespace Humanizer.Localisation.NumberToWords +using System.Globalization; + +namespace Humanizer.Localisation.NumberToWords { - internal class DefaultNumberToWordsConverter : INumberToWordsConverter + internal class DefaultNumberToWordsConverter : GenderlessNumberToWordsConverter { - /// - /// for Russian locale - /// 1.ToWords(GrammaticalGender.Masculine) -> "один" - /// 1.ToWords(GrammaticalGender.Feminine) -> "одна" - /// - /// Number to be turned to words - /// The grammatical gender to use for output words - /// - public virtual string Convert(int number, GrammaticalGender gender) - { - return Convert(number); - } + private readonly CultureInfo _culture; /// - /// 3501.ToWords() -> "three thousand five hundred and one" + /// Constructor. /// - /// Number to be turned to words - /// - public virtual string Convert(int number) + /// Culture to use. + public DefaultNumberToWordsConverter(CultureInfo culture) { - return number.ToString(); + _culture = culture; } /// - /// for Brazilian Portuguese - /// 1.ToOrdinalWords(GrammaticalGender.Masculine) -> "primeiro" - /// 1.ToOrdinalWords(GrammaticalGender.Feminine) -> "primeira" + /// 3501.ToWords() -> "three thousand five hundred and one" /// /// Number to be turned to words - /// The grammatical gender to use for output words /// - public virtual string ConvertToOrdinal(int number, GrammaticalGender gender) + public override string Convert(int number) { - return ConvertToOrdinal(number); + return number.ToString(_culture); } /// @@ -43,9 +30,9 @@ public virtual string ConvertToOrdinal(int number, GrammaticalGender gender) /// /// Number to be turned to ordinal words /// - public virtual string ConvertToOrdinal(int number) + public override string ConvertToOrdinal(int number) { - return number.ToString(); + return number.ToString(_culture); } } } \ No newline at end of file diff --git a/src/Humanizer/Localisation/NumberToWords/DutchNumberToWordsConverter.cs b/src/Humanizer/Localisation/NumberToWords/DutchNumberToWordsConverter.cs index a370f17c0..f071b1b05 100644 --- a/src/Humanizer/Localisation/NumberToWords/DutchNumberToWordsConverter.cs +++ b/src/Humanizer/Localisation/NumberToWords/DutchNumberToWordsConverter.cs @@ -9,7 +9,7 @@ namespace Humanizer.Localisation.NumberToWords /// Used the rules as stated here. /// http://www.beterspellen.nl/website/?pag=110 /// - internal class DutchNumberToWordsConverter : DefaultNumberToWordsConverter + internal class DutchNumberToWordsConverter : GenderlessNumberToWordsConverter { private static readonly string[] UnitsMap = { "nul", "een", "twee", "drie", "vier", "vijf", "zes", "zeven", "acht", "negen", "tien", "elf", "twaalf", "dertien", "veertien", "vijftien", "zestien", "zeventien", "achttien", "negentien" }; private static readonly string[] TensMap = { "nul", "tien", "twintig", "dertig", "veertig", "vijftig", "zestig", "zeventig", "tachtig", "negentig" }; @@ -24,12 +24,12 @@ class Fact } private static readonly Fact[] Hunderds = - { - new Fact {Value = 1000000000, Name = "miljard", Prefix = " ", Postfix = " ", DisplayOneUnit = true}, - new Fact {Value = 1000000, Name = "miljoen", Prefix = " ", Postfix = " ", DisplayOneUnit = true}, - new Fact {Value = 1000, Name = "duizend", Prefix = "", Postfix = " ", DisplayOneUnit = false}, - new Fact {Value = 100, Name = "honderd", Prefix = "", Postfix = "", DisplayOneUnit = false} - }; + { + new Fact {Value = 1000000000, Name = "miljard", Prefix = " ", Postfix = " ", DisplayOneUnit = true}, + new Fact {Value = 1000000, Name = "miljoen", Prefix = " ", Postfix = " ", DisplayOneUnit = true}, + new Fact {Value = 1000, Name = "duizend", Prefix = "", Postfix = " ", DisplayOneUnit = false}, + new Fact {Value = 100, Name = "honderd", Prefix = "", Postfix = "", DisplayOneUnit = false} + }; public override string Convert(int number) { @@ -88,7 +88,7 @@ public override string Convert(int number) }; private static readonly char[] EndingCharForSte = {'t', 'g', 'd'}; - + public override string ConvertToOrdinal(int number) { var word = Convert(number); diff --git a/src/Humanizer/Localisation/NumberToWords/EnglishNumberToWordsConverter.cs b/src/Humanizer/Localisation/NumberToWords/EnglishNumberToWordsConverter.cs index 1887f2cf4..d123caa91 100644 --- a/src/Humanizer/Localisation/NumberToWords/EnglishNumberToWordsConverter.cs +++ b/src/Humanizer/Localisation/NumberToWords/EnglishNumberToWordsConverter.cs @@ -3,7 +3,7 @@ namespace Humanizer.Localisation.NumberToWords { - internal class EnglishNumberToWordsConverter : DefaultNumberToWordsConverter + internal class EnglishNumberToWordsConverter : GenderlessNumberToWordsConverter { private static readonly string[] UnitsMap = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen" }; private static readonly string[] TensMap = { "zero", "ten", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety" }; diff --git a/src/Humanizer/Localisation/NumberToWords/FarsiNumberToWordsConverter.cs b/src/Humanizer/Localisation/NumberToWords/FarsiNumberToWordsConverter.cs index 9602fde39..66d6b3f70 100644 --- a/src/Humanizer/Localisation/NumberToWords/FarsiNumberToWordsConverter.cs +++ b/src/Humanizer/Localisation/NumberToWords/FarsiNumberToWordsConverter.cs @@ -3,7 +3,7 @@ namespace Humanizer.Localisation.NumberToWords { - internal class FarsiNumberToWordsConverter : DefaultNumberToWordsConverter + internal class FarsiNumberToWordsConverter : GenderlessNumberToWordsConverter { private static readonly string[] FarsiHundredsMap = { "صفر", "صد", "دویست", "سیصد", "چهارصد", "پانصد", "ششصد", "هفتصد", "هشتصد", "نهصد" }; private static readonly string[] FarsiTensMap = { "صفر", "ده", "بیست", "سی", "چهل", "پنجاه", "شصت", "هفتاد", "هشتاد", "نود" }; @@ -61,10 +61,5 @@ public override string ConvertToOrdinal(int number) var word = Convert(number); return string.Format("{0}{1}", word, word.EndsWith("ی") ? " ام" : "م"); } - - public override string ConvertToOrdinal(int number, GrammaticalGender gender) - { - return ConvertToOrdinal(number); - } } } diff --git a/src/Humanizer/Localisation/NumberToWords/FrenchNumberToWordsConverter.cs b/src/Humanizer/Localisation/NumberToWords/FrenchNumberToWordsConverter.cs index b022a5241..a8eee698c 100644 --- a/src/Humanizer/Localisation/NumberToWords/FrenchNumberToWordsConverter.cs +++ b/src/Humanizer/Localisation/NumberToWords/FrenchNumberToWordsConverter.cs @@ -3,7 +3,7 @@ namespace Humanizer.Localisation.NumberToWords { - internal class FrenchNumberToWordsConverter : DefaultNumberToWordsConverter + internal class FrenchNumberToWordsConverter : GenderedNumberToWordsConverter { 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"}; @@ -15,7 +15,7 @@ internal class FrenchNumberToWordsConverter : DefaultNumberToWordsConverter {91, "quatre-vingt-onze"} }; - public override string Convert(int number) + public override string Convert(int number, GrammaticalGender gender) { if (number == 0) return UnitsMap[0]; @@ -91,17 +91,9 @@ public override string Convert(int number) } public override string ConvertToOrdinal(int number, GrammaticalGender gender) - { - if (number == 1 && gender == GrammaticalGender.Feminine) - return "première"; - - return base.ConvertToOrdinal(number, gender); - } - - public override string ConvertToOrdinal(int number) { if (number == 1) - return "premier"; + return gender == GrammaticalGender.Feminine ? "première" : "premier"; var convertedNumber = Convert(number); diff --git a/src/Humanizer/Localisation/NumberToWords/GenderedNumberToWordsConverter.cs b/src/Humanizer/Localisation/NumberToWords/GenderedNumberToWordsConverter.cs new file mode 100644 index 000000000..4af91b4f3 --- /dev/null +++ b/src/Humanizer/Localisation/NumberToWords/GenderedNumberToWordsConverter.cs @@ -0,0 +1,48 @@ +namespace Humanizer.Localisation.NumberToWords +{ + abstract class GenderedNumberToWordsConverter : INumberToWordsConverter + { + private readonly GrammaticalGender _defaultGender; + + protected GenderedNumberToWordsConverter(GrammaticalGender defaultGender = GrammaticalGender.Masculine) + { + _defaultGender = defaultGender; + } + + /// + /// Converts the number to string using the locale's default grammatical gender + /// + /// + /// + public string Convert(int number) + { + return Convert(number, _defaultGender); + } + + /// + /// Converts the number to string using the provided grammatical gender + /// + /// + /// + /// + public abstract string Convert(int number, GrammaticalGender gender); + + /// + /// Converts the number to ordinal string using the locale's default grammatical gender + /// + /// + /// + public string ConvertToOrdinal(int number) + { + return ConvertToOrdinal(number, _defaultGender); + } + + /// + /// Converts the number to ordinal string using the provided grammatical gender + /// + /// + /// + /// + public abstract string ConvertToOrdinal(int number, GrammaticalGender gender); + } +} diff --git a/src/Humanizer/Localisation/NumberToWords/GenderlessNumberToWordsConverter.cs b/src/Humanizer/Localisation/NumberToWords/GenderlessNumberToWordsConverter.cs new file mode 100644 index 000000000..fa89f250d --- /dev/null +++ b/src/Humanizer/Localisation/NumberToWords/GenderlessNumberToWordsConverter.cs @@ -0,0 +1,41 @@ +namespace Humanizer.Localisation.NumberToWords +{ + abstract class GenderlessNumberToWordsConverter : INumberToWordsConverter + { + /// + /// Converts the number to string + /// + /// + /// + public abstract string Convert(int number); + + /// + /// Converts the number to string ignoring the provided grammatical gender + /// + /// + /// + /// + public string Convert(int number, GrammaticalGender gender) + { + return Convert(number); + } + + /// + /// Converts the number to ordinal string + /// + /// + /// + public abstract string ConvertToOrdinal(int number); + + /// + /// Converts the number to ordinal string ignoring the provided grammatical gender + /// + /// + /// + /// + public string ConvertToOrdinal(int number, GrammaticalGender gender) + { + return ConvertToOrdinal(number); + } + } +} diff --git a/src/Humanizer/Localisation/NumberToWords/GermanNumberToWordsConverter.cs b/src/Humanizer/Localisation/NumberToWords/GermanNumberToWordsConverter.cs index 99f0bb877..547534a09 100644 --- a/src/Humanizer/Localisation/NumberToWords/GermanNumberToWordsConverter.cs +++ b/src/Humanizer/Localisation/NumberToWords/GermanNumberToWordsConverter.cs @@ -3,7 +3,7 @@ namespace Humanizer.Localisation.NumberToWords { - internal class GermanNumberToWordsConverter : DefaultNumberToWordsConverter + internal class GermanNumberToWordsConverter : GenderedNumberToWordsConverter { private static readonly string[] UnitsMap = { "null", "ein", "zwei", "drei", "vier", "fünf", "sechs", "sieben", "acht", "neun", "zehn", "elf", "zwölf", "dreizehn", "vierzehn", "fünfzehn", "sechzehn", "siebzehn", "achtzehn", "neunzehn" }; private static readonly string[] TensMap = { "null", "zehn", "zwanzig", "dreißig", "vierzig", "fünfzig", "sechzig", "siebzig", "achtzig", "neunzig" }; @@ -13,7 +13,7 @@ internal class GermanNumberToWordsConverter : DefaultNumberToWordsConverter private static readonly string[] BillionOrdinalSingular = {"einmilliard", "einemilliarde"}; private static readonly string[] BillionOrdinalPlural = {"{0}milliard", "{0}milliarden"}; - public override string Convert(int number) + public override string Convert(int number, GrammaticalGender gender) { if (number == 0) return "null"; @@ -77,11 +77,6 @@ public override string Convert(int number) return string.Join("", parts); } - public override string ConvertToOrdinal(int number) - { - return ConvertToOrdinal(number, GrammaticalGender.Masculine); - } - public override string ConvertToOrdinal(int number, GrammaticalGender gender) { if (number == 0) diff --git a/src/Humanizer/Localisation/NumberToWords/HebrewNumberToWordsConverter.cs b/src/Humanizer/Localisation/NumberToWords/HebrewNumberToWordsConverter.cs index 759b158de..411dc0b53 100644 --- a/src/Humanizer/Localisation/NumberToWords/HebrewNumberToWordsConverter.cs +++ b/src/Humanizer/Localisation/NumberToWords/HebrewNumberToWordsConverter.cs @@ -1,14 +1,17 @@ using System; using System.Collections.Generic; +using System.Globalization; namespace Humanizer.Localisation.NumberToWords { - internal class HebrewNumberToWordsConverter : DefaultNumberToWordsConverter + internal class HebrewNumberToWordsConverter : GenderedNumberToWordsConverter { private static readonly string[] UnitsFeminine = { "אפס", "אחת", "שתיים", "שלוש", "ארבע", "חמש", "שש", "שבע", "שמונה", "תשע", "עשר" }; private static readonly string[] UnitsMasculine = { "אפס", "אחד", "שניים", "שלושה", "ארבעה", "חמישה", "שישה", "שבעה", "שמונה", "תשעה", "עשרה" }; private static readonly string[] TensUnit = { "עשר", "עשרים", "שלושים", "ארבעים", "חמישים", "שישים", "שבעים", "שמונים", "תשעים" }; + private readonly CultureInfo _culture; + private class DescriptionAttribute : Attribute { public string Description { get; set; } @@ -29,10 +32,10 @@ private enum Group Billions = 1000000000 } - public override string Convert(int number) + public HebrewNumberToWordsConverter(CultureInfo culture) + : base(GrammaticalGender.Feminine) { - // in Hebrew, the default number gender form is feminine. - return Convert(number, GrammaticalGender.Feminine); + _culture = culture; } public override string Convert(int number, GrammaticalGender gender) @@ -104,6 +107,11 @@ public override string Convert(int number, GrammaticalGender gender) return string.Join(" ", parts); } + public override string ConvertToOrdinal(int number, GrammaticalGender gender) + { + return number.ToString(_culture); + } + private void ToBigNumber(int number, Group group, List parts) { // Big numbers (million and above) always use the masculine form @@ -131,7 +139,7 @@ private void ToThousands(int number, List parts) parts.Add(Convert(thousands) + " אלף"); } - private void ToHundreds(int number, List parts) + private static void ToHundreds(int number, List parts) { // For hundreds, Hebrew is using the feminine form // See https://www.safa-ivrit.org/dikduk/numbers.php diff --git a/src/Humanizer/Localisation/NumberToWords/PolishNumberToWordsConverter.cs b/src/Humanizer/Localisation/NumberToWords/PolishNumberToWordsConverter.cs index 38386c368..894a90852 100644 --- a/src/Humanizer/Localisation/NumberToWords/PolishNumberToWordsConverter.cs +++ b/src/Humanizer/Localisation/NumberToWords/PolishNumberToWordsConverter.cs @@ -1,13 +1,21 @@ using System.Collections.Generic; +using System.Globalization; namespace Humanizer.Localisation.NumberToWords { - internal class PolishNumberToWordsConverter : DefaultNumberToWordsConverter + internal class PolishNumberToWordsConverter : GenderlessNumberToWordsConverter { private static readonly string[] HundredsMap = { "zero", "sto", "dwieście", "trzysta", "czterysta", "pięćset", "sześćset", "siedemset", "osiemset", "dziewięćset" }; private static readonly string[] TensMap = { "zero", "dziesięć", "dwadzieścia", "trzydzieści", "czterdzieści", "pięćdziesiąt", "sześćdziesiąt", "siedemdziesiąt", "osiemdziesiąt", "dziewięćdziesiąt" }; private static readonly string[] UnitsMap = { "zero", "jeden", "dwa", "trzy", "cztery", "pięć", "sześć", "siedem", "osiem", "dziewięć", "dziesięć", "jedenaście", "dwanaście", "trzynaście", "czternaście", "piętnaście", "szesnaście", "siedemnaście", "osiemnaście", "dziewiętnaście" }; + private readonly CultureInfo _culture; + + public PolishNumberToWordsConverter(CultureInfo culture) + { + _culture = culture; + } + private static void CollectPartsUnderThousand(ICollection parts, int number) { var hundreds = number/100; @@ -99,5 +107,10 @@ public override string Convert(int number) return string.Join(" ", parts); } + + public override string ConvertToOrdinal(int number) + { + return number.ToString(_culture); + } } } diff --git a/src/Humanizer/Localisation/NumberToWords/RussianNumberToWordsConverter.cs b/src/Humanizer/Localisation/NumberToWords/RussianNumberToWordsConverter.cs index 90bcd2aa7..e53092aad 100644 --- a/src/Humanizer/Localisation/NumberToWords/RussianNumberToWordsConverter.cs +++ b/src/Humanizer/Localisation/NumberToWords/RussianNumberToWordsConverter.cs @@ -4,7 +4,7 @@ namespace Humanizer.Localisation.NumberToWords { - internal class RussianNumberToWordsConverter : DefaultNumberToWordsConverter + internal class RussianNumberToWordsConverter : GenderedNumberToWordsConverter { private static readonly string[] HundredsMap = { "ноль", "сто", "двести", "триста", "четыреста", "пятьсот", "шестьсот", "семьсот", "восемьсот", "девятьсот" }; private static readonly string[] TensMap = { "ноль", "десять", "двадцать", "тридцать", "сорок", "пятьдесят", "шестьдесят", "семьдесят", "восемьдесят", "девяносто" }; @@ -14,11 +14,6 @@ internal class RussianNumberToWordsConverter : DefaultNumberToWordsConverter private static readonly string[] TensOrdinal = { string.Empty, "десят", "двадцат", "тридцат", "сороков", "пятидесят", "шестидесят", "семидесят", "восьмидесят", "девяност" }; private static readonly string[] UnitsOrdinal = { string.Empty, "перв", "втор", "трет", "четверт", "пят", "шест", "седьм", "восьм", "девят", "десят", "одиннадцат", "двенадцат", "тринадцат", "четырнадцат", "пятнадцат", "шестнадцат", "семнадцат", "восемнадцат", "девятнадцат" }; - public override string Convert(int number) - { - return Convert(number, GrammaticalGender.Masculine); - } - public override string Convert(int number, GrammaticalGender gender) { if (number == 0) @@ -42,11 +37,6 @@ public override string Convert(int number, GrammaticalGender gender) return string.Join(" ", parts); } - public override string ConvertToOrdinal(int number) - { - return ConvertToOrdinal(number, GrammaticalGender.Masculine); - } - public override string ConvertToOrdinal(int number, GrammaticalGender gender) { if (number == 0) diff --git a/src/Humanizer/Localisation/NumberToWords/SlovenianNumberToWordsConverter.cs b/src/Humanizer/Localisation/NumberToWords/SlovenianNumberToWordsConverter.cs index 743adf90f..eaa48ca2c 100644 --- a/src/Humanizer/Localisation/NumberToWords/SlovenianNumberToWordsConverter.cs +++ b/src/Humanizer/Localisation/NumberToWords/SlovenianNumberToWordsConverter.cs @@ -1,13 +1,21 @@ using System.Collections.Generic; +using System.Globalization; namespace Humanizer.Localisation.NumberToWords { - internal class SlovenianNumberToWordsConverter : DefaultNumberToWordsConverter - { - private static readonly string[] UnitsMap = {"nič", "ena", "dva", "tri", "štiri", "pet", "šest", "sedem", "osem", "devet", "deset", "enajst", "dvanajst", "trinajst", "štirinajst", "petnajst", "šestnajst", "sedemnajst", "osemnajst", "devetnajst"}; - private static readonly string[] TensMap = {"nič", "deset", "dvajset", "trideset", "štirideset", "petdeset", "šestdeset", "sedemdeset", "osemdeset", "devetdeset"}; + internal class SlovenianNumberToWordsConverter : GenderlessNumberToWordsConverter + { + private static readonly string[] UnitsMap = {"nič", "ena", "dva", "tri", "štiri", "pet", "šest", "sedem", "osem", "devet", "deset", "enajst", "dvanajst", "trinajst", "štirinajst", "petnajst", "šestnajst", "sedemnajst", "osemnajst", "devetnajst"}; + private static readonly string[] TensMap = {"nič", "deset", "dvajset", "trideset", "štirideset", "petdeset", "šestdeset", "sedemdeset", "osemdeset", "devetdeset"}; - public override string Convert(int number) + private readonly CultureInfo _culture; + + public SlovenianNumberToWordsConverter(CultureInfo culture) + { + _culture = culture; + } + + public override string Convert(int number) { if (number == 0) return "nič"; @@ -75,7 +83,12 @@ public override string Convert(int number) return string.Join("", parts); } - private string Part(string singular, string dual, string trialQuadral, string plural, int number) + public override string ConvertToOrdinal(int number) + { + return number.ToString(_culture); + } + + private string Part(string singular, string dual, string trialQuadral, string plural, int number) { if (number == 1) return singular; @@ -88,5 +101,5 @@ private string Part(string singular, string dual, string trialQuadral, string pl return string.Format(plural, Convert(number)); } - } + } } \ No newline at end of file diff --git a/src/Humanizer/Localisation/NumberToWords/SpanishNumberToWordsConverter.cs b/src/Humanizer/Localisation/NumberToWords/SpanishNumberToWordsConverter.cs index a3afe02ab..d8ef2d238 100644 --- a/src/Humanizer/Localisation/NumberToWords/SpanishNumberToWordsConverter.cs +++ b/src/Humanizer/Localisation/NumberToWords/SpanishNumberToWordsConverter.cs @@ -3,7 +3,7 @@ namespace Humanizer.Localisation.NumberToWords { - internal class SpanishNumberToWordsConverter : DefaultNumberToWordsConverter + internal class SpanishNumberToWordsConverter : GenderedNumberToWordsConverter { private static readonly string[] UnitsMap = { "cero", "uno", "dos", "tres", "cuatro", "cinco", "seis", "siete", "ocho", "nueve", "diez", "once", "doce", "trece", "catorce", "quince", "dieciséis", "diecisiete", "dieciocho", "diecinueve", "veinte", "veintiuno", @@ -25,7 +25,7 @@ internal class SpanishNumberToWordsConverter : DefaultNumberToWordsConverter {10, "décimo"} }; - public override string Convert(int number) + public override string Convert(int number, GrammaticalGender gender) { if (number == 0) return "cero"; @@ -92,7 +92,7 @@ public override string Convert(int number) return string.Join(" ", parts.ToArray()); } - public override string ConvertToOrdinal(int number, GrammaticalGender gender = GrammaticalGender.Masculine) + public override string ConvertToOrdinal(int number, GrammaticalGender gender) { string towords; if (!Ordinals.TryGetValue(number, out towords)) diff --git a/src/Humanizer/NumberToWordsExtension.cs b/src/Humanizer/NumberToWordsExtension.cs index ca9f211ec..afab61866 100644 --- a/src/Humanizer/NumberToWordsExtension.cs +++ b/src/Humanizer/NumberToWordsExtension.cs @@ -1,3 +1,4 @@ +using System.Globalization; using Humanizer.Configuration; namespace Humanizer @@ -11,10 +12,11 @@ public static class NumberToWordsExtension /// 3501.ToWords() -> "three thousand five hundred and one" /// /// Number to be turned to words + /// Culture to use. If null, current thread's UI culture is used. /// - public static string ToWords(this int number) + public static string ToWords(this int number, CultureInfo culture = null) { - return Configurator.NumberToWordsConverter.Convert(number); + return Configurator.GetNumberToWordsConverter(culture).Convert(number); } /// @@ -35,20 +37,22 @@ public static string ToWords(this int number) /// /// Number to be turned to words /// The grammatical gender to use for output words + /// Culture to use. If null, current thread's UI culture is used. /// - public static string ToWords(this int number, GrammaticalGender gender) + public static string ToWords(this int number, GrammaticalGender gender, CultureInfo culture = null) { - return Configurator.NumberToWordsConverter.Convert(number, gender); + return Configurator.GetNumberToWordsConverter(culture).Convert(number, gender); } /// /// 1.ToOrdinalWords() -> "first" /// /// Number to be turned to ordinal words + /// Culture to use. If null, current thread's UI culture is used. /// - public static string ToOrdinalWords(this int number) + public static string ToOrdinalWords(this int number, CultureInfo culture = null) { - return Configurator.NumberToWordsConverter.ConvertToOrdinal(number); + return Configurator.GetNumberToWordsConverter(culture).ConvertToOrdinal(number); } /// @@ -58,10 +62,11 @@ public static string ToOrdinalWords(this int number) /// /// Number to be turned to words /// The grammatical gender to use for output words + /// Culture to use. If null, current thread's UI culture is used. /// - public static string ToOrdinalWords(this int number, GrammaticalGender gender) + public static string ToOrdinalWords(this int number, GrammaticalGender gender, CultureInfo culture = null) { - return Configurator.NumberToWordsConverter.ConvertToOrdinal(number, gender); + return Configurator.GetNumberToWordsConverter(culture).ConvertToOrdinal(number, gender); } } }