18
Creating a json resource reader for dotnet core
In this post, we're going to create a custom Resource reader to use with dotnet core libraries. In the end, we can have a project dedicated to resources.
First, we need a class that represents our json
file:
using System.Collections.Generic;
namespace I18N
{
internal class JsonLocalization
{
public string Key { get; set; }
public Dictionary<string, string> LocalizedValues { get; set; }
}
}
The Key
is an unique identifier for the localization and LocalizedValues
is a dictionary, which its key is the language and the value the text that must be displayed.
We also going to create a new type of Exception
, to easily pinpoint what went wrong with the application.
using System;
namespace I18N
{
public class I18NException : Exception
{
public I18NException(string message) : base(message)
{
}
public I18NException(string message, Exception innerException) : base(message, innerException)
{
}
public I18NException()
{
}
}
}
Here is where the magic happens, the JsonLocalizer
class will read our json resources files
, store them in memory and make them available to our application.
In our constructor we expect two parameters, useBase
and additionalPaths
.
If useBase
is set to true, the localizer will load *.json
files that are in the Resources
folder.
additionalPaths
uses a type as a key, the localizer will use this type to find the assembly path and read the *.json
files in the Resources
folder.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
namespace I18N
{
public class JsonLocalizer
{
private readonly Dictionary<string, JsonLocalization[]> _localization
= new Dictionary<string, JsonLocalization[]>();
public JsonLocalizer(bool useBase = true, Dictionary<Type, string> additionalPaths = null)
{
if (useBase)
PopulateLocalization("Resources");
if (additionalPaths == null) return;
foreach (var additional in additionalPaths)
{
var codeBase = additional.Key.Assembly.CodeBase;
var uri = new UriBuilder(codeBase);
var data = Uri.UnescapeDataString(uri.Path);
var path = Path.GetDirectoryName(data);
var fullPath = Path.Combine(path, additional.Value);
PopulateLocalization(fullPath);
}
}
/// <summary>
/// resource:key:culture
/// resource is the resource name
/// key is the key you're looking for
/// culture is optional
/// </summary>
/// <param name="key"></param>
public string this[string key] => GetString(key);
private void PopulateLocalization(string path)
{
foreach (var resource in Directory.GetFiles(path, "*.json", SearchOption.AllDirectories))
{
try
{
var fileInfo = new FileInfo(resource);
var fileName = fileInfo.Name.Substring(0, fileInfo.Name.IndexOf('.'));
var loc = JsonConvert.DeserializeObject<JsonLocalization[]>(File.ReadAllText(resource));
_localization.Add(fileName, loc);
}
catch (ArgumentException e)
{
throw new I18NException($"Resource {resource} was already added, check your files.", e);
}
catch (Exception ex)
{
throw new I18NException("Something wrong is not right, check inner exception", ex);
}
}
}
private string GetString(string query)
{
try
{
string culture = null;
var split = query.Split(':');
var resource = split[0];
var key = split[1];
if (split.Length > 2)
culture = split[2];
culture = culture ?? CultureInfo.CurrentCulture.Name;
return _localization
.Single(l => l.Key == resource)
.Value.Single(x => x.Key == key)
.LocalizedValues[culture];
}
catch (Exception ex)
{
throw new I18NException($"Couldn't find key: {query}", ex);
}
}
}
}
In dotnet core
applications, you can add the JsonLocalizer
using the IServiceCollection
in the ConfigureServices
method.
// use it in DI as a singleton
public void ConfigureServices(IServiceCollection services)
{
// Other configurations ...
services.AddSingleton<JsonLocalizer>();
}
To handle additionalPaths
var additional = new Dictionary<Type, string>
{
{ typeof(MyClass), "My Resource Folder" },
{ typeof(MyAnotherClass), "My Resource Folder/Even Handles sub folders" }
};
var withExternalSources = new JsonLocalizer(additionalPaths: additional);
Now that we have everything set up, we can start using our localizer:
private readonly JsonLocalizer _localizer;
public class MySampleClass(JsonLocalizer localizer)
{
_localizer = localizer;
}
public string GetLocalizedMessage()
{
return _localizer["MyAppResource:MyKey"];
}
The Localizer will find your text by:
FileName:Key:Language
Here are some examples of how to write your resource files:
File Name | Resource Name |
---|---|
MyResource.json | MyResource |
MyApp.Resource.json | MyApp |
MyApp-Errors.Resource.json | MyApp-Errors |
MyApp.Errors.Resource.json | MyApp |
The Key
is the key inside the resource file, and the Language
is the culture, if not informed, will use the CultureInfo.CurrentCulture
value.
The json
resource file should follow this format:
[
{
"Key":"Name",
"LocalizedValues":{
"en-US":"Name",
"pt-BR":"Nome"
}
},
{
"Key":"Age",
"LocalizedValues":{
"en-US":"Age",
"pt-BR":"Idade"
}
}
]
18