File: Hosting\WebAssemblyCultureProvider.cs
Web Access
Project: src\src\Components\WebAssembly\WebAssembly\src\Microsoft.AspNetCore.Components.WebAssembly.csproj (Microsoft.AspNetCore.Components.WebAssembly)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.InteropServices.JavaScript;
 
namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting;
 
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026", Justification = "This type loads resx files. We don't expect it's dependencies to be trimmed in the ordinary case.")]
#pragma warning disable CA1852 // Seal internal types
internal partial class WebAssemblyCultureProvider
#pragma warning restore CA1852 // Seal internal types
{
    internal const string GetSatelliteAssemblies = "window.Blazor._internal.getSatelliteAssemblies";
    internal const string ReadSatelliteAssemblies = "window.Blazor._internal.readSatelliteAssemblies";
 
    // For unit testing.
    internal WebAssemblyCultureProvider(CultureInfo initialCulture, CultureInfo initialUICulture)
    {
        InitialCulture = initialCulture;
        InitialUICulture = initialUICulture;
    }
 
    public static WebAssemblyCultureProvider? Instance { get; private set; }
 
    public CultureInfo InitialCulture { get; }
 
    public CultureInfo InitialUICulture { get; }
 
    internal static void Initialize()
    {
        Instance = new WebAssemblyCultureProvider(
            initialCulture: CultureInfo.CurrentCulture,
            initialUICulture: CultureInfo.CurrentUICulture);
    }
 
    public void ThrowIfCultureChangeIsUnsupported()
    {
        // With ICU sharding enabled, bootstrapping WebAssembly will download a ICU shard based on the browser language.
        // If the application author was to change the culture as part of their Program.MainAsync, we might have
        // incomplete icu data for their culture. We would like to flag this as an error and notify the author to
        // use the combined icu data file instead.
        //
        // The Initialize method is invoked as one of the first steps bootstrapping the app within WebAssemblyHostBuilder.CreateDefault.
        // It allows us to capture the initial .NET culture that is configured based on the browser language.
        // The current method is invoked as part of WebAssemblyHost.RunAsync i.e. after user code in Program.MainAsync has run
        // thus allows us to detect if the culture was changed by user code.
        if (Environment.GetEnvironmentVariable("__BLAZOR_SHARDED_ICU") == "1" &&
            ((!CultureInfo.CurrentCulture.Name.Equals(InitialCulture.Name, StringComparison.Ordinal) ||
              !CultureInfo.CurrentUICulture.Name.Equals(InitialUICulture.Name, StringComparison.Ordinal))))
        {
            throw new InvalidOperationException("Blazor detected a change in the application's culture that is not supported with the current project configuration. " +
                "To change culture dynamically during startup, set <BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData> in the application's project file.");
        }
    }
 
    public virtual async ValueTask LoadCurrentCultureResourcesAsync()
    {
        if (!OperatingSystem.IsBrowser())
        {
            throw new PlatformNotSupportedException("This method is only supported in the browser.");
        }
 
        var culturesToLoad = GetCultures(CultureInfo.CurrentCulture);
 
        if (culturesToLoad.Length == 0)
        {
            return;
        }
 
        await WebAssemblyCultureProviderInterop.LoadSatelliteAssemblies(culturesToLoad);
    }
 
    internal static string[] GetCultures(CultureInfo cultureInfo)
    {
        var culturesToLoad = new List<string>();
 
        // Once WASM is ready, we have to use .NET's assembly loading to load additional assemblies.
        // First calculate all possible cultures that the application might want to load. We do this by
        // starting from the current culture and walking up the graph of parents.
        // At the end of the the walk, we'll have a list of culture names that look like
        // [ "fr-FR", "fr" ]
        while (cultureInfo != null && cultureInfo != CultureInfo.InvariantCulture)
        {
            culturesToLoad.Add(cultureInfo.Name);
 
            if (cultureInfo.Parent == cultureInfo)
            {
                break;
            }
 
            cultureInfo = cultureInfo.Parent;
        }
 
        return culturesToLoad.ToArray();
    }
 
    private partial class WebAssemblyCultureProviderInterop
    {
        [JSImport("INTERNAL.loadSatelliteAssemblies")]
        public static partial Task LoadSatelliteAssemblies(string[] culturesToLoad);
    }
}