Skip to content
Open
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
76 changes: 76 additions & 0 deletions Aigamo.ResXGenerator.Tests/GeneratorLocalizerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,82 @@ public void Generate_Localizer_NullForgivingOperators()
Generate(generator, nullForgivingOperators: true);
}

[Fact]
public void Generate_Localizer_FormattableValue_WithoutOption_DoesNotGenerateFormatMethods()
{
const string text =
"""
<?xml version="1.0" encoding="utf-8"?>
<root>
<data name="Greeting" xml:space="preserve">
<value>Hello {0}!</value>
</data>
</root>
""";

var generator = new LocalizerGenerator();
var result = generator.Generate(
options: new GenFileOptions
{
LocalNamespace = "VocaDb.Web.App_GlobalResources",
EmbeddedFilename = "VocaDb.Web.App_GlobalResources.CommonMessages",
GroupedFile = new GroupedAdditionalFile(
mainFile: new AdditionalTextWithHash(new AdditionalTextStub("", text), Guid.NewGuid()),
subFiles: []
),
GenerationType = GenerationType.StringLocalizer,
ClassName = "CommonMessages"
}
);

result.ErrorsAndWarnings.Should().BeNullOrEmpty();
result.SourceCode.ReplaceLineEndings().Should().Contain("public string Greeting => stringLocalizer[\"Greeting\"];");
result.SourceCode.ReplaceLineEndings().Should().NotContain("public string Greeting(object arg0)");
}

[Fact]
public void Generate_Localizer_FormattableValue_GeneratesMethodWithParameters_WhenEnabled()
{
const string text =
"""
<?xml version="1.0" encoding="utf-8"?>
<root>
<data name="Greeting" xml:space="preserve">
<value>Hello {0}!</value>
</data>
<data name="MultiGreeting" xml:space="preserve">
<value>Hi {1} {0}!</value>
</data>
</root>
""";

var generator = new LocalizerGenerator();
var result = generator.Generate(
options: new GenFileOptions
{
LocalNamespace = "VocaDb.Web.App_GlobalResources",
EmbeddedFilename = "VocaDb.Web.App_GlobalResources.CommonMessages",
GroupedFile = new GroupedAdditionalFile(
mainFile: new AdditionalTextWithHash(new AdditionalTextStub("", text), Guid.NewGuid()),
subFiles: []
),
GenerationType = GenerationType.StringLocalizer,
ClassName = "CommonMessages",
GenerateFormatMethods = true
}
);

result.ErrorsAndWarnings.Should().BeNullOrEmpty();
result.SourceCode.ReplaceLineEndings().Should().NotContain("public string Greeting => stringLocalizer[\"Greeting\"];");
result.SourceCode.ReplaceLineEndings().Should().Contain("public string Greeting(object arg0) => stringLocalizer[\"Greeting\", arg0];");
result.SourceCode.ReplaceLineEndings().Should().NotContain("public string MultiGreeting => stringLocalizer[\"MultiGreeting\"];");
result.SourceCode.ReplaceLineEndings().Should().Contain("public string MultiGreeting(object arg0, object arg1) => stringLocalizer[\"MultiGreeting\", arg0, arg1];");
result.SourceCode.ReplaceLineEndings().Should().NotContain("string Greeting {get;}");
result.SourceCode.ReplaceLineEndings().Should().Contain("string Greeting(object arg0);");
result.SourceCode.ReplaceLineEndings().Should().NotContain("string MultiGreeting {get;}");
result.SourceCode.ReplaceLineEndings().Should().Contain("string MultiGreeting(object arg0, object arg1);");
}

