【问题标题】:asp.net mvc localizationasp.net mvc 本地化
【发布时间】:2010-10-14 05:40:55
【问题描述】:

我正在尝试使用路线实现本地化

我有以下几点:

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

真正的问题是如何使用语言作为路由参数来实现本地化?

【问题讨论】:

标签: c# asp.net-mvc localization routing


【解决方案1】:

默认情况下,路由从左到右匹配,因此“domain/home/index”将首先匹配 lang=domain、controller=index、action(默认为 index)、id(默认为 0/空)。

要解决这个问题,我相信您可以在 MapRoute 上指定一个正则表达式(例如,匹配正好有 2 个字符的语言)——它在某些时候发生了变化……(抱歉,目前没有 IDE,所以我无法准确检查)。

根据记忆,它可能是:

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

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

【讨论】:

  • 只是为了避免和我一样的困惑。您没有向每个控制器操作添加 lang 参数或弄乱 RouteData。以全局方式处理语言真是太好了。
【解决方案2】:

我知道这是一个非常古老的问题,但刚刚解决了完整的相关问题,我想我会分享我的解决方案。

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

功能包括:

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

第一步:在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 }
        );

    }

第 2 步:创建本地化属性

这将在处理控制器请求之前查看控制器请求,并根据 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);
    }
}

第 3 步:将本地化应用到所有控制器

例如

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

第 4 步:更改语言(例如从菜单中)

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

向您的帐户控制器添加一个 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(@"/");
    }

第 5 步:添加语言菜单链接

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

例如(剃刀示例)

<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 会拒绝这些转义序列作为潜在的安全违规行为。

注意:对于非剃须刀设置,您基本上需要一个具有新语言和当前页面相对 URL 的锚点,路径如下: http://website.com/{language}/account/changelanguage/{existingURL}

其中{language} 是新的文化代码,{existingURL} 是当前相对页面地址的 URL 编码版本(这样我们将返回同一页面,并选择新的语言)。

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

返回 URL 所需的编码意味着您需要在 web.config 中启用某些转义字符,否则现有 URL 参数将导致错误。

在您的 web.config 中,找到 &lt;system.web&gt; 中的 httpRuntime 标记(或添加它)并向其添加以下内容(基本上删除该属性的标准版本中的 %):

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

在您的 web.config 中,找到 &lt;system.webserver&gt; 部分并在其中添加以下内容:

<security>
  <requestFiltering allowDoubleEscaping="true"/>
</security>

【讨论】:

  • 您好,很难在这段代码中找到错误。文化 = filterContext.HttpContext.Request.UserLanguages[0];引发机器人、爬虫、seo 工具等的异常。如果这不可用,则应该有另一个 else 将语言设置为默认值 - 简单的字符串,例如:“en-EN”。
  • @StefanCebulak:好点。我忘记了机器人不提供浏览器语言。大概UserLanguages 是空的?我会尽快更新我的代码。谢谢。
  • 是的,UserLanguages 为空。
  • @StefanCebulak:添加了一个基本的空检查回退。如果您有更好的选择,请告诉我。谢谢
  • 我不会使用 ActionFilter 来设置区域性,因为 ModelBinding -> 验证发生在 OnActionExecuting 之前。
【解决方案3】:

将约束添加为 new {lang = "[a-z]{2}"}。

此外,删除默认的 lang = "en"。如果你不这样做,当你在没有它的情况下浏览它时,路由会抓取语言规则。因此,如果您正在查看 domain 并选择 About,它将使用 domain/en/Home/About 而不是更简单的 domain/Home/About

【讨论】:

    【解决方案4】:

    您还可以引入比 Marc Gravell 和 Freddy Rios 更严格的约束。

    类似“en|de|fr|es”的东西。这意味着对语言进行硬编码,但通常这些语言很少且为人所知。

    【讨论】: