.Net Core Çoklu Dil Kullanımı (WEB+Class Lib)

14 Eki

Herkese selam,
.Net Core projelerinde çoklu dil yapılandırmasını Class Library ile nasıl yönetebiliriz anlatmaya çalışacağım.
Yani dil dosyalarımızı direkt WEB projesi içinde kullanmak yerine ayrı bir c# projesi yapıp, bu projedeki dosyaları nasıl WEB tarafında kullanırız aktaracağım.

Öncelike “SemMultiLang” isimli boş bir Solition oluşturdum.
Solition içine ilk olarak “SemMultiLang.LanguageLib” isminde bir Class Library projesi ekledim.

İkinci olarak da “SemMultiLang.LanguageWEB” isminde ASP.NET Core Web App (Model-View-Controller) projesi ekledim.

2 projede .Net 8.0 ile oluşturuldu. Burası önemli çünkü 2 projede aynı versiyonları kullanmalı. Eğer siz bu işlemleri örneğin .Net Core 5.0 ile yapıyorsanız o halde Framework versiyonunu ona göre seçmelisiniz.

Projemiz bu şekilde gözüküyor.

İşlemlere geçmeden önce SemMultiLang.LanguageLib isimli Class Library projemizi Web projemize ekleyelim.
Dependencies sağ tıklayıp > Add Project Referance… > Projects, Solition altındaki LanguageLib seçip ekledim.

Şuanda proje yapımız hazır. Artık asıl işlemlerimize geçebiliriz.

SemMultiLang.LanguageLib isimli proje altında

1- Languages isimli bir klasör oluşturdum. Bu klasör altına Lang.cs isimli bir class oluşturdum. Bu classı public’e çektim.
Bu klasör ismine ve class adına göre ileride dil dosyalarımızdan verileri okuyabileceğiz. Tüm ayarları bu isimlere göre yapıyoruz.

namespace SemMultiLang.LanguageLib.Languages
{
    public class Lang
    {
    }
}

2- Resources/Languages isimli klasörleri oluşturdum ve altına Lang.en-US.resx ve Lang.tr-TR.resx isminde dosyaları ekledim.
Resource altında Languages klasör ismi yukarıda oluşturduğumuz isimle aynı olmalı ve dil dosyalarımız yukarıdaki class adımızla aynı olmalı. Aksi durumda bu dil dosyalarından verileri çekemiyoruz.
tr-TR ya da en-US isimleri standart lokalizasyon ülke kodlarıdır. Bu listeden kullanmak istediğiniz dil/ülke kodlarına bakabilirsiniz.

Lang.en-US.resx dosyasına aşağıdaki labelları tanımladım:

Lang.tr-TR.resx dosyasına aşağıdaki labelları tanımladım:

SemMultiLang.LanguageWEB isimli projeye geçiyorum.

Projeye sağ tıklayıp, Middleware isimli bir klasör ekledim.
Klasör altına MiddlewareCookiesRequestLocalization.cs ve MiddlewareExtensionsCookiesRequestLocalization.cs isminde classları ekledim.

MiddlewareCookiesRequestLocalization.cs;

using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.Options;

namespace SemMultiLangTest.Middleware
{
    public class MiddlewareCookiesRequestLocalization : IMiddleware
    {
        readonly CookieRequestCultureProvider _provider;

        public MiddlewareCookiesRequestLocalization(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);
        }
    }
}

MiddlewareExtensionsCookiesRequestLocalization.cs

namespace SemMultiLangTest.Middleware
{
    public static class MiddlewareExtensionsCookiesRequestLocalization
    {
        public static IApplicationBuilder UseRequestLocalizationCookies(this IApplicationBuilder app)
        {
            return app.UseMiddleware<MiddlewareCookiesRequestLocalization>();
        }
    }
}

Bu middleware classları ile cookiler ile işlem yapılmasını sağlıyoruz. Bu sayede, aktif dili cookielere yazıp/okuyabiliyor olacağız.

SemMultiLang.LanguageWEB projesi içindeki Program.cs dosyama ilgili lokalizasyon kodlarımı ekliyorum.

Program.cs Full code;

