نقاط التمدد والتطوير في التعريب في الـ ASP.NET 5

29 يناير 2016     469 مشاهدة    0 تعليق
تم النشر في #التعريب  #Localization 

تطرقنا في المقال السابق عن التعريب في الـ ASP.NET 5 وتعرفنا على الخطوات اللازمة لتعريب أي تطبيق. اليوم سوف نواصل موضوعنا ونتعرف على الطرق التي تمكن المطور من إستخدام نقاط التمدد Extensibility والتي يمكن من خلالها التعامل مع أمور متقدمة في التعريب.

سأتحدث اليوم عن أهم نقطتين -حسب معرفتي- يمكن للمطورين من خلالها إستخدامهما للتطوير في التعريب ألا وهما:

1- مزودات التعريب

أتت الـ ASP.NET 5 بخمس مزودات يمكن من خلالها تحديد اللغه وهي:

  • AcceptLanguageHeaderRequestCultureProvider
  • CookieRequestCultureProvider
  • CustomRequestCultureProvider
  • QueryStringRequestCultureProvider

والتي يمكن من خلالها التعرف على اللغة من ملفات الكوكيز أو الـ Http Header أو الـ QueryString, ولو رجعنا إلى المصدر فسنلاحظ أن كل المزودات ترث من الفئة RequestCultureProvider, لذا يمكن لأي مطور من إنشاء مزود جديد للغة وذلك بالوراثه من تلك الفئة, وهذه هي أول نقطة تطوير في الـ Localization.

ففي مقالنا هذا سأقوم بإنشاء مزود جديد للغة ولنطلق عليه ConfigurationRequestCultureProvider والذي سيمكننا من تحديد اللغة من أي ملف إعدادات, طبعاً فمثالنا سنستخدم ملف JSON. ما علينا الأ الوراثه من الفئة RequestCultureProvider مثل ماذكرت سابقاً من ثم نقوم بجلب اللغة من ملف الإعدادات وذلك بإستخدام الـ Configuration APIs على النحو التالي

public class ConfigurationRequestCultureProvider : RequestCultureProvider
{
public override Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException(nameof(httpContext));
}

var builder = new ConfigurationBuilder();
builder.AddJsonFile("config.json");

var config = builder.Build();
string culture = config["culture"];
string uiCulture = config["uiCulture"];

culture = culture ?? "en-US";
uiCulture = uiCulture ?? culture;

return Task.FromResult(new ProviderCultureResult(culture, uiCulture));
}
}

فنلاحظ أنه تم إستخراج معلومات اللغة عبر المفتاحين culture و uiCulture المعرفين في ملف الإعدادات أدناه.

{
"culture": "ar-SA"
"uiCulture": "ar-SA"
}

وبالطبع نقوم بإضافة المزود الجديد في الخاصية RequestCultureProviders الموجودة في الفئة RequestLocalizationOptions.

public void Configure(IApplicationBuilder app, IStringLocalizer<Startup> localizer)
{
var supportedCultures = new List<CultureInfo>
{
new CultureInfo("en-US"),
new CultureInfo("ar-SA")
};

var options = new RequestLocalizationOptions()
{
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures
};

options.RequestCultureProviders.Insert(0, new JsonRequestCultureProvider());
app.UseRequestLocalization(options);
...
}

2- مصدر موارد التعريب

ثاني نقطة يمكن أن يستخدمها المطوروين هي تحديد مصدر موارد التعريب, فأتت الـ ASP.NET 5 بملفات موارد التعريب المعروفة من الإصدارات السابقة .resx لكن سمحت للمطورين بتحديد المصدر الذي يرونه مناسباً في تطبيقاتهم, لأن أتكلم في حديثي كثيراً عن ملفات الـ Resources, وإنما سأتطرق لكيفية تطبيق مصدر آخر قد يكون قاعدة بيانات, ملف XML أو JSON وغيرها من المصادر المتاحة. في مثالنا سأقوم بتحديد مصدر الموارد من بيانات مؤقته في الذاكره عبر الـ EntityFramework طبعاً لتسهيل وتوضيح الفكرة لا أقل ولا أكثر.

وكما هو من المعلوم أن الـ EntityFramework ماهي إلا ORM لذا سنقوم أولاً بتعريف الـ Models اللازمة والتي تكمن في مثالنا في الثقافة Culture والمصدر Resource

public class Culture
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List<Resource> Resources { get; set; }
}
public class Resource
{
public int Id { get; set; }
public string Key { get; set; }
public string Value { get; set; }
public virtual Culture Culture { get; set; }
}

وبعدها سنقوم بتعريف محتوى البيانات DataContext

public class LocalizationDbContext : DbContext
{
public DbSet<Culture> Cultures { get; set; }
public DbSet<Resource> Resources { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseInMemoryDatabase();
}
}

إلى هذه النقطة نكون قد وصلنا لنهاية تعريف الكائنات اللازمة لتعريف قاعدة بيانات مؤقتة في الذاكرة بإستخدام الـ EntityFramework.

الآن سنقوم بإستخدام النقطة الفعليه للتطوير والتمدد وذلك بتطبيق الواجهات IStringLocalizer و IStringLocalizer<T> و IStringLocalizerFactory