[Fact]
public void Generate_Localizer_NewLine()
{
Expand Down
4 changes: 2 additions & 2 deletions Aigamo.ResXGenerator.Tests/HelperGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ internal static partial class Helpers
""";
result.ErrorsAndWarnings.Should().BeNullOrEmpty();
result.FileName.Should().Be("Aigamo.ResXGenerator.1030_6.g.cs");
result.SourceCode.Should().Be(expected);
result.SourceCode.ReplaceLineEndings().Should().Be(expected.ReplaceLineEndings());
}
[Fact]
public void CanGenerateEmptyCombo()
Expand Down Expand Up @@ -75,6 +75,6 @@ internal static partial class Helpers
""";
result.ErrorsAndWarnings.Should().BeNullOrEmpty();
result.FileName.Should().Be("Aigamo.ResXGenerator..g.cs");
result.SourceCode.Should().Be(expected);
result.SourceCode.ReplaceLineEndings().Should().Be(expected.ReplaceLineEndings());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public void TestNormalResourceGen()
Test1.CreateDate.Should().Be("OldestDaDK");

// Test embedded files are as expected
Test1.TextFile.Should().Be("This is a test.\n");
Test1.TextFile.ReplaceLineEndings().Should().Be("This is a test.\n".ReplaceLineEndings());
Test1.BinaryFile.Should().BeEquivalentTo(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 });
Test1.TestIconAsBytes.Should().NotBeNull();
Test1.TestImageAsBytes.Should().NotBeNull();
Expand Down
60 changes: 48 additions & 12 deletions Aigamo.ResXGenerator/Generators/LocalizerGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Aigamo.ResXGenerator.Extensions;
using System.Text.RegularExpressions;
using Aigamo.ResXGenerator.Extensions;
using Aigamo.ResXGenerator.Models;
using Aigamo.ResXGenerator.Tools;

