J'ai traduit mon site Web MVC, qui fonctionne très bien. Si je sélectionne une autre langue (néerlandais ou anglais), le contenu est traduit. Cela fonctionne parce que je mets la culture dans la session.
Maintenant, je veux montrer la culture sélectionnée (= culture) dans l'URL. S'il s'agit de la langue par défaut, il ne doit pas être affiché dans l'URL. Si ce n'est pas la langue par défaut, il doit l'indiquer dans l'URL.
par exemple.:
Pour la culture par défaut (néerlandais):
site.com/foo
site.com/foo/bar
site.com/foo/bar/5
Pour la culture autre que celle par défaut (anglais):
site.com/en/foo
site.com/en/foo/bar
site.com/en/foo/bar/5
Mon problème c'est que je vois toujours ceci:
site.com/nl/foo/bar/5 même si je cliquais sur l'anglais (voir _Layout.cs). Mon contenu est traduit en anglais mais le paramètre route de l'URL reste sur "nl" au lieu de "en".
Comment puis-je résoudre ceci ou qu'est-ce que je fais mal?
J'ai essayé dans le global.asax de définir le RouteData mais ne m'a pas aidé.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("favicon.ico");
routes.LowercaseUrls = true;
routes.MapRoute(
name: "Errors",
url: "Error/{action}/{code}",
defaults: new { controller = "Error", action = "Other", code = RouteParameter.Optional }
);
routes.MapRoute(
name: "DefaultWithCulture",
url: "{culture}/{controller}/{action}/{id}",
defaults: new { culture = "nl", controller = "Home", action = "Index", id = UrlParameter.Optional },
constraints: new { culture = "[a-z]{2}" }
);// or maybe: "[a-z]{2}-[a-z]{2}
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { culture = "nl", controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
Global.asax.cs:
protected void Application_Start()
{
MvcHandler.DisableMvcResponseHeader = true;
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
protected void Application_AcquireRequestState(object sender, EventArgs e)
{
if (HttpContext.Current.Session != null)
{
CultureInfo ci = (CultureInfo)this.Session["Culture"];
if (ci == null)
{
string langName = "nl";
if (HttpContext.Current.Request.UserLanguages != null && HttpContext.Current.Request.UserLanguages.Length != 0)
{
langName = HttpContext.Current.Request.UserLanguages[0].Substring(0, 2);
}
ci = new CultureInfo(langName);
this.Session["Culture"] = ci;
}
HttpContextBase currentContext = new HttpContextWrapper(HttpContext.Current);
RouteData routeData = RouteTable.Routes.GetRouteData(currentContext);
routeData.Values["culture"] = ci;
Thread.CurrentThread.CurrentUICulture = ci;
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(ci.Name);
}
}
_Layout.cs (où je laisse l'utilisateur changer de langue)
// ...
<ul class="dropdown-menu" role="menu">
<li class="@isCurrentLang("nl")">@Html.ActionLink("Nederlands", "ChangeCulture", "Culture", new { lang = "nl", returnUrl = this.Request.RawUrl }, new { rel = "alternate", hreflang = "nl" })</li>
<li class="@isCurrentLang("en")">@Html.ActionLink("English", "ChangeCulture", "Culture", new { lang = "en", returnUrl = this.Request.RawUrl }, new { rel = "alternate", hreflang = "en" })</li>
</ul>
// ...
CultureController: (= où j'ai défini la session que j'utilise dans GlobalAsax pour changer CurrentCulture et CurrentUICulture)
public class CultureController : Controller
{
// GET: Culture
public ActionResult Index()
{
return RedirectToAction("Index", "Home");
}
public ActionResult ChangeCulture(string lang, string returnUrl)
{
Session["Culture"] = new CultureInfo(lang);
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
}
Cette approche présente plusieurs problèmes, mais cela se résume à un problème de flux de travail.
CultureController
dont le seul but est de rediriger l'utilisateur vers une autre page du site. Gardez à l'esprit que RedirectToAction
enverra une réponse HTTP 302 au navigateur de l'utilisateur, qui lui indiquera de rechercher le nouvel emplacement sur votre serveur. C'est un aller-retour inutile sur le réseau.HttpContext.Current.Request.UserLanguages
de l'utilisateur, ce qui peut être différent de la culture demandée dans l'URL.La troisième question est principalement due à une vision fondamentalement différente entre Microsoft et Google sur la façon de gérer la mondialisation.
Selon Microsoft (à l'origine), la même URL devait être utilisée pour toutes les cultures et le UserLanguages
du navigateur devait déterminer la langue d'affichage du site Web.
Selon Google, chaque culture doit être hébergée sur une URL différente . Cela a plus de sens si vous y réfléchissez. Il est souhaitable que chaque personne qui trouve votre site Web dans les résultats de recherche (SERP) puisse rechercher le contenu dans sa langue maternelle.
La mondialisation d'un site Web doit être considérée comme un contenu plutôt que comme une personnalisation - vous diffusez une culture vers un groupe de les gens, pas une personne individuelle. Par conséquent, l'utilisation de fonctionnalités de personnalisation d'ASP.NET telles que l'état de session ou les cookies pour implémenter la globalisation n'a généralement aucun sens. Ces fonctionnalités empêchent les moteurs de recherche d'indexer le contenu de vos pages localisées.
Si vous pouvez envoyer l'utilisateur à une culture différente en le routant simplement vers une nouvelle URL, vous vous inquiétez beaucoup moins - vous n'avez pas besoin d'une page séparée pour que l'utilisateur sélectionne sa culture, il vous suffit d'inclure un lien dans l'en-tête. ou footer pour changer la culture de la page existante et tous les liens basculeront automatiquement vers la culture choisie par l'utilisateur (car MVC réutilise automatiquement les valeurs de route de la requête en cours ).
Tout d’abord, supprimez le CultureController
et le code dans le Application_AcquireRequestState
méthode.
Maintenant, puisque la culture est une préoccupation transversale, définir la culture du fil actuel devrait être fait dans un IAuthorizationFilter
. Cela garantit que la culture est définie avant que ModelBinder
ne soit utilisé dans MVC.
using System.Globalization;
using System.Threading;
using System.Web.Mvc;
public class CultureFilter : IAuthorizationFilter
{
private readonly string defaultCulture;
public CultureFilter(string defaultCulture)
{
this.defaultCulture = defaultCulture;
}
public void OnAuthorization(AuthorizationContext filterContext)
{
var values = filterContext.RouteData.Values;
string culture = (string)values["culture"] ?? this.defaultCulture;
CultureInfo ci = new CultureInfo(culture);
Thread.CurrentThread.CurrentCulture = ci;
Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(ci.Name);
}
}
Vous pouvez définir le filtre globalement en l'enregistrant en tant que filtre global.
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new CultureFilter(defaultCulture: "nl"));
filters.Add(new HandleErrorAttribute());
}
}
Vous pouvez simplifier la sélection de la langue en créant un lien vers la même action et le même contrôleur pour la page actuelle et en l'incluant en tant qu'option dans l'en-tête ou le pied de page de votre _Layout.cshtml
.
@{
var routeValues = this.ViewContext.RouteData.Values;
var controller = routeValues["controller"] as string;
var action = routeValues["action"] as string;
}
<ul>
<li>@Html.ActionLink("Nederlands", @action, @controller, new { culture = "nl" }, new { rel = "alternate", hreflang = "nl" })</li>
<li>@Html.ActionLink("English", @action, @controller, new { culture = "en" }, new { rel = "alternate", hreflang = "en" })</li>
</ul>
Comme mentionné précédemment, tous les autres liens de la page se verront automatiquement transmettre une culture du contexte actuel, de sorte qu'ils resteront automatiquement dans la même culture. Il n'y a aucune raison de transmettre la culture explicitement dans ces cas.
@ActionLink("About", "About", "Home")
Avec le lien ci-dessus, si l'URL actuelle est /Home/Contact
, le lien généré sera /Home/About
. Si l'URL actuelle est /en/Home/Contact
, le lien sera généré comme /en/Home/About
.
Enfin, nous arrivons au coeur de votre question. La raison pour laquelle votre culture par défaut n'est pas générée correctement est que le routage est une carte bidirectionnelle et que vous mettiez en correspondance une demande entrante ou génériez une URL sortante, la première correspondance l'emporte toujours. Lors de la construction de votre URL, la première correspondance est DefaultWithCulture
.
Normalement, vous pouvez résoudre ce problème simplement en inversant l'ordre des itinéraires. Cependant, dans votre cas, cela entraînerait l’échec des itinéraires entrants.
Ainsi, l’option la plus simple dans votre cas est de construire un contrainte de route personnalisée pour gérer le cas particulier de la culture par défaut lors de la génération de l’URL. Vous renvoyez simplement false lorsque la culture par défaut est fournie et le framework de routage .NET ignorera la route DefaultWithCulture
et passera à la route enregistrée suivante (dans ce cas, Default
).
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Routing;
public class CultureConstraint : IRouteConstraint
{
private readonly string defaultCulture;
private readonly string pattern;
public CultureConstraint(string defaultCulture, string pattern)
{
this.defaultCulture = defaultCulture;
this.pattern = pattern;
}
public bool Match(
HttpContextBase httpContext,
Route route,
string parameterName,
RouteValueDictionary values,
RouteDirection routeDirection)
{
if (routeDirection == RouteDirection.UrlGeneration &&
this.defaultCulture.Equals(values[parameterName]))
{
return false;
}
else
{
return Regex.IsMatch((string)values[parameterName], "^" + pattern + "$");
}
}
}
Tout ce qui reste à faire est d'ajouter la contrainte à votre configuration de routage. Vous devez également supprimer le paramètre par défaut pour la culture dans la route DefaultWithCulture
, car vous souhaitez qu'il corresponde uniquement lorsqu'il existe une culture fournie dans l'URL. La route Default
doit quant à elle avoir une culture car il n’ya aucun moyen de la transmettre via l’URL.
routes.LowercaseUrls = true;
routes.MapRoute(
name: "Errors",
url: "Error/{action}/{code}",
defaults: new { controller = "Error", action = "Other", code = UrlParameter.Optional }
);
routes.MapRoute(
name: "DefaultWithCulture",
url: "{culture}/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
constraints: new { culture = new CultureConstraint(defaultCulture: "nl", pattern: "[a-z]{2}") }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { culture = "nl", controller = "Home", action = "Index", id = UrlParameter.Optional }
);
REMARQUE: Cette section ne s'applique que si vous utilisez MVC 5. Vous pouvez l'ignorer si vous utilisez une version précédente.
Pour AttributeRouting, vous pouvez simplifier les choses en automatisant la création de 2 itinéraires différents pour chaque action. Vous devez modifier un peu chaque route et les ajouter à la même structure de classe que celle utilisée par MapMvcAttributeRoutes
. Malheureusement, Microsoft a décidé de rendre les types internes, il est donc nécessaire que Reflection les instancie et les remplisse.
Ici, nous utilisons simplement la fonctionnalité intégrée de MVC pour analyser notre projet et créer un ensemble de routes, puis insérons un préfixe d’URL de route supplémentaire pour la culture et le CultureConstraint
avant d’ajouter les instances à notre RouteTable MVC.
Une route distincte est également créée pour résoudre les URL (de la même façon que AttributeRouting).
using System;
using System.Collections;
using System.Linq;
using System.Reflection;
using System.Web.Mvc;
using System.Web.Mvc.Routing;
using System.Web.Routing;
public static class RouteCollectionExtensions
{
public static void MapLocalizedMvcAttributeRoutes(this RouteCollection routes, string urlPrefix, object constraints)
{
MapLocalizedMvcAttributeRoutes(routes, urlPrefix, new RouteValueDictionary(constraints));
}
public static void MapLocalizedMvcAttributeRoutes(this RouteCollection routes, string urlPrefix, RouteValueDictionary constraints)
{
var routeCollectionRouteType = Type.GetType("System.Web.Mvc.Routing.RouteCollectionRoute, System.Web.Mvc");
var subRouteCollectionType = Type.GetType("System.Web.Mvc.Routing.SubRouteCollection, System.Web.Mvc");
FieldInfo subRoutesInfo = routeCollectionRouteType.GetField("_subRoutes", BindingFlags.NonPublic | BindingFlags.Instance);
var subRoutes = Activator.CreateInstance(subRouteCollectionType);
var routeEntries = Activator.CreateInstance(routeCollectionRouteType, subRoutes);
// Add the route entries collection first to the route collection
routes.Add((RouteBase)routeEntries);
var localizedRouteTable = new RouteCollection();
// Get a copy of the attribute routes
localizedRouteTable.MapMvcAttributeRoutes();
foreach (var routeBase in localizedRouteTable)
{
if (routeBase.GetType().Equals(routeCollectionRouteType))
{
// Get the value of the _subRoutes field
var tempSubRoutes = subRoutesInfo.GetValue(routeBase);
// Get the PropertyInfo for the Entries property
PropertyInfo entriesInfo = subRouteCollectionType.GetProperty("Entries");
if (entriesInfo.PropertyType.GetInterfaces().Contains(typeof(IEnumerable)))
{
foreach (RouteEntry routeEntry in (IEnumerable)entriesInfo.GetValue(tempSubRoutes))
{
var route = routeEntry.Route;
// Create the localized route
var localizedRoute = CreateLocalizedRoute(route, urlPrefix, constraints);
// Add the localized route entry
var localizedRouteEntry = CreateLocalizedRouteEntry(routeEntry.Name, localizedRoute);
AddRouteEntry(subRouteCollectionType, subRoutes, localizedRouteEntry);
// Add the default route entry
AddRouteEntry(subRouteCollectionType, subRoutes, routeEntry);
// Add the localized link generation route
var localizedLinkGenerationRoute = CreateLinkGenerationRoute(localizedRoute);
routes.Add(localizedLinkGenerationRoute);
// Add the default link generation route
var linkGenerationRoute = CreateLinkGenerationRoute(route);
routes.Add(linkGenerationRoute);
}
}
}
}
}
private static Route CreateLocalizedRoute(Route route, string urlPrefix, RouteValueDictionary constraints)
{
// Add the URL prefix
var routeUrl = urlPrefix + route.Url;
// Combine the constraints
var routeConstraints = new RouteValueDictionary(constraints);
foreach (var constraint in route.Constraints)
{
routeConstraints.Add(constraint.Key, constraint.Value);
}
return new Route(routeUrl, route.Defaults, routeConstraints, route.DataTokens, route.RouteHandler);
}
private static RouteEntry CreateLocalizedRouteEntry(string name, Route route)
{
var localizedRouteEntryName = string.IsNullOrEmpty(name) ? null : name + "_Localized";
return new RouteEntry(localizedRouteEntryName, route);
}
private static void AddRouteEntry(Type subRouteCollectionType, object subRoutes, RouteEntry newEntry)
{
var addMethodInfo = subRouteCollectionType.GetMethod("Add");
addMethodInfo.Invoke(subRoutes, new[] { newEntry });
}
private static RouteBase CreateLinkGenerationRoute(Route innerRoute)
{
var linkGenerationRouteType = Type.GetType("System.Web.Mvc.Routing.LinkGenerationRoute, System.Web.Mvc");
return (RouteBase)Activator.CreateInstance(linkGenerationRouteType, innerRoute);
}
}
Ensuite, il suffit d'appeler cette méthode à la place de MapMvcAttributeRoutes
.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// Call to register your localized and default attribute routes
routes.MapLocalizedMvcAttributeRoutes(
urlPrefix: "{culture}/",
constraints: new { culture = new CultureConstraint(defaultCulture: "nl", pattern: "[a-z]{2}") }
);
routes.MapRoute(
name: "DefaultWithCulture",
url: "{culture}/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
constraints: new { culture = new CultureConstraint(defaultCulture: "nl", pattern: "[a-z]{2}") }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { culture = "nl", controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
Incredible post par NightOwl888. Il manque cependant quelque chose - les itinéraires d'attributs de génération d'URL normaux (non localisés), qui sont ajoutés par réflexion, nécessitent également un paramètre de culture par défaut, sinon vous obtenez un paramètre de requête dans l'URL.
? culture = nl
Afin d'éviter cela, ces modifications doivent être effectuées:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Routing;
using System.Web.Routing;
namespace Endpoints.WebPublic.Infrastructure.Routing
{
public static class RouteCollectionExtensions
{
public static void MapLocalizedMvcAttributeRoutes(this RouteCollection routes, string urlPrefix, object defaults, object constraints)
{
MapLocalizedMvcAttributeRoutes(routes, urlPrefix, new RouteValueDictionary(defaults), new RouteValueDictionary(constraints));
}
public static void MapLocalizedMvcAttributeRoutes(this RouteCollection routes, string urlPrefix, RouteValueDictionary defaults, RouteValueDictionary constraints)
{
var routeCollectionRouteType = Type.GetType("System.Web.Mvc.Routing.RouteCollectionRoute, System.Web.Mvc");
var subRouteCollectionType = Type.GetType("System.Web.Mvc.Routing.SubRouteCollection, System.Web.Mvc");
FieldInfo subRoutesInfo = routeCollectionRouteType.GetField("_subRoutes", BindingFlags.NonPublic | BindingFlags.Instance);
var subRoutes = Activator.CreateInstance(subRouteCollectionType);
var routeEntries = Activator.CreateInstance(routeCollectionRouteType, subRoutes);
// Add the route entries collection first to the route collection
routes.Add((RouteBase)routeEntries);
var localizedRouteTable = new RouteCollection();
// Get a copy of the attribute routes
localizedRouteTable.MapMvcAttributeRoutes();
foreach (var routeBase in localizedRouteTable)
{
if (routeBase.GetType().Equals(routeCollectionRouteType))
{
// Get the value of the _subRoutes field
var tempSubRoutes = subRoutesInfo.GetValue(routeBase);
// Get the PropertyInfo for the Entries property
PropertyInfo entriesInfo = subRouteCollectionType.GetProperty("Entries");
if (entriesInfo.PropertyType.GetInterfaces().Contains(typeof(IEnumerable)))
{
foreach (RouteEntry routeEntry in (IEnumerable)entriesInfo.GetValue(tempSubRoutes))
{
var route = routeEntry.Route;
// Create the localized route
var localizedRoute = CreateLocalizedRoute(route, urlPrefix, constraints);
// Add the localized route entry
var localizedRouteEntry = CreateLocalizedRouteEntry(routeEntry.Name, localizedRoute);
AddRouteEntry(subRouteCollectionType, subRoutes, localizedRouteEntry);
// Add the default route entry
AddRouteEntry(subRouteCollectionType, subRoutes, routeEntry);
// Add the localized link generation route
var localizedLinkGenerationRoute = CreateLinkGenerationRoute(localizedRoute);
routes.Add(localizedLinkGenerationRoute);
// Add the default link generation route
//FIX: needed for default culture on normal attribute route
var newDefaults = new RouteValueDictionary(defaults);
route.Defaults.ToList().ForEach(x => newDefaults.Add(x.Key, x.Value));
var routeWithNewDefaults = new Route(route.Url, newDefaults, route.Constraints, route.DataTokens, route.RouteHandler);
var linkGenerationRoute = CreateLinkGenerationRoute(routeWithNewDefaults);
routes.Add(linkGenerationRoute);
}
}
}
}
}
private static Route CreateLocalizedRoute(Route route, string urlPrefix, RouteValueDictionary constraints)
{
// Add the URL prefix
var routeUrl = urlPrefix + route.Url;
// Combine the constraints
var routeConstraints = new RouteValueDictionary(constraints);
foreach (var constraint in route.Constraints)
{
routeConstraints.Add(constraint.Key, constraint.Value);
}
return new Route(routeUrl, route.Defaults, routeConstraints, route.DataTokens, route.RouteHandler);
}
private static RouteEntry CreateLocalizedRouteEntry(string name, Route route)
{
var localizedRouteEntryName = string.IsNullOrEmpty(name) ? null : name + "_Localized";
return new RouteEntry(localizedRouteEntryName, route);
}
private static void AddRouteEntry(Type subRouteCollectionType, object subRoutes, RouteEntry newEntry)
{
var addMethodInfo = subRouteCollectionType.GetMethod("Add");
addMethodInfo.Invoke(subRoutes, new[] { newEntry });
}
private static RouteBase CreateLinkGenerationRoute(Route innerRoute)
{
var linkGenerationRouteType = Type.GetType("System.Web.Mvc.Routing.LinkGenerationRoute, System.Web.Mvc");
return (RouteBase)Activator.CreateInstance(linkGenerationRouteType, innerRoute);
}
}
}
Et pour attribuer l'enregistrement des routes:
RouteTable.Routes.MapLocalizedMvcAttributeRoutes(
urlPrefix: "{culture}/",
defaults: new { culture = "nl" },
constraints: new { culture = new CultureConstraint(defaultCulture: "nl", pattern: "[a-z]{2}") }
);
Et en fait, après un certain temps, j’ai eu besoin d’ajouter une traduction d’URL, j’en ai donc creusé plus, et il apparaît qu’il n’est pas nécessaire de procéder au piratage par réflexion décrit. Les gars d'ASP.NET y ont pensé. Il existe une solution beaucoup plus propre. Vous pouvez plutôt étendre un DefaultDirectRouteProvider comme ceci:
public static class RouteCollectionExtensions
{
public static void MapLocalizedMvcAttributeRoutes(this RouteCollection routes, string defaultCulture)
{
var routeProvider = new LocalizeDirectRouteProvider(
"{culture}/",
defaultCulture
);
routes.MapMvcAttributeRoutes(routeProvider);
}
}
class LocalizeDirectRouteProvider : DefaultDirectRouteProvider
{
ILogger _log = LogManager.GetCurrentClassLogger();
string _urlPrefix;
string _defaultCulture;
RouteValueDictionary _constraints;
public LocalizeDirectRouteProvider(string urlPrefix, string defaultCulture)
{
_urlPrefix = urlPrefix;
_defaultCulture = defaultCulture;
_constraints = new RouteValueDictionary() { { "culture", new CultureConstraint(defaultCulture: defaultCulture) } };
}
protected override IReadOnlyList<RouteEntry> GetActionDirectRoutes(
ActionDescriptor actionDescriptor,
IReadOnlyList<IDirectRouteFactory> factories,
IInlineConstraintResolver constraintResolver)
{
var originalEntries = base.GetActionDirectRoutes(actionDescriptor, factories, constraintResolver);
var finalEntries = new List<RouteEntry>();
foreach (RouteEntry originalEntry in originalEntries)
{
var localizedRoute = CreateLocalizedRoute(originalEntry.Route, _urlPrefix, _constraints);
var localizedRouteEntry = CreateLocalizedRouteEntry(originalEntry.Name, localizedRoute);
finalEntries.Add(localizedRouteEntry);
originalEntry.Route.Defaults.Add("culture", _defaultCulture);
finalEntries.Add(originalEntry);
}
return finalEntries;
}
private Route CreateLocalizedRoute(Route route, string urlPrefix, RouteValueDictionary constraints)
{
// Add the URL prefix
var routeUrl = urlPrefix + route.Url;
// Combine the constraints
var routeConstraints = new RouteValueDictionary(constraints);
foreach (var constraint in route.Constraints)
{
routeConstraints.Add(constraint.Key, constraint.Value);
}
return new Route(routeUrl, route.Defaults, routeConstraints, route.DataTokens, route.RouteHandler);
}
private RouteEntry CreateLocalizedRouteEntry(string name, Route route)
{
var localizedRouteEntryName = string.IsNullOrEmpty(name) ? null : name + "_Localized";
return new RouteEntry(localizedRouteEntryName, route);
}
}
Il existe une solution basée sur cela, y compris la traduction d’URL ici: https://github.com/boudinov/mvc-5-routing-localization