-
Notifications
You must be signed in to change notification settings - Fork 25.2k
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βll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add BWA global Auto approach #31894
Add BWA global Auto approach #31894
Conversation
It's a long time from doing localization in Blazor :) but seems everything LGTM |
Co-authored-by: Hisham Bin Ateya <[email protected]>
Thanks @hishamco ... and that last commit was to update the client-only section earlier in the article where it also appears. What about my β in the opening comment βοΈ? ... any ideas on what I can say about those in the article? |
I need to check, but how I can produce the two formats? |
Run the sample app, set the culture to Chilean Spanish, and flip back and forth between the https://github.com/guardrex/BlazorCulturePerComponentInteractivity SSR ( ... and if you access the |
I will check later, if this will not postponed this PR |
It can definitely wait. I wasn't going to merge it until Monday at the earliest because it needs to be edited again. ... and it could wait until after Monday, too. There's no super rush on this work. |
@hishamco ... Are you still too busy to review? I could just go ahead with this and take reader feedback on this. I'm sure readers will let me know if something goes wrong ... they'll sharpen their BEAKS and CLAWS for me π¦π¨ if this is buggy code π. I'm more concerned about the "Question 2" situation because devs are going to see that with this code. I'm aware after our chats and talking to Ilona that WASM app glob is a bit different (a subset of data and possibly behavior) than full-blown ASP.NET Core glob. I think I'll try to see if I can use a different language than Chilean Spanish to get a stable date format between server and client. That would be fine if I can use something else to clear that behavior from the example. I'll report back in a bit ............. |
|
Well ... I'm actually not too keen on Mexican Spanish because the number format (dot separator) is the same that we use in English ...
Chilean Spanish uses a comma separator, and I'd like to go with a lang that uses a different number format ...
|
OK ... I have one ...... π¨π· Costa Rica π (
|
Frankly, I was busy with Orchard Core and something else in my home :) I hope to find time this weekend, if you didn't merge this yet |
@guardrex I did a quick look for what you did, I'm asking why you didn't rely on the cookie that send by ASP.NET Core, this SHOULD work correctly in both Server & Client FYI we already did something similar long time back in Oqtane Framework |
Only because traditionally we didn't take that approach for WASM (standalone or hosted). We always pushed local storage for WASM; so I just assumed that after the Blazor bundle comes down and Auto components go with CSR, they would switch over to local storage. I get the impression from you now that Auto components reaching CSR should just navigate to the server controller and then be redirected back, which is how we've always managed the cookie for Blazor Server <8.0. I'll try again only adopting our Blazor Server approach (cookie) in a BWA, but I'm buried in call web API work right now and probably won't be able to get back to this until the middle of next week. I'll ping you back here then when I complete a new round of testing with a new test app. |
@hishamco ... I was able to fix up the test app a bit. However, it still seems like using local storage is the best way to go for CSR because without it one is left reading the cookie directly via JS in order to set the culture. The latest version of the test app is here π https://github.com/guardrex/BlazorCulturePerComponentInteractivity If you're too busy to look, no worries ... I'll ping Mackinnon to take a look. |
I think both read from JS , right? |
I think only on the client (CSR) is when the culture is read from local storage via JS interop. I think for SSR that the Loc Middleware is reading the culture from the cookie. When the user changes the culture from an SSR component, the local storage is updated and they're thrown to the controller to update the cookie. The part that I'm not 100% clear on is what's happening when the culture selector is activated for CSR. If the component (and thus culture selector) are in SSR mode, it seems like it sets the culture in local storage and redirects to the controller and back ... which is updating the cookie.
SEE BELOW ... I think I understand it now. |
π€ ... I guess that's ok for CSR ... it sets the culture into local storage and redirects (for the cookie to be set correctly) ... and then on reload after coming back it sucks the changed culture in from local storage and rerenders the component CSR. AFAICT, this whole approach is just a bit hacky because there isn't a nice built-in way of managing things. I guess an alternative to local storage is to deal directly with the loc cookie in JS on the client. Idk if that's any better than this current approach. It would require as much or more code than this, and it would be perhaps equally inelegant as this approach. ... and I guess there's an alternate version of the current approach for CSR without the redirect where in the culture selector component ...
I guess the good news is that at least the current approach works and seems stable. |
I'll place the bits here in one spot to make it simpler to see the overall setup ... Server project of the BWASet up loc in the var supportedCultures = new[] { "en-US", "es-CR" };
var localizationOptions = new RequestLocalizationOptions()
.SetDefaultCulture(supportedCultures[0])
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures);
app.UseRequestLocalization(localizationOptions); Controller that can set the culture to the loc cookie and redirect back ... [Route("[controller]/[action]")]
public class CultureController : Controller
{
public IActionResult Set(string culture, string redirectUri)
{
if (culture != null)
{
HttpContext.Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(
new RequestCulture(culture, culture)));
}
return LocalRedirect(redirectUri);
}
} The window.blazorCulture = {
get: () => window.localStorage['BlazorCulture'],
set: (value) => window.localStorage['BlazorCulture'] = value
}; Client project of the BWAIn the CultureInfo culture;
var js = host.Services.GetRequiredService<IJSRuntime>();
var result = await js.InvokeAsync<string>("blazorCulture.get");
if (result != null)
{
culture = new CultureInfo(result);
}
else
{
culture = new CultureInfo("en-US");
await js.InvokeVoidAsync("blazorCulture.set", "en-US");
}
CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture; ... and here's the meat π ... the @using System.Globalization
@using System.Runtime.InteropServices
@inject IJSRuntime JS
@inject NavigationManager Navigation
<p>
<label>
Select your locale:
<select @bind="Culture">
@foreach (var culture in supportedCultures)
{
<option value="@culture">@cultureDict[culture.Name]</option>
}
</select>
</label>
</p>
@code
{
private Dictionary<string, string> cultureDict =
new()
{
{ "en-US", "English (United States)" },
{ "es-CR", "Spanish (Costa Rica)" }
};
private CultureInfo[] supportedCultures = new[]
{
new CultureInfo("en-US"),
new CultureInfo("es-CR"),
};
private CultureInfo Culture
{
get => CultureInfo.CurrentCulture;
set
{
if (CultureInfo.CurrentCulture != value)
{
JS.InvokeVoidAsync("blazorCulture.set", value.Name);
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);
}
}
}
} The basic idea is that regardless of rendering location, the culture is kept up-to-date in local storage, and the loc cookie is kept up-to-date via the controller. When going into CSR, local storage is read. When in SSR, the loc cookie is used. In order to avoid deltas on naming of the cultures between SSR and CSR for the dropdown list, the component uses a custom dictionary for the text displayed to the user. The example uses Costa Rica π¨π· Spanish because it changes both the date and number format the same way from English notwithstanding SSR/CSR. It makes a good demo for this ... and a mini-shoutout to our southern friends, whose good people have suffered quite a bit in recent years. AFAICT, this is how this approach should work. The only other alternatives that I could think of would be trying to deal with the loc cookie directly for CSR without a redirect, but it seems like it would be no better than this in terms of the amount of code and complexity of code (but it would potentially save the need for a redirect) ... IF it could be make to work cleanly. I tested a bit, and that approach had some π that didn't even let me get it running correctly. Another idea would be to maintain loc outside of the ASP.NET Core loc bits ... try to use some kind of service-based loc management. I didn't investigate it tho. |
@hishamco ... I'm going to go ahead with this. It's fairly close to what we had here before, merging the server and client approaches in a BWA for components rendering either way, server or client. It seems stable π€π. I propose to take reader feedback on it. I'm sure if devs run into trouble that I'll receive the normal threats of π¦ dismemberment π¨π. |
If you look closely
you set the cookie in the server, but you are using the local storage on the client, that's why I prefer to use the cookies everywhere |
Yes, but I don't understand how to get the cookie read when the app switches to CSR. I'll let Dan know that we need a PU engineer to build out the approach in a sample app for me. I'll ping you on the new issue that I open later with a cross-link to whatever they provide. |
I can create a demo if you want |
Yes, please do. BTW ... I don't know why you aren't on the article with a byline ("By Hisham Bin Ateya" ... cross-linked to your blog, X account, or company), but would you like a byline on the article with a cross-link? If so, what do you want it linked to? I opened a new issue to work further on the article, and I pinged you on it. Let's take up the discussion and work on that issue. |
Is the plan to add the sample and link it to our docs or refer to an actual blog post? |
Let's pick up with discussion on the issue. |
Fixes #30002
Addresses #28161
@hishamco ... I have another π RexHacks!β’ π¦ approach here. It SEEMS to work, but IDK if there are some πππ with what I did or some lurking πππ in here.
Because of the complexity of this, I thought you might want to pull it down and run it locally, so I placed my test app at ...
https://github.com/guardrex/BlazorCulturePerComponentInteractivity
Question 1
There is one thing here that I don't understand. When changing the culture from a CSR component (in the sample app, changing it from the
CultureClient
component), it does seem to update the localization cookie and carryover to the server-rendered component (in the sample app if you then navigate to theCultureServer
component). It's not clear to me where/how the localization cookie is being updated when that happens. The controller action in the server app isn't called when the culture is changed client-side, and I didn't think that Localization Middleware was reading the updated value from local storage. However, it seems to work. If you can see where/how the culture is being carried over to SSR, I'll add an explanation on it to the article text.Question 2
... and another βon the delta between the Chilean Spanish date format SSR vs. CSR.
SSR it's in the format ... 23-02-2024
CSR it's in the format ... 23/2/2024
Any idea on why that format is changing in an odd way? It's another odd behavioral difference that I'll explain in the article if it's known why it happens.
Internal previews