Expand Down Expand Up @@ -50,15 +51,50 @@ public interface I{{Options.ClassName}}
""";
}

private string GenerateMembers(FallBackItem fallbackItem) => !Validator.ValidateMember(fallbackItem, Options) ?
$"// Skipped invalid member name: {fallbackItem.Key}" :
$"public string {fallbackItem.Key} => stringLocalizer[\"{fallbackItem.Key}\"];";

private static string GenerateInterfaceMembers(FallBackItem fallbackItem) =>
$$"""
/// <summary>
/// Looks up a localized string similar to {{fallbackItem.Value.ToXmlCommentSafe()}}.
/// </summary>
string {{fallbackItem.Key}} {get;}
""";
private string GenerateMembers(FallBackItem fallbackItem)
{
if (!Validator.ValidateMember(fallbackItem, Options))
return $"// Skipped invalid member name: {fallbackItem.Key}";

var parameters = GetFormatParameters(fallbackItem.Value);
if (parameters.Length > 0 && Options.GenerateFormatMethods)
{
return $$"""
public string {{fallbackItem.Key}}({{string.Join(", ", parameters.Select(p => $"object {p}"))}}) => stringLocalizer["{{fallbackItem.Key}}", {{string.Join(", ", parameters)}}];
""";
}

return $"public string {fallbackItem.Key} => stringLocalizer[\"{fallbackItem.Key}\"];";
}

private string GenerateInterfaceMembers(FallBackItem fallbackItem)
{
var parameters = GetFormatParameters(fallbackItem.Value);
if (parameters.Length > 0 && Options.GenerateFormatMethods)
{
return $$"""
/// <summary>
/// Looks up a localized string similar to {{fallbackItem.Value.ToXmlCommentSafe()}}.
/// </summary>
string {{fallbackItem.Key}}({{string.Join(", ", parameters.Select(p => $"object {p}"))}});
""";
}

return $$"""
/// <summary>
/// Looks up a localized string similar to {{fallbackItem.Value.ToXmlCommentSafe()}}.
/// </summary>
string {{fallbackItem.Key}} {get;}
""";
}

private static string[] GetFormatParameters(string value)
{
var placeholders = Regex.Matches(value, "\\{(\\d+)\\}");
return [.. placeholders.Cast<Match>()
.Select(match => int.Parse(match.Groups[1].Value))
.Distinct()
.OrderBy(index => index)
.Select(index => $"arg{index}")];
}
}
1 change: 1 addition & 0 deletions Aigamo.ResXGenerator/QUICKSTART.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@
|GenerateCode*|ResXGenerator_GenerateCode|- true<br>- **false**|All except StringLocalizer|
|CustomToolNamespace|ResXGenerator_CustomToolNamespace|Any valid C# namespace|All except StringLocalizer|
|SkipFile| |- true<br>- **false**|All|
|GenerateFormatMethods|ResXGenerator_GenerateFormatMethods|- true<br>- **false**|StringLocalizer|

\* *Could be replaced by the option GenerationType with the value CodeGeneration*
7 changes: 7 additions & 0 deletions Aigamo.ResXGenerator/Tools/GenFileOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public readonly record struct GenFileOptions
public bool StaticClass { get; init; }
public bool NullForgivingOperators { get; init; }
public bool PublicClass { get; init; }
public bool GenerateFormatMethods { get; init; }
public string ClassName { get; init; }
public string? CustomToolNamespace { get; init; }
public string LocalNamespace { get; init; }
Expand Down Expand Up @@ -96,6 +97,12 @@ GlobalOptions globalOptions
? !perFileStaticClassSwitch.Equals("false", StringComparison.OrdinalIgnoreCase)
: globalOptions.StaticClass;

GenerateFormatMethods =
options.TryGetValue("build_metadata.EmbeddedResource.GenerateFormatMethods", out var generateFormatMethodsSwitch) &&
generateFormatMethodsSwitch is { Length: > 0 }
? generateFormatMethodsSwitch.Equals("true", StringComparison.OrdinalIgnoreCase)
: globalOptions.GenerateFormatMethods;

StaticMembers =
options.TryGetValue("build_metadata.EmbeddedResource.StaticMembers", out var staticMembersSwitch) &&
staticMembersSwitch is { Length: > 0 }
Expand Down
6 changes: 6 additions & 0 deletions Aigamo.ResXGenerator/Tools/GlobalOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public sealed record GlobalOptions // this must be a record or implement IEquata
public bool StaticClass { get; }
public bool NullForgivingOperators { get; }
public bool PublicClass { get; }
public bool GenerateFormatMethods { get; }
public string ClassNamePostfix { get; }
public bool GenerateCode { get; }
public GenerationType GenerationType { get; }
Expand Down Expand Up @@ -68,6 +69,11 @@ public GlobalOptions(AnalyzerConfigOptions options)
staticMembersSwitch.Equals("false", StringComparison.OrdinalIgnoreCase)
);

GenerateFormatMethods =
options.TryGetValue("build_property.ResXGenerator_GenerateFormatMethods", out var generateFormatMethodsSwitch) &&
generateFormatMethodsSwitch is { Length: > 0 } &&
generateFormatMethodsSwitch.Equals("true", StringComparison.OrdinalIgnoreCase);

PartialClass =
options.TryGetValue("build_property.ResXGenerator_PartialClass", out var partialClassSwitch) &&
partialClassSwitch is { Length: > 0 } &&
Expand Down
2 changes: 2 additions & 0 deletions Aigamo.ResXGenerator/build/Aigamo.ResXGenerator.props
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<CompilerVisibleProperty Include="ResXGenerator_InnerClassInstanceName" />
<CompilerVisibleProperty Include="ResXGenerator_GenerateCode" />
<CompilerVisibleProperty Include="ResXGenerator_GenerationType" />
<CompilerVisibleProperty Include="ResXGenerator_GenerateFormatMethods" />
<CompilerVisibleItemMetadata Include="EmbeddedResource" MetadataName="Link" />
<CompilerVisibleItemMetadata Include="EmbeddedResource" MetadataName="TargetPath" />
<CompilerVisibleItemMetadata Include="EmbeddedResource" MetadataName="CustomToolNamespace" />
Expand All @@ -36,6 +37,7 @@
<CompilerVisibleItemMetadata Include="EmbeddedResource" MetadataName="GenerationType" />
<CompilerVisibleItemMetadata Include="EmbeddedResource" MetadataName="LogicalName" />
<CompilerVisibleItemMetadata Include="EmbeddedResource" MetadataName="ManifestResourceName" />
<CompilerVisibleItemMetadata Include="EmbeddedResource" MetadataName="GenerateFormatMethods" />
</ItemGroup>

</Project>
Loading