Skip to content

Commit 6b15190

Browse files
authored
Make Blazor WASM respect current UI culture (#62905)
1 parent 0f963e9 commit 6b15190

File tree

5 files changed

+72
-12
lines changed

5 files changed

+72
-12
lines changed

src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyCultureProvider.cs

Lines changed: 29 additions & 11 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 System.Linq;
67
using System.Runtime.InteropServices.JavaScript;
78

89
namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting;
@@ -62,7 +63,7 @@ public virtual async ValueTask LoadCurrentCultureResourcesAsync()
6263
throw new PlatformNotSupportedException("This method is only supported in the browser.");
6364
}
6465

65-
var culturesToLoad = GetCultures(CultureInfo.CurrentCulture);
66+
var culturesToLoad = GetCultures(CultureInfo.CurrentCulture, CultureInfo.CurrentUICulture);
6667

6768
if (culturesToLoad.Length == 0)
6869
{
@@ -72,30 +73,47 @@ public virtual async ValueTask LoadCurrentCultureResourcesAsync()
7273
await WebAssemblyCultureProviderInterop.LoadSatelliteAssemblies(culturesToLoad);
7374
}
7475

75-
internal static string[] GetCultures(CultureInfo cultureInfo)
76+
internal static string[] GetCultures(CultureInfo cultureInfo, CultureInfo? uiCultureInfo = null)
7677
{
77-
var culturesToLoad = new List<string>();
78-
7978
// Once WASM is ready, we have to use .NET's assembly loading to load additional assemblies.
8079
// First calculate all possible cultures that the application might want to load. We do this by
8180
// starting from the current culture and walking up the graph of parents.
8281
// At the end of the the walk, we'll have a list of culture names that look like
8382
// [ "fr-FR", "fr" ]
84-
while (cultureInfo != null && cultureInfo != CultureInfo.InvariantCulture)
85-
{
86-
culturesToLoad.Add(cultureInfo.Name);
8783

88-
if (cultureInfo.Parent == cultureInfo)
84+
var culturesToLoad = GetCultureHierarchy(cultureInfo);
85+
if (cultureInfo != uiCultureInfo)
86+
{
87+
foreach (var culture in GetCultureHierarchy(uiCultureInfo))
8988
{
90-
break;
89+
if (!culturesToLoad.Contains(culture))
90+
{
91+
culturesToLoad = culturesToLoad.Append(culture);
92+
}
93+
// If the culture is in the list, we can break because we found the common parent.
94+
else
95+
{
96+
break;
97+
}
9198
}
92-
93-
cultureInfo = cultureInfo.Parent;
9499
}
95100

96101
return culturesToLoad.ToArray();
97102
}
98103

104+
private static IEnumerable<string> GetCultureHierarchy(CultureInfo? culture)
105+
{
106+
while (culture != CultureInfo.InvariantCulture && culture != null)
107+
{
108+
yield return culture.Name;
109+
if (culture == culture.Parent)
110+
{
111+
break;
112+
}
113+
culture = culture.Parent;
114+
}
115+
}
116+
99117
private partial class WebAssemblyCultureProviderInterop
100118
{
101119
[JSImport("INTERNAL.loadSatelliteAssemblies")]

src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyCultureProviderTest.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,21 @@ public void GetCultures_ReturnsCultureClosure(string cultureName, string[] expec
2424
Assert.Equal(expected, actual);
2525
}
2626

27+
[Theory]
28+
[InlineData("fr-FR", "tzm-Latn-DZ", new[] { "fr-FR", "fr", "tzm-Latn-DZ", "tzm-Latn", "tzm" })]
29+
[InlineData("en-US", "en-GB", new[] { "en-US", "en", "en-GB" })]
30+
[InlineData("fr-FR", null, new[] { "fr-FR", "fr" })]
31+
public void GetCultures_ReturnCultureClosureWithUICulture(string cultureName, string uiCultureName, string[] expected)
32+
{
33+
// Arrange
34+
var culture = cultureName != null ? new CultureInfo(cultureName) : null;
35+
var uiCulture = uiCultureName != null ? new CultureInfo(uiCultureName) : null;
36+
// Act
37+
var actual = WebAssemblyCultureProvider.GetCultures(culture, uiCulture);
38+
// Assert
39+
Assert.Equal(expected, actual);
40+
}
41+
2742
[Fact]
2843
public void ThrowIfCultureChangeIsUnsupported_ThrowsIfCulturesAreDifferentAndICUShardingIsUsed()
2944
{

src/Components/test/E2ETest/Tests/WebAssemblyLocalizationTest.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,23 @@ public void CanSetCultureAndReadLocalizedResources(string culture, string messag
3535
var messageDisplay = Browser.Exists(By.Id("message-display"));
3636
Assert.Equal(message, messageDisplay.Text);
3737
}
38+
39+
[Theory]
40+
[InlineData("en-US", "fr-FR", "Bonjour!")]
41+
[InlineData("fr-FR", "en-US", "Hello!")]
42+
public void CanSetCultureAndDifferentiateBetweenCurrentAndUICulture(string culture, string cultureUI, string message)
43+
{
44+
Navigate($"{ServerPathBase}/?culture={culture}&cultureUI={cultureUI}");
45+
46+
Browser.MountTestComponent<LocalizedText>();
47+
48+
var cultureDisplay = Browser.Exists(By.Id("culture-name-display"));
49+
Assert.Equal($"Culture is: {culture}", cultureDisplay.Text);
50+
51+
var cultureUIDisplay = Browser.Exists(By.Id("culture-ui-display"));
52+
Assert.Equal($"CultureUI is: {cultureUI}", cultureUIDisplay.Text);
53+
54+
var messageDisplay = Browser.Exists(By.Id("message-display"));
55+
Assert.Equal(message, messageDisplay.Text);
56+
}
3857
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
<h3 id="culture-name-display">Culture is: @System.Globalization.CultureInfo.CurrentCulture.Name</h3>
2+
<h3 id="culture-ui-display">CultureUI is: @System.Globalization.CultureInfo.CurrentUICulture.Name</h3>
23
<p id="message-display">@Resources.Message</p>

src/Components/test/testassets/BasicTestApp/Program.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ private static void ConfigureCulture(WebAssemblyHost host)
6363
{
6464
// In the absence of a specified value, we want the culture to be en-US so that the tests for bind can work consistently.
6565
var culture = new CultureInfo("en-US");
66+
var cultureUI = new CultureInfo("en-US");
6667

6768
Uri uri = null;
6869
try
@@ -77,12 +78,18 @@ private static void ConfigureCulture(WebAssemblyHost host)
7778
if (uri != null && HttpUtility.ParseQueryString(uri.Query)["culture"] is string cultureName)
7879
{
7980
culture = new CultureInfo(cultureName);
81+
cultureUI = culture; // Default to the same culture for UI if not specified
82+
}
83+
84+
if (uri != null && HttpUtility.ParseQueryString(uri.Query)["cultureUI"] is string cultureUIName)
85+
{
86+
cultureUI = new CultureInfo(cultureUIName);
8087
}
8188

8289
// CultureInfo.CurrentCulture is async-scoped and will not affect the culture in sibling scopes.
8390
// Use CultureInfo.DefaultThreadCurrentCulture instead to modify the application's default scope.
8491
CultureInfo.DefaultThreadCurrentCulture = culture;
85-
CultureInfo.DefaultThreadCurrentUICulture = culture;
92+
CultureInfo.DefaultThreadCurrentUICulture = cultureUI;
8693
}
8794

8895
// Supports E2E tests in StartupErrorNotificationTest

0 commit comments

Comments
 (0)