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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Blazor Web App glob/loc approach? #32020

Open
guardrex opened this issue Mar 10, 2024 · 20 comments
Open

Update Blazor Web App glob/loc approach? #32020

guardrex opened this issue Mar 10, 2024 · 20 comments

Comments

@guardrex
Copy link
Collaborator

Description

cc: @hishamco ... I'll work off this issue. Please post your sample cross-link here and let me know if you want a byline (and what cross-link to use for it if so).

Page URL

https://learn.microsoft.com/en-us/aspnet/core/blazor/globalization-localization?view=aspnetcore-8.0

Content source URL

https://github.com/dotnet/AspNetCore.Docs/blob/main/aspnetcore/blazor/globalization-localization.md

Document ID

d6f07538-228e-9f96-680f-6c324caf11d6

Article author

@guardrex

@guardrex guardrex added Source - Docs.ms Docs Customer feedback via GitHub Issue ⌚ Not Triaged labels Mar 10, 2024
@github-project-automation github-project-automation bot moved this to Triage in Blazor.Docs Mar 10, 2024
@guardrex guardrex added Pri1 doc-enhancement 8.0 .NET 8 and removed Source - Docs.ms Docs Customer feedback via GitHub Issue labels Mar 10, 2024
@guardrex guardrex moved this from Triage to 8.0 in Blazor.Docs Mar 10, 2024
@dotnet dotnet deleted a comment from github-actions bot Mar 10, 2024
@hishamco
Copy link
Member

Please let me know all your requirements, hopefully I will do quick one when the time permits

@guardrex
Copy link
Collaborator Author

guardrex commented Mar 10, 2024

Blazor Web App that allows the user to set the culture via a drop-down list from a set of selected cultures ... the app must persist the culture across SSR and CSR transitions between SSR/CSR/prerendering components.

If it's quicker for you, you can start with the one that I created and just modify it. It has the server/WASM/Auto components already in it and preconfigures for en-US and es-CR (Costa Rica), which work well for demo because the number and date formats change the same way between SSR and CSR glob/loc.

https://github.com/guardrex/BlazorCulturePerComponentInteractivity

Is the plan to add the sample and link it to our docs or refer to an actual blog post?

I'll place a sample app in the Blazor samples repo, and the article will explain basically how it works. You only need to provide the sample app. My problem is just that I'm too dumb of a 🦖 to figure it out 😆. I can write it up for the article and place the sample if I can just see how to set it up.

I can add you as a byline author "By ..." and link it to whatever you want if that's helpful to you.

@guardrex
Copy link
Collaborator Author

guardrex commented Mar 20, 2024

@hishamco ... I take it that you're still busy there? You could let me know what your idea is for CSR and the loc cookie to make it work? I can try to implement your idea.

Is your idea to interact directly with the loc cookie in the .Client project for CSR via JS interop? That's the only thing that I could think of to get CSR components to pick up the correct culture from the cookie. I tried it and failed to get that approach to work 🙈 ... but I could try again with fresh eyes and see if I can make it work.

If you have a different idea that doesn't rely on JS interop, what's your idea to make it work?

@hishamco
Copy link
Member

I tried it and failed to get that approach to work 🙈 ... but I could try again with fresh eyes and see if I can make it work.

Do you have your demo in a public repo, so I can start from where you stop instead of doing it from scratch

BTW I was sick for week or so, that's why I'm active at that time

@guardrex
Copy link
Collaborator Author

https://github.com/guardrex/BlazorCulturePerComponentInteractivity

@hishamco
Copy link
Member

I think the sample works well for both SSR & CSR, right? What I remember from our last conversation is trying the cookie approach, right?

@hishamco
Copy link
Member

After a quick look seems you are using both cookies & local storage

@guardrex
Copy link
Collaborator Author

Yes, it works by keeping both the loc cookie and local storage up-to-date. That way, when it's rendering a component server-side, it goes off the loc cookie; and when it's rendering client-side, it goes off of local storage. Due to the differences in the availability of naming server- versus client-side, I use a dictionary for the dropdown list text so that it appears the same way to the user.

It doesn't rely on the loc cookie for CSR. When I investigated trying to get the loc cookie to be used via CSR, the only approach that seemed like it might work would be to interact with the cookie directly via JS interop. However, I tried to make that work and couldn't get it working properly.

