2015年8月4日 星期二

[MVC] 多國語系開發(預設或使用Cookie紀錄設定)

 MVC


目的


l  實作一Website可由瀏覽器預設語系來顯示對應語系之文字。
l  加入可由使用者選擇語系並存入cookie作為未來預設之語系。

環境

l   Windows 7 Enterprise
l   Visual Studio 2013 update 5


期望結果


1. 由瀏覽器預設之語言顯示網頁文字對應之語系


l   英文 (en-US)



l   繁體中文(zh-TW)




2. 提供使用者可手動指定語系

 

選擇簡體中文後


實作1


加入各語系對應之文字資源檔

l   加入「ASP.NET資料夾」: App_GlobalResources

此為非必要之步驟,我們也可以將資源檔放到任何專案的位置,差別只是預設的資源檔屬性:「自訂工具」會不同。

以下是Resx的自訂工具選項:

Resx 自訂工具
執行工具後的差別
ResXFileCodeGenerator
類別:internal class
建置動作:內嵌資源
GlobalResourceProxyGenerator
類別:internal class
建置動作:內容
PublicResXFileCodeGenerator
類別:public class
建置動作:內嵌資源



PS.
可參考MSDN建置動作屬性


l   在底下加入各語系之資源檔,但同時必須新增一對應之預設資源檔;
以此例來說,

Resource.resx
預設 (主要檔案)
Resource.en-US.resx
英文
Resource.zh-CN.resx
簡體中文
Resource.zh-TW.resx
繁體中文


在此修改主要的Resouce.resx的屬性為:
n   自訂工具:PublicResXFileCodeGenerator
n   建置動作:內嵌資源
n   複製到輸出目錄:不要複製
n   自訂工具命名空間:MultiLangResx.Resources (可自行調整,所有的.resx都須設定)
 

其他的Resouce.{語系}.resx的屬性為:
n   自訂工具:空值
n   建置動作:內嵌資源
n   複製到輸出目錄:不要複製
n   自訂工具命名空間:MultiLangResx.Resources (可自行調整,所有的.resx都須設定)


l   使用Resx manager進行相關資源檔的資料管理。



在網站取得Http Request時,同時得知該封包的喜好語系

Global.asax 定義 BeginRequest 這個事件的細節。

protected void Application_BeginRequest(object sender, EventArgs e)
{
           try
            {
                    //取得用戶端語言喜好設定(已排序的字串陣列)
                    var userLanguages = Request.UserLanguages;

                    if (userLanguages.Length > 0)
                    {
                        try
                        {
                            ci = new CultureInfo(userLanguages[0]);
                        }
                        catch (CultureNotFoundException)
                        {
                            ci = CultureInfo.InvariantCulture;
                        }
                    }
                    else
                    {
                        ci = CultureInfo.InvariantCulture;
                    }
                }
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                System.Threading.Thread.CurrentThread.CurrentUICulture = ci;
                System.Threading.Thread.CurrentThread.CurrentCulture = ci;
            }

}


調整ViewModelDataAnnotations


public class UserVm
    {
        [Required(ErrorMessageResourceName = "Name_ValidationErrMsg", ErrorMessageResourceType = typeof(MultiLangResx.Resources.Resource))]
        [Display(Name ="Name", ResourceType=typeof(MultiLangResx.Resources.Resource))]
        public String Name { get;set;}

        [Required(ErrorMessageResourceName = "Sex_ValidationErrMsg", ErrorMessageResourceType = typeof(MultiLangResx.Resources.Resource))]
        [Display(Name = "Sex", ResourceType = typeof(MultiLangResx.Resources.Resource))]
        public SexEnum Sex { get; set; }


        [Display(Name = "Address", ResourceType = typeof(MultiLangResx.Resources.Resource))]
        public String Address { get; set; }
    }

