ASP.NET Core MVC Localization

This article shows some of the ways in which localization can be used in an ASP.NET Core MVC application.

Code: https://github.com/damienbod/AspNetCoreLocalization/

Posts in this series

2018-12-05: Updated to ASP.NET Core 2.2 and EF Core 2.2
2017-08-19: Updated to ASP.NET Core 2.0
2017-02-10: Updated to VS2017 msbuild
2016-06-28: Updated to ASP.NET Core 1.0 RTM
2016-05-16: Updated to ASP.NET Core 1.0 RC2 dotnet
2015-11-20: ASP.NET Core 1.0 RC1 version

Localization Setup

The localization is configured in the setup class and can be used throughout the application per dependency injection. The AddLocalization method is used in the ConfigureServices to define the resources and localization. This can then be used in the Configure method. Here, the RequestLocalizationOptions can be defined and is added to the stack using the UseRequestLocalization method. You can also define different options as required, for example the Accept-Header provider could be removed or a custom provider could be added.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

using Microsoft.AspNetCore.Localization;
using System.Globalization;
using Microsoft.Extensions.Options;
using Localization.SqlLocalizer.DbStringLocalizer;

namespace AspNet5Localization
{
    using System.IO;
    using Localization.SqlLocalizer;

    public class Startup
    {
        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);

            if (env.IsDevelopment())
            {
                builder.AddUserSecrets();
            }

            builder.AddEnvironmentVariables();
            Configuration = builder.Build();
        }

        public IConfigurationRoot Configuration { get; set; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddLocalization(options => options.ResourcesPath = "Resources");
     
            services.AddMvc()
                .AddViewLocalization()
                .AddDataAnnotationsLocalization();

            services.AddScoped<LanguageActionFilter>();

            services.Configure<RequestLocalizationOptions>(
                options =>
                    {
                        var supportedCultures = new List<CultureInfo>
                        {
                            new CultureInfo("en-US"),
                            new CultureInfo("de-CH"),
                            new CultureInfo("fr-CH"),
                            new CultureInfo("it-CH")
                        };

                        options.DefaultRequestCulture = new RequestCulture(culture: "en-US", uiCulture: "en-US");
                        options.SupportedCultures = supportedCultures;
                        options.SupportedUICultures = supportedCultures;
                    });
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole();
            loggerFactory.AddDebug();

            var locOptions = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();
            app.UseRequestLocalization(locOptions.Value);

            app.UseStaticFiles();

            app.UseMvc();
        }

        public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                .UseKestrel()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseIISIntegration()
                .UseStartup<Startup>()
                .Build();

            host.Run();
        }
    }
}

The localization can be used for example in an ASP.NET Core MVC controller. This is done by defining the IHtmlLocalizer with the name of your resx file(s). The resx files are defined in the folder defined in the Startup class AddLocalization method. The IHtmlLocalizer can then be used to return localized properties. A shared resource requires an emtpy class and special resource naming so that the magic string conventions work and the resource can be found. See the code for this.

using System.Globalization;
using System.Threading;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Localization;
using Microsoft.Extensions.Localization;
	
namespace AspNet5Localization.Controllers
{
    [Route("api/[controller]")]
    public class AboutController : Controller
    {
        private readonly IStringLocalizer<SharedResource> _localizer;
        private readonly IStringLocalizer<AboutController> _aboutLocalizerizer;

        public AboutController(IStringLocalizer<SharedResource> localizer, IStringLocalizer<AboutController> aboutLocalizerizer)
        {
            _localizer = localizer;
            _aboutLocalizerizer = aboutLocalizerizer;
        }

        [HttpGet]
        public string Get()
        {
            // _localizer["Name"] 
            return _aboutLocalizerizer["AboutTitle"];
        }
    }
}

Setting the culture in the Query

The culture required by the client application can be set in the query using the ?culture=de-CH.
The application can be run from the commandline, open the application in the src folder and enter:

dotnet restore

dotnet run

Now the query localization can be tested or used as follows:

http://localhost:5000/api/About?culture=de-CH

http://localhost:5000/api/About?culture=it-CH 

Setting the culture in the Accept Header

