fix resource class name generation -> 0.0.3
This commit is contained in:
137
Readme.md
137
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.
|
||||
|
||||
@@ -39,7 +41,7 @@ echo "<?xml version='1.0' encoding='utf-8'?>
|
||||
<value>Hello {0}</value>
|
||||
<comment>0 = name</comment>
|
||||
</data>
|
||||
<data name='World' type='System.String'>
|
||||
<data name='World'>
|
||||
<value>World</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -81,13 +83,12 @@ namespace MyTestConsoleApp
|
||||
{
|
||||
internal static class Resources
|
||||
{
|
||||
|
||||
#region ResourceManager
|
||||
|
||||
private static readonly Type _Type = typeof(Resources);
|
||||
|
||||
private static readonly Lazy<ResourceManager> _ResourceManager = new Lazy<ResourceManager>(
|
||||
() => new ResourceManager(_Type.FullName ?? string.Empty, _Type.Assembly),
|
||||
() => new ResourceManager("MyTestConsoleApp.Resources" ?? string.Empty, _Type.Assembly),
|
||||
LazyThreadSafetyMode.PublicationOnly
|
||||
);
|
||||
|
||||
@@ -95,6 +96,7 @@ namespace MyTestConsoleApp
|
||||
|
||||
#endregion // ResourceManager
|
||||
|
||||
#region Greetings
|
||||
|
||||
internal static class Greetings
|
||||
{
|
||||
@@ -112,6 +114,8 @@ namespace MyTestConsoleApp
|
||||
|
||||
}
|
||||
|
||||
#endregion // Greetings
|
||||
|
||||
#region World
|
||||
|
||||
private static readonly Lazy<IResourceString> LazyWorld = new Lazy<IResourceString>(
|
||||
@@ -122,12 +126,123 @@ namespace MyTestConsoleApp
|
||||
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 "<?xml version='1.0' encoding='utf-8'?>
|
||||
<root>
|
||||
<data name='Greetings'>
|
||||
<value>Hallo {0}</value>
|
||||
<comment>0 = name</comment>
|
||||
</data>
|
||||
<data name='World'>
|
||||
<value>Welt</value>
|
||||
</data>
|
||||
</root>
|
||||
" > 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.
|
||||
|
||||
|
||||
## How it works
|
||||
|
||||
The ResourceString.Net source code generator operates by reading all `AdditionalFiles` with the `.resx` file extension and generating a static class based on the specified `data` elements within the file.
|
||||
|
||||
For example, given the following XML element:
|
||||
|
||||
```xml
|
||||
<data name='World'>
|
||||
<value>Welt</value>
|
||||
</data>
|
||||
```
|
||||
|
||||
the source code generator transforms it into the following `C#` class members:
|
||||
|
||||
```cs
|
||||
#region World
|
||||
|
||||
private static readonly Lazy<IResourceString> LazyWorld = new Lazy<IResourceString>(
|
||||
() => new ResourceManagerString("World", ResourceManager, CultureInfo.CurrentCulture),
|
||||
LazyThreadSafetyMode.PublicationOnly
|
||||
);
|
||||
|
||||
public static IResourceString World => LazyWorld.Value;
|
||||
|
||||
#endregion // World
|
||||
```
|
||||
|
||||
If an element contains a format string, such as:
|
||||
|
||||
```xml
|
||||
<data name='Greetings'>
|
||||
<value>Hello {0} and {1}</value>
|
||||
</data>
|
||||
```
|
||||
|
||||
the generator generates following code to support the formatted string:
|
||||
|
||||
```cs
|
||||
#region Greetings
|
||||
|
||||
internal static class Greetings
|
||||
{
|
||||
private static readonly Lazy<IResourceString> LazyFormat = new Lazy<IResourceString>(
|
||||
() => new ResourceManagerString("Greetings", ResourceManager, CultureInfo.CurrentCulture),
|
||||
LazyThreadSafetyMode.PublicationOnly
|
||||
);
|
||||
|
||||
public static IResourceString Format => LazyFormat.Value;
|
||||
|
||||
public static IResourceString From(IResourceString p1, IResourceString p2) => new FormattedResourceString(
|
||||
Format,
|
||||
p1,
|
||||
p2
|
||||
);
|
||||
}
|
||||
|
||||
#endregion // Greetings
|
||||
```
|
||||
|
||||
In cases where the element with the format string includes a comment element like:
|
||||
|
||||
```xml
|
||||
<data name='Greetings'>
|
||||
<value>Hello {0} and {1}</value>
|
||||
<comment>0 = name, 1 = otherName </comment>
|
||||
</data>
|
||||
```
|
||||
|
||||
the source generator extracts the parameter names from the comment instead of using generic names:
|
||||
|
||||
```cs
|
||||
public static IResourceString From(IResourceString name, IResourceString otherName) => new FormattedResourceString(
|
||||
Format,
|
||||
name,
|
||||
otherName
|
||||
);
|
||||
```
|
||||
|
||||
This allows for more descriptive parameter names in the generated code.
|
||||
|
||||
## The ResourceString-Classes
|
||||
|
||||
### IResourceString
|
||||
@@ -213,12 +328,12 @@ 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 |
|
||||
| --------------------------------------------------------------------------------------------------- | ------- |
|
||||
| [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
|
||||
|
||||
|
||||
@@ -3,13 +3,20 @@ 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),
|
||||
CodeSnippetFactory.CreateResourceMangerMemberCodeSnippet(
|
||||
$"{namespaceString}.{className}",
|
||||
className
|
||||
),
|
||||
v.Resources
|
||||
),
|
||||
None: () => throw new InvalidOperationException()
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
|
||||
|
||||
@@ -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> _ResourceManager = new Lazy<ResourceManager>(
|
||||
() => 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()
|
||||
|
||||
@@ -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<IResourceString> CreateMemberCodeSnippets(
|
||||
IEnumerable<Resource> resources,
|
||||
IResourceString resourceManagerName
|
||||
)
|
||||
IResourceString resourceManagerName)
|
||||
{
|
||||
if (resources is null)
|
||||
{
|
||||
@@ -95,8 +98,7 @@ internal static class CodeSnippetFactory
|
||||
string namespaceString,
|
||||
string resourceClassName,
|
||||
IResourceString resourceManagerSnippet,
|
||||
IEnumerable<IResourceString> memberSnippets
|
||||
)
|
||||
IEnumerable<IResourceString> 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('.');
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ namespace ResourceString.Net.Logic.Properties
|
||||
private static readonly Type _Type = typeof(Resources);
|
||||
|
||||
private static readonly Lazy<ResourceManager> _ResourceManager = new Lazy<ResourceManager>(
|
||||
() => new ResourceManager(_Type.FullName ?? string.Empty, _Type.Assembly),
|
||||
() => new ResourceManager("ResourceString.Net.Logic.Properties.Resources" ?? string.Empty, _Type.Assembly),
|
||||
LazyThreadSafetyMode.PublicationOnly
|
||||
);
|
||||
|
||||
@@ -103,8 +103,9 @@ namespace ResourceString.Net.Logic.Properties
|
||||
|
||||
public static IResourceString Format => LazyFormat.Value;
|
||||
|
||||
public static IResourceString From(IResourceString resourceManagerTypeName) => new FormattedResourceString(
|
||||
public static IResourceString From(IResourceString className, IResourceString resourceManagerTypeName) => new FormattedResourceString(
|
||||
Format,
|
||||
className,
|
||||
resourceManagerTypeName
|
||||
);
|
||||
|
||||
|
||||
@@ -62,10 +62,10 @@ namespace {0}
|
||||
<value>
|
||||
#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
|
||||
</value>
|
||||
<comment>0 = ResourceManagerTypeName</comment>
|
||||
<comment>0 = ResourceManagerTypeName, 1 = ClassName</comment>
|
||||
</data>
|
||||
<data name="DefaultPropertyName_ResourceManager">
|
||||
<value>ResourceManager</value>
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user