Создание средства чтения ресурсов json для ядра dotnet

  • Михаил
  • 12 мин. на прочтение
  • 123
  • 19 Dec 2020
  • 19 Dec 2020

В этом посте мы собираемся создать пользовательское средство чтения ресурсов для использования с основными библиотеками dotnet. В конце концов, у нас может быть проект, посвященный ресурсам.

Во-первых, нам нужен класс, представляющий наш 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 resources files, сохранять их в памяти и делать их доступными для нашего приложения.

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

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

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 coreприложениях вы можете добавить JsonLocalizerиспользование метода IServiceCollectionin ConfigureServices.
 

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

 

Обрабатывать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);

 

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

private readonly JsonLocalizer _localizer;

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

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

 

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

FileName:Key:Language

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

Имя файлаИмя ресурса
MyResource.jsonМой ресурс
MyApp.Resource.jsonМое приложение
MyApp-Errors.Resource.jsonMyApp-ошибки
MyApp.Errors.Resource.jsonМое приложение

Это Keyключ внутри файла ресурсов, а Languageкультура, если она не проинформирована, будет использовать это CultureInfo.CurrentCultureзначение.

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

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