Skip to content

Commit 2acdb82

Browse files
authored
Initial app host localization (#10295)
* Initial app host localization * Fix * Update * Fix test * Fix test * Fix tests
1 parent 4d794b0 commit 2acdb82

File tree

95 files changed

+3879
-140
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

95 files changed

+3879
-140
lines changed

src/Aspire.Cli/Aspire.Cli.csproj

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,11 @@
4747
</ItemGroup>
4848

4949
<ItemGroup>
50-
<Compile Include="$(SharedDir)KnownConfigNames.cs" Link="KnownConfigNames.cs" />
50+
<Compile Include="$(SharedDir)KnownConfigNames.cs" Link="Utils\KnownConfigNames.cs" />
5151
<Compile Include="$(SharedDir)PathNormalizer.cs" Link="Utils\PathNormalizer.cs" />
5252
<Compile Include="$(SharedDir)CircularBuffer.cs" Link="Utils\CircularBuffer.cs" />
53-
<Compile Include="$(SharedDir)StringComparers.cs" Link="StringComparers.cs" />
53+
<Compile Include="$(SharedDir)StringComparers.cs" Link="Utils\StringComparers.cs" />
54+
<Compile Include="$(SharedDir)LocaleHelpers.cs" Link="Utils\LocaleHelpers.cs" />
5455
<Compile Include="$(RepoRoot)src\Aspire.Hosting\Backchannel\BackchannelDataTypes.cs" Link="Backchannel\CliBackchannelDataTypes.cs" />
5556
<Compile Include="$(SharedDir)PackageUpdateHelpers.cs" Link="Utils\PackageUpdateHelpers.cs" />
5657
</ItemGroup>

src/Aspire.Cli/Program.cs

Lines changed: 41 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,40 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.CommandLine;
5-
using System.Diagnostics.CodeAnalysis;
65
using System.Globalization;
76
using System.Text;
87
using Aspire.Cli.Backchannel;
98
using Aspire.Cli.Certificates;
109
using Aspire.Cli.Commands;
10+
using Aspire.Cli.Configuration;
1111
using Aspire.Cli.Interaction;
12+
using Aspire.Cli.NuGet;
1213
using Aspire.Cli.Projects;
14+
using Aspire.Cli.Resources;
15+
using Aspire.Cli.Telemetry;
16+
using Aspire.Cli.Templating;
17+
using Aspire.Cli.Utils;
18+
using Aspire.Hosting;
19+
using Aspire.Shared;
20+
using Microsoft.Extensions.Configuration;
1321
using Microsoft.Extensions.DependencyInjection;
22+
using Microsoft.Extensions.DependencyInjection.Extensions;
1423
using Microsoft.Extensions.Hosting;
1524
using Microsoft.Extensions.Logging;
1625
using Spectre.Console;
17-
using Aspire.Cli.NuGet;
18-
using Aspire.Cli.Templating;
19-
using Aspire.Cli.Configuration;
26+
using RootCommand = Aspire.Cli.Commands.RootCommand;
2027
using Aspire.Cli.DotNet;
21-
using Aspire.Cli.Resources;
22-
using Aspire.Hosting;
23-
using Microsoft.Extensions.DependencyInjection.Extensions;
24-
using Aspire.Cli.Utils;
25-
using Aspire.Cli.Telemetry;
26-
using Microsoft.Extensions.Configuration;
2728

2829
#if DEBUG
2930
using OpenTelemetry;
3031
using OpenTelemetry.Resources;
3132
using OpenTelemetry.Trace;
3233
#endif
3334

34-
using RootCommand = Aspire.Cli.Commands.RootCommand;
35-
3635
namespace Aspire.Cli;
3736

3837
public class Program
3938
{
40-
4139
private static string GetGlobalSettingsPath()
4240
{
4341
var homeDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
@@ -61,18 +59,20 @@ private static async Task<IHost> BuildApplicationAsync(string[] args)
6159
var workingDirectory = new DirectoryInfo(Environment.CurrentDirectory);
6260
ConfigurationHelper.RegisterSettingsFiles(builder.Configuration, workingDirectory, globalSettingsFile);
6361

64-
await TrySetLocaleOverrideAsync(builder.Configuration);
62+
await TrySetLocaleOverrideAsync(LocaleHelpers.GetLocaleOverride(builder.Configuration));
6563

6664
// Always configure OpenTelemetry.
67-
builder.Logging.AddOpenTelemetry(logging => {
65+
builder.Logging.AddOpenTelemetry(logging =>
66+
{
6867
logging.IncludeFormattedMessage = true;
6968
logging.IncludeScopes = true;
7069
});
7170

7271
#if DEBUG
7372
var otelBuilder = builder.Services
7473
.AddOpenTelemetry()
75-
.WithTracing(tracing => {
74+
.WithTracing(tracing =>
75+
{
7676
tracing.AddSource(AspireCliTelemetry.ActivitySourceName);
7777

7878
tracing.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("aspire-cli"));
@@ -132,6 +132,31 @@ private static async Task<IHost> BuildApplicationAsync(string[] args)
132132
return app;
133133
}
134134

135+
private static async Task TrySetLocaleOverrideAsync(string? localeOverride)
136+
{
137+
if (localeOverride is not null)
138+
{
139+
var result = LocaleHelpers.TrySetLocaleOverride(localeOverride);
140+
141+
string errorMessage;
142+
switch (result)
143+
{
144+
case SetLocaleResult.Success:
145+
return;
146+
case SetLocaleResult.InvalidLocale:
147+
errorMessage = string.Format(CultureInfo.CurrentCulture, ErrorStrings.UnsupportedLocaleProvided, localeOverride, string.Join(", ", LocaleHelpers.SupportedLocales));
148+
break;
149+
case SetLocaleResult.UnsupportedLocale:
150+
errorMessage = string.Format(CultureInfo.CurrentCulture, ErrorStrings.InvalidLocaleProvided, localeOverride);
151+
break;
152+
default:
153+
throw new InvalidOperationException($"Unexpected result: {result}");
154+
}
155+
156+
await Console.Error.WriteLineAsync(errorMessage);
157+
}
158+
}
159+
135160
private static IConfigurationService BuildConfigurationService(IServiceProvider serviceProvider)
136161
{
137162
var configuration = serviceProvider.GetRequiredService<IConfiguration>();
@@ -220,48 +245,4 @@ private static void AddInteractionServices(HostApplicationBuilder builder)
220245
builder.Services.AddSingleton<IInteractionService, ConsoleInteractionService>();
221246
}
222247
}
223-
224-
private static readonly string[] s_supportedLocales = ["en", "cs", "de", "es", "fr", "it", "ja", "ko", "pl", "pt-BR", "ru", "tr", "zh-CN", "zh-TW"];
225-
226-
private static async Task TrySetLocaleOverrideAsync(ConfigurationManager configuration)
227-
{
228-
var localeOverride = configuration[KnownConfigNames.CliLocaleOverride]
229-
// also support DOTNET_CLI_UI_LANGUAGE as it's a common dotnet environment variable
230-
?? configuration[KnownConfigNames.DotnetCliUiLanguage];
231-
if (localeOverride is not null)
232-
{
233-
if (!TrySetLocaleOverride(localeOverride, out var errorMessage))
234-
{
235-
await Console.Error.WriteLineAsync(errorMessage);
236-
}
237-
}
238-
239-
return;
240-
241-
static bool TrySetLocaleOverride(string localeOverride, [NotNullWhen(false)] out string? errorMessage)
242-
{
243-
try
244-
{
245-
var cultureInfo = new CultureInfo(localeOverride);
246-
if (s_supportedLocales.Contains(cultureInfo.Name) ||
247-
s_supportedLocales.Contains(cultureInfo.TwoLetterISOLanguageName))
248-
{
249-
CultureInfo.CurrentUICulture = cultureInfo;
250-
CultureInfo.CurrentCulture = cultureInfo;
251-
CultureInfo.DefaultThreadCurrentCulture = cultureInfo;
252-
CultureInfo.DefaultThreadCurrentUICulture = cultureInfo;
253-
errorMessage = null;
254-
return true;
255-
}
256-
257-
errorMessage = string.Format(CultureInfo.CurrentCulture, ErrorStrings.UnsupportedLocaleProvided, localeOverride, string.Join(", ", s_supportedLocales));
258-
return false;
259-
}
260-
catch (CultureNotFoundException)
261-
{
262-
errorMessage = string.Format(CultureInfo.CurrentCulture, ErrorStrings.InvalidLocaleProvided, localeOverride);
263-
return false;
264-
}
265-
}
266-
}
267248
}

src/Aspire.Dashboard/Aspire.Dashboard.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@
251251

252252
<ItemGroup>
253253
<Compile Include="$(SharedDir)ChannelExtensions.cs" Link="Extensions\ChannelExtensions.cs" />
254+
<Compile Include="$(SharedDir)LocaleHelpers.cs" Link="Utils\LocaleHelpers.cs" />
254255
<Compile Include="$(SharedDir)CompareHelpers.cs" Link="Utils\CompareHelpers.cs" />
255256
<Compile Include="$(SharedDir)IConfigurationExtensions.cs" Link="Utils\IConfigurationExtensions.cs" />
256257
<Compile Include="$(SharedDir)KnownConfigNames.cs" Link="Utils\KnownConfigNames.cs" />

src/Aspire.Dashboard/Utils/GlobalizationHelpers.cs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Diagnostics.CodeAnalysis;
55
using System.Globalization;
6+
using Aspire.Shared;
67
using Microsoft.AspNetCore.Localization;
78
using Microsoft.Extensions.Logging.Abstractions;
89
using Microsoft.Extensions.Options;
@@ -21,13 +22,7 @@ internal static class GlobalizationHelpers
2122

2223
static GlobalizationHelpers()
2324
{
24-
// our localization list comes from https://github.com/dotnet/arcade/blob/89008f339a79931cc49c739e9dbc1a27c608b379/src/Microsoft.DotNet.XliffTasks/build/Microsoft.DotNet.XliffTasks.props#L22
25-
var localizedCultureNames = new string[]
26-
{
27-
"en", "cs", "de", "es", "fr", "it", "ja", "ko", "pl", "pt-BR", "ru", "tr", "zh-Hans", "zh-Hant", // Standard cultures for compliance.
28-
};
29-
30-
var localizedCultureInfos = localizedCultureNames.Select(CultureInfo.GetCultureInfo).ToList();
25+
var localizedCultureInfos = LocaleHelpers.SupportedLocales.Select(CultureInfo.GetCultureInfo).ToList();
3126

3227
AllCultures = GetAllCultures();
3328

src/Aspire.Hosting.Azure.Functions/Aspire.Hosting.Azure.Functions.csproj

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<TargetFramework>$(DefaultTargetFramework)</TargetFramework>
@@ -22,18 +22,18 @@
2222
</ItemGroup>
2323

2424
<ItemGroup>
25-
<Compile Include="..\Aspire.Hosting\Properties\Resources.Designer.cs">
26-
<Link>Properties\Resources.Designer.cs</Link>
25+
<Compile Include="..\Aspire.Hosting\Resources\LaunchProfileStrings.Designer.cs">
26+
<Link>Resources\LaunchProfileStrings.Designer.cs</Link>
2727
<AutoGen>True</AutoGen>
2828
<DesignTime>True</DesignTime>
29-
<DependentUpon>Resources.resx</DependentUpon>
29+
<DependentUpon>LaunchProfileStrings.resx</DependentUpon>
3030
</Compile>
31-
<EmbeddedResource Include="..\Aspire.Hosting\Properties\Resources.resx">
32-
<Link>Properties\Resources.resx</Link>
31+
<EmbeddedResource Include="..\Aspire.Hosting\Resources\LaunchProfileStrings.resx">
32+
<Link>Resources\LaunchProfileStrings.resx</Link>
3333
<Generator>ResXFileCodeGenerator</Generator>
34-
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
35-
<CustomToolNamespace>Aspire.Hosting.Properties</CustomToolNamespace>
36-
<LogicalName>Aspire.Hosting.Properties.Resources.resources</LogicalName>
34+
<LastGenOutput>LaunchProfileStrings.Designer.cs</LastGenOutput>
35+
<CustomToolNamespace>Aspire.Hosting.Resources</CustomToolNamespace>
36+
<LogicalName>Aspire.Hosting.Resources.LaunchProfileStrings.resources</LogicalName>
3737
</EmbeddedResource>
3838
</ItemGroup>
3939

src/Aspire.Hosting.Testing/DistributedApplicationHostingTestingExtensions.cs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
using System.Globalization;
44
using Aspire.Hosting.ApplicationModel;
5-
using Aspire.Hosting.Testing.Properties;
65
using Microsoft.Extensions.DependencyInjection;
76
using Microsoft.Extensions.Hosting;
87

@@ -49,7 +48,7 @@ public static HttpClient CreateHttpClient(this DistributedApplication app, strin
4948
var resource = GetResource(app, resourceName);
5049
if (resource is not IResourceWithConnectionString resourceWithConnectionString)
5150
{
52-
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, Resources.ResourceDoesNotExposeConnectionStringExceptionMessage, resourceName), nameof(resourceName));
51+
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, Properties.Resources.ResourceDoesNotExposeConnectionStringExceptionMessage, resourceName), nameof(resourceName));
5352
}
5453

5554
return resourceWithConnectionString.GetConnectionStringAsync(cancellationToken);
@@ -82,7 +81,7 @@ static IResource GetResource(DistributedApplication app, string resourceName)
8281

8382
if (resource is null)
8483
{
85-
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, Resources.ResourceNotFoundExceptionMessage, resourceName), nameof(resourceName));
84+
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, Properties.Resources.ResourceNotFoundExceptionMessage, resourceName), nameof(resourceName));
8685
}
8786

