dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
14.84k stars 4.62k forks source link

.NET 8 Blazor - Support localization change without page reload #98415

Open joostvz opened 6 months ago

joostvz commented 6 months ago

Is there an existing issue for this?

Is your feature request related to a problem? Please describe the problem.

I'm trying to create a Blazor WebAssembly app where users can switch the display language. This only seems to work after forcing a reload of the page. According to the conversation in dotnet/aspnetcore#50361, switching languages without a page reload is currently not supported in Webassembly. I have a web app where it is not acceptable to reload the page when the user switches the language. A page refresh gives a bad user experience. And our app also contains a form, which data must be preserved when switching to another language.

I found a workaround by using this package: https://github.com/ScarletKuro/Blazor.WebAssembly.DynamicCulture, which calls an internal WebAssembly API: INTERNAL.loadSatelliteAssemblies. Normally, Blazor (WebAssembly) calls this method to load the assemblies for the current active culture. The Blazor.WebAssembly.DynamicCulture package calls this method to load the assemblies of all specified cultures. After loading all specified assemblies, it is possible to change the language without a page reload.

Describe the solution you'd like

Is it possible to open up the INTERNAL.loadSatelliteAssemblies API, so that developers can specify the cultures they want to be loaded? By default it can still load only the current culture, but it would be nice if developers can override this behavior.

Edit: My feature request is about opening up this API in a decent way, so that developers don't have to call API's which are marked as internal.

Additional context

No response

ghost commented 6 months ago

Tagging subscribers to 'arch-wasm': @lewing See info in area-owners.md if you want to be subscribed.

Issue Details
### Is there an existing issue for this? - [X] I have searched the existing issues ### Is your feature request related to a problem? Please describe the problem. I'm trying to create a Blazor WebAssembly app where users can switch the display language. This only seems to work after forcing a reload of the page. According to the conversation in dotnet/aspnetcore#50361, switching languages without a page reload is currently not supported in Webassembly. I have a web app where it is not acceptable to reload the page when the user switches the language. A page refresh gives a bad user experience. And our app also contains a form, which data must be preserved when switching to another language. I found a workaround by using this package: https://github.com/ScarletKuro/Blazor.WebAssembly.DynamicCulture, which calls an internal WebAssembly API: `INTERNAL.loadSatelliteAssemblies`. Normally, Blazor (WebAssembly) calls this method to load the assemblies for the current active culture. The _Blazor.WebAssembly.DynamicCulture_ package calls this method to load the assemblies of all specified cultures. After loading all specified assemblies, it is possible to change the language without a page reload. ### Describe the solution you'd like Is it possible to open up the `INTERNAL.loadSatelliteAssemblies` API, so that developers can specify the cultures they want to be loaded? By default it can still load only the current culture, but it would be nice if developers can override this behavior. ### Additional context _No response_
Author: joostvz
Assignees: -
Labels: `arch-wasm`, `untriaged`, `needs-area-label`
Milestone: -
ghost commented 6 months ago

Tagging subscribers to this area: @dotnet/area-system-globalization See info in area-owners.md if you want to be subscribed.

Issue Details
### Is there an existing issue for this? - [X] I have searched the existing issues ### Is your feature request related to a problem? Please describe the problem. I'm trying to create a Blazor WebAssembly app where users can switch the display language. This only seems to work after forcing a reload of the page. According to the conversation in dotnet/aspnetcore#50361, switching languages without a page reload is currently not supported in Webassembly. I have a web app where it is not acceptable to reload the page when the user switches the language. A page refresh gives a bad user experience. And our app also contains a form, which data must be preserved when switching to another language. I found a workaround by using this package: https://github.com/ScarletKuro/Blazor.WebAssembly.DynamicCulture, which calls an internal WebAssembly API: `INTERNAL.loadSatelliteAssemblies`. Normally, Blazor (WebAssembly) calls this method to load the assemblies for the current active culture. The _Blazor.WebAssembly.DynamicCulture_ package calls this method to load the assemblies of all specified cultures. After loading all specified assemblies, it is possible to change the language without a page reload. ### Describe the solution you'd like Is it possible to open up the `INTERNAL.loadSatelliteAssemblies` API, so that developers can specify the cultures they want to be loaded? By default it can still load only the current culture, but it would be nice if developers can override this behavior. ### Additional context _No response_
Author: joostvz
Assignees: -
Labels: `arch-wasm`, `area-System.Globalization`, `untriaged`, `needs-area-label`
Milestone: -
dvoituron commented 6 months ago

