Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion src/Aspire.Dashboard/Components/App.razor
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="@System.Globalization.CultureInfo.CurrentUICulture.Name">

<head>
<meta charset="utf-8" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@
<div class="input-container">
<FluentSelect
TOption="CultureInfo"
Immediate="true"
ChangeOnEnterOnly="true"
Position="SelectPosition.Below"
Label="@Loc[nameof(Dialogs.SettingsDialogLanguage)]"
Items="@_languageOptions"
OptionValue="@(c => c!.Name)"
OptionText="@(c => c!.NativeName.Humanize())"
ValueChanged="@ValueChanged"
@bind-SelectedOption="@_selectedUiCulture"
Expand Down
31 changes: 28 additions & 3 deletions src/Shared/LocaleHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Aspire.Shared;
internal static class LocaleHelpers
{
// our localization list comes from https://github.com/dotnet/arcade/blob/89008f339a79931cc49c739e9dbc1a27c608b379/src/Microsoft.DotNet.XliffTasks/build/Microsoft.DotNet.XliffTasks.props#L22
public static readonly string[] SupportedLocales = ["en", "cs", "de", "es", "fr", "it", "ja", "ko", "pl", "pt-BR", "ru", "tr", "zh-CN", "zh-Hans", "zh-Hant"];
public static readonly string[] SupportedLocales = ["en", "cs", "de", "es", "fr", "it", "ja", "ko", "pl", "pt-BR", "ru", "tr", "zh-Hans", "zh-Hant"];

public static SetLocaleResult TrySetLocaleOverride(string localeOverride)
{
Expand All @@ -24,8 +24,7 @@ public static SetLocaleResult TrySetLocaleOverride(string localeOverride)
try
{
var cultureInfo = new CultureInfo(localeOverride);
if (SupportedLocales.Contains(cultureInfo.Name) ||
SupportedLocales.Contains(cultureInfo.TwoLetterISOLanguageName))
if (IsSupportedCulture(cultureInfo))
{
CultureInfo.CurrentUICulture = cultureInfo;
CultureInfo.CurrentCulture = cultureInfo;
Expand All @@ -42,6 +41,32 @@ public static SetLocaleResult TrySetLocaleOverride(string localeOverride)
}
}

private static bool IsSupportedCulture(CultureInfo cultureInfo)
{
// Check exact name and two-letter ISO language name first.
if (SupportedLocales.Contains(cultureInfo.Name) ||
SupportedLocales.Contains(cultureInfo.TwoLetterISOLanguageName))
{
return true;
}

// Walk the parent chain to find a supported culture.
// For example, zh-CN's parent is zh-Hans which is supported.
var current = cultureInfo.Parent;
var depth = 0;
while (current != CultureInfo.InvariantCulture && depth < 5)
{
if (SupportedLocales.Contains(current.Name))
{
return true;
}
current = current.Parent;
depth++;
}
Comment on lines +55 to +67
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IsSupportedCulture hard-codes the parent-chain depth limit (depth < 5). This makes the rationale less clear and duplicates the same limit used elsewhere (e.g., GlobalizationHelpers). Consider extracting this into a named constant (and/or breaking on current == current.Parent) so the loop’s termination condition is self-documenting and easier to adjust in one place.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! Extracted MaxCultureParentDepth as a named constant (matching GlobalizationHelpers) and added a current != current.Parent guard against circular parent chains.


return false;
}

private static bool IsKnownCulture(string cultureName)
{
return CultureInfo
Expand Down
13 changes: 13 additions & 0 deletions tests/Aspire.Dashboard.Tests/GlobalizationHelpersTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Globalization;
using Aspire.Dashboard.Utils;
using Humanizer;
using Xunit;

namespace Aspire.Dashboard.Tests;
Expand Down Expand Up @@ -30,6 +31,16 @@ public void ExpandedLocalizedCultures_IncludesPopularCultures()
Assert.Contains("zh-CN", supportedCultures);
}

[Fact]
public void OrderedLocalizedCultures_HasUniqueDisplayNames()
{
var displayNames = GlobalizationHelpers.OrderedLocalizedCultures
.Select(c => c.NativeName.Humanize())
.ToList();

Assert.Equal(displayNames.Count, displayNames.Distinct().Count());
}

[Theory]
[InlineData("en", true, "en")]
[InlineData("en-US", true, "en")]
Expand Down Expand Up @@ -71,6 +82,8 @@ public void TryGetKnownParentCulture_VariousCultures_ReturnsExpectedResult(strin
[InlineData("en", "en-XX", "en")]
[InlineData("de", "en-US", null)]
[InlineData("zh-Hans", "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7", "zh-CN")]
[InlineData("zh-Hans", "zh-CN", "zh-CN")]
[InlineData("zh-Hans", "zh-CN,zh;q=0.9", "zh-CN")]
public async Task ResolveSetCultureToAcceptedCultureAsync_MatchRequestToResult(string requestedLanguage, string acceptLanguage, string? result)
{
// Arrange
Expand Down
Loading