Image for post
Image for post

JSON-based Blazor localization

Xavier Solau
Feb 8 · 5 min read

Now that we have seen in the previous article how to localize our component, we are going to customize it to use JSON files instead of RESX files.

You can find the Github source repository this article series is based here.

The Blazor article series

The hypothesis

Let’s say that our localization resources are going to be stored in JSON files and that those files are going to be embedded in the component assembly.

Note that we could store the resource files as static http files that our application would load on demand but let’s keep things as simple as possible for the article. In addition it has the advantage to be the same implementation for both Blazor Web Assembly and Blazor server side.

The files are going to be named as follow:

Finally we are going to use a custom JsonStringLocalizerFactory class and a JsonStringLocalizer class that will be able to read our JSON files.

Actually, you can use existing Nugets (JsonStringLocalizer, EFStringLocalizer or SqlLocalizer) that will probably do the work but the purpose of this article is also to show how it works inside.

Setting the JSON files

As in the previous article we will put the localization files in the Resources folder of the component project but instead of using RESX files we write JSON files.

Image for post
Image for post

With the JSON content for English:

{
"Body": "This Blazor component is defined in the <strong>{0}</strong> package and it is used from <strong id=\"{1}\">{2}</strong>.",
"ClickMe": "Click me!",
"EnterSomeText": "Enter some text:"
}

And for the French:

{
"Body": "Ce composant Blazor est défini dans le package <strong>{0}</strong> et il est utilisé par <strong id=\"{1}\">{2}</strong>.",
"ClickMe": "Cliquez ici !",
"EnterSomeText": "Saisissez un texte :"
}

Now that we have our JSON resources, we need to setup the project in order to embed the files in the assembly.

We have got two options:

Image for post
Image for post
  <ItemGroup>
<EmbeddedResource Include="Resources\Component1-fr.json" />
<EmbeddedResource Include="Resources\Component1.json" />
</ItemGroup>

JsonStringLocalizerFactory implementation

The project JsonLocalizer

Now that our localization resources are ready to use, we can just write our custom JSON string localizer and its factory.

Let’s create a dedicated project “JsonLocalizer” in the solution as a class library.

You can target .Net 5 or .Net Standard depending of the projects where you want to use the library. It is just good to know that if you target .Net Standard, you will be able to reuse your package in a larger range of projects.

We also need to add some Nuget package references:

The JsonStringLocalizer Factory

Now we need a factory that will be able to create our JsonStringLocalizer objects:

Let’s create a class JsonStringLocalizerFactory that implements the IStringLocalizerFactory interface from Microsoft.Extensions.Localization.

We will reuse the localization options to get the resource path so we need to create the constructor with IOptions<LocalizationOptions>:

private string ResourcesPath { get; }public JsonStringLocalizerFactory(
IOptions<LocalizationOptions> options)
{
ResourcesPath = options.Value.ResourcesPath;
}

Then we need to create our JsonStringLocalizer in the Create factory method with the EmbeddedFileProvider:

public IStringLocalizer Create(Type resourceSource)
{
var resources = new EmbeddedFileProvider(
resourceSource.Assembly);
return new JsonStringLocalizer(
resources,
ResourcesPath,
resourceSource.Name);
}

Finally the JsonStringLocalizer

The final step here is to create our JsonStringLocalizer class that will actually load the right JSON file and that will return the associated localized text for a given key.

The class must implement the interface IStringLocalizer from Microsoft.Extensions.Localization.

Basically there are two methods that we need to implement:

Both are going to look the same except that the second one is going to use string.Format in order to handle the arguments.

public LocalizedString this[string name]
{
get
{
var stringMap = LoadStringMap();
return new LocalizedString(name, stringMap[name]);
}
}
public LocalizedString this[string name, params object[] arguments]
{
get
{
var stringMap = LoadStringMap();
return new LocalizedString(
name,
string.Format(stringMap[name], arguments));
}
}

As you can see there is one last piece of code that we need to write to load the string map from the JSON file matching the current culture.

To get the culture there is the static property from CultureInfo: CurrentUICulture.

var cultureInfo = CultureInfo.CurrentUICulture;

Then we can use the file provider to find the resource:

var cultureName = cultureInfo.TwoLetterISOLanguageName;var fileInfo = FileProvider.GetFileInfo(
Path.Combine(ResourcesPath, $"{Name}-{cultureName}.json"));
if (!fileInfo.Exists)
{
fileInfo = FileProvider.GetFileInfo(
Path.Combine(ResourcesPath, $"{Name}.json"));
}

Once we have the file, we can just read it as a Dictionary.

JsonSerializer
.DeserializeAsync<Dictionary<string, string>>(...);

Giving us something like this to read the file:

using var stream = fileInfo.CreateReadStream();return JsonSerializer
.DeserializeAsync<Dictionary<string, string>>(stream).Result;

Unfortunately the IStringLocalizer interface does not return Tasks. So we can not fully use the async await pattern.

Note that this is a basic approach and there are many ways to improve this implementation! The first thing to do could be to cache the loaded resources in order to avoid de full load at every single request! But this is an other subject…

How to use it

Well all is now ready to be used both in server side or in WebAssembly! We just need to add our JsonLocalizer project as a dependency and to register our JsonStringLocalizerFactory.

WebAssembly

Here is the setup for a WebAssembly application in the Program.cs file:

// Register localization as usual
builder.Services.AddLocalization(options =>
{
options.ResourcesPath = "Resources";
});
// And add out custom factory
builder.Services.AddSingleton
<IStringLocalizerFactory, JsonStringLocalizerFactory>();

Server side

The same for the server side in Startup.cs file:

// Register localization as usual
services.AddLocalization(options =>
{
options.ResourcesPath = "Resources";
});
// And add out custom factory
services.AddSingleton
<IStringLocalizerFactory, JsonStringLocalizerFactory>();

That’s all, we are ready to play with it! Here is the code for the JsonStringLocalizer:

Next coming soon: How to setup your Blazor component Layout.

YounitedTech

Le blog Tech de Younited, où l’on parle de développement…

Thanks to Etienne Delattre

Xavier Solau

Written by

YounitedTech

Le blog Tech de Younited, où l’on parle de développement, d’architecture, de microservices, de cloud, de data… Et de comment on s’organise pour faire tout ça. Ah, et on recrute aussi, on vous a dit ?

Xavier Solau

Written by

YounitedTech

Le blog Tech de Younited, où l’on parle de développement, d’architecture, de microservices, de cloud, de data… Et de comment on s’organise pour faire tout ça. Ah, et on recrute aussi, on vous a dit ?

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store