探寻ASP.NET MVC鲜为人知的奥秘(3):寻找多语言的最佳实践方式

如果你的网站需要被世界各地的人访问,访问者会使用各种不同的语言和文字书写习惯,那么创建一个支持多语言的网站就是十分必要的了,这一篇文章就讲述怎么快速合理的创建网站对多语言的支持。接下来通过一个实例来讲述实践方式。

首先创建一个ASP.NET MVC5应用程序,命名为Internationalization:

然后在Models中添加一个示例的模型类:

public class Employee
    {
        [Display(Name = "Name", ResourceType = typeof(Resources.Resource))]
        [Required(ErrorMessageResourceType = typeof(Resources.Resource),
                  ErrorMessageResourceName = "NameRequired")]
        public string Name { get; set; }
    }

这里提到了Resources.Resource命名空间,接下来就创建它:

这是一个单独的项目,用来存放各种语言的资源文件,我们创建了三个资源文件,分别存放了中文(默认)、英文和阿拉伯文,资源文件中存放了如下资源项:

注意:这里的资源因为需要在项目外部使用,所以需要将访问修饰符修改为Public

接下来就是如何来确定访问者要使用的语言了,在每个请求中,都会有一个Accept-language的头,其中定义了可接受的语言类型,但是我们仅可以从它来判断浏览器中设置的语言,而这个语言类型可能并不是访问者实际需要的语言类型,所以,我们将设计一个可供选择的语言列表,然后在服务器端使用发回Cookie的方式保存浏览器端实际需要的语言。

首先需要创建一个CultureHelper类,这个类的功能就是来判断访问者实际需要的语言类型:

public class CultureHelper
    {
        private static readonly List<string> validCultures = new List<string>() { "zh-cn", "en-us","ar" };
        private static readonly List<string> cultures = new List<string>() { "zh-cn", "en-us","ar" };

        public static bool IsRightToLeft()
        {
            return Thread.CurrentThread.CurrentCulture.TextInfo.IsRightToLeft;
        }

        public static string GetImplementedCulture(string name)
        {
            if (string.IsNullOrEmpty(name))
            {
                return GetDefaultCulture();
            }
            if (!validCultures.Where(c => c.Equals(name, StringComparison.InvariantCultureIgnoreCase)).Any())
            {
                return GetDefaultCulture();
            }
            if (cultures.Where(c => c.Equals(name, StringComparison.InvariantCultureIgnoreCase)).Any())
            {
                return name;
            }
            var n = GetNeutralCulture(name);
            foreach (var c in cultures)
            {
                if (c.StartsWith(n))
                {
                    return c;
                }
            }
            return GetDefaultCulture();
        }

        public static string GetDefaultCulture()
        {
            return cultures[0];
        }

        public static string GetCurrentCulture()
        {
            return Thread.CurrentThread.CurrentCulture.Name;
        }

        public static string GetCurrentNeutralCulture()
        {
            return GetNeutralCulture(Thread.CurrentThread.CurrentCulture.Name);
        }

        public static string GetNeutralCulture(string name)
        {
            if (!name.Contains("-"))
            {
                return name;
            }
            else
            {
                return name.Split('-')[0];
            }
        }

    }

接下来创建一个BaseController类,并且确保之后创建的所有控制器继承自它:

public class BaseController : Controller
    {
        protected override IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
        {
            string cultureName = string.Empty;
            HttpCookie cultureCookie = Request.Cookies["_culture"];
            if (cultureCookie != null)
            {
                cultureName = cultureCookie.Value;
            }
            else
            {
                cultureName = Request.UserLanguages != null && Request.UserLanguages.Length > 0 ?
                    Request.UserLanguages[0] : null;
            }
            cultureName = CultureHelper.GetImplementedCulture(cultureName);
            Thread.CurrentThread.CurrentCulture = new CultureInfo(cultureName);
            Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
            return base.BeginExecuteCore(callback,state);
        }
    }

创建一个EmployeeController,作为多语言实践的一个示例:

public class EmployeeController : BaseController
    {
        //
        // GET: /User/
        public ActionResult Index()
        {
            return View();
        }

        public ActionResult SetCulture(string culture)
        {
            culture = CultureHelper.GetImplementedCulture(culture);
            HttpCookie cookie = Request.Cookies["_culture"];
            if (cookie != null)
            {
                cookie.Value = culture;
            }
            else
            {
                cookie = new HttpCookie("_culture");
                cookie.Value = culture;
                cookie.Expires = DateTime.Now.AddDays(1);
            }
            Response.Cookies.Add(cookie);
            return RedirectToAction("Index");
        }
    }

在程序包管理控制台中,使用PS命令,安装Bootstrap对RightToLeft文字习惯的支持:

Install-Package Twitter.Bootstrap.RTL

然后在App_Start中的BundleConfig.cs中添加两个资源文字的虚拟捆绑路径:

bundles.Add(new ScriptBundle("~/bundles/bootstrap-rtl").Include(
                     "~/Scripts/bootstrap-rtl.js",
                     "~/Scripts/respond.js"));

            bundles.Add(new StyleBundle("~/Content/css-rtl").Include(
                      "~/Content/css/bootstrap-rtl.css",
                      "~/Content/site.css"));

最后,创建Index视图:

@model Internationalization.Models.Employee

@{
    ViewBag.Title = "Index";
    var culture = System.Threading.Thread.CurrentThread.CurrentCulture.Name.ToLowerInvariant();
}