8887
return resource;
@@ -93,7 +92,7 @@ static string GetEndpointUriStringCore(DistributedApplication app, string resour
9392
var resource = GetResource(app, resourceName);
9493
if (resource is not IResourceWithEndpoints resourceWithEndpoints)
9594
{
96-
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, Resources.ResourceHasNoAllocatedEndpointsExceptionMessage, resourceName), nameof(resourceName));
95+
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, Properties.Resources.ResourceHasNoAllocatedEndpointsExceptionMessage, resourceName), nameof(resourceName));
9796
}
9897

9998
EndpointReference? endpoint;
@@ -108,7 +107,7 @@ static string GetEndpointUriStringCore(DistributedApplication app, string resour
108107

109108
if (endpoint is null)
110109
{
111-
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, Resources.EndpointForResourceNotFoundExceptionMessage, endpointName, resourceName), nameof(endpointName));
110+
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, Properties.Resources.EndpointForResourceNotFoundExceptionMessage, endpointName, resourceName), nameof(endpointName));
112111
}
113112

114113
return endpoint.Url;
@@ -119,7 +118,7 @@ static void ThrowIfNotStarted(DistributedApplication app)
119118
var lifetime = app.Services.GetRequiredService<IHostApplicationLifetime>();
120119
if (!lifetime.ApplicationStarted.IsCancellationRequested)
121120
{
122-
throw new InvalidOperationException(Resources.ApplicationNotStartedExceptionMessage);
121+
throw new InvalidOperationException(Properties.Resources.ApplicationNotStartedExceptionMessage);
123122
}
124123
}
125124

