2013年4月13日 星期六

ASP.NET MVC 讓 jQuery Validation Plugin 動態切換顯示訊息語系

上一篇「改變 jQuery Validation Plugin 預設訊息的語言 (localization)」介紹了如何讓 ASP.NET MVC 網站內使用 DataType Attribute 的屬性於前端頁面驗證時可以顯示正體中文訊息,而非預設顯示的英文訊息,在該篇文章當中也提到了多語系的應用,所以這一篇就來說如何讓 ASP.NET MVC 於切換語系的時候也讓 jQuery Validation Plugin 的預設顯示訊息也隨著語系的切換來做改變。

P.S.
本篇並不會完全實作全網站的多語系資源設定(App_GlobalResources),先把關注點放在怎麼讓 jQuery Validation Plugin 的眼正顯示訊息可以隨著語系的切換而作變化。


首先要在 ASP.NET MVC 的網站上顯示一個可讓使用者切換語系的項目,而一些有作多國語系的網站大多是提供下拉選單或是使用連結的方式讓使用者做選擇,我這邊只是做個示範演練,所以只會提供三個語系來做切換,分別是:正體中文、簡體中文以及日文,所以就用最簡單的方式,使用 HyperLink 讓使用者去點選切換,而關於這部份的實作,我則是參考以下的文章內容:

ASP.NET MVC and Localization | MIKE

我在網站專案裡建立一個 Core/ActionFilter 目錄,然後建立 SetCultureAttribute.cs,然後複製上面文章裡所提供的程式內容,不過程式內容則是修改了使用的語系以及預設的語系項目,

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Web;
using System.Web.Mvc;
 
namespace MvcApplication1.Core.ActionFilter
{
    public class SetCultureAttribute : FilterAttribute, IActionFilter
    {
        public void OnActionExecuting(ActionExecutingContext filterContext)
        {
            string cultureCode = SetCurrentLanguage(filterContext);
 
            if (string.IsNullOrEmpty(cultureCode)) return;
 
            HttpContext.Current.Response.Cookies.Add
            (
                new HttpCookie("Culture", cultureCode)
                {
                    HttpOnly = true,
                    Expires = DateTime.Now.AddYears(100)
                }
            );
 
            filterContext.HttpContext.Session["Culture"] = cultureCode;
 
            switch (cultureCode)
            {
                case "zh-TW":
                    filterContext.HttpContext.Session["CultureName"] = "正體中文";
                    break;
                case "zh-CN":
                    filterContext.HttpContext.Session["CultureName"] = "简体中文";
                    break;
                case "ja-JP":
                    filterContext.HttpContext.Session["CultureName"] = "日本語";
                    break;
                default:
                    filterContext.HttpContext.Session["CultureName"] = "正體中文";
                    break;
            }
 
            CultureInfo culture = new CultureInfo(cultureCode);
            System.Threading.Thread.CurrentThread.CurrentCulture = culture;
            System.Threading.Thread.CurrentThread.CurrentUICulture = culture;
        }
 
        public void OnActionExecuted(ActionExecutedContext filterContext)
        {
 
        }
 
        private static string GetCookieCulture(
            ActionExecutingContext filterContext,
            ICollection<string> Cultures)
        {
            /* Get the language in the cookie*/
            HttpCookie userCookie = filterContext.RequestContext.HttpContext.Request.Cookies["Culture"];
 
            if (userCookie != null)
            {
                if (!string.IsNullOrEmpty(userCookie.Value))
                {
                    if (Cultures.Contains(userCookie.Value))
                    {
                        return userCookie.Value;
                    }
                }
            }
            return string.Empty;
        }
 
        private static string GetSessionCulture(
            ActionExecutingContext filterContext,
            ICollection<string> Cultures)
        {
            if (filterContext.RequestContext.HttpContext.Session["Culture"] != null)
            {
                string SessionCulture = filterContext.RequestContext.HttpContext.Session["Culture"].ToString();
 
                if (!string.IsNullOrEmpty(SessionCulture))
                {
                    return Cultures.Contains(SessionCulture)
                        ? SessionCulture
                        : string.Empty;
                }
            }
            return string.Empty;
        }
 
        private static string GetBrowserCulture(
            ActionExecutingContext filterContext,
            IEnumerable<string> Cultures)
        {
            /* Gets Languages from Browser */
            IList<string> BrowserLanguages = filterContext.RequestContext.HttpContext.Request.UserLanguages;
 
            foreach (var thisBrowserLanguage in BrowserLanguages)
            {
                foreach (var thisCultureLanguage in Cultures)
                {
                    if (thisCultureLanguage != thisBrowserLanguage)
                    {
                        continue;
                    }
                    return thisCultureLanguage;
                }
            }
            return string.Empty;
        }
 
