ASP.NET MVC 本地化

5

我将尝试使用路由实现本地化。

以下是我的代码:

routes.MapRoute( "DefaultLocalized", 
                 "{lang}/{controller}/{action}/{id}",
                 new { controller = "Home",
                       action = "Index",
                       id = "",
                       lang = "en" }
               );

routes.MapRoute( "Default",
                 "{controller}/{action}/{id}",
                 new { controller = "Home",
                       action = "Index",
                       id = "" }
               );

当我调用我的页面domain/en/home/index时,它可以正常工作,但是当我调用domain/home/index时,我会收到错误404:找不到资源的错误。
此外,当我在domain/en/home/index时点击一个安全页面时,我会被重定向到domain/Account/login,如何将其重定向到domain/en/Account/login
还有,当我得到应用程序错误时,如何将其重定向到domain/en/home/error
真正的问题是如何实现语言路由参数的本地化?

虽然这是一个老问题,但我刚刚经历了在Razor中提供语言选择的完整解决方案的痛苦,因此我已经添加了一个新答案作为指南。https://dev59.com/OEbRa4cB1Zd3GeqP4-5m#19398879 希望能有所帮助 :) - iCollect.it Ltd
4个回答

10

默认情况下,路由匹配是从左到右的,因此"domain/home/index"将首先匹配到lang=domain,控制器为index,操作(默认为index),id(默认为0 / null)。

要解决这个问题,我相信您可以在MapRoute上指定一个正则表达式(例如,精确匹配具有2个字符的语言)-不过它可能已经在某个时候更改过了...(很抱歉,我现在没有IDE,所以无法准确检查)。

根据我的记忆,它可能是:

routes.MapRoute( "DefaultLocalized", 
             "{lang}/{controller}/{action}/{id}",
             new { controller = "Home",
                   action = "Index",
                   id = "",},
             new { lang = "[a-z]{2}" }
           );

请注意,你可能不希望每个操作都采用“string lang”,因此你应该在基本控制器或动作过滤器中处理路由的“lang”部分(在任何一种情况下,都应该将信息添加到ViewData中)。


1
为了避免像我一样的混淆,请注意:您不需要在每个控制器操作中添加lang参数或者搞乱RouteData。在全局范围内处理语言是更好的选择。 - Jakub Šturc

7

我知道这是一个非常古老的问题,但最近刚刚解决了所有相关问题,所以我想分享我的解决方案。

以下是一个完整的解决方案,包括一些额外的技巧,以便轻松更改语言。它允许指定特定的文化,而不仅仅是指定特定的语言(但在本例中仅保留语言部分)。

功能包括:

  • 回退到浏览器区域设置以确定语言
  • 使用 cookie 在访问间保留语言
  • 通过 URL 覆盖语言
  • 支持通过链接更改语言(例如简单的菜单选项)

步骤 1:修改 RouteConfig 中的 RegisterRoutes

这个新路由包括一个限制条件(正如其他人也建议的那样),以确保语言路径不会获取某些标准路径。没有必要设置默认语言值,因为所有这些都由 LocalisationAttribute 处理(请参见步骤 2)。

    public static void RegisterRoutes(RouteCollection routes)
    {
        ...

        // Special localisation route mapping - expects specific language/culture code as first param
        routes.MapRoute(
            name: "Localisation",
            url: "{lang}/{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
            constraints: new { lang = @"[a-z]{2}|[a-z]{2}-[a-zA-Z]{2}" }
        );

        // Default routing
        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );

    }

步骤二:创建本地化属性

这将在处理控制器请求之前,根据URL、cookie或默认浏览器语言环境更改当前语言环境。

// Based on: http://geekswithblogs.net/shaunxu/archive/2010/05/06/localization-in-asp.net-mvc-ndash-3-days-investigation-1-day.aspx
public class LocalisationAttribute : ActionFilterAttribute
{
    public const string LangParam = "lang";
    public const string CookieName = "mydomain.CurrentUICulture";

    // List of allowed languages in this app (to speed up check)
    private const string Cultures = "en-GB en-US de-DE fr-FR es-ES ro-RO ";

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        // Try getting culture from URL first
        var culture = (string)filterContext.RouteData.Values[LangParam];

        // If not provided, or the culture does not match the list of known cultures, try cookie or browser setting
        if (string.IsNullOrEmpty(culture) || !Cultures.Contains(culture))
        {
            // load the culture info from the cookie
            var cookie = filterContext.HttpContext.Request.Cookies[CookieName];
            var langHeader = string.Empty;
            if (cookie != null)
            {
                // set the culture by the cookie content
                culture = cookie.Value;
            }
            else
            {
                // set the culture by the location if not specified - default to English for bots
                culture = filterContext.HttpContext.Request.UserLanguages == null ? "en-EN" : filterContext.HttpContext.Request.UserLanguages[0];
            }
            // set the lang value into route data
            filterContext.RouteData.Values[LangParam] = langHeader;
        }