其中
l   Name : 在資源檔的名稱
l   ResourceType = typeof([自訂Name space].[預設資源檔名稱]

調整後,ViewModel便可和資源檔產生關聯,並在頁面上帶出對應之語系。
另外如果需要在View直接帶出對應語系的資源檔內容,可直接加入如下程式碼:

<td>@MultiLangResx.Resources.Resource.UserName</td>
<td>@MultiLangResx.Resources.Resource.IsChecked</td>

或是參考此篇文章的做法:String Extensions for Resx


測試

進行到這邊,我們已經可以由調整瀏覽器預設的語系,來瀏覽對應的多國語系網頁內容囉。

 




實作2


這邊要開始建立以使用者選擇喜好語系,並存入Cookie的方式,來偵測(加強)顯示的語系。
 
CultureHelper

l   預設網站支援的語系
PS.
也可改將這些資料維護在資料庫。

public enum CultureEnum
{
        /// <summary>
        /// 中文(繁體)
        /// </summary>
        [Description("zh-TW")]
        zhTW =0,

        /// <summary>
        /// 中文(简体)
        /// </summary>
        [Description("zh-CN")]
        zhCN,

        /// <summary>
        /// 英文
        /// </summary>
        [Description("en-US")]
        enUS
}

同時寫一支EnumExtensions,方便讀出AttributeDescription


l   CultureHelper

/// <summary>
    /// CultureHelper for 多國語言開發
    /// </summary>
    public class CultureHelper
    {
        /// <summary>
        /// Culture object declaration
        /// </summary>
        public class Culture
        {
            public int Key {get;set;}
            public String Value {get;set;}
        }

        /// <summary>
        /// 取得已對應的語言系(如果沒有對應的則以預設為主)
        /// </summary>
        /// <param name="culture">選擇的語系</param>
        /// <returns>對應的語系</returns>
        public static String GetImplementedCulture(String culture)
        {
            bool isImplemented = false;

            if(!String.IsNullOrEmpty(culture))
            {
                isImplemented = (GetAllImplementedCultures().Where(x => x.Value.Equals(culture)).Count() > 0) ? true : false;
            }


            if(!isImplemented)
            {
                return getDefaultCulture();
            }
            else
            {
                return culture;
            }
        }



        /// <summary>
        /// 取得所有有建立的語系
        /// </summary>
        /// <remarks>通常是給下拉式選單使用</remarks>
        /// <returns>語系集合</returns>
        public static IEnumerable<Culture> GetAllImplementedCultures()
        {
            List<Culture> cultureList = new List<Culture>();
            foreach(CultureEnum cl in Enum.GetValues(typeof(CultureEnum)))
            {
                cultureList.Add(new Culture {
                    Key = cl.ToIntValue(), Value = cl.GetDescription() });

            }

            return cultureList.AsEnumerable<Culture>();
        }

        private static String getDefaultCulture()
        {
            return CultureEnum.enUS.GetDescription(); //預設為en-US 英文
        }
    }


l   MVC : 選擇預設語系(View Model)

public class CultureVm
{
        [Display(Name = "CultureName", ResourceType = typeof(MultiLangResx.Resources.Resource))]

        public String Name { get; set; }
}



l   MVC : 選擇預設語系(View)

@using MyMultiLangWebSite.Extensions;
@model MyMultiLangWebSite.Models.CultureVm

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
   
    <br />
    <div>
        <b>Current Language@ViewBag.CookieCulture</b>
    </div>
    <hr />
    <table class="table">
        <tr>
            <td>
                @Html.DisplayNameFor(model => model.Name)
            </td>
            <td>
                @Html.DropDownListFor(model => model.Name, (IEnumerable<SelectListItem>)ViewData["LangSelectList"])
            </td>
            <td>
                <input type="submit"
value="@Html.DisplayName( "Submit".ToAutoMultiLang())">
            </td>
        </tr>
    </table>
}


參考畫面

l   MVC : 選擇預設語系(Controller)

public class MultiLangController : Controller
{
        public ActionResult Index()
        {
            #region Show the culture saved in Cookie
            HttpCookie cultureCookie = Request.Cookies["_culture"];

            if (cultureCookie != null)
            {
                ViewBag.CookieCulture = cultureCookie.Value as String;
            }
            else
            {
                ViewBag.CookieCulture = "(None)";
            }
            #endregion

            #region Get DropDownList items
            //ViewData
            ViewData["LangSelectList"] = CultureHelper.GetAllImplementedCultures()
                .ToSelectList(x => x.Value, x => x.Value, isOrder: true)
as IList<SelectListItem>;

            return View();
        }


        // POST: MultiLang/Delete/5
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Index(CultureVm vm)
        {
            try
            {
                #region 更改語系
                var culture = CultureHelper.GetImplementedCulture(vm.Name);
                HttpCookie cultureCookie = Request.Cookies["_culture"];
                if (cultureCookie != null)
                {
                    cultureCookie.Value = culture;
                }
                else
                {
                    cultureCookie = new HttpCookie("_culture");
                    cultureCookie.HttpOnly = true;
                    cultureCookie.Value = culture;
                    cultureCookie.Expires = DateTime.Now.AddMonths(2);
                }

                Response.Cookies.Add(cultureCookie);


                //設定Current Culture
                var ci = new CultureInfo(culture);
                System.Threading.Thread.CurrentThread.CurrentUICulture = ci;
                System.Threading.Thread.CurrentThread.CurrentCulture = ci;
                #endregion

                return RedirectToAction("Index");
}
            catch
            {
                return View();
            }
        }
    }

n   ToSelectList 這個方法請參考IEnumerableExtensions


l   調整 Global.asax

最後,在每次收到Request的時候,由HttpResponse的語系喜好為主,改為:
當已經設定Cookie時,以Cookie之設定為主。

protected void Application_BeginRequest(object sender, EventArgs e)
        {
            HttpCookie cultureCookie = Request.Cookies["_culture"];
            CultureInfo ci = null;

            try
            {
                if (cultureCookie != null//已設定Culture Cookie,則以Cookie為主
                {
                    ci = new CultureInfo(cultureCookie.Value);
                }
                else //未設定Culture Cookie,以HttpRequest的資訊為主
                {
                    //取得用戶端語言喜好設定(已排序的字串陣列)
                    // 請參考實作1
                }
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                System.Threading.Thread.CurrentThread.CurrentUICulture = ci;
                System.Threading.Thread.CurrentThread.CurrentCulture = ci;
            }

        }



l   實測

瀏覽器預設為英文


使用者未選擇預設語系時的畫面


選擇後,以記錄在Cookie的為主。



Reference































沒有留言:

張貼留言