JSON-based Blazor localization

Xavier Solau
YounitedTech
5 min readFeb 8, 2021

--

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 on here.

The Blazor article series

Related article demonstrating the use of the BlazorJsonLocalizer project that implements this Json-based Blazor localization.

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:

  • {ComponentName}.json: the English localization that will also be used as fallback.
  • {ComponentName}-{ISO2LanguageCode}.json: the localization for the given language.

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.

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:

  • With Visual Studio when your file is selected in the solution explorer you can setup the “Build Action” property to “Embedded resource”.
  • Or directly in the CSPROJ file:
  <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:

  • Microsoft.Extensions.Localization because we are actually extending some of its features.
  • System.Text.Json because we are going to read JSON files.
  • Microsoft.Extensions.FileProviders.Embedded because this package is really handy to read Assembly Embedded resources.

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:

  • LocalizedString this[string name]
  • LocalizedString this[string name, params object[] arguments]

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.

--

--