asp.net-mvcasp.net-mvc-4blazorlocalizationblazor-server-side

Blazor app localization with culture code in URL fails


For a Blazor Server project, I am trying to enable browsing to a localized page with URL like:

https://example.com/en/counter (for English) https://example.com/fr/counter (for French) https://example.com/es/counter (for Spanish)

The pages in my project are successfully localized with culture in the URL, but I keep getting errors shown in the image.

enter image description here

This error appears to be causing the Counter button to not responding to the click event.

The project can be found at:

https://github.com/tsutomua/blazor/tree/main/BlazorLocalizationTest

It seems that the Blazor files are not being loaded correctly because of the culture names dynamically added by CultureTemplatePageRouteModelConvention.

    // Add services to the container.
    builder.Services.AddRazorPages(options =>
    {
        // decorate all page routes with {culture} e.g. @page "/{culture}..."

        // https://www.learnrazorpages.com/razor-pages/routing#register-additional-routes
        // Commenting out the following line causes pages not to be localized, but the bottom error message disappears.
        options.Conventions.Add(new CultureTemplatePageRouteModelConvention());
    });

If there is a good way to load Blazor files, CSS files, and other code files while adding culture tags for the regular pages (e.g. the counter page), the problem may be resolved. Any help would be appreciated.

Pages I referenced:

Update & Solution

Thanks @Ruikai. I came up with the following class and now the pages including the navmenu are localized. The solution gets the culture value from cookie whenever a culture is not found in the route.

public class CookieRouteDataRequestCultureProvider : RequestCultureProvider
{
    public override Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
    {
        string previousRouteCulture;
        string routeCulture = (string)httpContext.Request.RouteValues["culture"];
        string urlCulture = httpContext.Request.Path.Value.Split('/')[1];

        // Culture provided in route values
        if (IsSupportedCulture(routeCulture))
        {
            previousRouteCulture = routeCulture;
            RequestCulture requestCulture = new RequestCulture(routeCulture, routeCulture);

            // If uncommented, left nav menu breaks after some navigations.
            SetThreadCulture(routeCulture);

            string cookieName = CookieRequestCultureProvider.DefaultCookieName;
            string cookieValue = CookieRequestCultureProvider.MakeCookieValue(requestCulture);
            httpContext.Response.Cookies.Append(cookieName, cookieValue);

            return Task.FromResult(new ProviderCultureResult(routeCulture));
        }
        // Culture provided in URL
        else if (IsSupportedCulture(urlCulture))
        {
            SetThreadCulture(urlCulture);

            return Task.FromResult(new ProviderCultureResult(urlCulture));
        }
        else
        {
            CookieCultureProvider cookieCultureProvider = new CookieCultureProvider(Options.SupportedCultures.ToList());
            string cultureFromCookieOrDefault = cookieCultureProvider.GetCultureFromCookie(httpContext);
            SetThreadCulture(cultureFromCookieOrDefault);

            return Task.FromResult(new ProviderCultureResult(cultureFromCookieOrDefault));
        }
    }

    private static void SetThreadCulture(string routeCulture)
    {
        CultureInfo cultureInfo = CultureInfo.GetCultureInfo(routeCulture);
        Thread.CurrentThread.CurrentCulture = cultureInfo;
        Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(routeCulture);
    }

    /**
     * Culture must be in the list of supported cultures
     */
    private bool IsSupportedCulture(string cultureCode) =>
        !string.IsNullOrEmpty(cultureCode)
        && Options.SupportedCultures.Any(x =>
            x.TwoLetterISOLanguageName.Equals(
                cultureCode,
                StringComparison.InvariantCultureIgnoreCase
            )
        );
}

Solution

  • This error appears to be causing the Counter button to not responding to the click event.

    You've setted ServerPrerender mode here

    <component type="typeof(App)" render-mode="ServerPrerendered" />
    

    If you left a break point on OnInitializedAsync(),you would know the method will be excuted twice.The static render is ok,but it fails to establish signalr connection during Interactive server-side rendering,for more details,see this document

    If there is a good way to load Blazor files, CSS files, and other code files while adding culture tags for the regular pages (e.g. the counter page)

    you may apply routeconstraint to your razor page/razor component to avoid route parttern confliction with the static files

    For example:

    public class CultureTemplatePageRouteModelConvention: IPageRouteModelConvention
    {
        public void Apply(PageRouteModel model)
        {
            foreach (SelectorModel selector in model.Selectors)
            {
                string template = selector.AttributeRouteModel.Template;
    
                if (template.StartsWith("MicrosoftIdentity")) continue;  // Skip MicrosoftIdentity pages
                
    
                // Prepend {culture}/ to the page routes allow for route-based localization
                selector.AttributeRouteModel.Order = -1;
                //selector.AttributeRouteModel.Template = AttributeRouteModel.CombineTemplates("{culture:cultureConstraint}", template);
    
                // Prepend the /{culture?}/ route value to allow for route-based localization
                selector.AttributeRouteModel.Template = AttributeRouteModel.CombineTemplates("{culture:cultureConstraint}", template);
            }
        }
    }
    

    index.razor:

    @page "/{culture:nonfile?}"
    

    Also, when you click nav bar,the culture won't be changed for no httprequest would be sent,the middleware app.UseRequestLocalization(); won't be executed. You could follow this part of document to switch the language