23

I need multi-language URL route of existing controller. Let me explain more:

I have a controller with name "Product" and View with name "Software"; therefore, by default if the user enters "http://example.com/en/Product/Software", get right content (that really exists in http://example.com/Product/Software),

However, if another user -- a French user -- types "http://example.com/fr/Produits/logiciels", must get above controller and show with right content (same http://example.com/Product/Software but with French text).

Note: I set the route table with "{language}/{controller}/{action}/{id}"

Any other invalid URL must show the 404 page.

Is it possible?

Cœur
  • 37,241
  • 25
  • 195
  • 267
Hamid
  • 1,099
  • 3
  • 22
  • 37
  • Actually that's not a very good idea if you're concerned about search engine ranking. You could rather redirect to English page always OR use a standard canonical URL for all instances of the same entity. – Sedat Kapanoglu Feb 02 '11 at 21:20

4 Answers4

12

Building upon Dan's post, I'm using the beneath for translating my controller and action names.

I created a table to store the values, this could and probably should be held in the resource files to keep everything together; however I used a database table as it works better with my companies processes.

CREATE TABLE [dbo].[RoutingTranslations](
[RouteId] [int] IDENTITY(1,1) NOT NULL,
[ControllerName] [nvarchar](50) NOT NULL,
[ActionName] [nvarchar](50) NOT NULL,
[ControllerDisplayName] [nvarchar](50) NOT NULL,
[ActionDisplayName] [nvarchar](50) NOT NULL,
[LanguageCode] [varchar](10) NOT NULL)

The RouteConfig.cs file was then changed to:

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        //Build up routing table based from the database.  
        //This will stop us from having to create shedloads of these statements each time a new language, controller or action is added
        using (GeneralEntities db = new GeneralEntities())
        {
            List<RoutingTranslation> rt = db.RoutingTranslations.ToList();
            foreach (var r in rt)
            {
                routes.MapRoute(
                    name: r.LanguageCode + r.ControllerDisplayName + r.ActionDisplayName,
                    url: r.LanguageCode + "/" + r.ControllerDisplayName + "/" + r.ActionDisplayName + "/{id}",
                    defaults: new { culture = r.LanguageCode, controller = r.ControllerName, action = r.ActionName, id = UrlParameter.Optional },
                    constraints: new { culture = r.LanguageCode }
                );
            }                
        }

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

    }
}

By default this will always use the English controller and action names, but allows you to provide an override by entering the values into the table.

