Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Blazor] Globalization & localization - correct async JS interop #32433

Merged
merged 4 commits into from
Apr 29, 2024
Merged
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
202 changes: 158 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,78 @@ 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)
guardrex marked this conversation as resolved.
Show resolved Hide resolved
{
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 +733,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 +760,86 @@ 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)
guardrex marked this conversation as resolved.
Show resolved Hide resolved
{
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 +930,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 +957,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 +987,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