The Tech Platform

Apr 18, 20218 min

XLocalizer for Asp.Net Core

Asp.Net Core localization, powered by online translation, auto resource creating and more...
 

 
Introduction


 
Developing a multi cultural web application requires building the localization infrastructure that will process the request localization and localize views, error messages, …etc. On the other hand; every localized culture requires at least one resource file, filled with all localized key-value pairs.


 
Building the localization infrastructure and filling the resource files can take a lot of time and effort. XLocalizer has been developed from scratch to solve these two issues and free the developer from unnesseary workload.

What does XLocalizer offers?
 

Simple localization setup: First of all, it is built to help developers create localized web applications easily without wasting time on developing localization infrastructure.


 
Auto localization: The most attractive two features of XLocalizer are Auto resource creating and Online translation. So, any missed key will be translated and added automatically to the relevant resource file.
 

Support for multiple resource types: By default, Asp.Net Core uses “.resx” resource files for storing localized strings. XLocalizer breaks the barriers by offering built-in localization stores (XML, RESX, DB). Additionally, custom resource types for any other file or db format can be implemented.
 

Flexiblity: XLocalizer is using the standard localization interfaces IStringLocalizer and IHtmlLocalizer, so it is easy to switch from the default .net core localization system to XLocalizer or vice versa. And with the help of built-in resource exportes, all localization resources can be exported from any file/db type to “.resx” file type.
 

Customization: XLocalizer can be customized in every detail to:

  • Use custom resource types (e.g. mysql, json, csv, …etc).

  • Use custom resource exporters to export resources from any source to any source.

  • Use custom translation services for translating resources.

Centralization: One single place to easily customize all validation errors, model binding erros and identity errors in a simple way.

In this tutorial I will show how to use XLocalizer with XML resource files and online translation. To learn the setup of other resource types like resx, db or custom sources, kindly visit: https://DOCS.Ziyad.info.
 

Installation
 

  • A few nugets is required to get the best of XLocalizer, first I will mention all pacakges, then we will see the role of each one in the next steps.

// The main package
 
PM > Install-Package XLocalizer
 

 
// Online translation support
 
PM > Install-Package XLocalizer.Translate
 

 
// Translation service
 
PM > Install-Package XLocalizer.Translate.MyMemoryTranslate
 

 
// Use html tags to localize views
 
PM > Install-Package XLocalizer.TagHelpers
 

 
// Additional taghelper package for language dropdown
 
PM > Install-Package LazZiya.TagHelpers
 

  • Resources folder: Under the project root create a new folder named “LocalizationResources”, then inside it create a new empty class named “LocSource”. This class will be used to access the relevant resource files from the code.

// Dummy class for grouping and accessing resource files
 
public class LocSource { }

No need to create culture specific resource files, they will be created and filled automatically by XLocalizer.


 
Setup XLocalizer
 

A small tip to speedup coding; VS2019 can automatically insert missing namespaces (using …). Or you can press (Crtl + . ) to view a context menu that will add the missing namespace.

  • Open startup file and configure request localization options as usual:

services.Configure<RequestLocalizationOptions>(ops =>
 
{
 
var cultures = new CultureInfo[] {
 
new CultureInfo("en"),
 
new CultureInfo("tr"),
 
... };
 

 
ops.SupportedCultres = cultures;
 
ops.SupportedUICultures = cultures;
 
ops.DefaultRequestCulture = new RequestCulture("en");
 

 
// Optional: add custom provider to support localization
 
// based on route value
 
ops.RequestCultureProviders.Insert(0, new RouteSegmentRequestCultureProvider(cultures));
 

 
});

XLocalizer supports multiple resource types such as XML, RESX, DB, …etc. In this sample I will use XML files to store localized values, so we need to register the built-inXmlResourceProvider, this provider will help us to use XML files as resource files for storing localized key-value pairs.
 
services.AddSingleton<IXResourceProvider, XmlResourceProvider>();
 

  • One of the major benefits of XLocalizer is online translation support, so we need to register at least one translation service in startup file.

services.AddHttpClient<ITranslator, MyMemoryTranslateService>();


 
I used MyMemoryTranslateService during the developmet of XLocalizer, but you are free to choose any of the available translation services or even implement your own one.

  • Optionally configure razor pages to use route based localization provider, so we can have the url like: http://localhost:111/en/Index. Then configure XLocalizer in the same step:

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";
 
});

  • Configure the app to use localization middleware

app.UseRequestLocalization();


 
Adding API Key for the translation Service


 
MyMemory translate API’s offers free anonymous usage till 1000 words/day (for the time of writing this story). So basicly you don’t have to add any key to test it. Anyhow, you can increase the free usage till 30.000 words/day just by providing an email and a freely generated key. See MyMemory API usage limits for more details.


 
Use MyMemory API Keygen to get a key, then add the key with a valid email address to user secrets file as below:

{
 
"XLocalizer.Translate": {
 
"MyMemory": {
 
"Email": "...",
 
"Key": "..."
 
}
 
}
 
}


 
Different translation services may require different setup. See translation services docs for details about setup of different translation services.


 
Complete XLocalizer Startup Code
 

