過去開發系統多語系大多都是製作多個語系UI,隨著前端Framework越來越強大
通常只要一個UI就可以應付各種語系版面,這次以.NET Framework 4.8 Web MVC 搭配 Resource 進行多語系擴充
參考了以下大大的文章
- https://dotblogs.com.tw/wasichris/2015/06/20/151608
- https://blog.miniasp.com/post/2010/01/05/ASPNET-MVC-Developer-Note-Part-15-Globalization-and-Localization
- https://afana.me/archive/2011/01/14/aspnet-mvc-internationalization.aspx/
Web.config
<?xml version="1.0"?> <configuration> <system.web> <!-- 語系設定 culture: 國家文化特性 (ex: 日期,數字和貨幣格式等) UICulture: 預設語系; 之後會依照用戶設定語系為主(保存在Cookies) --> <globalization culture="auto" uiCulture="auto" enableClientBasedCulture="true" /> </system.web> </configuration>
Global.asax
- 預設語系為客戶端瀏覽器
- 客戶端選擇語系保存在
Request.Cookies["Lang"]
,期限設定1年 - 客戶端更換瀏覽器語,語系需重新選擇
protected void Application_BeginRequest(Object sender, EventArgs e) { HttpCookie cultureCookie = Request.Cookies["Lang"]; // 取得語系檔名稱,若Cookies無設定,預設抓用戶瀏覽器語系 var cultureInfoName = CultureHelper.GetImplementedCulture((cultureCookie == null) ? Request.UserLanguages[0] : cultureCookie.Value); // 設定語系 System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(cultureInfoName); System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(cultureInfoName); }
建立類別庫 GlobalResources
Resource與實作專案目錄對照,可看團隊要用哪種方式來定義目錄結構,這樣比較好查找語系資源檔
EnumHelper.cs
using System; using System.ComponentModel; namespace GlobalResources { public class EnumHelper { /// <summary> /// 透過標籤描述內容反射出Enum數值 /// </summary> /// <typeparam name="T">Enum類別</typeparam> /// <param name="description">Enum描述標籤</param> /// <returns>Enum數值</returns> public static T GetValueFromDescription<T>(string description) { var type = typeof(T); if (!type.IsEnum) throw new InvalidOperationException(); foreach (var field in type.GetFields()) { var attribute = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute; if (attribute != null) { if (attribute.Description == description) return (T)field.GetValue(null); } else { if (field.Name == description) return (T)field.GetValue(null); } } throw new ArgumentException("Not found.", "description"); } /// <summary> /// 是否能透過標籤描述內容反射出Enum數值 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="description"></param> /// <returns></returns> public static bool TryGetValueFromDescription<T>(string description) { bool isOkToGetValueFromDescription = true; try { GetValueFromDescription<T>(description); } catch (Exception) { isOkToGetValueFromDescription = false; } return isOkToGetValueFromDescription; } } }
CultureHelper.cs
增加多語系相關方法
using System.Threading; namespace GlobalResources { /// <summary> /// 語系相關 /// </summary> public static class CultureHelper { /// <summary> /// 取得合法語系名稱 /// </summary> /// <param name="name">語系名稱 (e.g. en-US)</param> public static string GetImplementedCulture(string name) { // give a default culture just in case string cultureName = GetDefaultCulture(); // check if it's implemented if (EnumHelper.TryGetValueFromDescription<Language>(name)) cultureName = name; return cultureName; } /// <summary> /// 取得預設 語系名稱 /// </summary> /// <returns></returns> public static string GetDefaultCulture() { return Language.English.GetDescription(); } /// <summary> /// 取得目前 語系 /// </summary> /// <returns></returns> public static Language GetCurrentLanguage() { var currentCulture = Thread.CurrentThread.CurrentCulture.Name; // get implemented culture name currentCulture = GetImplementedCulture(currentCulture); // get language by implemented culture name return EnumHelper.GetValueFromDescription<Language>(currentCulture); } } }
EnumExtension.cs
列舉(Enum)反射相關方法
using System; using System.ComponentModel; using System.Reflection; namespace GlobalResources { public static class EnumExtension { /// <summary> /// 取得Enum的描述標籤內容 /// </summary> /// <returns></returns> public static string GetDescription(this Enum self) { FieldInfo fi = self.GetType().GetField(self.ToString()); DescriptionAttribute[] attributes = null; if (fi != null) { attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); if (attributes != null && attributes.Length > 0) return attributes[0].Description; } return self.ToString(); } } }
Language.cs
語系列舉,這邊定義英文、中文、日文
using System.ComponentModel; namespace GlobalResources { /// <summary> /// 語系清單 /// </summary> public enum Language { /// <summary> /// 英文 /// </summary> [Description("en-US")] English = 1, /// <summary> /// 繁體中文 /// </summary> [Description("zh-TW")] TraditionalChinese = 2, /// <summary> /// 日文 /// </summary> [Description("ja-JP")] Japanese = 3 } }
LocalizedDescriptionAttribute.cs
擴充列舉(Enum)屬性多語化
using System; using System.ComponentModel; using System.Resources; namespace GlobalResources { /// <summary> /// 屬性描述多語系 /// </summary> public class LocalizedDescriptionAttribute : DescriptionAttribute { private readonly string _resourceKey; private readonly ResourceManager _resource; public LocalizedDescriptionAttribute(string resourceKey, Type resourceType) { _resource = new ResourceManager(resourceType); _resourceKey = resourceKey; } public override string Description { get { string displayName = _resource.GetString(_resourceKey); return string.IsNullOrEmpty(displayName) ? string.Format("[[{0}]]", _resourceKey) : displayName; } } } }
語系資源檔 Resource 管理
建議安裝 Visual Studio 擴充工具 ResXManager進行多語系Resource
管理
使用工具他會自動建置相關對應檔案,只要存檔就會立刻觸發執行T4 Template產生程式碼
- 英文: Resource.resx
- 中文: Resource.zh-TW.resx
- 日文: Resource.ja-JP.resx