ASP.NET MVC i18n Resources 多國語系

過去開發系統多語系大多都是製作多個語系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管理

使用 ResXManager 工具管理語系比較方便
記得要把類別存取改成 Public

使用工具他會自動建置相關對應檔案,只要存檔就會立刻觸發執行T4 Template產生程式碼

  • 英文: Resource.resx
  • 中文: Resource.zh-TW.resx
  • 日文: Resource.ja-JP.resx

程式碼語系替換

View
Enum也可以替換
Controller
訂閱
通知
guest
0 留言
預約回饋
查看所有留言