(My internationalization code is largely based from this great blog post. http://afana.me/post/aspnet-mvc-internationalization-part-2.aspx)

Jay
  • 878
  • 3
  • 12
  • 22
  • This won't work with newly added routes though, will it since register routes is only executed a startup? – alex Apr 29 '16 at 00:03
6

As has been suggested before, this does depart from convention where the website urls (and routes) use English.

Nevertheless, it is possible, but in order to do it, you'll probably have to look at generating one route per action for every foreign language. So for a website with 20 actions and three languages (English, French, and German), you'll need 41 routes (20 French, 20 German and 1 English). Not the most efficient system, I admit, but it works as you want it to.

//You'll only need one of these, which is the default.
routes.MapRoute(
  "English route",
  "en/{controller}/{action}/{id}"
  new { controller = "Home", action = "Index", language = "en" },
);

routes.MapRoute(
  "FrenchHome",
  "fr/Demarrer/Index/{id}",
  new { controller = "Home", action = "Index", language = "fr" }
);

routes.MapRoute(
  "GermanHome",
  "de/Heim/Index/{id}", //'Heim' is, I believe the correct usage of Home in German.
  new { controller = "Home", action = "Index", language = "de" }
);

//Some more routes...

routes.MapRoute(
  "FrenchSoftware",
  "fr/Produit/Logiciels/{id}",
  new { controller = "Product", action = "Software", language = "fr" }
);

routes.MapRoute(
  "GermanSoftware",
  "de/Produkt/Software/{id}", //In this instance, Software should be the same in German and English.
  new { controller = "Product", action = "Software", language = "de" }
);

//And finally, the 404 action.
routes.MapRoute(
  "Catchall",
  "{language}/{*catchall}",
  new { controller = "Home", action = "PageNotFound", language = "en" },
  new { language = "^(en|fr|de)$" }
);

//This is for the folks who didn't put a language in their url.
routes.MapRoute(
  "Catchall",
  "{*catchall}",
  new { controller = "Home", action = "PageNotFound", language = "en" }
);

In your actions, for example Product/Software...

public ActionResult Software(string language, int id)
{
  //This would go off to the DAL and get the content in whatever language you want.
  ProductModel model = ProductService.GetSoftware(language, id);

  return View(model);
}

I would LOVE it if somebody came along and said that there's a better way of doing this, because I agree that having the url in a foreign language isn't good, and given that the Internet itself is moving towards allowing non-Roman characters in urls, the sooner we look at solutions to this, the better.

Not only that, but I know proud French people don't like to see their website urls contain English. :)

Dan Atkinson
  • 11,391
  • 14
  • 81
  • 114
  • 1
    (someone coming along with a better way...) What about not caring what is passed into the action method? Instead, route it like normal. But have an overload for OnActionExecuting() on your base controller(s) that sets the CultureUI accordingly based on the route (e.g. it would peek at the Request.Url in the filterContext). Or a more strongly-typed version would be to create your own LangaugeRoute() class that inherits from Route. Add a new property on it, LanguageCode, and set it in your MapRoute here in your answer. Then, you can cast it strongly-typed in your OnActionExecuting(). – eduncan911 Sep 24 '12 at 01:24
  • I like this, but checking the current culture has its risks, mainly relating to the possibility that you may not actually have a version of the website. For example, Turkish has quite a few issues when it comes to Culture (Google the Turkish-i problem). In any case, you'll have to create a bunch of if/else or casing statements somewhere to handle the translations that you do have in order to default to another if it's not correct. – Dan Atkinson Sep 24 '12 at 11:03
  • 1
    Also, this is an excellent blog post by Maarten Balliauw on the subject. http://blog.maartenballiauw.be/post/2010/01/26/Translating-routes-(ASPNET-MVC-and-Webforms).aspx – Dan Atkinson Sep 24 '12 at 11:05
  • @DanAtkinson thank you for your answer. It helped me a lot. But how to create now ActionLinks and to mantain the right path for the selected language? For example if i will have the selected language German, then having an @Html.ActionLink("HomeLink", "Index", "Home") I expect to receive href="de/Heim/Index" however i will receive href="en/Home/Index"... or if the "FrenchSoftware" route will be first in the line, i will receive href="fr/Produit/Logiciel". What should i do to receive the right href for the selected language? – John Jun 15 '14 at 19:05
  • @John Have a look at this post - http://afana.me/post/aspnet-mvc-internationalization-part-2.aspx. Using `Request.UserLanguage` should lead you in the right direction. – Dan Atkinson Jun 16 '14 at 00:22
2

You should have url something like "http://mysite.com/en/Product/Software" for English and "http://mysite.com/fr/Product/Software" for French, which would make much sense.

Use the same view for both.

Happy coding.

Ravi Vanapalli
  • 9,805
  • 3
  • 33
  • 43
  • This means non-friendly URLs, since not everyone in the world speaks English – erikkallen Jan 27 '10 at 11:12
  • By default you should use English erikkallen – Ravi Vanapalli Jan 27 '10 at 11:14
  • 1
    @erikkallen: So should the OP aim to provide URLs in all possible languages? The URL should identify a resource, and in doing so be as descriptive as possible of the nature of that resource. This does *not* mean that there should be multiple URLs to cater for multiple languages. The language in which the resource is rendered is what matters. Upvoted. – Will Vousden Jan 27 '10 at 11:24
  • Many thanks for fast response, http://mysite.com/en/Product/Software is ok for me, but how to do it ??? how to find right controller and view and set it??? Note: i using "{language}/{controller}/{action}/{id}" for route – Hamid Jan 27 '10 at 12:17
  • Hamid please check this link http://stackoverflow.com/questions/725220/language-for-controllers-names-in-asp-net-mvc answered by Fred – Ravi Vanapalli Jan 27 '10 at 13:31
1

I strongly recommend the following approach to implement the multi-language in a MVC 5 (and <) web application, because I find it to be the most versatile one among those I've tried in the latest years.

You basically need to implement three things:

  • A multi-language aware route to handle incoming URLs (if you're using MVC5 or above you could also go for Attribute-based routing instead, but I still prefer to use a global rule to handle this).
  • A LocalizationAttribute to handle these kinds of multi-language requests.
  • An helper method to generate these URLs within your application (Html.ActionLink and/or Url.Action extension methods).

See this answer for further details and code samples.

For additional info and further samples on this topic you can also read you can also read this blog post that I wrote on this topic.

Darkseal
  • 9,205
  • 8
  • 78
  • 111