Sample startup file with unnecessary code ommitted for simplification:
 

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

 
// ...
 

 
public void ConfigureServices(IServiceCollection services)
 
{
 
// Configure request localization
 
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");
 
ops.RequestCultureProviders.Insert(0,
 
new RouteSegmentRequestCultureProvider(cultures));
 
});
 

 
// Regisgter traslation service
 
services.AddHttpClient<ITranslator, MyMemoryTranslateService>();
 

 
// Register XmlResourceProvider
 
services.AddSingleton<IXResourceProvider, XmlResourceProvider>();
 
services.AddRazorPages()
 
.AddRazorPagesOptions(ops=>
 
{ ops.Conventions.Insert(0,
 
newRouteTemplateModelConventionRazorPages());
 
})
 
// Add XLocalizer
 
.AddXLocalizer<LocSource, MyMemoryTranslateService>(ops=>
 
{
 
ops.ResourcesPath="LocalizationResources";
 
ops.AutoAddKeys=true;
 
ops.AutoTranslate=true;
 

 
// Optional: Just in case you need to change the source
 
translation culture.
 
// if not provided, the default culture will be used
 
ops.TranslateFromCulture="en";
 

 
// Recommended: turn on caching during production for faster localization
 
ops.UseExpressMemoryCache=true;
 
});
 
}
 

 
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
 
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
 
{
 
// ...
 
// Use request localization middleware
 
app.UseRequestLocalization();
 

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

Sample startup file for XLocalizer setup based on Xml resource files and online translation

That is all the setup required in startup file. Next, we will configure views and backend localization.
 

Localizing Views


 
We have installed a handy nuget for localizing views XLocalizer.TagHelpers, this package makes it easy to localize views using html tag and html attributes, which keeps the html code clean and easy to read and maintain.

  • Add the taghelper in _ViewImports.cshtml file:

@addTagHelper *, XLocalizer.TagHelpers

  • Use localize-content attribute inside html tags to localize inner text/html:

<h1 localize-content>Welcome</h1>

  • Localize inner text/html paragraph with localize html tag:

<localize>
 
<h1>Welcome</h1>
 
<p>My contents...</p>
 
</localize>

  • Localize html string with arguments:

@{
 
var args = new object[] { "http://DOCS.Ziyad.info" }
 
}
 

 
<p localize-args="args">
 
Visit <a href="{0}">DOCS</a> for more details.
 
</p>

  • Localize html attributes like title:

<img src="../picture.jpg" localize-att-title="Nature picture" />


 
Below is the fully localized sample of Register.cshtml page, notice that we only need to add “localize-content” attribute to the relevant tag, that keeps the page code clean and makes it easy to read and update.

@page
 
@model RegisterModel
 
@{
 
ViewData["Title"] ="Register";
 
}
 

 
<h1 localize-content>@ViewData["Title"]</h1>
 

 
<div class="row">
 
<div class="col-md-4">
 
<form asp-route-returnUrl="@Model.ReturnUrl"method="post">
 
<h4 localize-content>Create a new account.</h4>
 
<hr />
 
<div asp-validation-summary="All"class="text-danger"></div>
 
<div class="form-group">
 
<label asp-for="Input.Email"></label>
 
<input asp-for="Input.Email"class="form-control" />
 
<span asp-validation-for="Input.Email"class="text-danger">
 
</span>
 
</div>
 
<div class="form-group">
 
<label asp-for="Input.Password"></label>
 
<input asp-for="Input.Password"class="form-control" />
 
<span asp-validation-for="Input.Password"class="text-danger">
 
</span>
 
</div>
 
<div class="form-group">
 
<label asp-for="Input.ConfirmPassword"></label>
 
<input asp-for="Input.ConfirmPassword"class="form-control" />
 
<span asp-validation-for="Input.ConfirmPassword"class="text-danger"></span> </div>
 
<button type="submit"class="btn btn-primary"localize-content>Register</button>
 
</form>
 
</div>
 
</div>
 
@section Scripts {
 
<partial name="_ValidationScriptsPartial" />
 
}

View localization sample with XLocalizer.TagHelpers

Localizing Validation Attrbiutes Errors, Model Binding Errors and Identity Error Messages


 
Localizing all framework error messages do not require any additional setup with XLocalizer, and it is not necessary to provide any error message inside the attribute tags! All error messages will be assigned and localized by the default setup of XLocalizer in startup.


 
Below is a sample use for some validation attributes.

[Required]
 
[EmailAddress]
 
[Display(Name = "Email")]
 
public string Email { get; set; }

Also for model binding errors and identity errors, we don’t have to do any additional setup, XLocalizer will take care of localizing all the error messages by default.


 
Below is a sample of backend localization of Register.cshtml.cs file:

public class InputModel
 
{
 
[Required]
 
[EmailAddress]
 
[Display(Name="Email")]
 
public stringEmail { get; set; }
 

 
[Required]
 
[StringLength(100, MinimumLength=6)]
 
[DataType(DataType.Password)]
 
[Display(Name="Password")]
 
public stringPassword { get; set; }
 

 
[DataType(DataType.Password)]
 
[Display(Name="Confirm password")]
 
[Compare("Password")]
 
public stringConfirmPassword { get; set; }
 
}

Validation attributes usage without defining error messages

Customizing Error Messages
 

In some cases you may need to customize the error messages for validation attributes, model binding or identity. Or you may want to provide the default error messages in a culture other than “en”, so XLocalizer can do translate from the correct culture.


 
The first solution is to use inline options setup in startup file, by providing the relevant error messages as below:

services.AddRazorPages()
 
.AddXLocalizer<...>(ops=>
 
{
 
// ...ops.ValidationErrors = new ValidationErrors
 
{
 
RequiredAttribute_ValidationError="The {0} field is
 
required.",
 
CompareAttribute_MustMatch="'{0}' and '{1}' do not match.",
 
StringLengthAttribute_ValidationError="The field {0} must
 
be a string with a maximum length of {1}.",
 
// ...
 
};
 
ops.ModelBindingErrors = new ModelBindingErrors
 
{
 
AttemptedValueIsInvalidAccessor="The value '{0}' is not
 
valid for {1}.",
 
MissingBindRequiredValueAccessor="A value for the '{0}'
 
parameter or property was not provided.",
 
MissingKeyOrValueAccessor="A value is required.",
 
// ...
 
};
 
ops.IdentityErrors = new IdentityErrors
 
{
 
DuplicateEmail="Email '{0}' is already taken.",
 
DuplicateUserName="User name '{0}' is already
 
taken.",
 
InvalidEmail="Email '{0}' is invalid.",
 
// ...
 
};
 
});

Customizing error messages in startup file

The other option is to configure all XLocalizer settings in a json file.


 
JSON Settings
 

If you are a developer who likes to keep startup file clean “like me :)” you will be happy to know that you can do all these customizations in a json file and use only one line to read the configurations in startup.

  • Add the relevant configuration to appsettings.json or any custom json file of your choice.