The HTTP Request Accept-Language header can also be used to request from the server the required culture.

This is implemented as follows for the de-CH culture

GET http://localhost:5000/api/About HTTP/1.1

Accept: */*
Accept-Language: de-CH
Host: localhost:5000

Or implemented as follows for the it-CH culture

GET http://localhost:5000/api/About HTTP/1.1

Accept: */*
Accept-Language: it-CH
Host: localhost:5000

Setting the culture in the Request URL

The culture can also be set in the URL. This is not supported out of the box and you must implement this yourself for example using an action filter.

The action filter can be implemented as follows:

using System.Globalization;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
	
namespace AspNet5Localization
{
    public class LanguageActionFilter : ActionFilterAttribute
    {
        private readonly ILogger _logger;

        public LanguageActionFilter(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger("LanguageActionFilter");
        }

        public override void OnActionExecuting(ActionExecutingContext context)
        {         
            string culture = context.RouteData.Values["culture"].ToString();
            _logger.LogInformation($"Setting the culture from the URL: {culture}");

#if NET451
            System.Threading.Thread.CurrentThread.CurrentCulture = new CultureInfo(culture);
            System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(culture);
#elif NET46
            System.Threading.Thread.CurrentThread.CurrentCulture = new CultureInfo(culture);
            System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(culture);
#else
            CultureInfo.CurrentCulture = new CultureInfo(culture);
            CultureInfo.CurrentUICulture = new CultureInfo(culture);
#endif
            base.OnActionExecuting(context);
        }
    }
}

The culture value is defined as route data and this is then used to set the culture.

The action filter is then registered in the Startup ConfigureServices method.

public void ConfigureServices(IServiceCollection services)
{
	services.AddLocalization(options => options.ResourcesPath = "Resources");

	services.AddMvc()
		.AddViewLocalization()
		.AddDataAnnotationsLocalization();

	services.AddScoped<LanguageActionFilter>();
}

This can then be used in a controller using an attribute routing parameter and applying the action filter to the controller class.

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;

namespace AspNet5Localization.Controllers
{
    [ServiceFilter(typeof(LanguageActionFilter))]
    [Route("api/{culture}/[controller]")]
    public class AboutWithCultureInRouteController : Controller
    {
        // http://localhost:5000/api/it-CH/AboutWithCultureInRoute
        // http://localhost:5000/api/fr-CH/AboutWithCultureInRoute
        private readonly IStringLocalizer<SharedResource> _localizer;


        public AboutWithCultureInRouteController(IStringLocalizer<SharedResource> localizer)
        {
            _localizer = localizer;
        }

        [HttpGet]
        public string Get()
        {
            return _localizer["Name"];
        }
    }
}

This can then be used as follows:

 http://localhost:5000/api/it-CH/AboutWithCultureInRoute
 
 http://localhost:5000/api/fr-CH/AboutWithCultureInRoute

This is very useful if you cannot rely on the browser culture.

Notes

Localization in ASP.NET Core RC2 dotnet supports most of the requirements for implementing localization in a web application. It is flexible and can be extended as required.

Links:

Microsoft Docs: Globalization and localization

https://github.com/aspnet/Localization

https://github.com/aspnet/Tooling/issues/236

http://www.jerriepelser.com/blog/how-aspnet5-determines-culture-info-for-localization

http://www.vodovnik.com/2015/09/20/localization-in-asp-net-5-mvc-6/

https://github.com/WeebDo/WeebDo.CMF

https://github.com/joeaudette/cloudscribe

https://github.com/aspnet/Mvc/tree/dev/test/WebSites/LocalizationWebSite

Example of localization middleware for culture in route

http://weblogs.asp.net/jeff/beating-localization-into-submission-on-asp-net-5

https://github.com/joeaudette/experiments

https://github.com/avodovnik/Cancel/tree/master/Source/Demo06.ViewLocationExpanders

