Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduces registry for localisers & exposes them via Configurator #243

Closed
wants to merge 11 commits into from
11 changes: 8 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -679,6 +680,10 @@ ByteSize.Parse("1.55 tB");
ByteSize.Parse("1.55 tb");
```

##<a id="configuration">Configuration</a>
Custom localisers for `Formatter`s, `NumberToWordsConverter`s, and `Ordinalizer`s may be added or updated via `Formatters`, `NumberToWordsConverters`, and `Ordinalizers` properties on `Configurator` class.
To register a localiser call `Register<T>(string localeCode)` or `Register(Func<TLocaliser> localiserFactory, string localeCode)` passing the desired localiser and the associated locale code; e.g. `Configurator.Formatters.Register<RomanianFormatter>("ro");`

##<a id="mix-this-into-your-framework-to-simplify-your-life">Mix this into your framework to simplify your life</a>
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?!

Expand Down Expand Up @@ -823,12 +828,12 @@ In cases like this in addition to creating a resource file you should also subcl
e.g. [RomanianFormatter](https://github.com/MehdiK/Humanizer/blob/master/src/Humanizer/Localisation/Formatters/RomanianFormatter.cs) and then override the methods that need the complex rules.
We think overriding the `GetResourceKey` method should be enough.
To see how to do that check out `RomanianFormatter` and `RussianFormatter`.
Then you return an instance of your class in the [Configurator](https://github.com/MehdiK/Humanizer/blob/master/src/Humanizer/Configuration/Configurator.cs) class in the getter of the [Formatter property](https://github.com/MehdiK/Humanizer/blob/master/src/Humanizer/Configuration/Configurator.cs) based on the current culture.
Then you register your formatter in the `FormatterRegistry` class with the associated culture.

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 `NumberToWordsConverterRegistry` with the associated locale 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).

Expand All @@ -848,4 +853,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.

##<a id="icon">Icon</a>
Icon created by [Tyrone Rieschiek](https://twitter.com/Inkventive)
Icon created by [Tyrone Rieschiek](https://twitter.com/Inkventive)
2 changes: 1 addition & 1 deletion release_notes.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
###In Development
- [#236](https://github.com/Mehdik/Humanizer/pull/236): Added Turkish localisation
- [#239](https://github.com/Mehdik/Humanizer/pull/239): Added Serbian localisation
- [#227](https://github.com/MehdiK/Humanizer/pull/227): Moved localiser registry to their own classes, allowed public access via Configurator, and made the default registrations lazy loaded

[Commits](https://github.com/MehdiK/Humanizer/compare/v1.24.1...master)

Expand All @@ -20,7 +21,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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,18 @@ public class CasingExtensions
public class Configurator
{
public Humanizer.DateTimeHumanizeStrategy.IDateTimeHumanizeStrategy DateTimeHumanizeStrategy { get; set; }
public Humanizer.Localisation.Formatters.IFormatter Formatter { get; }
public Humanizer.Configuration.LocaliserRegistry<Humanizer.Localisation.Formatters.IFormatter> Formatters { get; }
public Humanizer.Configuration.LocaliserRegistry<Humanizer.Localisation.NumberToWords.INumberToWordsConverter> NumberToWordsConverters { get; }
public Humanizer.Configuration.LocaliserRegistry<Humanizer.Localisation.Ordinalizers.IOrdinalizer> Ordinalizers { get; }
}

public class LocaliserRegistry`1
{
public LocaliserRegistry`1(TLocaliser defaultLocaliser) { }
public void Register(string localeCode) { }
public void Register(System.Func<> localiserFactory, string localeCode) { }
public void RegisterDefault(TLocaliser defaultLocaliser) { }
public TLocaliser ResolveForUiCulture() { }
}

public class DateHumanizeExtensions
Expand Down Expand Up @@ -200,6 +211,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() { }
Expand Down
71 changes: 51 additions & 20 deletions src/Humanizer/Configuration/Configurator.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using Humanizer.DateTimeHumanizeStrategy;
using Humanizer.Localisation.Formatters;
using Humanizer.Localisation.NumberToWords;
using Humanizer.Localisation.Ordinalizers;

namespace Humanizer.Configuration
{
Expand All @@ -11,33 +10,65 @@ namespace Humanizer.Configuration
/// </summary>
public static class Configurator
{
private static readonly IDictionary<string, Func<IFormatter>> FormatterFactories =
new Dictionary<string, Func<IFormatter>>(StringComparer.OrdinalIgnoreCase)
private static readonly LocaliserRegistry<IFormatter> _formatters = new FormatterRegistry();
/// <summary>
/// A registry of formatters used to format strings based on the current locale
/// </summary>
public static LocaliserRegistry<IFormatter> Formatters
{
get { return _formatters; }
}

private static readonly LocaliserRegistry<INumberToWordsConverter> _numberToWordsConverters = new NumberToWordsConverterRegistry();
/// <summary>
/// A registry of number to words converters used to localise ToWords and ToOrdinalWords methods
/// </summary>
public static LocaliserRegistry<INumberToWordsConverter> NumberToWordsConverters
{
{ "ro", () => new RomanianFormatter() },
{ "ru", () => new RussianFormatter() },
{ "ar", () => new ArabicFormatter() },
{ "he", () => new HebrewFormatter() },
{ "sk", () => new CzechSlovakPolishFormatter() },
{ "cs", () => new CzechSlovakPolishFormatter() },
{ "pl", () => new CzechSlovakPolishFormatter() },
{ "sr", () => new SerbianFormatter() }
};
get { return _numberToWordsConverters; }
}

private static LocaliserRegistry<IOrdinalizer> _ordinalizers = new OrdinalizerRegistry();
/// <summary>
/// A registry of ordinalizers used to localise Ordinalize method
/// </summary>
public static LocaliserRegistry<IOrdinalizer> Ordinalizers
{
get { return _ordinalizers; }
}

private static IDateTimeHumanizeStrategy _dateTimeHumanizeStrategy = new DefaultDateTimeHumanizeStrategy();

/// <summary>
/// The formatter to be used
/// </summary>
public static IFormatter Formatter
internal static IFormatter Formatter
{
get
{
return Formatters.ResolveForUiCulture();
}
}

/// <summary>
/// The converter to be used
/// </summary>
internal static INumberToWordsConverter NumberToWordsConverter
{
get
{
return NumberToWordsConverters.ResolveForUiCulture();
}
}

/// <summary>
/// The ordinalizer to be used
/// </summary>
internal static IOrdinalizer Ordinalizer
{
get
{
Func<IFormatter> formatterFactory;
if (FormatterFactories.TryGetValue(CultureInfo.CurrentUICulture.TwoLetterISOLanguageName, out formatterFactory))
return formatterFactory();

return new DefaultFormatter();
return Ordinalizers.ResolveForUiCulture();
}
}

Expand Down
19 changes: 19 additions & 0 deletions src/Humanizer/Configuration/FormatterRegistry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Humanizer.Localisation.Formatters;

namespace Humanizer.Configuration
{
internal class FormatterRegistry : LocaliserRegistry<IFormatter>
{
public FormatterRegistry() : base(new DefaultFormatter())
{
Register<RomanianFormatter>("ro");
Register<RussianFormatter>("ru");
Register<ArabicFormatter>("ar");
Register<HebrewFormatter>("he");
Register<CzechSlovakPolishFormatter>("sk");
Register<CzechSlovakPolishFormatter>("cs");
Register<CzechSlovakPolishFormatter>("pl");
Register<SerbianFormatter>("sr");
}
}
}
68 changes: 68 additions & 0 deletions src/Humanizer/Configuration/LocaliserRegistry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Globalization;

namespace Humanizer.Configuration
{
/// <summary>
/// A registry of localised system components with their associated locales
/// </summary>
/// <typeparam name="TLocaliser"></typeparam>
public class LocaliserRegistry<TLocaliser>
{
private readonly IDictionary<string, Lazy<TLocaliser>> _localisers = new Dictionary<string, Lazy<TLocaliser>>();
private TLocaliser _defaultLocaliser;

/// <summary>
/// Creates a localiser registry with the default localiser set to the provided value
/// </summary>
/// <param name="defaultLocaliser"></param>
public LocaliserRegistry(TLocaliser defaultLocaliser)
{
_defaultLocaliser = defaultLocaliser;
}

/// <summary>
/// Gets the localiser for the current UI culture
/// </summary>
public TLocaliser ResolveForUiCulture()
{
var culture = CultureInfo.CurrentUICulture;

Lazy<TLocaliser> factory;

if (_localisers.TryGetValue(culture.Name, out factory))
return factory.Value;

if (_localisers.TryGetValue(culture.TwoLetterISOLanguageName, out factory))
return factory.Value;

return _defaultLocaliser;
}

/// <summary>
/// Registers the localiser for the culture provided
/// </summary>
public void Register<T>(string localeCode)
where T: TLocaliser, new()
{
_localisers[localeCode] = new Lazy<TLocaliser>(() => new T());
}

/// <summary>
/// Registers the localiser for the culture provided
/// </summary>
public void Register(Func<TLocaliser> localiserFactory, string localeCode)
{
_localisers[localeCode] = new Lazy<TLocaliser>(localiserFactory);
}

/// <summary>
/// Registers the localiser as the catch all
/// </summary>
public void RegisterDefault(TLocaliser defaultLocaliser)
{
_defaultLocaliser = defaultLocaliser;
}
}
}
21 changes: 21 additions & 0 deletions src/Humanizer/Configuration/NumberToWordsConverterRegistry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Humanizer.Localisation.NumberToWords;

namespace Humanizer.Configuration
{
internal class NumberToWordsConverterRegistry : LocaliserRegistry<INumberToWordsConverter>
{
public NumberToWordsConverterRegistry() : base(new DefaultNumberToWordsConverter())
{
Register<EnglishNumberToWordsConverter>("en");
Register<ArabicNumberToWordsConverter>("ar");
Register<FarsiNumberToWordsConverter>("fa");
Register<SpanishNumberToWordsConverter>("es");
Register<PolishNumberToWordsConverter>("pl");
Register<BrazilianPortugueseNumberToWordsConverter>("pt-BR");
Register<RussianNumberToWordsConverter>("ru");
Register<FrenchNumberToWordsConverter>("fr");
Register<DutchNumberToWordsConverter>("nl");
Register<HebrewNumberToWordsConverter>("he");
}
}
}
15 changes: 15 additions & 0 deletions src/Humanizer/Configuration/OrdinalizerRegistry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Humanizer.Localisation.Ordinalizers;

namespace Humanizer.Configuration
{
internal class OrdinalizerRegistry : LocaliserRegistry<IOrdinalizer>
{
public OrdinalizerRegistry() : base(new DefaultOrdinalizer())
{
Register<EnglishOrdinalizer>("en");
Register<SpanishOrdinalizer>("es");
Register<RussianOrdinalizer>("ru");
Register<BrazilianPortugueseOrdinalizer>("pt-BR");
}
}
}
6 changes: 6 additions & 0 deletions src/Humanizer/Humanizer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="Localisation\Formatters\SerbianFormatter.cs" />
<Compile Include="Configuration\LocaliserRegistry.cs" />
<Compile Include="Configuration\FormatterRegistry.cs" />
<Compile Include="Configuration\NumberToWordsConverterRegistry.cs" />
<Compile Include="Configuration\OrdinalizerRegistry.cs" />
<Compile Include="Localisation\NumberToWords\BrazilianPortugueseNumberToWordsConverter.cs" />
<Compile Include="GrammaticalCase.cs" />
<Compile Include="GrammaticalGender.cs" />
Expand All @@ -81,9 +85,11 @@
<Compile Include="DateTimeHumanizeStrategy\DefaultDateTimeHumanizeStrategy.cs" />
<Compile Include="DateTimeHumanizeStrategy\IDateTimeHumanizeStrategy.cs" />
<Compile Include="DateTimeHumanizeStrategy\PrecisionDateTimeHumanizeStrategy.cs" />
<Compile Include="Localisation\NumberToWords\INumberToWordsConverter.cs" />
<Compile Include="Localisation\NumberToWords\PolishNumberToWordsConverter.cs" />
<Compile Include="Localisation\NumberToWords\RussianNumberToWordsConverter.cs" />
<Compile Include="Localisation\Formatters\HebrewFormatter.cs" />
<Compile Include="Localisation\Ordinalizers\IOrdinalizer.cs" />
<Compile Include="Localisation\Ordinalizers\RussianOrdinalizer.cs" />
<Compile Include="Localisation\Ordinalizers\SpanishOrdinalizer.cs" />
<Compile Include="Localisation\Ordinalizers\DefaultOrdinalizer.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace Humanizer.Localisation.NumberToWords
{
internal class DefaultNumberToWordsConverter
internal class DefaultNumberToWordsConverter : INumberToWordsConverter
{
/// <summary>
/// for Russian locale
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
namespace Humanizer.Localisation.NumberToWords
{
/// <summary>
/// An interface you should implement to localise ToWords and ToOrdinalWords methods
/// </summary>
public interface INumberToWordsConverter
{
/// <summary>
/// Converts the number to string using the locale's default grammatical gender
/// </summary>
/// <param name="number"></param>
/// <returns></returns>
string Convert(int number);

/// <summary>
/// Converts the number to string using the provided grammatical gender
/// </summary>
/// <param name="number"></param>
/// <param name="gender"></param>
/// <returns></returns>
string Convert(int number, GrammaticalGender gender);

/// <summary>
/// Converts the number to ordinal string using the locale's default grammatical gender
/// </summary>
/// <param name="number"></param>
/// <returns></returns>
string ConvertToOrdinal(int number);

/// <summary>
/// Converts the number to ordinal string using the provided grammatical gender
/// </summary>
/// <param name="number"></param>
/// <param name="gender"></param>
/// <returns></returns>
string ConvertToOrdinal(int number, GrammaticalGender gender);
}
}
Original file line number Diff line number Diff line change
@@ -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)
{
Expand Down
Loading