.NET Core'da JSON ile i18n Resource okuyucusu

.NET Core kütüphanelerinde, yerelleştirme amacıyla kullanılmak üzere özel bir Resource okuyucu oluşturacağız.

Category Fundamentals

Published: 05 Ağustos 2021

Bu yazıda .NET Core kütüphaneleri ile kullanmak üzere özel bir Resource okuyucu oluşturacağız. 

İlk olarak, json dosyamızı temsil eden bir sınıfa ihtiyacımız var:

using System.Collections.Generic;

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

Key yerelleştirilecek kaynak için tanımlanacak özel bir isimdir. LocalizedValues dillere göre görüntülenmesi gereken metni içerecek.

Ayrıca Exception için yeni bir sınıf oluşturacağız, uygulamayla neyin yanlış gittiğini kolayca belirlemek için.

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

İşte sihrin gerçekleştiği yer, JsonLocalizer sınıfı. Bizim için json resource dosyalarını okuyacak, hafızada saklayacak ve uygulamamıza sunacak.

Bu sınıfın oluşturulması için iki parametre istiyoruz useBase ve additionalPaths.

useBase pozitif olarak gönderilirse, Resources klasörindeki *.json dosyaları yüklenecektir.
additionalPaths ise Resources klasörünün yolunu bulmak için kullanılır.

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, stringadditionalPaths = null)
        {
            if (useBase)
                PopulateLocalization("Resources");

            if (additionalPaths == nullreturn;
            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 uygulamalarında, JsonLocalizer sınıfını IServiceCollection ConfigureServices yöntemini kullanarak ekleyebilirsiniz.

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

additionalPaths için ise

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

Artık her şeyi ayarladık, yerelleştiricimizi kullanmaya başlayabiliriz:

private readonly JsonLocalizer _localizer;

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

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

Yerelleştirici metni şu şekilde bulacaktır:

DosyaIsmi:Anahtar:Dil

Kaynak dosyalarınızı yazmanıza ilişkin bazı örnekler şunlardır:

Dosya AdıKaynak Adı
MyResource.jsonMyResource
MyApp.Resource.jsonMyApp
MyApp-Errors.Resource.jsonMyApp-Errors
MyApp.Errors.Resource.jsonMyApp

Key kaynak dosyasının içindeki anahtardır ve Language dil için kullanılır, bu paremetre verilmezse varsayılan kültür olan CultureInfo.CurrentCulture kullanılır.

json kaynak dosyası şu biçimi izlemelidir:

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