Makale Özeti

Artık yazılan uygulamalarda lokalizasyon özelliklerinin kullanılması yavaş yavaş kaçınılmaz duruma gelmektedir. Dolayısıyla uygulama içerisinde kullandığımız bazı lokalize etmemiz gereken metinler vs resx dosyalarından okuyarak bu işlemi kolaylıkla yapabilmekteyiz. Ancak bu durum bir veritabanı veya data kaynağından gelecek dataların lokalizasyonunda farklı olacaktır. Bu makalede bu sorunu nasıl çözebileceğimizi inceliyoruz.

Makale

         Merhabalar,

         Artık yazılan uygulamalarda lokalizasyon özelliklerinin kullanılması yavaş yavaş kaçınılmaz duruma gelmektedir. Dolayısıyla uygulama içerisinde kullandığımız bazı lokalize etmemiz gereken metinler vs resx dosyalarından okuyarak bu işlemi kolaylıkla yapabilmekteyiz. Ancak bu durum bir veritabanı veya data kaynağından gelecek dataların lokalizasyonunda farklı olacaktır. Günümüzdeki duruma bir örnek üzerinden baktığımızda genellikle uygulanan yöntem aşağıdaki gibi bir dizayn ile bu sorunu çözmektir.


         Dizayna baktığımızda bir nesneyin bir özelliğini farklı dillerde kullanabilmek için farklı bir nesne oluşturuyoruz ve bu nesneyi ana nesne içerisine list olarak ekliyoruz. Ancak uygulamamızda bu durumda birden fazla nesne olması durumunda nesne sayımız artacak ve gereksiz birçok kod yazmak zorunda kalacağız. Tüm bunların yanısıra bir orm aracı kullanıyorsak birçok mapping yapmamız gerekecek.

         Bu yöntemin yerine nesnelerin çoklu dil desteği olan özelliklerine yönelik bir geliştirme yapılabilir. Bunun modelini aşağıdaki gibi kurduğumuzu düşünelim.


         Gördüğünüz gibi bu yöntem hem daha anlaşılabilir hem de daha genel bir tasarım içermektedir. Çoklu dil desteği olan birçok nesnenin birçok özelliği için yukarıda gördüğünüz MultilanguageProperty nesnesini kullanabilirsiniz.

         Şimdi isterseniz bu gibi bir nesneyi nasıl yazacağımızı inceleyelim.

          Öncelikle MultilanguageProperty nesnemizin içerisinde kullanacağımız Language class'ını oluşturalım.

    public class Language
    {
        public enum ApplicationLanguage
        {
            Turkish,
            English,
        }
        public static ApplicationLanguage GetDefaultLanguage()
        {
            return ApplicationLanguage.Turkish;
        }
    }
 
Gördüğünüz gibi Language nesnemizi oluştururken içerisinde bir enum'da kullanacağımız dilleri tanımlıyoruz ve uygulamamızda kullanılacak varsayılan dili tanımlıyoruz. Burada varsayılan dili web uygulamalarında bir session değişkeninden, windows uygulamalarında ise static bir değişkenden okuyarak uygulamanızın kullanıcı basında varsayılan dil kullanmasını sağlayabilirsiniz.

