--- title: Globalization and localization in ASP.NET Core author: rick-anderson description: Learn how ASP.NET Core provides services and middleware for localizing content into different languages and cultures. ms.author: riande ms.date: 01/14/2017 uid: fundamentals/localization --- # Globalization and localization in ASP.NET Core By [Rick Anderson](https://twitter.com/RickAndMSFT), [Damien Bowden](https://twitter.com/damien_bod), [Bart Calixto](https://twitter.com/bartmax), [Nadeem Afana](https://afana.me/), and [Hisham Bin Ateya](https://twitter.com/hishambinateya) Creating a multilingual website with ASP.NET Core will allow your site to reach a wider audience. ASP.NET Core provides services and middleware for localizing into different languages and cultures. Internationalization involves [Globalization](/dotnet/api/system.globalization) and [Localization](/dotnet/standard/globalization-localization/localization). Globalization is the process of designing apps that support different cultures. Globalization adds support for input, display, and output of a defined set of language scripts that relate to specific geographic areas. Localization is the process of adapting a globalized app, which you have already processed for localizability, to a particular culture/locale. For more information see **Globalization and localization terms** near the end of this document. App localization involves the following: 1. Make the app's content localizable 2. Provide localized resources for the languages and cultures you support 3. Implement a strategy to select the language/culture for each request [View or download sample code](https://github.com/aspnet/AspNetCore.Docs/tree/master/aspnetcore/fundamentals/localization/sample/Localization) ([how to download](xref:index#how-to-download-a-sample)) ## Make the app's content localizable Introduced in ASP.NET Core, `IStringLocalizer` and `IStringLocalizer` were architected to improve productivity when developing localized apps. `IStringLocalizer` uses the [ResourceManager](/dotnet/api/system.resources.resourcemanager) and [ResourceReader](/dotnet/api/system.resources.resourcereader) to provide culture-specific resources at run time. The simple interface has an indexer and an `IEnumerable` for returning localized strings. `IStringLocalizer` doesn't require you to store the default language strings in a resource file. You can develop an app targeted for localization and not need to create resource files early in development. The code below shows how to wrap the string "About Title" for localization. [!code-csharp[](localization/sample/Localization/Controllers/AboutController.cs)] In the code above, the `IStringLocalizer` implementation comes from [Dependency Injection](dependency-injection.md). If the localized value of "About Title" isn't found, then the indexer key is returned, that is, the string "About Title". You can leave the default language literal strings in the app and wrap them in the localizer, so that you can focus on developing the app. You develop your app with your default language and prepare it for the localization step without first creating a default resource file. Alternatively, you can use the traditional approach and provide a key to retrieve the default language string. For many developers the new workflow of not having a default language *.resx* file and simply wrapping the string literals can reduce the overhead of localizing an app. Other developers will prefer the traditional work flow as it can make it easier to work with longer string literals and make it easier to update localized strings. Use the `IHtmlLocalizer` implementation for resources that contain HTML. `IHtmlLocalizer` HTML encodes arguments that are formatted in the resource string, but doesn't HTML encode the resource string itself. In the sample highlighted below, only the value of `name` parameter is HTML encoded. [!code-csharp[](../fundamentals/localization/sample/Localization/Controllers/BookController.cs?highlight=3,5,20&start=1&end=24)] **Note:** You generally want to only localize text and not HTML. At the lowest level, you can get `IStringLocalizerFactory` out of [Dependency Injection](dependency-injection.md): [!code-csharp[](localization/sample/Localization/Controllers/TestController.cs?start=9&end=26&highlight=7-13)] The code above demonstrates each of the two factory create methods. You can partition your localized strings by controller, area, or have just one container. In the sample app, a dummy class named `SharedResource` is used for shared resources. [!code-csharp[](localization/sample/Localization/Resources/SharedResource.cs)] Some developers use the `Startup` class to contain global or shared strings. In the sample below, the `InfoController` and the `SharedResource` localizers are used: [!code-csharp[](localization/sample/Localization/Controllers/InfoController.cs?range=9-26)] ## View localization The `IViewLocalizer` service provides localized strings for a [view](xref:mvc/views/overview). The `ViewLocalizer` class implements this interface and finds the resource location from the view file path. The following code shows how to use the default implementation of `IViewLocalizer`: [!code-cshtml[](localization/sample/Localization/Views/Home/About.cshtml)] The default implementation of `IViewLocalizer` finds the resource file based on the view's file name. There's no option to use a global shared resource file. `ViewLocalizer` implements the localizer using `IHtmlLocalizer`, so Razor doesn't HTML encode the localized string. You can parameterize resource strings and `IViewLocalizer` will HTML encode the parameters, but not the resource string. Consider the following Razor markup: ```cshtml @Localizer["Hello {0}!", UserManager.GetUserName(User)] ``` A French resource file could contain the following: | Key | Value | | ----- | ------ | | `Hello {0}!` | `Bonjour {0} !` | The rendered view would contain the HTML markup from the resource file. **Note:** You generally want to only localize text and not HTML. To use a shared resource file in a view, inject `IHtmlLocalizer`: [!code-cshtml[](../fundamentals/localization/sample/Localization/Views/Test/About.cshtml?highlight=5,12)] ## DataAnnotations localization DataAnnotations error messages are localized with `IStringLocalizer`. Using the option `ResourcesPath = "Resources"`, the error messages in `RegisterViewModel` can be stored in either of the following paths: * *Resources/ViewModels.Account.RegisterViewModel.fr.resx* * *Resources/ViewModels/Account/RegisterViewModel.fr.resx* [!code-csharp[](localization/sample/Localization/ViewModels/Account/RegisterViewModel.cs?start=9&end=26)] In ASP.NET Core MVC 1.1.0 and higher, non-validation attributes are localized. ASP.NET Core MVC 1.0 does **not** look up localized strings for non-validation attributes. ### Using one resource string for multiple classes The following code shows how to use one resource string for validation attributes with multiple classes: ```csharp public void ConfigureServices(IServiceCollection services) { services.AddMvc() .AddDataAnnotationsLocalization(options => { options.DataAnnotationLocalizerProvider = (type, factory) => factory.Create(typeof(SharedResource)); }); } ``` In the preceding code, `SharedResource` is the class corresponding to the resx where your validation messages are stored. With this approach, DataAnnotations will only use `SharedResource`, rather than the resource for each class. ## Provide localized resources for the languages and cultures you support ### SupportedCultures and SupportedUICultures ASP.NET Core allows you to specify two culture values, `SupportedCultures` and `SupportedUICultures`. The [CultureInfo](/dotnet/api/system.globalization.cultureinfo) object for `SupportedCultures` determines the results of culture-dependent functions, such as date, time, number, and currency formatting. `SupportedCultures` also determines the sorting order of text, casing conventions, and string comparisons. See [CultureInfo.CurrentCulture](/dotnet/api/system.stringcomparer.currentculture#System_StringComparer_CurrentCulture) for more info on how the server gets the Culture. The `SupportedUICultures` determines which translates strings (from *.resx* files) are looked up by the [ResourceManager](/dotnet/api/system.resources.resourcemanager). The `ResourceManager` simply looks up culture-specific strings that's determined by `CurrentUICulture`. Every thread in .NET has `CurrentCulture` and `CurrentUICulture` objects. ASP.NET Core inspects these values when rendering culture-dependent functions. For example, if the current thread's culture is set to "en-US" (English, United States), `DateTime.Now.ToLongDateString()` displays "Thursday, February 18, 2016", but if `CurrentCulture` is set to "es-ES" (Spanish, Spain) the output will be "jueves, 18 de febrero de 2016". ## Resource files A resource file is a useful mechanism for separating localizable strings from code. Translated strings for the non-default language are isolated *.resx* resource files. For example, you might want to create Spanish resource file named *Welcome.es.resx* containing translated strings. "es" is the language code for Spanish. To create this resource file in Visual Studio: 1. In **Solution Explorer**, right click on the folder which will contain the resource file > **Add** > **New Item**. ![Nested contextual menu: In Solution Explorer, a contextual menu is open for Resources. A second contextual menu is open for Add showing the New Item command highlighted.](localization/_static/newi.png) 2. In the **Search installed templates** box, enter "resource" and name the file. ![Add New Item dialog](localization/_static/res.png) 3. Enter the key value (native string) in the **Name** column and the translated string in the **Value** column. ![Welcome.es.resx file (the Welcome resource file for Spanish) with the word Hello in the Name column and the word Hola (Hello in Spanish) in the Value column](localization/_static/hola.png) Visual Studio shows the *Welcome.es.resx* file. ![Solution Explorer showing the Welcome Spanish (es) resource file](localization/_static/se.png) ## Resource file naming Resources are named for the full type name of their class minus the assembly name. For example, a French resource in a project whose main assembly is `LocalizationWebsite.Web.dll` for the class `LocalizationWebsite.Web.Startup` would be named *Startup.fr.resx*. A resource for the class `LocalizationWebsite.Web.Controllers.HomeController` would be named *Controllers.HomeController.fr.resx*. If your targeted class's namespace isn't the same as the assembly name you will need the full type name. For example, in the sample project a resource for the type `ExtraNamespace.Tools` would be named *ExtraNamespace.Tools.fr.resx*. In the sample project, the `ConfigureServices` method sets the `ResourcesPath` to "Resources", so the project relative path for the home controller's French resource file is *Resources/Controllers.HomeController.fr.resx*. Alternatively, you can use folders to organize resource files. For the home controller, the path would be *Resources/Controllers/HomeController.fr.resx*. If you don't use the `ResourcesPath` option, the *.resx* file would go in the project base directory. The resource file for `HomeController` would be named *Controllers.HomeController.fr.resx*. The choice of using the dot or path naming convention depends on how you want to organize your resource files. | Resource name | Dot or path naming | | ------------ | ------------- | | Resources/Controllers.HomeController.fr.resx | Dot | | Resources/Controllers/HomeController.fr.resx | Path | | | | Resource files using `@inject IViewLocalizer` in Razor views follow a similar pattern. The resource file for a view can be named using either dot naming or path naming. Razor view resource files mimic the path of their associated view file. Assuming we set the `ResourcesPath` to "Resources", the French resource file associated with the *Views/Home/About.cshtml* view could be either of the following: * Resources/Views/Home/About.fr.resx * Resources/Views.Home.About.fr.resx If you don't use the `ResourcesPath` option, the *.resx* file for a view would be located in the same folder as the view. ### RootNamespaceAttribute The [RootNamespace](/dotnet/api/microsoft.extensions.localization.rootnamespaceattribute?view=aspnetcore-2.1) attribute provides the root namespace of an assembly when the root namespace of an assembly is different than the assembly name. > [!WARNING] > This can occur when a project's name is not a valid .NET identifier. For instance `my-project-name.csproj` will use the root namespace `my_project_name` and the assembly name `my-project-name` leading to this error. If the root namespace of an assembly is different than the assembly name: * Localization does not work by default. * Localization fails due to the way resources are searched for within the assembly. `RootNamespace` is a build-time value which is not available to the executing process. If the `RootNamespace` is different from the `AssemblyName`, include the following in *AssemblyInfo.cs* (with parameter values replaced with the actual values): ```csharp using System.Reflection; using Microsoft.Extensions.Localization; [assembly: ResourceLocation("Resource Folder Name")] [assembly: RootNamespace("App Root Namespace")] ``` The preceding code enables the successful resolution of resx files. ## Culture fallback behavior When searching for a resource, localization engages in "culture fallback". Starting from the requested culture, if not found, it reverts to the parent culture of that culture. As an aside, the [CultureInfo.Parent](/dotnet/api/system.globalization.cultureinfo.parent) property represents the parent culture. This usually (but not always) means removing the national signifier from the ISO. For example, the dialect of Spanish spoken in Mexico is "es-MX". It has the parent "es"—Spanish non-specific to any country. Imagine your site receives a request for a "Welcome" resource using culture "fr-CA". The localization system looks for the following resources, in order, and selects the first match: * *Welcome.fr-CA.resx* * *Welcome.fr.resx* * *Welcome.resx* (if the `NeutralResourcesLanguage` is "fr-CA") As an example, if you remove the ".fr" culture designator and you have the culture set to French, the default resource file is read and strings are localized. The Resource manager designates a default or fallback resource for when nothing meets your requested culture. If you want to just return the key when missing a resource for the requested culture you must not have a default resource file. ### Generate resource files with Visual Studio If you create a resource file in Visual Studio without a culture in the file name (for example, *Welcome.resx*), Visual Studio will create a C# class with a property for each string. That's usually not what you want with ASP.NET Core. You typically don't have a default *.resx* resource file (a *.resx* file without the culture name). We suggest you create the *.resx* file with a culture name (for example *Welcome.fr.resx*). When you create a *.resx* file with a culture name, Visual Studio won't generate the class file. ### Add other cultures Each language and culture combination (other than the default language) requires a unique resource file. You create resource files for different cultures and locales by creating new resource files in which the ISO language codes are part of the file name (for example, **en-us**, **fr-ca**, and **en-gb**). These ISO codes are placed between the file name and the *.resx* file extension, as in *Welcome.es-MX.resx* (Spanish/Mexico). ## Implement a strategy to select the language/culture for each request ### Configure localization Localization is configured in the `Startup.ConfigureServices` method: [!code-csharp[](localization/sample/Localization/Startup.cs?name=snippet1)] * `AddLocalization` Adds the localization services to the services container. The code above also sets the resources path to "Resources". * `AddViewLocalization` Adds support for localized view files. In this sample view localization is based on the view file suffix. For example "fr" in the *Index.fr.cshtml* file. * `AddDataAnnotationsLocalization` Adds support for localized `DataAnnotations` validation messages through `IStringLocalizer` abstractions. ### Localization middleware The current culture on a request is set in the localization [Middleware](xref:fundamentals/middleware/index). The localization middleware is enabled in the `Startup.Configure` method. The localization middleware must be configured before any middleware which might check the request culture (for example, `app.UseMvcWithDefaultRoute()`). [!code-csharp[](localization/sample/Localization/Startup.cs?name=snippet2)] `UseRequestLocalization` initializes a `RequestLocalizationOptions` object. On every request the list of `RequestCultureProvider` in the `RequestLocalizationOptions` is enumerated and the first provider that can successfully determine the request culture is used. The default providers come from the `RequestLocalizationOptions` class: 1. `QueryStringRequestCultureProvider` 2. `CookieRequestCultureProvider` 3. `AcceptLanguageHeaderRequestCultureProvider` The default list goes from most specific to least specific. Later in the article we'll see how you can change the order and even add a custom culture provider. If none of the providers can determine the request culture, the `DefaultRequestCulture` is used. ### QueryStringRequestCultureProvider Some apps will use a query string to set the [culture and UI culture](https://msdn.microsoft.com/library/system.globalization.cultureinfo.aspx). For apps that use the cookie or Accept-Language header approach, adding a query string to the URL is useful for debugging and testing code. By default, the `QueryStringRequestCultureProvider` is registered as the first localization provider in the `RequestCultureProvider` list. You pass the query string parameters `culture` and `ui-culture`. The following example sets the specific culture (language and region) to Spanish/Mexico: `http://localhost:5000/?culture=es-MX&ui-culture=es-MX` If you only pass in one of the two (`culture` or `ui-culture`), the query string provider will set both values using the one you passed in. For example, setting just the culture will set both the `Culture` and the `UICulture`: `http://localhost:5000/?culture=es-MX` ### CookieRequestCultureProvider Production apps will often provide a mechanism to set the culture with the ASP.NET Core culture cookie. Use the `MakeCookieValue` method to create a cookie. The `CookieRequestCultureProvider` `DefaultCookieName` returns the default cookie name used to track the user's preferred culture information. The default cookie name is `.AspNetCore.Culture`. The cookie format is `c=%LANGCODE%|uic=%LANGCODE%`, where `c` is `Culture` and `uic` is `UICulture`, for example: c=en-UK|uic=en-US If you only specify one of culture info and UI culture, the specified culture will be used for both culture info and UI culture. ### The Accept-Language HTTP header The [Accept-Language header](https://www.w3.org/International/questions/qa-accept-lang-locales) is settable in most browsers and was originally intended to specify the user's language. This setting indicates what the browser has been set to send or has inherited from the underlying operating system. The Accept-Language HTTP header from a browser request isn't an infallible way to detect the user's preferred language (see [Setting language preferences in a browser](https://www.w3.org/International/questions/qa-lang-priorities.en.php)). A production app should include a way for a user to customize their choice of culture. ### Set the Accept-Language HTTP header in IE 1. From the gear icon, tap **Internet Options**. 2. Tap **Languages**. ![Internet Options](localization/_static/lang.png) 3. Tap **Set Language Preferences**. 4. Tap **Add a language**. 5. Add the language. 6. Tap the language, then tap **Move Up**. ::: moniker range=">= aspnetcore-3.0" ### The Content-Language HTTP header The [Content-Language](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Language) entity header: - Is used to describe the language(s) intended for the audience. - Allows a user to differentiate according to the users' own preferred language. Entity headers are used in both HTTP requests and responses. In ASP.NET Core 3.0 the `Content-Language` header can be added by setting the property `ApplyCurrentCultureToResponseHeaders`. Adding the `Content-Language` header: - Allows the RequestLocalizationMiddleware to set the `Content-Language` header with the `CurrentUICulture`. - Eliminates the need to set the response header `Content-Language` explicitly. ```csharp app.UseRequestLocalization(new RequestLocalizationOptions { ApplyCurrentCultureToResponseHeaders = true }); ``` ::: moniker-end ### Use a custom provider Suppose you want to let your customers store their language and culture in your databases. You could write a provider to look up these values for the user. The following code shows how to add a custom provider: ::: moniker range="< aspnetcore-3.0" ```csharp private const string enUSCulture = "en-US"; services.Configure(options => { var supportedCultures = new[] { new CultureInfo(enUSCulture), new CultureInfo("fr") }; options.DefaultRequestCulture = new RequestCulture(culture: enUSCulture, uiCulture: enUSCulture); options.SupportedCultures = supportedCultures; options.SupportedUICultures = supportedCultures; options.RequestCultureProviders.Insert(0, new CustomRequestCultureProvider(async context => { // My custom request culture logic return new ProviderCultureResult("en"); })); }); ``` ::: moniker-end ::: moniker range=">= aspnetcore-3.0" ```csharp private const string enUSCulture = "en-US"; services.Configure(options => { var supportedCultures = new[] { new CultureInfo(enUSCulture), new CultureInfo("fr") }; options.DefaultRequestCulture = new RequestCulture(culture: enUSCulture, uiCulture: enUSCulture); options.SupportedCultures = supportedCultures; options.SupportedUICultures = supportedCultures; options.AddInitialRequestCultureProvider(new CustomRequestCultureProvider(async context => { // My custom request culture logic return new ProviderCultureResult("en"); })); }); ``` ::: moniker-end Use `RequestLocalizationOptions` to add or remove localization providers. ### Set the culture programmatically This sample **Localization.StarterWeb** project on [GitHub](https://github.com/aspnet/entropy) contains UI to set the `Culture`. The *Views/Shared/_SelectLanguagePartial.cshtml* file allows you to select the culture from the list of supported cultures: [!code-cshtml[](localization/sample/Localization/Views/Shared/_SelectLanguagePartial.cshtml)] The *Views/Shared/_SelectLanguagePartial.cshtml* file is added to the `footer` section of the layout file so it will be available to all views: [!code-cshtml[](localization/sample/Localization/Views/Shared/_Layout.cshtml?range=43-56&highlight=10)] The `SetLanguage` method sets the culture cookie. [!code-csharp[](localization/sample/Localization/Controllers/HomeController.cs?range=57-67)] You can't plug in the *_SelectLanguagePartial.cshtml* to sample code for this project. The **Localization.StarterWeb** project on [GitHub](https://github.com/aspnet/entropy) has code to flow the `RequestLocalizationOptions` to a Razor partial through the [Dependency Injection](dependency-injection.md) container. ## Globalization and localization terms The process of localizing your app also requires a basic understanding of relevant character sets commonly used in modern software development and an understanding of the issues associated with them. Although all computers store text as numbers (codes), different systems store the same text using different numbers. The localization process refers to translating the app user interface (UI) for a specific culture/locale. [Localizability](/dotnet/standard/globalization-localization/localizability-review) is an intermediate process for verifying that a globalized app is ready for localization. The [RFC 4646](https://www.ietf.org/rfc/rfc4646.txt) format for the culture name is `-`, where `` is the language code and `` is the subculture code. For example, `es-CL` for Spanish (Chile), `en-US` for English (United States), and `en-AU` for English (Australia). [RFC 4646](https://www.ietf.org/rfc/rfc4646.txt) is a combination of an ISO 639 two-letter lowercase culture code associated with a language and an ISO 3166 two-letter uppercase subculture code associated with a country or region. See [Language Culture Name](https://msdn.microsoft.com/library/ee825488(v=cs.20).aspx). Internationalization is often abbreviated to "I18N". The abbreviation takes the first and last letters and the number of letters between them, so 18 stands for the number of letters between the first "I" and the last "N". The same applies to Globalization (G11N), and Localization (L10N). Terms: * Globalization (G11N): The process of making an app support different languages and regions. * Localization (L10N): The process of customizing an app for a given language and region. * Internationalization (I18N): Describes both globalization and localization. * Culture: It's a language and, optionally, a region. * Neutral culture: A culture that has a specified language, but not a region. (for example "en", "es") * Specific culture: A culture that has a specified language and region. (for example "en-US", "en-GB", "es-CL") * Parent culture: The neutral culture that contains a specific culture. (for example, "en" is the parent culture of "en-US" and "en-GB") * Locale: A locale is the same as a culture. [!INCLUDE[](~/includes/localization/currency.md)] ::: moniker range=">= aspnetcore-3.0" [!INCLUDE[](~/includes/localization/unsupported-culture-log-level.md)] ::: moniker-end ## Additional resources * * [Localization.StarterWeb project](https://github.com/aspnet/Entropy/tree/master/samples/Localization.StarterWeb) used in the article. * [Globalizing and localizing .NET applications](/dotnet/standard/globalization-localization/index) * [Resources in .resx Files](/dotnet/framework/resources/working-with-resx-files-programmatically) * [Microsoft Multilingual App Toolkit](https://marketplace.visualstudio.com/items?itemName=MultilingualAppToolkit.MultilingualAppToolkit-18308) * [Localization & Generics](https://github.com/hishamco/hishambinateya.com/blob/master/Posts/localization-and-generics.md) * [What is new in Localization in ASP.NET Core 3.0](http://hishambinateya.com/what-is-new-in-localization-in-asp.net-core-3.0)