Derinlemesine yazılım eğitimleri için kanalımı takip edebilirsiniz...

Asp.NET Core’da Çok Dilli Uygulamalar Geliştirme

Merhaba,

Her web uygulamasının ideali, üretildiği kültürün sınırlarını aşabilmek ve daha geniş kitlelere ulaşarak farklı coğrafyalara hitap edebilmektir. Bu ideali gerçekleştirebilmek ve kozmopolit bir platform oluşturabilmek için birden çok dili destekleyebilmek ve gelen ziyaretçilerin bölge ve kültürlerine uygun içerik sunabilecek uygulamalar geliştirmek gerekmektedir. Asp.NET Core mimarisi, bu ihtiyaca istinaden özünde zengin bir küreselleştirme(globalization) ve yerelleştirme(localization) desteği barındırmakta ve biz geliştiriciler açısından kolaylıkla çok dilli uygulamalar oluşturmamızı sağlayacak olan özellikler sunmaktadır. Bu içeriğimizde, web uygulamalarımızda ziyaretçilerin tercihlerine göre yerelleştirilmiş içerikler sunabilmek için kullanabileceğimiz yapıları sizlere tanıtacak ve çoklu dil desteğinin temelde hangi mantıkla sağlandığını pratiksel olarak inceliyor olacağız.

Başlarken

İçeriğimiz boyunca anlatılanlara tatbik amaçlı eşlik edebilmek istiyorsanız eğer makaleye başlamadan önce hali hazırda bir Asp.NET Core MVC ya da API uygulaması oluşturmanızı tavsiye ederim. Ben deniz bu içeriğimizdeki tüm kodları .NET 6 – Asp.NET Core mimarisi üzerinden örneklendireceğim ve çalışma neticesinde örnek projeyi makalenin sonunda paylaşıyor olacağım.

Asp.NET Core’da Localization(Yerelleştirme) Yapılandırması

Asp.NET Core mimarisinin en güçlü yanlarından biri doğuştan middleware yapılanmasına dayanmasıdır. Haliyle client’lardan gelen istekleri seri bir şekilde türlü kontroller ve iş akışlarından geçirebilmekte ve duruma göre an öncesi aksiyonlar alıp, gerekli öncül çalışmaları sağlayabilmekteyiz.

Bu mantıkla olayı değerlendirirsek eğer, bir client’ın bölgesine göre gerekli yerelleştirmeyi sağlayabilmek için yapılan isteğin handle edilme sürecinde kültürün otomatik olarak ayarlanması gerekmektedir. İşte bu sebepten dolayı UseRequestLocalization middleware’ini yapılandırmamız gerekmektedir. Bunun için öncelikle ilgili uygulamanın ‘Program.cs’ dosyasına gelerek(Asp.NET Core 6.0’dan önceki sürümlerde Startup.cs dosyasına) aşağıdaki konfigürasyonun sağlanması gerekmektedir.

.
.
.
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews()
                .AddViewLocalization();

builder.Services.AddLocalization(options =>
{
    options.ResourcesPath = "Resources";
});
.
.
.

Yukarıdaki kod bloğuna göz atarsanız eğer; ilgili uygulamaya 9. satırda yerelleştirme ile ilgili AddLocalization servisinin eklendiğini görmekteyiz. Bu servis içerisindeki ‘ResourcesPath’ özelliği sayesinde, uygulamanın localization için nereden besleneceğine dair temel kaynak(resource) dosyalarının bulunduğu dizin bildirimini gerçekleştirmektedir.

Bunun dışında 6. satıra nazar eylersek eğer uygulamaya ‘AddControllersWithViews’ servisi akabinde AddViewLocalization servisi eklenmektedir. Bu servis ise MVC mimarisindeki V‘nin yani ‘View’ dosyalarının yerelleştirme için ihtiyacı olan hizmeti sağlamakta ve uygulamaya aktarmaktadır.

Uygulama Tarafından Desteklenecek Kültürlerin Yapılandırılması

Temel localization yapılandırmasından sonra sıra RequestLocalizationOptions konfigürasyonu üzerinden uygulama tarafından desteklenecek kültürlerin yapılandırmasına gelmiştir. Bunun için yine ‘Program.cs’ dosyasında(ya da Startup.cs) aşağıdaki gibi çalışılması yeterlidir;

