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

Added optional Culture parameter to NumberToWords #300

Merged
merged 17 commits into from
Jun 22, 2014
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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("ru")) => "eleven"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example is not correct, either it should be new CultureInfo("en") or the result "одинадцать"

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thx, i've fixed it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

С двумя "н"

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hazzik as i said, i've fixed sample. and there is no typo you mentioned. in fact, it only existed in @mexx 's comment :)

1.ToWords(GrammaticalGender.Masculine, new CultureInfo("ru")) => "один"
```

###<a id="number-toordinalwords">Number to ordinal words</a>
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:

Expand Down Expand Up @@ -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"
```

###<a id="roman-numerals">Roman numerals</a>
Humanizer can change numbers to Roman numerals using the `ToRoman` extension. The numbers 1 to 10 can be expressed in Roman numerals as follows:

Expand Down
1 change: 1 addition & 0 deletions release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ 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 TLocaliser ResolveForCulture(System.Globalization.CultureInfo culture) { }
public TLocaliser ResolveForUiCulture() { }
Expand Down Expand Up @@ -291,10 +292,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
Expand Down
21 changes: 20 additions & 1 deletion src/Humanizer.Tests/NumberToWordsTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Xunit;
using System.Globalization;
using Xunit;
using Xunit.Extensions;

namespace Humanizer.Tests
Expand Down Expand Up @@ -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)));
}
}
}
8 changes: 3 additions & 5 deletions src/Humanizer/Configuration/Configurator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,10 @@ internal static IFormatter GetFormatter(CultureInfo culture)
/// <summary>
/// The converter to be used
/// </summary>
internal static INumberToWordsConverter NumberToWordsConverter
/// <param name="culture">The culture to retrieve number to words converter for. Null means that current thread's UI culture should be used.</param>
internal static INumberToWordsConverter GetNumberToWordsConverter(CultureInfo culture)
{
get
{
return NumberToWordsConverters.ResolveForUiCulture();
}
return NumberToWordsConverters.ResolveForCulture(culture);
}

/// <summary>
Expand Down
18 changes: 15 additions & 3 deletions src/Humanizer/Configuration/LocaliserRegistry.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Globalization;

namespace Humanizer.Configuration
Expand All @@ -7,10 +8,12 @@ namespace Humanizer.Configuration
/// A registry of localised system components with their associated locales
/// </summary>
/// <typeparam name="TLocaliser"></typeparam>
public class LocaliserRegistry<TLocaliser>
public class LocaliserRegistry<TLocaliser>
where TLocaliser : class
{
private readonly IDictionary<string, TLocaliser> _localisers = new Dictionary<string, TLocaliser>();
private readonly TLocaliser _defaultLocaliser;
private readonly Func<CultureInfo, TLocaliser> _defaultLocaliserFactory;

/// <summary>
/// Creates a localiser registry with the default localiser set to the provided value
Expand All @@ -21,6 +24,15 @@ public LocaliserRegistry(TLocaliser defaultLocaliser)
_defaultLocaliser = defaultLocaliser;
}

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

/// <summary>
/// Gets the localiser for the current thread's UI culture
/// </summary>
Expand All @@ -45,7 +57,7 @@ public TLocaliser ResolveForCulture(CultureInfo culture)
if (_localisers.TryGetValue(culture.TwoLetterISOLanguageName, out localiser))
return localiser;

return _defaultLocaliser;
return _defaultLocaliser ?? _defaultLocaliserFactory(culture);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace Humanizer.Configuration
{
internal class NumberToWordsConverterRegistry : LocaliserRegistry<INumberToWordsConverter>
{
public NumberToWordsConverterRegistry() : base(new DefaultNumberToWordsConverter())
public NumberToWordsConverterRegistry() : base((culture) => new DefaultNumberToWordsConverter(culture))
{
Register("en", new EnglishNumberToWordsConverter());
Register("ar", new ArabicNumberToWordsConverter());
Expand Down
2 changes: 2 additions & 0 deletions src/Humanizer/Humanizer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@
<Compile Include="GrammaticalGender.cs" />
<Compile Include="Localisation\GrammaticalNumber\RussianGrammaticalNumber.cs" />
<Compile Include="Localisation\GrammaticalNumber\RussianGrammaticalNumberDetector.cs" />
<Compile Include="Localisation\NumberToWords\GenderedNumberToWordsConverter.cs" />
<Compile Include="Localisation\NumberToWords\GenderlessNumberToWordsConverter.cs" />
<Compile Include="Localisation\NumberToWords\SlovenianNumberToWordsConverter.cs" />
<Compile Include="Localisation\NumberToWords\DutchNumberToWordsConverter.cs" />
<Compile Include="Localisation\NumberToWords\DefaultNumberToWordsConverter.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = { "", "ألفاً", "مليوناً", "ملياراً", "تريليوناً", "كوادريليوناً", "كوينتليوناً", "سكستيليوناً" };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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" };
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,51 +1,38 @@
namespace Humanizer.Localisation.NumberToWords
using System.Globalization;

namespace Humanizer.Localisation.NumberToWords
{
internal class DefaultNumberToWordsConverter : INumberToWordsConverter
internal sealed class DefaultNumberToWordsConverter : GenderlessNumberToWordsConverter
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you make this class sealed?

P.S. In fact I've never found a legitimate case to create a sealed class.

{
/// <summary>
/// for Russian locale
/// 1.ToWords(GrammaticalGender.Masculine) -> "один"
/// 1.ToWords(GrammaticalGender.Feminine) -> "одна"
/// </summary>
/// <param name="number">Number to be turned to words</param>
/// <param name="gender">The grammatical gender to use for output words</param>
/// <returns></returns>
public virtual string Convert(int number, GrammaticalGender gender)
{
return Convert(number);
}
private readonly CultureInfo _culture;

/// <summary>
/// 3501.ToWords() -> "three thousand five hundred and one"
/// Constructor.
/// </summary>
/// <param name="number">Number to be turned to words</param>
/// <returns></returns>
public virtual string Convert(int number)
/// <param name="culture">Culture to use.</param>
public DefaultNumberToWordsConverter(CultureInfo culture)
{
return number.ToString();
_culture = culture;
}

/// <summary>
/// for Brazilian Portuguese
/// 1.ToOrdinalWords(GrammaticalGender.Masculine) -> "primeiro"
/// 1.ToOrdinalWords(GrammaticalGender.Feminine) -> "primeira"
/// 3501.ToWords() -> "three thousand five hundred and one"
/// </summary>
/// <param name="number">Number to be turned to words</param>
/// <param name="gender">The grammatical gender to use for output words</param>
/// <returns></returns>
public virtual string ConvertToOrdinal(int number, GrammaticalGender gender)
public override string Convert(int number)
{
return ConvertToOrdinal(number);
return number.ToString(_culture);
}

/// <summary>
/// 1.ToOrdinalWords() -> "first"
/// </summary>
/// <param name="number">Number to be turned to ordinal words</param>
/// <returns></returns>
public virtual string ConvertToOrdinal(int number)
public override string ConvertToOrdinal(int number)
{
return number.ToString();
return number.ToString(_culture);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Humanizer.Localisation.NumberToWords
/// Used the rules as stated here.
/// http://www.beterspellen.nl/website/?pag=110
/// </summary>
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" };
Expand All @@ -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)
{
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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" };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = { "صفر", "ده", "بیست", "سی", "چهل", "پنجاه", "شصت", "هفتاد", "هشتاد", "نود" };
Expand Down Expand Up @@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"};
Expand All @@ -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];
Expand Down Expand Up @@ -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);

Expand Down
Loading