From 6bd28e9a87f2c2d185c20e6f442642e47a2d682e Mon Sep 17 00:00:00 2001 From: Artur Dorochowicz Date: Sat, 15 Aug 2026 08:11:59 +0000 Subject: [PATCH 1/4] Support concatenated localizer strings Co-authored-by: Junie --- .../SingularStringExtractor.cs | 25 ++++++++++++-- .../SingularStringExtractorTests.cs | 34 +++++++++++++++++-- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/OrchardCoreContrib.PoExtractor.DotNet.CS/SingularStringExtractor.cs b/src/OrchardCoreContrib.PoExtractor.DotNet.CS/SingularStringExtractor.cs index 1653d3c..83419ac 100644 --- a/src/OrchardCoreContrib.PoExtractor.DotNet.CS/SingularStringExtractor.cs +++ b/src/OrchardCoreContrib.PoExtractor.DotNet.CS/SingularStringExtractor.cs @@ -33,13 +33,34 @@ accessor.Expression is IdentifierNameSyntax identifierName && { var argument = accessor.ArgumentList.Arguments.FirstOrDefault(); - if (argument != null && argument.Expression is LiteralExpressionSyntax literal && literal.IsKind(SyntaxKind.StringLiteralExpression)) + if (argument != null && TryGetString(argument.Expression, out var value)) { - result = CreateLocalizedString(literal.Token.ValueText, null, node); + result = CreateLocalizedString(value, null, node); return true; } } return false; } + + private static bool TryGetString(ExpressionSyntax expression, out string value) + { + if (expression is LiteralExpressionSyntax literal && literal.IsKind(SyntaxKind.StringLiteralExpression)) + { + value = literal.Token.ValueText; + return true; + } + + if (expression is BinaryExpressionSyntax binary && + binary.IsKind(SyntaxKind.AddExpression) && + TryGetString(binary.Left, out var left) && + TryGetString(binary.Right, out var right)) + { + value = left + right; + return true; + } + + value = null; + return false; + } } diff --git a/test/OrchardCoreContrib.PoExtractor.DotNet.CS.Tests/SingularStringExtractorTests.cs b/test/OrchardCoreContrib.PoExtractor.DotNet.CS.Tests/SingularStringExtractorTests.cs index 272a1d7..e430089 100644 --- a/test/OrchardCoreContrib.PoExtractor.DotNet.CS.Tests/SingularStringExtractorTests.cs +++ b/test/OrchardCoreContrib.PoExtractor.DotNet.CS.Tests/SingularStringExtractorTests.cs @@ -14,7 +14,7 @@ public void ExtractString() var extractor = new SingularStringExtractor(metadataProvider); var syntaxTree = CSharpSyntaxTree.ParseText($"S[\"{text}\"];", path: "DummyPath"); - + var node = syntaxTree .GetRoot() .DescendantNodes() @@ -27,4 +27,34 @@ public void ExtractString() Assert.True(extracted); Assert.Equal(text, result.Text); } -} \ No newline at end of file + + [Theory] + [InlineData("""S["my " + "text"];""", "my text")] + [InlineData("""S["a " + "long " + "text"];""", "a long text")] + [InlineData( + """ + S["This is a long piece of text " + + "continued on another line."]; + """, + "This is a long piece of text continued on another line.")] + public void ExtractConcatenatedString(string source, string expected) + { + // Arrange + var metadataProvider = new CSharpMetadataProvider("DummyBasePath"); + var extractor = new SingularStringExtractor(metadataProvider); + + var syntaxTree = CSharpSyntaxTree.ParseText(source, path: "DummyPath"); + + var node = syntaxTree + .GetRoot() + .DescendantNodes() + .ElementAt(2); + + // Act + var extracted = extractor.TryExtract(node, out var result); + + // Assert + Assert.True(extracted); + Assert.Equal(expected, result.Text); + } +} From 6bf6c6f486a9e0b2aa0cd205c54c184cae929295 Mon Sep 17 00:00:00 2001 From: Hisham Bin Ateya Date: Sun, 16 Aug 2026 07:23:26 +0300 Subject: [PATCH 2/4] Apply suggestions from code review Co-authored-by: Hisham Bin Ateya --- .../SingularStringExtractor.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/OrchardCoreContrib.PoExtractor.DotNet.CS/SingularStringExtractor.cs b/src/OrchardCoreContrib.PoExtractor.DotNet.CS/SingularStringExtractor.cs index 83419ac..43e1432 100644 --- a/src/OrchardCoreContrib.PoExtractor.DotNet.CS/SingularStringExtractor.cs +++ b/src/OrchardCoreContrib.PoExtractor.DotNet.CS/SingularStringExtractor.cs @@ -48,6 +48,7 @@ private static bool TryGetString(ExpressionSyntax expression, out string value) if (expression is LiteralExpressionSyntax literal && literal.IsKind(SyntaxKind.StringLiteralExpression)) { value = literal.Token.ValueText; + return true; } @@ -57,10 +58,12 @@ private static bool TryGetString(ExpressionSyntax expression, out string value) TryGetString(binary.Right, out var right)) { value = left + right; + return true; } value = null; + return false; } } From 61c0b66ac87778cf6d287ec7ccc6e3431d06921f Mon Sep 17 00:00:00 2001 From: Artur Dorochowicz Date: Sun, 16 Aug 2026 20:16:24 +0000 Subject: [PATCH 3/4] Consolidate singular string extractor tests --- .../SingularStringExtractorTests.cs | 32 +++++-------------- 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/test/OrchardCoreContrib.PoExtractor.DotNet.CS.Tests/SingularStringExtractorTests.cs b/test/OrchardCoreContrib.PoExtractor.DotNet.CS.Tests/SingularStringExtractorTests.cs index e430089..4586ba8 100644 --- a/test/OrchardCoreContrib.PoExtractor.DotNet.CS.Tests/SingularStringExtractorTests.cs +++ b/test/OrchardCoreContrib.PoExtractor.DotNet.CS.Tests/SingularStringExtractorTests.cs @@ -5,30 +5,14 @@ namespace OrchardCoreContrib.PoExtractor.DotNet.CS.Tests; public class SingularStringExtractorTests { - [Fact] - public void ExtractString() - { - // Arrange - var text = "Thing"; - var metadataProvider = new CSharpMetadataProvider("DummyBasePath"); - var extractor = new SingularStringExtractor(metadataProvider); - - var syntaxTree = CSharpSyntaxTree.ParseText($"S[\"{text}\"];", path: "DummyPath"); - - var node = syntaxTree - .GetRoot() - .DescendantNodes() - .ElementAt(2); - - // Act - var extracted = extractor.TryExtract(node, out var result); - - // Assert - Assert.True(extracted); - Assert.Equal(text, result.Text); - } - [Theory] + [InlineData("""S["Thing"];""", "Thing")] + [InlineData( + """ + S[@"This is a multi-line + string."]; + """, + "This is a multi-line\nstring.")] [InlineData("""S["my " + "text"];""", "my text")] [InlineData("""S["a " + "long " + "text"];""", "a long text")] [InlineData( @@ -37,7 +21,7 @@ public void ExtractString() "continued on another line."]; """, "This is a long piece of text continued on another line.")] - public void ExtractConcatenatedString(string source, string expected) + public void ExtractString(string source, string expected) { // Arrange var metadataProvider = new CSharpMetadataProvider("DummyBasePath"); From f1b5bb8bb7ac6556a66f8da89ef24880b25db65a Mon Sep 17 00:00:00 2001 From: Artur Dorochowicz Date: Sun, 16 Aug 2026 20:29:44 +0000 Subject: [PATCH 4/4] Fix test - don't assume new line char --- .../SingularStringExtractorTests.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/OrchardCoreContrib.PoExtractor.DotNet.CS.Tests/SingularStringExtractorTests.cs b/test/OrchardCoreContrib.PoExtractor.DotNet.CS.Tests/SingularStringExtractorTests.cs index 4586ba8..873f17d 100644 --- a/test/OrchardCoreContrib.PoExtractor.DotNet.CS.Tests/SingularStringExtractorTests.cs +++ b/test/OrchardCoreContrib.PoExtractor.DotNet.CS.Tests/SingularStringExtractorTests.cs @@ -12,7 +12,10 @@ public class SingularStringExtractorTests S[@"This is a multi-line string."]; """, - "This is a multi-line\nstring.")] + """ + This is a multi-line + string. + """)] [InlineData("""S["my " + "text"];""", "my text")] [InlineData("""S["a " + "long " + "text"];""", "a long text")] [InlineData(