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

There are quite a few ways of setting active culture in an ASP.NET MVC app. We discuss the most viable ones in our beginner guide.

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).
🔗 Resource » To learn more about the CultureInfo class we use here, check out this guide on all you need to know about CultureInfo in .NET applications.

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 domain code with Phrase, a hassle-free i18n solution. Built by developers for developers, Phrase offers GitHub, Bitbucket, and GitLab sync integrations, a flexible API and CLI, over-the-air (OTA) translations for mobile apps, and much, much more. Check out all of Phrase’s products, and sign up for a free 14-day trial.
If your want to keep learning about internationalization in .NET apps, here are some tutorials you might find interesting:

Keep exploring

Photo-realistic sheet music featuring developer-style translation code in place of musical notes. The staff lines show snippets like t('auth.signin.button') and JSON structures, combining the aesthetics of musical notation with programming syntax to illustrate the idea of “composable localization.”

Blog post

Localization as code: a composable approach to localization

Why is localization still a manual, disconnected process in a world where everything else is already “as code”? Learn how a composable, developer-friendly approach brings localization into your CI/CD pipeline, with automation, observability, and Git-based workflows built in.

A woman in a light sweater sits in a home office, focused on her laptop, representing a developer or content manager working on WordPress localization tasks in a calm, professional environment.

Blog post

How to build a scalable WordPress i18n workflow

WordPress powers the web, but translating it well takes more than plugins. Discover how to build a scalable localization workflow using gettext, best practices, and the Phrase plugin.

Blog post

Localizing Unity games with the official Phrase plugin

Want to localize your Unity game without the CSV chaos? Discover how the official Phrase Strings Unity plugin simplifies your game’s localization workflow—from string table setup to pulling translations directly into your project. Whether you’re building for German, Serbian, or beyond, this guide shows how to get started fast and localize like a pro.

Blog post

Internationalization beyond code: A developer’s guide to real-world language challenges

Discover how language affects your UI. From text expansion to pluralization, this guide explores key i18n pitfalls and best practices for modern web developers.

A digital artwork featuring the Astro.js logo in bold yellow and purple tones, floating above Earth's horizon with a stunning cosmic nebula in the background. The vibrant space setting symbolizes the global and scalable nature of Astro’s localization capabilities, reinforcing the article’s focus on internationalization in web development.

Blog post

Astro.js localization part 2: dynamic content localization

Learn how to localize your Astro.js website with static and dynamic content translation. Explore Astro’s built-in i18n features and Paraglide for handling UI elements, navigation, and dynamic text seamlessly.