Skip to content

Commit 74a316b

Browse files
authored
Localize AbsolutePath validation messages (#13115)
Add localization infrastructure to Microsoft.Build.Framework for AbsolutePath validation messages. ## Changes - Created Resources/Strings.resx with localized messages for path validation - Created Resources/AssemblyResources.cs (FrameworkResources class) to load resources - Updated AbsolutePath.ValidatePath() to use localized strings Closes #13114
1 parent 29c87f1 commit 74a316b

20 files changed

Lines changed: 446 additions & 6 deletions

src/Framework.UnitTests/AbsolutePath_Tests.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Microsoft.Build.Shared;
88
using Shouldly;
99
using Xunit;
10+
using Xunit.NetCore.Extensions;
1011

1112
namespace Microsoft.Build.UnitTests
1213
{
@@ -47,18 +48,22 @@ public void AbsolutePath_FromAbsolutePath_ShouldPreservePath()
4748
[Theory]
4849
[InlineData(null)]
4950
[InlineData("")]
51+
[UseInvariantCulture]
5052
public void AbsolutePath_NullOrEmpty_ShouldThrow(string? path)
5153
{
52-
Should.Throw<ArgumentException>(() => new AbsolutePath(path!));
54+
var exception = Should.Throw<ArgumentException>(() => new AbsolutePath(path!));
55+
exception.Message.ShouldContain("Path must not be null or empty");
5356
}
5457

5558
[Theory]
5659
[InlineData(null)]
5760
[InlineData("")]
61+
[UseInvariantCulture]
5862
public void AbsolutePath_NullOrEmptyWithBasePath_ShouldThrow(string? path)
5963
{
6064
var basePath = GetTestBasePath();
61-
Should.Throw<ArgumentException>(() => new AbsolutePath(path!, basePath));
65+
var exception = Should.Throw<ArgumentException>(() => new AbsolutePath(path!, basePath));
66+
exception.Message.ShouldContain("Path must not be null or empty");
6267
}
6368

6469
[Theory]
@@ -213,5 +218,13 @@ public void AbsolutePath_UnixPathValidation_ShouldAcceptOnlyTrueAbsolutePaths(st
213218
{
214219
ValidatePathAcceptance(path, shouldBeAccepted);
215220
}
221+
222+
[WindowsOnlyFact]
223+
[UseInvariantCulture]
224+
public void AbsolutePath_NotRooted_ShouldThrowWithLocalizedMessage()
225+
{
226+
var exception = Should.Throw<ArgumentException>(() => new AbsolutePath("relative/path"));
227+
exception.Message.ShouldContain("Path must be rooted");
228+
}
216229
}
217230
}

src/Framework/Microsoft.Build.Framework.csproj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,11 @@
7373
<ItemGroup>
7474
<RCResourceFile Include="native.rc" />
7575
</ItemGroup>
76+
77+
<ItemGroup>
78+
<EmbeddedResource Update="Resources\Strings.resx">
79+
<LogicalName>$(AssemblyName).Strings.resources</LogicalName>
80+
<SubType>Designer</SubType>
81+
</EmbeddedResource>
82+
</ItemGroup>
7683
</Project>

src/Framework/PathHelpers/AbsolutePath.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,15 +86,15 @@ private static void ValidatePath(string path)
8686
{
8787
if (string.IsNullOrEmpty(path))
8888
{
89-
throw new ArgumentException("Path must not be null or empty.", nameof(path));
89+
throw new ArgumentException(FrameworkResources.GetString("PathMustNotBeNullOrEmpty"), nameof(path));
9090
}
9191

9292
// Path.IsPathFullyQualified is not available in .NET Standard 2.0
9393
// in .NET Framework it's provided by package and in .NET it's built-in
9494
#if NETFRAMEWORK || NET
9595
if (!Path.IsPathFullyQualified(path))
9696
{
97-
throw new ArgumentException("Path must be rooted.", nameof(path));
97+
throw new ArgumentException(FrameworkResources.GetString("PathMustBeRooted"), nameof(path));
9898
}
9999
#endif
100100
}
@@ -109,15 +109,15 @@ public AbsolutePath(string path, AbsolutePath basePath)
109109
{
110110
if (string.IsNullOrEmpty(path))
111111
{
112-
throw new ArgumentException("Path must not be null or empty.", nameof(path));
112+
throw new ArgumentException(FrameworkResources.GetString("PathMustNotBeNullOrEmpty"), nameof(path));
113113
}
114114

115115
// This function should not throw when path has illegal characters.
116116
// For .NET Framework, Microsoft.IO.Path.Combine should be used instead of System.IO.Path.Combine to achieve it.
117117
// For .NET Core, System.IO.Path.Combine already does not throw in this case.
118118
Value = Path.Combine(basePath.Value, path);
119119
OriginalValue = path;
120-
}
120+
}
121121

