Skip to content

Commit

Permalink
[Blazor] Globalization & localization - correct async JS interop (#32433
Browse files Browse the repository at this point in the history
)
  • Loading branch information
hakenr committed Apr 29, 2024
1 parent 2d09f53 commit fb884a2
Showing 1 changed file with 160 additions and 44 deletions.
204 changes: 160 additions & 44 deletions aspnetcore/blazor/globalization-localization.md
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ const string defaultCulture = "en-US";

var js = host.Services.GetRequiredService<IJSRuntime>();
var result = await js.InvokeAsync<string>("blazorCulture.get");
var culture = CultureInfo.GetCulture(result ?? defaultCulture);
var culture = CultureInfo.GetCultureInfo(result ?? defaultCulture);

if (result == null)
{
Expand All @@ -458,15 +458,17 @@ The following `CultureSelector` component shows how to perform the following act

`CultureSelector.razor`:

:::moniker range=">= aspnetcore-7.0"

```razor
@using System.Globalization
@using System.Globalization
@inject IJSRuntime JS
@inject NavigationManager Navigation
<p>
<label>
Select your locale:
<select @bind="Culture">
<select @bind="selectedCulture" @bind:after="ApplySelectedCultureAsync">
@foreach (var culture in supportedCultures)
{
<option value="@culture">@culture.DisplayName</option>
Expand All @@ -483,25 +485,79 @@ The following `CultureSelector` component shows how to perform the following act
new CultureInfo("es-CR"),
};
private CultureInfo Culture
private CultureInfo? selectedCulture;
protected override void OnInitialized()
{
selectedCulture = CultureInfo.CurrentCulture;
}
private async Task ApplySelectedCultureAsync()
{
get => CultureInfo.CurrentCulture;
set
if (CultureInfo.CurrentCulture != selectedCulture)
{
if (CultureInfo.CurrentCulture != value)
{
var js = (IJSInProcessRuntime)JS;
js.InvokeVoid("blazorCulture.set", value.Name);
await JS.InvokeVoidAsync("blazorCulture.set", selectedCulture!.Name);
Navigation.NavigateTo(Navigation.Uri, forceLoad: true);
}
}
}
```

Navigation.NavigateTo(Navigation.Uri, forceLoad: true);
:::moniker-end

:::moniker range="< aspnetcore-7.0"

```razor
@using System.Globalization
@inject IJSRuntime JS
@inject NavigationManager Navigation
<p>
<label>
Select your locale:
<select value="@selectedCulture" @onchange="HandleSelectedCultureChanged">
@foreach (var culture in supportedCultures)
{
<option value="@culture">@culture.DisplayName</option>
}
</select>
</label>
</p>
@code
{
private CultureInfo[] supportedCultures = new[]
{
new CultureInfo("en-US"),
new CultureInfo("es-CR"),
};
private CultureInfo? selectedCulture;
protected override void OnInitialized()
{
selectedCulture = CultureInfo.CurrentCulture;
}
private async Task HandleSelectedCultureChanged(ChangeEventArgs args)
{
selectedCulture = CultureInfo.GetCultureInfo((string)args.Value!);
if (CultureInfo.CurrentCulture != selectedCulture)
{
await JS.InvokeVoidAsync("blazorCulture.set", selectedCulture!.Name);
Navigation.NavigateTo(Navigation.Uri, forceLoad: true);
}
}
}
```

:::moniker-end

> [!NOTE]
> For more information on <xref:Microsoft.JSInterop.IJSInProcessRuntime>, see <xref:blazor/js-interop/call-javascript-from-dotnet#synchronous-js-interop-in-client-side-components>.
> For more information on <xref:Microsoft.JSInterop.IJSInProcessRuntime>, see <xref:blazor/js-interop/call-javascript-from-dotnet#invoke-javascript-functions-without-reading-a-returned-value-invokevoidasync>.
Inside the closing tag of the `</main>` element in the `MainLayout` component (`MainLayout.razor`), add the `CultureSelector` component:

Expand Down Expand Up @@ -678,14 +734,17 @@ The following `CultureSelector` component shows how to call the `Set` method of

`CultureSelector.razor`:

:::moniker range=">= aspnetcore-7.0"

```razor
@using System.Globalization
@inject IJSRuntime JS
@inject NavigationManager Navigation
<p>
<label>
Select your locale:
<select @bind="Culture">
<select @bind="selectedCulture" @bind:after="ApplySelectedCultureAsync">
@foreach (var culture in supportedCultures)
{
<option value="@culture">@culture.DisplayName</option>
Expand All @@ -702,32 +761,87 @@ The following `CultureSelector` component shows how to call the `Set` method of
new CultureInfo("es-CR"),
};
private CultureInfo? selectedCulture;
protected override void OnInitialized()
{
Culture = CultureInfo.CurrentCulture;
selectedCulture = CultureInfo.CurrentCulture;
}
private CultureInfo Culture
private async Task ApplySelectedCultureAsync()
{
get => CultureInfo.CurrentCulture;
set
if (CultureInfo.CurrentCulture != selectedCulture)
{
if (CultureInfo.CurrentCulture != value)
var uri = new Uri(Navigation.Uri)
.GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
var cultureEscaped = Uri.EscapeDataString(selectedCulture.Name);
var uriEscaped = Uri.EscapeDataString(uri);
Navigation.NavigateTo(
$"Culture/Set?culture={cultureEscaped}&redirectUri={uriEscaped}",
forceLoad: true);
}
}
}
```

:::moniker-end

:::moniker range="< aspnetcore-7.0"

```razor
@using System.Globalization
@inject IJSRuntime JS
@inject NavigationManager Navigation
<p>
<label>
Select your locale:
<select value="@selectedCulture" @onchange="HandleSelectedCultureChanged">
@foreach (var culture in supportedCultures)
{
var uri = new Uri(Navigation.Uri)
.GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
var cultureEscaped = Uri.EscapeDataString(value.Name);
var uriEscaped = Uri.EscapeDataString(uri);
Navigation.NavigateTo(
$"Culture/Set?culture={cultureEscaped}&redirectUri={uriEscaped}",
forceLoad: true);
<option value="@culture">@culture.DisplayName</option>
}
</select>
</label>
</p>
@code
{
private CultureInfo[] supportedCultures = new[]
{
new CultureInfo("en-US"),
new CultureInfo("es-CR"),
};
private CultureInfo? selectedCulture;
protected override void OnInitialized()
{
selectedCulture = CultureInfo.CurrentCulture;
}
private async Task HandleSelectedCultureChanged(ChangeEventArgs args)
{
selectedCulture = CultureInfo.GetCultureInfo((string)args.Value!);
if (CultureInfo.CurrentCulture != selectedCulture)
{
var uri = new Uri(Navigation.Uri)
.GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
var cultureEscaped = Uri.EscapeDataString(selectedCulture.Name);
var uriEscaped = Uri.EscapeDataString(uri);
Navigation.NavigateTo(
$"Culture/Set?culture={cultureEscaped}&redirectUri={uriEscaped}",
forceLoad: true);
}
}
}
```

:::moniker-end

:::moniker range=">= aspnetcore-8.0"

Add the `CultureSelector` component to the `MainLayout` component. Place the following markup inside the closing `</main>` tag in the `Components/Layout/MainLayout.razor` file:
Expand Down Expand Up @@ -818,7 +932,7 @@ const string defaultCulture = "en-US";

var js = host.Services.GetRequiredService<IJSRuntime>();
var result = await js.InvokeAsync<string>("blazorCulture.get");
var culture = CultureInfo.GetCulture(result ?? defaultCulture);
var culture = CultureInfo.GetCultureInfo(result ?? defaultCulture);

if (result == null)
{
Expand All @@ -845,14 +959,13 @@ The component adopts the following approaches to work for either SSR or CSR comp

```razor
@using System.Globalization
@using System.Runtime.InteropServices
@inject IJSRuntime JS
@inject NavigationManager Navigation
<p>
<label>
Select your locale:
<select @bind="Culture">
<select @bind="@selectedCulture" @bind:after="ApplySelectedCultureAsync">
@foreach (var culture in supportedCultures)
{
<option value="@culture">@cultureDict[culture.Name]</option>
Expand All @@ -876,31 +989,34 @@ The component adopts the following approaches to work for either SSR or CSR comp
new CultureInfo("es-CR"),
};
private CultureInfo Culture
private CultureInfo? selectedCulture;
protected override void OnInitialized()
{
selectedCulture = CultureInfo.CurrentCulture;
}
private async Task ApplySelectedCultureAsync()
{
get => CultureInfo.CurrentCulture;
set
if (CultureInfo.CurrentCulture != selectedCulture)
{
if (CultureInfo.CurrentCulture != value)
{
JS.InvokeVoidAsync("blazorCulture.set", value.Name);
await JS.InvokeVoidAsync("blazorCulture.set", selectedCulture!.Name);
var uri = new Uri(Navigation.Uri)
.GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
var cultureEscaped = Uri.EscapeDataString(value.Name);
var uriEscaped = Uri.EscapeDataString(uri);
var uri = new Uri(Navigation.Uri)
.GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
var cultureEscaped = Uri.EscapeDataString(selectedCulture.Name);
var uriEscaped = Uri.EscapeDataString(uri);
Navigation.NavigateTo(
$"Culture/Set?culture={cultureEscaped}&redirectUri={uriEscaped}",
forceLoad: true);
}
Navigation.NavigateTo(
$"Culture/Set?culture={cultureEscaped}&redirectUri={uriEscaped}",
forceLoad: true);
}
}
}
```

> [!NOTE]
> For more information on <xref:Microsoft.JSInterop.IJSInProcessRuntime>, see <xref:blazor/js-interop/call-javascript-from-dotnet#synchronous-js-interop-in-client-side-components>.
> For more information on <xref:Microsoft.JSInterop.IJSInProcessRuntime>, see <xref:blazor/js-interop/call-javascript-from-dotnet#invoke-javascript-functions-without-reading-a-returned-value-invokevoidasync>.
In the `.Client` project, place the following `CultureClient` component to study how globalization works for CSR components.

Expand Down

0 comments on commit fb884a2

Please sign in to comment.