public class EFStringLocalizer : IStringLocalizer
{
private readonly LocalizationDbContext _db;

public EFStringLocalizer(LocalizationDbContext db)
{
_db = db;
}

public LocalizedString this[string name]
{
get
{
var value = GetString(name);
return new LocalizedString(name, value ?? name, resourceNotFound: value == null);
}
}

public LocalizedString this[string name, params object[] arguments]
{
get
{
var format = GetString(name);
var value = string.Format(format ?? name, arguments);
return new LocalizedString(name, value, resourceNotFound: format == null);
}
}

public IStringLocalizer WithCulture(CultureInfo culture)
{
CultureInfo.DefaultThreadCurrentCulture = culture;
return new EFStringLocalizer(_db);
}

public IEnumerable<LocalizedString> GetAllStrings(bool includeAncestorCultures)
{
return _db.Resources
.Include(r => r.Culture)
.Where(r => r.Culture.Name == CultureInfo.CurrentCulture.Name)
.Select(r => new LocalizedString(r.Key, r.Value, true));
}

private string GetString(string name)
{
return _db.Resources
.Include(r => r.Culture)
.Where(r => r.Culture.Name == CultureInfo.CurrentCulture.Name)
.FirstOrDefault(r => r.Key == name)?.Value;
}
}

فكما نلاحظ من الشفرة السابقة أنه تم تطبيق الدوال اللازمة لجلب النصوص من الذاكرة عبر الـ EntityFramework وإستخدام كائن من الفئة LocalizationDbContext التي تعريفها سابقاً.

وبنفس الطريقة سيتم تطبيق الواجهة IStringLocalizer<T> وماهو إلا نسخة من الواجهة الأولى والخاصة بالـ Generics.

public class EFStringLocalizer<T> : IStringLocalizer<T>
{
private readonly LocalizationDbContext _db;

public EFStringLocalizer(LocalizationDbContext db)
{
_db = db;
}

public LocalizedString this[string name]
{
get
{
var value = GetString(name);
return new LocalizedString(name, value ?? name, resourceNotFound: value == null);
}
}

public LocalizedString this[string name, params object[] arguments]
{
get
{
var format = GetString(name);
var value = string.Format(format ?? name, arguments);
return new LocalizedString(name, value, resourceNotFound: format == null);
}
}

public IStringLocalizer WithCulture(CultureInfo culture)
{
CultureInfo.DefaultThreadCurrentCulture = culture;
return new EFStringLocalizer(_db);
}

public IEnumerable<LocalizedString> GetAllStrings(bool includeAncestorCultures)
{
return _db.Resources
.Include(r => r.Culture)
.Where(r => r.Culture.Name == CultureInfo.CurrentCulture.Name)
.Select(r => new LocalizedString(r.Key, r.Value, true));
}

private string GetString(string name)
{
return _db.Resources
.Include(r => r.Culture)
.Where(r => r.Culture.Name == CultureInfo.CurrentCulture.Name)
.FirstOrDefault(r => r.Key == name)?.Value;
}
}

أما الـواجهة IStringLocalizerFactory فهي المسؤلة لإنشاء كائن من فئة تطبق الواجهة IStringLocalizer.

public class EFStringLocalizerFactory : IStringLocalizerFactory
{
private readonly LocalizationDBContext _db;

public EFStringLocalizerFactory()
{
_db = new LocalizationDBContext();
_db.AddRange(
new Culture
{
Name = "en-US",
Resources = new List<Resource>() { new Resource { Key = "Hello", Value = "Hello" } }
},
new Culture
{
Name = "fr-FR",
Resources = new List<Resource>() { new Resource { Key = "Hello", Value = "Bonjour" } }
},
new Culture
{
Name = "es-ES",
Resources = new List<Resource>() { new Resource { Key = "Hello", Value = "Hola" } }
},
new Culture
{
Name = "jp-JP",
Resources = new List<Resource>() { new Resource { Key = "Hello", Value = "こんにちは" } }
},
new Culture
{
Name = "zh",
Resources = new List<Resource>() { new Resource { Key = "Hello", Value = "您好" } }
},
new Culture
{
Name = "zh-CN",
Resources = new List<Resource>() { new Resource { Key = "Hello", Value = "您好" } }
}
);
_db.SaveChanges();
}

public IStringLocalizer Create(Type resourceSource)
{
return new EFStringLocalizer(_db);
}

public IStringLocalizer Create(string baseName, string location)
{
return new EFStringLocalizer(_db);
}
}

أخيراً يتم تعريف كائن من الفئة EFStringLocalizerFactory في الـ Localization Middleware ليتم التعرف على المصدر الجديد في التطبيق

public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IStringLocalizerFactory, EFStringLocalizerFactory>();
}

وبعدها إستخدامه في التطبيق عبر الـ DependencyInjection

public void Configure(IApplicationBuilder app, IStringLocalizerFactory localizerFactory)
{
var localizer = localizerFactory.Create(null);
...
}

لمشاهدة الشفرة المصدرية كاملة يمكنك النقر على الرابط.

تويتر فيسبوك قوقل + لينكد إن


اكتب تعليقك