@hishamco
Copy link
Member

Let me check the CSR and make the cookie works as well

@hishamco
Copy link
Member

Shall I submit a PR to your repo?

@guardrex
Copy link
Collaborator Author

Sure ... that's cool.

@hishamco
Copy link
Member

Check the PR guardrex/BlazorCulturePerComponentInteractivity#1 and let me know if you have comments

@guardrex
Copy link
Collaborator Author

guardrex commented Mar 24, 2024

Yes ... that was about what I was trying to do and didn't quite get it working.

I'll take a closer look on Monday. If it's all good, I'll update the article section from the example.

If you want an author byline on the topic, let me know what you want it linked to.

UPDATE > "I'll take a closer look on Monday"

It will be either Tuesday or Wednesday. I'm bogged down at the moment in security bits.

@hishamco
Copy link
Member

If there's anything to help please let me know

@guardrex guardrex moved this from 8.0 to In progress in Blazor.Docs Mar 26, 2024
@guardrex
Copy link
Collaborator Author

@hishamco ... I just looked it over.

So, the change you made was to basically swap interacting directly with the cookie for local storage.

However, this seems less elegant to me. The cookie approach ...

  • Requires more code (and messy code because it's more work to deal with the cookie), as shown below.
  • It requires that the client app reference an additional package (Microsoft.AspNetCore.Localization).
  • It seems to use values for the loc cookie for CSR that are different from the server-based loc cookie. For English, wouldn't the normal server-side cookie value be c=en-US|uic=en-US, not just en-US?
  • Will make the CSR parts of a BWA less like what we're pitching for pure standalone WASM, which uses local storage.
  • Doesn't help shed use of the controller under CSR to set the cookie server-side so that server-side loc can pick up its value. We're still stuck having to run code locally via JS interop and toss back and forth the server, so there's no improvement for that aspect.

Why would the cookie approach be better than local storage? If there isn't a compelling reason to change, I'd kind'a like to stick with local storage for CSR, as the topic already covers ...

https://learn.microsoft.com/aspnet/core/blazor/globalization-localization#dynamically-set-the-culture-in-a-blazor-web-app-by-user-preference

Local Storage approach Cookie approach
window.blazorCulture = {
get: () => window.localStorage['BlazorCulture'],
set: (value) => window.localStorage['BlazorCulture'] = value
};
window.blazorCulture = {
get: function (name) {
name = name + "=";
var decodedCookie = decodeURIComponent(document.cookie);
var ca = decodedCookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) === ' ') {
c = c.substring(1);
}
if (c.indexOf(name) === 0) {
return c.substring(name.length, c.length);
}
}
return "";
},
set: function (name, value) {
const days = 7;
var d = new Date();
d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toUTCString();
document.cookie = name + "=" + value + ";" + expires + ";path=/";
}
const string defaultCulture = "en-US";
var js = host.Services.GetRequiredService();
var result = await js.InvokeAsync("blazorCulture.get");
var culture = CultureInfo.GetCulture(result ?? defaultCulture);
if (result == null)
{
await js.InvokeVoidAsync("blazorCulture.set", defaultCulture);
}
CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;
CultureInfo culture;
var cultureCookieName = CookieRequestCultureProvider.DefaultCookieName;
var js = host.Services.GetRequiredService();
var localizationCookie = await js.InvokeAsync("blazorCulture.get", cultureCookieName);
var result = CookieRequestCultureProvider.ParseCookieValue(localizationCookie)?.UICultures?[0].Value;
if (result != null)
{
culture = new CultureInfo(result);
}
else
{
culture = new CultureInfo("en-US");
await js.InvokeVoidAsync("blazorCulture.set", cultureCookieName, "en-US");
}
CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;

@hishamco
Copy link
Member

Requires more code (and messy code because it's more work to deal with the cookie), as shown below.

Not much :)

It requires that the client app reference an additional package (Microsoft.AspNetCore.Localization).

You can avoid it by providing the localization cookie name or get the value directly from the server. FYI the cookie APIs is able to read any cookie value

It seems to use values for the loc cookie for CSR that are different from the server-based loc cookie. For English, wouldn't the normal server-side cookie value be c=en-US|uic=en-US, not just en-US?

