5

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 project renders site.com/foo/foo/bar this url like site.com/tr/foo/bar it's not okay and i guess it should redirect to 404 page.

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?

EDIT

I found this approach. It's using CookieRequestCultureProvider and there is no culture info in url but at least there are no corupted urls. I don't know if this is okay for SEO.

Eren Peksen
  • 175
  • 2
  • 10

1 Answers1

2

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: "{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 :)

DarkSideMoon
  • 835
  • 1
  • 11
  • 17
  • Thank you for your detailed answer :), There are problems with this proach as well, if you try another Controllers page you can get 'not friendly route' problem as well, its working with Home controller cause its default(I think...) but other Controllers route is corrupted and they are not working for default culture. Another problem is if you try site.com/Home/Home its still working, this causes to create complicated urls. And can't i just use one resource file for every language like old asp.net, with this new file naming makes translation and reusable words management hard. – Eren Peksen Jan 20 '21 at 07:42
  • I did some research and I think CookieRequestCultureProvider is more suitable for my friendly route pattern. There will be no culture info in url(only setter urls will have parameter) but its like much safer and no corrupted urls. If I'm missing any point, I would like to hear your opinion as well. – Eren Peksen Jan 20 '21 at 09:50
  • Well, I create new View page and add new `Resources`. Everything works fine for me. What does it mean for you **not friendly route**? The urls are: `http://localhost:56889/en/home/privacy` and `http://localhost:56889/fr/home/privacy`. I think I have `home` in name of url, isn't it? And for you it is not friendly, I am right? – DarkSideMoon Jan 20 '21 at 20:35
  • site.com/en/home/privacy is totally fine for me but i wanted default culture works without definition on url and if you call site.com/dummyCultureOrAnythingHere/home/privacy this url in your project it will render just like site.com/en/home/privacy, this is not friendly i guess, it should redirect to homepage or show 404 page. Is your new View is binded to another Controller? Because if you try to open another Controllers Index Page it causes the above "dummy 404" sample, try to open site.com/newController but it will render site.com/en/home/index and url will stay site.com/newController. – Eren Peksen Jan 20 '21 at 22:12
  • 2
    I understand you. I found similar answers to your question, but with older version of net core. [This](https://stackoverflow.com/a/32839796/3520507) and [this](https://stackoverflow.com/a/35970676/3520507). I will investigate this answers and try to create similar solution. – DarkSideMoon Jan 21 '21 at 10:36
  • Hi @Eren Pekşen, I have changed answear. I changed only default `MapControllerRoute` and it start works correct. `endpoints.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");` [Default language](https://imgur.com/a/5prrvFU) and [another language](https://imgur.com/a/xj9c4N1). I think it works as expected. – DarkSideMoon Jan 24 '21 at 09:22
  • If my answer is solve your problem, could you please accept it. Thanks a lot! – DarkSideMoon Jan 25 '21 at 20:59
  • I created new project and tried but still accepting urls like [this](https://prnt.sc/xsw5r9) , I'm usign cookie provider in my project now and I think it's more reliable and easy to use. – Eren Peksen Jan 29 '21 at 09:28