122122
/// <summary>
123123
/// Implicitly converts an AbsolutePath to a string.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Globalization;
5+
using System.Reflection;
6+
using System.Resources;
7+
8+
namespace Microsoft.Build.Framework
9+
{
10+
/// <summary>
11+
/// This class provides access to the Framework assembly's resources.
12+
/// </summary>
13+
/// <remarks>
14+
/// Named FrameworkResources (not AssemblyResources) to avoid conflicts with
15+
/// Microsoft.Build.Shared.AssemblyResources which is visible via InternalsVisibleTo.
16+
/// </remarks>
17+
internal static class FrameworkResources
18+
{
19+
/// <summary>
20+
/// The assembly's primary resources.
21+
/// </summary>
22+
private static readonly ResourceManager s_resources = new ResourceManager("Microsoft.Build.Framework.Strings", typeof(FrameworkResources).GetTypeInfo().Assembly);
23+
24+
/// <summary>
25+
/// Loads the specified resource string.
26+
/// </summary>
27+
/// <remarks>This method is thread-safe.</remarks>
28+
/// <param name="name">The name of the string resource to load.</param>
29+
/// <returns>The resource string.</returns>
30+
internal static string GetString(string name)
31+
{
32+
// NOTE: the ResourceManager.GetString() method is thread-safe
33+
string? resource = s_resources.GetString(name, CultureInfo.CurrentUICulture);
34+
35+
FrameworkErrorUtilities.VerifyThrow(resource != null, $"Missing resource '{name}'");
36+
37+
return resource!;
38+
}
39+
}
40+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<root>
3+
<!--
4+
Microsoft ResX Schema
5+
6+
Version 2.0
7+
8+
The primary goals of this format is to allow a simple XML format
9+
that is mostly human readable. The generation and parsing of the
10+
various data types are done through the TypeConverter classes
11+
associated with the data types.
12+
13+
Example:
14+
15+
... ado.net/XML headers & schema ...
16+
<resheader name="resmimetype">text/microsoft-resx</resheader>
17+
<resheader name="version">2.0</resheader>
18+
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
19+
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
20+
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
21+
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
22+
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
23+
<value>[base64 mime encoded serialized .NET Framework object]</value>
24+
</data>
25+
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
26+
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
27+
<comment>This is a comment</comment>
28+
</data>
29+
30+
There are any number of "resheader" rows that contain simple
31+
name/value pairs.
32+
33+
Each data row contains a name, and value. The row also contains a
34+
type or mimetype. Type corresponds to a .NET class that support
35+
text/value conversion through the TypeConverter architecture.
36+
Classes that don't support this are serialized and stored with the
37+
mimetype set.
38+
39+
The mimetype is used for serialized objects, and tells the
40+
ResXResourceReader how to depersist the object. This is currently not
41+
extensible. For a given mimetype the value must be set accordingly:
42+
43+
Note - application/x-microsoft.net.object.binary.base64 is the format
44+
that the ResXResourceWriter will generate, however the reader can
45+
read any of the formats listed below.
46+
47+
mimetype: application/x-microsoft.net.object.binary.base64
48+
value : The object must be serialized with
49+
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
50+
: and then encoded with base64 encoding.
51+
52+
mimetype: application/x-microsoft.net.object.soap.base64
53+
value : The object must be serialized with
54+
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
55+
: and then encoded with base64 encoding.
56+
57+
mimetype: application/x-microsoft.net.object.bytearray.base64
58+
value : The object must be serialized into a byte array
59+
: using a System.ComponentModel.TypeConverter
60+
: and then encoded with base64 encoding.
61+
-->
62+
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
63+
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
64+
<xsd:element name="root" msdata:IsDataSet="true">
65+
<xsd:complexType>
66+
<xsd:choice maxOccurs="unbounded">
67+
<xsd:element name="metadata">
68+
<xsd:complexType>
69+
<xsd:sequence>
70+
<xsd:element name="value" type="xsd:string" minOccurs="0" />
71+
</xsd:sequence>
72+
<xsd:attribute name="name" use="required" type="xsd:string" />
73+
<xsd:attribute name="type" type="xsd:string" />
74+
<xsd:attribute name="mimetype" type="xsd:string" />
75+
<xsd:attribute ref="xml:space" />
76+
</xsd:complexType>
77+
</xsd:element>
78+
<xsd:element name="assembly">
79+
<xsd:complexType>
80+
<xsd:attribute name="alias" type="xsd:string" />
81+
<xsd:attribute name="name" type="xsd:string" />
82+
</xsd:complexType>
83+
</xsd:element>
84+
<xsd:element name="data">
85+
<xsd:complexType>
86+
<xsd:sequence>
87+
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
88+
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
89+
</xsd:sequence>
90+
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
91+
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
92+
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
93+
<xsd:attribute ref="xml:space" />
94+
</xsd:complexType>
95+
</xsd:element>
96+
<xsd:element name="resheader">
97+
<xsd:complexType>
98+
<xsd:sequence>
99+
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
100+
</xsd:sequence>
101+
<xsd:attribute name="name" type="xsd:string" use="required" />
102+
</xsd:complexType>
103+
</xsd:element>
104+
</xsd:choice>
105+
</xsd:complexType>
106+
</xsd:element>
107+
</xsd:schema>
108+
<resheader name="resmimetype">
109+
<value>text/microsoft-resx</value>
110+
</resheader>
111+
<resheader name="version">
112+
<value>2.0</value>
113+
</resheader>
114+
<resheader name="reader">
115+
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
116+
</resheader>
117+
<resheader name="writer">
118+
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
119+
</resheader>
120+
<data name="PathMustNotBeNullOrEmpty" xml:space="preserve">
121+
<value>Path must not be null or empty.</value>
122+
</data>
123+
<data name="PathMustBeRooted" xml:space="preserve">
124+
<value>Path must be rooted.</value>
125+
</data>
126+
</root>

src/Framework/Resources/xlf/Strings.cs.xlf

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Framework/Resources/xlf/Strings.de.xlf

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Framework/Resources/xlf/Strings.en.xlf

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Framework/Resources/xlf/Strings.es.xlf

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Framework/Resources/xlf/Strings.fr.xlf

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)