前陣子,我提出一個以jQuery實作網頁多語系切換的點子,主張透過UI.htm維護文字對照表,提供js以Class註記加查表的方式,讓網頁可直接呈現預設語系文字(傳統上要將可切換文字全都換成代碼,可讀性大減),再用對照方式查出並置換為其他語系內容。

同事迫於我的淫威在了解該架構的便利性後,開始逐步在專案中試用。今天同事MSN給我,許了一個願:

呼叫ml("預設語系文字內容")傳回其他語系對照的做法在寫Javascript時很好用,但很希望在aspx.cs端也提供相同功能!!

身為始作俑者元件供應者,幫開發人員實現心願是責無旁貸的使命,所以花了點時間寫了jQueryMultiLangAgent類別來滿足需求。

將jQueryMultiLangAgent.cs(程式碼如下)放在App_Code或編譯入專案中,就可在C#裡指定UI.htm(Yes! 與前端共用)建構jQueryMultiLangAgent物件,接著可由Languages取得所有支援語系的清單,指定好CurrentLanguage,就能呼叫MapText()將預設語系文字轉成指定語系的內容囉!

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.IO;
using System.Text.RegularExpressions;
using System.Web.Caching;
 
public class jQueryMultiLangAgent
{
    //文字對照表資料物件
    public class MultiLangData
    {
        public List<string> Languages;
        public Dictionary<string, int> IndexBook;
        public Dictionary<string, List<string>> TextDictionary;
    }
    //UI.htm的實體路徑
    public string LangFilePath;
    //對照表存於Cache中,發現不存在時就重新產生
    public MultiLangData Data
    {
        get
        {
            if (HttpContext.Current.Cache[LangFilePath] == null)
            {
                string html = File.ReadAllText(LangFilePath);
                //取出標記資料區塊
                #region Extract the part between <!-- BEGIN --> and <!-- END -->
                int x = html.IndexOf("<!-- BEGIN -->");
                int y = html.IndexOf("<!-- END -->");
                if (x == -1 || y == -1)
                    throw new ApplicationException(
                        string.Format("{0} is missing BEGIN/END mark!",
                        LangFilePath));
                html = html.Substring(x + 14, y - x - 14);
                #endregion
 
                Dictionary<string, List<string>> dictionary =
                    new Dictionary<string, List<string>>();
                Dictionary<string, int> index = new Dictionary<string, int>();
                //SelectChildElements, GetInnerHTML用Regex解析tr, th, td等元素內容取值
                List<string> trs = SelectChildElements(html, "tr");
                //取得全部的語系代碼,最前面的當成預設語系
                List<string> langs = SelectChildElements(trs.First(), "th").Skip(2)
                                    .Select(o => GetInnerHTML(o)).ToList();
                foreach (string lang in langs)
                    dictionary.Add(lang, new List<string>());
 
                int count = 0;
                foreach (string tr in trs.Skip(1))
                {
                    List<string> tdHtmls = SelectChildElements(tr, "td").Skip(2)
                                           .Select(o => GetInnerHTML(o)).ToList();
                    //最前面的文字欄位為預設語系
                    if (index.ContainsKey(tdHtmls[0]))
                        throw new ApplicationException("Duplicated Text - " + tdHtmls[0]);
                    //記錄索引位置,未來用Dictionary以文字查出位置
                    index.Add(tdHtmls[0], count++);
                    //逐一放入各語系文字
                    for (int i = 0; i < langs.Count; i++)
                        dictionary[langs[i]].Add(tdHtmls[i]);
                }
                //將資料物件存入Cache,並與檔案設定相依性
                HttpContext.Current.Cache.Add(LangFilePath, new MultiLangData()
                {
                    Languages = langs,
                    IndexBook = index,
                    TextDictionary = dictionary
                }, new System.Web.Caching.CacheDependency(LangFilePath),
                Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration,
                CacheItemPriority.High, null
                );
            }
            return HttpContext.Current.Cache[LangFilePath] as MultiLangData;
        }
    }
 
    #region Language Options
    //所有可用語系清單
    public List<string> Languages
    {
        get { return Data.Languages; }
    }
    private string currLang = "NA";
    //目前指定語系
    public string CurrentLanguage
    {
        get { return currLang; }
        set {
            if (!Languages.Contains(value))
                throw new ApplicationException(
                    "Language " + value + " not defined!");
            currLang = value;
        }
    }
    #endregion
    //將預設語系文字轉為目前指定語系文字,支援string.Format
    public string MapText(string text, params object[] args)
    {
        if (!Data.IndexBook.ContainsKey(text))
            throw new ApplicationException(
                "'" + text + "' not found in default language.");
        return string.Format(
            Data.TextDictionary[CurrentLanguage][Data.IndexBook[text]], args);
    }
    //建構式,指定UI.htm,取得預設語系時會觸發UI.htm解讀程序
    public jQueryMultiLangAgent(string langHtmlPath)
    {
        LangFilePath = HttpContext.Current.Server.MapPath(langHtmlPath);
        CurrentLanguage = Languages[0];
    }
    #region Parser Utility
    private static List<string> SelectChildElements(string html, string tag)
    {
        List<string> lst = new List<string>();
        foreach (Match m in Regex.Matches(html, string.Format("<{0}>.*?</{0}>", tag),
            RegexOptions.Singleline | RegexOptions.IgnoreCase))
            lst.Add(m.Value);
        return lst;
    }
    private static string GetInnerHTML(string html)
    {
        int st = html.IndexOf(">") +1, ed = html.LastIndexOf("<");
        return html.Substring(st, ed - st).Trim(' ', '\r', '\n');
    }
    #endregion
}

應用範例如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
 
public partial class MultiLang_UI : System.Web.UI.Page
{
    jQueryMultiLangAgent mlAgent = new jQueryMultiLangAgent(
        "UI.lang.htm");
    string demoString = "員工姓名有無效字元: {0}";
    private void showText()
    {
        mlAgent.CurrentLanguage = ddlLangs.SelectedItem.ToString();
        lblText.Text = mlAgent.MapText(demoString, "SUCC");
    }
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
        {
            ddlLangs.DataSource = mlAgent.Languages;
            ddlLangs.DataBind();
            showText();
        }
    }
    protected void btnSwitch_Click(object sender, EventArgs e)
    {
        showText();
    }
}

歡迎指教!


Comments

Be the first to post a comment

Post a comment