diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml index 4f456d7..4cd5722 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create_release.yml @@ -96,7 +96,7 @@ jobs: file_pattern: CHANGELOG.md # Download artifacts - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: NuGet path: Artifacts/ diff --git a/.github/workflows/publish.yml b/.github/workflows/publish_private.yml similarity index 98% rename from .github/workflows/publish.yml rename to .github/workflows/publish_private.yml index dcdfaa9..244f92e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish_private.yml @@ -61,7 +61,7 @@ jobs: steps: # Download artifacts - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: NuGet path: Artifacts/ diff --git a/.gitignore b/.gitignore index c4ce8fa..495aedd 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ tools/FAKE/ build-log.xml Nuget.key TestResult.xml +nuget.config # Build results [Bb]uild/ diff --git a/src/MyNet.Humanizer.UnitTests/CollectionHumanizeTests.cs b/src/MyNet.Humanizer.UnitTests/CollectionHumanizeTests.cs index ff1ce89..4dc72b4 100644 --- a/src/MyNet.Humanizer.UnitTests/CollectionHumanizeTests.cs +++ b/src/MyNet.Humanizer.UnitTests/CollectionHumanizeTests.cs @@ -15,6 +15,7 @@ public class SomeClass } [UseCulture("en")] + [Collection("UseCultureSequential")] public class CollectionHumanizeTests { [Fact] @@ -104,7 +105,6 @@ public void HumanizeUsesObjectDisplayFormatterWhenSeparatorIsProvided() public void HumanizeHandlesNullItemsWithoutAnException() => Assert.Null(Record.Exception(() => new object?[] { null, null }.Humanize(", ", "and"))); [Fact] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1861:Avoid constant arrays as arguments", Justification = "It's for Unit Tests")] public void HumanizeHandlesNullStringDisplayFormatterReturnsWithoutAnException() => Assert.Null(Record.Exception(() => new[] { "A", "B", "C" }.Humanize(_ => null, ", ", "and"))); [Fact] @@ -114,11 +114,9 @@ public void HumanizeUsesObjectDisplayFormatterWhenSeparatorIsProvided() public void HumanizeRunsObjectDisplayFormatterOnNulls() => Assert.Equal("1, 2 and 3", new int?[] { 1, null, 3 }.Humanize(_ => _ ?? 2, ", ", "and")); [Fact] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1861:Avoid constant arrays as arguments", Justification = "It's for Unit Tests")] public void HumanizeRemovesEmptyItemsByDefault() => Assert.Equal("A and C", new[] { "A", " ", "C" }.Humanize(DummyFormatter, ", ", "and")); [Fact] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1861:Avoid constant arrays as arguments", Justification = "It's for Unit Tests")] public void HumanizeTrimsItemsByDefault() => Assert.Equal("A, B and C", new[] { "A", " B ", "C" }.Humanize(DummyFormatter, ", ", "and")); private static readonly Func DummyFormatter = input => input; diff --git a/src/MyNet.Humanizer.UnitTests/DateTimeHumanize.cs b/src/MyNet.Humanizer.UnitTests/DateTimeHumanize.cs index 50472fd..529843c 100644 --- a/src/MyNet.Humanizer.UnitTests/DateTimeHumanize.cs +++ b/src/MyNet.Humanizer.UnitTests/DateTimeHumanize.cs @@ -13,38 +13,40 @@ public static class DateTimeHumanize { private static readonly object LockObject = new(); - private static void VerifyWithCurrentDate(string expectedString, TimeSpan deltaFromNow, CultureInfo? culture) + private static void VerifyWithCurrentDate(string expectedString, string expectedCultureName, 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 - VerifyWithDate(expectedString, deltaFromNow, culture, localNow, utcNow); + VerifyWithDate(expectedString, expectedCultureName, deltaFromNow, culture, localNow, utcNow); } - private static void VerifyWithDateInjection(string expectedString, TimeSpan deltaFromNow, CultureInfo? culture) + private static void VerifyWithDateInjection(string expectedString, string expectedCultureName, 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); - VerifyWithDate(expectedString, deltaFromNow, culture, now, utcNow); + VerifyWithDate(expectedString, expectedCultureName, deltaFromNow, culture, now, utcNow); } - private static void VerifyWithDate(string expectedString, TimeSpan deltaFromBase, CultureInfo? culture, DateTime? baseDate, DateTime? baseDateUtc) + private static void VerifyWithDate(string expectedString, string expectedCultureName, TimeSpan deltaFromBase, CultureInfo? culture, DateTime? baseDate, DateTime? baseDateUtc) { + Assert.Equal(expectedCultureName, culture?.Name ?? CultureInfo.CurrentCulture.Name); + if (culture == null) { Assert.Equal(expectedString, baseDateUtc?.Add(deltaFromBase).Humanize(utcDate: true, dateToCompareAgainst: baseDateUtc)); - Assert.Equal(expectedString, baseDate?.Add(deltaFromBase).Humanize(baseDate, utcDate: false)); + Assert.Equal(expectedString, baseDate?.Add(deltaFromBase).Humanize(baseDate, utcDate: false, culture: culture)); } else { Assert.Equal(expectedString, baseDateUtc?.Add(deltaFromBase).Humanize(utcDate: true, dateToCompareAgainst: baseDateUtc, culture: culture)); - Assert.Equal(expectedString, baseDate?.Add(deltaFromBase).Humanize(culture, baseDate, utcDate: false)); + Assert.Equal(expectedString, baseDate?.Add(deltaFromBase).Humanize(baseDate, utcDate: false, culture: culture)); } } - public static void Verify(string expectedString, int unit, TimeUnit timeUnit, Tense tense, CultureInfo? culture = null, DateTime? baseDate = null, DateTime? baseDateUtc = null) + public static void Verify(string expectedString, string expectedCultureName, int unit, TimeUnit timeUnit, Tense tense, CultureInfo? culture = null, DateTime? baseDate = null, DateTime? baseDateUtc = null) { // We lock this as these tests can be multi-threaded and we're setting a static lock (LockObject) @@ -82,11 +84,11 @@ public static void Verify(string expectedString, int unit, TimeUnit timeUnit, Te if (baseDate == null) { - VerifyWithCurrentDate(expectedString, deltaFromNow, culture); - VerifyWithDateInjection(expectedString, deltaFromNow, culture); + VerifyWithCurrentDate(expectedString, expectedCultureName, deltaFromNow, culture); + VerifyWithDateInjection(expectedString, expectedCultureName, deltaFromNow, culture); } else - VerifyWithDate(expectedString, deltaFromNow, culture, baseDate, baseDateUtc); + VerifyWithDate(expectedString, expectedCultureName, deltaFromNow, culture, baseDate, baseDateUtc); } } } diff --git a/src/MyNet.Humanizer.UnitTests/DateTimeHumanizeExtensionsFrTests.cs b/src/MyNet.Humanizer.UnitTests/DateTimeHumanizeExtensionsFrTests.cs index 42c5871..c8502ef 100644 --- a/src/MyNet.Humanizer.UnitTests/DateTimeHumanizeExtensionsFrTests.cs +++ b/src/MyNet.Humanizer.UnitTests/DateTimeHumanizeExtensionsFrTests.cs @@ -1,85 +1,87 @@ // Copyright (c) Stéphane ANDRE. All Right Reserved. // See the LICENSE file in the project root for more information. -using MyNet.Utilities.Units; using MyNet.Humanizer.DateTimes; +using MyNet.Utilities.Units; using Xunit; namespace MyNet.Humanizer.UnitTests { - [UseCulture("fr-FR")] + [UseCulture(Culture)] + [Collection("UseCultureSequential")] public class DateTimeHumanizeExtensionsFrTests { + public const string Culture = "fr-FR"; [Theory] [InlineData(1, "il y a 1 seconde")] [InlineData(2, "il y a 2 secondes")] [InlineData(10, "il y a 10 secondes")] - public void SecondsAgo(int seconds, string expected) => DateTimeHumanize.Verify(expected, seconds, TimeUnit.Second, Tense.Past); + public void SecondsAgo(int seconds, string expected) => DateTimeHumanize.Verify(expected, Culture, seconds, TimeUnit.Second, Tense.Past); [Theory] [InlineData(1, "dans 1 seconde")] [InlineData(2, "dans 2 secondes")] [InlineData(10, "dans 10 secondes")] - public void SecondsFromNow(int seconds, string expected) => DateTimeHumanize.Verify(expected, seconds, TimeUnit.Second, Tense.Future); + public void SecondsFromNow(int seconds, string expected) => DateTimeHumanize.Verify(expected, Culture, seconds, TimeUnit.Second, Tense.Future); [Theory] [InlineData(1, "il y a 1 minute")] [InlineData(2, "il y a 2 minutes")] [InlineData(10, "il y a 10 minutes")] [InlineData(60, "il y a 1 heure")] - public void MinutesAgo(int minutes, string expected) => DateTimeHumanize.Verify(expected, minutes, TimeUnit.Minute, Tense.Past); + public void MinutesAgo(int minutes, string expected) => DateTimeHumanize.Verify(expected, Culture, minutes, TimeUnit.Minute, Tense.Past); [Theory] [InlineData(1, "dans 1 minute")] [InlineData(2, "dans 2 minutes")] [InlineData(10, "dans 10 minutes")] - public void MinutesFromNow(int minutes, string expected) => DateTimeHumanize.Verify(expected, minutes, TimeUnit.Minute, Tense.Future); + public void MinutesFromNow(int minutes, string expected) => DateTimeHumanize.Verify(expected, Culture, minutes, TimeUnit.Minute, Tense.Future); [Theory] [InlineData(1, "il y a 1 heure")] [InlineData(2, "il y a 2 heures")] [InlineData(10, "il y a 10 heures")] - public void HoursAgo(int hours, string expected) => DateTimeHumanize.Verify(expected, hours, TimeUnit.Hour, Tense.Past); + public void HoursAgo(int hours, string expected) => DateTimeHumanize.Verify(expected, Culture, hours, TimeUnit.Hour, Tense.Past); [Theory] [InlineData(1, "dans 1 heure")] [InlineData(2, "dans 2 heures")] [InlineData(10, "dans 10 heures")] - public void HoursFromNow(int hours, string expected) => DateTimeHumanize.Verify(expected, hours, TimeUnit.Hour, Tense.Future); + public void HoursFromNow(int hours, string expected) => DateTimeHumanize.Verify(expected, Culture, hours, TimeUnit.Hour, Tense.Future); [Theory] [InlineData(1, "hier")] [InlineData(2, "il y a 2 jours")] [InlineData(10, "il y a 10 jours")] - public void DaysAgo(int days, string expected) => DateTimeHumanize.Verify(expected, days, TimeUnit.Day, Tense.Past); + public void DaysAgo(int days, string expected) => DateTimeHumanize.Verify(expected, Culture, days, TimeUnit.Day, Tense.Past); [Theory] [InlineData(1, "demain")] [InlineData(2, "dans 2 jours")] [InlineData(10, "dans 10 jours")] - public void DaysFromNow(int days, string expected) => DateTimeHumanize.Verify(expected, days, TimeUnit.Day, Tense.Future); + public void DaysFromNow(int days, string expected) => DateTimeHumanize.Verify(expected, Culture, days, TimeUnit.Day, Tense.Future); [Theory] [InlineData(1, "il y a 1 mois")] [InlineData(2, "il y a 2 mois")] [InlineData(10, "il y a 10 mois")] - public void MonthsAgo(int months, string expected) => DateTimeHumanize.Verify(expected, months, TimeUnit.Month, Tense.Past); + public void MonthsAgo(int months, string expected) => DateTimeHumanize.Verify(expected, Culture, months, TimeUnit.Month, Tense.Past); [Theory] [InlineData(1, "dans 1 mois")] [InlineData(2, "dans 2 mois")] [InlineData(10, "dans 10 mois")] - public void MonthsFromNow(int months, string expected) => DateTimeHumanize.Verify(expected, months, TimeUnit.Month, Tense.Future); + public void MonthsFromNow(int months, string expected) => DateTimeHumanize.Verify(expected, Culture, months, TimeUnit.Month, Tense.Future); [Theory] [InlineData(1, "il y a 1 an")] [InlineData(2, "il y a 2 ans")] - public void YearsAgo(int years, string expected) => DateTimeHumanize.Verify(expected, years, TimeUnit.Year, Tense.Past); + public void YearsAgo(int years, string expected) => DateTimeHumanize.Verify(expected, Culture, years, TimeUnit.Year, Tense.Past); [Theory] [InlineData(1, "dans 1 an")] [InlineData(2, "dans 2 ans")] - public void YearsFromNow(int years, string expected) => DateTimeHumanize.Verify(expected, years, TimeUnit.Year, Tense.Future); + public void YearsFromNow(int years, string expected) => DateTimeHumanize.Verify(expected, Culture, years, TimeUnit.Year, Tense.Future); } } diff --git a/src/MyNet.Humanizer.UnitTests/DateTimeHumanizeExtensionsTests.cs b/src/MyNet.Humanizer.UnitTests/DateTimeHumanizeExtensionsTests.cs index 23ad9f6..0aa2f3f 100644 --- a/src/MyNet.Humanizer.UnitTests/DateTimeHumanizeExtensionsTests.cs +++ b/src/MyNet.Humanizer.UnitTests/DateTimeHumanizeExtensionsTests.cs @@ -9,22 +9,25 @@ namespace MyNet.Humanizer.UnitTests { - [UseCulture("en-US")] + [UseCulture(Culture)] + [Collection("UseCultureSequential")] public class DateTimeHumanizeExtensionsTests { + public const string Culture = "en-US"; + [Theory] [InlineData(1, "1 second ago")] [InlineData(10, "10 seconds ago")] [InlineData(59, "59 seconds ago")] [InlineData(60, "1 minute ago")] - public void SecondsAgo(int seconds, string expected) => DateTimeHumanize.Verify(expected, seconds, TimeUnit.Second, Tense.Past); + public void SecondsAgo(int seconds, string expected) => DateTimeHumanize.Verify(expected, Culture, seconds, TimeUnit.Second, Tense.Past); [Theory] [InlineData(1, "1 second from now")] [InlineData(10, "10 seconds from now")] [InlineData(59, "59 seconds from now")] [InlineData(60, "1 minute from now")] - public void SecondsFromNow(int seconds, string expected) => DateTimeHumanize.Verify(expected, seconds, TimeUnit.Second, Tense.Future); + public void SecondsFromNow(int seconds, string expected) => DateTimeHumanize.Verify(expected, Culture, seconds, TimeUnit.Second, Tense.Future); [Theory] [InlineData(1, "1 minute ago")] @@ -35,7 +38,7 @@ public class DateTimeHumanizeExtensionsTests [InlineData(60, "1 hour ago")] [InlineData(119, "2 hours ago")] [InlineData(120, "2 hours ago")] - public void MinutesAgo(int minutes, string expected) => DateTimeHumanize.Verify(expected, minutes, TimeUnit.Minute, Tense.Past); + public void MinutesAgo(int minutes, string expected) => DateTimeHumanize.Verify(expected, Culture, minutes, TimeUnit.Minute, Tense.Past); [Theory] [InlineData(1, "1 minute from now")] @@ -44,21 +47,21 @@ public class DateTimeHumanizeExtensionsTests [InlineData(45, "45 minutes from now")] [InlineData(119, "2 hours from now")] [InlineData(120, "2 hours from now")] - public void MinutesFromNow(int minutes, string expected) => DateTimeHumanize.Verify(expected, minutes, TimeUnit.Minute, Tense.Future); + public void MinutesFromNow(int minutes, string expected) => DateTimeHumanize.Verify(expected, Culture, minutes, TimeUnit.Minute, Tense.Future); [Theory] [InlineData(1, "1 hour ago")] [InlineData(10, "10 hours ago")] [InlineData(23, "23 hours ago")] [InlineData(24, "yesterday")] - public void HoursAgo(int hours, string expected) => DateTimeHumanize.Verify(expected, hours, TimeUnit.Hour, Tense.Past); + public void HoursAgo(int hours, string expected) => DateTimeHumanize.Verify(expected, Culture, hours, TimeUnit.Hour, Tense.Past); [Theory] [InlineData(1, "1 hour from now")] [InlineData(10, "10 hours from now")] [InlineData(23, "23 hours from now")] [InlineData(24, "tomorrow")] - public void HoursFromNow(int hours, string expected) => DateTimeHumanize.Verify(expected, hours, TimeUnit.Hour, Tense.Future); + public void HoursFromNow(int hours, string expected) => DateTimeHumanize.Verify(expected, Culture, hours, TimeUnit.Hour, Tense.Future); [Theory] [InlineData(35, "tomorrow")] @@ -69,7 +72,7 @@ public void HoursFromNowNotTomorrow(int hours, string expected) var utcNow = new DateTime(2014, 6, 28, 9, 58, 22, DateTimeKind.Utc); var now = new DateTime(2014, 6, 28, 9, 58, 22, DateTimeKind.Local); - DateTimeHumanize.Verify(expected, hours, TimeUnit.Hour, Tense.Future, null, now, utcNow); + DateTimeHumanize.Verify(expected, Culture, hours, TimeUnit.Hour, Tense.Future, null, now, utcNow); } [Theory] @@ -77,38 +80,38 @@ public void HoursFromNowNotTomorrow(int hours, string expected) [InlineData(10, "10 days ago")] [InlineData(27, "4 weeks ago")] [InlineData(32, "5 weeks ago")] - public void DaysAgo(int days, string expected) => DateTimeHumanize.Verify(expected, days, TimeUnit.Day, Tense.Past); + public void DaysAgo(int days, string expected) => DateTimeHumanize.Verify(expected, Culture, days, TimeUnit.Day, Tense.Past); [Theory] [InlineData(1, "tomorrow")] [InlineData(10, "10 days from now")] [InlineData(27, "4 weeks from now")] [InlineData(32, "5 weeks from now")] - public void DaysFromNow(int days, string expected) => DateTimeHumanize.Verify(expected, days, TimeUnit.Day, Tense.Future); + public void DaysFromNow(int days, string expected) => DateTimeHumanize.Verify(expected, Culture, days, TimeUnit.Day, Tense.Future); [Theory] [InlineData(1, "1 month ago")] [InlineData(10, "10 months ago")] [InlineData(11, "11 months ago")] [InlineData(12, "1 year ago")] - public void MonthsAgo(int months, string expected) => DateTimeHumanize.Verify(expected, months, TimeUnit.Month, Tense.Past); + public void MonthsAgo(int months, string expected) => DateTimeHumanize.Verify(expected, Culture, months, TimeUnit.Month, Tense.Past); [Theory] [InlineData(1, "1 month from now")] [InlineData(10, "10 months from now")] [InlineData(11, "11 months from now")] [InlineData(12, "1 year from now")] - public void MonthsFromNow(int months, string expected) => DateTimeHumanize.Verify(expected, months, TimeUnit.Month, Tense.Future); + public void MonthsFromNow(int months, string expected) => DateTimeHumanize.Verify(expected, Culture, months, TimeUnit.Month, Tense.Future); [Theory] [InlineData(1, "1 year ago")] [InlineData(2, "2 years ago")] - public void YearsAgo(int years, string expected) => DateTimeHumanize.Verify(expected, years, TimeUnit.Year, Tense.Past); + public void YearsAgo(int years, string expected) => DateTimeHumanize.Verify(expected, Culture, years, TimeUnit.Year, Tense.Past); [Theory] [InlineData(1, "1 year from now")] [InlineData(2, "2 years from now")] - public void YearsFromNow(int years, string expected) => DateTimeHumanize.Verify(expected, years, TimeUnit.Year, Tense.Future); + public void YearsFromNow(int years, string expected) => DateTimeHumanize.Verify(expected, Culture, years, TimeUnit.Year, Tense.Future); [Fact] public void Never() @@ -128,6 +131,6 @@ public void Nullable_ExpectSame() [Theory] [InlineData(1, TimeUnit.Year, Tense.Future, "en-US", "1 year from now")] [InlineData(40, TimeUnit.Second, Tense.Past, "fr-FR", "il y a 40 secondes")] - public void CanSpecifyCultureExplicitly(int unit, TimeUnit timeUnit, Tense tense, string culture, string expected) => DateTimeHumanize.Verify(expected, unit, timeUnit, tense, culture: new CultureInfo(culture)); + public void CanSpecifyCultureExplicitly(int unit, TimeUnit timeUnit, Tense tense, string culture, string expected) => DateTimeHumanize.Verify(expected, culture, unit, timeUnit, tense, culture: new CultureInfo(culture)); } } diff --git a/src/MyNet.Humanizer.UnitTests/DehumanizeToEnumTests.cs b/src/MyNet.Humanizer.UnitTests/DehumanizeToEnumTests.cs index a2610da..2fc19b2 100644 --- a/src/MyNet.Humanizer.UnitTests/DehumanizeToEnumTests.cs +++ b/src/MyNet.Humanizer.UnitTests/DehumanizeToEnumTests.cs @@ -8,7 +8,7 @@ namespace MyNet.Humanizer.UnitTests public class DehumanizeToEnumTests { [Fact] - public void ThrowsForEnumNoMatch() => _ = Assert.Throws(() => EnumTestsResources.MemberWithDescriptionAttribute.DehumanizeTo()); + public void ThrowsForEnumNoMatch() => _ = Assert.Throws(() => EnumTestsResources.MemberWithDescriptionAttribute.DehumanizeTo(onNoMatch: OnNoMatch.ThrowsException)); [Fact] public void DehumanizeMembersWithoutDescriptionAttribute() => Assert.Equal(EnumUnderTest.MemberWithoutDescriptionAttribute, EnumUnderTest.MemberWithoutDescriptionAttribute.ToString().DehumanizeTo()); diff --git a/src/MyNet.Humanizer.UnitTests/MetricNumeralExtensionsTests.cs b/src/MyNet.Humanizer.UnitTests/MetricNumeralExtensionsTests.cs index 20b316c..65d0b7c 100644 --- a/src/MyNet.Humanizer.UnitTests/MetricNumeralExtensionsTests.cs +++ b/src/MyNet.Humanizer.UnitTests/MetricNumeralExtensionsTests.cs @@ -10,6 +10,7 @@ namespace MyNet.Humanizer.UnitTests { [UseCulture("en-US")] + [Collection("UseCultureSequential")] public class MetricNumeralExtensionsTests { // Return a sequence of -24 -> 26 diff --git a/src/MyNet.Humanizer.UnitTests/MyNet.Humanizer.UnitTests.csproj b/src/MyNet.Humanizer.UnitTests/MyNet.Humanizer.UnitTests.csproj index 9fd2f08..28e88d8 100644 --- a/src/MyNet.Humanizer.UnitTests/MyNet.Humanizer.UnitTests.csproj +++ b/src/MyNet.Humanizer.UnitTests/MyNet.Humanizer.UnitTests.csproj @@ -1,13 +1,13 @@  - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/MyNet.Humanizer.UnitTests/NumberHumanizeExtensionsTests.cs b/src/MyNet.Humanizer.UnitTests/NumberHumanizeExtensionsTests.cs index b2d00b6..8953ead 100644 --- a/src/MyNet.Humanizer.UnitTests/NumberHumanizeExtensionsTests.cs +++ b/src/MyNet.Humanizer.UnitTests/NumberHumanizeExtensionsTests.cs @@ -9,6 +9,7 @@ namespace MyNet.Humanizer.UnitTests { [UseCulture("en-US")] + [Collection("UseCultureSequential")] public class NumberHumanizeExtensionsTests { [Theory] diff --git a/src/MyNet.Humanizer.UnitTests/TimeSpanHumanizeTests.cs b/src/MyNet.Humanizer.UnitTests/TimeSpanHumanizeTests.cs index b2c83a3..25dcd60 100644 --- a/src/MyNet.Humanizer.UnitTests/TimeSpanHumanizeTests.cs +++ b/src/MyNet.Humanizer.UnitTests/TimeSpanHumanizeTests.cs @@ -10,6 +10,7 @@ namespace MyNet.Humanizer.UnitTests { [UseCulture("en-US")] + [Collection("UseCultureSequential")] public class TimeSpanHumanizeTests { [Fact] diff --git a/src/MyNet.Humanizer.UnitTests/ToQuantityExtensionsTests.cs b/src/MyNet.Humanizer.UnitTests/ToQuantityExtensionsTests.cs index 83f8921..a3b1caf 100644 --- a/src/MyNet.Humanizer.UnitTests/ToQuantityExtensionsTests.cs +++ b/src/MyNet.Humanizer.UnitTests/ToQuantityExtensionsTests.cs @@ -9,6 +9,7 @@ namespace MyNet.Humanizer.UnitTests { [UseCulture("en-US")] + [Collection("UseCultureSequential")] public class ToQuantityExtensionsTests { [Theory] @@ -82,12 +83,12 @@ public void ToQuantityWordsWithCurrentCultureFormatting(string word, int quantit } [Theory] - [InlineData("case", 0, "N0", "it-IT", "0 cases")] + [InlineData("case", 0, "N0", "it-IT", "0 case")] [InlineData("case", 1, "N0", "it-IT", "1 case")] [InlineData("case", 2, "N0", "it-IT", "2 cases")] [InlineData("case", 1234567, "N0", "it-IT", "1.234.567 cases")] [InlineData("case", 1234567, "N2", "it-IT", "1.234.567,00 cases")] - [InlineData("euro", 0, "C0", "es-ES", "0 € euros")] + [InlineData("euro", 0, "C0", "es-ES", "0 € euro")] [InlineData("euro", 1, "C0", "es-ES", "1 € euro")] [InlineData("euro", 2, "C0", "es-ES", "2 € euros")] [InlineData("euro", 2, "C2", "es-ES", "2,00 € euros")] diff --git a/src/MyNet.Humanizer.UnitTests/TransformersTests.cs b/src/MyNet.Humanizer.UnitTests/TransformersTests.cs index e517165..021c2d5 100644 --- a/src/MyNet.Humanizer.UnitTests/TransformersTests.cs +++ b/src/MyNet.Humanizer.UnitTests/TransformersTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Stéphane ANDRE. All Right Reserved. // See the LICENSE file in the project root for more information. +using System.Globalization; using MyNet.Humanizer.Transformer; using Xunit; @@ -18,25 +19,25 @@ public class TransformersTests [InlineData("Title Case", "Title Case")] [InlineData("apostrophe's aren't capitalized", "Apostrophe's Aren't Capitalized")] [InlineData("titles with, commas work too", "Titles With, Commas Work Too")] - public void TransformToTitleCase(string input, string expectedOutput) => Assert.Equal(expectedOutput, input.Transform(To.TitleCase)); + public void TransformToTitleCase(string input, string expectedOutput) => Assert.Equal(expectedOutput, input.Transform(CultureInfo.CurrentCulture, To.TitleCase)); [Theory] [InlineData("lower case statement", "lower case statement")] [InlineData("Sentence casing", "sentence casing")] [InlineData("No honor for UPPER case", "no honor for upper case")] [InlineData("Title Case", "title case")] - public void TransformToLowerCase(string input, string expectedOutput) => Assert.Equal(expectedOutput, input.Transform(To.LowerCase)); + public void TransformToLowerCase(string input, string expectedOutput) => Assert.Equal(expectedOutput, input.Transform(CultureInfo.CurrentCulture, To.LowerCase)); [Theory] [InlineData("lower case statement", "Lower case statement")] [InlineData("Sentence casing", "Sentence casing")] [InlineData("honors UPPER case", "Honors UPPER case")] - public void TransformToSentenceCase(string input, string expectedOutput) => Assert.Equal(expectedOutput, input.Transform(To.SentenceCase)); + public void TransformToSentenceCase(string input, string expectedOutput) => Assert.Equal(expectedOutput, input.Transform(CultureInfo.CurrentCulture, To.SentenceCase)); [Theory] [InlineData("lower case statement", "LOWER CASE STATEMENT")] [InlineData("Sentence casing", "SENTENCE CASING")] [InlineData("Title Case", "TITLE CASE")] - public void TransformToUpperCase(string input, string expectedOutput) => Assert.Equal(expectedOutput, input.Transform(To.UpperCase)); + public void TransformToUpperCase(string input, string expectedOutput) => Assert.Equal(expectedOutput, input.Transform(CultureInfo.CurrentCulture, To.UpperCase)); } } diff --git a/src/MyNet.Humanizer.UnitTests/UseCultureAttribute.cs b/src/MyNet.Humanizer.UnitTests/UseCultureAttribute.cs index 94332e1..972c52c 100644 --- a/src/MyNet.Humanizer.UnitTests/UseCultureAttribute.cs +++ b/src/MyNet.Humanizer.UnitTests/UseCultureAttribute.cs @@ -48,7 +48,7 @@ public override void Before(MethodInfo methodUnderTest) { _originalCulture = CultureInfo.CurrentCulture; - CultureInfoService.Current.SetCulture(Culture); + GlobalizationService.Current.SetCulture(Culture); } /// @@ -60,7 +60,7 @@ public override void After(MethodInfo methodUnderTest) { if (_originalCulture == null) return; - CultureInfoService.Current.SetCulture(_originalCulture); + GlobalizationService.Current.SetCulture(_originalCulture); } } } diff --git a/src/MyNet.Humanizer/CasingExtensions.cs b/src/MyNet.Humanizer/CasingExtensions.cs index 45765fc..c2827f0 100644 --- a/src/MyNet.Humanizer/CasingExtensions.cs +++ b/src/MyNet.Humanizer/CasingExtensions.cs @@ -3,6 +3,7 @@ using MyNet.Humanizer.Transformer; using System; +using System.Globalization; namespace MyNet.Humanizer { @@ -16,14 +17,15 @@ public static class CasingExtensions /// /// /// + /// /// - public static string ApplyCase(this string input, LetterCasing casing) => casing switch + public static string ApplyCase(this string input, LetterCasing casing, CultureInfo? culture = null) => casing switch { LetterCasing.Normal => input, - LetterCasing.Title => input.Transform(To.TitleCase), - LetterCasing.LowerCase => input.Transform(To.LowerCase), - LetterCasing.AllCaps => input.Transform(To.UpperCase), - LetterCasing.Sentence => input.Transform(To.SentenceCase), + LetterCasing.Title => input.Transform(culture, To.TitleCase), + LetterCasing.LowerCase => input.Transform(culture, To.LowerCase), + LetterCasing.AllCaps => input.Transform(culture, To.UpperCase), + LetterCasing.Sentence => input.Transform(culture, To.SentenceCase), _ => throw new ArgumentOutOfRangeException(nameof(casing)), }; @@ -32,4 +34,4 @@ public static class CasingExtensions public static string ToTitle(this string input) => ApplyCase(input, LetterCasing.Title); public static string ToSentence(this string input) => ApplyCase(input, LetterCasing.Sentence); } -} \ No newline at end of file +} diff --git a/src/MyNet.Humanizer/DateTimeHumanizeExtensions.cs b/src/MyNet.Humanizer/DateTimeHumanizeExtensions.cs index 98f969e..6d248a4 100644 --- a/src/MyNet.Humanizer/DateTimeHumanizeExtensions.cs +++ b/src/MyNet.Humanizer/DateTimeHumanizeExtensions.cs @@ -3,8 +3,8 @@ using System; using System.Globalization; -using MyNet.Utilities.Extensions; using MyNet.Humanizer.DateTimes; +using MyNet.Utilities.Localization; using MyNet.Utilities.Units; namespace MyNet.Humanizer @@ -13,20 +13,6 @@ public static class DateTimeHumanizeExtensions { static DateTimeHumanizeExtensions() => ResourceLocator.Initialize(); - // http://stackoverflow.com/questions/11/how-do-i-calculate-relative-time - /// - /// Calculates the distance of time in words between two provided dates - /// - public static string? Humanize(this DateTime? input, DateTime? dateToCompareAgainst = null, TimeUnit unitMin = TimeUnit.Second, TimeUnit unitMax = TimeUnit.Year, bool utcDate = true) - => Humanize(input, CultureInfo.CurrentCulture, dateToCompareAgainst, unitMin, unitMax, utcDate); - - // http://stackoverflow.com/questions/11/how-do-i-calculate-relative-time - /// - /// Calculates the distance of time in words between two provided dates - /// - public static string? Humanize(this DateTime input, DateTime? dateToCompareAgainst = null, TimeUnit unitMin = TimeUnit.Second, TimeUnit unitMax = TimeUnit.Year, bool utcDate = true) - => Humanize(input, CultureInfo.CurrentCulture, dateToCompareAgainst, unitMin, unitMax, utcDate); - /// /// Turns the current or provided date into a human readable sentence, overload for the nullable DateTime, returning 'never' in case null /// @@ -37,15 +23,16 @@ public static class DateTimeHumanizeExtensions /// /// Culture to use. If null, current thread's UI culture is used. /// distance of time in words - public static string? Humanize(this DateTime? input, CultureInfo culture, DateTime? dateToCompareAgainst = null, TimeUnit unitMin = TimeUnit.Second, TimeUnit unitMax = TimeUnit.Year, bool utcDate = true) => input.HasValue - ? Humanize(input.Value, culture, dateToCompareAgainst, unitMin, unitMax, utcDate) - : culture.GetProvider()?.Never(); + public static string? Humanize(this DateTime? input, DateTime? dateToCompareAgainst = null, TimeUnit unitMin = TimeUnit.Second, TimeUnit unitMax = TimeUnit.Year, bool utcDate = true, CultureInfo? culture = null) + => input.HasValue + ? Humanize(input.Value, dateToCompareAgainst, unitMin, unitMax, utcDate, culture) + : LocalizationService.GetOrCurrent()?.Never(); // http://stackoverflow.com/questions/11/how-do-i-calculate-relative-time /// /// Calculates the distance of time in words between two provided dates /// - public static string? Humanize(this DateTime input, CultureInfo culture, DateTime? dateToCompareAgainst = null, TimeUnit unitMin = TimeUnit.Second, TimeUnit unitMax = TimeUnit.Year, bool utcDate = true) + public static string Humanize(this DateTime input, DateTime? dateToCompareAgainst = null, TimeUnit unitMin = TimeUnit.Second, TimeUnit unitMax = TimeUnit.Year, bool utcDate = true, CultureInfo? culture = null) { var comparisonBase = dateToCompareAgainst ?? DateTime.UtcNow; @@ -61,7 +48,7 @@ public static class DateTimeHumanizeExtensions /// /// Calculates the distance of time in words between two provided dates /// - public static string? Humanize(this DateTime input, DateTime comparisonBase, TimeUnit unitMin, TimeUnit unitMax, CultureInfo culture) + public static string Humanize(this DateTime input, DateTime comparisonBase, TimeUnit unitMin, TimeUnit unitMax, CultureInfo? culture = null) { var ts = new TimeSpan(Math.Abs(comparisonBase.Ticks - input.Ticks)); @@ -98,16 +85,13 @@ public static class DateTimeHumanizeExtensions /// /// Calculates the distance of time in words between two provided dates /// - public static string? Humanize(DateTime input, DateTime comparisonBase, TimeUnit timeUnit) => Humanize(input, comparisonBase, timeUnit, CultureInfo.CurrentCulture); - - // http://stackoverflow.com/questions/11/how-do-i-calculate-relative-time - /// - /// Calculates the distance of time in words between two provided dates - /// - public static string? Humanize(DateTime input, DateTime comparisonBase, TimeUnit timeUnit, CultureInfo culture) + public static string Humanize(DateTime input, DateTime comparisonBase, TimeUnit timeUnit, CultureInfo? culture = null) { var tense = input > comparisonBase ? Tense.Future : Tense.Past; - var formatter = culture.GetProvider(); + var formatter = LocalizationService.GetOrCurrent(culture); + + if (formatter is null) return string.Empty; + var ts = new TimeSpan(Math.Abs(comparisonBase.Ticks - input.Ticks)); var count = 0; @@ -146,16 +130,14 @@ public static class DateTimeHumanizeExtensions break; } - return count > 0 ? formatter?.DateHumanize(tense, timeUnit, count) : formatter?.Now(); + return count > 0 ? formatter.DateHumanize(tense, timeUnit, count) : formatter.Now(); } - public static string? ToMonthAbbreviated(this DateTime date) => ToMonthAbbreviated(date, CultureInfo.CurrentCulture); - - public static string? ToMonthAbbreviated(this DateTime date, CultureInfo culture) + public static string ToMonthAbbreviated(this DateTime date, CultureInfo? culture = null) { var result = string.Empty; - var monthNames = culture.DateTimeFormat.AbbreviatedMonthNames; + var monthNames = (culture ?? GlobalizationService.Current.Culture).DateTimeFormat.AbbreviatedMonthNames; if (monthNames != null && monthNames.Length > 0) { result = monthNames[(date.Month - 1) % monthNames.Length]; diff --git a/src/MyNet.Humanizer/DateTimes/DateTimeFormatter.cs b/src/MyNet.Humanizer/DateTimes/DateTimeFormatter.cs index 3cae455..6ee6ab1 100644 --- a/src/MyNet.Humanizer/DateTimes/DateTimeFormatter.cs +++ b/src/MyNet.Humanizer/DateTimes/DateTimeFormatter.cs @@ -58,19 +58,19 @@ public class DateTimeFormatter(CultureInfo culture) : IDateTimeFormatter /// Now /// /// Returns Now - public virtual string? Now() => _culture.Translate(NowFormat); + public virtual string Now() => NowFormat.Translate(_culture); /// /// Never /// /// Returns Never - public virtual string? Never() => _culture.Translate(NeverFormat); + public virtual string Never() => NeverFormat.Translate(_culture); /// /// 0 seconds /// /// Returns 0 seconds as the string representation of Zero TimeSpan - public virtual string? Zero() => _culture.Translate(ZeroFormat); + public virtual string Zero() => ZeroFormat.Translate(_culture); /// /// Returns the string representation of the provided DateTime @@ -79,7 +79,7 @@ public class DateTimeFormatter(CultureInfo culture) : IDateTimeFormatter /// /// /// - public virtual string? DateHumanize(Tense timeUnitTense, TimeUnit timeUnit, int count) => count.ToString(_culture.Translate(GetDateTimeResourceKey(timeUnitTense, timeUnit, count)), _culture); + public virtual string DateHumanize(Tense timeUnitTense, TimeUnit timeUnit, int count) => count.ToString(GetDateTimeResourceKey(timeUnitTense, timeUnit, count).Translate(_culture), _culture); /// /// Returns the string representation of the provided TimeSpan @@ -88,7 +88,7 @@ public class DateTimeFormatter(CultureInfo culture) : IDateTimeFormatter /// /// /// Is thrown when timeUnit is larger than TimeUnit.Week - public virtual string? TimeSpanHumanize(TimeUnit timeUnit, int count) => count.ToString(_culture.Translate(GetTimeSpanResourceKey(timeUnit, count)), _culture); + public virtual string TimeSpanHumanize(TimeUnit timeUnit, int count) => count.ToString(GetTimeSpanResourceKey(timeUnit, count).Translate(_culture), _culture); /// /// Override this method if your locale has complex rules around multiple units; e.g. Arabic, Russian @@ -97,9 +97,9 @@ public class DateTimeFormatter(CultureInfo culture) : IDateTimeFormatter /// /// The number of the units being used in formatting /// - protected virtual string? GetDateTimeResourceKey(Tense timeUnitTense, TimeUnit unit, int count) => count == 1 && unit == TimeUnit.Day + protected virtual string GetDateTimeResourceKey(Tense timeUnitTense, TimeUnit unit, int count) => count == 1 && unit == TimeUnit.Day ? timeUnitTense == Tense.Future ? TomorrowFormat : YesterdayFormat - : DateTimeFormat.FormatWith(timeUnitTense.ToString(), unit.ToString()).WithCountSuffix(count); + : DateTimeFormat.FormatWith(timeUnitTense.ToString(), unit.ToString()).ToCountKey(count); /// /// Override this method if your locale has complex rules around multiple units; e.g. Arabic, Russian @@ -107,6 +107,6 @@ public class DateTimeFormatter(CultureInfo culture) : IDateTimeFormatter /// /// The number of the units being used in formatting /// - protected virtual string? GetTimeSpanResourceKey(TimeUnit unit, int count) => TimeSpanFormat.FormatWith(unit.ToString()).WithCountSuffix(count); + protected virtual string GetTimeSpanResourceKey(TimeUnit unit, int count) => TimeSpanFormat.FormatWith(unit.ToString()).ToCountKey(count); } } diff --git a/src/MyNet.Humanizer/DateTimes/IDateTimeFormatter.cs b/src/MyNet.Humanizer/DateTimes/IDateTimeFormatter.cs index 1e6464f..1a44ca4 100644 --- a/src/MyNet.Humanizer/DateTimes/IDateTimeFormatter.cs +++ b/src/MyNet.Humanizer/DateTimes/IDateTimeFormatter.cs @@ -16,19 +16,19 @@ public interface IDateTimeFormatter /// Now /// /// Returns Now - string? Now(); + string Now(); /// /// Never /// /// Returns Never - string? Never(); + string Never(); /// /// 0 seconds /// /// Returns 0 seconds as the string representation of Zero TimeSpan - string? Zero(); + string Zero(); /// @@ -38,7 +38,7 @@ public interface IDateTimeFormatter /// /// /// - string? DateHumanize(Tense timeUnitTense, TimeUnit timeUnit, int count); + string DateHumanize(Tense timeUnitTense, TimeUnit timeUnit, int count); /// @@ -47,6 +47,6 @@ public interface IDateTimeFormatter /// /// /// - string? TimeSpanHumanize(TimeUnit timeUnit, int count); + string TimeSpanHumanize(TimeUnit timeUnit, int count); } } diff --git a/src/MyNet.Humanizer/EnumDehumanizeExtensions.cs b/src/MyNet.Humanizer/EnumDehumanizeExtensions.cs index ee349e3..1deb4c8 100644 --- a/src/MyNet.Humanizer/EnumDehumanizeExtensions.cs +++ b/src/MyNet.Humanizer/EnumDehumanizeExtensions.cs @@ -2,6 +2,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Globalization; using System.Linq; namespace MyNet.Humanizer @@ -11,20 +12,18 @@ namespace MyNet.Humanizer /// public static class EnumDehumanizeExtensions { - public static TTargetEnum? DehumanizeToNullable(this string input) - where TTargetEnum : Enum => DehumanizeToPrivate(input, typeof(TTargetEnum), OnNoMatch.ReturnsDefault) is TTargetEnum result ? result : default; - /// /// Dehumanizes a string into the Enum it was originally Humanized from! /// /// The target enum /// The string to be converted /// + /// /// If TTargetEnum is not an enum /// Couldn't find any enum member that matches the string /// - public static TTargetEnum DehumanizeTo(this string input, OnNoMatch onNoMatch = OnNoMatch.ThrowsException) - where TTargetEnum : struct, Enum => DehumanizeToPrivate(input, typeof(TTargetEnum), onNoMatch) is TTargetEnum result ? result : default; + public static TTargetEnum DehumanizeTo(this string input, OnNoMatch onNoMatch = OnNoMatch.ReturnsDefault, CultureInfo? culture = null) + where TTargetEnum : struct, Enum => DehumanizeToPrivate(input, typeof(TTargetEnum), onNoMatch, culture) is TTargetEnum result ? result : default; /// /// Dehumanizes a string into the Enum it was originally Humanized from! @@ -32,12 +31,13 @@ public static TTargetEnum DehumanizeTo(this string input, OnNoMatch /// The string to be converted /// The target enum /// What to do when input is not matched to the enum. + /// /// /// Couldn't find any enum member that matches the string /// If targetEnum is not an enum - public static Enum? DehumanizeTo(this string input, Type targetEnum, OnNoMatch onNoMatch = OnNoMatch.ThrowsException) => DehumanizeToPrivate(input, targetEnum, onNoMatch); + public static Enum? DehumanizeTo(this string input, Type targetEnum, OnNoMatch onNoMatch = OnNoMatch.ReturnsDefault, CultureInfo? culture = null) => DehumanizeToPrivate(input, targetEnum, onNoMatch, culture); - private static Enum? DehumanizeToPrivate(string input, Type targetEnum, OnNoMatch onNoMatch) + private static Enum? DehumanizeToPrivate(string input, Type targetEnum, OnNoMatch onNoMatch, CultureInfo? culture = null) { var nullableType = Nullable.GetUnderlyingType(targetEnum); @@ -49,7 +49,7 @@ public static TTargetEnum DehumanizeTo(this string input, OnNoMatch .OfType() .FirstOrDefault(value => string.Equals(value.ToString(), input, StringComparison.OrdinalIgnoreCase) - || string.Equals(value.Humanize(), input, StringComparison.OrdinalIgnoreCase)); + || string.Equals(value.Humanize(culture: culture), input, StringComparison.OrdinalIgnoreCase)); return match == null && onNoMatch == OnNoMatch.ThrowsException ? throw new NoMatchFoundException("Couldn't find any enum member that matches the string " + input) diff --git a/src/MyNet.Humanizer/EnumHumanizeExtensions.cs b/src/MyNet.Humanizer/EnumHumanizeExtensions.cs index 5bdf7bc..11b6b88 100644 --- a/src/MyNet.Humanizer/EnumHumanizeExtensions.cs +++ b/src/MyNet.Humanizer/EnumHumanizeExtensions.cs @@ -9,6 +9,7 @@ using System.Resources; using MyNet.Utilities; using MyNet.Utilities.Extensions; +using MyNet.Utilities.Localization; namespace MyNet.Humanizer { @@ -23,13 +24,13 @@ public static class EnumHumanizeExtensions /// Turns an enum member into a human readable string; e.g. AnonymousUser -> Anonymous user. It also honors DescriptionAttribute data annotation /// /// - public static string? Humanize(this Enum value, bool abbreviation = false) + public static string? Humanize(this Enum value, bool abbreviation = false, CultureInfo? culture = null) { var displayAttr = value.GetAttribute(); if (displayAttr != null) { if (displayAttr.ResourceType != null) - return new ResourceManager(displayAttr.ResourceType).GetString(displayAttr.Name ?? displayAttr.Description ?? string.Empty, CultureInfo.CurrentCulture); + return new ResourceManager(displayAttr.ResourceType).GetString(displayAttr.Name ?? displayAttr.Description ?? string.Empty, culture ?? GlobalizationService.Current.Culture); if (!string.IsNullOrEmpty(displayAttr.Description)) return displayAttr.Description; @@ -43,7 +44,7 @@ public static class EnumHumanizeExtensions return descAttr.Description; var resourceName = value.GetType().Name + value.ToString(); - var result = resourceName.Translate(abbreviation); + var result = abbreviation ? resourceName.TranslateAbbreviated(culture) : resourceName.Translate(culture); return result == resourceName ? value.ToString().Humanize() : result; } @@ -53,10 +54,11 @@ public static class EnumHumanizeExtensions /// /// The enum member to be humanized /// The casing to use for humanizing the enum member + /// /// - public static string? Humanize(this Enum input, LetterCasing casing) + public static string? Humanize(this Enum input, LetterCasing casing, CultureInfo? culture = null) { - var humanizedEnum = input.Humanize(); + var humanizedEnum = input.Humanize(culture: culture); return humanizedEnum?.ApplyCase(casing); } diff --git a/src/MyNet.Humanizer/EnumerationDehumanizeExtensions.cs b/src/MyNet.Humanizer/EnumerationDehumanizeExtensions.cs index 6095c0f..63aef64 100644 --- a/src/MyNet.Humanizer/EnumerationDehumanizeExtensions.cs +++ b/src/MyNet.Humanizer/EnumerationDehumanizeExtensions.cs @@ -2,6 +2,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Globalization; using System.Linq; using MyNet.Utilities; @@ -12,32 +13,29 @@ namespace MyNet.Humanizer /// public static class EnumerationDehumanizeExtensions { - public static TTargetEnum? DehumanizeToNullable(this string input, OnNoMatch onNoMatch = OnNoMatch.ThrowsException) - where TTargetEnum : Enumeration - => (TTargetEnum?)DehumanizeToPrivate(input, typeof(TTargetEnum), OnNoMatch.ReturnsDefault); - /// /// Dehumanizes a string into the Enum it was originally Humanized from! /// /// The target enum /// The string to be converted /// + /// /// If TTargetEnum is not an enum /// Couldn't find any enum member that matches the string /// - public static TTargetEnum DehumanizeTo(this string input, OnNoMatch onNoMatch = OnNoMatch.ThrowsException) + public static TTargetEnum DehumanizeTo(this string input, OnNoMatch onNoMatch = OnNoMatch.ReturnsDefault, CultureInfo? culture = null) where TTargetEnum : Enumeration - => (TTargetEnum)DehumanizeToPrivate(input, typeof(TTargetEnum), onNoMatch)!; + => (TTargetEnum)DehumanizeToPrivate(input, typeof(TTargetEnum), onNoMatch, culture)!; - public static IEnumeration? DehumanizeTo(this string input, Type targetEnum, OnNoMatch onNoMatch = OnNoMatch.ThrowsException) => (IEnumeration?)DehumanizeToPrivate(input, targetEnum, onNoMatch); + public static IEnumeration? DehumanizeTo(this string input, Type targetEnum, OnNoMatch onNoMatch = OnNoMatch.ReturnsDefault, CultureInfo? culture = null) => (IEnumeration?)DehumanizeToPrivate(input, targetEnum, onNoMatch, culture); - private static object? DehumanizeToPrivate(string input, Type targetEnum, OnNoMatch onNoMatch) + private static object? DehumanizeToPrivate(string input, Type targetEnum, OnNoMatch onNoMatch, CultureInfo? culture = null) { var match = Enumeration.GetAll(targetEnum) .OfType() .FirstOrDefault(value => string.Equals(value.ToString(), input, StringComparison.OrdinalIgnoreCase) - || string.Equals(value.Humanize(), input, StringComparison.OrdinalIgnoreCase)); + || string.Equals(value.Humanize(culture: culture), input, StringComparison.OrdinalIgnoreCase)); return match is null && onNoMatch == OnNoMatch.ThrowsException ? throw new NoMatchFoundException("Couldn't find any enum member that matches the string " + input) diff --git a/src/MyNet.Humanizer/EnumerationHumanizeExtensions.cs b/src/MyNet.Humanizer/EnumerationHumanizeExtensions.cs index 1739d47..fabf091 100644 --- a/src/MyNet.Humanizer/EnumerationHumanizeExtensions.cs +++ b/src/MyNet.Humanizer/EnumerationHumanizeExtensions.cs @@ -1,6 +1,7 @@ // Copyright (c) Stéphane ANDRE. All Right Reserved. // See the LICENSE file in the project root for more information. +using System.Globalization; using MyNet.Utilities; using MyNet.Utilities.Extensions; @@ -13,9 +14,9 @@ public static class EnumerationHumanizeExtensions { static EnumerationHumanizeExtensions() => ResourceLocator.Initialize(); - public static string? Humanize(this IEnumeration value, bool abbreviation = false) + public static string? Humanize(this IEnumeration value, bool abbreviation = false, CultureInfo? culture = null) { - var result = value.ResourceKey.Translate(abbreviation); + var result = abbreviation ? value.ResourceKey.TranslateAbbreviated(culture) : value.ResourceKey.Translate(culture); return result == value.ResourceKey ? value.ToString()?.Humanize() : result; } @@ -26,9 +27,9 @@ public static class EnumerationHumanizeExtensions /// The enum member to be humanized /// The casing to use for humanizing the enum member /// - public static string? Humanize(this IEnumeration input, LetterCasing casing) + public static string? Humanize(this IEnumeration input, LetterCasing casing, CultureInfo? culture = null) { - var humanizedEnum = input.Humanize(); + var humanizedEnum = input.Humanize(culture: culture); return humanizedEnum?.ApplyCase(casing); } diff --git a/src/MyNet.Humanizer/InflectorExtensions.cs b/src/MyNet.Humanizer/InflectorExtensions.cs index 6dd45fd..1ceb06e 100644 --- a/src/MyNet.Humanizer/InflectorExtensions.cs +++ b/src/MyNet.Humanizer/InflectorExtensions.cs @@ -3,10 +3,10 @@ using System.Globalization; using System.Text.RegularExpressions; -using MyNet.Utilities.Extensions; using MyNet.Humanizer.Inflections; -using MyNet.Utilities.Localization; using MyNet.Utilities; +using MyNet.Utilities.Extensions; +using MyNet.Utilities.Localization; namespace MyNet.Humanizer { @@ -15,36 +15,46 @@ namespace MyNet.Humanizer /// public static partial class InflectorExtensions { - static InflectorExtensions() => ResourceLocator.Initialize(); + public const string PluralSuffix = "Plural"; + public const string ZeroSuffix = "Zero"; - private static string GetPluralKey(this string key) => key + "Plural"; - private static string GetZeroKey(this string key) => key + "Zero"; + static InflectorExtensions() => ResourceLocator.Initialize(); - public static string? Translate(this string key, double count, bool abbreviation = false) => key?.WithCountSuffix(count)?.Translate(abbreviation); + public static string ToPluralKey(this string key) => $"{key}{PluralSuffix}"; - public static string? Translate(this string key, string filename, double count) => TranslationService.Current.Translate(key.WithCountSuffix(count), filename); + public static string ToZeroKey(this string key) => $"{key}{ZeroSuffix}"; - public static string? TranslateWithCountAndOptionalFormat(this string key, double count, bool abbreviation = false) + public static string ToCountKey(this string key, double count, CultureInfo? culture = null) { - var format = key?.WithCountSuffix(count)?.Translate(abbreviation); + var isPlural = count.IsPlural(culture); + return count.NearlyEqual(0) ? key.ToZeroKey() : !isPlural ? key : key.ToPluralKey(); + } - var isPlural = TranslationService.Current.GetProvider()?.IsPlural(count); - return isPlural.IsTrue() ? count.ToString(format, CultureInfo.CurrentCulture) : format; + public static bool IsPlural(this int count, CultureInfo? culture = null) => LocalizationService.GetOrCurrent(culture)?.IsPlural(count) ?? count > 1; + + public static bool IsPlural(this double count, CultureInfo? culture = null) => LocalizationService.GetOrCurrent(culture)?.IsPlural(count) ?? count > 1; + + public static bool IsPlural(this long count, CultureInfo? culture = null) => LocalizationService.GetOrCurrent(culture)?.IsPlural(count) ?? count > 1; + + public static string TranslateWithCount(this string key, double count, bool abbreviation = false, CultureInfo? culture = null) + { + var countKey = key.ToCountKey(count, culture); + return abbreviation ? countKey.TranslateAbbreviated(culture) : countKey.Translate(culture); } - public static string? WithCountSuffix(this string key, double count) + public static string TranslateWithCount(this string key, string filename, double count, bool abbreviation = false, CultureInfo? culture = null) { - var isPlural = TranslationService.Current.GetProvider()?.IsPlural(count); - return count.NearlyEqual(0) ? key.GetZeroKey() : isPlural != null && !isPlural.Value ? key : key.GetPluralKey(); + var countKey = key.ToCountKey(count, culture); + return abbreviation ? countKey.TranslateAbbreviated(filename, culture) : countKey.Translate(filename, culture); } - /// - /// Pluralizes the provided input considering irregular words - /// - /// Word to be pluralized - /// Normally you call Pluralize on singular words; but if you're unsure call it with false - /// - public static string? Pluralize(this string word, bool inputIsKnownToBeSingular = true) => word.Pluralize(CultureInfo.CurrentCulture, inputIsKnownToBeSingular); + public static string TranslateAndFormatWithCount(this string key, double count, bool abbreviation = false, CultureInfo? culture = null) + { + var format = key.TranslateWithCount(count, abbreviation); + + var isPlural = count.IsPlural(culture); + return isPlural ? count.ToString(format, culture ?? GlobalizationService.Current.Culture) : format; + } /// /// Pluralizes the provided input considering irregular words @@ -53,16 +63,7 @@ public static partial class InflectorExtensions /// /// Normally you call Pluralize on singular words; but if you're unsure call it with false /// - public static string? Pluralize(this string word, CultureInfo culture, bool inputIsKnownToBeSingular = true) => culture.GetProvider()?.Pluralize(word, inputIsKnownToBeSingular); - - /// - /// Singularizes the provided input considering irregular words - /// - /// Word to be singularized - /// Normally you call Singularize on plural words; but if you're unsure call it with false - /// Skip singularizing single words that have an 's' on the end - /// - public static string? Singularize(this string word, bool inputIsKnownToBePlural = true, bool skipSimpleWords = false) => word.Singularize(CultureInfo.CurrentCulture, inputIsKnownToBePlural, skipSimpleWords); + public static string Pluralize(this string word, bool inputIsKnownToBeSingular = true, CultureInfo? culture = null) => LocalizationService.GetOrCurrent(culture)?.Pluralize(word, inputIsKnownToBeSingular) ?? string.Empty; /// /// Singularizes the provided input considering irregular words @@ -72,38 +73,38 @@ public static partial class InflectorExtensions /// Normally you call Singularize on plural words; but if you're unsure call it with false /// Skip singularizing single words that have an 's' on the end /// - public static string? Singularize(this string word, CultureInfo culture, bool inputIsKnownToBePlural = true, bool skipSimpleWords = false) => culture.GetProvider()?.Singularize(word, inputIsKnownToBePlural, skipSimpleWords); + public static string Singularize(this string word, bool inputIsKnownToBePlural = true, bool skipSimpleWords = false, CultureInfo? culture = null) => LocalizationService.GetOrCurrent(culture)?.Singularize(word, inputIsKnownToBePlural, skipSimpleWords) ?? string.Empty; /// /// Humanizes the input with Title casing /// /// The string to be titleized /// - public static string? Titleize(this string input) => input.Humanize(LetterCasing.Title); + public static string Titleize(this string input) => input.Humanize(LetterCasing.Title); /// /// By default, pascalize converts strings to UpperCamelCase also removing underscores /// /// /// - public static string? Pascalize(this string input) => PascalizeRegex().Replace(input, match => match.Groups[1].Value.ToUpper()); + public static string Pascalize(this string input) => PascalizeRegex().Replace(input, match => match.Groups[1].Value.ToUpper()); /// /// Separates the input words with underscore /// /// The string to be underscored /// - public static string? Underscore(this string input) => UnderscoreRegex().Replace(UnderscoreRegex2().Replace(UnderscoreRegex3().Replace(input, "$1_$2"), "$1_$2"), "_").ToLower(); + public static string Underscore(this string input) => UnderscoreRegex().Replace(UnderscoreRegex2().Replace(UnderscoreRegex3().Replace(input, "$1_$2"), "$1_$2"), "_").ToLower(); /// /// Same as Pascalize except that the first character is lower case /// /// /// - public static string? Camelize(this string input) + public static string Camelize(this string input) { var word = input.Pascalize(); - return word?.Length > 0 ? $"{word.Substring(0, 1).ToLower()}{word.Substring(1)}" : word; + return word.Length > 0 ? $"{word.Substring(0, 1).ToLower()}{word.Substring(1)}" : word; } /// @@ -111,21 +112,21 @@ public static partial class InflectorExtensions /// /// /// - public static string? Dasherize(this string underscoredWord) => underscoredWord.Replace('_', '-'); + public static string Dasherize(this string underscoredWord) => underscoredWord.Replace('_', '-'); /// /// Replaces underscores with hyphens in the string /// /// /// - public static string? Hyphenate(this string underscoredWord) => underscoredWord.Dasherize(); + public static string Hyphenate(this string underscoredWord) => underscoredWord.Dasherize(); /// /// Separates the input words with hyphens and all the words are converted to lowercase /// /// /// - public static string? Kebaberize(this string input) => input.Underscore()?.Dasherize(); + public static string Kebaberize(this string input) => input.Underscore().Dasherize(); [GeneratedRegex("(?:^|_| +)(.)")] private static partial Regex PascalizeRegex(); diff --git a/src/MyNet.Humanizer/MyNet.Humanizer.csproj b/src/MyNet.Humanizer/MyNet.Humanizer.csproj index ccec6f5..c671008 100644 --- a/src/MyNet.Humanizer/MyNet.Humanizer.csproj +++ b/src/MyNet.Humanizer/MyNet.Humanizer.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/MyNet.Humanizer/NumberHumanizeExtensions.cs b/src/MyNet.Humanizer/NumberHumanizeExtensions.cs index 0dc5b12..fce586c 100644 --- a/src/MyNet.Humanizer/NumberHumanizeExtensions.cs +++ b/src/MyNet.Humanizer/NumberHumanizeExtensions.cs @@ -4,7 +4,6 @@ using System; using System.Globalization; using MyNet.Utilities.Extensions; -using MyNet.Humanizer.Inflections; using MyNet.Utilities; namespace MyNet.Humanizer @@ -16,36 +15,36 @@ public static class NumberHumanizeExtensions static NumberHumanizeExtensions() => ResourceLocator.Initialize(); - public static string? Humanize(this T value, TUnit unit, TUnit? minUnit = default, TUnit? maxUnit = default, bool abbreviation = true, string? format = null) + public static string? Humanize(this T value, TUnit unit, TUnit? minUnit = default, TUnit? maxUnit = default, bool abbreviation = true, string? format = null, CultureInfo? culture = null) where T : struct, IComparable where TUnit : Enum - => Humanize(value, unit.GetType(), unit, minUnit, maxUnit, abbreviation, format); + => Humanize(value, unit.GetType(), unit, minUnit, maxUnit, abbreviation, format, culture); - public static string? Humanize(this T value, Type type, Enum unit, Enum? minUnit = null, Enum? maxUnit = null, bool abbreviation = true, string? format = null) + public static string? Humanize(this T value, Type type, Enum unit, Enum? minUnit = null, Enum? maxUnit = null, bool abbreviation = true, string? format = null, CultureInfo? culture = null) where T : struct, IComparable { var (newValue, newUnit) = value.Simplify(type, unit, minUnit, maxUnit); - return Humanize(newValue, type, newUnit, abbreviation, format); + return Humanize(newValue, type, newUnit, abbreviation, format, culture); } - public static string? Humanize(this T value, TUnit unit, bool abbreviation = true, string? format = null) + public static string? Humanize(this T value, TUnit unit, bool abbreviation = true, string? format = null, CultureInfo? culture = null) where T : struct, IComparable where TUnit : Enum - => Humanize(value, unit.GetType(), unit, abbreviation, format); + => Humanize(value, unit.GetType(), unit, abbreviation, format, culture); - public static string? Humanize(this T value, Type type, Enum unit, bool abbreviation = true, string? format = null) + public static string? Humanize(this T value, Type type, Enum unit, bool abbreviation = true, string? format = null, CultureInfo? culture = null) where T : struct, IComparable { if (double.TryParse(value.ToString(), out var dbl)) { var input = type.Name + unit; var isInt = value is int; - var transformedInput = input.Translate(abbreviation); + var transformedInput = abbreviation ? input.TranslateAbbreviated(culture) : input.Translate(culture); if (!abbreviation) { - var isPlural = CultureInfo.CurrentCulture.GetProvider()?.IsPlural(dbl); - transformedInput = isPlural != null && !isPlural.Value + var isPlural = dbl.IsPlural(culture); + transformedInput = !isPlural ? transformedInput?.Singularize(inputIsKnownToBePlural: false) : transformedInput?.Pluralize(inputIsKnownToBeSingular: false); } diff --git a/src/MyNet.Humanizer/OrdinalizeExtensions.cs b/src/MyNet.Humanizer/OrdinalizeExtensions.cs index b7ede9f..913f600 100644 --- a/src/MyNet.Humanizer/OrdinalizeExtensions.cs +++ b/src/MyNet.Humanizer/OrdinalizeExtensions.cs @@ -2,7 +2,6 @@ // See the LICENSE file in the project root for more information. using System.Globalization; -using MyNet.Utilities.Extensions; using MyNet.Humanizer.Ordinalizing; using MyNet.Utilities.Localization; @@ -15,31 +14,13 @@ public static class OrdinalizeExtensions { static OrdinalizeExtensions() => ResourceLocator.Initialize(); - /// - /// Turns a number into an ordinal string used to denote the position in an ordered sequence such as 1st, 2nd, 3rd, 4th. - /// - /// The number, in string, to be ordinalized - /// - public static string? Ordinalize(this string numberString) => Ordinalize(numberString, CultureInfo.CurrentCulture); - /// /// Turns a number into an ordinal string used to denote the position in an ordered sequence such as 1st, 2nd, 3rd, 4th. /// /// The number, in string, to be ordinalized /// Culture to use. If null, current thread's UI culture is used. /// - public static string? Ordinalize(this string numberString, CultureInfo culture) => culture.GetProvider()?.Convert(int.Parse(numberString, culture), numberString); - - /// - /// Turns a number into an ordinal string used to denote the position in an ordered sequence such as 1st, 2nd, 3rd, 4th. - /// Gender for Brazilian Portuguese locale - /// "1".Ordinalize(GrammaticalGender.Masculine) -> "1º" - /// "1".Ordinalize(GrammaticalGender.Feminine) -> "1ª" - /// - /// The number, in string, to be ordinalized - /// The grammatical gender to use for output words - /// - public static string? Ordinalize(this string numberString, GrammaticalGender gender) => Ordinalize(numberString, gender, CultureInfo.CurrentCulture); + public static string Ordinalize(this string numberString, CultureInfo? culture = null) => LocalizationService.GetOrCurrent(culture)?.Convert(int.Parse(numberString, culture), numberString) ?? string.Empty; /// /// Turns a number into an ordinal string used to denote the position in an ordered sequence such as 1st, 2nd, 3rd, 4th. @@ -51,14 +32,7 @@ public static class OrdinalizeExtensions /// The grammatical gender to use for output words /// Culture to use. If null, current thread's UI culture is used. /// - public static string? Ordinalize(this string numberString, GrammaticalGender gender, CultureInfo culture) => culture.GetProvider()?.Convert(int.Parse(numberString, culture), numberString, gender); - - /// - /// Turns a number into an ordinal number used to denote the position in an ordered sequence such as 1st, 2nd, 3rd, 4th. - /// - /// The number to be ordinalized - /// - public static string? Ordinalize(this int number) => Ordinalize(number, CultureInfo.CurrentCulture); + public static string Ordinalize(this string numberString, GrammaticalGender gender, CultureInfo? culture = null) => LocalizationService.GetOrCurrent(culture)?.Convert(int.Parse(numberString, culture), numberString, gender) ?? string.Empty; /// /// Turns a number into an ordinal number used to denote the position in an ordered sequence such as 1st, 2nd, 3rd, 4th. @@ -66,18 +40,7 @@ public static class OrdinalizeExtensions /// The number to be ordinalized /// Culture to use. If null, current thread's UI culture is used. /// - public static string? Ordinalize(this int number, CultureInfo culture) => culture.GetProvider()?.Convert(number, number.ToString(culture)); - - /// - /// Turns a number into an ordinal number used to denote the position in an ordered sequence such as 1st, 2nd, 3rd, 4th. - /// Gender for Brazilian Portuguese locale - /// 1.Ordinalize(GrammaticalGender.Masculine) -> "1º" - /// 1.Ordinalize(GrammaticalGender.Feminine) -> "1ª" - /// - /// The number to be ordinalized - /// The grammatical gender to use for output words - /// - public static string? Ordinalize(this int number, GrammaticalGender gender) => TranslationService.Current.GetProvider()?.Convert(number, number.ToString(CultureInfo.InvariantCulture), gender); + public static string Ordinalize(this int number, CultureInfo? culture = null) => LocalizationService.GetOrCurrent(culture)?.Convert(number, number.ToString(culture)) ?? string.Empty; /// /// Turns a number into an ordinal number used to denote the position in an ordered sequence such as 1st, 2nd, 3rd, 4th. @@ -89,6 +52,6 @@ public static class OrdinalizeExtensions /// The grammatical gender to use for output words /// Culture to use. If null, current thread's UI culture is used. /// - public static string? Ordinalize(this int number, GrammaticalGender gender, CultureInfo culture) => culture.GetProvider()?.Convert(number, number.ToString(culture), gender); + public static string Ordinalize(this int number, GrammaticalGender gender, CultureInfo? culture = null) => LocalizationService.GetOrCurrent(culture)?.Convert(number, number.ToString(culture), gender) ?? string.Empty; } } diff --git a/src/MyNet.Humanizer/ResourceLocator.cs b/src/MyNet.Humanizer/ResourceLocator.cs index 08f09ba..030c0d1 100644 --- a/src/MyNet.Humanizer/ResourceLocator.cs +++ b/src/MyNet.Humanizer/ResourceLocator.cs @@ -20,16 +20,16 @@ public static void Initialize() TranslationService.RegisterResources(nameof(DateHumanizeResources), DateHumanizeResources.ResourceManager); TranslationService.RegisterResources(nameof(EnumHumanizeResources), EnumHumanizeResources.ResourceManager); - TranslationService.AddDefaultProvider(); - TranslationService.AddDefaultProvider(); + LocalizationService.Register(); + LocalizationService.Register(); - _ = TranslationService.Get(Cultures.English).AddProvider() - .AddProvider() - .AddProvider(); + LocalizationService.Register(Cultures.English); + LocalizationService.Register(Cultures.English); + LocalizationService.Register(Cultures.English); - _ = TranslationService.Get(Cultures.French).AddProvider() - .AddProvider() - .AddProvider(); + LocalizationService.Register(Cultures.French); + LocalizationService.Register(Cultures.French); + LocalizationService.Register(Cultures.French); _isInitialized = true; } diff --git a/src/MyNet.Humanizer/StringHumanizeExtensions.cs b/src/MyNet.Humanizer/StringHumanizeExtensions.cs index b914d70..982b3df 100644 --- a/src/MyNet.Humanizer/StringHumanizeExtensions.cs +++ b/src/MyNet.Humanizer/StringHumanizeExtensions.cs @@ -12,6 +12,7 @@ namespace MyNet.Humanizer /// public static class StringHumanizeExtensions { + private static readonly Regex PascalCaseWordPartsRegex = new(@"[\p{Lu}]?[\p{Ll}]+|[0-9]+[\p{Ll}]*|[\p{Lu}]+(?=[\p{Lu}][\p{Ll}]|[0-9]|\b)|[\p{Lo}]+", RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture | RegexOptionsUtil.Compiled); diff --git a/src/MyNet.Humanizer/TimeSpanHumanizeExtensions.cs b/src/MyNet.Humanizer/TimeSpanHumanizeExtensions.cs index b7b3790..fa4fdd3 100644 --- a/src/MyNet.Humanizer/TimeSpanHumanizeExtensions.cs +++ b/src/MyNet.Humanizer/TimeSpanHumanizeExtensions.cs @@ -5,10 +5,10 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using MyNet.Utilities.Extensions; using MyNet.Humanizer.DateTimes; -using MyNet.Utilities.Units; using MyNet.Utilities; +using MyNet.Utilities.Localization; +using MyNet.Utilities.Units; namespace MyNet.Humanizer { @@ -17,36 +17,8 @@ namespace MyNet.Humanizer /// public static class TimeSpanHumanizeExtensions { - static TimeSpanHumanizeExtensions() => ResourceLocator.Initialize(); - /// - /// Turns a TimeSpan into a human readable form. E.g. 1 day. - /// - /// - /// The maximum number of time units to return. Defaulted is 1 which means the largest unit is returned - /// The maximum unit of time to output. The default value is . The time units and will give approximations for time spans bigger 30 days by calculating with 365.2425 days a year and 30.4369 days a month. - /// The minimum unit of time to output. - /// The separator to use when combining humanized time parts. If null, the default collection formatter for the current culture is used. - /// - /// - public static string? Humanize(this TimeSpan timeSpan, int precision = 1, TimeUnit maxUnit = TimeUnit.Year, TimeUnit minUnit = TimeUnit.Millisecond, string collectionSeparator = ", ", string? lastSeparator = null) - => Humanize(timeSpan, CultureInfo.CurrentCulture, precision, maxUnit, minUnit, collectionSeparator, lastSeparator); - - /// - /// Turns a TimeSpan into a human readable form. E.g. 1 day. - /// - /// - /// The maximum number of time units to return. - /// Controls whether empty time units should be counted towards maximum number of time units. Leading empty time units never count. - /// The maximum unit of time to output. The default value is . The time units and will give approximations for time spans bigger than 30 days by calculating with 365.2425 days a year and 30.4369 days a month. - /// The minimum unit of time to output. - /// The separator to use when combining humanized time parts. If null, the default collection formatter for the current culture is used. - /// - /// - public static string? Humanize(this TimeSpan timeSpan, int precision, bool countEmptyUnits, TimeUnit maxUnit = TimeUnit.Year, TimeUnit minUnit = TimeUnit.Millisecond, string collectionSeparator = ", ", string? lastSeparator = null) - => Humanize(timeSpan, CultureInfo.CurrentCulture, precision, countEmptyUnits, maxUnit, minUnit, collectionSeparator, lastSeparator); - /// /// Turns a TimeSpan into a human readable form. E.g. 1 day. /// @@ -58,7 +30,7 @@ public static class TimeSpanHumanizeExtensions /// The separator to use when combining humanized time parts. If null, the default collection formatter for the current culture is used. /// /// - public static string? Humanize(this TimeSpan timeSpan, CultureInfo culture, int precision = 1, TimeUnit maxUnit = TimeUnit.Year, TimeUnit minUnit = TimeUnit.Millisecond, string collectionSeparator = ", ", string? lastSeparator = null) => timeSpan.Humanize(culture, precision, false, maxUnit, minUnit, collectionSeparator, lastSeparator); + public static string? Humanize(this TimeSpan timeSpan, int precision = 1, TimeUnit maxUnit = TimeUnit.Year, TimeUnit minUnit = TimeUnit.Millisecond, string collectionSeparator = ", ", string? lastSeparator = null, CultureInfo? culture = null) => timeSpan.Humanize(precision, false, maxUnit, minUnit, collectionSeparator, lastSeparator, culture); /// /// Turns a TimeSpan into a human readable form. E.g. 1 day. @@ -72,17 +44,17 @@ public static class TimeSpanHumanizeExtensions /// The separator to use when combining humanized time parts. If null, the default collection formatter for the current culture is used. /// /// - public static string? Humanize(this TimeSpan timeSpan, CultureInfo culture, int precision, bool countEmptyUnits, TimeUnit maxUnit = TimeUnit.Year, TimeUnit minUnit = TimeUnit.Millisecond, string collectionSeparator = ", ", string? lastSeparator = null) + public static string? Humanize(this TimeSpan timeSpan, int precision, bool countEmptyUnits, TimeUnit maxUnit = TimeUnit.Year, TimeUnit minUnit = TimeUnit.Millisecond, string collectionSeparator = ", ", string? lastSeparator = null, CultureInfo? culture = null) { - IEnumerable timeParts = CreateTheTimePartsWithUpperAndLowerLimits(timeSpan, culture, maxUnit, minUnit); + IEnumerable timeParts = CreateTheTimePartsWithUpperAndLowerLimits(timeSpan, maxUnit, minUnit, culture); timeParts = SetPrecisionOfTimeSpan(timeParts, precision, countEmptyUnits); return ConcatenateTimeSpanParts(timeParts, collectionSeparator, lastSeparator); } - private static List CreateTheTimePartsWithUpperAndLowerLimits(TimeSpan timespan, CultureInfo culture, TimeUnit maxUnit, TimeUnit minUnit) + private static List CreateTheTimePartsWithUpperAndLowerLimits(TimeSpan timespan, TimeUnit maxUnit, TimeUnit minUnit, CultureInfo? culture = null) { - var cultureFormatter = culture.GetProvider(); + var cultureFormatter = LocalizationService.GetOrCurrent(culture); var firstValueFound = false; var timeUnitsEnumTypes = GetEnumTypesForTimeUnit(); var timeParts = new List(); diff --git a/src/MyNet.Humanizer/ToQuantityExtensions.cs b/src/MyNet.Humanizer/ToQuantityExtensions.cs index 3704288..3bb8eaa 100644 --- a/src/MyNet.Humanizer/ToQuantityExtensions.cs +++ b/src/MyNet.Humanizer/ToQuantityExtensions.cs @@ -3,8 +3,6 @@ using System; using System.Globalization; -using MyNet.Utilities.Extensions; -using MyNet.Humanizer.Inflections; namespace MyNet.Humanizer { @@ -82,8 +80,8 @@ public static class ToQuantityExtensions private static string? ToQuantity(this string input, long quantity, ShowQuantityAs showQuantityAs = ShowQuantityAs.Numeric, string? format = null, IFormatProvider? formatProvider = null) { - var isPlural = CultureInfo.CurrentCulture.GetProvider()?.IsPlural(quantity); - var transformedInput = isPlural != null && !isPlural.Value + var isPlural = quantity.IsPlural(formatProvider as CultureInfo); + var transformedInput = !isPlural ? input.Singularize(inputIsKnownToBePlural: false) : input.Pluralize(inputIsKnownToBeSingular: false); @@ -109,8 +107,8 @@ public static class ToQuantityExtensions /// public static string? ToQuantity(this string input, double quantity, string? format = null, IFormatProvider? formatProvider = null) { - var isPlural = CultureInfo.CurrentCulture.GetProvider()?.IsPlural(quantity); - var transformedInput = isPlural != null && !isPlural.Value + var isPlural = quantity.IsPlural(formatProvider as CultureInfo); + var transformedInput = !isPlural ? input.Singularize(inputIsKnownToBePlural: false) : input.Pluralize(inputIsKnownToBeSingular: false); diff --git a/src/MyNet.Humanizer/Transformer/IStringTransformer.cs b/src/MyNet.Humanizer/Transformer/IStringTransformer.cs index 087190e..2bd791b 100644 --- a/src/MyNet.Humanizer/Transformer/IStringTransformer.cs +++ b/src/MyNet.Humanizer/Transformer/IStringTransformer.cs @@ -1,6 +1,8 @@ // Copyright (c) Stéphane ANDRE. All Right Reserved. // See the LICENSE file in the project root for more information. +using System.Globalization; + namespace MyNet.Humanizer.Transformer { /// @@ -12,7 +14,8 @@ public interface IStringTransformer /// Transform the input /// /// String to be transformed + /// /// - string Transform(string input); + string Transform(string input, CultureInfo culture); } } diff --git a/src/MyNet.Humanizer/Transformer/To.cs b/src/MyNet.Humanizer/Transformer/To.cs index 8740334..7f6cad4 100644 --- a/src/MyNet.Humanizer/Transformer/To.cs +++ b/src/MyNet.Humanizer/Transformer/To.cs @@ -1,7 +1,9 @@ // Copyright (c) Stéphane ANDRE. All Right Reserved. // See the LICENSE file in the project root for more information. +using System.Globalization; using System.Linq; +using MyNet.Utilities.Localization; namespace MyNet.Humanizer.Transformer { @@ -14,9 +16,10 @@ public static class To /// Transforms a string using the provided transformers. Transformations are applied in the provided order. /// /// + /// /// /// - public static string Transform(this string input, params IStringTransformer[] transformers) => transformers.Aggregate(input, (current, stringTransformer) => stringTransformer.Transform(current)); + public static string Transform(this string input, CultureInfo? culture = null, params IStringTransformer[] transformers) => transformers.Aggregate(input, (current, stringTransformer) => stringTransformer.Transform(current, culture ?? GlobalizationService.Current.Culture)); /// /// Changes string to title case diff --git a/src/MyNet.Humanizer/Transformer/ToLowerCase.cs b/src/MyNet.Humanizer/Transformer/ToLowerCase.cs index 60f4eae..8a987e3 100644 --- a/src/MyNet.Humanizer/Transformer/ToLowerCase.cs +++ b/src/MyNet.Humanizer/Transformer/ToLowerCase.cs @@ -7,6 +7,6 @@ namespace MyNet.Humanizer.Transformer { internal class ToLowerCase : IStringTransformer { - public string Transform(string input) => CultureInfo.CurrentCulture.TextInfo.ToLower(input); + public string Transform(string input, CultureInfo culture) => culture.TextInfo.ToLower(input); } } diff --git a/src/MyNet.Humanizer/Transformer/ToSentenceCase.cs b/src/MyNet.Humanizer/Transformer/ToSentenceCase.cs index 354b749..e6efd3d 100644 --- a/src/MyNet.Humanizer/Transformer/ToSentenceCase.cs +++ b/src/MyNet.Humanizer/Transformer/ToSentenceCase.cs @@ -7,6 +7,6 @@ namespace MyNet.Humanizer.Transformer { internal class ToSentenceCase : IStringTransformer { - public string Transform(string input) => input.Length >= 1 ? string.Concat(input.Substring(0, 1).ToUpper(CultureInfo.CurrentCulture), input.Substring(1)) : input.ToUpper(CultureInfo.CurrentCulture); + public string Transform(string input, CultureInfo culture) => input.Length >= 1 ? string.Concat(input.Substring(0, 1).ToUpper(culture), input.Substring(1)) : input.ToUpper(culture); } } diff --git a/src/MyNet.Humanizer/Transformer/ToTitleCase.cs b/src/MyNet.Humanizer/Transformer/ToTitleCase.cs index 5d93319..f95a828 100644 --- a/src/MyNet.Humanizer/Transformer/ToTitleCase.cs +++ b/src/MyNet.Humanizer/Transformer/ToTitleCase.cs @@ -2,13 +2,14 @@ // See the LICENSE file in the project root for more information. using System; +using System.Globalization; using System.Text.RegularExpressions; namespace MyNet.Humanizer.Transformer { internal partial class ToTitleCase : IStringTransformer { - public string Transform(string input) + public string Transform(string input, CultureInfo culture) { var result = input; diff --git a/src/MyNet.Humanizer/Transformer/ToUpperCase.cs b/src/MyNet.Humanizer/Transformer/ToUpperCase.cs index e04ae32..fee78f4 100644 --- a/src/MyNet.Humanizer/Transformer/ToUpperCase.cs +++ b/src/MyNet.Humanizer/Transformer/ToUpperCase.cs @@ -1,10 +1,12 @@ // Copyright (c) Stéphane ANDRE. All Right Reserved. // See the LICENSE file in the project root for more information. +using System.Globalization; + namespace MyNet.Humanizer.Transformer { internal class ToUpperCase : IStringTransformer { - public string Transform(string input) => input.ToUpper(); + public string Transform(string input, CultureInfo culture) => input.ToUpper(); } } diff --git a/src/MyNetHumanizer.sln b/src/MyNetHumanizer.sln index 917296b..c65f33a 100644 --- a/src/MyNetHumanizer.sln +++ b/src/MyNetHumanizer.sln @@ -8,6 +8,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_config", "_config", "{5D1B ..\.editorconfig = ..\.editorconfig Directory.build.props = Directory.build.props MyNetHumanizer.ruleset = MyNetHumanizer.ruleset + nuget.config = nuget.config EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyNet.Humanizer", "MyNet.Humanizer\MyNet.Humanizer.csproj", "{DA8D78E2-C629-473D-BADB-03259DFACDB3}" @@ -25,12 +26,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_doc", "_doc", "{EA1558C3-C EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_github", "_github", "{E1FDB1EB-FB63-409E-8962-2929AD1EE63C}" ProjectSection(SolutionItems) = preProject - ..\.github\workflows\create_release.yml = ..\.github\workflows\create_release.yml ..\.github\workflows\ci.yml = ..\.github\workflows\ci.yml + ..\.github\workflows\create_release.yml = ..\.github\workflows\create_release.yml ..\.github\GitVersion.yml = ..\.github\GitVersion.yml ..\.github\workflows\git_version.yml = ..\.github\workflows\git_version.yml - ..\.github\workflows\publish.yml = ..\.github\workflows\publish.yml ..\.github\workflows\publish_release.yml = ..\.github\workflows\publish_release.yml + ..\.github\workflows\publish_private.yml = ..\.github\workflows\publish_private.yml EndProjectSection EndProject Global