{
 
"XLocalizerOptions" : {
 
"AutoAddKeys" : true,
 
"AutoTranslate" : true,
 
// ... }
 
}

  • Setup XLocalizer to read the relevant configuration section:

services.AddRaqzorPages()
 
.AddXLocalizer<...>(ops => Configuration.GetSection("XLocalizerOptions").Bind(ops));

  • Below is a sample json settings for XLocalizer options with the customizable error messages:

{
 
"XLocalizerOptions": {
 
"ResourcesPath": "LocalizationResources",
 
"AutoAddKeys": true,
 
"AutoTranslate": true,
 
"UseExpressMemoryCache": true,
 
"TranslateFromCulture": "en",
 
"ValidationErrors": {
 
"CompareAttribute_MustMatch": "'{0}' and '{1}' do not
 
match. They should not be different!",
 
"CreditCardAttribute_Invalid": "The {0} field is not a
 
valid credit card number.",
 
"CustomValidationAttribute_ValidationError": "{0} is not
 
valid.",
 
"...": "..."
 
},
 
"IdentityErrors": {
 
"DuplicateEmail": "Email '{0}' is already taken.",
 
"DuplicateUserName": "User name '{0}' is already taken.
 
Please try another one.",
 
"InvalidEmail": "Email '{0}' is invalid.",
 
"...": "..."
 
},
 
"ModelBindingErrors": {
 
"AttemptedValueIsInvalidAccessor": "The value '{0}' is
 
not valid for {1}.",
 
"MissingBindRequiredValueAccessor": "A value for the
 
'{0}' parameter or property was not provided.",
 
"MissingKeyOrValueAccessor": "A value is required.",
 
"...": "..."
 
}
 
}
 
}

See full settings here

So, this is a single place and simple way to customize all error messages. These messages will be translated to other cultuers by XLocalizer depending on the request culture.
 

Adding Language Navigation
 

Each multi cultural web application must provide a way to switch between different languages. You may have your own implementation for language navigation, but just in case you need to add one easily (we’ve installed LazZiya.TagHelpers earlier);

  • Add taghelpers to _ViewImports file

@addTagHelper *, LazZiya.TagHelpers

  • Open _layout.cshtml and add the language navigation wher you need it to appear:

<language-nav></language-nav>


 
I highly recommed to setup the language navigation to configure culture cookie as described in the docs page here. So the culture choice can be stored in a cookie for later use.


 
Run The Application
 

If you have done all the steps correctly, and once you start the application take a look at the ouput window in VS to see the logs, you will see that XLocalizer has started to translate the views and insert values automatically. Additionally, all validation attributes, model binding and identity error also localized.

Sample screenshot of localized registration form

Notice: Localizing identity pages requires scaffolding the identity into the project.


 
All you need to add a new culture is; add the culture to the supported cultures in startup file, and keep all the rest to be done by XLocalizer :)

Source: Medium

The Tech Platform

www.thetechplatform.com

    0