ASP.NET Core localization and translation

ASP.NET Core localization and translation with examples

Localization is crucial for making your ASP.NET Core applications accessible to users from different cultures and languages. By integrating effective software internationalization practices, you ensure that your applications are not only linguistically accurate but also culturally relevant, enhancing user experience across diverse markets. In this tutorial, we’ll explore how to implement localization and translation in ASP.NET Core.

We’ll cover:

  • ASP.NET Core i18n/l10n (internationalization and localization)
  • Adding language resources and following localization conventions
  • Localizing text with IStringLocalizer
  • Translating views with IViewLocalizer
  • Detecting user culture using IRequestCultureProvider
  • Localizing date and time formats
  • Handling pluralization in localization

Check out the source code for this tutorial on GitHub.

    Prerequisites

    Before you begin, make sure you have:

    • ASP.NET Core 8+ installed
    • Visual Studio 2022
    • Basic understanding of C# and ASP.NET

    ASP.NET Core localization: Preparing project

    First, let’s set up a basic ASP.NET Core project that we’ll later turn into an internationalized web application.

    Open Visual Studio and create a new project with the following configuration:

    • Template: ASP.NET Core Empty
    • Name: ASPLokaliseDemo
    • Target Framework: .NET 8.0 (LTS)
    • Note: Tick the “Place solution and project in the same directory” option.

    Setting up services and building the application

    Next, open the Program.cs file and add the following code::

    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    builder.Services.AddControllersWithViews();
    builder.Services.AddRazorPages();
    
    var app = builder.Build();

    Key points:

    • Initializes the WebApplicationBuilder to configure services and middleware.
    • Registers MVC and Razor Pages services for dependency injection.
    • Builds the app with the configured services.

    Configuring the HTTP request pipeline

    Now, let’s configure the HTTP request pipeline by adding this code into the same file:

    // Configure the HTTP request pipeline.
    if (app.Environment.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    
    app.UseHttpsRedirection();
    app.UseStaticFiles();

    Key points:

    • Enables the Developer Exception Page in development mode.
    • Redirects HTTP requests to HTTPS for security.
    • Serves static files like HTML, CSS, and JavaScript.

    Adding routing and endpoint mapping

    Finally, set up routing and endpoint mapping in the same file:

    app.UseRouting();
    
    app.UseAuthorization();
    
    app.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
    
    app.MapRazorPages();
    
    app.Run();

    Key points:

    • UseRouting matches incoming requests to their corresponding endpoints.
    • UseAuthorization checks for user permissions.
    • Maps the default route and Razor Pages.
    • Runs the application and handles incoming HTTP requests.

    Creating the Home Controller

    To set up a Home Controller for your project:

    1. Create a Controllers directory in the project’s root.
    2. Add the HomeController:
      • In Visual Studio, right-click the Controllers directory.
      • Select Add > New Item…
      • Choose MVC Controller – Empty and name it HomeController.cs.
    3. Update HomeController.cs:
      • Visual Studio will generate a basic HomeController class with an Index method. This method will be the default action when users navigate to the root URL.

    Here’s what the HomeController.cs should look like:

    using Microsoft.AspNetCore.Mvc;
    
    namespace ASPLokaliseDemo.Controllers
    {
        public class HomeController : Controller
        {
            public IActionResult Index()
            {
                return View();
            }
        }
    }

    The HomeController is created to match the “default” route set up earlier in Program.cs. When the application runs, and the user navigates to the root URL (e.g., https://localhost:5001/), the request is routed to the Index action of the HomeController.

    Creating the Razor view for the Index action

    With your HomeController set up, it’s time to create the view for the Index action:

    1. Create a Views directory in the project’s root.
    2. Add a Home folder inside the Views directory.
    3. Add the Index.cshtml file:
      • Right-click the Home folder.
      • Select Add > New Item…
      • Choose Razor View and name it Index.cshtml.
    4. Fill in Index.cshtml with the following code:
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="utf-8" />
        <title>ASP.NET localization demo | Lokalise</title>
    </head>
    <body>
        <p>Ready to get localized!</p>
    </body>
    </html>

    Razor views are used to generate HTML dynamically on the server. The Index.cshtml file corresponds to the Index action in HomeController. When this action is invoked, it looks for a view named Index.cshtml within the Views/Home/ directory by convention.

    Resource (RESX) files for ASP.NET localization

    Before diving into localization logic, we need to set up language resources in our ASP.NET Core project. In ASP.NET Core, localized strings for classes or views are stored in .resx files.

    ASP.NET Core localized resource file organizing

    In ASP.NET Core, localization involves storing strings in .resx resource files. These files contain the localized text for different languages and are referenced by the corresponding views or classes within the application.

    Organizing and naming resource files

    Resource files follow specific naming conventions:

    • Matching namespace: If the namespace matches the project’s assembly name, the resource file should be named after the class.
      • Example: For ASPNETCoreL10n.HomeController, name the resource file HomeController.resx.
    • Non-matching namespace: If the namespace doesn’t match, use the fully qualified class name.
      • Example: For ASPNETCoreUtils.StringFormatter, name the resource file ASPNETCoreUtils.StringFormatter.resx.

    Resource files are typically placed in the same directory as the class or view they localize, simplifying the association. Alternatively, you can organize all resources in a centralized Resources folder.

    Supporting multiple locales

    To support multiple languages, create separate .resx files for each locale:

    • Neutral culture: <resource-file-name>.<language>.resx
      • Example: HomeController.en.resx (for English)
    • Specific culture: <resource-file-name>.<language>-<region>.resx
      • Example: HomeController.fr-FR.resx (for French – France)

    The language and region codes follow ISO standards, such as en-US for English (United States) and fr-FR for French (France).

    Creating resource files for localization

    To support English (US) and French (France) localization in your HomeController, follow these steps:

    1. Create the Resources directory:
      • In the root of your project, create a new folder named Resources.
    2. Add resource files:
      • Inside the Resources folder, add two resource files named Controllers.HomeController.en-US.resx and Controllers.HomeController.fr-FR.resx.
    3. Adding keys to resource files:
      • Open one of the resource files, like Controllers.HomeController.en-US.resx, in Visual Studio.
      • Click on the “plus” icon (or “Add Resource” if you see that option).
      • A dialog will appear, prompting you to enter the details of the new resource.
        • Name: Enter welcome. This is the key that you’ll use in your code to reference this resource.
        • Value: Enter Welcome!. This is the English text that will be displayed to users when the welcome key is accessed.
      • Click “Add” or “OK” to save the resource.
    4. Handling multiple locales:
      • After adding the key to one resource file, repeat the process for the other file (Controllers.HomeController.fr-FR.resx).
      • In the French resource file, set the Value for the welcome key to Bienvenue !.
    5. Fallback resource file:
      • Once you add resources, you might notice that a default Controllers.HomeController.resx file is created in the same directory. This file acts as a fallback for when a specific localization isn’t available. Open it, and you should see the welcome key listed. However, this file typically remains empty or contains neutral/default values.
    6. Review the resource table:
      • In each resource file, you’ll see a table where your welcome key is listed along with its corresponding values. For the en-US file, you’ll have Welcome!, and for the fr-FR file, you’ll have Bienvenue !. These values will be used based on the user’s locale.

    By following these steps, you’ve successfully set up localized resources for your HomeController in English and French!

    Implementing ASP.NET core localization with IStringLocalizer

    In ASP.NET Core, using the IStringLocalizer interface is the recommended way to manage localization. It integrates smoothly with the framework, making your code cleaner and easier to maintain.

    Setting up localization in the application

    First, we need to configure our application to support localization. Open your Program.cs file and update it with the following code:

    var builder = WebApplication.CreateBuilder(args);
    
    builder.Services.AddControllersWithViews();
    builder.Services.AddRazorPages();
    
    // Add localization services
    builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
    
    var app = builder.Build();
    
    // Enable request localization middleware
    app.UseRequestLocalization();
    
    // Additional configurations like routing, authorization, etc.
    // ...
    
    app.Run();

    In this setup, we’re adding localization services and pointing to the Resources folder for our .resx files. The UseRequestLocalization middleware is crucial—it ensures that your application can switch between different cultures and languages based on the user’s request.

    Localizing strings in the HomeController

    Now, let’s modify the HomeController to use IStringLocalizer for fetching localized strings. Here’s how to do it:

    1. Open the HomeController.cs file.
    2. Update the class to inject IStringLocalizer and use it to retrieve the localized strings:
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Localization;
    
    namespace ASPLokaliseDemo.Controllers
    {
        public class HomeController(IStringLocalizer<HomeController> localizer) : Controller
        {
            public IActionResult Index()
            {
                ViewData["greeting"] = localizer["welcome"];
                return View();
            }
        }
    }

    Here’s what’s happening:

    • IStringLocalizer<HomeController> is injected into the controller, giving you access to localized strings specific to this controller.
    • The line _localizer["welcome"] retrieves the localized string associated with the welcome key. This string will change based on the current culture set in the user’s request.

    Displaying the localized message in the view

    Finally, let’s update the view to display the localized message. Open the Views/Home/Index.cshtml file and modify it as follows:

    <body>
        <h1>@ViewData["greeting"]</h1>
    </body>

    Now, when a user visits the homepage, they’ll see a greeting message that’s automatically translated based on their language preference.

    Switching locales in ASP.NET Core

    By default, your ASP.NET Core application might always display the same locale (like en-US), even if you change your browser’s language settings or access the site from different regions. This happens because we haven’t yet configured the application to detect and switch between supported locales automatically.

    Setting up supported cultures

    To support different cultures, you need to specify which ones your application can handle. In ASP.NET Core, this is done using the SupportedCultures and SupportedUICultures properties:

    • SupportedCultures: These cultures affect culture-specific operations like date and time formatting.
    • SupportedUICultures: These cultures determine how the UI elements (such as text in Razor Views) are localized.

    Without defining these properties, ASP.NET Core won’t know which languages or cultures your application is ready to handle.

    Using UseRequestLocalization middleware

    To enable your application to switch locales based on user requests (like their browser’s language setting), you’ll configure the UseRequestLocalization middleware. This middleware adjusts the application’s culture for each request based on the user’s preferences.

    First, update Program.cs with the following code:

    using System.Globalization;
    
    var builder = WebApplication.CreateBuilder(args);
    
    // Add localization services and specify the Resources folder
    builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
    
    var app = builder.Build();
    
    // Define the supported cultures
    var supportedCultures = new[] { new CultureInfo("en-US"), new CultureInfo("fr-FR") };
    
    // Configure the Request Localization options
    var requestLocalizationOptions = new RequestLocalizationOptions
    {
        DefaultRequestCulture = new RequestCulture("en-US"),  // Default culture
        SupportedCultures = supportedCultures,
        SupportedUICultures = supportedCultures
    };
    
    // Add the Request Localization middleware
    app.UseRequestLocalization(requestLocalizationOptions);
    
    // Configure the HTTP request pipeline
    if (app.Environment.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    
    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseRouting();
    app.UseAuthorization();
    
    app.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
    
    app.MapRazorPages();
    
    app.Run();

    In this configuration:

    • SupportedCultures: We specify the cultures (en-US for English and fr-FR for French) that our application supports.
    • RequestLocalizationOptions: This object sets the supported cultures and defines a default culture (en-US in this example).
    • UseRequestLocalization: This middleware is added early in the pipeline to ensure that the culture is set correctly for each request.

    Enhancing locale switching with RequestCultureProviders

    To further refine how your application detects and switches locales, you can add various RequestCultureProviders. These providers determine the culture based on different sources, like query strings, cookies, or browser headers.

    Here’s how to include additional providers:

    // Define the supported cultures
    var supportedCultures = new[] { new CultureInfo("en-US"), new CultureInfo("fr-FR") };
    
    // Configure the Request Localization options
    var requestLocalizationOptions = new RequestLocalizationOptions
    {
        DefaultRequestCulture = new RequestCulture("en-US"),
        SupportedCultures = supportedCultures,
        SupportedUICultures = supportedCultures,
        // Explicitly specifying the type for RequestCultureProviders
        RequestCultureProviders =
        [
            new QueryStringRequestCultureProvider(),
            new CookieRequestCultureProvider(),
            new AcceptLanguageHeaderRequestCultureProvider()
        ]
    };
    
    app.UseRequestLocalization(requestLocalizationOptions);

    You can access your site with a URL like https://localhost:5001/?culture=fr-FR to see the French version.

    Understanding RequestCultureProviders

    RequestCultureProviders in ASP.NET Core check different sources to determine the appropriate culture for each request:

    • QueryStringRequestCultureProvider: This provider checks the URL query string for culture information. For example, https://localhost:5001/?culture=fr-FR&ui-culture=fr-FR would set the culture to French.
    • CookieRequestCultureProvider: This provider reads the user’s culture preference from a cookie. It’s useful for remembering the user’s language across sessions.
    • AcceptLanguageHeaderRequestCultureProvider: This provider uses the Accept-Language header sent by the browser to determine the culture, typically reflecting the user’s language preferences in their browser settings.

    The order in which these providers are listed matters. ASP.NET Core will go through them sequentially and apply the first one that successfully determines a culture. A common order might start with the QueryStringRequestCultureProvider for explicit user choices, followed by the CookieRequestCultureProvider to remember past choices, and finally, the AcceptLanguageHeaderRequestCultureProvider as a fallback based on the browser’s settings.

    Date and time localization

    After setting up text localization and enabling language switching, it’s important to ensure that dates and times are also formatted according to the user’s culture. Date and time formats vary widely between cultures, and ASP.NET Core makes it easy to display these in the appropriate format.

    Displaying a localized date on the home page

    To demonstrate, let’s add the current date to the home page and ensure it’s localized based on the selected culture.

    In your HomeController, pass the current date and time to the view. ASP.NET Core will automatically format this date according to the current culture.

    Here’s how to modify the Index action:

    public IActionResult Index()
    {
        ViewData["greeting"] = localizer["welcome"];
        ViewData["currentDate"] = DateTime.Now.ToString("D", CultureInfo.CurrentCulture);  // "D" stands for long date format
        return View();
    }

    What’s happening:

    • DateTime.Now.ToString("D", CultureInfo.CurrentCulture) fetches the current date and formats it as a long date string (like “Monday, August 16, 2024”) according to the user’s selected culture. The "D" specifier tells ASP.NET Core to use the long date pattern, which includes the day of the week, the full month name, the day, and the year.
    • CultureInfo.CurrentCulture automatically reflects the culture set by the localization providers (query string, cookie, or browser settings), adapting the date format to match the user’s locale.

    Now, update the Index.cshtml view to display the localized date alongside the greeting message:

    <body>
        <h1>@ViewData["greeting"]</h1>
        <p>@ViewData["currentDate"]</p>
    </body>

    What’s happening:

    • The @ViewData["currentDate"] line will now display the date formatted according to the user’s selected culture.

    Custom date format example

    If you prefer a different format, such as "Monday, August 16, 2024", you can specify it directly:

    ViewData["currentDate"] = DateTime.Now.ToString("dddd, MMMM dd yyyy", CultureInfo.CurrentCulture);

    Handling time zones

    If your application serves users in different time zones, you may want to convert the date and time to the user’s local time zone before displaying it:

    var userTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time"); // Example for CET
    ViewData["currentDate"] = TimeZoneInfo.ConvertTime(DateTime.Now, userTimeZone).ToString("F", CultureInfo.CurrentCulture);

    In this case, the “F” format specifier produces a detailed date and time string that includes the day of the week, the full month name, the day, the year, and the time down to the seconds.

    Pluralization

    When localizing content, it’s important to account for pluralization—how words change depending on quantity. Different languages handle plurals in unique ways, so ensuring that your application correctly displays singular and plural forms is crucial for providing a natural user experience.

    To handle pluralization, we’ll add entries for both singular and plural forms in our resource files. First, open the HomeController.en-US.resx file using the XML editor, and add the following entries:

      <data name="welcome" xml:space="preserve">
        <value>Welcome!</value>
      </data>
      <data name="AppleCount" xml:space="preserve">
        <value>{0} apple</value>
      </data>
      <data name="AppleCountPlural" xml:space="preserve">
        <value>{0} apples</value>
      </data>

    These entries define the messages for singular (AppleCount) and plural (AppleCountPlural) forms. The {0} placeholder will be replaced by the actual number at runtime.

    Do the same for the French resource file (HomeController.fr-FR.resx):

      <data name="welcome" xml:space="preserve">
        <value>Bienvenue !</value>
      </data>
      <data name="AppleCount" xml:space="preserve">
        <value>{0} pomme</value>
      </data>
      <data name="AppleCountPlural" xml:space="preserve">
        <value>{0} pommes</value>
      </data>

    Next, we’ll modify the HomeController to decide whether to use the singular or plural form based on the number of items (in this case, apples).

    Here’s the updated Index action:

    public IActionResult Index()
    {
        ViewData["greeting"] = localizer["welcome"];
        // ViewData["currentDate"] = DateTime.Now.ToString("D", CultureInfo.CurrentCulture);  // "D" stands for long date format
        var userTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time"); // Example for CET
        ViewData["currentDate"] = TimeZoneInfo.ConvertTime(DateTime.Now, userTimeZone).ToString("F", CultureInfo.CurrentCulture);
    
        int appleCount = 3;  // Example count
        string appleMessage = appleCount == 1
            ? localizer["AppleCount", appleCount]
            : localizer["AppleCountPlural", appleCount];
    
        ViewData["appleMessage"] = appleMessage;
    
        return View();
    }

    Finally, let’s display the localized message in the view. Open the Index.cshtml file and update it as follows:

    <body>
        <h1>@ViewData["greeting"]</h1>
        <p>@ViewData["currentDate"]</p>
        <p>@ViewData["appleMessage"]</p>
    </body>

    Now your text is pluralized!

    Using IViewLocalizer for Views in ASP.NET Core

    While IStringLocalizer is excellent for general localization within controllers and other backend code, ASP.NET Core provides IViewLocalizer specifically for localizing content directly within Razor views. This can make your view code cleaner and more tightly integrated with localization, especially when you need to localize strings that are specific to a particular view.

    IViewLocalizer is an interface in ASP.NET Core designed for use in Razor views. It automatically associates the localized strings with the specific view they are used in. This means that it looks for resource files that match the view’s path and name, making it very convenient for localizing view-specific content.

    Setting Up IViewLocalizer in a View

    IViewLocalizer is a specialized service in ASP.NET Core used for localizing content directly within Razor views. It’s particularly useful when the text you want to localize is closely tied to a specific view, keeping your localization logic organized and easy to manage.

    To use IViewLocalizer in a Razor view, you first need to inject it using the @inject directive. This makes the IViewLocalizer service available within the view.

    For example, in your Index.cshtml view, add the following:

    <p>From IView: @Localizer["welcome_iview"]</p>

    Next, you need to ensure that your ASP.NET Core application is configured to support view localization. Open the Program.cs file and add the necessary services:

    using Microsoft.AspNetCore.Localization;
    using System.Globalization;
    
    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    builder.Services.AddControllersWithViews();
    builder.Services.AddRazorPages();
    
    // Add localization services
    builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
    
    // Register View Localization
    builder.Services.AddMvc()
        .AddViewLocalization();  // This adds support for IViewLocalizer
    
    var app = builder.Build();

    To localize content in a specific view, you’ll need to create resource files corresponding to that view:

    • Resources/Views/Home/Index.en-US.resx
    • Resources/Views/Home/Index.fr-FR.resx

    Then create translation keys inside as before. Make sure that your key are assigned to the proper resources file (to the Index and not to the resources for controllers). You can easily move your translation keys:

    Resource file for the index view

    When to Use IViewLocalizer vs. IStringLocalizer

    It’s important to choose the right localizer based on your needs:

    • IViewLocalizer: Use this when the content you’re localizing is specific to a single view. It keeps localization tied to the view, which can simplify maintenance since everything related to that view is kept together. For example, a localized string that only appears on the Index page is a good candidate for IViewLocalizer.
    • IStringLocalizer: This is more flexible and can be used across controllers, services, and views. It’s ideal for localization that spans multiple areas of your application, such as common phrases, error messages, or UI elements that are reused in different parts of the site. IStringLocalizer is best for centralizing localization, making it easier to update common strings across the application.

    Example: If you have a string like “Submit” that appears on various buttons across multiple views, IStringLocalizer would be the better choice. But if you have a unique message or header that only shows up on a single page, IViewLocalizer might be more appropriate.

    Let Lokalise do the localizing

    In this section, I’d like to introduce you to Lokalise, a translation management system that handles all your ASP.NET application’s internationalization needs.

    Lokalise offers a range of features that simplify the localization process, such as easy integration with third-party services like GitHub, Jira, Figma, Trello, S3, and more. It also supports collaborative translations, provides quality assurance tools, and allows easy translation management through a central dashboard.

    With these features, Lokalise makes it easier to expand your ASP.NET Core app to all the locales you plan to reach.

    Getting started with Lokalise is simple:

    • Sign up for a free trial (no credit card information required).
    • Log in to your account.
    • Follow the wizard instructions to create a new Web and mobile project.
      • Make sure to select the correct base and target languages. For instance, if your sample app’s default locale is en-US with support for fr-FR, ensure you choose similar locales in Lokalise. Don’t worry if you make a mistake; you can easily adjust the languages later through the UI.

    Once your project is set up, open it and go to the Upload page. From there, choose the RESX files from your ASP.NET project:

    Upload RESX files to Lokalise TMS

    Alternatively, you can drag and drop the entire Resources folder into the designated area.

    Starting file upload to Lokalise

    After uploading, return to the Editor to modify your translations, add more languages, and create new keys as needed.

    Editing ASP.NET translation files on Lokalise

    When you’re ready, navigate to the Download page, select RESX as the file format, and download your translated files back into your project.

    Downloading RESX files for ASP.NET translation

    That’s it! For more detailed guidance, check out the Getting started documentation on Lokalise, where you’ll find a collection of articles to help you kick-start your localization journey.

    Conclusion

    Localizing your ASP.NET Core application is essential for reaching a global audience. By leveraging built-in tools like IStringLocalizer and IViewLocalizer, you can easily manage text and date formats across multiple cultures.

    And with Lokalise, the process becomes even smoother, allowing you to efficiently handle translations and expand your app’s reach. Whether you’re managing simple strings or complex pluralization, the combination of ASP.NET Core and Lokalise provides a powerful and flexible solution for all your internationalization needs.

    Related articles
    Stop wasting time with manual localization tasks. 

    Launch global products days from now.