.
.
.
builder.Services.Configure<RequestLocalizationOptions>(options =>
{
    options.DefaultRequestCulture = new("tr-TR");

    CultureInfo[] cultures = new CultureInfo[]
    {
        new("tr-TR"),
        new("en-US"),
        new("fr-FR")
    };

    options.SupportedCultures = cultures;
    options.SupportedUICultures = cultures;
});
.
.
.

Yukarıdaki kod bloğuna göz atarsanız eğer, uygulama sürecinde kullanılacak kültürlerin tanımlaması gerçekleştirilmiştir. Bizler örneklendirmemizde ‘Türkçe’, ‘İngilizce’ ve ‘Fransızca’ olmak üzere üç dil kullanacağız. Bu kültürler arasından ‘Türkçe’nin varsayılan kültür olarak belirlendiğine dikkatinizi çekerim.

Tüm bu işlemlerden sonra artık yapılması gereken tek hamle UseRequestLocalization middleware’inin çağrılarak, etkinleştirilmesidir.

.
.
.
app.UseRequestLocalization();
.
.
.

Kullanıcıların hangi kültürden olduğunu nereden anlayacağız?
Gelen isteklerin hangi kültürden olduğunu anlayabilmek için şu ana kadar konfigürasyonunu oluşturduğumuz UseRequestLocalization middleware’inin çalışma mantığını incelememiz yeterli olacaktır. UseRequestLocalization middleware’i tetiklendiği anda konfigüre edilmiş olan RequestLocalizationOptions türünden nesnenin ayarlarını uygulamaya yüklemekte ve gelen istekteki kültürü RequestCultureProvider şeklinde nitelendirilen bir sağlayıcı dizisiyle kontrol etmektedir. Bu sağlayıcı dizisi;

  • QueryStringRequestCultureProvider
  • CookieRequestCultureProvider
  • AcceptLanguageHeaderRequestCultureProvider

olmak üzere üç adettir. Kullanıcı yaptığı isteğin hangi kültürden olduğunu bu sağlayıcıların herhangi biriyle server’a bildirecektir. Haliyle bizler bu bildirime göre gerekli dil dönüşümünü sağlayacağız. Eğer ki kullanıcıdan herhangi bir provider’da kültür bilgisi söz konusu değilse o zaman ‘DefaultRequestCulture’ property’sine verilen kültür geçerli olacaktır.

Peki hoca! bu sağlayıcıları hafiften açıklayabilir misin?
Yazımızın devamında, çok dilliliği test edebilmek için bu provider’ları tek tek tam teferruatlı mevzu bahis ediyor olacağız. Amma velakin sizlerin meramını dindirebilmek ve hazır lafı geçmişken hafiften ön bilgi verebilmek için aşağıda sağlayıcılara dair kısaca açıklamalarda bulunalım.

  • QueryStringRequestCultureProvider
    Gelen isteğin hangi kültürden olduğunu query string’de ‘culture’ parametresi eşliğinde bildirmektedir.
  • CookieRequestCultureProvider
    Gelen isteğin hangi kültürden olduğunu Cookie’de ‘.AspNetCore.Culture’ key’ine karşılık bildirmektedir.
  • AcceptLanguageHeaderRequestCultureProvider
    Gelen isteğin hangi kültürden olduğunu header’da ‘Accept-Language’ key’ine karşılık bildirmektedir.

Artık uygulama üzerinde temel localization konfigürasyonu tamamlanmış bulunmaktadır. Bundan sonra, farklı dillerde metin karşılığını barındıracak olan resource dosyalarının adlandırma ve konum kurallarının detaylarını konuşmaya geçebiliriz.

Resource Dosyalarını Adlandırma ve Konumlandırma Kuralları

Asp.NET Core uygulamalarında, klasik Asp.NET’te olduğu gibi simgeler, resimler, özelleştirilmiş dosyalar yahut sabit metinler için .resx uzantılı resource yapıları kullanılmaktadır. Bu yapılar sayesinde harici uzantıları ve sabit metinleri kaynak kodundan ayırabilmekte ve daha dinamik bir şekilde yönetebilmekteyiz. Bu mantıkla farklı kültür ve dillere dair gerekli verileri resource dosyalarında tutmamız ve o anki kültüre göre ihtiyaç doğrultusunda ilgili resource dosyasından verileri getirmemiz oldukça ideal bir yaklaşım olacaktır.

