Click here to Skip to main content
14,385,566 members

Localization Extensibility in ASP.NET Core 1.0

Rate this:
4.69 (7 votes)
Please Sign up or sign in to vote.
4.69 (7 votes)
28 Feb 2016CPOL
Extending Localization in ASP.NET Core 1.0

Introduction

As we know, Globalization is the process of designing and developing applications that function for multiple cultures, and Localization is the process of customizing your application for a given culture and locale.

In this article, we will dig into the extensible points that localization in ASP.NET Core 1.0 offers for the developers. As much as I know about the localization in the new version of ASP.NET, there are two main extensible points that I will describe underneath, localization Culture Providers & Localization Resources.

Background

Localization, and ASP.NET Core 1.0 knowledge required.

Using the Code

1. Localization Culture Providers

ASP.NET Core 1.0 came up with five providers that can determine the current culture of the web application:

  • AcceptLanguageHeaderRequestCultureProvider
  • CookieRequestCultureProvider
  • CustomRequestCultureProvider
  • QueryStringRequestCultureProvider

So, it can determine the culture either from cookies, http header or query string. And if we have a look to the source code in the localization repository, we will notice that all the providers inherit from RequestCultureProvider. The developer can easily use this extensible point to create a new culture provider that retrieves the culture from custom source by inheriting from the previous class.

In this article, I'm going to create a new culture provider called ConfigurationRequestCultureProvider which retrieves the culture from configuration file (JSON) in our case. As I mentioned before, we need to inherit from RequestCultureProvider and we will get the culture from the configuration file using the configuration APIs as the following:

public class ConfigurationRequestCultureProvider : RequestCultureProvider
{
    public override Task<ProviderCultureResult> 
    DetermineProviderCultureResult(HttpContext httpContext)
    {
        if (httpContext == null)
        {
            throw new ArgumentNullException(nameof(httpContext));
        }

        var builder = new ConfigurationBuilder();
        builder.AddJsonFile("config.json");

        var config = builder.Build();
        string culture = config["culture"];
        string uiCulture = config["uiCulture"];

        culture = culture ?? "en-US";
        uiCulture = uiCulture ?? culture;

        return Task.FromResult(new ProviderCultureResult(culture, uiCulture));
    }
}

As we saw before, we get cuture information using culture & uiCulture keys which are defined in the json configuration file below:

{
  "culture": "ar-SA"
  "uiCulture": "ar-SA"
}

After that, we need to add the new provider into RequestCultureProviders property which is available in RequestLocalizationOptions class.

public void Configure(IApplicationBuilder app, IStringLocalizer<startup> localizer)
{
    var supportedCultures = new List<cultureinfo>
    {
        new CultureInfo("en-US"),
        new CultureInfo("ar-SA")
    };

    var options = new RequestLocalizationOptions()
    {
        DefaultRequestCulture = new RequestCulture("en-US"),
        SupportedCultures = supportedCultures,
        SupportedUICultures = supportedCultures
    };

    options.RequestCultureProviders.Insert(0, new JsonRequestCultureProvider());
    app.UseRequestLocalization(options);
    ...
}    

You can find the source of the above sample on the ASP.NET Entropy repository.

2. Localization Resources

The second point that the ASP.NET developer may use to extend the localization is specifying the localization entries aka Resource, which a key/value pair, that contains all the required entries with their translation for a specific culture. ASP.NETCore 1.0 out-of-the-box uses the old source .resx files to store the culture specific entries, but give you the ability to switch into your custom storage such as XML, JSON, .. etc., to retrieve the localization entries.

In this article, we will use a memory storage using EntityFramework, to store the localization entries. I will not dive into much details about EF, so we will start building our needed models Culture and Resource.

public class Culture
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual List<Resource> Resources { get; set; }
}
public class Resource
{
    public int Id { get; set; }
    public string Key { get; set; }
    public string Value { get; set; }
    public virtual Culture Culture { get; set; }
}

After that, we define the DataContext:

public class LocalizationDbContext : DbContext
{
    public DbSet<culture> Cultures { get; set; }
    public DbSet<resource> Resources { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseInMemoryDatabase();
    }
}

At this point, finish defining the required objects for the data store, now we will start to use the extensible point by implementing the following interfaces IStringLocalizer, <span dir="ltr">IStringLocalizer<T></span>,<span dir="ltr"> </span>IStringLocalizerFactory.

public class EFStringLocalizer : IStringLocalizer
{
    private readonly LocalizationDbContext _db;

    public EFStringLocalizer(LocalizationDbContext db)
    {
        _db = db;
    }

    public LocalizedString this[string name]
    {
        get
        {
            var value = GetString(name);
            return new LocalizedString(name, value ?? name, resourceNotFound: value == null);
        }
    }

    public LocalizedString this[string name, params object[] arguments]
    {
        get
        {
            var format = GetString(name);
            var value = string.Format(format ?? name, arguments);
            return new LocalizedString(name, value, resourceNotFound: format == null);
        }
    }

    public IStringLocalizer WithCulture(CultureInfo culture)
    {
        CultureInfo.DefaultThreadCurrentCulture = culture;
        return new EFStringLocalizer(_db);
    }