Artık MultilanguageProperty nesnemizi kodlamaya başlayabiliriz.
    public class MultilanguageProperty<T>:IEnumerable<KeyValuePair<Language.ApplicationLanguage,T>>
    {
        public IDictionary<Language.ApplicationLanguage, T> Values { get; set; }
        public MultilanguageProperty()
        {
            Values = new Dictionary<Language.ApplicationLanguage, T>();
        }
Öncelikle nesnemizi string, int, datetime veya herhangi başka bir tipteki özelliklerin yerine kullanabilmemiz için generic bir nesne olarak tanımlıyoruz. Ve kullanıcıların bu property içerisindeki birçok değeri almak için değerler arasında dönebilmesini sağlamak amacıyla IEnumerable interface'ini implement ediyoruz.

Ayrıca nesnemizde IDictionary tipinden values adında bir property tanımlıyoruz. IDictionary generic tipine baktığımızda bu nesnenin key olarak ApplicationLanguage enum'unu ve karşılığında value olarak tanıttığımız generic T tipini taşıdığını görüyoruz. Bu property bizim çokludil değerlerimizi saklayacak olan property'dir. Ve nesnemizin yapıcı metodu içerisinde bu property'e Dictionary nesnesinin yeni bir instance'ını alarak atıyoruz.

Şimdi Values property'sinde sakladığımız değerlere nasıl ulaşabileceğimizi inceleyelim.
        public T GetValue(Language.ApplicationLanguage language)
        {
            return (T)Values[language];
        }
        public T GetValue()
        {
            Language.ApplicationLanguage applicationLanguage = Language.GetDefaultLanguage();
            return (T)Values[applicationLanguage];
        }
        public T this[Language.ApplicationLanguage l]
        {
            get
            {
                return Values[l];
            }
            set
            {
                Values[l] = value;
            }
        }
 
Sakladığımız değerlere ulaşmak için GetValue adındaki metodumuzu kullanıyoruz. Gördüğünüz gibi değerlere ulaştığımızda metodların bize geridönüş tipi generic tiptendir. GetValue metodunun iki tane overload'ı vardır. Bunlardan biri belirtilen dildeki değeri vermek için diğeri ise biraz önce Language class'ında implemantasyonunu yaptığımız varsayılan dildeki değeri döndürmesi içindir. Ayrıca indexer kullanarak kullanıcıların direk olarak property üzerinden indexer kullanarak istedikleri dildeki değeri almalarını sağlayabiliyoruz.

Şimdi ise kodumuzu kullanacak kişiler için farklı bir kolaylık sağlayacağız. Implicit operatörler tanımlayarak kullanıcıların bir tip çevirme yapmadan nesnemizi kullanabilmeleri için aşağıdaki kodu nesnemize ekliyoruz.
        public static implicit operator T(MultilanguageProperty<T> instance)
        {
            if (instance == null)
            {
                return default(T);
            }
            return instance.GetValue();
        }
        public static implicit operator List<KeyValuePair<Language.ApplicationLanguage,T>>(MultilanguageProperty<T> instance)
        {
            if (instance == null)
            {
                return null;
            }
            return instance.Values.ToList();
        }
 
Gördüğünüz gibi ilk metodda nesnemizin tanıttığımız generic T tipindeki bir değişkene direkt atanabilmesi için implicit conversation operatörümüzü yazıyoruz ve ikinci metodda ise nesnemizin bir List değişkenine atanabilmesi için gerekli kodumuzu yazıyoruz. Bu kodların kullanımda nasıl bir kolaylık sağladığını makalemizin sonunda göreceğiz.
 
        #region IEnumerable<KeyValuePair<ApplicationLanguage,T>> Members
 
        public IEnumerator<KeyValuePair<Language.ApplicationLanguage, T>> GetEnumerator()
        {
            return Values.GetEnumerator();
        }
 
        #endregion
 
        #region IEnumerable Members
 
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return Values.GetEnumerator();
        }
 
        #endregion
    }
}
 
Ve en son olarak kullanıcıların bu property içinde dönebilmeleri için implement ettiğimiz IEnumerable inteface'inin gerektirdiği metodları yazıyoruz ve nesnemizi kodlamayı bitiriyoruz.

Şimdi isterseniz bir örnek yaparak nesnemizin kullanımını inceleyelim. Örneğimizde kullanacağımız test nesnemizi aşağıdaki gibi oluşturuyoruz.
    public class TestObject
    {
        public TestObject()
        {
            this.Name = new MultilanguageProperty<string>();
        }
        public int Id { get; set; }
        public string UniversalCode { get; set; }
        public MultilanguageProperty<string> Name { get; set; }
    }
 
Gördüğünüz gibi test nesnemiz üzerinde Name property'sinin farklı diller için  farklı değerleri barındırmasını istediğimizden MultilanguageProperty tipinden oluşturduk ve generic tip olarak string'i belirledik.

Şimdi örnek uygulamamızı yazıyoruz.
    class Program
    {
        public static List<TestObject> GetSampleDataForTestObject()
        {
            List<TestObject> listTestObject = new List<TestObject>();
 
            TestObject testObject1 = new TestObject { Id = 1, UniversalCode = "TR" };
            testObject1.Name.Values.Add(Language.ApplicationLanguage.Turkish, "Türkiye");
            testObject1.Name.Values.Add(Language.ApplicationLanguage.English, "Turkey");
 
            TestObject testObject2 = new TestObject { Id = 2, UniversalCode = "USA" };
            testObject2.Name.Values.Add(Language.ApplicationLanguage.Turkish, "Amerika Birleşik Devletleri");
            testObject2.Name.Values.Add(Language.ApplicationLanguage.English, "United States Of America");
 
            TestObject testObject3 = new TestObject { Id = 3, UniversalCode = "FR" };
            testObject3.Name.Values.Add(Language.ApplicationLanguage.Turkish, "Fransa");
            testObject3.Name.Values.Add(Language.ApplicationLanguage.English, "France");
 
            listTestObject.Add(testObject1);
            listTestObject.Add(testObject2);
            listTestObject.Add(testObject3);
 
            return listTestObject;
        }
        static void Main(string[] args)
        {
 
            List<TestObject> listTestObject = GetSampleDataForTestObject();
            WriteAllList(listTestObject);
            Console.WriteLine();
            WriteForSpecificLanguageByIndexer(listTestObject);
            Console.WriteLine();
            WriteForSpecificLanguageByGetValue(listTestObject);
            Console.WriteLine();
            WriteForDefaultLanguageByIndexer(listTestObject);
            Console.WriteLine();
            WriteForDefaultLanguageByGetValue(listTestObject);
            Console.WriteLine();
            WriteForDefaultLanguageByImplicit(listTestObject);
            Console.Read();
        }
        private static void WriteAllList(List<TestObject> listTestObject)
        {
            Console.WriteLine("Writing All List");
            foreach (TestObject to in listTestObject)
            {
                Console.WriteLine("Id:" + to.Id + " Code:" + to.UniversalCode);
                foreach (KeyValuePair<Language.ApplicationLanguage, string> kvp in to.Name)
                {
                    Console.WriteLine("     Language:" + kvp.Key.ToString() + " Name:" + kvp.Value.ToString());
                }
            }
        }
        private static void WriteForSpecificLanguageByIndexer(List<TestObject> listTestObject)
        {
            Console.WriteLine("Writing Specific Language Value By Indexer");
            string s = listTestObject[0].Name[Language.ApplicationLanguage.English];
            Console.WriteLine(s);
        }
        private static void WriteForSpecificLanguageByGetValue(List<TestObject> listTestObject)
        {
            Console.WriteLine("Writing Specific Language Value By GetValue Method");
            string s = listTestObject[0].Name.GetValue(Language.ApplicationLanguage.English);
            Console.WriteLine(s);
        }
        private static void WriteForDefaultLanguageByIndexer(List<TestObject> listTestObject)
        {
            Console.WriteLine("Writing Default Language Value By Indexer");
            string s = listTestObject[1].Name[Language.GetDefaultLanguage()];
            Console.WriteLine(s);
        }
        private static void WriteForDefaultLanguageByGetValue(List<TestObject> listTestObject)
        {
            Console.WriteLine("Writing Default Language Value By GetValue Method");
            string s = listTestObject[1].Name.GetValue();
            Console.WriteLine(s);
        }
        private static void WriteForDefaultLanguageByImplicit(List<TestObject> listTestObject)
        {
            Console.WriteLine("Writing Default Language Value By Implicit Operator");
            string s = listTestObject[2].Name;
            Console.WriteLine(s);
        }
 
    }
 
Kodumuzu inceleyecek olursak;

GetSampleDataForTestObject metodunda test nesnemizden birçok örnek yaratarak bir list'e atıyoruz aynı zamanda test nesnemizdeki Name özelliğine çoklu dil değerlerinin nasıl atanabileceğinide görüyoruz. Gerçek uygulamalarınızda, bilhassa orm araçları kullanıyorsanız bu nesne doldurma işlemini birkaç satır kodla veritabanından yapabilirsiniz.

Main'de ise gördüğünüz gibi listemize çeşitli metodlar kullanarak erişiyoruz ve Multilanguage Name property'sindeki değerleri farklı diller için farklı yöntemlere kullanarak okuyoruz.

WriteAllList metodu nesne içerisindeki tüm dil değerlerine nasıl erişebileceğinizi göstermektedir. Bu metod içerisindeki kodumuzda Name property'sinin değerleri içerisinde dönerken Multilanguage nesnemize implement ettiğimiz IEnumerable interface'inden faydalanıyoruz.

WriteForSpecificLanguageByIndexer metodunda ise Name property'mizin indexer'ına değerini almak istediğimiz dil tipini vererek değeri okuyoruz.

WriteForSpecificLanguageByGetValue metodunda ise Name property'mizin GetValue metoduna  değerini almak istediğimiz dil tipini vererek değeri okuyoruz.

WriteDefaultLanguageByIndexer metodunda ise Name property'mizin indexer'ına varsayılan dil değerini okuyarak veriyoruz ve değeri okuyoruz.

WriteDefaultLanguageByGetValue metodunda ise  Name property'mizin GetValue metoduna parametre vermiyoruz ve varsayılan dil değerini okuyoruz.

WriteForDefaultLanguageByImplicit metodunda ise gördüğünüz gibi nesnemizin Name property'sini direkt olarak bir string değişkene atayabiliyor ve varsayılan dil değerini bu şekilde kolaylıkla okuyabiliyoruz.

Uygulamamızı çalıştırdığımızda örnek bir ekran görüntüsü aşağıdaki gibi olacaktır.



Umarım faydalı olmuştur.
Tamer ÖZ
oztamer@hotmail.com
Ornek Kodlar