.net core 5 friendly default culture routing

I tried to add multi language feature to my asp.net-core project but there are some changes between .net 3.1 and 5.0 in RequestLocalization and i couldn't get what i want. I added Resource files for each language and I used Resource in my razor pages, its working but there is one unwanted default route bug and i want my routing to work friendly for default culture.

This is what i want,

For default culture (Turkish):

site.com/foo
site.com/foo/bar
site.com/foo/bar/5

For non-default culture (English):

site.com/en/foo
site.com/en/foo/bar
site.com/en/foo/bar/5

My other problem is; my route accepts site.com/foo/foo/bar as Turkish culture as well and its not friendly.

My Startup sample code below:

public void ConfigureServices(IServiceCollection services)
{
    services.AddResponseCompression();
    services.AddLocalization(opts => opts.ResourcesPath = "Resources");
    services.Configure<RequestLocalizationOptions>(options =>
    {
        var supportedCultures = new[]
        {
            new CultureInfo("tr-TR"),
            new CultureInfo("en")
        };

        options.DefaultRequestCulture = new RequestCulture("tr");
        options.SupportedCultures = supportedCultures;
        options.SupportedUICultures = supportedCultures;
        options.RequestCultureProviders.Insert(0, new RouteDataRequestCultureProvider());
    });

    services.AddControllersWithViews();
    services.AddRazorPages();
    services.AddRouting(options => options.LowercaseUrls = true);
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseResponseCompression();

    if (env.IsDevelopment())
        app.UseDeveloperExceptionPage();
    else
    {
        app.UseExceptionHandler("/Home/Error");
        // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseRouting();
    var supportedCultures = new string[] { "tr-TR", "en" };
    app.UseRequestLocalization(options =>
                options
                .AddSupportedCultures(supportedCultures)
                .AddSupportedUICultures(supportedCultures)
                .SetDefaultCulture("tr-TR")
                .RequestCultureProviders.Insert(0, new CustomRequestCultureProvider(context => Task.FromResult(new ProviderCultureResult("tr-TR"))))
    );

    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(name: "culture-route", pattern: "{culture}/{controller=Home}/{action=Index}/{id?}");
        endpoints.MapControllerRoute(name: "default", "{culture=tr}/{controller=Home}/{action=Index}/{id?}");
    });
}

Razor Resource usage and culture change navs

Resource files

How can I solve this or what am I doing wrong?

1 answer

  • answered 2021-01-19 21:02 DarkSideMoon

    You need to configure localization in ASP.Net Core a little bit different for this purpose.

    I have created new ASP.Net Core MVC project and do the following steps:

    1. First of all, you need to create custom UrlRequestCultureProvider
        public class UrlRequestCultureProvider : RequestCultureProvider
        {
            private static readonly Regex PartLocalePattern = new Regex(@"^[a-z]{2}(-[a-z]{2,4})?$", RegexOptions.IgnoreCase);
            private static readonly Regex FullLocalePattern = new Regex(@"^[a-z]{2}-[A-Z]{2}$", RegexOptions.IgnoreCase);
    
            private static readonly Dictionary<string, string> LanguageMap = new Dictionary<string, string>
            {
                { "en", "en-US" },
                { "fr", "fr-FR" }
            };
    
            public override Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
            {
                if (httpContext == null)
                {
                    throw new ArgumentNullException(nameof(httpContext));
                }
    
                var parts = httpContext.Request.Path.Value.Split('/');
                // Get culture from path
                var culture = parts[1];
    
                if (parts.Length < 3)
                {
                    return Task.FromResult<ProviderCultureResult>(null);
                }
    
                // For full languages fr-FR or en-US pattern
                if (FullLocalePattern.IsMatch(culture))
                {
                    return Task.FromResult(new ProviderCultureResult(culture));
                }
    
                // For part languages fr or en pattern
                if (PartLocalePattern.IsMatch(culture))
                {
                    var fullCulture = LanguageMap[culture];
                    return Task.FromResult(new ProviderCultureResult(fullCulture));
                }
    
                return Task.FromResult<ProviderCultureResult>(null);
            }
        }
    
    1. In ConfigureServices() add this code:
                services.AddControllersWithViews().AddViewLocalization();
                services.AddLocalization(options => options.ResourcesPath = "Resources");
    
                services.Configure<RequestLocalizationOptions>(options =>
                {
                    var supportedCulters = new List<CultureInfo>()
                    {
                        new CultureInfo("en-US"),
                        new CultureInfo("fr-FR")
                    };
    
                    options.DefaultRequestCulture = new RequestCulture(supportedCulters.FirstOrDefault());
                    options.SupportedCultures = supportedCulters;
                    options.SupportedUICultures = supportedCulters;
    
                    options.RequestCultureProviders.Insert(0, new UrlRequestCultureProvider() 
                    { 
                        Options = options 
                    });
                });
    
    1. In Configure() add this code:
                var requestLocalizationOptions = app.ApplicationServices.GetRequiredService<IOptions<RequestLocalizationOptions>>();
                app.UseRequestLocalization(requestLocalizationOptions.Value);
    
                app.UseRouting();
    
                app.UseAuthorization();
    
                app.UseEndpoints(endpoints =>
                {
                    endpoints.MapControllerRoute(
                        name: "default",
                        pattern: "{culture=en}/{controller=Home}/{action=Index}/{id?}");
    
                    endpoints.MapControllerRoute(
                        name: "culture",
                        pattern: "{culture}/{controller=Home}/{action=Index}/{id?}");
                });
    
    1. Also, I added Resources for en-US and fr-FR localization. More information about Resource file naming in Microsoft documentation.
    Views.Home.Index.en-US.resx
    Views.Home.Index.fr-FR.resx
    
    1. Finally, this is my Home view
    @using Microsoft.AspNetCore.Mvc.Localization
    @inject IViewLocalizer Localizer
    
    @{
        ViewData["Title"] = "Home Page";
    }
    
    <div class="text-center">
        <h1 class="display-4">@Localizer["Welcome"]</h1>
    </div>
    

    The results you can see on the screenshots.

    Defaul -> Default

    English -> English

    French -> French

    You can ask me questions and have fun with localizations :)