    public IEnumerable<LocalizedString> GetAllStrings(bool includeAncestorCultures)
    {
        return _db.Resources
            .Include(r => r.Culture)
            .Where(r => r.Culture.Name == CultureInfo.CurrentCulture.Name)
            .Select(r => new LocalizedString(r.Key, r.Value, true));
    }

    private string GetString(string name)
    {
        return _db.Resources
            .Include(r => r.Culture)
            .Where(r => r.Culture.Name == CultureInfo.CurrentCulture.Name)
            .FirstOrDefault(r => r.Key == name)?.Value;
    }
}

As we saw from the previous code, all the needed functions are implemented to fetch the localization values from the memory using an object of LocalizationDbContext class that we defined previously.

In the same way, we can implement the generic version of the IStringLocalizer.

public class EFStringLocalizer<T> : IStringLocalizer<T>
{
    private readonly LocalizationDbContext _db;

    public EFStringLocalizer(LocalizationDbContext db)
    {
        _db = db;
    }

    public LocalizedString this[string name]
    {
        get
        {
            var value = GetString(name);
            return new LocalizedString(name, value ?? name, resourceNotFound: value == null);
        }
    }

    public LocalizedString this[string name, params object[] arguments]
    {
        get
        {
            var format = GetString(name);
            var value = string.Format(format ?? name, arguments);
            return new LocalizedString(name, value, resourceNotFound: format == null);
        }
    }

    public IStringLocalizer WithCulture(CultureInfo culture)
    {
        CultureInfo.DefaultThreadCurrentCulture = culture;
        return new EFStringLocalizer(_db);
    }

    public IEnumerable<LocalizedString> GetAllStrings(bool includeAncestorCultures)
    {
        return _db.Resources
            .Include(r => r.Culture)
            .Where(r => r.Culture.Name == CultureInfo.CurrentCulture.Name)
            .Select(r => new LocalizedString(r.Key, r.Value, true));
    }

    private string GetString(string name)
    {
        return _db.Resources
            .Include(r => r.Culture)
            .Where(r => r.Culture.Name == CultureInfo.CurrentCulture.Name)
            .FirstOrDefault(r => r.Key == name)?.Value;
    }
}

The IStringLocalizerFactory interface is responsible to create an object of IStringLocalizer.

public class EFStringLocalizerFactory : IStringLocalizerFactory
{
    private readonly LocalizationDBContext _db;

    public EFStringLocalizerFactory()
    {
        _db = new LocalizationDBContext();
        _db.AddRange(
            new Culture
            {
                Name = "en-US",
                Resources = new List<Resource>() 
                { new Resource { Key = "Hello", Value = "Hello" } }
            },
            new Culture
            {
                Name = "fr-FR",
                Resources = new List<Resource>() 
                { new Resource { Key = "Hello", Value = "Bonjour" } }
            },
            new Culture
            {
                Name = "es-ES",
                Resources = new List<Resource>() 
                { new Resource { Key = "Hello", Value = "Hola" } }
            },
            new Culture
            {
                Name = "jp-JP",
                Resources = new List<Resource>() 
                { new Resource { Key = "Hello", Value = "?????" } }
            },
            new Culture
            {
                Name = "zh",
                Resources = new List<Resource>() 
                { new Resource { Key = "Hello", Value = "??" } }
            },
            new Culture
            {
                Name = "zh-CN",
                Resources = new List<Resource>() 
                { new Resource { Key = "Hello", Value = "??" } }
            }
        );
        _db.SaveChanges();
    }

    public IStringLocalizer Create(Type resourceSource)
    {
        return new EFStringLocalizer(_db);
    }

    public IStringLocalizer Create(string baseName, string location)
    {
        return new EFStringLocalizer(_db);
    }
}

Last but not least, instantiation of EFStringLocalizerFactory is required in the localization middleware, to let the localization use the customized localization resource.

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IStringLocalizerFactory, EFStringLocalizerFactory>();
}

Finally, using the created object via Dependency Injection.

public void Configure(IApplicationBuilder app, IStringLocalizerFactory localizerFactory)
{
    var localizer = localizerFactory.Create(null);
    ...
}

You can find the source of the above sample on the ASP.NET Entropy repository.

For more information about localization, you can have a look at the source code on the ASP.NET Localization repository.

Points of Interest

Working with localization APIs in ASP.NET Core 1.0 is quite cool, because the new ASP.NET world simplify the localization stuff in such a way that we have never seen before, it's more simple, clean and straightforward. I find myself enjoying when I wrote the code. :)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author


Comments and Discussions

 
QuestionEFStringLocalizerFactory DbContext Singleton Pin
TotzkePaul4-Apr-17 5:07
MemberTotzkePaul4-Apr-17 5:07 
QuestionDoes anyone even try this before voting? Pin
Syed Muhammad Fahad9-May-16 13:08
MemberSyed Muhammad Fahad9-May-16 13:08 
BugTypo in code Pin
VSG245-Apr-16 11:22
MemberVSG245-Apr-16 11:22 
QuestionAwesome article! How about data annotations? Pin
206mph25-Mar-16 12:09
Member206mph25-Mar-16 12:09 
GeneralMy Vote of 5 Pin
aarif moh shaikh28-Feb-16 18:35
professionalaarif moh shaikh28-Feb-16 18:35 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Article
Posted 28 Feb 2016

Tagged as

Stats

35.8K views
6 bookmarked