        private static string SetCurrentLanguage(ActionExecutingContext filterContext)
        {
            IList<string> Cultures = new List<string> 
            {
                "zh-TW", 
                "zh-CN",
                "ja-JP"
            };
 
            string CookieValue = GetCookieCulture(filterContext, Cultures);
 
            if (string.IsNullOrEmpty(CookieValue))
            {
                string SessionValue = GetSessionCulture(filterContext, Cultures);
 
                if (string.IsNullOrEmpty(SessionValue))
                {
                    string BrowserCulture = GetBrowserCulture(filterContext, Cultures);
                    return string.IsNullOrEmpty(BrowserCulture)
                        ? "zh-TW"
                        : BrowserCulture;
                }
                return SessionValue;
            }
            return CookieValue;
        }
    }
}

有關 CultureInfo 的 CultureType Name 可以參考以下的網頁:

C# Examples : Culture Names [C#]

接著建立一個 BaseController.cs 並且在 Controller 類別上方標註剛剛所建立的 SetCulture Attribute,然後在 BaseController 中建立一個 SetCulture Action 方法,讓顯示在前端頁面的語系連結來使用,

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcApplication1.Core.ActionFilter;
 
namespace MvcApplication1.Core
{
    [SetCulture]
    public class BaseController : Controller
    {
 
        public ActionResult SetCulture(string id)
        {
            HttpCookie userCookie = Request.Cookies["Culture"];
 
            userCookie.Value = id;
            userCookie.Expires = DateTime.Now.AddYears(100);
            Response.SetCookie(userCookie);
 
            return Redirect(Request.UrlReferrer.ToString());
        }
 
    }
}

建立好之後,記得讓既有的 Controller 都改去繼承 BaseController,

image

再來在 Views/Shared 目錄下建立一個 _SetCulture.cshtml,讓頁面上都可以共用,

Current Language : 
<span style="color: #ff0000; font-weight: bold;">
    @(Session["CultureName"].ToString())
</span> | 
@{
    string currentController = ViewContext.Controller.ValueProvider.GetValue("controller").RawValue.ToString();
    string cultrue = Session["Culture"] == null ? "zh-TW" : Session["Culture"].ToString();
    
    switch (cultrue)
    {
        case "zh-CN":
            <a href="/@currentController/SetCulture/zh-TW">[ 正體中文 ]</a>
            <a href="/@currentController/SetCulture/ja-JP">[ 日本語 ]</a>
            break;
        case "ja-JP":
            <a href="/@currentController/SetCulture/zh-TW">[ 正體中文 ]</a>
            <a href="/@currentController/SetCulture/zh-CN">[ 简体中文 ]</a>
            break;
        default:
            <a href="/@currentController/SetCulture/zh-CN">[ 简体中文 ]</a>
            <a href="/@currentController/SetCulture/ja-JP">[ 日本語 ]</a>        
            break;
    }
}

_SetCulture.cshtml 建立完成後,加入到 _Layout.cshtml 頁面裡,

image

步驟到這裡先看看前端頁面的顯示結果,

一開始進入到頁面是預設顯示為「正體中文」,

image

選擇簡體中文

image

選擇日本語

image

完成這個階段之後可以先休息一下。

 

在休息的同時可以先到 jQuery Validation Plugin 的 GitHub Repository 下的 localization 目錄,把「message_ja.js」與「messages_zh.js」給複製一份到 Scripts/jQueryValidation_localization 目錄下,然後把檔名中的 ja 更改為 ja-JP、zh 更改為 zh-CN,也把 zh_TW 更改為 zh-TW,

https://github.com/jzaefferer/jquery-validation/blob/master/localization/messages_ja.js

https://github.com/jzaefferer/jquery-validation/blob/master/localization/messages_zh.js

image

 

前置作業已經做得差不多了,再來就是讓前端頁面所顯示的 jQuery Validation Plugin Message 可以依據所選擇的語系不同而變換,先來個最簡單的作法,因為我是把 Culture 的 Code 與 Name 都放在 Session 中,所以可以直接在有引用 messages_{ CultureCode }.js 的頁面去把中間 CultureCode 改用 Session 來取代,

image

@{
    string cultureCode = Session["Culture"] == null
        ? "zh-TW"
        : Session["Culture"].ToString();
}
 
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
    <script src="~/Scripts/jQueryValidation_localization/messages_@(cultureCode).js"></script>
}

 

最簡單的作法也意味著未來的維護會最麻煩,所以再做個修改,把這一段引用 messages_{ cultureCode }.js 的部份拿到後端的程式來處理,前端只需要在頁面上 script tag 裡用 Url.Content() 去呼叫這一個方法就可以,

