diff --git a/Readme.md b/Readme.md index 6d0f88a..a004034 100644 --- a/Readme.md +++ b/Readme.md @@ -3,8 +3,10 @@ ## What is ResourceString.Net? ResourceString.Net is a powerful .NET library that allows you to work with string resources in a type-safe manner. -It leverages the `resx` file in your project and utilizes a [c# source code generator](https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview) to create a comprehensive API. +It leverages the `resx` file in your project and utilizes a [c# source code generator](https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview) to create a comprehensive API. No [`Designer.cs`](https://learn.microsoft.com/en-us/dotnet/framework/tools/resgen-exe-resource-file-generator?source=recommendations) or [`T4 Text Templates`](https://learn.microsoft.com/en-us/visualstudio/modeling/code-generation-and-t4-text-templates?) filer for resources are required any more. + With ResourceString.Net, you can handle resource strings as "multi-language strings" (see [The ResourceString-Classes](#the-resourcestring-classes)) instead of built-in strings. + This provides the ability to switch languages during runtime without the need to rerun string factory methods. Additionally, ResourceString.Net ensures that formatted strings have methods with the correct number of expected parameters. @@ -81,53 +83,85 @@ namespace MyTestConsoleApp { internal static class Resources { + #region ResourceManager - #region ResourceManager + private static readonly Type _Type = typeof(Resources); + + private static readonly Lazy _ResourceManager = new Lazy( + () => new ResourceManager("MyTestConsoleApp.Resources" ?? string.Empty, _Type.Assembly), + LazyThreadSafetyMode.PublicationOnly + ); - private static readonly Type _Type = typeof(Resources); + public static ResourceManager ResourceManager => _ResourceManager.Value; + + #endregion // ResourceManager - private static readonly Lazy _ResourceManager = new Lazy( - () => new ResourceManager(_Type.FullName ?? string.Empty, _Type.Assembly), - LazyThreadSafetyMode.PublicationOnly - ); + #region Greetings - public static ResourceManager ResourceManager => _ResourceManager.Value; + internal static class Greetings + { + private static readonly Lazy LazyFormat = new Lazy( + () => new ResourceManagerString("Greetings", ResourceManager, CultureInfo.CurrentCulture), + LazyThreadSafetyMode.PublicationOnly + ); - #endregion // ResourceManager - + public static IResourceString Format => LazyFormat.Value; + + public static IResourceString From(IResourceString name) => new FormattedResourceString( + Format, + name + ); - internal static class Greetings - { - private static readonly Lazy LazyFormat = new Lazy( - () => new ResourceManagerString("Greetings", ResourceManager, CultureInfo.CurrentCulture), - LazyThreadSafetyMode.PublicationOnly - ); + } - public static IResourceString Format => LazyFormat.Value; - - public static IResourceString From(IResourceString name) => new FormattedResourceString( - Format, - name - ); - - } - - #region World + #endregion // Greetings - private static readonly Lazy LazyWorld = new Lazy( - () => new ResourceManagerString("World", ResourceManager, CultureInfo.CurrentCulture), - LazyThreadSafetyMode.PublicationOnly - ); + #region World - public static IResourceString World => LazyWorld.Value; + private static readonly Lazy LazyWorld = new Lazy( + () => new ResourceManagerString("World", ResourceManager, CultureInfo.CurrentCulture), + LazyThreadSafetyMode.PublicationOnly + ); - #endregion // World - + public static IResourceString World => LazyWorld.Value; + + #endregion // World } } ``` +### Add Multi-Languages + + +Run the following script to add a language specific resource file and add a culture change during runtime: +```sh + +echo " + + + Hallo {0} + 0 = name + + + Welt + + +" > Resources.de.resx + +echo " +Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.GetCultureInfo(\"de-DE\"); + +Console.WriteLine(message.Value); +" >> Program.cs + +dotnet run +# Expected output: Hello World \n Hallo Welt +``` + +In the code from `Program.cs` you can see there is explicit rebuild of the format string required after the culture switch. +The `IResourceString` object themselves are rebuilding the culture specific string. + ## The ResourceString-Classes ### IResourceString @@ -212,13 +246,13 @@ You can redirect the output to a file if desired. ## Third party packages -| Package | Version | -| ------------------------------------- | ------- | -| Microsoft.CodeAnalysis.Analyzers | 3.3.4 | -| Microsoft.CodeAnalysis.CSharp | 4.3.0 | -| NETStandard.Library (Auto-referenced) | 2.0.3 | -| LanguageExt.Core | 4.4.3 | -| System.Resources.Extensions | 7.0.0 | +| Package | Version | +| --------------------------------------------------------------------------------------------------- | ------- | +| [Microsoft.CodeAnalysis.Analyzers](https://www.nuget.org/packages/Microsoft.CodeAnalysis.Analyzers) | 3.3.4 | +| [Microsoft.CodeAnalysis.CSharp](https://www.nuget.org/packages/Microsoft.CodeAnalysis.CSharp) | 4.3.0 | +| [NETStandard.Library](https://www.nuget.org/packages/NETStandard.Library) | 2.0.3 | +| [LanguageExt.Core](https://www.nuget.org/packages/LanguageExt.Core) | 4.4.3 | +| [System.Resources.Extensions](https://www.nuget.org/packages/System.Resources.Extensions) | 7.0.0 | ## Development Notes diff --git a/ResourceString.Net.App.Console/Program.cs b/ResourceString.Net.App.Console/Program.cs index 54add19..f43a218 100644 --- a/ResourceString.Net.App.Console/Program.cs +++ b/ResourceString.Net.App.Console/Program.cs @@ -3,14 +3,21 @@ using ResourceString.Net.Logic.Parsers.Resx; var sourceFile = args.First(); var namespaceString = args.Skip(1).FirstOrDefault() ?? "Properties"; -var className = args.Skip(2).FirstOrDefault() ?? "Resources"; + +var resourceFileName = args.Skip(2).FirstOrDefault() ?? "Resources"; +var className = CodeSnippetFactory.TransformToClassName( + resourceFileName +); var result = Parser.TryParse(System.IO.File.ReadAllText(sourceFile)).Match( Some: v => CodeSnippetFactory.CreateResourceClassCodeSnippet( - namespaceString, + CodeSnippetFactory.TransformToNamespace(namespaceString), className, - CodeSnippetFactory.CreateResourceMangerMemberCodeSnippet(className), - v.Resources + CodeSnippetFactory.CreateResourceMangerMemberCodeSnippet( + $"{namespaceString}.{className}", + className + ), + v.Resources ), None: () => throw new InvalidOperationException() ); diff --git a/ResourceString.Net.Logic.Tests/LogicResourcesTests.cs b/ResourceString.Net.Logic.Tests/LogicResourcesTests.cs index 15970e0..608f55b 100644 --- a/ResourceString.Net.Logic.Tests/LogicResourcesTests.cs +++ b/ResourceString.Net.Logic.Tests/LogicResourcesTests.cs @@ -68,10 +68,12 @@ public class LogicResourcesTests var rm = Properties.Resources.ResourceManager; Assert.AreEqual( CodeSnippetFactory.CreateResourceMangerMemberCodeSnippet( + "Resources", "Resources" ).Value, string.Format( rm.GetString("ResourceManagerMemberTemplate") ?? string.Empty, + "Resources", "Resources" ) ); @@ -83,6 +85,7 @@ public class LogicResourcesTests var rm = Properties.Resources.ResourceManager; var rmSnippet = CodeSnippetFactory.CreateResourceMangerMemberCodeSnippet( + "Resources", "Resources" ); diff --git a/ResourceString.Net.Logic.Tests/ResxFileTests.cs b/ResourceString.Net.Logic.Tests/ResxFileTests.cs index 0a1ea55..e269693 100644 --- a/ResourceString.Net.Logic.Tests/ResxFileTests.cs +++ b/ResourceString.Net.Logic.Tests/ResxFileTests.cs @@ -36,10 +36,10 @@ namespace TestNameSpace #region ResourceManager - private static readonly Type _Type = typeof(ResourceManager); + private static readonly Type _Type = typeof(TestResourceClass); private static readonly Lazy _ResourceManager = new Lazy( - () => new ResourceManager(_Type.FullName ?? string.Empty, _Type.Assembly), + () => new ResourceManager(""ResourceManager"" ?? string.Empty, _Type.Assembly), LazyThreadSafetyMode.PublicationOnly ); @@ -175,7 +175,7 @@ namespace TestNameSpace Some: v => CodeSnippetFactory.CreateResourceClassCodeSnippet( "TestNameSpace", "TestResourceClass", - CodeSnippetFactory.CreateResourceMangerMemberCodeSnippet("ResourceManager"), + CodeSnippetFactory.CreateResourceMangerMemberCodeSnippet("ResourceManager", "TestResourceClass"), v.Resources ), None: () => throw new InvalidOperationException() diff --git a/ResourceString.Net.Logic/Factories/CodeSnippetFactory.cs b/ResourceString.Net.Logic/Factories/CodeSnippetFactory.cs index dcdcbf2..0c12ab1 100644 --- a/ResourceString.Net.Logic/Factories/CodeSnippetFactory.cs +++ b/ResourceString.Net.Logic/Factories/CodeSnippetFactory.cs @@ -2,15 +2,19 @@ using ResourceString.Net.Logic.DomainObjects.Resx; using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; namespace ResourceString.Net.Logic.Factories; internal static class CodeSnippetFactory { - public static IResourceString CreateResourceMangerMemberCodeSnippet(string typeName) + public static IResourceString CreateResourceMangerMemberCodeSnippet( + string resourceName, + string className) { return Properties.Resources.ResourceManagerMemberTemplate.From( - LiteralString.Factory(typeName) + LiteralString.Factory(resourceName), + LiteralString.Factory(className) ); } @@ -24,8 +28,7 @@ internal static class CodeSnippetFactory public static IEnumerable CreateMemberCodeSnippets( IEnumerable resources, - IResourceString resourceManagerName - ) + IResourceString resourceManagerName) { if (resources is null) { @@ -95,8 +98,7 @@ internal static class CodeSnippetFactory string namespaceString, string resourceClassName, IResourceString resourceManagerSnippet, - IEnumerable memberSnippets - ) + IEnumerable memberSnippets) { return Properties.Resources.ResourcesClassTemplate.From( LiteralString.Factory(namespaceString), @@ -123,4 +125,28 @@ internal static class CodeSnippetFactory CreateMemberCodeSnippets(resources) ); } + + public static string TransformToClassName(string input) + { + // Remove invalid characters and replace them with underscores + string sanitizedInput = Regex.Replace( + input?.Trim() ?? string.Empty, + @"[^a-zA-Z0-9_]", "_" + ); + + // Remove leading digits if the string starts with a digit + return Regex.Replace(sanitizedInput, @"^\d+", ""); + } + + public static string TransformToNamespace(string input) + { + // Remove invalid characters and replace them with dots + string sanitizedInput = Regex.Replace( + input?.Trim() ?? string.Empty, + @"[^a-zA-Z0-9_.]", "." + ); + + // Remove leading dots if present + return sanitizedInput.TrimStart('.'); + } } \ No newline at end of file diff --git a/ResourceString.Net.Logic/Properties/Resources.cs b/ResourceString.Net.Logic/Properties/Resources.cs index a901733..b565e50 100644 --- a/ResourceString.Net.Logic/Properties/Resources.cs +++ b/ResourceString.Net.Logic/Properties/Resources.cs @@ -12,18 +12,18 @@ namespace ResourceString.Net.Logic.Properties #region ResourceManager private static readonly Type _Type = typeof(Resources); - + private static readonly Lazy _ResourceManager = new Lazy( - () => new ResourceManager(_Type.FullName ?? string.Empty, _Type.Assembly), + () => new ResourceManager("ResourceString.Net.Logic.Properties.Resources" ?? string.Empty, _Type.Assembly), LazyThreadSafetyMode.PublicationOnly ); public static ResourceManager ResourceManager => _ResourceManager.Value; #endregion // ResourceManager - - internal static class ResourceStringMembers + + internal static class ResourceStringMembers { private static readonly Lazy LazyFormat = new Lazy( () => new ResourceManagerString("ResourceStringMembers", ResourceManager, CultureInfo.CurrentCulture), @@ -31,16 +31,16 @@ namespace ResourceString.Net.Logic.Properties ); public static IResourceString Format => LazyFormat.Value; - + public static IResourceString From(IResourceString resourceId, IResourceString resourceManagerPropertyName) => new FormattedResourceString( - Format, + Format, resourceId, resourceManagerPropertyName ); - - } - - internal static class ResourceFormatClassMembers + + } + + internal static class ResourceFormatClassMembers { private static readonly Lazy LazyFormat = new Lazy( () => new ResourceManagerString("ResourceFormatClassMembers", ResourceManager, CultureInfo.CurrentCulture), @@ -48,17 +48,17 @@ namespace ResourceString.Net.Logic.Properties ); public static IResourceString Format => LazyFormat.Value; - + public static IResourceString From(IResourceString resourceId, IResourceString resourceManagerPropertyName, IResourceString fromMethodDefinition) => new FormattedResourceString( - Format, + Format, resourceId, resourceManagerPropertyName, fromMethodDefinition ); - - } - - internal static class ResourceFormatClassFromMethod + + } + + internal static class ResourceFormatClassFromMethod { private static readonly Lazy LazyFormat = new Lazy( () => new ResourceManagerString("ResourceFormatClassFromMethod", ResourceManager, CultureInfo.CurrentCulture), @@ -66,16 +66,16 @@ namespace ResourceString.Net.Logic.Properties ); public static IResourceString Format => LazyFormat.Value; - + public static IResourceString From(IResourceString fromMethodSignature, IResourceString parameterNames) => new FormattedResourceString( - Format, + Format, fromMethodSignature, parameterNames ); - - } - - internal static class ResourcesClassTemplate + + } + + internal static class ResourcesClassTemplate { private static readonly Lazy LazyFormat = new Lazy( () => new ResourceManagerString("ResourcesClassTemplate", ResourceManager, CultureInfo.CurrentCulture), @@ -83,18 +83,18 @@ namespace ResourceString.Net.Logic.Properties ); public static IResourceString Format => LazyFormat.Value; - + public static IResourceString From(IResourceString ns, IResourceString className, IResourceString resourceManagerRegion, IResourceString resourceRegions) => new FormattedResourceString( - Format, + Format, ns, className, resourceManagerRegion, resourceRegions ); - - } - - internal static class ResourceManagerMemberTemplate + + } + + internal static class ResourceManagerMemberTemplate { private static readonly Lazy LazyFormat = new Lazy( () => new ResourceManagerString("ResourceManagerMemberTemplate", ResourceManager, CultureInfo.CurrentCulture), @@ -102,14 +102,15 @@ namespace ResourceString.Net.Logic.Properties ); public static IResourceString Format => LazyFormat.Value; - - public static IResourceString From(IResourceString resourceManagerTypeName) => new FormattedResourceString( - Format, + + public static IResourceString From(IResourceString className, IResourceString resourceManagerTypeName) => new FormattedResourceString( + Format, + className, resourceManagerTypeName ); - - } - + + } + #region DefaultPropertyName_ResourceManager private static readonly Lazy LazyDefaultPropertyName_ResourceManager = new Lazy( @@ -120,6 +121,6 @@ namespace ResourceString.Net.Logic.Properties public static IResourceString DefaultPropertyName_ResourceManager => LazyDefaultPropertyName_ResourceManager.Value; #endregion // DefaultPropertyName_ResourceManager - + } -} +} \ No newline at end of file diff --git a/ResourceString.Net.Logic/Properties/Resources.resx b/ResourceString.Net.Logic/Properties/Resources.resx index 9fd20fe..307a0b3 100644 --- a/ResourceString.Net.Logic/Properties/Resources.resx +++ b/ResourceString.Net.Logic/Properties/Resources.resx @@ -62,10 +62,10 @@ namespace {0} #region ResourceManager - private static readonly Type _Type = typeof({0}); + private static readonly Type _Type = typeof({1}); private static readonly Lazy<ResourceManager> _ResourceManager = new Lazy<ResourceManager>( - () => new ResourceManager(_Type.FullName ?? string.Empty, _Type.Assembly), + () => new ResourceManager("{0}" ?? string.Empty, _Type.Assembly), LazyThreadSafetyMode.PublicationOnly ); @@ -73,7 +73,7 @@ namespace {0} #endregion // ResourceManager - 0 = ResourceManagerTypeName + 0 = ResourceManagerTypeName, 1 = ClassName ResourceManager diff --git a/ResourceString.Net/Generator.cs b/ResourceString.Net/Generator.cs index b7a39f6..da59483 100644 --- a/ResourceString.Net/Generator.cs +++ b/ResourceString.Net/Generator.cs @@ -36,14 +36,19 @@ public class Generator : IIncrementalGenerator ? t.assembly.Name : $"{t.assembly.Name}.{relativeNamespace}"; + var resourceFileName = t.name; + var className = CodeSnippetFactory.TransformToClassName(t.name); var snippet = CodeSnippetFactory.CreateResourceClassCodeSnippet( - ns, - t.name, - CodeSnippetFactory.CreateResourceMangerMemberCodeSnippet(t.name), + CodeSnippetFactory.TransformToNamespace(ns), + className, + CodeSnippetFactory.CreateResourceMangerMemberCodeSnippet( + $"{ns}.{resourceFileName}", + className + ), v.Resources ); - spc.AddSource(ns + t.name, snippet.Value); + spc.AddSource($"{ns}.{className}", snippet.Value); }); }); } diff --git a/flake.nix b/flake.nix index 15d4525..999ea44 100644 --- a/flake.nix +++ b/flake.nix @@ -4,7 +4,7 @@ outputs = { self, nixpkgs }: let name = "ResourceString.Net"; - version = "0.0.2"; + version = "0.0.3"; supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ]; forAllSystems = nixpkgs.lib.genAttrs supportedSystems;