Multilingual ResourceManager dgn Auto Translate (Part I)
Dalam pengembangan aplikasi, multi language merupakan point plus dalam aplikasi. Dalam .NET sendiri fitur tersebut sudah ada yaitu menggunakan ResourceManager, namun dalam prakteknya setiap bahasa kita membutuhkan tenaga kerja yg menguasai bahasa terkait, dan juga harus mencompile dan mendeploy kembali. Tentunya ujung2nya adalah waktu dan cost.
Source code dapat didownload di sini
Dalam postingan kali ini saya akan memberikan contoh membuat Custom ResourceManager dgn implementasi service Translator Microsoft. Contoh screennya adalah sbb :
English

Indonesian

Japanese

Korean
Cukup menarik bukan ?
Sebelum menyentuh lebih dalam, sebaiknya lihat dahulu link dibawah ini :
- ResourceManager, detail penggunanaannya dapat dilihat pada http://msdn.microsoft.com/en-us/library/aa905797.aspx.
- Contoh Implementasi Translator telah posting oleh teman kita Yugie, linknya adalah http://students.netindonesia.net/blogs/yugie/archive/2011/03/02/mudah-translator-dengan-bing-web-service.aspx
- Dan juga untuk detail API Translator dapat dilihat pada http://api.microsofttranslator.com/
Jika sudah membaca link diatas, oke kita lanjut....
Saya membuat enum LanguageType dan Extensionnya, tujuannya untuk mapping dengan API Translator dan juga mapping CultureInfonya
Code Snippet
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Globalization;
- namespace MyResourceManager
- {
- public enum LanguageType
- {
- AutoDetect,
- Arabic,
- Bulgarian,
- Catalan,
- Chinese_Simplified,
- Chinese_Traditional,
- Czech,
- Danish,
- Dutch,
- English,
- Estonian,
- Finnish,
- French,
- German,
- Greek,
- Haitian_Creole,
- Hebrew,
- Hungarian,
- Indonesian,
- Italian,
- Japanese,
- Korean,
- Latvian,
- Lithuanian,
- Norwegian,
- Polish,
- Portuguese,
- Romanian,
- Russian,
- Slovak,
- Slovenian,
- Spanish,
- Swedish,
- Thai,
- Turkish,
- Ukrainian,
- Vietnamese
- }
- public static class LanguageTypeExtension
- {
- public static string GetLanguageCode(this LanguageType value)
- {
- switch (value)
- {
- case LanguageType.AutoDetect:
- return "";
- case LanguageType.Arabic:
- return "ar";
- case LanguageType.Bulgarian:
- return "bg";
- case LanguageType.Catalan:
- return "ca";
- case LanguageType.Chinese_Simplified:
- return "zh-CHS";
- case LanguageType.Chinese_Traditional:
- return "zh-CHT";
- case LanguageType.Czech:
- return "cs";
- case LanguageType.Danish:
- return "da";
- case LanguageType.Dutch:
- return "nl";
- case LanguageType.English:
- return "en";
- case LanguageType.Estonian:
- return "et";
- case LanguageType.Finnish:
- return "fi";
- case LanguageType.French:
- return "fr";
- case LanguageType.German:
- return "de";
- case LanguageType.Greek:
- return "el";
- case LanguageType.Haitian_Creole:
- return "ht";
- case LanguageType.Hebrew:
- return "he";
- case LanguageType.Hungarian:
- return "hu";
- case LanguageType.Indonesian:
- return "id";
- case LanguageType.Italian:
- return "it";
- case LanguageType.Japanese:
- return "ja";
- case LanguageType.Korean:
- return "ko";
- case LanguageType.Latvian:
- return "lv";
- case LanguageType.Lithuanian:
- return "lt";
- case LanguageType.Norwegian:
- return "no";
- case LanguageType.Polish:
- return "pl";
- case LanguageType.Portuguese:
- return "pt";
- case LanguageType.Romanian:
- return "ro";
- case LanguageType.Russian:
- return "ru";
- case LanguageType.Slovak:
- return "sk";
- case LanguageType.Slovenian:
- return "sl";
- case LanguageType.Spanish:
- return "es";
- case LanguageType.Swedish:
- return "sv";
- case LanguageType.Thai:
- return "th";
- case LanguageType.Turkish:
- return "tr";
- case LanguageType.Ukrainian:
- return "uk";
- case LanguageType.Vietnamese:
- return "vi";
- default:
- return "";
- }
- }
- }
- public static class CultureInfoExtension
- {
- public static LanguageType GetLanguageType(this CultureInfo value)
- {
- string name = value.Name.ToLower();
- LanguageType result = LanguageType.English;
- switch (name)
- {
- case "af":
- case "af-za":
- result = LanguageType.English;
- break;
- case "sq":
- case "sq-al":
- break;
- case "ar":
- case "ar-dz":
- case "ar-bh":
- case "ar-eg":
- case "ar-iq":
- case "ar-jo":
- case "ar-kw":
- case "ar-lb":
- case "ar-ly":
- case "ar-ma":
- case "ar-om":
- case "ar-qa":
- case "ar-sa":
- case "ar-sy":
- case "ar-tn":
- case "ar-ae":
- case "ar-ye":
- result = LanguageType.Arabic;
- break;
- case "hy":
- case "hy-am":
- break;
- case "az":
- case "az-az-cyrl":
- case "az-az-latn":
- break;
- case "eu":
- case "eu-es":
- break;
- case "be":
- case "be-by":
- break;
- case "bg":
- case "bg-bg":
- result = LanguageType.Bulgarian;
- break;
- case "ca":
- case "ca-es":
- result = LanguageType.Catalan;
- break;
- case "zh-hk":
- case "zh-mo":
- case "zh-cn":
- case "zh-sg":
- case "zh-tw":
- case "zh-chs":
- result = LanguageType.Chinese_Simplified;
- break;
- case "zh-cht":
- result = LanguageType.Chinese_Traditional;
- break;
- case "hr":
- case "hr-hr":
- break;
- case "cs":
- case "cs-cz":
- result = LanguageType.Czech;
- break;
- case "da":
- case "da-dk":
- result = LanguageType.Danish;
- break;
- case "div":
- case "div-mv":
- break;
- case "nl":
- case "nl-be":
- case "nl-nl":
- result = LanguageType.Dutch;
- break;
- case "en":
- case "en-au":
- case "en-bz":
- case "en-ca":
- case "en-cb":
- case "en-ie":
- case "en-jm":
- case "en-nz":
- case "en-ph":
- case "en-za":
- case "en-tt":
- case "en-gb":
- case "en-us":
- case "en-zw":
- result = LanguageType.English;
- break;
- case "et":
- case "et-ee":
- result = LanguageType.Estonian;
- break;
- case "fo":
- case "fo-fo":
- break;
- case "fa":
- case "fa-ir":
- break;
- case "fi":
- case "fi-fi":
- result = LanguageType.Finnish;
- break;
- case "fr":
- case "fr-be":
- case "fr-ca":
- case "fr-fr":
- case "fr-lu":
- case "fr-mc":
- case "fr-ch":
- result = LanguageType.French;
- break;
- case "gl":
- case "gl-es":
- break;
- case "ka":
- case "ka-ge":
- break;
- case "de":
- case "de-at":
- case "de-de":
- case "de-li":
- case "de-lu":
- case "de-ch":
- result = LanguageType.German;
- break;
- case "el":
- case "el-gr":
- result = LanguageType.Greek;
- break;
- case "gu":
- case "gu-in":
- break;
- case "he":
- case "he-il":
- result = LanguageType.Hebrew;
- break;
- case "hi":
- case "hi-in":
- break;
- case "hu":
- case "hu-hu":
- result = LanguageType.Hungarian;
- break;
- case "is":
- case "is-is":
- break;
- case "id":
- case "id-id":
- result = LanguageType.Indonesian;
- break;
- case "it":
- case "it-it":
- case "it-ch":
- result = LanguageType.Italian;
- break;
- case "ja":
- case "ja-jp":
- result = LanguageType.Japanese;
- break;
- case "kn":
- case "kn-in":
- break;
- case "kk":
- case "kk-kz":
- break;
- case "kok":
- case "kok-in":
- break;
- case "ko":
- case "ko-kr":
- result = LanguageType.Korean;
- break;
- case "ky":
- case "ky-kz":
- break;
- case "lv":
- case "lv-lv":
- result = LanguageType.Latvian;
- break;
- case "lt":
- case "lt-lt":
- result = LanguageType.Lithuanian;
- break;
- case "mk":
- case "mk-mk":
- break;
- case "ms":
- case "ms-bn":
- case "ms-my":
- break;
- case "mr":
- case "mr-in":
- break;
- case "mn":
- case "mn-mn":
- break;
- case "no":
- result = LanguageType.Norwegian;
- break;
-
- case "nb-no":
- break;
- case "nn-no":
- break;
- case "pl": break;
- case "pl-pl":
- result = LanguageType.Polish;
- break;
- case "pt":
- case "pt-br":
- case "pt-pt":
- result = LanguageType.Portuguese;
- break;
- case "pa":
- case "pa-in":
- break;
- case "ro":
- case "ro-ro":
- result = LanguageType.Romanian;
- break;
- case "ru":
- case "ru-ru":
- result = LanguageType.Russian;
- break;
- case "sa":
- case "sa-in":
- break;
- case "sr-sp-cyrl":
- case "sr-sp-latn":
- break;
- case "sk":
- case "sk-sk":
- result = LanguageType.Slovak;
- break;
- case "sl":
- case "sl-si":
- result = LanguageType.Slovenian;
- break;
- case "es":
- case "es-ar":
- case "es-bo":
- case "es-cl":
- case "es-co":
- case "es-cr":
- case "es-do":
- case "es-ec":
- case "es-sv":
- case "es-gt":
- case "es-hn":
- case "es-mx":
- case "es-ni":
- case "es-pa":
- case "es-py":
- case "es-pe":
- case "es-pr":
- case "es-es":
- case "es-uy":
- case "es-ve":
- result = LanguageType.Spanish;
- break;
- case "sw":
- case "sw-ke":
- break;
- case "sv":
- case "sv-fi":
- case "sv-se":
- result = LanguageType.Swedish;
- break;
- case "syr":
- case "syr-sy":
- break;
- case "ta":
- case "ta-in":
- break;
- case "tt":
- case "tt-ru":
- break;
- case "te":
- case "te-in":
- break;
- case "th":
- case "th-th":
- result = LanguageType.Thai;
- break;
- case "tr":
- case "tr-tr":
- result = LanguageType.Turkish;
- break;
- case "uk":
- case "uk-ua":
- result = LanguageType.Ukrainian;
- break;
- case "ur":
- case "ur-pk":
- break;
- case "uz":
- case "uz-uz-cyrl":
- case "uz-uz-latn":
- break;
- case "vi":
- case "vi-vn":
- result = LanguageType.Vietnamese;
- break;
- default:
- result = LanguageType.AutoDetect;
- break;
- }
- return result;
- }
- }
-
- }
Kemudian saya membuat class Utility untuk memudahkan memanggil fungsi API Translatornya
Code Snippet
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Configuration;
- using System.Globalization;
- namespace MyResourceManager
- {
- public static class Utils
- {
- /// <summary>
- /// Application ID untuk kebutuhan Service API Translator, APP ID dapat dibuat dari link http://www.bing.com/developers/appids.aspx
- /// </summary>
- private static string _AppId = ConfigurationManager.AppSettings["MSTranslatorAppId"].ToString();
- /// <summary>
- /// default kode bahasa ResourceKey valuenya dapat didapat dari LanguageTypeExtension.GetLanguageCode()
- /// </summary>
- private static string _ResourceKeyLanguage = ConfigurationManager.AppSettings["ResourceKeyLanguage"].ToString();
- public static string ResourceKeyLanguage
- {
- get
- {
- return _ResourceKeyLanguage;
- }
- }
- public static string AppId
- {
- get
- {
- return _AppId;
- }
- }
- public static string Translate(string resourceKey, LanguageType from, LanguageType to)
- {
- if (from.Equals(to)) // Skip jika translate ke bahasa yg sama;
- return resourceKey;
- string result = string.Empty;
- using (MSTranslator.LanguageServiceClient client = new MSTranslator.LanguageServiceClient())
- {
- result = client.Translate(_AppId, resourceKey, from.GetLanguageCode(), to.GetLanguageCode(), "text/plain", "general");
- }
- return result;
- }
- public static string Translate(string resourceKey, CultureInfo from, CultureInfo to)
- {
- LanguageType fromLang = from.GetLanguageType();
- LanguageType toLang = to.GetLanguageType();
- return Translate(resourceKey, fromLang, toLang);
- }
- }
- }
Kemudian implementasi ResourceManagernya
(disini saya tidak menjelaskan lebih detail, beberapa code saya ambil dari contoh http://msdn.microsoft.com/en-us/library/aa905797.aspx.
Code Snippet
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Resources;
- using System.Globalization;
- using System.Web.Compilation;
- using System.Collections.Specialized;
- namespace MyResourceManager
- {
- public class DisposableBaseType : IDisposable
- {
- private bool m_Disposed;
- protected bool Disposed
- {
- get
- {
- lock (this)
- {
- return m_Disposed;
- }
- }
- }
- #region IDisposable Members
- public void Dispose()
- {
- lock (this)
- {
- if (m_Disposed == false)
- {
- Cleanup();
- m_Disposed = true;
- GC.SuppressFinalize(this);
- }
- }
- }
- #endregion
- protected virtual void Cleanup()
- {
- // override to provide cleanup
- }
- ~DisposableBaseType()
- {
- Cleanup();
- }
- }
- public class MyResourceReader : DisposableBaseType, IResourceReader, IEnumerable<KeyValuePair<string, object>>
- {
- private ListDictionary _listDic;
- public MyResourceReader(ListDictionary dic)
- {
- this._listDic = dic;
- }
- #region IResourceReader Members
- public void Close()
- {
- this.Dispose();
- }
- public System.Collections.IDictionaryEnumerator GetEnumerator()
- {
- if (Disposed)
- throw new ObjectDisposedException("MyResourceReader object is already disposed.");
- return this._listDic.GetEnumerator();
- }
- #endregion
- #region IEnumerable Members
- System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
- {
- if (Disposed)
- throw new ObjectDisposedException("MyResourceReader object is already disposed.");
- return this._listDic.GetEnumerator();
- }
- #endregion
- protected override void Cleanup()
- {
- try
- {
- this._listDic = null;
- }
- finally
- {
- base.Cleanup();
- }
- }
- #region IEnumerable<KeyValuePair<string,object>> Members
- IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
- {
- if (Disposed)
- throw new ObjectDisposedException("MyResourceReader object is already disposed.");
- return this._listDic.GetEnumerator() as IEnumerator<KeyValuePair<string, object>>;
- }
- #endregion
- }
- public class MyResourceProvider : DisposableBaseType, IResourceProvider
- {
- private string classKey;
- private Dictionary<string, Dictionary<string, string>> resourceCache = new Dictionary<string, Dictionary<string, string>>();
- private CultureInfo resourceKeyLanguage = new CultureInfo(Utils.ResourceKeyLanguage);
- public MyResourceProvider(string classKey)
- {
- this.classKey = classKey;
- }
- #region IResourceProvider Members
- public object GetObject(string resourceKey, CultureInfo culture)
- {
- if (Disposed)
- throw new ObjectDisposedException("MyResourceProvider object is already disposed.");
- if (string.IsNullOrEmpty(resourceKey))
- throw new ArgumentNullException("resourceKey");
- if (culture == null)
- culture = CultureInfo.CurrentUICulture;
- if (string.IsNullOrEmpty(culture.Name))
- culture = System.Globalization.CultureInfo.CurrentUICulture;
- string resourceValue = null;
- Dictionary<string, string> resCacheByCulture = null;
- if (resourceCache.ContainsKey(culture.Name))
- {
- resCacheByCulture = resourceCache[culture.Name];
- if (resCacheByCulture.ContainsKey(resourceKey))
- resourceValue = resCacheByCulture[resourceKey];
- }
- if (resourceValue == null)
- {
- resourceValue = Utils.Translate(resourceKey, resourceKeyLanguage, culture);
- // add this result to the cache
- // find the dictionary for this culture
- // add this key/value pair to the inner dictionary
- lock (this)
- {
- if (resCacheByCulture == null)
- {
- resCacheByCulture = new Dictionary<string, string>();
- resourceCache.Add(culture.Name, resCacheByCulture);
- }
- resCacheByCulture.Add(resourceKey, resourceValue);
- }
- }
- return resourceValue;
- }
- public IResourceReader ResourceReader
- {
- get
- {
- if (Disposed)
- throw new ObjectDisposedException("MyResourceProvider object is already disposed.");
- // this is required for implicit resources
- // this is also used for the expression editor sheet
- ListDictionary resourceDictionary = new ListDictionary();
- return new MyResourceReader(resourceDictionary);
- }
- }
- #endregion
- protected override void Cleanup()
- {
- try
- {
- this.resourceCache.Clear();
- }
- finally
- {
- base.Cleanup();
- }
- }
- }
- public class MyResourceProviderFactory : ResourceProviderFactory
- {
- public override IResourceProvider CreateGlobalResourceProvider(string classKey)
- {
- return new MyResourceProvider(classKey);
- }
- public override IResourceProvider CreateLocalResourceProvider(string virtualPath)
- {
- string classKey = virtualPath;
- if (!string.IsNullOrEmpty(virtualPath))
- {
- virtualPath = virtualPath.Remove(0, 1);
- classKey = virtualPath.Remove(0, virtualPath.IndexOf('/') + 1);
- }
- return new MyResourceProvider(classKey);
- }
- }
- }
Kemudian tambahan web.config nya
Code Snippet
- .
- .
- .
- .
- <appSettings>
- <!-- default kode bahasa ResourceKey valuenya dapat didapat dari LanguageTypeExtension.GetLanguageCode() -->
- <add key="ResourceKeyLanguage" value="en" />
- <!-- Application ID untuk kebutuhan Service API Translator, APP ID dapat dibuat dari link http://www.bing.com/developers/appids.aspx -->
- <add key="MSTranslatorAppId" value="2FF28BD209C6653904A8307F0BA50E1D97E862B0"/>
- </appSettings>
- <system.web>
- <!-- uiCulture untuk default tampilan bahsa di UInya -->
- <globalization culture="en-US" uiCulture="en-US" resourceProviderFactoryType="MyResourceManager.MyResourceProviderFactory, MyResourceManager" />
- .
- .
- .
- .
Pada contoh ini ada beberapa configurasi yg perlu kita ketahui
- Pada AppSetting ResourceKeyLanguage ini digunakan untuk default bahasa ResourceKey nya. Setiap kita membuat resourceKey, musti dalam bahasa yg diset pada ResourceKeyLanguage.
- Pada AppSetting MSTranslatorAppId, Application ID harus diset untuk mendapatkannya harus register terlebih dahulu pada link http://www.bing.com/developers/appids.aspx
Berhubung waktu terbatas, sepertinya cukup untuk sekian dulu... 