Peki resource dosyalarını nerede ve nasıl oluşturmalıyız?
Çoklu dil operasyonları için oluşturulacak resource dosyalarında isim standardı
[file-name].[language-code].resx şeklinde olacaktır. Buna örnek vermemiz gerekirse eğer; İngilizce dil verilerini depolayacağımız resource kaynağının adı Lang.en-US.resx olacakken, benzer mantıkla Fransızca için Lang.fr-FR.resx şeklinde olacaktır. Eee haliyle Türkçe dil kaynağının ise Lang.tr-TR.resx olacaktır. Tabi buradaki isim formatı her ne kadar bir standart gerektirse de kendi kararınıza göre değiştirilebilir.

Resource dosyalarının nerede tanımlanacağına gelirsek eğer bunun için yukarıda AddLocalization servisini tanımlarken ResourcesPath property’sine verdiğimiz ‘Resources’ değerini kullanacağız. Bu değer, projenin kök dizininde ‘Resources’ adında bir klasör olacağı ve içerisinde .resx uzantılı dil kaynaklarının barındırılacağı anlamına gelmektedir.
Asp.NET Core'da Çok Dilli Uygulamalar GeliştirmeYukarıdaki görselde görüldüğü üzere ‘Resources\Languages‘ dizini altında örneklendirmemiz gereği üç dile karşılık dil depoları oluşturulmuş bulunmaktadır. Burada zihinlerinizden geçen Neden Resources klasörü altına Languages klasörü eklendi? sorusunu duyar gibiyim… Bunun nedeni birazdan bu resource dosyalarını okumayı ele alırken anlaşılacaktır. O noktaya gelmeden şimdilik bu kaynak dosyalarını açalım ve içlerini aşağıdaki gibi olacak şekilde dolduralım.

Lang.tr-TR Lang.fr-FR Lang.en-US
Asp.NET Core'da Çok Dilli Uygulamalar Geliştirme Asp.NET Core'da Çok Dilli Uygulamalar Geliştirme Asp.NET Core'da Çok Dilli Uygulamalar Geliştirme

Resource Dosyalarının Okunması

Ziyaretçilerin kültürlerine göre resource dosyalarının okunabilmesi için temelde

  • IStringLocalizer | IStringLocalizer<T>
  • IHtmlLocalizer | IHtmlLocalizer<T>

olmak üzere iki farklı yerel servis mevcuttur. Her bir servisin kendi şahsına münhasır davranış modelinin mevcut olmasının yanında tümünde ortak olan teknik bir nokta vardır. O da, tüm servisler de resource dosyalarının okunmasının pratikte aynı altyapıyı gerektirmesidir. Haliyle bu servisleri incelemeye gelmeden önce mevzu bahis altyapıyı oluşturup hali hazır duruma getirelim.

Bunun için tek yapılması gereken resource dosyalarıyla aynı isimde olan bir sınıfın oluşturulmasıdır. Bizlerin dil verilerini depoladığımız dosyalara tekrar göz atarsanız Lang.***.resx formatında oluşturulduğunu göreceksiniz. Haliyle bu resource dosyalarından okuma işlemini yapacak olan sınıfımızın adı da Lang.cs olacaktır. Burada isimlendirme formatının kritik arz ettiği aşikardır. Dosyaların formatı ne ise, okuma işleminden sorumlu sınıfın adı da bire bir o olmalıdır.

Peki bu sınıf nerede oluşturulacaktır?
İşte bu sorunun cevabı yukarıda oluşturulan ‘Resources\Languages‘de saklıdır. Evet, bu sınıf projenin kök dizininde bulunan ‘Languages’ isimli bir klasörde tanımlanmalıdır. Ya da bu sınıfın namespace’i namespace Project.Languages şeklinde olmalıdır. Aksi taktirde .NET Core’da ki yerel localization servisleri bu sınıfın dizinini, ilgili dil verilerinin bulunduğu depoların diziniyle eşleştiremeyecek ve böylece veri okuma kabileyeti söz konusu olmayacaktır. Burada genelleme yaparsak eğer resource dosyaları ‘a\b’ dizininde ise bu resource dosyalarındaki verileri okuyacak sınıf proje kök dizini içerisinde tanımlanmış ‘b’ dizinin de olmalıdır.