31 comments

  1. […] ASP.NET 5 MVC 6 Localization – Damienbod explores localization techniques in ASP.NET 5 / MVC6, walking through the configuration of the various aspects of the configuration […]

  2. The current ASP.NET 4 way of using an external resource file everywhere via the auto-generated typesafe wrapper (e.g. AccountResources.UsernameLabel) seems a lot nicer than this pass a string approach (_htmlLocalizer[“UsernameLabel”]).

  3. Cleve Littlefield · · Reply

    I assume since it is an interface, what if I want to replace the localization implementation entirely (not use resource files)? I ask because I have worked on sites with interesting localization requirements. A couple examples:

    * Localization pulls from external CMS system.
    * White label sites, where you pass in a context that tells the system to use one set of localization strings versus another.
    * Localization has an override feature, where you have a base set of strings, and then for certain scenarios based on context you have an override set of strings. This for pages that share 90% of the localization, but have some special considerations based on context.

    I am hoping that instead of just custom rolling my own localization that ignores MVC way of doing things, that using your new interface I can replace that implementation and all these features can be supported in a transparent way.

    1. Hi Cleve

      Thanks for your reply. Using different src resources is an important feature for me as well. Maybe you could ask this question on gitHub aspnet/localization. Don’t think this is possible yet apart from database resources. A lot of translations are delivered as excel, so a type of conversion would also be wished.

      The 90% should work fine as you can use parameters in the resource requests.

      The white labels feature can be solved by using your IoC, (named resolve)

      Greetings Damien

  4. […] Faire de la localisation (gestion des langues) avec votre application ASP.NET 5. […]

  5. thetyne · · Reply

    Hi,
    Thanks for this item.
    I have found an issue in visual studio 2015, when you use vs 2015 for execute web site, the localization fail.
    Environnement culture is correct but resources file don’t change.
    it’s reproduce with your solution when you use this url : http://localhost:5000/api/About?culture=en-US

    1. Hi

      thanks

      I have created an issue for this.

      https://github.com/aspnet/Tooling/issues/236

      Greetings Damien

  6. Hi Damian, how does one actually add a RESX file to an ASP.NET 5 project?

    I cannot find the template when I right-click on the project and select Add > New Item

  7. Hi Jerrie

    It’s not possible yet in VS2015 for ASP.NET5, I created one in an old project and copied it to the ASP.NET 5 project. A gitHub issue exists for this in aspnet/tooling

    Also, every time you add a new resource to the file, the gen. class is compiled to an internal class. You need to change this to a public class for it to work in MVC6. A gitHub issue also exists for this.

    Greetings Damien

    1. Thanks Damien. I came across the issue you logged in GH soon after I commented here, so I’ve got it sorted out now 🙂

      1. rakrouki · ·

        Hi Jerriep
        try to install the new RC1 update for VS 2015 it will sort the issue of adding new resources files to the solution; you have to select add new item and pick resource file from the list of available files
        Regards

      2. Hi Jerrie

        I’ve just created a global issue for this, could you give your views here?

        https://github.com/aspnet/Home/issues/1142

        Greetings Damien

  8. Hi Damien,

    I’m reading all of your localization posts and github issues, but still don’t know what to do in my project. I have a webapplication which has to be localized for displaying localized labels on the views. And the validations are in the separate libraries. These validations should be localized too.
    What should I do? Should I implement my own IStringLocalizerFactory?

    Regards,
    Alex

  9. Hi Alex

    Yes, it looks like this. Damian Edwards is updating a lot of the localization now, and it will change or work in rc2. He’s also creating an example which shows how view localization is done.

    At present, if the validations are in a separate library, it causes problems. This should be ok in rc2, check his issues…

    Hope this helps

    Greetings Damien

  10. Hi! In this moment, the RC1, AddViewLocalization is not working. Now you have AddLocalization, right? I have tested your example, but in this moment, I can’t make it works… My main problem is with DI in my HomeController with IHtmlLocalizer… Any ideas? Thank you so much!!

  11. Paul Marsden · · Reply

    HI Damien
    Thanks for all the help you give us lowly programmers.
    I’ve downloaded your AspNet5Localization sample and it is not working for me. I’m using rc1 and the dnx web command to no avail.
    Any idea why it’s not working?
    Paul

    1. Hi Paul

      Thanks for the comment. The latest version uses rc2. Your using rc1. I have created a rc1 branch which you can use. Try switching to this branch and it should work. (With all the rc1 known issues.)

      Greetings Damien

      1. Chema Roldán · ·

        Hi Damien, I can solve my problem, I still have the same error… All compiled fine (rc1 tree), but the translation is not working ( running outside VS, dnx web command). What is wrong? I’m using rc1, and dnx45 in my project json. I have created a question in stackoverflow, http://stackoverflow.com/questions/35433059/asp-net-5-mvc-6-resources-localization Thanks!!

  12. Hi Damien
    Thank you for the good work you do for us lowly programmers.
    I’ve downloaded and tried your sample AspNet5Localization and I can’t get it to work. I’m using rc1 and the dnx web command.
    Any ideas why it isn’t working?
    Regards
    Paul

  13. Chema Roldán · · Reply

    Hi Damien, I have updated your example to ASP.NET 5 MVC 6 (dnx451). You can change the language, using cookie 😉 You can check it here:

    https://github.com/chemitaxis/Localization.StackOverflow

    1. Hi Chema

      nice, thanks

      Greetings Damien

  14. David Keller · · Reply

    Hi Damien, thank you for your work on Localization! I tried to run a clone of your repo with RC2 preview but dotnet-ef as dependency is not found.

    See output:

    Errors in /Users/david/Projects/AspNet5Localization/AspNet5Localization/src/AspNet5Localization/project.json
    Unable to resolve ‘dotnet-ef (>= 1.0.0)’ for ‘.NETCoreApp,Version=v1.0’.

    NuGet Config files used:
    /Users/david/Projects/AspNet5Localization/AspNet5Localization/NuGet.Config
    /Users/david/.nuget/NuGet/NuGet.Config

    Feeds used:
    https://www.myget.org/F/aspnetrc2/api/v3/index.json
    https://api.nuget.org/v3/index.json
    https://www.myget.org/F/aspnetvnext/api/v3/index.json

    ## Info
    $ dotnet –info
    .NET Command Line Tools (1.0.0-preview1-002702)

    Product Information:
    Version: 1.0.0-preview1-002702
    Commit Sha: 6cde21225e

    Runtime Environment:
    OS Name: Mac OS X
    OS Version: 10.11
    OS Platform: Darwin

    Which feeds do you have additionally?

    Thanks

    1. Hi David, I just got the tooling, fixed the code, the latest version should run without problems.

      Greetings Damien

  15. Hi David, thanks

    I’m using the following feeds:

    https://www.myget.org/F/aspnetrc2/api/v3/index.json
    https://api.nuget.org/v3/index.json
    https://www.myget.org/F/aspnetvnext/api/v3/index.json
    https://dotnet.myget.org/F/dotnet-core/api/v3/index.json

    and the latest dotnet installed.

    From what I’ve read, the RC2 release will be tonight, so I can test the preview then. I cannot test from VS as it is not released yet.

    I will update this then to the default release feeds.

    Hope this helps

    Greetings Damien

  16. Ahmed · · Reply

    “The runtime doesn’t look up localized strings for non-validation attributes”
    from the official documentation
    so how we should localize the display attribute
    Thanks a lot

  17. Hi Damien, thank you for your work on Localization with Core 1 ! ,

    I just study your project, and I have some questions :

    1) Is it possiblo to localize also on .cshtml pages ?
    2) I see all inizialization is on startup, but if I need to change language ?

    Thank for your work

    Mark

  18. Raj Aryan · · Reply

    Hi Mark,
    Did you get your answer from Damien? I appreciate the Brilliant work done by Damien, but poor documentation is a big headache and better not to use such things.

  19. […] oggetto si ottiene il valore 12 aprile 2017. Ho cercato di aggiungere solo in tedesco utilizzando questo articolo e anche questo, ma senza […]

  20. […] date obtient la valeur 12 avril 2017. J'ai essayé d'ajouter en allemand uniquement à l'aide de cette article et également cette, mais sans […]

  21. […] de objetos obtiene el valor de 12 de abril de 2017. He intentado añadir alemán sólo mediante este artículo y también este, pero sin […]

Leave a reply to Mark Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.