![]() |
ASP.NET Core Localization and Internationalization |
ASP.NET Core (Wikipedia) is a popular cross-platform open-source web framework and the successor of ASP.NET. ASP.NET Core contains three technologies: Razor Pages, MVC, and Web API. ASP.NET Core's online documentation contains useful topics. Read it first.
ASP.NET Core and Blazor use a new way to localize strings. They still use .resx files like the other .NET platforms, but you no longer need to create the original .resx files. All you have to do is to inject a localizer interface and then call the localizer's this[string name] indexer for those strings that you want to localize.
The end of the document contains links to get the full source code of the samples. After reading this document, we recommend reading a tutorial about how to use Soluling.
The following instructions are for .NET 6 and later, which use the minimal hosting model (Program.cs without Startup.cs). The samples use .NET 9.
The localization classes are part of the ASP.NET Core shared framework, so you don't need to add any NuGet package. Add the following lines to _ViewImports.cshtml.
@using Microsoft.AspNetCore.Mvc.Localization @inject IViewLocalizer Localizer
This will automatically inject IViewLocalizer into each view or page.
View and page files typically contain a lot of strings that need to be localized. For example, the following view contains a string in the title element.
<title>Driving Time Calculator</title>
To localize the title element, use the injected IViewLocalizer.
<title>@Localizer["Driving Time Calculator"]</title>
If you did not add the IViewLocalizer injection into _ViewImports.cshtml, you have to inject the localizer into each view or page.
@inject IViewLocalizer Localizer
For controllers, view models, and page models, you perform similar steps. First, define a local IStringLocalizer variable.
private IStringLocalizer localizer;
Then inject an IStringLocalizer<T> into the constructor and assign that into the local variable. Make sure you inject a generic version of IStringLocalizer and pass the type of the model into the localizer type.
public IndexModel(IStringLocalizer<IndexModel> localizer)
{
this.localizer = localizer;
}
You don't have to pass the page class, but you can pass any class. For example, if you want to have all strings in a single .resx file, always pass the same class, no matter whether it is the controller, page, or view.
public IndexModel(IStringLocalizer<Shared> localizer)
{
this.localizer = localizer;
}
The difference between the above two samples is that in the first sample, the strings are stored in IndexModel.resx, and that file contains only the strings of this class. In the second sample, the strings are stored in Shared.resx, and if that class is used somewhere else, the combined strings of all classes using it are stored in the Shared.resx file.
Finally, use the localizer object in your code.
public void OnGet()
{
Message = localizer["Enter values and click Calculate."];
}
Optionally, you can also add a comment on the same line following the localizer call.
str = localizer["Enter values and click Calculate."]; //loc This is a comment
If you use String.Format or string interpolation such as
str = string.Format("Sport {0}", Value.Id);
str = $"Sport {Value.Id}";
use localizer
str = localizer["Sport {0}", Value.Id]; //loc 0: Id of the sport
Note! You cannot use string interpolation with the localizer. For example, this won't work.
str = localizer[$"Sport {Value.Id}"];
This is because the interpolated string will be evaluated before it is passed to the localizer, resulting in a different string passed each time depending on the ID value.
This is all you have to do to get the strings ready for localization. Previously with ASP.NET, you had to manually create .resx files and add each string into these files. With ASP.NET Core, you no longer have to do this.
Some of your .cs files might have strings that need to be localized. If so, you can inject IStringLocalizer into the class and use it. The traditional way to inject anything into the class is through the constructor.
using Microsoft.Extensions.Localization;
namespace Sample
{
public class MyClass
{
private IStringLocalizer _localizer;
public MyClass(IStringLocalizer<MyClass> localizer)
{
_localizer = localizer;
}
public string GetValue()
{
return _localizer["This is a sample"];
}
}
}
The type parameter you use with IStringLocalizer can be whatever type you want to use. Each type you use will have its own .resx generated. So if you want to have only one .resx, always use the same type.
In a Razor component, you can also use the [Inject] attribute instead of a constructor. Then you don't need a constructor, and you don't have to store the injected localizer object. Note! [Inject] works only in components (classes derived from ComponentBase, such as the code-behind of a .razor file). In a plain class, such as a service, the property is not set, so use constructor injection there.
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
namespace Sample
{
public partial class MyComponent : ComponentBase
{
[Inject]
public IStringLocalizer<MyComponent> Localizer { get; set; }
public string GetValue()
{
return Localizer["This is a sample"];
}
}
}
To use the localized resource files, we need to configure our project for localization in Program.cs. First, we need to specify the location where the localized .resx files are.
var builder = WebApplication.CreateBuilder(args);
// Begin I18N configuration
builder.Services.AddLocalization(opts => { opts.ResourcesPath = "Resources"; });
// End I18N configuration
To enable IViewLocalizer, call AddViewLocalization when you add MVC or Razor Pages to the services.
// MVC builder.Services.AddControllersWithViews() .AddViewLocalization(); // Razor Pages builder.Services.AddRazorPages() .AddViewLocalization();
Then we need to tell what our supported locales are and what the default locale is. The following code adds English, German, and Finnish as supported cultures.
var app = builder.Build();
// Begin I18N configuration
var supportedCultures = new[] { "en", "de", "fi" };
var options = new RequestLocalizationOptions()
.SetDefaultCulture(supportedCultures[0])
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures);
app.UseRequestLocalization(options);
// End I18N configurationThe above is the standard way to configure the middleware for localization. However, it hard-codes the supported languages into your code. If you later add a new language, you need to modify the source code, compile, and redeploy your application. To avoid this, you could place the language codes into the configuration file and read them from the file on runtime. To make this even easier, we have implemented a class extension for IApplicationBuilder. The localization configuration takes only one line.
// Begin I18N configuration app.UseRequestLocalizationWithAvailableLanguages(Assembly.GetExecutingAssembly().Location, "en"); // End I18N configuration
The above code configures the localization but also detects the deployed satellite assembly files and uses those languages when configuring the localization. To add a new language, you only have to deploy the satellite assembly file on the server. You can get the code from GitHub and <data-dir>\Library\NET\ASP.NET.
Most models contain data annotations such as display names of properties and validation errors. ASP.NET Core has built-in support for localizing data annotations. Except for the annotations, you don't have to add anything else to the model. However, you have to configure the services to localize the data annotations. Call AddDataAnnotationsLocalization in Program.cs.
builder.Services.AddRazorPages() .AddViewLocalization() .AddDataAnnotationsLocalization();
Add the Display attribute to every property and enum value, and the ErrorMessage to the validation attributes.
public class Sport
{
[Display(Name = "Name")]
[Required(ErrorMessage = "The name is required.")]
public string Name { get; set; }
[Display(Name = "Kind")]
public SportKind Kind { get; set; }
}
public enum SportKind
{
[Display(Name = "Individual")]
Individual,
[Display(Name = "Team")]
Team
}
ASP.NET Core uses culture providers to choose the current language. Each provider can set the language in its own way. The purpose of a provider is to set Culture and UICulture of the thread handling the HTTP request. There are three built-in providers. They are:
QueryStringRequestCultureProvider provider uses a query string to pass the culture. The culture parameter sets Culture, and the ui-culture parameter sets UICulture. The following example sets the culture to Finnish in Finland.
https://www.soluling.com/?culture=fi-FI&ui-culture=fi-FI
If you only pass one parameter, both Culture and UICulture are set to the same value.
https://www.soluling.com/?culture=fi-FI
CookieRequestCultureProvider provider reads the Culture and UICulture parameters from a cookie. The default cookie name is .AspNetCore.Culture.
c=de-DE|uic=de-DE
where c is Culture and uic is UICulture.
AcceptLanguageHeaderRequestCultureProvider provider reads the culture from the Accept-Language HTTP header.
By default, ASP.NET Core localization configures the three above providers in such a way that first the query string is used (if any), then a cookie is used (if any), and finally, Accept-Language is used. If you don't pass the query parameter and you don't have a cookie, then the default language of the browser (i.e., Accept-Language) is used. You can override Accept-Language by setting a cookie, and you can override the cookie by passing a culture parameter. For most users, this is what you want. However, if you want to store the active culture in a database, you can write your own provider and add it to the RequestCultureProviders of the options in Program.cs.
Soluling does not replace the built-in internationalization and localization method of ASP.NET Core and Blazor. Instead, Soluling extends it, making application internationalization and localization much easier. The following list shows the Soluling features that help you when localizing ASP.NET Core and Blazor applications.
ASP.NET Core and Blazor do not require the original .resx files to run the application in the original language. Still, they need localized .resx files to run the application in any other language. To create the localized .resx files, you will need the original .resx files as a template. So, the original .resx files are required. It is a time-consuming and error-prone job to create them and make sure they are up to date. Here is where Soluling provides an enormous help. Soluling reads your .csproj file to locate all your source code files (.cshtml, .cs, and .razor), finding the instances where a localizer interface or data annotation attributes have been used. When found, Soluling extracts the string passed to the localizer interface and writes the string into the right .resx file.
You no longer have to create and maintain the original .resx files, but Soluling creates them for you and always keeps them up to date. If you modify strings in the source code, you just run Soluling to update the resource files. In addition to extracting the strings and writing the original .resx files, Soluling also reads the string comments from your source code. Everything, your strings, and comments are all in place in your source code, and you never have to write any .resx.
You only have to tell Soluling where your .csproj or .sln file is located. No need to select the individual resource or source code files. If you later add new pages, views, controllers or components to your application, you don't have to change your localization settings at all.
After Soluling has created the original .resx files, it scans the .resx files, extracting the strings. Use Soluling to translate the project. Finally, when building, Soluling creates localized .resx files. It can also make localized satellite assembly files.
All this can be done in your build process using Soluling's command-line tool, SoluMake.
Now that our application has been internationalized, we can create a Soluling project for it. Start Soluling. Drag your .csproj file into Soluling or click New from File or Files and browse the .csproj file. The project wizard appears, showing the Localizer options page.

