From 530b2f51af2d7e08fa302cff1bbaa064c8fc4d56 Mon Sep 17 00:00:00 2001 From: Dmitry Gokun Date: Wed, 28 May 2014 22:37:46 +0300 Subject: [PATCH 01/16] Added option Culture parameter to DateTime.Humanize --- ...provalTest.approve_public_api.approved.txt | 19 +++++---- src/Humanizer.Tests/DateHumanize.cs | 42 +++++++++++++------ .../Localisation/nl/DateHumanizeTests.cs | 10 ++--- src/Humanizer/Configuration/Configurator.cs | 11 +++-- .../Configuration/LocaliserRegistry.cs | 13 +++++- src/Humanizer/DateHumanizeExtensions.cs | 6 ++- .../DefaultDateTimeHumanizeStrategy.cs | 30 +++++++------ .../IDateTimeHumanizeStrategy.cs | 3 +- .../PrecisionDateTimeHumanizeStrategy.cs | 19 +++++---- .../Formatters/DefaultFormatter.cs | 30 +++++++------ .../Localisation/Formatters/IFormatter.cs | 10 +++-- src/Humanizer/Localisation/Resources.cs | 8 ++-- src/Humanizer/TimeSpanHumanizeExtensions.cs | 2 +- 13 files changed, 126 insertions(+), 77 deletions(-) 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 f1381d093..d7b6055ab 100644 --- a/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt +++ b/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt @@ -87,29 +87,30 @@ public class LocaliserRegistry`1 public void Register(string localeCode) { } public void Register(System.Func<> localiserFactory, string localeCode) { } public void RegisterDefault(TLocaliser defaultLocaliser) { } + public TLocaliser ResolveForCulture(System.Globalization.CultureInfo culture) { } public TLocaliser ResolveForUiCulture() { } } public class DateHumanizeExtensions { - public string Humanize(System.DateTime input, bool utcDate, System.Nullable dateToCompareAgainst) { } + public string Humanize(System.DateTime input, bool utcDate, System.Nullable dateToCompareAgainst, System.Globalization.CultureInfo culture) { } } public class DefaultDateTimeHumanizeStrategy { public DefaultDateTimeHumanizeStrategy() { } - public string Humanize(System.DateTime input, System.DateTime comparisonBase) { } + public string Humanize(System.DateTime input, System.DateTime comparisonBase, System.Globalization.CultureInfo culture) { } } public interface IDateTimeHumanizeStrategy { - string Humanize(System.DateTime input, System.DateTime comparisonBase); + string Humanize(System.DateTime input, System.DateTime comparisonBase, System.Globalization.CultureInfo culture); } public class PrecisionDateTimeHumanizeStrategy { public PrecisionDateTimeHumanizeStrategy(double precision) { } - public string Humanize(System.DateTime input, System.DateTime comparisonBase) { } + public string Humanize(System.DateTime input, System.DateTime comparisonBase, System.Globalization.CultureInfo culture) { } } public class EnumDehumanizeExtensions @@ -215,16 +216,16 @@ public interface ICollectionFormatter public class DefaultFormatter { public DefaultFormatter() { } - public string DateHumanize(Humanizer.Localisation.TimeUnit timeUnit, Humanizer.Localisation.Tense timeUnitTense, int unit) { } - public string DateHumanize_Now() { } + public string DateHumanize(Humanizer.Localisation.TimeUnit timeUnit, Humanizer.Localisation.Tense timeUnitTense, int unit, System.Globalization.CultureInfo culture) { } + public string DateHumanize_Now(System.Globalization.CultureInfo culture) { } public string TimeSpanHumanize(Humanizer.Localisation.TimeUnit timeUnit, int unit) { } public string TimeSpanHumanize_Zero() { } } public interface IFormatter { - string DateHumanize(Humanizer.Localisation.TimeUnit timeUnit, Humanizer.Localisation.Tense timeUnitTense, int unit); - string DateHumanize_Now(); + string DateHumanize(Humanizer.Localisation.TimeUnit timeUnit, Humanizer.Localisation.Tense timeUnitTense, int unit, System.Globalization.CultureInfo culture); + string DateHumanize_Now(System.Globalization.CultureInfo culture); string TimeSpanHumanize(Humanizer.Localisation.TimeUnit timeUnit, int unit); string TimeSpanHumanize_Zero(); } @@ -250,7 +251,7 @@ public class ResourceKeys public class Resources { - public string GetResource(string resourceKey) { } + public string GetResource(string resourceKey, System.Globalization.CultureInfo culture) { } } public enum Tense diff --git a/src/Humanizer.Tests/DateHumanize.cs b/src/Humanizer.Tests/DateHumanize.cs index e498a2904..42d312c99 100644 --- a/src/Humanizer.Tests/DateHumanize.cs +++ b/src/Humanizer.Tests/DateHumanize.cs @@ -1,4 +1,6 @@ using System; +using System.Globalization; +using System.Threading; using Humanizer.Configuration; using Humanizer.DateTimeHumanizeStrategy; using Humanizer.Localisation; @@ -8,26 +10,33 @@ namespace Humanizer.Tests { public class DateHumanize { - static void VerifyWithCurrentDate(string expectedString, TimeSpan deltaFromNow) + private static void VerifyWithCurrentDate(string expectedString, TimeSpan deltaFromNow) { - var utcNow = DateTime.UtcNow; - var localNow = DateTime.Now; + CheckWithExplicitAndImplicitCulture(culture => + { + var utcNow = DateTime.UtcNow; + var localNow = DateTime.Now; - // feels like the only way to avoid breaking tests because CPU ticks over is to inject the base date - Assert.Equal(expectedString, utcNow.Add(deltaFromNow).Humanize(utcDate: true, dateToCompareAgainst: utcNow)); - Assert.Equal(expectedString, localNow.Add(deltaFromNow).Humanize(utcDate: false, dateToCompareAgainst: localNow)); + // feels like the only way to avoid breaking tests because CPU ticks over is to inject the base date + Assert.Equal(expectedString, utcNow.Add(deltaFromNow).Humanize(true, utcNow, culture)); + Assert.Equal(expectedString, localNow.Add(deltaFromNow).Humanize(false, localNow, culture)); + }); } - static void VerifyWithDateInjection(string expectedString, TimeSpan deltaFromNow) + private static void VerifyWithDateInjection(string expectedString, TimeSpan deltaFromNow) { - var utcNow = new DateTime(2013, 6, 20, 9, 58, 22, DateTimeKind.Utc); - var now = new DateTime(2013, 6, 20, 11, 58, 22, DateTimeKind.Local); + CheckWithExplicitAndImplicitCulture(culture => + { + var utcNow = new DateTime(2013, 6, 20, 9, 58, 22, DateTimeKind.Utc); + var now = new DateTime(2013, 6, 20, 11, 58, 22, DateTimeKind.Local); - Assert.Equal(expectedString, utcNow.Add(deltaFromNow).Humanize(utcDate: true, dateToCompareAgainst: utcNow)); - Assert.Equal(expectedString, now.Add(deltaFromNow).Humanize(false, now)); + Assert.Equal(expectedString, utcNow.Add(deltaFromNow).Humanize(true, utcNow, culture)); + Assert.Equal(expectedString, now.Add(deltaFromNow).Humanize(false, now, culture)); + }); } - public static void Verify(string expectedString, int unit, TimeUnit timeUnit, Tense tense, double? precision = null) + public static void Verify(string expectedString, int unit, TimeUnit timeUnit, Tense tense, + double? precision = null) { if (precision.HasValue) Configurator.DateTimeHumanizeStrategy = new PrecisionDateTimeHumanizeStrategy(precision.Value); @@ -68,5 +77,14 @@ public static void Verify(string expectedString, int unit, TimeUnit timeUnit, Te VerifyWithCurrentDate(expectedString, deltaFromNow); VerifyWithDateInjection(expectedString, deltaFromNow); } + + private static void CheckWithExplicitAndImplicitCulture(Action action) + { + action(null); + + CultureInfo culture = Thread.CurrentThread.CurrentUICulture; + using (new AmbientCulture(culture.TwoLetterISOLanguageName == "da" ? "tr" : "da")) + action(culture); + } } } \ No newline at end of file diff --git a/src/Humanizer.Tests/Localisation/nl/DateHumanizeTests.cs b/src/Humanizer.Tests/Localisation/nl/DateHumanizeTests.cs index d598bbebc..85e736950 100644 --- a/src/Humanizer.Tests/Localisation/nl/DateHumanizeTests.cs +++ b/src/Humanizer.Tests/Localisation/nl/DateHumanizeTests.cs @@ -14,7 +14,7 @@ public class DateHumanizeTests : AmbientCulture [InlineData(-1, "gisteren")] public void DaysAgo(int days, string expected) { - Assert.Equal(expected, DateTime.UtcNow.AddDays(days).Humanize()); + DateHumanize.Verify(expected, days, TimeUnit.Day, Tense.Past); } [Theory] @@ -22,7 +22,7 @@ public void DaysAgo(int days, string expected) [InlineData(-1, "één uur geleden")] public void HoursAgo(int hours, string expected) { - Assert.Equal(expected, DateTime.UtcNow.AddHours(hours).Humanize()); + DateHumanize.Verify(expected, hours, TimeUnit.Hour, Tense.Past); } [Theory] @@ -39,7 +39,7 @@ public void MinutesAgo(int minutes, string expected) [InlineData(-1, "één maand geleden")] public void MonthsAgo(int months, string expected) { - Assert.Equal(expected, DateTime.UtcNow.AddMonths(months).Humanize()); + DateHumanize.Verify(expected, months, TimeUnit.Month, Tense.Past); } [Theory] @@ -47,7 +47,7 @@ public void MonthsAgo(int months, string expected) [InlineData(-1, "één seconde geleden")] public void SecondsAgo(int seconds, string expected) { - Assert.Equal(expected, DateTime.UtcNow.AddSeconds(seconds).Humanize()); + DateHumanize.Verify(expected, seconds, TimeUnit.Second, Tense.Past); } [Theory] @@ -55,7 +55,7 @@ public void SecondsAgo(int seconds, string expected) [InlineData(-1, "één jaar geleden")] public void YearsAgo(int years, string expected) { - Assert.Equal(expected, DateTime.UtcNow.AddYears(years).Humanize()); + DateHumanize.Verify(expected, years, TimeUnit.Year, Tense.Past); } } } diff --git a/src/Humanizer/Configuration/Configurator.cs b/src/Humanizer/Configuration/Configurator.cs index af94e3c22..581fb7f01 100644 --- a/src/Humanizer/Configuration/Configurator.cs +++ b/src/Humanizer/Configuration/Configurator.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Reflection; using Humanizer.DateTimeHumanizeStrategy; using Humanizer.Localisation.Formatters; @@ -58,16 +59,14 @@ internal static ICollectionFormatter CollectionFormatter return CollectionFormatters.ResolveForUiCulture(); } } - + /// /// The formatter to be used /// - internal static IFormatter Formatter + /// The culture to retrieve formatter for. If not specified, current thread's UI culture is used. + internal static IFormatter GetFormatter(CultureInfo culture = null) { - get - { - return Formatters.ResolveForUiCulture(); - } + return Formatters.ResolveForCulture(culture); } /// diff --git a/src/Humanizer/Configuration/LocaliserRegistry.cs b/src/Humanizer/Configuration/LocaliserRegistry.cs index 8491d4a9f..a0dab6ec0 100644 --- a/src/Humanizer/Configuration/LocaliserRegistry.cs +++ b/src/Humanizer/Configuration/LocaliserRegistry.cs @@ -23,11 +23,20 @@ public LocaliserRegistry(TLocaliser defaultLocaliser) } /// - /// Gets the localiser for the current UI culture + /// Gets the localiser for the current thread's UI culture /// public TLocaliser ResolveForUiCulture() { - var culture = CultureInfo.CurrentUICulture; + return ResolveForCulture(); + } + + /// + /// Gets the localiser for the specified culture + /// + /// The culture to retrieve localiser for. If not specified, current thread's UI culture is used. + public TLocaliser ResolveForCulture(CultureInfo culture = null) + { + culture = culture ?? CultureInfo.CurrentUICulture; Lazy factory; diff --git a/src/Humanizer/DateHumanizeExtensions.cs b/src/Humanizer/DateHumanizeExtensions.cs index 7147e3baf..70874e073 100644 --- a/src/Humanizer/DateHumanizeExtensions.cs +++ b/src/Humanizer/DateHumanizeExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using Humanizer.Configuration; namespace Humanizer @@ -14,15 +15,16 @@ public static class DateHumanizeExtensions /// The date to be humanized /// Boolean value indicating whether the date is in UTC or local /// Date to compare the input against. If null, current date is used as base + /// Culture to use. If null, current thread's UI culture is used. /// distance of time in words - public static string Humanize(this DateTime input, bool utcDate = true, DateTime? dateToCompareAgainst = null) + public static string Humanize(this DateTime input, bool utcDate = true, DateTime? dateToCompareAgainst = null, CultureInfo culture = null) { var comparisonBase = dateToCompareAgainst ?? DateTime.UtcNow; if (!utcDate) comparisonBase = comparisonBase.ToLocalTime(); - return Configurator.DateTimeHumanizeStrategy.Humanize(input, comparisonBase); + return Configurator.DateTimeHumanizeStrategy.Humanize(input, comparisonBase, culture); } } } \ No newline at end of file diff --git a/src/Humanizer/DateTimeHumanizeStrategy/DefaultDateTimeHumanizeStrategy.cs b/src/Humanizer/DateTimeHumanizeStrategy/DefaultDateTimeHumanizeStrategy.cs index 284bab811..4ee8195f9 100644 --- a/src/Humanizer/DateTimeHumanizeStrategy/DefaultDateTimeHumanizeStrategy.cs +++ b/src/Humanizer/DateTimeHumanizeStrategy/DefaultDateTimeHumanizeStrategy.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using Humanizer.Configuration; using Humanizer.Localisation; @@ -15,53 +16,56 @@ public class DefaultDateTimeHumanizeStrategy : IDateTimeHumanizeStrategy /// /// /// + /// /// - public string Humanize(DateTime input, DateTime comparisonBase) + public string Humanize(DateTime input, DateTime comparisonBase, CultureInfo culture) { var tense = input > comparisonBase ? Tense.Future : Tense.Past; var ts = new TimeSpan(Math.Abs(comparisonBase.Ticks - input.Ticks)); + var formatter = Configurator.GetFormatter(culture); + if (ts.TotalMilliseconds < 500) - return Configurator.Formatter.DateHumanize(TimeUnit.Millisecond, tense, 0); + return formatter.DateHumanize(TimeUnit.Millisecond, tense, 0, culture); if (ts.TotalSeconds < 60) - return Configurator.Formatter.DateHumanize(TimeUnit.Second, tense, ts.Seconds); + return formatter.DateHumanize(TimeUnit.Second, tense, ts.Seconds, culture); if (ts.TotalSeconds < 120) - return Configurator.Formatter.DateHumanize(TimeUnit.Minute, tense, 1); + return formatter.DateHumanize(TimeUnit.Minute, tense, 1, culture); if (ts.TotalMinutes < 60) - return Configurator.Formatter.DateHumanize(TimeUnit.Minute, tense, ts.Minutes); + return formatter.DateHumanize(TimeUnit.Minute, tense, ts.Minutes, culture); if (ts.TotalMinutes < 90) - return Configurator.Formatter.DateHumanize(TimeUnit.Hour, tense, 1); + return formatter.DateHumanize(TimeUnit.Hour, tense, 1, culture); if (ts.TotalHours < 24) - return Configurator.Formatter.DateHumanize(TimeUnit.Hour, tense, ts.Hours); + return formatter.DateHumanize(TimeUnit.Hour, tense, ts.Hours, culture); if (ts.TotalHours < 48) - return Configurator.Formatter.DateHumanize(TimeUnit.Day, tense, 1); + return formatter.DateHumanize(TimeUnit.Day, tense, 1, culture); if (ts.TotalDays < 28) - return Configurator.Formatter.DateHumanize(TimeUnit.Day, tense, ts.Days); + return formatter.DateHumanize(TimeUnit.Day, tense, ts.Days, culture); if (ts.TotalDays >= 28 && ts.TotalDays < 30) { if (comparisonBase.Date.AddMonths(tense == Tense.Future ? 1 : -1) == input.Date) - return Configurator.Formatter.DateHumanize(TimeUnit.Month, tense, 1); - return Configurator.Formatter.DateHumanize(TimeUnit.Day, tense, ts.Days); + return formatter.DateHumanize(TimeUnit.Month, tense, 1, culture); + return formatter.DateHumanize(TimeUnit.Day, tense, ts.Days, culture); } if (ts.TotalDays < 345) { int months = Convert.ToInt32(Math.Floor(ts.TotalDays / 29.5)); - return Configurator.Formatter.DateHumanize(TimeUnit.Month, tense, months); + return formatter.DateHumanize(TimeUnit.Month, tense, months, culture); } int years = Convert.ToInt32(Math.Floor(ts.TotalDays / 365)); if (years == 0) years = 1; - return Configurator.Formatter.DateHumanize(TimeUnit.Year, tense, years); + return formatter.DateHumanize(TimeUnit.Year, tense, years, culture); } } } \ No newline at end of file diff --git a/src/Humanizer/DateTimeHumanizeStrategy/IDateTimeHumanizeStrategy.cs b/src/Humanizer/DateTimeHumanizeStrategy/IDateTimeHumanizeStrategy.cs index 5aedd233b..7d663a794 100644 --- a/src/Humanizer/DateTimeHumanizeStrategy/IDateTimeHumanizeStrategy.cs +++ b/src/Humanizer/DateTimeHumanizeStrategy/IDateTimeHumanizeStrategy.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; namespace Humanizer.DateTimeHumanizeStrategy { @@ -10,6 +11,6 @@ public interface IDateTimeHumanizeStrategy /// /// Calculates the distance of time in words between two provided dates used for DateTime.Humanize /// - string Humanize(DateTime input, DateTime comparisonBase); + string Humanize(DateTime input, DateTime comparisonBase, CultureInfo culture); } } \ No newline at end of file diff --git a/src/Humanizer/DateTimeHumanizeStrategy/PrecisionDateTimeHumanizeStrategy.cs b/src/Humanizer/DateTimeHumanizeStrategy/PrecisionDateTimeHumanizeStrategy.cs index 06c243b8b..cec212dd3 100644 --- a/src/Humanizer/DateTimeHumanizeStrategy/PrecisionDateTimeHumanizeStrategy.cs +++ b/src/Humanizer/DateTimeHumanizeStrategy/PrecisionDateTimeHumanizeStrategy.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using Humanizer.Configuration; using Humanizer.Localisation; @@ -25,8 +26,9 @@ public PrecisionDateTimeHumanizeStrategy(double precision = .75) /// /// /// + /// /// - public string Humanize(DateTime input, DateTime comparisonBase) + public string Humanize(DateTime input, DateTime comparisonBase, CultureInfo culture) { var ts = new TimeSpan(Math.Abs(comparisonBase.Ticks - input.Ticks)); var tense = input > comparisonBase ? Tense.Future : Tense.Past; @@ -59,13 +61,14 @@ public string Humanize(DateTime input, DateTime comparisonBase) } // start computing result from larger units to smaller ones - if (years > 0) return Configurator.Formatter.DateHumanize(TimeUnit.Year, tense, years); - if (months > 0) return Configurator.Formatter.DateHumanize(TimeUnit.Month, tense, months); - if (days > 0) return Configurator.Formatter.DateHumanize(TimeUnit.Day, tense, days); - if (hours > 0) return Configurator.Formatter.DateHumanize(TimeUnit.Hour, tense, hours); - if (minutes > 0) return Configurator.Formatter.DateHumanize(TimeUnit.Minute, tense, minutes); - if (seconds > 0) return Configurator.Formatter.DateHumanize(TimeUnit.Second, tense, seconds); - return Configurator.Formatter.DateHumanize(TimeUnit.Millisecond, tense, 0); + var formatter = Configurator.GetFormatter(culture); + if (years > 0) return formatter.DateHumanize(TimeUnit.Year, tense, years, culture); + if (months > 0) return formatter.DateHumanize(TimeUnit.Month, tense, months, culture); + if (days > 0) return formatter.DateHumanize(TimeUnit.Day, tense, days, culture); + if (hours > 0) return formatter.DateHumanize(TimeUnit.Hour, tense, hours, culture); + if (minutes > 0) return formatter.DateHumanize(TimeUnit.Minute, tense, minutes, culture); + if (seconds > 0) return formatter.DateHumanize(TimeUnit.Second, tense, seconds, culture); + return formatter.DateHumanize(TimeUnit.Millisecond, tense, 0, culture); } } } \ No newline at end of file diff --git a/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs b/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs index 37eca6839..66ec76a77 100644 --- a/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs +++ b/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs @@ -1,4 +1,6 @@ -namespace Humanizer.Localisation.Formatters +using System.Globalization; + +namespace Humanizer.Localisation.Formatters { /// /// Default implementation of IFormatter interface. @@ -8,10 +10,11 @@ public class DefaultFormatter : IFormatter /// /// Now /// + /// /// Returns Now - public virtual string DateHumanize_Now() + public virtual string DateHumanize_Now(CultureInfo culture) { - return GetResourceForDate(TimeUnit.Millisecond, Tense.Past, 0); + return GetResourceForDate(TimeUnit.Millisecond, Tense.Past, 0, culture); } /// @@ -20,10 +23,11 @@ public virtual string DateHumanize_Now() /// /// /// + /// /// - public virtual string DateHumanize(TimeUnit timeUnit, Tense timeUnitTense, int unit) + public virtual string DateHumanize(TimeUnit timeUnit, Tense timeUnitTense, int unit, CultureInfo culture) { - return GetResourceForDate(timeUnit, timeUnitTense, unit); + return GetResourceForDate(timeUnit, timeUnitTense, unit, culture); } /// @@ -46,26 +50,27 @@ public virtual string TimeSpanHumanize(TimeUnit timeUnit, int unit) return GetResourceForTimeSpan(timeUnit, unit); } - private string GetResourceForDate(TimeUnit unit, Tense timeUnitTense, int count) + private string GetResourceForDate(TimeUnit unit, Tense timeUnitTense, int count, CultureInfo culture) { string resourceKey = ResourceKeys.DateHumanize.GetResourceKey(unit, timeUnitTense: timeUnitTense, count: count); - return count == 1 ? Format(resourceKey) : Format(resourceKey, count); + return count == 1 ? Format(resourceKey, culture) : Format(resourceKey, count, culture); } private string GetResourceForTimeSpan(TimeUnit unit, int count) { string resourceKey = ResourceKeys.TimeSpanHumanize.GetResourceKey(unit, count); - return count == 1 ? Format(resourceKey) : Format(resourceKey, count); + return count == 1 ? Format(resourceKey, null) : Format(resourceKey, count, null); } /// /// /// /// + /// /// - protected virtual string Format(string resourceKey) + protected virtual string Format(string resourceKey, CultureInfo culture) { - return Resources.GetResource(GetResourceKey(resourceKey)); + return Resources.GetResource(GetResourceKey(resourceKey), culture); } /// @@ -73,10 +78,11 @@ protected virtual string Format(string resourceKey) /// /// /// + /// /// - protected virtual string Format(string resourceKey, int number) + protected virtual string Format(string resourceKey, int number, CultureInfo culture) { - return Resources.GetResource(GetResourceKey(resourceKey, number)).FormatWith(number); + return Resources.GetResource(GetResourceKey(resourceKey, number), culture).FormatWith(number); } /// diff --git a/src/Humanizer/Localisation/Formatters/IFormatter.cs b/src/Humanizer/Localisation/Formatters/IFormatter.cs index 887209658..462efef21 100644 --- a/src/Humanizer/Localisation/Formatters/IFormatter.cs +++ b/src/Humanizer/Localisation/Formatters/IFormatter.cs @@ -1,4 +1,6 @@ -namespace Humanizer.Localisation.Formatters +using System.Globalization; + +namespace Humanizer.Localisation.Formatters { /// /// Implement this interface if your language has complex rules around dealing with numbers. @@ -10,8 +12,9 @@ public interface IFormatter /// /// Now /// + /// /// Returns Now - string DateHumanize_Now(); + string DateHumanize_Now(CultureInfo culture); /// /// Returns the string representation of the provided DateTime @@ -19,8 +22,9 @@ public interface IFormatter /// /// /// + /// /// - string DateHumanize(TimeUnit timeUnit, Tense timeUnitTense, int unit); + string DateHumanize(TimeUnit timeUnit, Tense timeUnitTense, int unit, CultureInfo culture); /// /// 0 seconds diff --git a/src/Humanizer/Localisation/Resources.cs b/src/Humanizer/Localisation/Resources.cs index 1e028632b..6eafd3cfd 100644 --- a/src/Humanizer/Localisation/Resources.cs +++ b/src/Humanizer/Localisation/Resources.cs @@ -1,4 +1,5 @@ -using System.Resources; +using System.Globalization; +using System.Resources; namespace Humanizer.Localisation { @@ -13,10 +14,11 @@ public static class Resources /// Returns the value of the specified string resource /// /// The name of the resource to retrieve. + /// The culture of the resource to retrieve. /// The value of the resource localized for the caller's current UI culture. - public static string GetResource(string resourceKey) + public static string GetResource(string resourceKey, CultureInfo culture = null) { - return ResourceManager.GetString(resourceKey); + return ResourceManager.GetString(resourceKey, culture); } } } diff --git a/src/Humanizer/TimeSpanHumanizeExtensions.cs b/src/Humanizer/TimeSpanHumanizeExtensions.cs index f51c2831c..660b3be2e 100644 --- a/src/Humanizer/TimeSpanHumanizeExtensions.cs +++ b/src/Humanizer/TimeSpanHumanizeExtensions.cs @@ -38,7 +38,7 @@ public static string Humanize(this TimeSpan timeSpan, int precision = 1) private static string GetTimePart(TimeSpan timespan) { - var formatter = Configurator.Formatter; + var formatter = Configurator.GetFormatter(); if (timespan.Days >= 7) return formatter.TimeSpanHumanize(TimeUnit.Week, timespan.Days/7); From adea66c3f26cec533dadd25b7f8132fa518b9566 Mon Sep 17 00:00:00 2001 From: Dmitry Gokun Date: Thu, 29 May 2014 15:54:18 +0300 Subject: [PATCH 02/16] Fixed formatting --- src/Humanizer.Tests/DateHumanize.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Humanizer.Tests/DateHumanize.cs b/src/Humanizer.Tests/DateHumanize.cs index 42d312c99..dda5ce615 100644 --- a/src/Humanizer.Tests/DateHumanize.cs +++ b/src/Humanizer.Tests/DateHumanize.cs @@ -35,8 +35,7 @@ private static void VerifyWithDateInjection(string expectedString, TimeSpan delt }); } - public static void Verify(string expectedString, int unit, TimeUnit timeUnit, Tense tense, - double? precision = null) + public static void Verify(string expectedString, int unit, TimeUnit timeUnit, Tense tense, double? precision = null) { if (precision.HasValue) Configurator.DateTimeHumanizeStrategy = new PrecisionDateTimeHumanizeStrategy(precision.Value); From 3dde23dbdf31a81bdbcd4e6e47130ee4fb235e19 Mon Sep 17 00:00:00 2001 From: Dmitry Gokun Date: Thu, 29 May 2014 19:51:31 +0300 Subject: [PATCH 03/16] Reverted changes to DateHumanize (testing with both implicit & explicit culture) --- src/Humanizer.Tests/DateHumanize.cs | 39 ++++++++--------------------- 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/src/Humanizer.Tests/DateHumanize.cs b/src/Humanizer.Tests/DateHumanize.cs index dda5ce615..e498a2904 100644 --- a/src/Humanizer.Tests/DateHumanize.cs +++ b/src/Humanizer.Tests/DateHumanize.cs @@ -1,6 +1,4 @@ using System; -using System.Globalization; -using System.Threading; using Humanizer.Configuration; using Humanizer.DateTimeHumanizeStrategy; using Humanizer.Localisation; @@ -10,29 +8,23 @@ namespace Humanizer.Tests { public class DateHumanize { - private static void VerifyWithCurrentDate(string expectedString, TimeSpan deltaFromNow) + static void VerifyWithCurrentDate(string expectedString, TimeSpan deltaFromNow) { - CheckWithExplicitAndImplicitCulture(culture => - { - var utcNow = DateTime.UtcNow; - var localNow = DateTime.Now; + var utcNow = DateTime.UtcNow; + var localNow = DateTime.Now; - // feels like the only way to avoid breaking tests because CPU ticks over is to inject the base date - Assert.Equal(expectedString, utcNow.Add(deltaFromNow).Humanize(true, utcNow, culture)); - Assert.Equal(expectedString, localNow.Add(deltaFromNow).Humanize(false, localNow, culture)); - }); + // feels like the only way to avoid breaking tests because CPU ticks over is to inject the base date + Assert.Equal(expectedString, utcNow.Add(deltaFromNow).Humanize(utcDate: true, dateToCompareAgainst: utcNow)); + Assert.Equal(expectedString, localNow.Add(deltaFromNow).Humanize(utcDate: false, dateToCompareAgainst: localNow)); } - private static void VerifyWithDateInjection(string expectedString, TimeSpan deltaFromNow) + static void VerifyWithDateInjection(string expectedString, TimeSpan deltaFromNow) { - CheckWithExplicitAndImplicitCulture(culture => - { - var utcNow = new DateTime(2013, 6, 20, 9, 58, 22, DateTimeKind.Utc); - var now = new DateTime(2013, 6, 20, 11, 58, 22, DateTimeKind.Local); + var utcNow = new DateTime(2013, 6, 20, 9, 58, 22, DateTimeKind.Utc); + var now = new DateTime(2013, 6, 20, 11, 58, 22, DateTimeKind.Local); - Assert.Equal(expectedString, utcNow.Add(deltaFromNow).Humanize(true, utcNow, culture)); - Assert.Equal(expectedString, now.Add(deltaFromNow).Humanize(false, now, culture)); - }); + Assert.Equal(expectedString, utcNow.Add(deltaFromNow).Humanize(utcDate: true, dateToCompareAgainst: utcNow)); + Assert.Equal(expectedString, now.Add(deltaFromNow).Humanize(false, now)); } public static void Verify(string expectedString, int unit, TimeUnit timeUnit, Tense tense, double? precision = null) @@ -76,14 +68,5 @@ public static void Verify(string expectedString, int unit, TimeUnit timeUnit, Te VerifyWithCurrentDate(expectedString, deltaFromNow); VerifyWithDateInjection(expectedString, deltaFromNow); } - - private static void CheckWithExplicitAndImplicitCulture(Action action) - { - action(null); - - CultureInfo culture = Thread.CurrentThread.CurrentUICulture; - using (new AmbientCulture(culture.TwoLetterISOLanguageName == "da" ? "tr" : "da")) - action(culture); - } } } \ No newline at end of file From abbc23ed12d6864750113d78abf69dd35c2e2a57 Mon Sep 17 00:00:00 2001 From: Dmitry Gokun Date: Thu, 29 May 2014 19:56:32 +0300 Subject: [PATCH 04/16] Added a test for getting culture-specific resource with explicitly specified culture --- src/Humanizer.Tests/Localisation/ResourcesTests.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Humanizer.Tests/Localisation/ResourcesTests.cs b/src/Humanizer.Tests/Localisation/ResourcesTests.cs index 6f8897705..54f061164 100644 --- a/src/Humanizer.Tests/Localisation/ResourcesTests.cs +++ b/src/Humanizer.Tests/Localisation/ResourcesTests.cs @@ -1,4 +1,5 @@ -using Humanizer.Localisation; +using System.Globalization; +using Humanizer.Localisation; using Xunit; namespace Humanizer.Tests.Localisation @@ -6,7 +7,7 @@ namespace Humanizer.Tests.Localisation public class ResourcesTests { [Fact] - public void CanGetCultureSpecificTranslations() + public void CanGetCultureSpecificTranslationsWithImplicitCulture() { using (new AmbientCulture("ro")) { @@ -14,5 +15,12 @@ public void CanGetCultureSpecificTranslations() Assert.Equal("acum {0} de ani", format); } } + + [Fact] + public void CanGetCultureSpecificTranslationsWithExplicitCulture() + { + var format = Resources.GetResource("DateHumanize_MultipleYearsAgo_Above20", new CultureInfo("ro")); + Assert.Equal("acum {0} de ani", format); + } } } From 0788a38553c0db86fc8ab12e21a5fd995554c016 Mon Sep 17 00:00:00 2001 From: Dmitry Gokun Date: Thu, 29 May 2014 19:58:06 +0300 Subject: [PATCH 05/16] Fixed method's comment --- src/Humanizer/Localisation/Resources.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Humanizer/Localisation/Resources.cs b/src/Humanizer/Localisation/Resources.cs index 6eafd3cfd..631f5b525 100644 --- a/src/Humanizer/Localisation/Resources.cs +++ b/src/Humanizer/Localisation/Resources.cs @@ -14,8 +14,8 @@ public static class Resources /// Returns the value of the specified string resource /// /// The name of the resource to retrieve. - /// The culture of the resource to retrieve. - /// The value of the resource localized for the caller's current UI culture. + /// The culture of the resource to retrieve. If not specified, current thread's UI culture is used. + /// The value of the resource localized for the specified culture. public static string GetResource(string resourceKey, CultureInfo culture = null) { return ResourceManager.GetString(resourceKey, culture); From ed4121cf5e617957769876b596a538ebb07fc217 Mon Sep 17 00:00:00 2001 From: Dmitry Gokun Date: Fri, 30 May 2014 14:38:34 +0300 Subject: [PATCH 06/16] Only using optional "Culture" parameter for public methods --- src/Humanizer/Configuration/Configurator.cs | 4 ++-- src/Humanizer/Configuration/LocaliserRegistry.cs | 4 ++-- src/Humanizer/TimeSpanHumanizeExtensions.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Humanizer/Configuration/Configurator.cs b/src/Humanizer/Configuration/Configurator.cs index 581fb7f01..afadff4f5 100644 --- a/src/Humanizer/Configuration/Configurator.cs +++ b/src/Humanizer/Configuration/Configurator.cs @@ -63,8 +63,8 @@ internal static ICollectionFormatter CollectionFormatter /// /// The formatter to be used /// - /// The culture to retrieve formatter for. If not specified, current thread's UI culture is used. - internal static IFormatter GetFormatter(CultureInfo culture = null) + /// The culture to retrieve formatter for. Null means that current thread's UI culture should be used. + internal static IFormatter GetFormatter(CultureInfo culture) { return Formatters.ResolveForCulture(culture); } diff --git a/src/Humanizer/Configuration/LocaliserRegistry.cs b/src/Humanizer/Configuration/LocaliserRegistry.cs index a0dab6ec0..abf4f407e 100644 --- a/src/Humanizer/Configuration/LocaliserRegistry.cs +++ b/src/Humanizer/Configuration/LocaliserRegistry.cs @@ -27,14 +27,14 @@ public LocaliserRegistry(TLocaliser defaultLocaliser) /// public TLocaliser ResolveForUiCulture() { - return ResolveForCulture(); + return ResolveForCulture(null); } /// /// Gets the localiser for the specified culture /// /// The culture to retrieve localiser for. If not specified, current thread's UI culture is used. - public TLocaliser ResolveForCulture(CultureInfo culture = null) + public TLocaliser ResolveForCulture(CultureInfo culture) { culture = culture ?? CultureInfo.CurrentUICulture; diff --git a/src/Humanizer/TimeSpanHumanizeExtensions.cs b/src/Humanizer/TimeSpanHumanizeExtensions.cs index 660b3be2e..e84f64509 100644 --- a/src/Humanizer/TimeSpanHumanizeExtensions.cs +++ b/src/Humanizer/TimeSpanHumanizeExtensions.cs @@ -38,7 +38,7 @@ public static string Humanize(this TimeSpan timeSpan, int precision = 1) private static string GetTimePart(TimeSpan timespan) { - var formatter = Configurator.GetFormatter(); + var formatter = Configurator.GetFormatter(null); if (timespan.Days >= 7) return formatter.TimeSpanHumanize(TimeUnit.Week, timespan.Days/7); From 1f8ad39f68d4600d0c6241eb5b56d65a0b2441f3 Mon Sep 17 00:00:00 2001 From: Dmitry Gokun Date: Fri, 30 May 2014 14:56:13 +0300 Subject: [PATCH 07/16] + unit tests for DateTime.Humanize with explicit culure --- src/Humanizer.Tests/DateHumanize.cs | 19 ++++++++++--------- .../DateHumanizeDefaultStrategyTests.cs | 12 +++++++++++- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/Humanizer.Tests/DateHumanize.cs b/src/Humanizer.Tests/DateHumanize.cs index e498a2904..51be3f6fa 100644 --- a/src/Humanizer.Tests/DateHumanize.cs +++ b/src/Humanizer.Tests/DateHumanize.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using Humanizer.Configuration; using Humanizer.DateTimeHumanizeStrategy; using Humanizer.Localisation; @@ -8,26 +9,26 @@ namespace Humanizer.Tests { public class DateHumanize { - static void VerifyWithCurrentDate(string expectedString, TimeSpan deltaFromNow) + static void VerifyWithCurrentDate(string expectedString, TimeSpan deltaFromNow, CultureInfo culture) { var utcNow = DateTime.UtcNow; var localNow = DateTime.Now; // feels like the only way to avoid breaking tests because CPU ticks over is to inject the base date - Assert.Equal(expectedString, utcNow.Add(deltaFromNow).Humanize(utcDate: true, dateToCompareAgainst: utcNow)); - Assert.Equal(expectedString, localNow.Add(deltaFromNow).Humanize(utcDate: false, dateToCompareAgainst: localNow)); + Assert.Equal(expectedString, utcNow.Add(deltaFromNow).Humanize(utcDate: true, dateToCompareAgainst: utcNow, culture: culture)); + Assert.Equal(expectedString, localNow.Add(deltaFromNow).Humanize(utcDate: false, dateToCompareAgainst: localNow, culture: culture)); } - static void VerifyWithDateInjection(string expectedString, TimeSpan deltaFromNow) + static void VerifyWithDateInjection(string expectedString, TimeSpan deltaFromNow, CultureInfo culture) { var utcNow = new DateTime(2013, 6, 20, 9, 58, 22, DateTimeKind.Utc); var now = new DateTime(2013, 6, 20, 11, 58, 22, DateTimeKind.Local); - Assert.Equal(expectedString, utcNow.Add(deltaFromNow).Humanize(utcDate: true, dateToCompareAgainst: utcNow)); - Assert.Equal(expectedString, now.Add(deltaFromNow).Humanize(false, now)); + Assert.Equal(expectedString, utcNow.Add(deltaFromNow).Humanize(utcDate: true, dateToCompareAgainst: utcNow, culture: culture)); + Assert.Equal(expectedString, now.Add(deltaFromNow).Humanize(false, now, culture: culture)); } - public static void Verify(string expectedString, int unit, TimeUnit timeUnit, Tense tense, double? precision = null) + public static void Verify(string expectedString, int unit, TimeUnit timeUnit, Tense tense, double? precision = null, CultureInfo culture = null) { if (precision.HasValue) Configurator.DateTimeHumanizeStrategy = new PrecisionDateTimeHumanizeStrategy(precision.Value); @@ -65,8 +66,8 @@ public static void Verify(string expectedString, int unit, TimeUnit timeUnit, Te break; } - VerifyWithCurrentDate(expectedString, deltaFromNow); - VerifyWithDateInjection(expectedString, deltaFromNow); + VerifyWithCurrentDate(expectedString, deltaFromNow, culture); + VerifyWithDateInjection(expectedString, deltaFromNow, culture); } } } \ No newline at end of file diff --git a/src/Humanizer.Tests/DateHumanizeDefaultStrategyTests.cs b/src/Humanizer.Tests/DateHumanizeDefaultStrategyTests.cs index 432f5ef97..230a02b3b 100644 --- a/src/Humanizer.Tests/DateHumanizeDefaultStrategyTests.cs +++ b/src/Humanizer.Tests/DateHumanizeDefaultStrategyTests.cs @@ -1,4 +1,5 @@ -using Humanizer.Localisation; +using System.Globalization; +using Humanizer.Localisation; using Xunit; using Xunit.Extensions; @@ -138,5 +139,14 @@ public void Now() { DateHumanize.Verify("now", 0, TimeUnit.Year, Tense.Future); } + + [Theory] + [InlineData(1, TimeUnit.Year, Tense.Future, "en-US", "one year from now")] + [InlineData(40, TimeUnit.Second, Tense.Past, "ru-RU", "40 секунд назад")] + [InlineData(2, TimeUnit.Day, Tense.Past, "sv-SE", "för 2 dagar sedan")] + public void ExplicitCultureIsUsed(int unit, TimeUnit timeUnit, Tense tense, string culture, string expected) + { + DateHumanize.Verify(expected, unit, timeUnit, tense, culture: new CultureInfo(culture)); + } } } From 3bb7fec128eccb7691dd2660e004b93d0447217c Mon Sep 17 00:00:00 2001 From: Dmitry Gokun Date: Fri, 30 May 2014 15:00:52 +0300 Subject: [PATCH 08/16] Updated readme to reflect new "culture" parameter for DateTime.Humanize --- readme.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index d294af357..55bdfaae2 100644 --- a/readme.md +++ b/readme.md @@ -233,10 +233,12 @@ DateTime.UtcNow.AddHours(30).Humanize() => "tomorrow" DateTime.UtcNow.AddHours(2).Humanize() => "2 hours from now" ``` -Humanizer supports local as well as UTC dates. You could also provide the date you want the input date to be compared against. If null, it will use the current date as comparison base. Here is the API signature: +Humanizer supports local as well as UTC dates. You could also provide the date you want the input date to be compared against. If null, it will use the current date as comparison base. +Also, culture to use can be specified explicitly. If it is not, current thread's current UI culture is used. +Here is the API signature: ```C# -public static string Humanize(this DateTime input, bool utcDate = true, DateTime? dateToCompareAgainst = null) +public static string Humanize(this DateTime input, bool utcDate = true, DateTime? dateToCompareAgainst = null, CultureInfo culture = null) ``` Many localizations are available for this method. Here is a few examples: From 464e7b428c0da47a08dba0b481f9a1567bdb5689 Mon Sep 17 00:00:00 2001 From: Dmitry Gokun Date: Fri, 30 May 2014 15:02:45 +0300 Subject: [PATCH 09/16] Updated release_notes to reflect new "culture" parameter for DateTime.Humanize --- release_notes.md | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/release_notes.md b/release_notes.md index 2ca9a2170..c2736a496 100644 --- a/release_notes.md +++ b/release_notes.md @@ -4,7 +4,8 @@ - [#281](https://github.com/Mehdik/Humanizer/pull/281): Changed the logic for handling hyphenation and large numbers ending in twelve for English ordinal words; e.g. before "twenty first" now "twenty-first" - [#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 + [Commits](https://github.com/MehdiK/Humanizer/compare/v1.26.1...master) ###v1.26.1 - 2014-05-20 @@ -26,7 +27,7 @@ [Commits](https://github.com/MehdiK/Humanizer/compare/v1.24.1...v1.25.4) ###v1.24.1 - 2014-04-21 - - [#232](https://github.com/Mehdik/Humanizer/pull/232): Adding code & tests to handle Arabic numbers to ordinal + - [#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 @@ -49,8 +50,8 @@ - [#199](https://github.com/MehdiK/Humanizer/pull/199): Added Hebrew Number to words (both genders) - [#202](https://github.com/MehdiK/Humanizer/pull/202): Fixed typo sekunttia -> sekuntia (Finnish translation) - [#203](https://github.com/MehdiK/Humanizer/pull/203): Added feminine gender for french ordinal words - - [#208](https://github.com/MehdiK/Humanizer/pull/208): Added Hebrew implementation of future DateTime - + - [#208](https://github.com/MehdiK/Humanizer/pull/208): Added Hebrew implementation of future DateTime + [Commits](https://github.com/MehdiK/Humanizer/compare/v1.21.1...v1.22.1) ###v1.21.1 - 2014-04-12 @@ -60,7 +61,7 @@ - [#190](https://github.com/MehdiK/Humanizer/pull/190): Added French translation for ToWords and ToOrdinalWords - [#179](https://github.com/MehdiK/Humanizer/pull/179): Added Hungarian localisation - [#181](https://github.com/Mehdik/Humanizer/pull/181): Added Bulgarian localization, date and timespan tests - - [#141](https://github.com/MehdiK/Humanizer/pull/141): Added Indonesian localization + - [#141](https://github.com/MehdiK/Humanizer/pull/141): Added Indonesian localization - [#148](https://github.com/Mehdik/Humanizer/pull/148): Added Hebrew localization for date and timespan [Commits](https://github.com/MehdiK/Humanizer/compare/v1.20.15...v1.21.1) @@ -82,12 +83,12 @@ [Commits](https://github.com/MehdiK/Humanizer/compare/v1.19.1...v1.20.2) ###v1.19.1 - 2014-04-10 - - [#149](https://github.com/MehdiK/Humanizer/pull/149): Improved & refactored number to words localisation + - [#149](https://github.com/MehdiK/Humanizer/pull/149): Improved & refactored number to words localisation - [#143](https://github.com/MehdiK/Humanizer/pull/143): Added Russian translation for future DateTime, TimeSpan and Now - [#144](https://github.com/MehdiK/Humanizer/pull/144): Added Danish localization (strings, tests) - [#146](https://github.com/MehdiK/Humanizer/pull/146): Added Spanish translation for future DateTime, TimeSpan and Now - - + + [Commits](https://github.com/MehdiK/Humanizer/compare/v1.18.1...v1.19.1) ###v1.18.1 - 2014-04-09 @@ -120,7 +121,7 @@ ###v1.14.1 - 2014-03-26 - [#108](https://github.com/MehdiK/Humanizer/pull/108): Added support for custom description attributes - - [#106](https://github.com/MehdiK/Humanizer/pull/106): + - [#106](https://github.com/MehdiK/Humanizer/pull/106): - Refactored IFormatter and DefaultFormatter - Refactored `DateTime.Humanize` and `TimeSpan.Humanize` - Changed `ResourceKeys` to use a dynamic key generation @@ -179,7 +180,7 @@ If you were catching `CannotMapToTargetException` on a `DehumanizeTo` call, that ####Potential breaking change The return type of `DehumanizeTo` was changed from `Enum` to `TTargetEnum` to make the API a lot easier to work with. That also potentially means that your calls to the old method may be broken. -Depending on how you were using the method you might have to either drop the now redundant cast to `TTargetEnum` in your code, or +Depending on how you were using the method you might have to either drop the now redundant cast to `TTargetEnum` in your code, or fix it based on your requirements. [Commits](https://github.com/MehdiK/Humanizer/compare/v1.5.1...v1.6.1) @@ -205,7 +206,7 @@ fix it based on your requirements. ###v1.1.0 - 2014-01-01 - [#37](https://github.com/MehdiK/Humanizer/pull/37): added `ToQuantity` method - - [#43](https://github.com/MehdiK/Humanizer/pull/43): + - [#43](https://github.com/MehdiK/Humanizer/pull/43): - added `Plurality` enum - can call `Singularize` on singular and `Pluralize` on plural words - `ToQuantity` can be called on words with unknown plurality @@ -214,7 +215,7 @@ fix it based on your requirements. ###v1.0.29 - 2013-12-25 - [#26](https://github.com/MehdiK/Humanizer/pull/26): added Norwegian (nb-NO) localization for `DateTime.Humanize()` - - [#33](https://github.com/MehdiK/Humanizer/pull/33): + - [#33](https://github.com/MehdiK/Humanizer/pull/33): - changed to Portable Class Library with support for .Net 4+, SilverLight 5, Windows Phone 8 and Win Store applications - symbols nuget package is published so you can step through Humanizer code while debugging your code From 70f8f72ea5360a8738463f70ebe9e49a5c8e79e8 Mon Sep 17 00:00:00 2001 From: Dmitry Gokun Date: Sun, 1 Jun 2014 07:46:40 +0300 Subject: [PATCH 10/16] Added optional Culture parameter to TimeSpan.Humanize --- readme.md | 8 ++++++ release_notes.md | 2 +- ...provalTest.approve_public_api.approved.txt | 10 +++---- src/Humanizer.Tests/TimeSpanHumanizeTests.cs | 11 ++++++++ .../Formatters/DefaultFormatter.cs | 14 +++++----- .../Localisation/Formatters/IFormatter.cs | 6 +++-- src/Humanizer/TimeSpanHumanizeExtensions.cs | 26 ++++++++++--------- 7 files changed, 51 insertions(+), 26 deletions(-) diff --git a/readme.md b/readme.md index 55bdfaae2..267a6195d 100644 --- a/readme.md +++ b/readme.md @@ -315,6 +315,14 @@ TimeSpan.FromMilliseconds(2).Humanize() => "2 milisekundy" TimeSpan.FromMilliseconds(5).Humanize() => "5 milisekúnd" ``` +Culture to use can be specified explicitly. If it is not, current thread's current UI culture is used. Example: + +```C# + +TimeSpan.FromDays(1).Humanize(culture: "ru-RU") => "один день" + +``` + ###Humanize Collections You can call `Humanize` on any `IEnumerable` to get a nicely formatted string representing the objects in the collection. By default `ToString()` will be called on each item to get its representation but a formatting function may be passed to `Humanize` instead. Additionally, a default separator is provided("and" in English), but a different separator may be passed into `Humanize`. diff --git a/release_notes.md b/release_notes.md index c2736a496..9a7730feb 100644 --- a/release_notes.md +++ b/release_notes.md @@ -4,7 +4,7 @@ - [#281](https://github.com/Mehdik/Humanizer/pull/281): Changed the logic for handling hyphenation and large numbers ending in twelve for English ordinal words; e.g. before "twenty first" now "twenty-first" - [#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 + - [#286](https://github.com/MehdiK/Humanizer/pull/286): Added optional Culture parameter to DateTime.Humanize & TimeSpan.Humanize [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 d7b6055ab..6d92e8d8c 100644 --- a/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt +++ b/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt @@ -218,16 +218,16 @@ public class DefaultFormatter public DefaultFormatter() { } public string DateHumanize(Humanizer.Localisation.TimeUnit timeUnit, Humanizer.Localisation.Tense timeUnitTense, int unit, System.Globalization.CultureInfo culture) { } public string DateHumanize_Now(System.Globalization.CultureInfo culture) { } - public string TimeSpanHumanize(Humanizer.Localisation.TimeUnit timeUnit, int unit) { } - public string TimeSpanHumanize_Zero() { } + public string TimeSpanHumanize(Humanizer.Localisation.TimeUnit timeUnit, int unit, System.Globalization.CultureInfo culture) { } + public string TimeSpanHumanize_Zero(System.Globalization.CultureInfo culture) { } } public interface IFormatter { string DateHumanize(Humanizer.Localisation.TimeUnit timeUnit, Humanizer.Localisation.Tense timeUnitTense, int unit, System.Globalization.CultureInfo culture); string DateHumanize_Now(System.Globalization.CultureInfo culture); - string TimeSpanHumanize(Humanizer.Localisation.TimeUnit timeUnit, int unit); - string TimeSpanHumanize_Zero(); + string TimeSpanHumanize(Humanizer.Localisation.TimeUnit timeUnit, int unit, System.Globalization.CultureInfo culture); + string TimeSpanHumanize_Zero(System.Globalization.CultureInfo culture); } public interface INumberToWordsConverter @@ -367,7 +367,7 @@ public class StringHumanizeExtensions public class TimeSpanHumanizeExtensions { - public string Humanize(System.TimeSpan timeSpan, int precision) { } + public string Humanize(System.TimeSpan timeSpan, int precision, System.Globalization.CultureInfo culture) { } } public class To diff --git a/src/Humanizer.Tests/TimeSpanHumanizeTests.cs b/src/Humanizer.Tests/TimeSpanHumanizeTests.cs index e386c4fca..118aa2328 100644 --- a/src/Humanizer.Tests/TimeSpanHumanizeTests.cs +++ b/src/Humanizer.Tests/TimeSpanHumanizeTests.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using Xunit; using Xunit.Extensions; @@ -110,5 +111,15 @@ public void NoTime() var actual = noTime.Humanize(); Assert.Equal("no time", actual); } + + [Theory] + [InlineData(1, "en-US", "1 millisecond")] + [InlineData(6 * 24 * 60 * 60 * 1000, "ru-RU", "6 дней")] + [InlineData(11 * 60 * 60 * 1000, "ar", "11 ساعة")] + public void ExplicitCultureIsUsed(int ms, string culture, string expected) + { + var actual = TimeSpan.FromMilliseconds(ms).Humanize(culture: new CultureInfo(culture)); + Assert.Equal(expected, actual); + } } } diff --git a/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs b/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs index 66ec76a77..8ebc41d8d 100644 --- a/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs +++ b/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs @@ -33,10 +33,11 @@ public virtual string DateHumanize(TimeUnit timeUnit, Tense timeUnitTense, int u /// /// 0 seconds /// + /// /// Returns 0 seconds as the string representation of Zero TimeSpan - public virtual string TimeSpanHumanize_Zero() + public virtual string TimeSpanHumanize_Zero(CultureInfo culture) { - return GetResourceForTimeSpan(TimeUnit.Millisecond, 0); + return GetResourceForTimeSpan(TimeUnit.Millisecond, 0, culture); } /// @@ -44,10 +45,11 @@ public virtual string TimeSpanHumanize_Zero() /// /// /// + /// /// - public virtual string TimeSpanHumanize(TimeUnit timeUnit, int unit) + public virtual string TimeSpanHumanize(TimeUnit timeUnit, int unit, CultureInfo culture) { - return GetResourceForTimeSpan(timeUnit, unit); + return GetResourceForTimeSpan(timeUnit, unit, culture); } private string GetResourceForDate(TimeUnit unit, Tense timeUnitTense, int count, CultureInfo culture) @@ -56,10 +58,10 @@ private string GetResourceForDate(TimeUnit unit, Tense timeUnitTense, int count, return count == 1 ? Format(resourceKey, culture) : Format(resourceKey, count, culture); } - private string GetResourceForTimeSpan(TimeUnit unit, int count) + private string GetResourceForTimeSpan(TimeUnit unit, int count, CultureInfo culture) { string resourceKey = ResourceKeys.TimeSpanHumanize.GetResourceKey(unit, count); - return count == 1 ? Format(resourceKey, null) : Format(resourceKey, count, null); + return count == 1 ? Format(resourceKey, culture) : Format(resourceKey, count, culture); } /// diff --git a/src/Humanizer/Localisation/Formatters/IFormatter.cs b/src/Humanizer/Localisation/Formatters/IFormatter.cs index 462efef21..4f0a6bb27 100644 --- a/src/Humanizer/Localisation/Formatters/IFormatter.cs +++ b/src/Humanizer/Localisation/Formatters/IFormatter.cs @@ -29,15 +29,17 @@ public interface IFormatter /// /// 0 seconds /// + /// /// Returns 0 seconds as the string representation of Zero TimeSpan - string TimeSpanHumanize_Zero(); + string TimeSpanHumanize_Zero(CultureInfo culture); /// /// Returns the string representation of the provided TimeSpan /// /// /// + /// /// - string TimeSpanHumanize(TimeUnit timeUnit, int unit); + string TimeSpanHumanize(TimeUnit timeUnit, int unit, CultureInfo culture); } } diff --git a/src/Humanizer/TimeSpanHumanizeExtensions.cs b/src/Humanizer/TimeSpanHumanizeExtensions.cs index e84f64509..f4ef4d60a 100644 --- a/src/Humanizer/TimeSpanHumanizeExtensions.cs +++ b/src/Humanizer/TimeSpanHumanizeExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Text; using Humanizer.Configuration; using Humanizer.Localisation; @@ -15,13 +16,14 @@ public static class TimeSpanHumanizeExtensions /// /// /// The maximum number of time units to return. Defaulted is 1 which means the largest unit is returned + /// Culture to use. If null, current thread's UI culture is used. /// - public static string Humanize(this TimeSpan timeSpan, int precision = 1) + public static string Humanize(this TimeSpan timeSpan, int precision = 1, CultureInfo culture = null) { var result = new StringBuilder(); for (int i = 0; i < precision; i++) { - var timePart = GetTimePart(timeSpan); + var timePart = GetTimePart(timeSpan, culture); if (result.Length > 0) result.Append(", "); @@ -36,28 +38,28 @@ public static string Humanize(this TimeSpan timeSpan, int precision = 1) return result.ToString(); } - private static string GetTimePart(TimeSpan timespan) + private static string GetTimePart(TimeSpan timespan, CultureInfo culture) { - var formatter = Configurator.GetFormatter(null); + var formatter = Configurator.GetFormatter(culture); if (timespan.Days >= 7) - return formatter.TimeSpanHumanize(TimeUnit.Week, timespan.Days/7); + return formatter.TimeSpanHumanize(TimeUnit.Week, timespan.Days/7, culture); - if(timespan.Days >= 1) - return formatter.TimeSpanHumanize(TimeUnit.Day, timespan.Days); + if (timespan.Days >= 1) + return formatter.TimeSpanHumanize(TimeUnit.Day, timespan.Days, culture); if (timespan.Hours >= 1) - return formatter.TimeSpanHumanize(TimeUnit.Hour, timespan.Hours); + return formatter.TimeSpanHumanize(TimeUnit.Hour, timespan.Hours, culture); if (timespan.Minutes >= 1) - return formatter.TimeSpanHumanize(TimeUnit.Minute, timespan.Minutes); + return formatter.TimeSpanHumanize(TimeUnit.Minute, timespan.Minutes, culture); if (timespan.Seconds >= 1) - return formatter.TimeSpanHumanize(TimeUnit.Second, timespan.Seconds); + return formatter.TimeSpanHumanize(TimeUnit.Second, timespan.Seconds, culture); if (timespan.Milliseconds >= 1) - return formatter.TimeSpanHumanize(TimeUnit.Millisecond, timespan.Milliseconds); + return formatter.TimeSpanHumanize(TimeUnit.Millisecond, timespan.Milliseconds, culture); - return formatter.TimeSpanHumanize_Zero(); + return formatter.TimeSpanHumanize_Zero(culture); } static TimeSpan TakeOutTheLargestUnit(TimeSpan timeSpan) From c8bcd8782a42ce9717fe14510761df3fbea43dc3 Mon Sep 17 00:00:00 2001 From: Dmitry Gokun Date: Tue, 3 Jun 2014 22:32:00 +0300 Subject: [PATCH 11/16] 1) Made Date/Time formatters to own "culture"; 2) Explicitly registering DefaultFormatter's for the supported languages in FormatterRegistry's constructor --- ...provalTest.approve_public_api.approved.txt | 18 +++---- .../DateHumanizeDefaultStrategyTests.cs | 2 +- .../Configuration/FormatterRegistry.cs | 50 ++++++++++++++++--- .../DefaultDateTimeHumanizeStrategy.cs | 24 ++++----- .../PrecisionDateTimeHumanizeStrategy.cs | 14 +++--- .../Formatters/ArabicFormatter.cs | 5 ++ .../Formatters/CzechSlovakPolishFormatter.cs | 5 ++ .../Formatters/DefaultFormatter.cs | 49 ++++++++++-------- .../Formatters/HebrewFormatter.cs | 5 ++ .../Localisation/Formatters/IFormatter.cs | 16 ++---- .../Formatters/RomanianFormatter.cs | 5 ++ .../Formatters/RussianFormatter.cs | 5 ++ .../Formatters/SerbianFormatter.cs | 5 ++ .../Formatters/SlovenianFormatter.cs | 5 ++ src/Humanizer/TimeSpanHumanizeExtensions.cs | 14 +++--- 15 files changed, 146 insertions(+), 76 deletions(-) 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 6d92e8d8c..a37baef21 100644 --- a/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt +++ b/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt @@ -215,19 +215,19 @@ public interface ICollectionFormatter public class DefaultFormatter { - public DefaultFormatter() { } - public string DateHumanize(Humanizer.Localisation.TimeUnit timeUnit, Humanizer.Localisation.Tense timeUnitTense, int unit, System.Globalization.CultureInfo culture) { } - public string DateHumanize_Now(System.Globalization.CultureInfo culture) { } - public string TimeSpanHumanize(Humanizer.Localisation.TimeUnit timeUnit, int unit, System.Globalization.CultureInfo culture) { } - public string TimeSpanHumanize_Zero(System.Globalization.CultureInfo culture) { } + public DefaultFormatter(string localeCode) { } + public string DateHumanize(Humanizer.Localisation.TimeUnit timeUnit, Humanizer.Localisation.Tense timeUnitTense, int unit) { } + public string DateHumanize_Now() { } + public string TimeSpanHumanize(Humanizer.Localisation.TimeUnit timeUnit, int unit) { } + public string TimeSpanHumanize_Zero() { } } public interface IFormatter { - string DateHumanize(Humanizer.Localisation.TimeUnit timeUnit, Humanizer.Localisation.Tense timeUnitTense, int unit, System.Globalization.CultureInfo culture); - string DateHumanize_Now(System.Globalization.CultureInfo culture); - string TimeSpanHumanize(Humanizer.Localisation.TimeUnit timeUnit, int unit, System.Globalization.CultureInfo culture); - string TimeSpanHumanize_Zero(System.Globalization.CultureInfo culture); + string DateHumanize(Humanizer.Localisation.TimeUnit timeUnit, Humanizer.Localisation.Tense timeUnitTense, int unit); + string DateHumanize_Now(); + string TimeSpanHumanize(Humanizer.Localisation.TimeUnit timeUnit, int unit); + string TimeSpanHumanize_Zero(); } public interface INumberToWordsConverter diff --git a/src/Humanizer.Tests/DateHumanizeDefaultStrategyTests.cs b/src/Humanizer.Tests/DateHumanizeDefaultStrategyTests.cs index 230a02b3b..60d994329 100644 --- a/src/Humanizer.Tests/DateHumanizeDefaultStrategyTests.cs +++ b/src/Humanizer.Tests/DateHumanizeDefaultStrategyTests.cs @@ -142,7 +142,7 @@ public void Now() [Theory] [InlineData(1, TimeUnit.Year, Tense.Future, "en-US", "one year from now")] - [InlineData(40, TimeUnit.Second, Tense.Past, "ru-RU", "40 секунд назад")] + [InlineData(40, TimeUnit.Second, Tense.Past, "ru-RU", "40 секунд назад")] [InlineData(2, TimeUnit.Day, Tense.Past, "sv-SE", "för 2 dagar sedan")] public void ExplicitCultureIsUsed(int unit, TimeUnit timeUnit, Tense tense, string culture, string expected) { diff --git a/src/Humanizer/Configuration/FormatterRegistry.cs b/src/Humanizer/Configuration/FormatterRegistry.cs index d9c2f6734..927d63a09 100644 --- a/src/Humanizer/Configuration/FormatterRegistry.cs +++ b/src/Humanizer/Configuration/FormatterRegistry.cs @@ -4,17 +4,53 @@ namespace Humanizer.Configuration { internal class FormatterRegistry : LocaliserRegistry { - public FormatterRegistry() : base(new DefaultFormatter()) + public FormatterRegistry() : base(new DefaultFormatter("en-US")) { - Register("ro"); - Register("ru"); + RegisterDefaultFormatter("af"); Register("ar"); + RegisterDefaultFormatter("bg"); + RegisterCzechSlovakPolishFormatter("cs"); + RegisterDefaultFormatter("da"); + RegisterDefaultFormatter("de"); + RegisterDefaultFormatter("el"); + RegisterDefaultFormatter("es"); + RegisterDefaultFormatter("fa"); + RegisterDefaultFormatter("fi-FI"); + RegisterDefaultFormatter("fr"); + RegisterDefaultFormatter("fr-BE"); Register("he"); - Register("sk"); - Register("cs"); - Register("pl"); - Register("sr"); + RegisterDefaultFormatter("hu"); + RegisterDefaultFormatter("id"); + RegisterDefaultFormatter("ja"); + RegisterDefaultFormatter("nb"); + RegisterDefaultFormatter("nb-NO"); + RegisterDefaultFormatter("nl"); + RegisterCzechSlovakPolishFormatter("pl"); + RegisterDefaultFormatter("pt-BR"); + Register("ro"); + Register("ru"); + RegisterCzechSlovakPolishFormatter("sk"); Register("sl"); + RegisterSerbianFormatter("sr"); + RegisterSerbianFormatter("sr-Latn"); + RegisterDefaultFormatter("sv"); + RegisterDefaultFormatter("tr"); + RegisterDefaultFormatter("vi"); + } + + private void RegisterDefaultFormatter(string localeCode) + { + Register(() => new DefaultFormatter(localeCode), localeCode); + } + + private void RegisterCzechSlovakPolishFormatter(string localeCode) + { + Register(() => new CzechSlovakPolishFormatter(localeCode), localeCode); + } + + private void RegisterSerbianFormatter(string localeCode) + { + Register(() => new SerbianFormatter(localeCode), localeCode); } } } \ No newline at end of file diff --git a/src/Humanizer/DateTimeHumanizeStrategy/DefaultDateTimeHumanizeStrategy.cs b/src/Humanizer/DateTimeHumanizeStrategy/DefaultDateTimeHumanizeStrategy.cs index 4ee8195f9..42de15b8b 100644 --- a/src/Humanizer/DateTimeHumanizeStrategy/DefaultDateTimeHumanizeStrategy.cs +++ b/src/Humanizer/DateTimeHumanizeStrategy/DefaultDateTimeHumanizeStrategy.cs @@ -26,46 +26,46 @@ public string Humanize(DateTime input, DateTime comparisonBase, CultureInfo cult var formatter = Configurator.GetFormatter(culture); if (ts.TotalMilliseconds < 500) - return formatter.DateHumanize(TimeUnit.Millisecond, tense, 0, culture); + return formatter.DateHumanize(TimeUnit.Millisecond, tense, 0); if (ts.TotalSeconds < 60) - return formatter.DateHumanize(TimeUnit.Second, tense, ts.Seconds, culture); + return formatter.DateHumanize(TimeUnit.Second, tense, ts.Seconds); if (ts.TotalSeconds < 120) - return formatter.DateHumanize(TimeUnit.Minute, tense, 1, culture); + return formatter.DateHumanize(TimeUnit.Minute, tense, 1); if (ts.TotalMinutes < 60) - return formatter.DateHumanize(TimeUnit.Minute, tense, ts.Minutes, culture); + return formatter.DateHumanize(TimeUnit.Minute, tense, ts.Minutes); if (ts.TotalMinutes < 90) - return formatter.DateHumanize(TimeUnit.Hour, tense, 1, culture); + return formatter.DateHumanize(TimeUnit.Hour, tense, 1); if (ts.TotalHours < 24) - return formatter.DateHumanize(TimeUnit.Hour, tense, ts.Hours, culture); + return formatter.DateHumanize(TimeUnit.Hour, tense, ts.Hours); if (ts.TotalHours < 48) - return formatter.DateHumanize(TimeUnit.Day, tense, 1, culture); + return formatter.DateHumanize(TimeUnit.Day, tense, 1); if (ts.TotalDays < 28) - return formatter.DateHumanize(TimeUnit.Day, tense, ts.Days, culture); + return formatter.DateHumanize(TimeUnit.Day, tense, ts.Days); if (ts.TotalDays >= 28 && ts.TotalDays < 30) { if (comparisonBase.Date.AddMonths(tense == Tense.Future ? 1 : -1) == input.Date) - return formatter.DateHumanize(TimeUnit.Month, tense, 1, culture); - return formatter.DateHumanize(TimeUnit.Day, tense, ts.Days, culture); + return formatter.DateHumanize(TimeUnit.Month, tense, 1); + return formatter.DateHumanize(TimeUnit.Day, tense, ts.Days); } if (ts.TotalDays < 345) { int months = Convert.ToInt32(Math.Floor(ts.TotalDays / 29.5)); - return formatter.DateHumanize(TimeUnit.Month, tense, months, culture); + return formatter.DateHumanize(TimeUnit.Month, tense, months); } int years = Convert.ToInt32(Math.Floor(ts.TotalDays / 365)); if (years == 0) years = 1; - return formatter.DateHumanize(TimeUnit.Year, tense, years, culture); + return formatter.DateHumanize(TimeUnit.Year, tense, years); } } } \ No newline at end of file diff --git a/src/Humanizer/DateTimeHumanizeStrategy/PrecisionDateTimeHumanizeStrategy.cs b/src/Humanizer/DateTimeHumanizeStrategy/PrecisionDateTimeHumanizeStrategy.cs index cec212dd3..64e9ba567 100644 --- a/src/Humanizer/DateTimeHumanizeStrategy/PrecisionDateTimeHumanizeStrategy.cs +++ b/src/Humanizer/DateTimeHumanizeStrategy/PrecisionDateTimeHumanizeStrategy.cs @@ -62,13 +62,13 @@ public string Humanize(DateTime input, DateTime comparisonBase, CultureInfo cult // start computing result from larger units to smaller ones var formatter = Configurator.GetFormatter(culture); - if (years > 0) return formatter.DateHumanize(TimeUnit.Year, tense, years, culture); - if (months > 0) return formatter.DateHumanize(TimeUnit.Month, tense, months, culture); - if (days > 0) return formatter.DateHumanize(TimeUnit.Day, tense, days, culture); - if (hours > 0) return formatter.DateHumanize(TimeUnit.Hour, tense, hours, culture); - if (minutes > 0) return formatter.DateHumanize(TimeUnit.Minute, tense, minutes, culture); - if (seconds > 0) return formatter.DateHumanize(TimeUnit.Second, tense, seconds, culture); - return formatter.DateHumanize(TimeUnit.Millisecond, tense, 0, culture); + if (years > 0) return formatter.DateHumanize(TimeUnit.Year, tense, years); + if (months > 0) return formatter.DateHumanize(TimeUnit.Month, tense, months); + if (days > 0) return formatter.DateHumanize(TimeUnit.Day, tense, days); + if (hours > 0) return formatter.DateHumanize(TimeUnit.Hour, tense, hours); + if (minutes > 0) return formatter.DateHumanize(TimeUnit.Minute, tense, minutes); + if (seconds > 0) return formatter.DateHumanize(TimeUnit.Second, tense, seconds); + return formatter.DateHumanize(TimeUnit.Millisecond, tense, 0); } } } \ No newline at end of file diff --git a/src/Humanizer/Localisation/Formatters/ArabicFormatter.cs b/src/Humanizer/Localisation/Formatters/ArabicFormatter.cs index eb71f4389..fb210e91d 100644 --- a/src/Humanizer/Localisation/Formatters/ArabicFormatter.cs +++ b/src/Humanizer/Localisation/Formatters/ArabicFormatter.cs @@ -5,6 +5,11 @@ internal class ArabicFormatter : DefaultFormatter private const string DualPostfix = "_Dual"; private const string PluralPostfix = "_Plural"; + public ArabicFormatter() + : base("ar") + { + } + protected override string GetResourceKey(string resourceKey, int number) { //In Arabic pluralization 2 entities gets a different word. diff --git a/src/Humanizer/Localisation/Formatters/CzechSlovakPolishFormatter.cs b/src/Humanizer/Localisation/Formatters/CzechSlovakPolishFormatter.cs index 84918eda5..4cd4d7d56 100644 --- a/src/Humanizer/Localisation/Formatters/CzechSlovakPolishFormatter.cs +++ b/src/Humanizer/Localisation/Formatters/CzechSlovakPolishFormatter.cs @@ -4,6 +4,11 @@ internal class CzechSlovakPolishFormatter : DefaultFormatter { private const string PaucalPostfix = "_Paucal"; + public CzechSlovakPolishFormatter(string localeCode) + : base(localeCode) + { + } + protected override string GetResourceKey(string resourceKey, int number) { if (number > 1 && number < 5) diff --git a/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs b/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs index 8ebc41d8d..f0eaaa0d0 100644 --- a/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs +++ b/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs @@ -7,14 +7,24 @@ namespace Humanizer.Localisation.Formatters /// public class DefaultFormatter : IFormatter { + private readonly CultureInfo _culture; + + /// + /// Constructor. + /// + /// Name of the culture to use. + public DefaultFormatter(string localeCode) + { + _culture = new CultureInfo(localeCode); + } + /// /// Now /// - /// /// Returns Now - public virtual string DateHumanize_Now(CultureInfo culture) + public virtual string DateHumanize_Now() { - return GetResourceForDate(TimeUnit.Millisecond, Tense.Past, 0, culture); + return GetResourceForDate(TimeUnit.Millisecond, Tense.Past, 0); } /// @@ -23,21 +33,19 @@ public virtual string DateHumanize_Now(CultureInfo culture) /// /// /// - /// /// - public virtual string DateHumanize(TimeUnit timeUnit, Tense timeUnitTense, int unit, CultureInfo culture) + public virtual string DateHumanize(TimeUnit timeUnit, Tense timeUnitTense, int unit) { - return GetResourceForDate(timeUnit, timeUnitTense, unit, culture); + return GetResourceForDate(timeUnit, timeUnitTense, unit); } /// /// 0 seconds /// - /// /// Returns 0 seconds as the string representation of Zero TimeSpan - public virtual string TimeSpanHumanize_Zero(CultureInfo culture) + public virtual string TimeSpanHumanize_Zero() { - return GetResourceForTimeSpan(TimeUnit.Millisecond, 0, culture); + return GetResourceForTimeSpan(TimeUnit.Millisecond, 0); } /// @@ -45,34 +53,32 @@ public virtual string TimeSpanHumanize_Zero(CultureInfo culture) /// /// /// - /// /// - public virtual string TimeSpanHumanize(TimeUnit timeUnit, int unit, CultureInfo culture) + public virtual string TimeSpanHumanize(TimeUnit timeUnit, int unit) { - return GetResourceForTimeSpan(timeUnit, unit, culture); + return GetResourceForTimeSpan(timeUnit, unit); } - private string GetResourceForDate(TimeUnit unit, Tense timeUnitTense, int count, CultureInfo culture) + private string GetResourceForDate(TimeUnit unit, Tense timeUnitTense, int count) { string resourceKey = ResourceKeys.DateHumanize.GetResourceKey(unit, timeUnitTense: timeUnitTense, count: count); - return count == 1 ? Format(resourceKey, culture) : Format(resourceKey, count, culture); + return count == 1 ? Format(resourceKey) : Format(resourceKey, count); } - private string GetResourceForTimeSpan(TimeUnit unit, int count, CultureInfo culture) + private string GetResourceForTimeSpan(TimeUnit unit, int count) { string resourceKey = ResourceKeys.TimeSpanHumanize.GetResourceKey(unit, count); - return count == 1 ? Format(resourceKey, culture) : Format(resourceKey, count, culture); + return count == 1 ? Format(resourceKey) : Format(resourceKey, count); } /// /// /// /// - /// /// - protected virtual string Format(string resourceKey, CultureInfo culture) + protected virtual string Format(string resourceKey) { - return Resources.GetResource(GetResourceKey(resourceKey), culture); + return Resources.GetResource(GetResourceKey(resourceKey), _culture); } /// @@ -80,11 +86,10 @@ protected virtual string Format(string resourceKey, CultureInfo culture) /// /// /// - /// /// - protected virtual string Format(string resourceKey, int number, CultureInfo culture) + protected virtual string Format(string resourceKey, int number) { - return Resources.GetResource(GetResourceKey(resourceKey, number), culture).FormatWith(number); + return Resources.GetResource(GetResourceKey(resourceKey, number), _culture).FormatWith(number); } /// diff --git a/src/Humanizer/Localisation/Formatters/HebrewFormatter.cs b/src/Humanizer/Localisation/Formatters/HebrewFormatter.cs index 23542079f..ef151ef2c 100644 --- a/src/Humanizer/Localisation/Formatters/HebrewFormatter.cs +++ b/src/Humanizer/Localisation/Formatters/HebrewFormatter.cs @@ -5,6 +5,11 @@ internal class HebrewFormatter : DefaultFormatter private const string DualPostfix = "_Dual"; private const string PluralPostfix = "_Plural"; + public HebrewFormatter() + : base("he") + { + } + protected override string GetResourceKey(string resourceKey, int number) { //In Hebrew pluralization 2 entities gets a different word. diff --git a/src/Humanizer/Localisation/Formatters/IFormatter.cs b/src/Humanizer/Localisation/Formatters/IFormatter.cs index 4f0a6bb27..887209658 100644 --- a/src/Humanizer/Localisation/Formatters/IFormatter.cs +++ b/src/Humanizer/Localisation/Formatters/IFormatter.cs @@ -1,6 +1,4 @@ -using System.Globalization; - -namespace Humanizer.Localisation.Formatters +namespace Humanizer.Localisation.Formatters { /// /// Implement this interface if your language has complex rules around dealing with numbers. @@ -12,9 +10,8 @@ public interface IFormatter /// /// Now /// - /// /// Returns Now - string DateHumanize_Now(CultureInfo culture); + string DateHumanize_Now(); /// /// Returns the string representation of the provided DateTime @@ -22,24 +19,21 @@ public interface IFormatter /// /// /// - /// /// - string DateHumanize(TimeUnit timeUnit, Tense timeUnitTense, int unit, CultureInfo culture); + string DateHumanize(TimeUnit timeUnit, Tense timeUnitTense, int unit); /// /// 0 seconds /// - /// /// Returns 0 seconds as the string representation of Zero TimeSpan - string TimeSpanHumanize_Zero(CultureInfo culture); + string TimeSpanHumanize_Zero(); /// /// Returns the string representation of the provided TimeSpan /// /// /// - /// /// - string TimeSpanHumanize(TimeUnit timeUnit, int unit, CultureInfo culture); + string TimeSpanHumanize(TimeUnit timeUnit, int unit); } } diff --git a/src/Humanizer/Localisation/Formatters/RomanianFormatter.cs b/src/Humanizer/Localisation/Formatters/RomanianFormatter.cs index 0cfd46310..ab0e406e9 100644 --- a/src/Humanizer/Localisation/Formatters/RomanianFormatter.cs +++ b/src/Humanizer/Localisation/Formatters/RomanianFormatter.cs @@ -4,6 +4,11 @@ internal class RomanianFormatter : DefaultFormatter { private const string Above20PostFix = "_Above20"; + public RomanianFormatter() + : base("ro") + { + } + protected override string GetResourceKey(string resourceKey, int number) { var mod100 = number%100; diff --git a/src/Humanizer/Localisation/Formatters/RussianFormatter.cs b/src/Humanizer/Localisation/Formatters/RussianFormatter.cs index fa17a0eff..14f054706 100644 --- a/src/Humanizer/Localisation/Formatters/RussianFormatter.cs +++ b/src/Humanizer/Localisation/Formatters/RussianFormatter.cs @@ -4,6 +4,11 @@ namespace Humanizer.Localisation.Formatters { internal class RussianFormatter : DefaultFormatter { + public RussianFormatter() + : base("ru") + { + } + protected override string GetResourceKey(string resourceKey, int number) { var grammaticalNumber = RussianGrammaticalNumberDetector.Detect(number); diff --git a/src/Humanizer/Localisation/Formatters/SerbianFormatter.cs b/src/Humanizer/Localisation/Formatters/SerbianFormatter.cs index 8f8ea913e..ebe765abf 100644 --- a/src/Humanizer/Localisation/Formatters/SerbianFormatter.cs +++ b/src/Humanizer/Localisation/Formatters/SerbianFormatter.cs @@ -4,6 +4,11 @@ internal class SerbianFormatter : DefaultFormatter { private const string PaucalPostfix = "_Paucal"; + public SerbianFormatter(string localeCode) + : base(localeCode) + { + } + protected override string GetResourceKey(string resourceKey, int number) { int mod10 = number % 10; diff --git a/src/Humanizer/Localisation/Formatters/SlovenianFormatter.cs b/src/Humanizer/Localisation/Formatters/SlovenianFormatter.cs index 2fe886daf..249f1da56 100644 --- a/src/Humanizer/Localisation/Formatters/SlovenianFormatter.cs +++ b/src/Humanizer/Localisation/Formatters/SlovenianFormatter.cs @@ -5,6 +5,11 @@ internal class SlovenianFormatter : DefaultFormatter private const string DualPostfix = "_Dual"; private const string TrialQuadralPostfix = "_TrialQuadral"; + public SlovenianFormatter() + : base("sl") + { + } + protected override string GetResourceKey(string resourceKey, int number) { if (number == 2) diff --git a/src/Humanizer/TimeSpanHumanizeExtensions.cs b/src/Humanizer/TimeSpanHumanizeExtensions.cs index f4ef4d60a..bcf8fc5e8 100644 --- a/src/Humanizer/TimeSpanHumanizeExtensions.cs +++ b/src/Humanizer/TimeSpanHumanizeExtensions.cs @@ -42,24 +42,24 @@ private static string GetTimePart(TimeSpan timespan, CultureInfo culture) { var formatter = Configurator.GetFormatter(culture); if (timespan.Days >= 7) - return formatter.TimeSpanHumanize(TimeUnit.Week, timespan.Days/7, culture); + return formatter.TimeSpanHumanize(TimeUnit.Week, timespan.Days/7); if (timespan.Days >= 1) - return formatter.TimeSpanHumanize(TimeUnit.Day, timespan.Days, culture); + return formatter.TimeSpanHumanize(TimeUnit.Day, timespan.Days); if (timespan.Hours >= 1) - return formatter.TimeSpanHumanize(TimeUnit.Hour, timespan.Hours, culture); + return formatter.TimeSpanHumanize(TimeUnit.Hour, timespan.Hours); if (timespan.Minutes >= 1) - return formatter.TimeSpanHumanize(TimeUnit.Minute, timespan.Minutes, culture); + return formatter.TimeSpanHumanize(TimeUnit.Minute, timespan.Minutes); if (timespan.Seconds >= 1) - return formatter.TimeSpanHumanize(TimeUnit.Second, timespan.Seconds, culture); + return formatter.TimeSpanHumanize(TimeUnit.Second, timespan.Seconds); if (timespan.Milliseconds >= 1) - return formatter.TimeSpanHumanize(TimeUnit.Millisecond, timespan.Milliseconds, culture); + return formatter.TimeSpanHumanize(TimeUnit.Millisecond, timespan.Milliseconds); - return formatter.TimeSpanHumanize_Zero(culture); + return formatter.TimeSpanHumanize_Zero(); } static TimeSpan TakeOutTheLargestUnit(TimeSpan timeSpan) From 2390f5046b74343c7b2fa12e61c01f38e7980ec8 Mon Sep 17 00:00:00 2001 From: Dmitry Gokun Date: Sun, 15 Jun 2014 09:10:48 +0300 Subject: [PATCH 12/16] NumberToWords: added support for the explicit culture --- readme.md | 14 ++++++ release_notes.md | 1 + ...provalTest.approve_public_api.approved.txt | 9 ++-- src/Humanizer.Tests/NumberToWordsTests.cs | 21 +++++++- src/Humanizer/Configuration/Configurator.cs | 8 ++-- .../Configuration/LocaliserRegistry.cs | 18 +++++-- .../NumberToWordsConverterRegistry.cs | 2 +- src/Humanizer/Humanizer.csproj | 2 + .../ArabicNumberToWordsConverter.cs | 2 +- ...azilianPortugueseNumberToWordsConverter.cs | 18 ++----- .../DefaultNumberToWordsConverter.cs | 41 ++++++---------- .../DutchNumberToWordsConverter.cs | 16 +++---- .../EnglishNumberToWordsConverter.cs | 2 +- .../FarsiNumberToWordsConverter.cs | 7 +-- .../FrenchNumberToWordsConverter.cs | 14 ++---- .../GenderedNumberToWordsConverter.cs | 48 +++++++++++++++++++ .../GenderlessNumberToWordsConverter.cs | 41 ++++++++++++++++ .../GermanNumberToWordsConverter.cs | 9 +--- .../HebrewNumberToWordsConverter.cs | 18 +++++-- .../PolishNumberToWordsConverter.cs | 10 +++- .../RussianNumberToWordsConverter.cs | 12 +---- .../SlovenianNumberToWordsConverter.cs | 10 +++- .../SpanishNumberToWordsConverter.cs | 6 +-- src/Humanizer/NumberToWordsExtension.cs | 21 ++++---- 24 files changed, 232 insertions(+), 118 deletions(-) create mode 100644 src/Humanizer/Localisation/NumberToWords/GenderedNumberToWordsConverter.cs create mode 100644 src/Humanizer/Localisation/NumberToWords/GenderlessNumberToWordsConverter.cs diff --git a/readme.md b/readme.md index d3846b2b7..5311c0245 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("ru")) => "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..66fb513db 100644 --- a/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt +++ b/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt @@ -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() { } @@ -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 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..855c72874 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,10 +8,12 @@ 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 Func _defaultLocaliserFactory; /// /// Creates a localiser registry with the default localiser set to the provided value @@ -21,6 +24,15 @@ public LocaliserRegistry(TLocaliser defaultLocaliser) _defaultLocaliser = defaultLocaliser; } + /// + /// Creates a localiser registry with the default localiser factory set to the provided value + /// + /// + public LocaliserRegistry(Func defaultLocaliser) + { + _defaultLocaliserFactory = defaultLocaliser; + } + /// /// Gets the localiser for the current thread's UI culture /// @@ -45,7 +57,7 @@ public TLocaliser ResolveForCulture(CultureInfo culture) if (_localisers.TryGetValue(culture.TwoLetterISOLanguageName, out localiser)) return localiser; - return _defaultLocaliser; + return _defaultLocaliser ?? _defaultLocaliserFactory(culture); } /// diff --git a/src/Humanizer/Configuration/NumberToWordsConverterRegistry.cs b/src/Humanizer/Configuration/NumberToWordsConverterRegistry.cs index 45c829a14..e1510ee02 100644 --- a/src/Humanizer/Configuration/NumberToWordsConverterRegistry.cs +++ b/src/Humanizer/Configuration/NumberToWordsConverterRegistry.cs @@ -4,7 +4,7 @@ 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()); 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..078fad21b 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 sealed 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..ee996c05c --- /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 using the locale's default grammatical gender + /// + /// + /// + public abstract string Convert(int number); + + /// + /// Converts the number to string using the provided grammatical gender + /// + /// + /// + /// + public string Convert(int number, GrammaticalGender gender) + { + return Convert(number); + } + + /// + /// Converts the number to ordinal string using the locale's default grammatical gender + /// + /// + /// + public abstract string ConvertToOrdinal(int number); + + /// + /// Converts the number to ordinal string using 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..1866263a6 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() + : base(GrammaticalGender.Feminine) { - // in Hebrew, the default number gender form is feminine. - return Convert(number, GrammaticalGender.Feminine); + _culture = new CultureInfo("he"); } 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..32e5d1112 100644 --- a/src/Humanizer/Localisation/NumberToWords/PolishNumberToWordsConverter.cs +++ b/src/Humanizer/Localisation/NumberToWords/PolishNumberToWordsConverter.cs @@ -1,13 +1,16 @@ 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 = new CultureInfo("pl"); + private static void CollectPartsUnderThousand(ICollection parts, int number) { var hundreds = number/100; @@ -99,5 +102,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..0d073ed95 100644 --- a/src/Humanizer/Localisation/NumberToWords/SlovenianNumberToWordsConverter.cs +++ b/src/Humanizer/Localisation/NumberToWords/SlovenianNumberToWordsConverter.cs @@ -1,12 +1,15 @@ using System.Collections.Generic; +using System.Globalization; namespace Humanizer.Localisation.NumberToWords { - internal class SlovenianNumberToWordsConverter : DefaultNumberToWordsConverter + 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"}; + private readonly CultureInfo _culture = new CultureInfo("sl"); + public override string Convert(int number) { if (number == 0) @@ -75,6 +78,11 @@ public override string Convert(int number) return string.Join("", parts); } + 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) 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); } } } From 1e343d5c8e1faae735f64f141e8428926ee424fc Mon Sep 17 00:00:00 2001 From: Dmitry Gokun Date: Thu, 19 Jun 2014 00:13:42 +0300 Subject: [PATCH 13/16] Fixed NumberToWords sample --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 5311c0245..fe15f78f5 100644 --- a/readme.md +++ b/readme.md @@ -563,7 +563,7 @@ Obviously this only applies to some cultures. For others passing gender in doesn 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" +11.ToWords(new CultureInfo("en")) => "eleven" 1.ToWords(GrammaticalGender.Masculine, new CultureInfo("ru")) => "один" ``` From 3d43f8e1c3310726c491fd4eade5f89e20e72b5a Mon Sep 17 00:00:00 2001 From: Dmitry Gokun Date: Thu, 19 Jun 2014 18:50:57 +0300 Subject: [PATCH 14/16] Using real "Culture" object with NumberToWords converters --- ...provalTest.approve_public_api.approved.txt | 1 + .../Configuration/LocaliserRegistry.cs | 42 ++++++++++++------- .../NumberToWordsConverterRegistry.cs | 6 +-- .../HebrewNumberToWordsConverter.cs | 4 +- .../PolishNumberToWordsConverter.cs | 7 +++- .../SlovenianNumberToWordsConverter.cs | 29 +++++++------ 6 files changed, 55 insertions(+), 34 deletions(-) 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 66fb513db..eba13a1c6 100644 --- a/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt +++ b/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt @@ -86,6 +86,7 @@ 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() { } } diff --git a/src/Humanizer/Configuration/LocaliserRegistry.cs b/src/Humanizer/Configuration/LocaliserRegistry.cs index 855c72874..a7e4880f3 100644 --- a/src/Humanizer/Configuration/LocaliserRegistry.cs +++ b/src/Humanizer/Configuration/LocaliserRegistry.cs @@ -11,9 +11,8 @@ namespace Humanizer.Configuration public class LocaliserRegistry where TLocaliser : class { - private readonly IDictionary _localisers = new Dictionary(); - private readonly TLocaliser _defaultLocaliser; - private readonly Func _defaultLocaliserFactory; + private readonly IDictionary> _localisers = new Dictionary>(); + private readonly Func _defaultLocaliser; /// /// Creates a localiser registry with the default localiser set to the provided value @@ -21,7 +20,7 @@ public class LocaliserRegistry /// public LocaliserRegistry(TLocaliser defaultLocaliser) { - _defaultLocaliser = defaultLocaliser; + _defaultLocaliser = (culture) => defaultLocaliser; } /// @@ -30,7 +29,7 @@ public LocaliserRegistry(TLocaliser defaultLocaliser) /// public LocaliserRegistry(Func defaultLocaliser) { - _defaultLocaliserFactory = defaultLocaliser; + _defaultLocaliser = defaultLocaliser; } /// @@ -47,25 +46,36 @@ 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; - - TLocaliser localiser; - - if (_localisers.TryGetValue(culture.Name, out localiser)) - return localiser; - - if (_localisers.TryGetValue(culture.TwoLetterISOLanguageName, out localiser)) - return localiser; - - return _defaultLocaliser ?? _defaultLocaliserFactory(culture); + return FindLocaliser(culture ?? CultureInfo.CurrentUICulture)(culture); } /// /// 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; + + if (_localisers.TryGetValue(culture.TwoLetterISOLanguageName, out localiser)) + return localiser; + + return _defaultLocaliser; + } } } diff --git a/src/Humanizer/Configuration/NumberToWordsConverterRegistry.cs b/src/Humanizer/Configuration/NumberToWordsConverterRegistry.cs index e1510ee02..b22be4615 100644 --- a/src/Humanizer/Configuration/NumberToWordsConverterRegistry.cs +++ b/src/Humanizer/Configuration/NumberToWordsConverterRegistry.cs @@ -10,13 +10,13 @@ public NumberToWordsConverterRegistry() : base((culture) => new DefaultNumberToW 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/Localisation/NumberToWords/HebrewNumberToWordsConverter.cs b/src/Humanizer/Localisation/NumberToWords/HebrewNumberToWordsConverter.cs index 1866263a6..411dc0b53 100644 --- a/src/Humanizer/Localisation/NumberToWords/HebrewNumberToWordsConverter.cs +++ b/src/Humanizer/Localisation/NumberToWords/HebrewNumberToWordsConverter.cs @@ -32,10 +32,10 @@ private enum Group Billions = 1000000000 } - public HebrewNumberToWordsConverter() + public HebrewNumberToWordsConverter(CultureInfo culture) : base(GrammaticalGender.Feminine) { - _culture = new CultureInfo("he"); + _culture = culture; } public override string Convert(int number, GrammaticalGender gender) diff --git a/src/Humanizer/Localisation/NumberToWords/PolishNumberToWordsConverter.cs b/src/Humanizer/Localisation/NumberToWords/PolishNumberToWordsConverter.cs index 32e5d1112..894a90852 100644 --- a/src/Humanizer/Localisation/NumberToWords/PolishNumberToWordsConverter.cs +++ b/src/Humanizer/Localisation/NumberToWords/PolishNumberToWordsConverter.cs @@ -9,7 +9,12 @@ internal class PolishNumberToWordsConverter : GenderlessNumberToWordsConverter 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 = new CultureInfo("pl"); + private readonly CultureInfo _culture; + + public PolishNumberToWordsConverter(CultureInfo culture) + { + _culture = culture; + } private static void CollectPartsUnderThousand(ICollection parts, int number) { diff --git a/src/Humanizer/Localisation/NumberToWords/SlovenianNumberToWordsConverter.cs b/src/Humanizer/Localisation/NumberToWords/SlovenianNumberToWordsConverter.cs index 0d073ed95..eaa48ca2c 100644 --- a/src/Humanizer/Localisation/NumberToWords/SlovenianNumberToWordsConverter.cs +++ b/src/Humanizer/Localisation/NumberToWords/SlovenianNumberToWordsConverter.cs @@ -3,14 +3,19 @@ namespace Humanizer.Localisation.NumberToWords { - 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"}; + 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"}; - private readonly CultureInfo _culture = new CultureInfo("sl"); + private readonly CultureInfo _culture; - public override string Convert(int number) + public SlovenianNumberToWordsConverter(CultureInfo culture) + { + _culture = culture; + } + + public override string Convert(int number) { if (number == 0) return "nič"; @@ -78,12 +83,12 @@ public override string Convert(int number) return string.Join("", parts); } - public override string ConvertToOrdinal(int number) - { - return number.ToString(_culture); - } + public override string ConvertToOrdinal(int number) + { + return number.ToString(_culture); + } - private string Part(string singular, string dual, string trialQuadral, string plural, int number) + private string Part(string singular, string dual, string trialQuadral, string plural, int number) { if (number == 1) return singular; @@ -96,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 From 4d4270c6b803909ad67aca191b5b3a399888d4fa Mon Sep 17 00:00:00 2001 From: Dmitry Gokun Date: Fri, 20 Jun 2014 07:30:31 +0300 Subject: [PATCH 15/16] Small fixes during code review --- .../NumberToWords/DefaultNumberToWordsConverter.cs | 2 +- .../NumberToWords/GenderlessNumberToWordsConverter.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Humanizer/Localisation/NumberToWords/DefaultNumberToWordsConverter.cs b/src/Humanizer/Localisation/NumberToWords/DefaultNumberToWordsConverter.cs index 078fad21b..ece6e9b0c 100644 --- a/src/Humanizer/Localisation/NumberToWords/DefaultNumberToWordsConverter.cs +++ b/src/Humanizer/Localisation/NumberToWords/DefaultNumberToWordsConverter.cs @@ -2,7 +2,7 @@ namespace Humanizer.Localisation.NumberToWords { - internal sealed class DefaultNumberToWordsConverter : GenderlessNumberToWordsConverter + internal class DefaultNumberToWordsConverter : GenderlessNumberToWordsConverter { private readonly CultureInfo _culture; diff --git a/src/Humanizer/Localisation/NumberToWords/GenderlessNumberToWordsConverter.cs b/src/Humanizer/Localisation/NumberToWords/GenderlessNumberToWordsConverter.cs index ee996c05c..696aa78dd 100644 --- a/src/Humanizer/Localisation/NumberToWords/GenderlessNumberToWordsConverter.cs +++ b/src/Humanizer/Localisation/NumberToWords/GenderlessNumberToWordsConverter.cs @@ -3,14 +3,14 @@ abstract class GenderlessNumberToWordsConverter : INumberToWordsConverter { /// - /// Converts the number to string using the locale's default grammatical gender + /// Converts the number to string using /// /// /// public abstract string Convert(int number); /// - /// Converts the number to string using the provided grammatical gender + /// Converts the number to string ignoring the provided grammatical gender /// /// /// @@ -21,14 +21,14 @@ public string Convert(int number, GrammaticalGender gender) } /// - /// Converts the number to ordinal string using the locale's default grammatical gender + /// Converts the number to ordinal string /// /// /// public abstract string ConvertToOrdinal(int number); /// - /// Converts the number to ordinal string using the provided grammatical gender + /// Converts the number to ordinal string ignoring the provided grammatical gender /// /// /// From 7ccfd203ab2a4b6c2500651f4b71da0554453c8e Mon Sep 17 00:00:00 2001 From: "Dmitry V. Gokun" Date: Fri, 20 Jun 2014 09:07:40 +0300 Subject: [PATCH 16/16] Fixed comment --- .../NumberToWords/GenderlessNumberToWordsConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Humanizer/Localisation/NumberToWords/GenderlessNumberToWordsConverter.cs b/src/Humanizer/Localisation/NumberToWords/GenderlessNumberToWordsConverter.cs index 696aa78dd..fa89f250d 100644 --- a/src/Humanizer/Localisation/NumberToWords/GenderlessNumberToWordsConverter.cs +++ b/src/Humanizer/Localisation/NumberToWords/GenderlessNumberToWordsConverter.cs @@ -3,7 +3,7 @@ abstract class GenderlessNumberToWordsConverter : INumberToWordsConverter { /// - /// Converts the number to string using + /// Converts the number to string /// /// ///