public class Program
{
    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);

        // Add services to the container.
        builder.Services.AddControllersWithViews()
                        .AddViewLocalization();

        // + localization
        builder.Services.AddLocalization(opt =>
        {
            opt.ResourcesPath = "Resources";
        });

        builder.Services.Configure<RequestLocalizationOptions>(opt =>
        {
            opt.DefaultRequestCulture = new Microsoft.AspNetCore.Localization.RequestCulture("tr-TR");

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

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

        builder.Services.AddScoped<MiddlewareCookiesRequestLocalization>();
        // -

        var app = builder.Build();

        // Configure the HTTP request pipeline.
        if (!app.Environment.IsDevelopment())
        {
            app.UseExceptionHandler("/Home/Error");
            // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthorization();

        // + localization
        app.UseRequestLocalization();
        app.UseRequestLocalizationCookies();
        // -

        app.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");

        app.Run();
    }
}

builder.Services.AddControllersWithViews() altına AddViewLocalization() methodunu ekliyorum. sonrasında kaynak yolumu Resources olarak belirtiyorum.
Projemizde labellarımızı ResourcePath kısmına yazdığmız dosya adı altında arayacak. Bunun altında ise tr-TR ve en-US olacak şekilde 2 culture kullanacağımı belirtiyorum.
Scope olarak Cookie Middleware geçiyorum en aşağıda ise app.UseRequestLocalization(); ve app.UseRequestLocalizationCookies(); ile uygulamada lokalizasyon ve cookie lokalizasyonu kullanacağımı belirtmiş oluyorum.
Buraya kadar geldiysek teknik olarak tüm alt yapı işlemlerimiz tamamlandı. Artık bu Resource Dosyalarımızın(label dosyalarının) içinden verileri çekip bir kaç yerde kullanalım. Projemin genel görünümü bu şekilde oldu;

Öncelikle bir labelın içini okuyabilmemiz için aktif dilimizi çekmemiz gerekiyor bunu için bir kaç yöntem var;
1- QueryStringRequestCultureProvider, sitemizin URL kısmında ?culture=tr-TR yazan yerden veriyi çekmemizi sağlar.
2- CookieRequestCultureProvider, şuan bizim örneğimizde bu yapı aktif. Cookielere yazılan aktif dili çekmemizi sağlar.
3- AcceptLanguageHeaderRequestCultureProvider, siteye yapılan isteklerin headerında accept-language attribute özelliğine yazılan değer üzerinden aktif dili çekebiliriz. Bu yöntem api projelerimizde kullanılabilir. Bu örnekte sizlere bunu göstermedim fakat kendi apilerimde bunu kullandığım yerler bulunmakta.

Aktif dili yukarıdaki yöntemlerle çekiyoruz. Peki bunu html ya da controller tarafında nasıl kullanacağız. Bunun içinde 2 yöntem mevcut;
1- IStringLocalizer | IStringLocalizer: Label içindeki veriyi string olarak bize verir.
2- IHtmlLocalizer | IHtmlLocalizer: Label içindeki veriyi html olarak bize verir. Örneği label dosyası içinde html kodları varsa bunları biçimli şekilde kullanabilmemiz için html özellikleriyle beraber bize veriyi verir.

Şimdi bir kaç örnekle labellarımızdan verilerimizi çekip kullanalım.
İlk örneğimizde Controller tarafında labelı çekip kullanalım.

HomeController.cs dosyamı açtım ve aşağıdaki gibi düzenledim.

HomeController.cs full;

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;
    private readonly IStringLocalizer<Lang> _stringLocalizer;

    public HomeController(ILogger<HomeController> logger, IStringLocalizer<Lang> stringLocalizer)
    {
        _logger = logger;
        _stringLocalizer = stringLocalizer;
    }

    public IActionResult Index()
    {
        ViewBag.WelcomeLabel = _stringLocalizer["Welcome"];

        return View();
    }

    public IActionResult Privacy()
    {
        return View();
    }

    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
    public IActionResult Error()
    {
        return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
    }
}

IStringLocalizer olarak tanım yapmamızın sebebi, resource dosyamızın yani label dosyamıızn adının Lang ile başlamasından dolayıdır. Lang.cs isimli boş bir classı sadece burada kullanabilmek için oluşturduk. Burada sizlerde benim gibi neden boş class oluşturup kullandık diyebilirsiniz, biraz araştırdım fakat bundan farklı bir yöntem bulamadım. Bu yüzden ben de boş bir oluşturup (Lang.cs) Resource dosya isimlerimin başına bu classla aynı ismi verip çekerek kullandım.(Lang.tr-TR.resx, Lang.en-US.resx)

Sonrasında Index methodunun içinde _stringLocalizer[“Welcome”]; diyerek label dosyamının içindeki Welcome key’inin ya da Welcome etiketi diyebiliriz bunun karşılığını çekip ViewBag.WelcomeLabel içine attım.

Şimdi bu ViewBag içindeki veriyi html’de gösterlim.

Views/Home/Index.cshtml kodlarım;

@{
    ViewData["Title"] = "Home Page";
}

<div class="text-center">
    <h1 class="display-4">@ViewBag.WelcomeLabel</h1>

    <p>Learn about <a href="https://learn.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

<pre>

