meta info

MVC/Razor Pages and XML Based Localization Setup

By Ziya Mollamahmut

Localization based on XML files works very similar to RESX process, we only use .xml files instead of .resx to store resources. But the ability to edit XML files at run time gives us the flexibility of automtically adding missing resources to XML resource files.

Table of contents

Install

Install nuget package:

PM > Install-Package XLocalizer
PM > Install-Package XLocalizer.Translate.MyMemoryTranslate

Notice: To install the latest preview add -Pre to the command line

Create resources folder

Let's start by creating a folder and a dummy class for our localized resources.

  • Create a new folder under the root of the project and name it LocalizationResources or anything else...
  • Create a new class inside LocalizationResources folder and name it LocSource or anything else...
  • Do not create resource files manually! they will be created automatically by XLocalizer.

The folder structure will be like below:

  • ProjectRoot/
    • LocalizationResources/
      • LocSource.cs

LocSource is empty class, it will be used only to refere to .resx resource files inside the code.

public class LocSource
{
}

Startup settings

  • Configure request localization options and optionally add RouteSegmentRequestCultureProvider :
// Add namespace for optional routing setup
using XLocalizer.Routing;

// Configure request localization options
services.Configure<RequestLocalizationOptions>(ops => 
{
    // Define supported cultures
    var cultures = new CultureInfo[] { new CultureInfo("en"), new CultureInfo("tr"), new CultureInfo("ar") };
    ops.SupportedCultures = cultures;
    ops.SupportedUICultures = cultures;
    ops.DefaultRequestCulture = new RequestCulture("en");

    // Optional: add custom provider to support localization based on {culture} route value
    ops.RequestCultureProviders.Insert(0, new RouteSegmentRequestCultureProvider(cultures));
});        
  • Register xml resource provider: By default XLocalizer works with .resx resource files. But to enable auto key adding we need to register XmlResourceProvider :
services.AddSingleton<IXResourceProvider, XmlResourceProvider>();

You can provide localization support based on any custom file type by implementing IXResourceProvider interface.

  • Register a translation service to enable auto translation option:
services.AddHttpClient<ITranslator, MyMemoryTranslateService>();

You can provide translation support based on any custom provider type by implementing ITranslator interface.

  • Add route based localization for razor pages:
services.AddRazorPages()
        .AddRazorPagesOptions(ops => { ops.Conventions.Insert(0, new RouteTemplateModelConventionRazorPages()); });

Or if you are using MVC:

services.AddMvc()
        .AddMvcOptions(ops => { ops.Conventions.Insert(0, new RouteTemplateModelConventionMvc()); });

and make sure that all controllers and actions have [Route("...")] attribute.

  • Add XLocalizer setup and enable AutoAddKeys and AutoTranslate options:
services.AddRazorPages()
        .AddRazorPagesOptions(ops => { ops.Conventions.Insert(0, new RouteTemplateModelConventionRazorPages()); });
        .AddXLocalizer<LocSource, MyMemoryTranslateService>(ops => 
        {
            ops.ResourcesPath = "LocalizationResources";
            ops.AutoAddKeys = true;
            ops.AutoTranslate = true;
            ops.TranslateFromCulture = "en"
        });

If TranslateFromCulture is not defined, the default request culture will be used as source culture for translation.

  • Configure the app to use request localization middleware:
app.UseRequestLocalization();

User secrets

MyMemory offers a free or paid subscriptions, and depending on your setup it may require an Api key to be provided, for more details see MyMemoryTranslateService.

Read more about translation services in Translation services.

Caching

XLocalizer has built-in caching enabled by default. Caching helps to speedup the retriving of localized resources. But, it is recommended to switch caching off during development to avoid caching values that are subject to change frequently.

ops.UseExpressMemoryCache = false;

Full startup code for XML

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using XLocalizer.Translate;
using XLocalizer;
using XLocalizer.Xml;
using System.Globalization;
using XLocalizer.Routing;
using XLocalizer.Translate.MyMemoryTranslate;
using XmlLocalizationSample.LocalizationResources;

namespace XmlLocalizationSample
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<RequestLocalizationOptions>(ops =>
            {
                var cultures = new CultureInfo[] { new CultureInfo("en"), new CultureInfo("tr"), new CultureInfo("ar") };
                ops.SupportedCultures = cultures;
                ops.SupportedUICultures = cultures;
                ops.DefaultRequestCulture = new Microsoft.AspNetCore.Localization.RequestCulture("en");

                // Optional: add custom provider to support localization based on route value {culture}
                ops.RequestCultureProviders.Insert(0, new RouteSegmentRequestCultureProvider(cultures));
            });
            
            // Optional: To enable online translation register one or more translation services
            services.AddHttpClient<ITranslator, MyMemoryTranslateService>();

            // Comment below line to switch to RESX based localization.
            services.AddSingleton<IXResourceProvider, XmlResourceProvider>();

            services.AddRazorPages()
                // Optional: Add {culture} route template to all razor pages routes e.g. /en/Index
                .AddRazorPagesOptions(ops => { ops.Conventions.Insert(0, new RouteTemplateModelConventionRazorPages()); })

                // Add XLocalization with a default resource <LocSource>
                // and specify a service for handling translation requests
                .AddXLocalizer<LocSource, MyMemoryTranslateService>(ops =>
                {
                    ops.ResourcesPath = "LocalizationResources";
                    ops.AutoAddKeys = true;
                    ops.AutoTranslate = true;
                });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseDatabaseErrorPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            // Use request localization middleware
            app.UseRequestLocalization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapRazorPages();
            });
        }
    }
}

Sample project

Download sample project from GitHub

Next: Localizing views