src/Aspire.Hosting/ApplicationModel/CommandsConfigurationExtensions.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using Aspire.Hosting.Dashboard;
55
using Aspire.Hosting.Orchestrator;
6+
using Aspire.Hosting.Resources;
67
using Microsoft.Extensions.DependencyInjection;
78

89
namespace Aspire.Hosting.ApplicationModel;
@@ -18,7 +19,7 @@ internal static void AddLifeCycleCommands(this IResource resource)
1819

1920
resource.Annotations.Add(new ResourceCommandAnnotation(
2021
name: KnownResourceCommands.StartCommand,
21-
displayName: "Start",
22+
displayName: CommandStrings.StartName,
2223
executeCommand: async context =>
2324
{
2425
var orchestrator = context.ServiceProvider.GetRequiredService<ApplicationOrchestrator>();
@@ -42,7 +43,7 @@ internal static void AddLifeCycleCommands(this IResource resource)
4243
return ResourceCommandState.Hidden;
4344
}
4445
},
45-
displayDescription: "Start resource",
46+
displayDescription: CommandStrings.StartDescription,
4647
parameter: null,
4748
confirmationMessage: null,
4849
iconName: "Play",
@@ -51,7 +52,7 @@ internal static void AddLifeCycleCommands(this IResource resource)
5152

5253
resource.Annotations.Add(new ResourceCommandAnnotation(
5354
name: KnownResourceCommands.StopCommand,
54-
displayName: "Stop",
55+
displayName: CommandStrings.StopName,
5556
executeCommand: async context =>
5657
{
5758
var orchestrator = context.ServiceProvider.GetRequiredService<ApplicationOrchestrator>();
@@ -75,7 +76,7 @@ internal static void AddLifeCycleCommands(this IResource resource)
7576
return ResourceCommandState.Hidden;
7677
}
7778
},
78-
displayDescription: "Stop resource",
79+
displayDescription: CommandStrings.StopDescription,
7980
parameter: null,
8081
confirmationMessage: null,
8182
iconName: "Stop",
@@ -84,7 +85,7 @@ internal static void AddLifeCycleCommands(this IResource resource)
8485

8586
resource.Annotations.Add(new ResourceCommandAnnotation(
8687
name: KnownResourceCommands.RestartCommand,
87-
displayName: "Restart",
88+
displayName: CommandStrings.RestartName,
8889
executeCommand: async context =>
8990
{
9091
var orchestrator = context.ServiceProvider.GetRequiredService<ApplicationOrchestrator>();
@@ -105,7 +106,7 @@ internal static void AddLifeCycleCommands(this IResource resource)
105106
return ResourceCommandState.Enabled;
106107
}
107108
},
108-
displayDescription: "Restart resource",
109+
displayDescription: CommandStrings.RestartDescription,
109110
parameter: null,
110111
confirmationMessage: null,
111112
iconName: "ArrowCounterclockwise",

src/Aspire.Hosting/ApplicationModel/ContainerMountAnnotation.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Diagnostics;
5-
using Aspire.Hosting.Properties;
5+
using Aspire.Hosting.Resources;
66

77
namespace Aspire.Hosting.ApplicationModel;
88

@@ -25,18 +25,18 @@ public ContainerMountAnnotation(string? source, string target, ContainerMountTyp
2525
{
2626
if (string.IsNullOrEmpty(source))
2727
{
28-
throw new ArgumentNullException(nameof(source), Resources.ContainerMountBindMountsRequireSourceExceptionMessage);
28+
throw new ArgumentNullException(nameof(source), MessageStrings.ContainerMountBindMountsRequireSourceExceptionMessage);
2929
}
3030

3131
if (!Path.IsPathRooted(source))
3232
{
33-
throw new ArgumentException(Resources.ContainerMountBindMountsRequireRootedPaths, nameof(source));
33+
throw new ArgumentException(MessageStrings.ContainerMountBindMountsRequireRootedPaths, nameof(source));
3434
}
3535
}
3636

3737
if (type == ContainerMountType.Volume && string.IsNullOrEmpty(source) && isReadOnly)
3838
{
39-
throw new ArgumentException(Resources.ContainerMountAnonymousVolumesReadOnlyExceptionMessage, nameof(isReadOnly));
39+
throw new ArgumentException(MessageStrings.ContainerMountAnonymousVolumesReadOnlyExceptionMessage, nameof(isReadOnly));
4040
}
4141

4242
Source = source;

0 commit comments

Comments
 (0)