Skip to content

Commit 3ad2c28

Browse files
committed
Use resx comment - clean up
1 parent 3ef8e2e commit 3ad2c28

9 files changed

+159
-12
lines changed

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2023 Allan Ritchie
3+
Copyright (c) 2026 Allan Ritchie
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//HintName: CommentsTest.StringsLocalized.g.cs
2+
namespace CommentsTest;
3+
4+
public partial class StringsLocalized
5+
{
6+
readonly global::Microsoft.Extensions.Localization.IStringLocalizer localizer;
7+
8+
public StringsLocalized(global::Microsoft.Extensions.Localization.IStringLocalizer<global::CommentsTest.Strings> localizer)
9+
{
10+
this.localizer = localizer;
11+
}
12+
13+
public global::Microsoft.Extensions.Localization.IStringLocalizer Localizer => this.localizer;
14+
15+
/// <summary>
16+
/// Displayed on the home screen when user first opens the app
17+
/// </summary>
18+
public string WelcomeMessage => this.localizer["WelcomeMessage"];
19+
/// <summary>
20+
/// Goodbye and see you later
21+
/// </summary>
22+
public string GoodbyeMessage => this.localizer["GoodbyeMessage"];
23+
/// <summary>
24+
/// Greeting message where {0} is the user&apos;s name
25+
/// </summary>
26+
public string GreetingFormat(object parameter0)
27+
{
28+
return string.Format(this.localizer["GreetingFormat"], parameter0);
29+
}
30+
31+
/// <summary>
32+
/// Goodbye {0}, see you tomorrow!
33+
/// </summary>
34+
public string FarewellFormat(object parameter0)
35+
{
36+
return string.Format(this.localizer["FarewellFormat"], parameter0);
37+
}
38+
39+
/// <summary>
40+
/// Full greeting with user name, message count, and sender name
41+
/// </summary>
42+
public string DetailedGreetingFormat(object parameter0, object parameter1, object parameter2)
43+
{
44+
return string.Format(this.localizer["DetailedGreeting"], parameter0, parameter1, parameter2);
45+
}
46+
47+
/// <summary>
48+
/// Comment with &lt;special&gt; &amp; &quot;characters&quot; to &apos;escape&apos;
49+
/// </summary>
50+
public string SpecialComment => this.localizer["SpecialComment"];
51+
/// <summary>
52+
/// Shows the current date in long format
53+
/// </summary>
54+
public string DateGreetingFormat(object parameter0)
55+
{
56+
return string.Format(this.localizer["DateGreeting"], parameter0);
57+
}
58+
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//HintName: ServiceCollectionExtensions.g.cs
2+
// <auto-generated />
3+
// This file is auto-generated by Shiny.Extensions.Localization.Generator
4+
// Do not edit this file directly, instead edit the .resx files in your project.
5+
using global::Microsoft.Extensions.DependencyInjection;
6+
7+
namespace CommentsTest;
8+
9+
public static class ServiceCollectionExtensions_Generated
10+
{
11+
public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddStronglyTypedLocalizations(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services)
12+
{
13+
services.AddLocalization();
14+
services.AddSingleton<global::CommentsTest.StringsLocalized>();
15+
return services;
16+
}
17+
}

Shiny.Extensions.Localization.Generator.Tests/LocalizationSourceGeneratorTests.FormatParametersWithFormattingOptionsTest#FormattingTest.FormattingLocalized.g.verified.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public string NumberFormatMessageFormat(object parameter0)
4747
/// <summary>
4848
/// Event scheduled for {0:yyyy-MM-dd HH:mm:ss}
4949
/// </summary>
50-
public string CustomDateTimeFormatFormat(object parameter0)
50+
public string CustomDateTimeFormat(object parameter0)
5151
{
5252
return string.Format(this.localizer["CustomDateTimeFormat"], parameter0);
5353
}

Shiny.Extensions.Localization.Generator.Tests/LocalizationSourceGeneratorTests.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,4 +193,51 @@ public async Task FormatParametersWithFormattingOptionsTest()
193193
await Verify(results)
194194
.UseMethodName("FormatParametersWithFormattingOptionsTest");
195195
}
196+
197+
[Fact]
198+
public async Task CommentsInResxFileTest()
199+
{
200+
var compilation = CSharpCompilation.Create(
201+
assemblyName: "Tests"
202+
);
203+
var generator = new LocalizationSourceGenerator().AsSourceGenerator();
204+
var options = new TestAnalyzerConfigOptionsProvider();
205+
options.Options.Add("build_property.MSBuildProjectFullPath", "Shiny.Extensions.Localization.Generator");
206+
options.Options.Add("build_property.MSBuildProjectName", "CommentsTest");
207+
208+
var resource1 = new ResxAdditionalText("Strings.resx");
209+
210+
// Simple property with comment - should use comment in summary
211+
resource1.AddStringWithComment("WelcomeMessage", "Welcome to our application", "Displayed on the home screen when user first opens the app");
212+
213+
// Simple property without comment - should fall back to value
214+
resource1.AddString("GoodbyeMessage", "Goodbye and see you later");
215+
216+
// Format method with comment - should use comment in summary
217+
resource1.AddStringWithComment("GreetingFormat", "Hello {0}!", "Greeting message where {0} is the user's name");
218+
219+
// Format method without comment - should fall back to value
220+
resource1.AddString("FarewellFormat", "Goodbye {0}, see you tomorrow!");
221+
222+
// Multiple parameters with comment
223+
resource1.AddStringWithComment("DetailedGreeting", "Hello {0}, you have {1} new messages from {2}", "Full greeting with user name, message count, and sender name");
224+
225+
// Comment with special characters that need XML escaping
226+
resource1.AddStringWithComment("SpecialComment", "Test value", "Comment with <special> & \"characters\" to 'escape'");
227+
228+
// Format with formatting options and comment
229+
resource1.AddStringWithComment("DateGreeting", "Today is {0:MMMM dd, yyyy}", "Shows the current date in long format");
230+
231+
GeneratorDriver driver = CSharpGeneratorDriver.Create(
232+
[generator],
233+
optionsProvider: options,
234+
additionalTexts: ImmutableArray.Create<AdditionalText>(resource1)
235+
);
236+
237+
driver = driver.RunGenerators(compilation);
238+
var results = driver.GetRunResult();
239+
240+
await Verify(results)
241+
.UseMethodName("CommentsInResxFileTest");
242+
}
196243
}

