diff --git a/readme.md b/readme.md index ee97d1abd..c67db9ec8 100644 --- a/readme.md +++ b/readme.md @@ -26,6 +26,7 @@ Humanizer meets all your .NET needs for manipulating and displaying strings, enu - [Number to ordinal words](#number-to-ordinal-words) - [Roman numerals](#roman-numerals) - [ByteSize](#bytesize) + - [Configuration](#configuration) - [Mix this into your framework to simplify your life](#mix-this-into-your-framework-to-simplify-your-life) - [How to contribute?](#how-to-contribute) - [Contribution guideline](#contribution-guideline) @@ -679,6 +680,11 @@ ByteSize.Parse("1.55 tB"); ByteSize.Parse("1.55 tb"); ``` +##Configuration + +Custom factories for `Formatter`s, `NumberToWordsConverter`s, and `Ordinalizer`s may be added or updated via `FormatterFactoryManager`, `NumberToWordsConverterFactoryManager`, and `OrdinalizerFactoryManager` using the `SetFactory` method. + + ##Mix this into your framework to simplify your life This is just a baseline and you can use this to simplify your day to day job. For example, in Asp.Net MVC we keep chucking `Display` attribute on ViewModel properties so `HtmlHelper` can generate correct labels for us; but, just like enums, in vast majority of cases we just need a space between the words in property name - so why not use `"string".Humanize` for that?! @@ -828,7 +834,7 @@ Then you return an instance of your class in the [Configurator](https://github.c Translations for `ToWords` and `ToOrdinalWords` methods are currently done in code as there is a huge difference between the way different languages deal with number words. Check out [Dutch](https://github.com/MehdiK/Humanizer/blob/master/src/Humanizer/Localisation/NumberToWords/DutchNumberToWordsConverter.cs) and [Russian](https://github.com/MehdiK/Humanizer/blob/master/src/Humanizer/Localisation/NumberToWords/RussianNumberToWordsConverter.cs) localisations for examples of how you can write a Converter for your language. -You should then register your converter in the [ConverterFactory](https://github.com/MehdiK/Humanizer/blob/master/src/Humanizer/NumberToWordsExtension.cs#L13) for it to kick in on your locale. +You should then register your converter in the [NumberToWordsConverterFactoryManager](https://github.com/MehdiK/Humanizer/blob/master/src/Humanizer/Configuration/NumberToWordsConverterFactoryManager.cs#L35) for it to kick in on your locale. Don't forget to write tests for your localisations. Check out the existing [DateHumanizeTests](https://github.com/MehdiK/Humanizer/blob/master/src/Humanizer.Tests/Localisation/ru-RU/DateHumanizeTests.cs), [TimeSpanHumanizeTests](https://github.com/MehdiK/Humanizer/blob/master/src/Humanizer.Tests/Localisation/ru-RU/TimeSpanHumanizeTests.cs) and [NumberToWordsTests](https://github.com/MehdiK/Humanizer/blob/master/src/Humanizer.Tests/Localisation/ru-RU/NumberToWordsTests.cs). @@ -848,4 +854,4 @@ Alexander I. Zaytsev ([@hazzik](https://github.com/hazzik)) Humanizer is released under the MIT License. See the [bundled LICENSE](https://github.com/MehdiK/Humanizer/blob/master/LICENSE) file for details. ##Icon -Icon created by [Tyrone Rieschiek](https://twitter.com/Inkventive) \ No newline at end of file +Icon created by [Tyrone Rieschiek](https://twitter.com/Inkventive) diff --git a/release_notes.md b/release_notes.md index 068079dd8..40c45800a 100644 --- a/release_notes.md +++ b/release_notes.md @@ -6,6 +6,7 @@ - [#232](https://github.com/Mehdik/Humanizer/pull/232): Adding code & tests to handle Arabic numbers to ordinal - [#235](https://github.com/Mehdik/Humanizer/pull/235): Fixed the conversion for "1 millon" in SpanishNumberToWordsConverter - [#233](https://github.com/Mehdik/Humanizer/pull/233): Added build.cmd and Verify build configuration for strict project build and analysis + - [#227](https://github.com/MehdiK/Humanizer/pull/227): Moved factory collections to their own classes, allowed public access via Configurator, and made the default factories lazy loaded [Commits](https://github.com/MehdiK/Humanizer/compare/v1.23.1...v1.24.1) @@ -18,7 +19,6 @@ - [#231](https://github.com/Mehdik/Humanizer/pull/231): Added more settings for FromNow, Dual and Plural (Arabic) - [#222](https://github.com/Mehdik/Humanizer/pull/222): Updated Ordinalize and ToOrdinalWords to account for special exceptions with 1 and 3. - [Commits](https://github.com/MehdiK/Humanizer/compare/v1.22.1...v1.23.1) ###v1.22.1 - 2014-04-14 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 4682973c1..69ac757ef 100644 --- a/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt +++ b/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt @@ -66,7 +66,17 @@ public class CasingExtensions public class Configurator { public Humanizer.DateTimeHumanizeStrategy.IDateTimeHumanizeStrategy DateTimeHumanizeStrategy { get; set; } - public Humanizer.Localisation.Formatters.IFormatter Formatter { get; } + public Humanizer.Configuration.FactoryManager FormatterFactoryManager { get; } + public Humanizer.Configuration.FactoryManager NumberToWordsConverterFactoryManager { get; } + public Humanizer.Configuration.FactoryManager OrdinalizerFactoryManager { get; } +} + +public class FactoryManager`1 +{ + public FactoryManager`1(System.Collections.Generic.IDictionary<, > factories) { } + public System.Func<> GetFactory(System.Globalization.CultureInfo culture) { } + public void SetDefaultFactory(System.Func<> factory) { } + public void SetFactory(System.Func<> factory, System.Globalization.CultureInfo culture) { } } public class DateHumanizeExtensions @@ -200,6 +210,20 @@ public interface IFormatter string TimeSpanHumanize_Zero(); } +public interface INumberToWordsConverter +{ + string Convert(int number); + string Convert(int number, Humanizer.GrammaticalGender gender); + string ConvertToOrdinal(int number); + string ConvertToOrdinal(int number, Humanizer.GrammaticalGender gender); +} + +public interface IOrdinalizer +{ + string Convert(int number, string numberString); + string Convert(int number, string numberString, Humanizer.GrammaticalGender gender); +} + public class ResourceKeys { public ResourceKeys() { } diff --git a/src/Humanizer/Configuration/Configurator.cs b/src/Humanizer/Configuration/Configurator.cs index b5d013de2..75711b1fd 100644 --- a/src/Humanizer/Configuration/Configurator.cs +++ b/src/Humanizer/Configuration/Configurator.cs @@ -3,6 +3,8 @@ using System.Globalization; using Humanizer.DateTimeHumanizeStrategy; using Humanizer.Localisation.Formatters; +using Humanizer.Localisation.NumberToWords; +using Humanizer.Localisation.Ordinalizers; namespace Humanizer.Configuration { @@ -11,32 +13,60 @@ namespace Humanizer.Configuration /// public static class Configurator { - private static readonly IDictionary> FormatterFactories = - new Dictionary>(StringComparer.OrdinalIgnoreCase) + + private static FactoryManager _formatterFactoryManager = new FormatterFactoryManager(); + + public static FactoryManager FormatterFactoryManager + { + get { return _formatterFactoryManager; } + } + + private static FactoryManager _numberToWordsConverterFactoryManager = new NumberToWordsConverterFactoryManager(); + + public static FactoryManager NumberToWordsConverterFactoryManager + { + get { return _numberToWordsConverterFactoryManager; } + } + + private static FactoryManager _ordinalizerFactoryManager = new OrdinalizerFactoryManager(); + + public static FactoryManager OrdinalizerFactoryManager { - { "ro", () => new RomanianFormatter() }, - { "ru", () => new RussianFormatter() }, - { "ar", () => new ArabicFormatter() }, - { "he", () => new HebrewFormatter() }, - { "sk", () => new CzechSlovakPolishFormatter() }, - { "cs", () => new CzechSlovakPolishFormatter() }, - { "pl", () => new CzechSlovakPolishFormatter() } - }; + get { return _ordinalizerFactoryManager; } + } private static IDateTimeHumanizeStrategy _dateTimeHumanizeStrategy = new DefaultDateTimeHumanizeStrategy(); /// /// The formatter to be used /// - public static IFormatter Formatter + internal static IFormatter Formatter + { + get + { + return FormatterFactoryManager.GetFactory()(); + } + } + + /// + /// The converter to be used + /// + internal static INumberToWordsConverter NumberToWordsConverter + { + get + { + return NumberToWordsConverterFactoryManager.GetFactory()(); + } + } + + /// + /// The ordinalizer to be used + /// + internal static IOrdinalizer Ordinalizer { get { - Func formatterFactory; - if (FormatterFactories.TryGetValue(CultureInfo.CurrentUICulture.TwoLetterISOLanguageName, out formatterFactory)) - return formatterFactory(); - - return new DefaultFormatter(); + return OrdinalizerFactoryManager.GetFactory()(); } } diff --git a/src/Humanizer/Configuration/FactoryManager.cs b/src/Humanizer/Configuration/FactoryManager.cs new file mode 100644 index 000000000..cce217da5 --- /dev/null +++ b/src/Humanizer/Configuration/FactoryManager.cs @@ -0,0 +1,56 @@ +using System; +using System; +using System.Collections.Generic; +using System.Globalization; + +namespace Humanizer.Configuration +{ + public class FactoryManager + { + private IDictionary> _factories; + + public FactoryManager(IDictionary> factories) + { + _factories = factories; + } + + /// + /// Gets the factory for the CultureInfo provided. + /// If no culture is provided, CultureInfo.CurrentUICulture will be used + /// + public Func GetFactory(CultureInfo culture = null) + { + culture = culture ?? CultureInfo.CurrentUICulture; + + Func factory; + + if (_factories.TryGetValue(culture.Name, out factory)) + return factory; + + if (_factories.TryGetValue(culture.TwoLetterISOLanguageName, out factory)) + return factory; + + return _factories["default"]; + } + + /// + /// Set the factory for the culture provided. + /// + public void SetFactory(Func factory, CultureInfo culture = null) + { + culture = culture ?? CultureInfo.CurrentUICulture; + + _factories[culture.Name] = factory; + } + + /// + /// Set the default factory for when a culture does not have a + /// specific factory set. + /// + public void SetDefaultFactory(Func factory) + { + _factories["default"] = factory; + } + + } +} diff --git a/src/Humanizer/Configuration/FormatterFactoryManager.cs b/src/Humanizer/Configuration/FormatterFactoryManager.cs new file mode 100644 index 000000000..539119216 --- /dev/null +++ b/src/Humanizer/Configuration/FormatterFactoryManager.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using Humanizer.Localisation.Formatters; + +namespace Humanizer.Configuration +{ + internal class FormatterFactoryManager : FactoryManager + { + private static readonly Lazy _lazyRomanianFormatter = new Lazy(); + private static readonly Lazy _lazyRussianFormatter = new Lazy(); + private static readonly Lazy _lazyArabicFormatter = new Lazy(); + private static readonly Lazy _lazyHebrewFormatter = new Lazy(); + private static readonly Lazy _lazyCzechSlovakPolishFormatter = new Lazy(); + private static readonly Lazy _defaultFormatter = new Lazy(); + + public FormatterFactoryManager() + : base( + new Dictionary> + { + { "ro", () => _lazyRomanianFormatter.Value }, + { "ru", () => _lazyRussianFormatter.Value }, + { "ar", () => _lazyArabicFormatter.Value }, + { "he", () => _lazyHebrewFormatter.Value }, + { "sk", () => _lazyCzechSlovakPolishFormatter.Value }, + { "cs", () => _lazyCzechSlovakPolishFormatter.Value }, + { "pl", () => _lazyCzechSlovakPolishFormatter.Value } + }) + { + SetDefaultFactory(() => _defaultFormatter.Value); + } + } +} diff --git a/src/Humanizer/Configuration/NumberToWordsConverterFactoryManager.cs b/src/Humanizer/Configuration/NumberToWordsConverterFactoryManager.cs new file mode 100644 index 000000000..08ef0ad68 --- /dev/null +++ b/src/Humanizer/Configuration/NumberToWordsConverterFactoryManager.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using Humanizer.Localisation.NumberToWords; + +namespace Humanizer.Configuration +{ + internal class NumberToWordsConverterFactoryManager : FactoryManager + { + private static readonly Lazy _lazyEnglishNumberToWordsConverter = new Lazy(); + private static readonly Lazy _lazyArabicNumberToWordsConverter = new Lazy(); + private static readonly Lazy _lazyFarsiNumberToWordsConverter = new Lazy(); + private static readonly Lazy _lazySpanishNumberToWordsConverter = new Lazy(); + private static readonly Lazy _lazyPolishNumberToWordsConverter = new Lazy(); + private static readonly Lazy _lazyBrazilianPortugueseNumberToWordsConverter = new Lazy(); + private static readonly Lazy _lazyRussianNumberToWordsConverter = new Lazy(); + private static readonly Lazy _lazyFrenchNumberToWordsConverter = new Lazy(); + private static readonly Lazy _lazyDutchNumberToWordsConverter = new Lazy(); + private static readonly Lazy _lazyHebrewNumberToWordsConverter = new Lazy(); + private static readonly Lazy _lazyDefaultNumberToWordsConverter = new Lazy(); + + public NumberToWordsConverterFactoryManager() + : base( + new Dictionary> + { + {"en", () => _lazyEnglishNumberToWordsConverter.Value}, + {"ar", () => _lazyArabicNumberToWordsConverter.Value}, + {"fa", () => _lazyFarsiNumberToWordsConverter.Value}, + {"es", () => _lazySpanishNumberToWordsConverter.Value}, + {"pl", () => _lazyPolishNumberToWordsConverter.Value}, + {"pt-BR", () => _lazyBrazilianPortugueseNumberToWordsConverter.Value}, + {"ru", () => _lazyRussianNumberToWordsConverter.Value}, + {"fr", () => _lazyFrenchNumberToWordsConverter.Value}, + {"nl", () => _lazyDutchNumberToWordsConverter.Value}, + {"he", () => _lazyHebrewNumberToWordsConverter.Value} + }) + { + SetDefaultFactory(() => _lazyDefaultNumberToWordsConverter.Value); + } + } +} diff --git a/src/Humanizer/Configuration/OrdinalizerFactoryManager.cs b/src/Humanizer/Configuration/OrdinalizerFactoryManager.cs new file mode 100644 index 000000000..c891163c6 --- /dev/null +++ b/src/Humanizer/Configuration/OrdinalizerFactoryManager.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using Humanizer.Localisation.Ordinalizers; + +namespace Humanizer.Configuration +{ + internal class OrdinalizerFactoryManager : FactoryManager + { + private static Lazy _lazyEnglishOrdinalizer = new Lazy(); + private static Lazy _lazySpanishOrdinalizer = new Lazy(); + private static Lazy _lazyBrazilianPortugueseOrdinalizer = new Lazy(); + private static Lazy _lazyRussianOrdinalizer = new Lazy(); + private static Lazy _lazyDefaultOrdinalizer = new Lazy(); + + public OrdinalizerFactoryManager() + : base( + new Dictionary> + { + {"en", () => _lazyEnglishOrdinalizer.Value}, + {"es", () => _lazySpanishOrdinalizer.Value}, + {"pt-BR", () => _lazyBrazilianPortugueseOrdinalizer.Value}, + {"ru", () => _lazyRussianOrdinalizer.Value} + }) + { + SetDefaultFactory(() => _lazyDefaultOrdinalizer.Value); + } + } +} diff --git a/src/Humanizer/Humanizer.csproj b/src/Humanizer/Humanizer.csproj index d4118dc75..07017cb73 100644 --- a/src/Humanizer/Humanizer.csproj +++ b/src/Humanizer/Humanizer.csproj @@ -59,6 +59,10 @@ true + + + + @@ -80,9 +84,11 @@ + + diff --git a/src/Humanizer/Localisation/NumberToWords/DefaultNumberToWordsConverter.cs b/src/Humanizer/Localisation/NumberToWords/DefaultNumberToWordsConverter.cs index c3cb9df3e..35c34adaf 100644 --- a/src/Humanizer/Localisation/NumberToWords/DefaultNumberToWordsConverter.cs +++ b/src/Humanizer/Localisation/NumberToWords/DefaultNumberToWordsConverter.cs @@ -1,6 +1,6 @@ namespace Humanizer.Localisation.NumberToWords { - internal class DefaultNumberToWordsConverter + internal class DefaultNumberToWordsConverter : INumberToWordsConverter { /// /// for Russian locale diff --git a/src/Humanizer/Localisation/NumberToWords/INumberToWordsConverter.cs b/src/Humanizer/Localisation/NumberToWords/INumberToWordsConverter.cs new file mode 100644 index 000000000..84ad76a07 --- /dev/null +++ b/src/Humanizer/Localisation/NumberToWords/INumberToWordsConverter.cs @@ -0,0 +1,11 @@ +using System; +namespace Humanizer.Localisation.NumberToWords +{ + public interface INumberToWordsConverter + { + string Convert(int number); + string Convert(int number, Humanizer.GrammaticalGender gender); + string ConvertToOrdinal(int number); + string ConvertToOrdinal(int number, Humanizer.GrammaticalGender gender); + } +} diff --git a/src/Humanizer/Localisation/Ordinalizers/DefaultOrdinalizer.cs b/src/Humanizer/Localisation/Ordinalizers/DefaultOrdinalizer.cs index 3f70a5049..01955f49e 100644 --- a/src/Humanizer/Localisation/Ordinalizers/DefaultOrdinalizer.cs +++ b/src/Humanizer/Localisation/Ordinalizers/DefaultOrdinalizer.cs @@ -1,6 +1,6 @@ namespace Humanizer.Localisation.Ordinalizers { - internal class DefaultOrdinalizer + internal class DefaultOrdinalizer : IOrdinalizer { public virtual string Convert(int number, string numberString, GrammaticalGender gender) { diff --git a/src/Humanizer/Localisation/Ordinalizers/IOrdinalizer.cs b/src/Humanizer/Localisation/Ordinalizers/IOrdinalizer.cs new file mode 100644 index 000000000..6e2e63179 --- /dev/null +++ b/src/Humanizer/Localisation/Ordinalizers/IOrdinalizer.cs @@ -0,0 +1,9 @@ +using System; +namespace Humanizer.Localisation.Ordinalizers +{ + public interface IOrdinalizer + { + string Convert(int number, string numberString); + string Convert(int number, string numberString, Humanizer.GrammaticalGender gender); + } +} diff --git a/src/Humanizer/NumberToWordsExtension.cs b/src/Humanizer/NumberToWordsExtension.cs index 198095650..9ae57d1c4 100644 --- a/src/Humanizer/NumberToWordsExtension.cs +++ b/src/Humanizer/NumberToWordsExtension.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using Humanizer.Configuration; using Humanizer.Localisation.NumberToWords; namespace Humanizer @@ -10,21 +11,6 @@ namespace Humanizer /// public static class NumberToWordsExtension { - private static readonly IDictionary> ConverterFactories = - new Dictionary> - { - {"en", () => new EnglishNumberToWordsConverter()}, - {"ar", () => new ArabicNumberToWordsConverter()}, - {"fa", () => new FarsiNumberToWordsConverter()}, - {"es", () => new SpanishNumberToWordsConverter()}, - {"pl", () => new PolishNumberToWordsConverter()}, - {"pt-BR", () => new BrazilianPortugueseNumberToWordsConverter()}, - {"ru", () => new RussianNumberToWordsConverter()}, - {"fr", () => new FrenchNumberToWordsConverter()}, - {"nl", () => new DutchNumberToWordsConverter()}, - {"he", () => new HebrewNumberToWordsConverter()} - }; - /// /// 3501.ToWords() -> "three thousand five hundred and one" /// @@ -32,7 +18,7 @@ public static class NumberToWordsExtension /// public static string ToWords(this int number) { - return Converter.Convert(number); + return Configurator.NumberToWordsConverter.Convert(number); } /// @@ -56,7 +42,7 @@ public static string ToWords(this int number) /// public static string ToWords(this int number, GrammaticalGender gender) { - return Converter.Convert(number, gender); + return Configurator.NumberToWordsConverter.Convert(number, gender); } /// @@ -66,7 +52,7 @@ public static string ToWords(this int number, GrammaticalGender gender) /// public static string ToOrdinalWords(this int number) { - return Converter.ConvertToOrdinal(number); + return Configurator.NumberToWordsConverter.ConvertToOrdinal(number); } /// @@ -79,23 +65,9 @@ public static string ToOrdinalWords(this int number) /// public static string ToOrdinalWords(this int number, GrammaticalGender gender) { - return Converter.ConvertToOrdinal(number, gender); + return Configurator.NumberToWordsConverter.ConvertToOrdinal(number, gender); } - private static DefaultNumberToWordsConverter Converter - { - get - { - Func converterFactory; - - if (ConverterFactories.TryGetValue(CultureInfo.CurrentUICulture.Name, out converterFactory)) - return converterFactory(); - if (ConverterFactories.TryGetValue(CultureInfo.CurrentUICulture.TwoLetterISOLanguageName, out converterFactory)) - return converterFactory(); - - return new DefaultNumberToWordsConverter(); - } - } } } \ No newline at end of file diff --git a/src/Humanizer/OrdinalizeExtensions.cs b/src/Humanizer/OrdinalizeExtensions.cs index 403d5295f..cfc54c11b 100644 --- a/src/Humanizer/OrdinalizeExtensions.cs +++ b/src/Humanizer/OrdinalizeExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using Humanizer.Configuration; using Humanizer.Localisation.Ordinalizers; namespace Humanizer @@ -26,7 +27,7 @@ public static class OrdinalizeExtensions /// public static string Ordinalize(this string numberString) { - return Ordinalizer.Convert(int.Parse(numberString), numberString); + return Configurator.Ordinalizer.Convert(int.Parse(numberString), numberString); } /// @@ -40,7 +41,7 @@ public static string Ordinalize(this string numberString) /// public static string Ordinalize(this string numberString, GrammaticalGender gender) { - return Ordinalizer.Convert(int.Parse(numberString), numberString, gender); + return Configurator.Ordinalizer.Convert(int.Parse(numberString), numberString, gender); } /// @@ -50,7 +51,7 @@ public static string Ordinalize(this string numberString, GrammaticalGender gend /// public static string Ordinalize(this int number) { - return Ordinalizer.Convert(number, number.ToString(CultureInfo.InvariantCulture)); + return Configurator.Ordinalizer.Convert(number, number.ToString(CultureInfo.InvariantCulture)); } /// @@ -64,23 +65,7 @@ public static string Ordinalize(this int number) /// public static string Ordinalize(this int number, GrammaticalGender gender) { - return Ordinalizer.Convert(number, number.ToString(CultureInfo.InvariantCulture), gender); - } - - private static DefaultOrdinalizer Ordinalizer - { - get - { - Func ordinalizerFactory; - - if (OrdinalizerFactories.TryGetValue(CultureInfo.CurrentUICulture.Name, out ordinalizerFactory)) - return ordinalizerFactory(); - - if (OrdinalizerFactories.TryGetValue(CultureInfo.CurrentUICulture.TwoLetterISOLanguageName, out ordinalizerFactory)) - return ordinalizerFactory(); - - return new DefaultOrdinalizer(); - } + return Configurator.Ordinalizer.Convert(number, number.ToString(CultureInfo.InvariantCulture), gender); } } } \ No newline at end of file