How Do I Set Culture in an ASP.NET MVC App?

Setting the Locale or Culture in an ASP.NET MVC App

There are several ways to set the locale or culture in an ASP.NET MVC app. Let's walk through them.

When internationalizing an ASP.NET MVC application, we often want to set the current locale or culture so that our app can use this active culture when displaying strings (say, from resource files), formatting dates, etc.

A few ways to set the active culture are:

  • For every action in our controllers,
  • Once in a base controller,
  • And/or through our routes.

Let’s quickly go through these approaches.

Setting the Active Culture in Controller Actions

In .NET apps, our active locale or culture needs to be set at the thread level. We could do this for every action in our controllers.

using System.Globalization;
using System.Threading;
using System.Web.Mvc;

namespace I18nDemo.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("ar");
            Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("ar");

            return View();
        }
    }
}

The previous code will set the active culture to Arabic in the Index action of our HomeController. While this works, it’s not very DRY: We’d have to repeat this code in every action we want localized.

๐Ÿ—’ Note ยป CurrentCulture controls number and date formatting. CurrentUICulture controls the UI (resource strings).

Setting the Active Culture in a Base Controller

A wiser approach might be to set active culture in an override: the OnActionExecuting() method of a common base controller.

using System.Globalization;
using System.Threading;
using System.Web.Mvc;

namespace I18nDemo.Controllers
{
    public class BaseController : Controller
    {
        protected override void OnActionExecuting(
            ActionExecutingContext filterContext)
        {
            base.OnActionExecuting(filterContext);

            var cultureInfo = CultureInfo.GetCultureInfo("ar");

            Thread.CurrentThread.CurrentCulture = cultureInfo;
            Thread.CurrentThread.CurrentUICulture = cultureInfo;
        }
    }
}

OnActionExecuting() will run before every action of the controller. Our localized controllers can inherit from a common BaseController, which overrides OnActionExecuting().

using System.Web.Mvc;

namespace I18nDemo.Controllers
{
    public class HomeController : BaseController
    {
        public ActionResult Index()
        {
            return View();
        }
    }
}

This approach is much more DRY: any controller derived from BaseController will ensure that its actions set the active app culture, without any additional code.

Localized Routes

We rarely want to hard-code our active culture in our controllers, however. Oftentimes we will have a set of cultures that our app supports (English and Arabic, for example), and we want our app’s routes to reflect these. So we may want our routes to look like “/en/about” and “ar/about”, and to have the first URI segment of our routes (“en” or “ar”) dictate the app’s active locale. We can achieve this by modifying our route configuration.

using System.Web.Mvc;
using System.Web.Routing;

namespace I18nDemo
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                name: "Root",
                url: "",
                defaults: new {
                    controller = "Base",
                    action = "RedirectToLocalized" }
            );

            routes.MapRoute(
                name: "Default",
                url: "{culture}/{controller}/{action}/{id}",
                defaults: new {
                    culture = "en",
                    controller = "Home",
                    action = "Index",
                    id = UrlParameter.Optional },
                constraints: new { culture = "en|ar" }
            );
        }
    }
}
using System.Globalization;
using System.Threading;
using System.Web.Mvc;

namespace I18nDemo.Controllers
{
    public class BaseController : Controller
    {
        protected override void OnActionExecuting(
            ActionExecutingContext filterContext)
        {
            string culture = filterContext.RouteData.Values["culture"]?.ToString()
                                ?? "en";

            // Set the action parameter just in case we didn't get one
            // from the route.
            filterContext.ActionParameters["culture"] = culture;
            
            var cultureInfo = CultureInfo.GetCultureInfo(culture);

            Thread.CurrentThread.CurrentCulture = cultureInfo;
            Thread.CurrentThread.CurrentUICulture = cultureInfo;

            // Because we've overwritten the ActionParameters, we
            // make sure we provide the override to the 
            // base implementation.
            base.OnActionExecuting(filterContext);
        }

        public ActionResult RedirectToLocalized()
        {
            return RedirectPermanent("/en");
        }
    }
}

We redirect our root route (“/”) to a localized route via a route mapping and the BaseController.RedirectToLocalized() action. Our localized route mapping constrains its culture segment to our app’s supported locales. With that in place, we can read the route’s culture param in our common OnActionExecuting(), and feed it into the CurrentCulture and CurrentUICulture props for our app.

Conclusion

We could get more sophisticated with our culture-setting in ASP.NET MVC apps, of course. It will all depend on the complexity of our apps. For scalable, robust i18n, compliment your tight domain code with Phrase, a hassle-free i18n solution. Built by developers for developers, Phrase offers GitHub, Bitbucket, and GitLab sync, a flexible API and CLI, over-the-air (OTA) translations for mobile apps, and much, much more. Check out all of Phrase’s features, and sign up for a free 14-day trial.

4.5 (90%) 8 votes
Comments