Shiny.Extensions.Localization.Generator.Tests/ResxAdditionalText.cs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Text;
1+
using System.Text;
2+
using System.Security;
23
using Microsoft.CodeAnalysis;
34
using Microsoft.CodeAnalysis.Text;
45

@@ -7,12 +8,18 @@ namespace Shiny.Extensions.Localization.Generator.Tests;
78

89
public class ResxAdditionalText : AdditionalText
910
{
10-
readonly Dictionary<string, string> strings = new();
11+
readonly Dictionary<string, (string Value, string? Comment)> strings = new();
1112
public ResxAdditionalText(string path) => this.Path = path;
1213

1314
public ResxAdditionalText AddString(string key, string value)
1415
{
15-
this.strings.Add(key, value);
16+
this.strings.Add(key, (value, null));
17+
return this;
18+
}
19+
20+
public ResxAdditionalText AddStringWithComment(string key, string value, string comment)
21+
{
22+
this.strings.Add(key, (value, comment));
1623
return this;
1724
}
1825

@@ -39,14 +46,23 @@ public override SourceText GetText(CancellationToken cancellationToken = default
3946
""");
4047
foreach (var pair in this.strings)
4148
{
42-
sb.AppendLine($"<data name=\"{pair.Key}\" xml:space=\"preserve\">");
43-
sb.AppendLine($"<value>{pair.Value}</value>");
49+
sb.AppendLine($"<data name=\"{EscapeXml(pair.Key)}\" xml:space=\"preserve\">");
50+
sb.AppendLine($"<value>{EscapeXml(pair.Value.Value)}</value>");
51+
if (!string.IsNullOrEmpty(pair.Value.Comment))
52+
{
53+
sb.AppendLine($"<comment>{EscapeXml(pair.Value.Comment)}</comment>");
54+
}
4455
sb.AppendLine("</data>");
4556
}
4657
sb.Append("</root>");
4758

4859
return SourceText.From(sb.ToString(), Encoding.UTF8);
4960
}
61+
62+
static string EscapeXml(string value)
63+
{
64+
return SecurityElement.Escape(value) ?? value;
65+
}
5066
}
5167
/*
5268
<?xml version="1.0" encoding="utf-8"?>

Shiny.Extensions.Localization.Generator.Tests/Shiny.Extensions.Localization.Generator.Tests.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.7.0" />
2424
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.6" />
2525
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.6" />
26-
<PackageReference Include="ResXResourceReader.NetStandard" Version="1.3.0" />
2726
</ItemGroup>
2827

2928
</Project>

Shiny.Extensions.Localization.Generator/LocalizationSourceGenerator.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,19 +118,28 @@ bool useInternalAccessor
118118
{
119119
var resourceKey = dataElement.Attribute("name")?.Value ?? string.Empty;
120120
var resourceValue = dataElement.Element("value")?.Value ?? string.Empty;
121+
var resourceComment = dataElement.Element("comment")?.Value ?? string.Empty;
121122
var propertyName = SafePropertyKey(resourceKey);
122123

123124
var formatParameters = GetFormatParameters(resourceValue);
124125

126+
// Use comment if available, otherwise use value as fallback
127+
var summaryText = !string.IsNullOrEmpty(resourceComment)
128+
? resourceComment
129+
: resourceValue;
130+
125131
if (formatParameters.Any())
126132
{
127133
// Generate method for format strings
128-
var methodName = $"{propertyName}Format";
134+
// Avoid double "Format" suffix if property name already ends with Format
135+
var methodName = propertyName.EndsWith("Format", StringComparison.Ordinal)
136+
? propertyName
137+
: $"{propertyName}Format";
129138
var parameters = string.Join(", ", formatParameters.Select(i => $"object parameter{i}"));
130139
var arguments = string.Join(", ", formatParameters.Select(i => $"parameter{i}"));
131140

132141
sb.AppendLine("\t/// <summary>");
133-
sb.AppendLine($"\t/// {EscapeXmlComment(resourceValue)}");
142+
sb.AppendLine($"\t/// {EscapeXmlComment(summaryText)}");
134143
sb.AppendLine("\t/// </summary>");
135144
sb.AppendLine($"\tpublic string {methodName}({parameters})");
136145
sb.AppendLine("\t{");
@@ -142,7 +151,7 @@ bool useInternalAccessor
142151
{
143152
// Generate property for simple strings
144153
sb.AppendLine("\t/// <summary>");
145-
sb.AppendLine($"\t/// {EscapeXmlComment(resourceValue)}");
154+
sb.AppendLine($"\t/// {EscapeXmlComment(summaryText)}");
146155
sb.AppendLine("\t/// </summary>");
147156
sb.AppendLine($"\tpublic string {propertyName} => this.localizer[\"{resourceKey}\"];");
148157
}

version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "https://raw.githubusercontent.com/AArnott/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
3-
"version": "2.0.0-beta.{height}",
3+
"version": "2.0.1-beta.{height}",
44
"release": {
55
"branchName": "v{version}",
66
"versionIncrement": "build",

0 commit comments

Comments
 (0)