Şimdi localization açısından resource dosyalarını okumamızı sağlayacak servisleri sırasıyla incelemeye geçebiliriz.

  • IStringLocalizer | IStringLocalizer<T>
    Controller sınıflarında resource dosyalarından localization verilerini metinsel olarak almamızı sağlayan servistir.

    Kullanımı;

    private readonly IStringLocalizer<Lang> _stringLocalizer;
    

    şeklindedir. Dikkat ederseniz generic olarak resource dosyalarını okuyacak olan sınıfın(Lang) bildirimi yapılmaktadır.

    Örnek olarak aşağıdaki kod bloğunu inceleyebilirsiniz.

        public class HomeController : Controller
        {
            private readonly IStringLocalizer<Lang> _stringLocalizer;
    
            public HomeController(IStringLocalizer<Lang> stringLocalizer)
                => _stringLocalizer = stringLocalizer;
    
            public IActionResult Index()
            {
                ViewBag.PageAbout = _stringLocalizer["page.About"];
                ViewBag.PageHome = _stringLocalizer["page.Home"];
                return View();
            }
        }
    

    Görüldüğü üzere ‘IStringLocalizer’ dependency injection ile talep edilebilmekte ve belirtilen key’lere karşılık uygun localization verinin gelmesi sağlanmaktadır.

  • IHtmlLocalizer | IHtmlLocalizer<T>
    Resource dosyalarında kayıtlı verilerin içerisinde varsa HTML kodları, bunların tarayıcı tarafından derlenebilecek şekilde biçimlendirmesini sağlayan servistir. ‘IStringLocalizer’ verileri direkt getirirken, ‘IHtmlLocalizer’ ise HTML etiketlerini biçimlendirerek getirir.

    Kullanımı ‘IStringLocalizer’ ile birebir benzerdir;

    private readonly IHtmlLocalizer<Lang> _htmlLocalizer;
    

    Örnek mahiyetinde aşağıdaki kodu inceleyebilirsiniz.

        public class HomeController : Controller
        {
            private readonly IHtmlLocalizer<Lang> _htmlLocalizer;
    
            public HomeController(IHtmlLocalizer<Lang> htmlLocalizer)
                => _htmlLocalizer = htmlLocalizer;
    
            public IActionResult Index()
            {
                ViewBag.PageAbout = _htmlLocalizer["page.About"];
                ViewBag.PageHome = _htmlLocalizer["page.Home"];
                return View();
            }
        }
    

RequestCultureProvider İle Kullanıcı Kültürünü Yakalama

Yazımızın önceki satırlarında, ziyaretçilerden gelen istekler üzerinden kültürlerini RequestCultureProvider ismi verilen sağlayıcılar sayesinde edinebileceğimizden ve bu sağlayıcıların QueryStringRequestCultureProvider, CookieRequestCultureProvider ve AcceptLanguageHeaderRequestCultureProvider olmak üzere üç adet olduğundan bahsetmiştik. Şimdi gelin, ziyaretçilerin kültürlerini öğrenebilmemizi sağlayacak olan bu sağlayıcıları tek tek teferruatlarıyla inceleyelim.

  • QueryStringRequestCultureProvider
    Gelen ziyaretçi web uygulamasındaki varsayılan dilden farklı bir kültüre aitse buna müdahale edebilmek ve dili kendine göre değiştirebilmek için query string üzerinden ‘culture’ parametresini aşağıdaki gibi gönderebilir.
    Asp.NET Core'da Çok Dilli Uygulamalar Geliştirme
  • AcceptLanguageHeaderRequestCultureProvider
    Benzer şekilde gelen ziyaretçi yine varsayılan dilden farklı bir kültüre aitse buna müdahale edebilmek için yapacağı request’in header’da ‘Accept-Language’ key’ine karşılık dil bildiriminde bulunabilir.
  • CookieRequestCultureProvider
    Çoğu web uygulaması, ziyaretçi kültürünü Cookie’de tutmaktadır. Haliyle bizler de ziyaretçiden gelen dil bilgisini CookieRequestCultureProvider ile cookie’de saklayabiliriz. Bunun için aşağıdaki geliştirmenin yapılması gerekmektedir.

    • 1. Adım
      Kullanıcıdan gelen istek neticesinde kültürüne uygun cookie değerini oluşturacak middleware’i tanımlayalım.

          public class RequestLocalizationCookiesMiddleware : IMiddleware
          {
              readonly CookieRequestCultureProvider _provider;
      
              public RequestLocalizationCookiesMiddleware(IOptions<RequestLocalizationOptions> requestLocalizationOptions)
                  => _provider = requestLocalizationOptions.Value.RequestCultureProviders.Where(x => x is CookieRequestCultureProvider).Cast<CookieRequestCultureProvider>().FirstOrDefault();
      
              public async Task InvokeAsync(HttpContext context, RequestDelegate next)
              {
                  if (_provider != null)
                  {
                      IRequestCultureFeature feature = context.Features.Get<IRequestCultureFeature>();
                      if (feature != null)
                          context.Response.Cookies.Append(_provider.CookieName, CookieRequestCultureProvider.MakeCookieValue(feature.RequestCulture));
                  }
      
                  await next(context);
              }
          }
      

      Burada ziyaretçinin kültürü ister query string’den(QueryStringRequestCultureProvider) isterse de header bilgisinden(AcceptLanguageHeaderRequestCultureProvider) gelsin fark etmeksizin yakalanmakta ve cookie olarak response’a eklenmektedir.

    • 2. Adım
      Bu middleware’i pipeline’a entegre edecek olan extension metodu tasarlayalım.

          public static class RequestLocalizationCookiesMiddlewareExtensions
          {
              public static IApplicationBuilder UseRequestLocalizationCookies(this IApplicationBuilder app)
                  => app.UseMiddleware<RequestLocalizationCookiesMiddleware>();
          }
      
    • 3. Adım
      Oluşturulan middleware’i dependency injection container’ına ekleyelim.

      builder.Services.AddScoped<RequestLocalizationCookiesMiddleware>();
      

      Ve ardından UseRequestLocalization middleware’inden sonra oluşturduğumuz ‘UseRequestLocalizationCookies’ extension metodunu çağıralım.

      app.UseRequestLocalization();
      
      app.UseRequestLocalizationCookies();
      

    Bu çalışmadan sonra ziyaretçi hangi dili gönderiyorsa o cookie’ye kaydedilecektir.
    Asp.NET Core'da Çok Dilli Uygulamalar Geliştirme
    Dikkat ederseniz eğer cookie’ye .AspNetCore.Culture key’i karşılığında kaydedilen dil bilgisi sayesinde ziyaretçinin tekrardan dili gönderme gibi bir sorumluluğu kalmamaktadır.

