asp.net-coreblazor-server-sideasp.net-core-localization

Is Blazor server culture set via a url?


Reading this section, it looks like the way to set the locale in Blazor for a user is:

 Navigation.NavigateTo(
                    $"Culture/Set?culture={cultureEscaped}&redirectUri={uriEscaped}",
                    forceLoad: true);

Is that correct? That strikes me as a really convoluted solution.

And does setting this then set it for the session/circuit? For both the strings returned from IStringLocalizer and the date/number formatting?

Also, is it correct to assume that the culture for a session/circuit, if I don't navigate to this url, is the one set in the user's browser?


Solution

  • Here's the source codes of RequestLocalizationMiddleware:

    public class RequestLocalizationMiddleware
    {
        private const int MaxCultureFallbackDepth = 5;
    
        private readonly RequestDelegate _next;
        private readonly RequestLocalizationOptions _options;
        private readonly ILogger _logger;
    
        /// <summary>
        /// Creates a new <see cref="RequestLocalizationMiddleware"/>.
        /// </summary>
        /// <param name="next">The <see cref="RequestDelegate"/> representing the next middleware in the pipeline.</param>
        /// <param name="options">The <see cref="RequestLocalizationOptions"/> representing the options for the
        /// <param name="loggerFactory">The <see cref="ILoggerFactory"/> used for logging.</param>
        /// <see cref="RequestLocalizationMiddleware"/>.</param>
        public RequestLocalizationMiddleware(RequestDelegate next, IOptions<RequestLocalizationOptions> options, ILoggerFactory loggerFactory)
        {
            if (options == null)
            {
                throw new ArgumentNullException(nameof(options));
            }
    
            _next = next ?? throw new ArgumentNullException(nameof(next));
            _logger = loggerFactory?.CreateLogger<RequestLocalizationMiddleware>() ?? throw new ArgumentNullException(nameof(loggerFactory));
            _options = options.Value;
        }
    
        /// <summary>
        /// Invokes the logic of the middleware.
        /// </summary>
        /// <param name="context">The <see cref="HttpContext"/>.</param>
        /// <returns>A <see cref="Task"/> that completes when the middleware has completed processing.</returns>
        public async Task Invoke(HttpContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }
    
            var requestCulture = _options.DefaultRequestCulture;
    
            IRequestCultureProvider? winningProvider = null;
    
            if (_options.RequestCultureProviders != null)
            {
                foreach (var provider in _options.RequestCultureProviders)
                {
                    var providerResultCulture = await provider.DetermineProviderCultureResult(context);
                    if (providerResultCulture == null)
                    {
                        continue;
                    }
                    var cultures = providerResultCulture.Cultures;
                    var uiCultures = providerResultCulture.UICultures;
    
                    CultureInfo? cultureInfo = null;
                    CultureInfo? uiCultureInfo = null;
                    if (_options.SupportedCultures != null)
                    {
                        cultureInfo = GetCultureInfo(
                            cultures,
                            _options.SupportedCultures,
                            _options.FallBackToParentCultures);
    
                        if (cultureInfo == null)
                        {
                            _logger.UnsupportedCultures(provider.GetType().Name, cultures);
                        }
                    }
    
                    if (_options.SupportedUICultures != null)
                    {
                        uiCultureInfo = GetCultureInfo(
                            uiCultures,
                            _options.SupportedUICultures,
                            _options.FallBackToParentUICultures);
    
                        if (uiCultureInfo == null)
                        {
                            _logger.UnsupportedUICultures(provider.GetType().Name, uiCultures);
                        }
                    }
    
                    if (cultureInfo == null && uiCultureInfo == null)
                    {
                        continue;
                    }
    
                    cultureInfo ??= _options.DefaultRequestCulture.Culture;
                    uiCultureInfo ??= _options.DefaultRequestCulture.UICulture;
    
                    var result = new RequestCulture(cultureInfo, uiCultureInfo);
                    requestCulture = result;
                    winningProvider = provider;
                    break;
                }
            }
    
            context.Features.Set<IRequestCultureFeature>(new RequestCultureFeature(requestCulture, winningProvider));
    
            SetCurrentThreadCulture(requestCulture);
    
            if (_options.ApplyCurrentCultureToResponseHeaders)
            {
                var headers = context.Response.Headers;
                headers.ContentLanguage = requestCulture.UICulture.Name;
            }
    
            await _next(context);
        }
    
        private static void SetCurrentThreadCulture(RequestCulture requestCulture)
        {
            CultureInfo.CurrentCulture = requestCulture.Culture;
            CultureInfo.CurrentUICulture = requestCulture.UICulture;
        }
    
        private static CultureInfo? GetCultureInfo(
            IList<StringSegment> cultureNames,
            IList<CultureInfo> supportedCultures,
            bool fallbackToParentCultures)
        {
            foreach (var cultureName in cultureNames)
            {
                // Allow empty string values as they map to InvariantCulture, whereas null culture values will throw in
                // the CultureInfo ctor
                if (cultureName != null)
                {
                    var cultureInfo = GetCultureInfo(cultureName, supportedCultures, fallbackToParentCultures, currentDepth: 0);
                    if (cultureInfo != null)
                    {
                        return cultureInfo;
                    }
                }
            }
    
            return null;
        }
    
        private static CultureInfo? GetCultureInfo(StringSegment name, IList<CultureInfo>? supportedCultures)
        {
            // Allow only known culture names as this API is called with input from users (HTTP requests) and
            // creating CultureInfo objects is expensive and we don't want it to throw either.
            if (name == null || supportedCultures == null)
            {
                return null;
            }
            var culture = supportedCultures.FirstOrDefault(
                supportedCulture => StringSegment.Equals(supportedCulture.Name, name, StringComparison.OrdinalIgnoreCase));
    
            if (culture == null)
            {
                return null;
            }
    
            return CultureInfo.ReadOnly(culture);
        }
    
        private static CultureInfo? GetCultureInfo(
            StringSegment cultureName,
            IList<CultureInfo> supportedCultures,
            bool fallbackToParentCultures,
            int currentDepth)
        {
            var culture = GetCultureInfo(cultureName, supportedCultures);
    
            if (culture == null && fallbackToParentCultures && currentDepth < MaxCultureFallbackDepth)
            {
                var lastIndexOfHyphen = cultureName.LastIndexOf('-');
    
                if (lastIndexOfHyphen > 0)
                {
                    // Trim the trailing section from the culture name, e.g. "fr-FR" becomes "fr"
                    var parentCultureName = cultureName.Subsegment(0, lastIndexOfHyphen);
    
                    culture = GetCultureInfo(parentCultureName, supportedCultures, fallbackToParentCultures, currentDepth + 1);
                }
            }
    
            return culture;
        }
    }
    

    how does it then set the CultureInfo.CurrentCulture for a session?

    here CultureInfo.CurrentCulture was setted :

    private static void SetCurrentThreadCulture(RequestCulture requestCulture)
        {
            CultureInfo.CurrentCulture = requestCulture.Culture;
            CultureInfo.CurrentUICulture = requestCulture.UICulture;
        }
    

    The providers are in order ,and when you send http request the first time you reach your app,the other providers usually don't contain the culture ,so it always get value from AcceptHeader provider

    var providerResultCulture = await provider.DetermineProviderCultureResult(context);
                    if (providerResultCulture == null)
                    {
                        continue;
                    }
                    var cultures = providerResultCulture.Cultures;
                    var uiCultures = providerResultCulture.UICultures;
    

    If you don't send another http request,the culture won't change,for example,if you nevigate to another component with the culture set in your url,the culture won't change

    Notice if you input the url in address bar,if you are in ServerPrerendered mode,the culture would change the first time the component was rendered,and it would change back the second time component was rendered.