Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions .docfx/api/namespaces/Cuemon.Extensions.Globalization.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
uid: Cuemon.Extensions.Globalization
summary: *content
---
The `Cuemon.Extensions.Globalization` namespace contains extension methods that complements the `Cuemon.Globalization` namespace while being an addition to the `System.Globalization` namespace.
The `Cuemon.Extensions.Globalization` namespace contains extension methods that is an addition to the `System.Globalization` namespace.

[!INCLUDE [availability-default](../../includes/availability-default.md)]

Complements: [Cuemon.Globalization namespace](/api/dotnet/Cuemon.Globalization.html) 📘
Complements: [System.Globalization namespace](https://docs.microsoft.com/en-us/dotnet/api/system.globalization) 🔗

### Extension Methods

Expand All @@ -15,10 +15,11 @@ Complements: [Cuemon.Globalization namespace](/api/dotnet/Cuemon.Globalization.h
|CultureInfo|⬇️|`UseNationalLanguageSupport`|

### CSharp Example

```csharp
var danishCultureIcu = CultureInfo("da-dk");
var danishCultureNls = new CultureInfo("da-dk").UseNationalLanguageSupport();
var danishCultureIcu = new CultureInfo("da-dk", false);
var danishCultureNls = new CultureInfo("da-dk", false).UseNationalLanguageSupport();

// danishCultureIcu outputs dd.MM.yyyy from danishCultureIcu.DateTimeFormat.ShortDatePattern
// danishCultureNls outputs dd-MM-yyyy from danishCultureNls.DateTimeFormat.ShortDatePattern
```
```
14 changes: 13 additions & 1 deletion .nuget/Cuemon.Extensions.Globalization/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,24 @@ It is, by heart, free, flexible and built to extend and boost your agile codebel

## **Cuemon.Extensions.Globalization** for .NET

The `Cuemon.Extensions.Globalization` namespace contains extension methods that complements the Cuemon.Globalization namespace while being an addition to the System.Globalization namespace.
The `Cuemon.Extensions.Globalization` namespace contains extension methods that is an addition to the `System.Globalization` namespace.

It aims to provide a way to favor National Language Support (NLS) over International Components for Unicode (ICU).

More documentation available at our documentation site:

- [Cuemon for .NET documentation](https://docs.cuemon.net/api/extensions/dotnet/Cuemon.Extensions.Globalization.html) 🔗

### CSharp Example

```csharp
var danishCultureIcu = new CultureInfo("da-dk", false);
var danishCultureNls = new CultureInfo("da-dk", false).UseNationalLanguageSupport();

// danishCultureIcu outputs dd.MM.yyyy from danishCultureIcu.DateTimeFormat.ShortDatePattern
// danishCultureNls outputs dd-MM-yyyy from danishCultureNls.DateTimeFormat.ShortDatePattern
```

## Related Packages

* [Cuemon.AspNetCore](https://www.nuget.org/packages/Cuemon.AspNetCore/) 📦
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1204,9 +1204,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Cuemon.Core\Cuemon.Core.csproj" />
<ProjectReference Include="..\Cuemon.Extensions.Reflection\Cuemon.Extensions.Reflection.csproj" />
<ProjectReference Include="..\Cuemon.Extensions.IO\Cuemon.Extensions.IO.csproj" />
</ItemGroup>

</Project>
19 changes: 15 additions & 4 deletions src/Cuemon.Extensions.Globalization/CultureInfoExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,26 @@ public static IEnumerable<CultureInfo> UseNationalLanguageSupport(this IEnumerab
{
var surrogate = typeof(CultureInfoExtensions).GetEmbeddedResources($"{culture.Name}.bin", ManifestResourceMatch.ContainsName).SingleOrDefault();
var ms = new MemoryStream(surrogate.Value.DecompressGZip().ToByteArray());
var suggogateCulture = YamlFormatter.DeserializeObject<CultureInfoSurrogate>(ms, o =>
var surrogateCulture = YamlFormatter.DeserializeObject<CultureInfoSurrogate>(ms, o =>
{
o.Settings.NamingConvention = NullNamingConvention.Instance;
o.Settings.ReflectionRules = new MemberReflection();
o.Settings.IndentSequences = false;
});
Enrich(culture, suggogateCulture);
EnrichedCultureInfos.Add(culture);
enrichedCultures.Add(culture);

Comment on lines +62 to +68
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Dispose MemoryStream to Prevent Memory Leaks

The MemoryStream instance ms is not being disposed, which can lead to memory leaks. Consider wrapping it in a using statement to ensure it is properly disposed after use.

Apply this diff to address the issue:

-var ms = new MemoryStream(surrogate.Value.DecompressGZip().ToByteArray());
+using (var ms = new MemoryStream(surrogate.Value.DecompressGZip().ToByteArray()))
+{
     var surrogateCulture = YamlFormatter.DeserializeObject<CultureInfoSurrogate>(ms, o =>
     {
         o.Settings.NamingConvention = NullNamingConvention.Instance;
         o.Settings.ReflectionRules = new MemberReflection();
         o.Settings.IndentSequences = false;
     });
+}

Committable suggestion was skipped due to low confidence.

if (culture.IsReadOnly)
{
var cultureClone = culture.Clone() as CultureInfo;
Enrich(cultureClone, surrogateCulture);
EnrichedCultureInfos.Add(cultureClone);
enrichedCultures.Add(cultureClone);
}
else
{
Enrich(culture, surrogateCulture);
EnrichedCultureInfos.Add(culture);
enrichedCultures.Add(culture);
}
Comment on lines +69 to +81
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Prevent Duplicate Entries in EnrichedCultureInfos

There's a potential for duplicate entries in EnrichedCultureInfos if the same culture is processed multiple times. This can lead to unnecessary memory consumption and redundant data.

Modify the code to check for existing entries before adding:

 if (culture.IsReadOnly)
 {
     var cultureClone = culture.Clone() as CultureInfo;
     Enrich(cultureClone, surrogateCulture);
-    EnrichedCultureInfos.Add(cultureClone);
+    if (!EnrichedCultureInfos.Any(ci => ci.Name.Equals(cultureClone.Name, StringComparison.Ordinal)))
+    {
+        EnrichedCultureInfos.Add(cultureClone);
+    }
     enrichedCultures.Add(cultureClone);
 }
 else
 {
     Enrich(culture, surrogateCulture);
-    EnrichedCultureInfos.Add(culture);
+    if (!EnrichedCultureInfos.Any(ci => ci.Name.Equals(culture.Name, StringComparison.Ordinal)))
+    {
+        EnrichedCultureInfos.Add(culture);
+    }
     enrichedCultures.Add(culture);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (culture.IsReadOnly)
{
var cultureClone = culture.Clone() as CultureInfo;
Enrich(cultureClone, surrogateCulture);
EnrichedCultureInfos.Add(cultureClone);
enrichedCultures.Add(cultureClone);
}
else
{
Enrich(culture, surrogateCulture);
EnrichedCultureInfos.Add(culture);
enrichedCultures.Add(culture);
}
if (culture.IsReadOnly)
{
var cultureClone = culture.Clone() as CultureInfo;
Enrich(cultureClone, surrogateCulture);
if (!EnrichedCultureInfos.Any(ci => ci.Name.Equals(cultureClone.Name, StringComparison.Ordinal)))
{
EnrichedCultureInfos.Add(cultureClone);
}
enrichedCultures.Add(cultureClone);
}
else
{
Enrich(culture, surrogateCulture);
if (!EnrichedCultureInfos.Any(ci => ci.Name.Equals(culture.Name, StringComparison.Ordinal)))
{
EnrichedCultureInfos.Add(culture);
}
enrichedCultures.Add(culture);
}

🛠️ Refactor suggestion

Optional: Preserve Read-Only State After Enrichment

If it's important to maintain the read-only state of the original CultureInfo, consider making the enriched clone read-only after enrichment. This can be achieved using the CultureInfo.ReadOnly method.

Apply this change to preserve the read-only state:

 var cultureClone = culture.Clone() as CultureInfo;
 Enrich(cultureClone, surrogateCulture);
+var readOnlyCultureClone = CultureInfo.ReadOnly(cultureClone);
-EnrichedCultureInfos.Add(cultureClone);
+EnrichedCultureInfos.Add(readOnlyCultureClone);
-enrichedCultures.Add(cultureClone);
+enrichedCultures.Add(readOnlyCultureClone);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (culture.IsReadOnly)
{
var cultureClone = culture.Clone() as CultureInfo;
Enrich(cultureClone, surrogateCulture);
EnrichedCultureInfos.Add(cultureClone);
enrichedCultures.Add(cultureClone);
}
else
{
Enrich(culture, surrogateCulture);
EnrichedCultureInfos.Add(culture);
enrichedCultures.Add(culture);
}
if (culture.IsReadOnly)
{
var cultureClone = culture.Clone() as CultureInfo;
Enrich(cultureClone, surrogateCulture);
var readOnlyCultureClone = CultureInfo.ReadOnly(cultureClone);
EnrichedCultureInfos.Add(readOnlyCultureClone);
enrichedCultures.Add(readOnlyCultureClone);
}
else
{
Enrich(culture, surrogateCulture);
EnrichedCultureInfos.Add(culture);
enrichedCultures.Add(culture);
}

⚠️ Potential issue

Ensure Thread Safety When Modifying Shared Collections

The static list EnrichedCultureInfos is being modified without synchronization, which may cause race conditions in multi-threaded environments. To prevent potential concurrency issues, consider using thread-safe collections or implementing locking mechanisms.

One way to address this is by replacing EnrichedCultureInfos with a thread-safe collection like ConcurrentBag<CultureInfo>:

-private static readonly List<CultureInfo> EnrichedCultureInfos = new();
+private static readonly ConcurrentBag<CultureInfo> EnrichedCultureInfos = new();

...

                 if (enrichedCulture != null)
                 {
                     enrichedCultures.Add(enrichedCulture);
                 }
                 else
                 {
                     // Existing code
-                    EnrichedCultureInfos.Add(cultureClone);
+                    EnrichedCultureInfos.Add(cultureClone);
                     enrichedCultures.Add(cultureClone);
                 }

Alternatively, implement a locking mechanism:

+private static readonly object EnrichedCultureInfosLock = new();

...

                 if (enrichedCulture != null)
                 {
                     enrichedCultures.Add(enrichedCulture);
                 }
                 else
                 {
+                    lock (EnrichedCultureInfosLock)
+                    {
                         // Existing code
                         EnrichedCultureInfos.Add(cultureClone);
+                    }
                     enrichedCultures.Add(cultureClone);
                 }

Committable suggestion was skipped due to low confidence.

}
}
return enrichedCultures;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,41 @@
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using Codebelt.Extensions.Xunit;
using Cuemon.Globalization;
using Xunit;
using Xunit.Abstractions;

namespace Cuemon.Extensions.Globalization
{
public class CultureInfoExtensionsTest : Test
{
public CultureInfoExtensionsTest(ITestOutputHelper output) : base(output)
{
}

[Fact]
public void UseNationalLanguageSupport_ShouldHaveDifferentFormattingAsWindowsVariant()
{
var sut1 = new CultureInfo("da-DK", false);
var sut2 = (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
? new CultureInfo("da-DK") // Linux uses ICU
: new CultureInfo("da-DK", false) // Ensure we do not read from user culture settings on Windows
).UseNationalLanguageSupport();

Assert.NotEqual(sut1.DateTimeFormat, sut2.DateTimeFormat);
Assert.NotEqual(sut1.NumberFormat, sut2.NumberFormat);
#if NET48_OR_GREATER
Assert.Equal(sut1.DateTimeFormat.ShortDatePattern, sut2.DateTimeFormat.ShortDatePattern);
#else
Assert.Equal("dd.MM.yyyy", sut1.DateTimeFormat.ShortDatePattern);
Assert.Equal("dd-MM-yyyy", sut2.DateTimeFormat.ShortDatePattern);
#endif
}

[Fact]
public void MergeWithOriginal_ShouldHaveDifferentFormattingAsWindowsVariant()
public void UseNationalLanguageSupport_ShouldHaveDifferentFormattingAsWindowsVariant_FromReadOnlyCultureInfos()
{
var sut1 = World.GetCultures(new RegionInfo("DK")).Single(ci => ci.Name == "da-DK");
var sut2 = new CultureInfo("da-dk").UseNationalLanguageSupport();
var sut1 = CultureInfo.GetCultureInfo("da-DK");
var sut2 = CultureInfo.GetCultureInfo("da-DK").UseNationalLanguageSupport();

Assert.NotEqual(sut1.DateTimeFormat, sut2.DateTimeFormat);
Assert.NotEqual(sut1.NumberFormat, sut2.NumberFormat);
Expand Down