I think you could add this C# code to call the equivalent JS code. You have an example here.

[JSImport("INTERNAL.loadSatelliteAssemblies")]
internal static partial Task LoadSatelliteAssemblies(string[] culturesToLoad);
joostvz commented 6 months ago

@dvoituron Thanks for your response. This is indeed what the Blazor.WebAssembly.DynamicCulture library is also doing, and this approach works.

My only concern is that this is using an internal Blazor API, with zero guarantees. Hence my feature request to open up this API in a decent way.

lewing commented 6 months ago

There isn't anything in the runtime that restricts which satellite assemblies can be loaded and if they are added to the correct items I believe the build will correctly pick them up but blazor has some custom logic here so @maraf can you take a look and give appropriate guidance.

maraf commented 6 months ago

Making a public C# API is on our radar, but so far you are the first one asking for it.

In the meantime there are several workarounds a) Use JSImport as mentioned above (the drawback is that the API might change between major .NET versions) b) Load all satellite assemblies Blazor.start({ configureRuntime: runtime => runtime.withConfig({ loadAllSatelliteResources: true }) }) (wich also uses internal switch) c) If you know which assemblies contain resources, you fetch them from C#

var http = new HttpClient();
var dllBytes = await http.GetByteArrayAsync("/_framework/en-US/WasmBasicTestApp.resources.wasm");
AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(dllBytes));

I'm curious what is your motivation for switching between cultures without reload? In a typical scenario users do this once and then use the prefered culture.

joostvz commented 6 months ago

Thanks for the provided workarounds @maraf. I will give them a try.

My scenario is the following: I'm developing a web app for a kiosk in a store. Users can select their preferred language on the screen. A page reload would probably cause a blinking page. Also, users should be able to switch the language at any moment in the process, even when they're filling in data in a form. A page reload would require all form data to be temporarily stored somewhere.

So for my scenario a page reload is possible, but has negative impact on the user experience, and adds technical challenges.

maraf commented 6 months ago

My scenario is the following

Thank you for sharing! It makes perfect sense for your scenario

alexspirou commented 4 months ago

Making a public C# API is on our radar, but so far you are the first one asking for it.

In the meantime there are several workarounds a) Use JSImport as mentioned above (the drawback is that the API might change between major .NET versions) b) Load all satellite assemblies Blazor.start({ configureRuntime: runtime => runtime.withConfig({ loadAllSatelliteResources: true }) }) (wich also uses internal switch) c) If you know which assemblies contain resources, you fetch them from C#

var http = new HttpClient();
var dllBytes = await http.GetByteArrayAsync("/_framework/en-US/WasmBasicTestApp.resources.wasm");
AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(dllBytes));

I'm curious what is your motivation for switching between cultures without reload? In a typical scenario users do this once and then use the prefered culture.

Hello @maraf, could you please provide guidance on where to write Blazor.start({ configureRuntime: runtime => runtime.withConfig({ loadAllSatelliteResources: true }) }) in a Blazor application?

joostvz commented 4 months ago

@alexspirou I'm currently using this approach, and did the following:

  1. In your App.razor file, add autostart="false" to the include statement for the blazor.web.js file: <script src="_framework/blazor.web.js" autostart="false"></script>
  2. Create a new javascript file where you customize the start method, and link this file in your App.razor:
    Blazor.start({
     webAssembly: {
         configureRuntime: dotnet => {
             dotnet.withConfig({ loadAllSatelliteResources: true });
         }
     }
    });
alexspirou commented 4 months ago

Thanks for the response @joostvz.

var dllBytes = await http.GetByteArrayAsync("/_framework/en-US/WasmBasicTestApp.resources.wasm");

My resources are in a class library under the folder named Resources. How did you get the Assemblies?