I just used the default values provided by CookieRequestCultureProvider

Will make the CSR parts of a BWA less like what we're pitching for pure standalone WASM, which uses local storage.

Doesn't help shed use of the controller under CSR to set the cookie server-side so that server-side loc can pick up its value.

I didn't know that your requirement to use the controller

We're still stuck having to run code locally via JS interop and toss back and forth the server, so there's no improvement for that aspect.

What's the issue here? Could you please elaborate?

@guardrex
Copy link
Collaborator Author

guardrex commented Mar 26, 2024

Not much :)

It's still messy 🤮😆 compared to local storage, and it still departs from our standalone WASM approach.

You can avoid it by providing the localization cookie name

That's fair, and I think that would be wise.

I just used the default values provided by CookieRequestCultureProvider

I'm not sure what you mean. You used just the name value of the culture (e.g., en-US) for the cookie value, but I thought that such loc cookies use the c=...|uic=... format for the value. I don't really know much about the cookie. I just noticed a delta on that when I was looking at what the ASP.NET Core loc bits were doing compared to what you had for the cookie values. So, it looks like there's this delta in cookie values between the two, and that seems less elegant. Because local storage is a completely independent system, it can be different ... use different values ... and it doesn't seem inconsistent.

I didn't know that your requirement to use the controller
What's the issue here? Could you please elaborate?

In the CultureSelector component to make this work, the controller still has to be hit. Otherwise, the system breaks down and culture can't be updated. Using the cookie approach didn't get rid of that. It didn't simplify anything really in that regard. I was hoping that there was a magic way ✨ to perhaps drop the controller request for CSR and make this whole system still work. However, it seems like the controller has to be hit both for SSR and CSR to make this work. The direct cookie use approach didn't do anything to help that situation out and make it simpler with less code and with dropping a server request for components on CSR.

All that this cookie approach did was result in nasty JS cookie code 😆 over sweet JS local storage code 😎. I just felt that the loc storage code is a lot nicer to see and work with ... and going back to what we have earlier in the article, it's exactly the approach taken for standalone WASM (i.e., the true, real CSR Blazor app type). It feels more like a natural extension of what we're telling users to do in a standalone WASM app.

Anyway ... these are just impressions. If you really think that the cookie approach is better, let me know why you think so. I think Dan and Artak will decide which way the article should go. If you don't have a good reason for this approach tho, I still like the local storage approach the best, and that's what I'd use, unless there are some 🐉 or 😈 that I'm not aware of that could break it 💥 in some way.

Here's the controller code that I'm referring to .....................

In the server project ...

[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);
    }
}

In the CultureSelector component ...

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);

@guardrex
Copy link
Collaborator Author

guardrex commented Mar 26, 2024

BTW ... one more thing ... the CultureSelector isn't invoking blazorCulture.set the same way. It invokes it like this ...

JS.InvokeVoidAsync("blazorCulture.set", value.Name);

... without the cookie name.

UPDATE: Actually, it looks like that line can be removed 🔪 for the cookie approach, just leaving the controller request there.

@mkArtakMSFT
Copy link
Member

@MackinnonBuck looks like @guardrex has some questions with this. But I know you've a ton on your plate already. So please keep this on the backlog of your todo list. I think it'll be ok to tackle closer to preview 6/7 timeframe.

@guardrex guardrex moved this from In progress to P0/P1 - High Priority in Blazor.Docs Aug 5, 2024
@guardrex guardrex moved this from P0/P1 - High Priority to 8.0 in Blazor.Docs Aug 5, 2024
@guardrex guardrex removed the Pri1 label Aug 22, 2024
@guardrex
Copy link
Collaborator Author

UPDATE (12/12): Just chatted with Steve offline about this. We're going to wait until 2025 to look at this. I'll mark it P3 (low priority) for now, and I'll upgrade its priority next year.

@guardrex guardrex added the Pri3 label Dec 12, 2024
@guardrex guardrex moved this from Triage to P3 - Low Priority in Blazor.Docs Dec 12, 2024
@guardrex guardrex changed the title Update Blazor Web App glob/loc approach Update Blazor Web App glob/loc approach? Dec 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: P3 - Low Priority
Development

No branches or pull requests

5 participants