        // Keep the part up to the "-" as the primary language
        var language = culture.Split(new char[] { '-' }, StringSplitOptions.RemoveEmptyEntries)[0];
        filterContext.RouteData.Values[LangParam] = language;

        // Set the language - ignore specific culture for now
        Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(language);

        // save the locale into cookie (full locale)
        HttpCookie _cookie = new HttpCookie(CookieName, culture);
        _cookie.Expires = DateTime.Now.AddYears(1);
        filterContext.HttpContext.Response.SetCookie(_cookie);

        // Pass on to normal controller processing
        base.OnActionExecuting(filterContext);
    }
}

步骤三:将本地化应用于所有控制器

例如:

[Localisation]  <<< ADD THIS TO ALL CONTROLLERS (OR A BASE CONTROLLER)
public class AccountController : Controller
{

步骤四:更改语言(例如从菜单中)

这是一个有些棘手并需要一些解决方法的地方。

在您的账户控制器中添加一个ChangeLanguage方法。这将从“先前路径”中剥离出任何现有的语言代码,以使新语言生效。

    // Regex to find only the language code part of the URL - language (aa) or locale (aa-AA) syntax
    static readonly Regex removeLanguage = new Regex(@"/[a-z]{2}/|/[a-z]{2}-[a-zA-Z]{2}/", RegexOptions.Compiled);

    [AllowAnonymous]
    public ActionResult ChangeLanguage(string id)
    {
        if (!string.IsNullOrEmpty(id))
        {
            // Decode the return URL and remove any language selector from it
            id = Server.UrlDecode(id);
            id = removeLanguage.Replace(id, @"/");
            return Redirect(id);
        }
        return Redirect(@"/");
    }

步骤五:添加语言菜单链接

菜单选项包括一个链接,新语言作为路由参数指定。

例如(Razor示例)

<li>@Html.ActionLink("English", "ChangeLanguage", "Account", new { lang = "en", id = HttpUtility.UrlEncode(Request.RawUrl) }, null)</li>
<li>@Html.ActionLink("Spanish", "ChangeLanguage", "Account", new { lang = "es", id = HttpUtility.UrlEncode(Request.RawUrl) }, null)</li>

返回URL是当前页面的编码形式,以便成为URL的id参数。这意味着您需要启用某些转义序列,否则Razor会将其拒绝作为潜在的安全违规行为。

注意:对于非Razor设置,您基本上需要一个锚点,其中包含新语言和当前页面相对URL的路径,例如: http://website.com/{language}/account/changelanguage/{existingURL}

其中,{language}是新的区域代码,{existingURL}是当前相对页面地址的URL编码版本(以便我们选择新语言时返回到同一页面)。

步骤6:启用URL中的某些“不安全”字符

由于需要对返回URL进行编码,因此您需要在web.config中启用某些转义字符,否则现有的URL参数将导致错误。

在您的web.config中,找到<system.web>下的httpRuntime标签(或添加它),并添加以下内容(基本上是删除该属性中的%):

  requestPathInvalidCharacters="&lt;,&gt;,&amp;,:,\,?"

在您的web.config文件中,找到<system.webserver>部分,并在其中添加以下内容:
<security>
  <requestFiltering allowDoubleEscaping="true"/>
</security>

你好,这段代码中很难找到错误。culture = filterContext.HttpContext.Request.UserLanguages[0]; 对于机器人、爬虫、SEO工具等会引发异常。如果不可用,应该有另一个else if语句将语言设置为默认值——例如简单字符串:"en-EN"。 - Stefan Cebulak
@StefanCebulak:说得好。我忘记了机器人不提供浏览器语言。假设“UserLanguages”为空?我会尽快更新我的代码。谢谢。 - iCollect.it Ltd
是的,UserLanguages为空。 - Stefan Cebulak
@StefanCebulak:添加了基本的空值检查回退。如果您有更好的替代方案,请告诉我。谢谢。 - iCollect.it Ltd
我不会使用ActionFilter来设置文化,因为ModelBinding->Validation会在OnActionExecuting之前发生。 - maracuja-juice
@Marimba:这对我们的项目没有明显的影响。模型绑定会在上述示例中产生什么影响?谢谢。 - iCollect.it Ltd

4

添加一个新的约束条件 {lang = "[a-z]{2}"}。

此外,删除默认的语言设置 lang = "en"。如果不删除,当您在没有使用语言规则浏览网站时,路由将捕获语言规则。因此,如果您正在查看 domain 并选择了 About,它会使用 domain/en/Home/About 而不是更简单的 domain/Home/About。


3
你可以引入一个比Marc Gravell和Freddy Rios还严格的约束条件。
例如像“en|de|fr|es”这样。这意味着硬编码语言,但通常这些语言很少且已知。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接