json

В данном посте рассмотрено создание настраиваемой программы для чтения ресурсов из json файла под Net Core. По итогу, у нас будет библиотека локализации ресурсов программы, с помощью которой мы сможем добавить в любое свое программное обеспечение поддержку нескольких языков. Давайте приступим.

Во-первых, нам нужен класс, описывающий наш json-файл:

using System.Collections.Generic;

namespace I18N
{
    internal class JsonLocalization
    {
        public string Key { get; set; }
        public Dictionary<string, string> LocalizedValues { get; set; }
    }
}

Key — это уникальный идентификатор для локализации, а LocalizedValues — это словарь, ключ которого — язык, а значение — текст, который должен отображаться.

Мы также собираемся создать новый тип Exception, чтобы легко определить, что пошло не так с приложением.

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()
        {
        }
    }
}

Здесь происходит магия: класс JsonLocalizer будет читать наши файлы ресурсов json, сохранять их в памяти и делать доступными для нашего приложения.

В конструктор должны быть переданы два параметра: useBase и additionalPaths.

Если для useBase установлено значение true, локализатор загрузит файлы * .json, находящиеся в папке Resources.
AdditionalPaths использует тип в качестве ключа, локализатор будет использовать этот тип для поиска пути сборки и чтения файлов * .json в папке Resources.


Skip to content
Log in
Create account
Creating a json resource reader for dotnet core
#csharp
15 июл. ・3 min read

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"
      }
   }
]

Discussion (0)
pic
Code of Conduct • Report abuse
Read next
mr_eking profile image
Advanced Blazor State Management Using Fluxor, part 6 - Persisting State

Eric King - Mar 14
jessicanathany profile image
Creating a simple project Web API with VSCode and Entity Framework

Jéssica Nathany - Mar 13
techwithpat profile image
Call an existing Web API with a C# application (VIDEO)

Patrick Tshibanda - Feb 21
isaacojeda profile image
C#: Llamadas confiables a APIs con Polly y RestEase

Isaac Ojeda - Feb 8
Eduardo Julião
Software Developer | Geek since 1993 | @monsieurxuxu on twitter
 

    Work
    Software Engineer
    Joined
    3 июл. 2021 г.

More from Eduardo Julião
Event Stream - Server Side
#csharp
Dependency Inversion Principle
#beginners #csharp
Interface Segregation Principle
#beginners #csharp

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

В приложениях dotnet вы можете добавить JsonLocalizer с помощью IServiceCollection в методе ConfigureServices.

// use it in DI as a singleton
public void ConfigureServices(IServiceCollection services)
{
   // Other configurations ...
   services.AddSingleton<JsonLocalizer>();
}

Для обработки дополнительных путей

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

Теперь, когда у нас все настроено, мы можем начать использовать наш локализатор:

private readonly JsonLocalizer _localizer;

public class MySampleClass(JsonLocalizer localizer)
{
   _localizer = localizer;
}

public string GetLocalizedMessage()
{
   return _localizer["MyAppResource:MyKey"];
}

Локализатор найдет ваш текст по такой структуре:

FileName:Key:Language

Вот несколько примеров того, как писать файлы ресурсов:

Снимок

Ключ — это ключ внутри файла ресурсов, а язык — это культура, если не сообщить, будет использоваться значение CultureInfo.CurrentCulture.

Файл ресурсов json должен иметь следующий формат:

[
   {
      "Key":"Name",
      "LocalizedValues":{
         "en-US":"Name",
         "pt-BR":"Nome"
      }
   },
   {
      "Key":"Age",
      "LocalizedValues":{
         "en-US":"Age",
         "pt-BR":"Idade"
      }
   }
]