Web Uygulamasında Desteklenen Dillerin Bilgisini Almak

Localization operasyonlarında en önemli hususlardan biri uygulama sürecinde desteklenmekte olan dillerin toplu bilgisini alabilmektir. Bunun için türlü yöntemler uygulanabileceği gibi .NET Core localization servislerinin bizlere sağladığı aşağıdaki gibi pratik dokunuşlar da mevcuttur.

    public class LangController : Controller
    {
        readonly RequestLocalizationOptions _localizationOptions;

        public LangController(IOptions<RequestLocalizationOptions> localizationOptions)
            => _localizationOptions = localizationOptions.Value;

        public IActionResult AllLanguages()
        {
            IRequestCultureFeature requestCulture = HttpContext.Features.Get<IRequestCultureFeature>();
            var allCultures = _localizationOptions.SupportedCultures
                    .Select(culture => new
                    {
                        Name = culture.Name,
                        Text = culture.DisplayName
                    }).ToList();
            return Ok(allCultures);
        }
    }

Asp.NET Core'da Çok Dilli Uygulamalar GeliştirmeGörüldüğü üzere IRequestCultureFeature arayüzü sayesinde uygulamanın ‘Program.cs'(Startup.cs) dosyasında konfigüre edilen RequestLocalizationOptions servisinin içerisinde tanımlanan tüm kültürler basit bir şekilde tarafımızca elde edilmiştir.

Nihai olarak;
Bu içeriğimizde, web geliştiricileri açısından korkutucu bir konu olan çoklu dil desteğinin özünde gayet basit ve sıradan bir mimariye sahip olduğunu gözlemlemiş olduk. Hele hele bu sıradanlığın Asp.NET Core mimarisiyle daha da basit bir hale getirildiğini görmüş ve hiç kompleks işleme gerek duyulmaksızın hızlıca nasıl entegre yapılabileceğini tecrübe etmiş olduk. Bu içeriğimiz boyunca, bir web uygulamasında yerelleştirme operasyonlarının nasıl yapılandırıldığına, çalışma zamanında ziyaretçilere uygun dile web uygulamasının nasıl eşlik edebileceğine ve belli başlı kültür sağlayıcılarının neler olduğuna teorik ve pratik olmak üzere temas etmeye çalıştık. Bol bol faydalamanız dileğiyle…

Sabredip okuduğunuz için teşekkür ederim.

İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…

Not : Örnek projeyi indirebilmek için buraya tıklayınız.

Bunlar da hoşunuza gidebilir...

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir