Asp.NET MVC Çoklu Dil Fonksiyonları – 1

Web uygulamamızda, farklı diller altında destek vermemiz gerekebilir.Asp.NET MVC mimarisinde, bu işlemin nasıl yapıldığını, hangi sınıfların kullanıldığını irdeleyeceğiz.Baştan söylüyorum ki, bu yazı biraz uzun ve detaylı anlatıma sahip olacaktır.Dikkatli okumanızı hatta bir kaç sefer okumanızı tavsiye ederim.

Ben bir proje üzerinden anlatım yaparak gideceğim için, herhangi bir MVC projesi oluşturuyorum.
Projemizde kullandığımız kelimelerin, desteğini verceğimiz dildeki karşılıklarını özel bir yapı olan “Add_GlobalResources” dosyası içinde, bir resource dosyası içinde tutacağız.

Şimdi projemize bu dosyayı ekleyelim.Solution Explorer penceresinden projemize sağ tıklayıp, “Add” -> “Add ASP.NET Folder” -> “Add_GlobalResources” kombinasyonlarını takip edip, bu klasörü projemize eklemiş oluyoruz.

Solution Explorer penceresinde, projemize dosyanın eklenmiş hali.


Oluşturduğumuz bu “Add_GlobalResources” isimdeki özel dosyanın içine, kelimelerimizin farklı dillerdeki karşılıklarını yazacağımız Resources dosayasını ekleyelim.

“Add_GlobalResources” klasörüne sağ tıklayıp, “Add” sekmesinden “New Item” seçeneğini seçelim.Çıkan pencereden “Resources File” dosyasına tıklayalım.Burada dikkat etmemiz gereken bir husus var.Bu Resource dosyasının adına ben şimdilik “lang” diyorum.Bu ismi vermemin iki sebebi vardır.

Birincisi, kullanacağımız sınıflar varsayılan olarak “lang” adında bir parametre kullanıyorlar.Bunları istersek değiştirebiliriz.Bu sınıfları yazımızın devamında göreceğiz.
İkinci sebep ise, varsayılan dil olarak “lang” ismindeki Resource dosyasını kullanacağız.Yani oluşturacağımız dilin standardına göre “lang.fr”(Fransızca) gibisinden isimlerde Resource dosyaları oluşturuacağız.Bu standartlar önemlidir.Mesela Türkçe’de “.tr”, İngilizce’de “en” gibi…

Herneyse, “Add_GlobalResources” dosyamıza “lang.resx” adında Resource dosyamızı eklemiş bulunmaktayız.

Eğer “lang.resx” dosyamızı açarsak karşımıza aşağıdaki gibi bir pencere gelecektir.

Kırmızıyla çizilen “Name” alanındaki anahtar değerlere karşılık gelen mavi renkle çizilen alandaki değerler yazılacaktır.

Asp.NET MVC’de çoklu dil işlemlerini, şu dört sınıfla yapıyoruz.
LocalizationLocalizationWebFormViewLocalizationWebFormViewEngine ve ResourceExtensions sınıflarıdır.(Topluca indirmek için burayı tıklayınız.)

Bu sınıfları, projemizde “CokluDilSiniflari” adli bir klasör açıp içine atalım.

Dikkat !!! Bu sınıfları .dll olarak eklemediğimizden dolayı, namespacelerini projenizdeki altına eklediğiniz dosyanın ismine uygun bir biçimde namespace isimlerini değiştirin.

Şimdi bu sınıfları biraz inceleyelim.Öncelikle “Localization” sınıfından başlayalım.

--- Localization.cs ---
  public class LocalizationAttribute : ActionFilterAttribute
       {
           public override void OnActionExecuting(ActionExecutingContext filterContext)
           {
               if (filterContext.RouteData.Values["lang"] != null &&
                   !string.IsNullOrWhiteSpace(filterContext.RouteData.Values["lang"].ToString()))
               {
                   // set the culture from the route data (url)
                   var lang = filterContext.RouteData.Values["lang"].ToString();
                   Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(lang);
               }
               else
               {
                   // load the culture info from the cookie
                   var cookie = filterContext.HttpContext.Request.Cookies["MvcLocalization.CurrentUICulture"];
                   var langHeader = string.Empty;
                   if (cookie != null)
                   {
                       // set the culture by the cookie content
                       langHeader = cookie.Value;
                       Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(langHeader);
                   }
                   else
                   {
                       // set the culture by the location if not speicified
                       langHeader = filterContext.HttpContext.Request.UserLanguages[0];
                       Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(langHeader);
                   }
                   // set the lang value into route data
                   filterContext.RouteData.Values["lang"] = langHeader;
               }
               // save the location into cookie
               HttpCookie _cookie = new HttpCookie("MvcLocalization.CurrentUICulture", Thread.CurrentThread.CurrentUICulture.Name);
               _cookie.Expires = DateTime.Now.AddYears(1);
               filterContext.HttpContext.Response.SetCookie(_cookie);
               base.OnActionExecuting(filterContext);
           }
       }

Yukarıdaki kodları incelerseniz eğer, “Localization” sınıfı “ActionFilterAttribute” sınıfından türediği için bir Attribute sınıfıdır.Bu Attribute’un yazıldığı Controller metodu çalıştığı anda, adres çubuğundan “filterContext.RouteData.Values[“lang”]” kodu ile, “lang” değişkenindeki değeri arayacaktır.Hatırlarsanız, Resource dosyası oluştururken, “lang” ismini vermiştik.İşte bu sınıfın içindeki RouteData.Values komutunun değişken adı “lang” olduğundan dolayıdır.İsterseniz bu değeri değiştirebilirsiniz.

“lang” değişkenine gelen değere göre, sitemizde hangi dil dosyasının çalışacağını belirleyecektir.

Şimdide, “ResourceExtensions” isimdeki sınıfı inceleyelim.

--- ResourceExtensions.cs ---
    public static class ResourceExtensions
    {
        public static string Resource(this Controller controller, string expression, params object[] args)
        {
            ResourceExpressionFields fields = GetResourceFields(expression, "~/");
            return GetGlobalResource(fields, args);
        }
        public static string Resource(this HtmlHelper htmlHelper, string expression, params object[] args)
        {
            string path = (string)htmlHelper.ViewData[LocalizationWebFormView.ViewPathKey];
            if (string.IsNullOrEmpty(path))
                path = "~/";

            ResourceExpressionFields fields = GetResourceFields(expression, path);
            if (!string.IsNullOrEmpty(fields.ClassKey))
                return GetGlobalResource(fields, args);

            return GetLocalResource(path, fields, args);
        }
        public static string Language(this HtmlHelper htmlHelper, string controller, string key, params object[] args)
        {
            //Replace Language with a name of your choice, if you have lang.en.resx, you should change the value here to "lang"
            string expression ;
            if(controller!="")
                expression = "lang"+controller+"," + key;
            else
                expression = "lang," + key;
            return Resource(htmlHelper, expression, args);
        }
        static string GetLocalResource(string path, ResourceExpressionFields fields, object[] args)
        {
            try
            {
                return string.Format((string)HttpContext.GetLocalResourceObject(path, fields.ResourceKey, CultureInfo.CurrentUICulture), args);
            }
            catch { return (string)HttpContext.GetLocalResourceObject(path, fields.ResourceKey, CultureInfo.CurrentUICulture); }
        }
        static string GetGlobalResource(ResourceExpressionFields fields, object[] args)
        {
            try
            {
                return string.Format((string)HttpContext.GetGlobalResourceObject(fields.ClassKey, fields.ResourceKey, CultureInfo.CurrentUICulture), args);
            }
            catch { return (string)HttpContext.GetGlobalResourceObject(fields.ClassKey, fields.ResourceKey, CultureInfo.CurrentUICulture); }
        }
        static ResourceExpressionFields GetResourceFields(string expression, string virtualPath)
        {
            var context = new ExpressionBuilderContext(virtualPath);
            var builder = new ResourceExpressionBuilder();
            return (ResourceExpressionFields)builder.ParseExpression(expression, typeof(string), context);
        }
    }

Yukarıdaki “ResourceExtensions.cs” adındaki sınıfın kodlarından, “Language” adındaki metoda dikkat edelim.Hatta ben bu metodu daha ayrıntılı bir şekilde anlatabilmek için aşağıya alıyorum.

        public static string Language(this HtmlHelper htmlHelper, string controller, string key, params object[] args)
        {
            //Replace Language with a name of your choice, if you have lang.en.resx, you should change the value here to "lang"
            string expression ;
            if(controller!="")
                expression = "lang"+controller+"," + key;
            else
                expression = "lang," + key;
            return Resource(htmlHelper, expression, args);
        }

Bizim belirtmiş olduğumuz dil kalıp dosyasının adı neyse o çalıştırılacaktır.Dikkat ederseniz eğer, Resource dosyamızın adına “lang” koymuştuk.Bu ismin sebeplerinden biride bu sınıftaki “lang” değeridir.Gördüğünüz gibi, “lang” + controller ifadesinde, “lang” ismindeki resource dosyasından bahsediyor.Eğer controller parametresine “.tr”, “.en” gibi değerler gönderilirse, “lang.tr” veya “lang.en” resource dosyalarını temsil edecektir.

Tekrardan söylüyorum.Bu sınıflar içindeki “lang” kelimesi yerine başka değerler yazabilirsiniz.Tabi o değerde resource dosyanızı oluşturmanız gerekmektedir.

Şimdide “LocalizationWebFormView” ismindeki sınıfımızı inceleyelim.

    public class LocalizationWebFormView:WebFormView
    {
        internal const string ViewPathKey = "~/App_GlobalResources/";
        public LocalizationWebFormView(string viewPath)
            : base(viewPath)
        {
        }
        public LocalizationWebFormView(string viewPath, string masterPath)
            : base(viewPath, masterPath)
        {
        }
        public override void Render(ViewContext viewContext, TextWriter writer)
        {
            // there seems to be a bug with RenderPartial tainting the page's view data
            // so we should capture the current view path, and revert back after rendering
            string originalViewPath = (string)viewContext.ViewData[ViewPathKey];
            viewContext.ViewData[ViewPathKey] = ViewPath;
            base.Render(viewContext, writer);
            viewContext.ViewData[ViewPathKey] = originalViewPath;
        }
    }

Bu sınıf içinde önemli olan nokta, “ViewPathKey” ismindeki sabit değişken, Resource dosyalarımızın hangi klasör altında olduğunu belirtmektedir.

Sevgili okurlarım, biliyorum kafanız karışmış, takipte zorlanmış hatta yazıyı okumayı buraya kadar gelemeden bırakmış olabilirsiniz.Ama bilesiniz ki, elimden geldiği kadar açıklayıcı ve herşeyin birbiriyle bağlantısını sizlere göstererek anlatım yapmaya çalışıyorum.Gayretten zarar gelmez.Hadi bakalım 🙂

Devam edelim.

Benim üzerinde çalıştığım MVC projesi, hazır “Home(Controller)” adında bir Controller sınıfı bulundurmaktadır.Bu sınıfın içinde ActionResult tipinden “Index” adındaki metodun, View katmanındaki dosyasına aşağıdaki gibi bir tablo yerleştiriyorum.

    <table width="60%">
        <tr>
            <td>
                İsim
            </td>
            <td>
                Soyisim
            </td>
            <td>
                Yaş
            </td>
            <td>
                Meslek
            </td>
        </tr>
    </table>

Burada yapmak istediğim şu.Eğer varsayılan olarak dil yapısını kullanacaksam yukarıda görüğünüz gibi değerler ekranda gözüksün.Eğer İngilizce dil yapısını kullanacaksam, “İsim” yerine “Name”, “Soyisim” yerine “Surname”, “Yaş” yerine “Age” ve “Meslek” yerine “Job” kelimelerinin gözükmesini istiyorum.Ya da Fransızca dil yapısını kullanacaksam, gerekli tercümelerinin yerlerinde gözükmesini istiyorum 🙂

Bu işlemi sağlayacak olan fonksiyonum, “ResourceExtensions.cs” sınıfı içerisinde bulunan “Language” fonksiyonudur.Bu fonksiyon HtmlHelper tipinden bir Extension fonksiyonu olduğuna dikkat edelim.Yani “Html” nesnesi üzerinden erişeceğiz.

Şimdi bu işlemi yapmak için, View katmanında bulunan “Home” klasörü altındaki “Index.aspx” sayfasının kodlarına “ResourceExtensions.cs” sınıfının bulunduğu, “CokluDilSiniflari” klasörünü using etmeliyim.

<%@ Import Namespace="MVCCokluDil.CokluDilSiniflari" %>

Index.aspx sayfasına bu şekilde bir Import işlemi uygulayabilirim.
Eğer bu klasör altındaki sınıfları bütün projedeki View klasörlerinde kullanacaksam eğer aşağıdaki Web.config dosyasında belirtmeliyiz.

  <system.web>
...
    <pages>
      <namespaces>
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Routing" />
        <add namespace="MVCCokluDil.CokluDilSiniflari" />
      </namespaces>
    </pages>
...
  </system.web>
//Bu şekilde Web.Config dosyasına yazarsak, bütün projeden bu namespace e ulaşılır.

Bu ek bilgiyide verdikten sonra, konumuza dönelim.

Şimdi “lang.resx” dosyasını varsayılan olarak kabul ettiğimden dolayı, projemizin varsayılan dil lügatını oluşturalım.

Yukarıda gördüğünüz gibi, varsayılan Resource dosyamızı oluşturduk.

Şimdi “Index.aspx” sayfamızda varsayılan olsun olmasın, dil yapımıza göre verilerimiz gösterilsin.

    <table width="60%">
        <tr>
            <td>
                <%: Html.Language("","İsim") %>
            </td>
            <td>
                <%: Html.Language("","Soyisim") %>
            </td>
            <td>
                <%: Html.Language("","Yaş") %>
            </td>
            <td>
                <%: Html.Language("","Meslek") %>
            </td>
        </tr>
    </table>

Ve son olarak “Home(Controller)” isimdeki Controller sınıfımızdaki ActionResult tipinden olan “Index” metoduna “Localization” Attribute’unu yazalım.

        [Localization]
        public ActionResult Index()
        {
            return View();
        }

Projemizi başlattığımız zaman, karşımıza aşağıdaki gibi ekran gelecektir.

Gördüğünüz gibi, varsayılan “lang” Resource dosyamızdaki kelimeler gelmiştir.

Aslında Asp.NET MVC projelerinde çoklu dil desteği nasıl yapılır görmüş olduk.Ancak konumuz daha burada bitmedi.Varsayılan dil yapısında projemizi çalıştırdık.Bu aşamadan sonra, projemizi, İngilizce ve Fransızca dillerinde çalıştıracağız.

Bir sonraki yazımızda bu konuya devam edeceğiz.Şimdilik görüşmek üzere,,
Faydalanmanız dileğiyle,,
İyi Çalışmalar…

Bunlar da hoşunuza gidebilir...

21 Cevaplar

  1. cem dedi ki:

    Merhabalar, paylaşımınız için teşekkürler fakat neden böyle bir şey kullanmadınız.(http://www.yazgelistir.com/makale/asp-net-mvc-localization) Yani bu çok daha kullanışlı ve basit değilmi?

    • Gençay dedi ki:

      Merhaba,
      Bildiğiniz gibi temel sebep bir işin birden fazla yöntemi yani algoritması olmasıdır.Ancak bu durumu yansıtan asıl nazik nokta şudur;
      Verdiğiniz linkteki ve internette diğer yöntemler olsun, Türkçe kaynak olabilirler lakin destek sıkıntısı yaşamaktadır.Yani takıldığınız bir sorun neticesinde sizlere bu konu hakkında pek fazla insan yardımcı olmayacaktır.Çünkü genellikle makale sitelerinde isminin duyulması ve bildiğini hatırlayacak kadar bilgi paylaşması, sanırım çoğu Türk yazar için geçerli bir durum.

      Tabi işinde profesyonel olduğu kadar paylaşımlarında da bir o kadar kendini, anlatıcılığını gösteren, yaptığı emeğin neticesinde insanlara destek veren Türk yazarlarımız mevcuttur.Benim buradaki kastımın hakiki üstadlarımızdan bağımsız olduğunu bilmenizi isterim.

      Gelelim asıl sorunuza, neden bu şekilde bir yöntem kullanmadım?
      Bu konuyu detaylı bir şekilde araştırırken, bir video kaynağından gördüğün ve aynı şekilde yabancı bir kaç makaleyle desteklediğim yöntemdi.Çünkü çoğu yöntem neticesiz ve sorunlarına çözümsüz kaldığında bu yöntem sıkıntısız çalışıyordu.

      Ancak ben hatayı aşamadım diye o yöntemi kötüleyecek değilim.Deneyin ve hangisi sizin yönteminiz ve hangisinde kendinize bişey katabiliyorsunuz siz seçin..

      Saygılarımla

  2. ilker dedi ki:

    Hocam Merhabalar;

    Öncelikle böyle yararlı bir yazı hazırladığınız için çok teşekkür ederim. Hocam şöyle bir sıkıntıyla karşılaştım. Ben mvc 3 kullanıyorum ve bana Partial methodunu override ettirmiyor. bu sıkıntıdan nasıl kurtulabilirim diye soracaktım. şimdiden teşekkür ederim

  3. Mithat CAN dedi ki:

    Hocam Allah razı olsun LocalizationWebFormView class’ında sorun yaşadım sayende bulduğum çözüme emin oldum.

  4. ibrahim dedi ki:

    Hocam öncelikle elinize sağlık teşekkür ederim ama @Html.Language(“”,””) şeklinde Index.cshtml de kullandığım zaman bu hatayı alıyorum Error 2 ‘System.Web.Mvc.HtmlHelper’ does not contain a definition for ‘Language’ and no extension method ‘Language’ accepting a first argument of type ‘System.Web.Mvc.HtmlHelper’ could be found (are you missing a using directive or an assembly reference?) bu sorunu çözmem için ne yapmalıyım.Şimdiden teşekkür ederim.

    • Gençay dedi ki:

      Hatadan anladığım kadarıyla aşağı yukarı netice verebilecek fikrim şöyle olacaktır.Çalıştığınız sınıfta bir namespace hatası söz konusu olabilir.Import(using) etmeniz gereken namespacelerde de eksiklik olabilir.İlgili sayfa kodlarını görmeden tam olarak kesin bir çözüm getirmek oldukça zor oluyor.

      Saygılarımla…

  5. Emre dedi ki:

    hocam emeğinin için çok teşekkürler, anlaşılır ve faydalı bir makale, sormak istediğim bu projeyi veritabanı bağlantılı bir hale nasıl getirebiliriz, bunun için bir anlatımızın olacak mı? şimdiden teşekkür ederim.

    • Gençay dedi ki:

      Veritabanı işlemlerini biliyor musun?

      • Emre dedi ki:

        detaylı olarak bilgim yok, bilgim sadece model aracılığı ile sql sunucudan veri çekmekten ibaret

        • Gençay dedi ki:

          Bu makalede kullanılan Resource dosyaları aslında bir veritabanı işlevi görmektedir. Siz hangi nedenle veritabanında çalışmak istiyorsunuz? Ayriyetten veritabanıyla buradakinden farklı ne yapmak istiyorsunuz?

          • Emre dedi ki:

            dinamik içerik oluşturmak için veritabanı ile çalışmak istiyorum.

          • Gençay dedi ki:

            Dinamik içerik oluşturmak için bu makale içeriği biraz yersiz olur. Sizin aradığınız temel veritabanı işlemleriyle dinamik içerik oluşturmak ve bunları yayınlamaktır. Ha eğer ki dinamik içerik sistemine buradaki çoklu dil özelliğini katacağım derseniz öncelikle ilk şartları öğreniniz, ardından yaptığınız uygulamada çoklu dili nasıl dinamik bir seviyede yapacağınız konusunda size rehberlik edebiliriz.

            Sevgiler.

  6. İsmail dedi ki:

    Verdiğiniz Bilgiler için teşekkür ederim

  7. ahmet dedi ki:

    Bu tarafından bakmamıştım ben güzel oldu böyle

  8. ismail dedi ki:

    Verdiğiniz bilgiler güncel teşekkürler

  9. Emre dedi ki:

    Selam,
    Bu yöntem ile URL’ler de değişiyor mu? Yani,
    İngilizce için; account/login iken;
    Türkçe yapıldığında; hesap/giris oluyor mu?

    Bu işlemi gerçekleştirebilmek için TR ve EN için ayrı iki action oluşturmak haricinde bir yöntem varmıdır?

    • Gençay dedi ki:

      Merhaba Emre,

      Bu yöntem ile direkt olarak URL yapısında bir değişiklik beklemek doğru olmaz. Ama bu yöntemle birlikte bu değişiklikte programlanabilir.

      Ayriyetten birçok yöntemle çoklu dil yapısı oluşturulabilir. Ben yıllar önce bu tarz bir yaklaşımı ortaya sergilemiş bulunmakla beraber bu ve bunun dışında birçok yöntemle çalışmalarımı şekillendirmiş bulunmaktayım. Tabi ki de bu yöntemlere değinebilmek için ayrı bir başlıkta içerik oluşturmak gerekecektir.

      Sevgiler…

  10. Ercan dedi ki:

    Güzel bir makale, emeğinize sağlık.

Bir cevap yazın

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

*

Copy Protected by Chetan's WP-Copyprotect.