LocalizationHelpers.cs

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Mvc;
 
namespace MvcApplication1.Core.Helpers
{
    public static class LocalizationHelpers
    {
        public static string GetJQueryValidationMessageFilePath()
        {
            var currentCulture = CultureInfo.CurrentCulture;
            var filePattern = "~/Scripts/jQueryValidation_localization/messages_{0}.js";
            var regionalisedFileToUse = string.Format(filePattern, "zh-TW");
 
            if (File.Exists(HttpContext.Current.Server.MapPath(string.Format(filePattern, currentCulture.Name))))
            {
                regionalisedFileToUse = string.Format(filePattern, currentCulture.Name);
            }
            else if (File.Exists(HttpContext.Current.Server.MapPath(string.Format(filePattern, currentCulture.TwoLetterISOLanguageName))))
            {
                regionalisedFileToUse = string.Format(filePattern, currentCulture.TwoLetterISOLanguageName);
            }
            return regionalisedFileToUse;
        }
    }
}

前端的使用

@using MvcApplication1.Core.Helpers
...
...
...
...
<div>
    @Html.ActionLink("Back to List", "Index")
</div>
 
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
    <script src="@Url.Content(LocalizationHelpers.GetJQueryValidationMessageFilePath())" type="text/javascript"></script>
}
執行結果:

正體中文

image

image

簡體中文

image

image

日本語

image

image

 

又或者也可以簡單一點,由後端取得 Culture Code,前端再以 HtmlHelper 的方式取得就可以,

在 LocalizationHelpers.cs 增加一個 HtmlHelper 擴充方法「CultureCode」,

image

而前端頁面的使用就修改為以下的內容,

image

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
    <script src="~/Scripts/jQueryValidation_localization/messages_@(Html.CultureCode()).js"></script>
}

 

更新:

文章發佈沒多久後才又突然想起,在前面產生多語系連結的地方,我們在 _SetcCulture.cshtml 裡面顯示多語系名稱的地方也有用到 Session,所以這邊也依照上面擴充 HtmlHelper 的方式,另外建立一個 HtmlHelper 擴充方法,讓 View 可以不經由 Session 而取得 Culture Name。

首先已經不用 Session 來裝載 Culture Name 的資料了,所以在 SetCultureAttribute 的 OnActionExecuting() 方法內設定 CultureName Session 就可以移除了,

image

接著到 LocalizationHelper.cs 去增加一個 CultureName 的 HtmlHelper 擴充方法,

public static IHtmlString CultureName(this HtmlHelper html)
{
    string currentUICulture = System.Threading.Thread
        .CurrentThread
        .CurrentUICulture.ToString();
 
    currentUICulture = HttpUtility.HtmlAttributeEncode(currentUICulture);
 
    string currentCultureName = string.Empty;
 
    switch (currentUICulture)
    {
        case "zh-CN":
            currentCultureName = "简体中文";
            break;
        case "ja-JP":
            currentCultureName = "日本語";
            break;
        default:
            currentCultureName = "正體中文";
            break;
    }
    return new HtmlString(currentCultureName);
}

最後就是修改 _SetCulture.cshtml 的內容,

image

執行結果

image

前端的 View 裡面就不需要處理 Session 了。

 

2013-04-16 限時更新:

把這篇文章的實作範例網站放到 Windows Azure 上,至於放到什麼時候…… 想到要撤下來就會撤,不另行通知!

首頁:http://goo.gl/rC8J5

UserInfo Create 頁面:http://goo.gl/4jOPR

只有兩個頁面而已。

 


以上的介紹只是簡單的作法,如果是比較複雜的多語系的系統,會建議使用 Resources 來做管理。

 

參考連結:

Globalization, Internationalization and Localization in ASP.NET MVC 3, JavaScript and jQuery - Part 1 - Scott Hanselman

ASP.NET MVC and Localization | MIKE

 

以上

2 則留言:

  1. 回覆
    1. 嗯嗯,我知道可以利用 DisplayMode 來做多語系以及不同裝置顯示的功能,
      感謝 demo 的提醒。

      刪除

提醒

千萬不要使用 Google Talk (Hangouts) 或 Facebook 及時通訊與我聯繫、提問,因為會掉訊息甚至我是過了好幾天之後才發現到你曾經傳給我訊息過,請多多使用「詢問與建議」(在左邊,就在左邊),另外比較深入的問題討論,或是有牽涉到你實作程式碼的內容,不適合在留言板裡留言討論,請務必使用「詢問與建議」功能(可以夾帶檔案),謝謝。