When using Soluling, you do not have to create the original resource files, but Soluling automatically scans your source code to create them. By default, the original resource file will only exist in the memory, but you can optionally also make Soluling write the files. Use the third option if you do the extraction by yourself. For most projects, the default settings work fine. To learn the meaning of each option, read the documentation.
Click Next. The Resources page appears. Accept the default settings where only the .resx files are localized.

Click Next. If there are existing localized files, Soluling shows the Import existing localized files page. You can select the languages that are imported.

Click Next. The Select Languages page appears. Add the languages you want to support and also select the original language.

We added Finnish and German as target languages and English as the original language. Complete the wizard by clicking Finish. A new project appears.
Enter the translations or let your translator translate the project. Learn more about how to translate a project.
Once translated, choose Home | Build All to build the localized files. Soluling writes the localized .resx files (e.g., Resources\Pages\Index.fi.resx) and creates the satellite assembly files (e.g., fi\<assembly>.resources.dll in the output directory). Deploy the satellite assemblies with your application. Use the Satellite assembly options and Output sheets to configure the output.
When you change the strings in your source code, open the Soluling project and choose Home | Scan. Soluling extracts the strings again, keeps the existing translations, and you only translate the strings that were added or changed.
With Soluling Cloud, you and your translators can translate the strings in a browser using Translate. Soluling Cloud cannot scan a Visual Studio project itself, so you create the project in Soluling Desktop, upload it to Soluling Cloud, and build the localized files in Soluling Desktop. You need a Soluling account. See the available Cloud plans.
.ntp).The project now also appears on the dashboard (Cloud > Dashboard for Projects, Users and Terminology at www.soluling.com), marked as created from a .ntp file.
Click the project's green Translate button on the dashboard and translate, or click the project's Manage button and invite a translator for each language under Languages and Invitations. The comments you wrote in the source code (//loc) are shown to the translators.
In Soluling Desktop, on the Translate tab, click Import from Cloud. Soluling downloads the translations made in the cloud and imports them into your local project. Then choose Home | Build All to build the localized .resx files and satellite assemblies.
When you change the strings in your source code, in Soluling Desktop, on the Home tab, click Scan so the project picks up the changes. On the Translate tab, click Upload to Cloud. Translate the new and changed strings in Translate, then Import from Cloud and build again. To do the same steps from a build script, use SoluMake. See the Cloud tutorial.
If you use MVC, there is another option to localize view files (.cshtml). Instead of using Localizer in your views and using .resx to localize them, Soluling can make localized versions of the view files. To enable view file localization, check HTML resources in the Resources sheet.

The benefit of the view localization is that you do not have to modify your view by injecting and adding localizer. Instead, you can hard code strings in HTML markup or script parts. To learn more about view file localization, see the <data-dir>\Samples\ASP.NET\Core\MVC\DrivingView sample.
Note! Razor Pages do not currently support localized page files, so using the localizer is the only option. Web API does not use view or page files, so there is no need to localize them.
GitHub and <data-dir>\Samples\ASP.NET\Core contain sample subdirectories for each ASP.NET Core programming model. Samples.sln contains all the samples.
GitHub and <data-dir>\Samples\ASP.NET\Core\MVC contain the following ASP.NET Core MVC samples:
| GitHub/Directory | Description |
|---|---|
| Driving | A simple driving time calculator sample that shows how to localize strings and images and how to use plural enabled message strings. |
| DrivingView | As above, but each view file has a set of localized view files. |
| HelloWorld | A localized Hello World application. |
| Minimal | A minimal localized application. |
GitHub and <data-dir>\Samples\ASP.NET\Core\RazorPages contain the following ASP.NET Core Razor Pages samples:
| GitHub/Directory | Description |
|---|---|
| Driving | A simple driving time calculator sample that shows how to localize strings and images and how to use plural enabled message strings. |
| Sport | A simple localized sport application that uses the multilingual sport API (see below). Learn more about the application from here. |
GitHub and <data-dir>\Samples\ASP.NET\Core\WebAPI contain the following ASP.NET Core Web API samples:
| GitHub/Directory | Description |
|---|---|
| SimpleAPI | A simple multilingual API that uses hard coded resources. Implements only the GET method. |
| SportAPI | A multilingual API that stores sports in a database. Implements a CRUD (Wikipedia) API. Learn more about the application here. |
The WebAPI directory also contains Waterpolo.json and Waterpolo_fi.json and Add.bat that uses curl to add a new sport and its Finnish translation to the SportAPI.
| GitHub/Directory | Description |
|---|---|
| Console\HelloWorldConsole | A console application that uses IStringLocalizer outside of a web application. |
| SportLibrary | A class library that contains the sport model and service that the Sport and SportAPI samples use. |
| MachineTranslationLibrary | A class library that translates text using machine translation services. |