Projeyi çalıştırıp ilk testimizi yapalım. Site açıldıktan sonra sonuna ?culture=tr-TR(<em>https://localhost:7237/?culture=tr-TR</em>) yazalım. Karşımıza Welcome etiketinin tr-TR.resx dosyasındaki karşılığı gelmeli yani Hoş geldiniz yazmalı.

<a href="https://semihcelikol.com/wp-content/uploads/2024/10/View1.jpg"><img src="https://semihcelikol.com/wp-content/uploads/2024/10/View1.jpg" alt="" width="848" height="336" class="aligncenter size-full wp-image-2610" /></a>

Şimdide ingilizcesine bakalım, bunun içinde url kısmına yazdığımız parametreyi değiştirerek ?culture=en-US (<em>https://localhost:7237/?culture=en-US</em>) yapalım. Bu kez Welcome yazmalı.
<a href="https://semihcelikol.com/wp-content/uploads/2024/10/View2.jpg"><img src="https://semihcelikol.com/wp-content/uploads/2024/10/View2.jpg" alt="" width="818" height="350" class="aligncenter size-full wp-image-2611" /></a>

Controller tarafında kullanmak bu şekildeydi. Peki gelin şimdi controller olmada direkt .cshtml içinde nasıl kullanılırız bir de ona bakalım. Bu işlem için html içinde <strong>@inject</strong> yönteminden yararlanacağız.

<strong>Home.cshtml</strong> içini aşağıdaki gibi değiştirdim.

Home.cshtml full code;
<pre class="prettyprint">
@using Microsoft.Extensions.Localization
@using SemMultiLang.LanguageLib.Languages

@inject IStringLocalizer<Lang> _strLocalization;


@{
    ViewData["Title"] = _strLocalization["Home"];
}

<div class="text-center">
    <h1 class="display-4">@ViewBag.WelcomeLabel</h1>


    <p>@_strLocalization["OverviewDesc"]</p>
</div>

Çalıştırdığımız ilgili labelları görüntüleyebiliyorum.
culture=tr-TR;

culture=en-US;

Gördüğünüz gibi title ve açıklama kısımları belirttiğim dil koduna göre otomatik şekilde değişti. İşlemlerimiz bu kadar.
Fakat ben bonus olarak şu helper classını da sizlere vermek istiyorum. Bazı durumlarda classlar içinde stringlocalizer kullanmadan label karşılıklarını çekmek isteyebiliriz. Bu durumda direkt label dosyasının içinden verileri okuyabilecğeimiz bir sınıf yaptım. Şu şekilde;

LanguageHelper isimli bir class'ı LanguageLib projesine ekledim.

LanguageHelper.cs full code;

using System.Globalization;
using System.Reflection;
using System.Resources;

namespace SemMultiLang.LanguageLib
{
    public class LanguageHelper
    {
        ResourceManager _resourceManager;

        public LanguageHelper()
        {
            _resourceManager = new ResourceManager("SemMultiLang.LanguageLib.Resources.Languages.Lang",
                                                   Assembly.GetExecutingAssembly());
        }

        public string GetLabel(string language, string labelKey)
        {

            if (string.IsNullOrWhiteSpace(language))
            {
                throw new Exception("Language is required");
            }

            string ret = "";

            try
            {
                CultureInfo culture = CultureInfo.CreateSpecificCulture(language);

                ret = _resourceManager.GetString(labelKey, culture);

                return ret;
            }
            catch
            {
                ret = labelKey;

                return ret;
            }
        }
    }
}

Bu class içinde GetLabel methodu 2 parametre alıyor, buraya dil kodunu ve çekmek istediğimiz label kodunu verdiğimizde, bize karşılığını dönüyor.

Peki bu classı Web tarafında nasıl kullanırız ? gelin bakalım.

HomeController.cs içindeki Index methodunu aşağıdaki gibi güncelledim;

public IActionResult Index()
{
    ViewBag.WelcomeLabel = _stringLocalizer["Welcome"];

    LanguageLib.LanguageHelper languageHelper = new LanguageLib.LanguageHelper();

    // + LanguageHelper
    string activeCulture = HttpContext.Features.Get<IRequestCultureFeature>().RequestCulture.Culture.Name;

    ViewBag.HomeLabel = languageHelper.GetLabel(activeCulture, "Home");
    //-

    return View();
}

Home.cshtml aşağıdaki gibi güncelledim;


@using Microsoft.Extensions.Localization
@using SemMultiLang.LanguageLib.Languages

@inject IStringLocalizer<Lang> _strLocalization;


@{
    ViewData["Title"] = _strLocalization["Home"];
}

<div class="text-center">
    <h1 class="display-4">@ViewBag.WelcomeLabel</h1>


    <p>@_strLocalization["OverviewDesc"]</p>

    <p>LanguageHelper.cs yönteminden çekilen label: @ViewBag.HomeLabel</p>
</div>

Çalıştırıp test ettiğimde, culture=tr-TR için;

culture=en-US için;

Şeklinde label etiketiminin karşılıklarını çekebildiğimi görüyorum. İşlemlerimiz bu kadardır. İşinize yaraması dileğiyle herkese iyi çalışmalar dilerim.
Projenin tam halini GitHub'a ekledim.
GitHub Link: https://github.com/semihcelikol/SemMultiLang

Multilanguage konusunu araştırırken ve projemde kullanmaya çalışırken aşağıdaki kaynaklardan yararlandım.

https://www.gencayyildiz.com/blog/asp-net-coreda-cok-dilli-uygulamalar-gelistirme/

https://www.youtube.com/watch?v=QZtW9S-WvPA

Bana destek olmak isterseniz Buy Me a Coffe üzerinden bir kahve ısmarlayabilirsiniz.

Buy Me A Coffee

Herkese sağlıklı günler ve iyi çalışmalar.

Bir Cevap Yazın