@helper selected(string c, string culture)
{
    if (c == culture)
    {
        @:checked="checked"
    }
}

@*选择语言列表*@
@using (Html.BeginForm("SetCulture", "Employee"))
{
    <fieldset>
        <legend></legend>
        <div class="control-group">
            <div class="
                 ">
                <label for="zh-cn">
                    <input name="culture" id="zh-cn" value="zh-cn" type="radio" @selected("zh-cn", culture) /> 中文
                </label>
            </div>
        </div>
        <div class="control-group">
            <div class="controls">
                <label for="en-us">
                    <input name="culture" id="en-us" value="en-us" type="radio" @selected("en-us", culture) /> English
                </label>
            </div>
        </div>
        <div class="control-group">
            <div class="controls">
                <label for="en-us">
                    <input name="culture" id="ar" value="ar" type="radio" @selected("ar", culture) />  عربي
                </label>
            </div>
        </div>

    </fieldset>
}


@*创建员工表单*@
@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h2>@Resources.Resource.AddEmployee</h2>
        <hr />
        @Html.ValidationSummary(true)

        <div class="form-group">
            @Html.LabelFor(model => model.Name, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Name)
                @Html.ValidationMessageFor(model => model.Name)
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}

@*在选择语言后,自动提交*@
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
    <script type="text/javascript">
        (function ($) {
            $("input[type = 'radio']").click(function () {
                $(this).parents("form").submit(); // post form
            });

        })(jQuery);
    </script>
}

还需要更改_Layout.cshtml文件,当文字习惯为右到左时,需要切换bootstrap的样式文件:

<!DOCTYPE html>
<html lang="@Internationalization.Helpers.CultureHelper.GetCurrentNeutralCulture()" dir="@(Internationalization.Helpers.CultureHelper.IsRightToLeft() ? "rtl" : "ltr")">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - 我的 ASP.NET 应用程序</title>
    @Styles.Render("~/Content/css" + (Internationalization.Helpers.CultureHelper.IsRightToLeft() ? "-rtl" : ""))
    @Scripts.Render("~/bundles/modernizr")
</head>
<body>
    <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                @Html.ActionLink("应用程序名称", "Index", "Home", null, new { @class = "navbar-brand" })
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li>@Html.ActionLink("主页", "Index", "Home")</li>
                    <li>@Html.ActionLink("关于", "About", "Home")</li>
                    <li>@Html.ActionLink("联系方式", "Contact", "Home")</li>
                </ul>
            </div>
        </div>
    </div>
    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; @DateTime.Now.Year - 我的 ASP.NET 应用程序</p>
        </footer>
    </div>

    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap" + (Internationalization.Helpers.CultureHelper.IsRightToLeft() ? "-rtl" : ""))
    @RenderSection("scripts", required: false)
</body>
</html>

演示效果:

最终DEMO地址:点击这里,感谢园友支持,这一期会一直进行下去的

如果您觉得这篇文章对您有用,劳烦给个赞!

如果您觉得这篇文章可能对别人游泳,劳烦您推荐一个!

如果您觉得这篇文章真扯淡,那么你又给我刷了个访问量!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏恰童鞋骚年

自己动手写一个简单的MVC框架(第一版)

  路由(Route)、控制器(Controller)、行为(Action)、模型(Model)、视图(View)

1542
来自专栏大内老A

我所理解的Remoting(1):Marshaling & Activation[下篇]

在上面一片文章,我花了大量的文字来来描述了Remote Object如何通过Marshaling的过程从Server端所在的Application Domain...

2236
来自专栏一个爱瞎折腾的程序猿

mvc网站迁移.net core记录

ConfigureServices方法中配置即可,详情见院长文章 http://www.cnblogs.com/dudu/p/5879913.html

1671
来自专栏jeremy的技术点滴

开发小技巧备忘

3407
来自专栏技术之路

【权限的思考】(一)使用反射实现动态权限

  每一个业务系统都会根据业务需要配置各种各样的权限,实现方式也是千差万别,各有各的优缺点。今天我们 利用反射来做一个小的权限管理Demo。也可以说是插件化的权...

2199
来自专栏木宛城主

SharePoint中在线编辑文档

我一直以为只有在Document Library里面的File才会支持在线编辑。直到今天早上我才发现用IE打开List里面的Attachments也是支持在线...

3676
来自专栏菩提树下的杨过

Office Open XML学习(1)-创建excel文档,并向单元格中插入字符串

做企业级应用,跟office打交道是少不了的。这里的Office不仅仅局限于微软的Office,还有第三方的Open Office之类。.Net传统的Offic...

3369
来自专栏菩提树下的杨过

多线程中的ManualResetEvent

先来看一下简单的多线程控制台程序: using System; using System.Threading; namespace ManualResetEv...

2125
来自专栏菩提树下的杨过

FluorineFx:基于RSO(远程共享对象)的文本聊天室

在前一篇“FluorineFx:远程共享对象(Remote SharedObjects)”里,已经大致知道了在FluorineFX中如何使用RSO,这一篇将利用...

2668
来自专栏偏前端工程师的驿站

实现滑动分页(微博分页方式)

  现在大家都在上微博,而微博的滑动分页引起了我的兴趣,于是自己模仿着做,以下是这段时间的成果(单纯实现,没有考虑到效率和其他细节问题)   实现内容:以30条...

2329

扫码关注云+社区

领取腾讯云代金券