AttributeRouting icon indicating copy to clipboard operation
AttributeRouting copied to clipboard

Extension Html.ActionLink method to create URL link in specific culture

Open marccals opened this issue 11 years ago • 12 comments

I think that It would be useful to have an extension method for Html.ActionLink to specify in which culture has to be created the URL link. , Html.ActionLink("LinkText", "Action", "Controller", "Culture").

There is place where It can be useful for example, for reload the same page with a different culture.

I have been searching in AttributeRouting code to discover where the url links are created but I haven't found. Can you explain me it?

If you are interested I will do the extension method and I will make a pull request.

Thanks

marccals avatar Mar 01 '13 13:03 marccals

I think this is unnecessary overhead. If you need the culture/language for filling out url params, you can just pass this info via routeData when generating the links. However, I think it's more straightforward to simply set the CurrentUICulture for the thread and let AR handle the route translations from there.

If you find you are relying on a url param for the culture/language, give me more details on what you're doing. I'd prefer to come up with a more hands-off approach if necessary.

mccalltd avatar Mar 02 '13 20:03 mccalltd

I will try to explain me better.

Imagine that I have one news page in my web page that supports 3 language, english, catalan and spanish. The news page will be accessible with /en/news, /es/noticias and /ca/noticies

Suppose that a user with English as preferred language in his browser go at our website and click to news link and he goes to /en/news, when he is in news page he decides to change the language to catalan. To do this I would like to put two links on the top of page for change the language, one link for catalan and one link for spanish, the catalan link goes to /ca/noticies and spanish link goes to /es/noticias. Users click one of the links to change the language, and after do it he still in the same page but with different language

If the user's browser preferred language is english, the CurrentUICulture set will be english, the problem is that I don't know how to generate links to change the language and still in the same page. How Can I generate a link in catalan or spanish if CurrentCultureUI is set to English?

RouteData with parameter language doesn't work because as you suggested me in issue https://github.com/mccalltd/AttributeRouting/issues/203 I would use urls as

[RoutePrefix("en/Shops", TranslationKey = "Shops")]

Thanks

marccals avatar Mar 04 '13 20:03 marccals

Hello,

That is also a problem I have ... To change the culture I need to call another action.

In that action It can be tricky to redirect to the source URL from which the action was called.

Refresh the current view but with a different culture value would be useful. Then the CurrentHandler would take care of changing the culture based on the culture parameter value.

Does this make sense?

Thank You, Miguel

mdmoura avatar Mar 05 '13 11:03 mdmoura

I see. Go ahead and implement a helper and submit the pull request. We'll move on from there. I may give it a crack myself too. Will let you know if I do.

mccalltd avatar Mar 05 '13 19:03 mccalltd

Mccalltd I tryed to do It, but I haven't found where the URL is generated in AttributeRouting when I invoke a Html.ActionLink. To do it please can you explain me in which classes the URL are generated when I invoke an Html.ActionLink? The creation is related to URLHelperExtension class?

Thanks

marccals avatar Mar 05 '13 22:03 marccals

Shapper I understand what you say to solve It, Your culture selectors links always call the same action, no?

But I can't figure out the code of that action to redirect to a translated URL. Do you have a sample code of that action? It's curiosity.

As I said to mccalltd I will try to do and Html.ActionLink extension to do It.

marccals avatar Mar 05 '13 22:03 marccals

@marccals:

I have a CultureController with an action Modify(String culture). My culture selectors links always point to this action that returns Home/Index.

Inside this action I tried to find the requested URL. For example:

return Redirect(HttpContext.Request.UrlReferrer.AbsoluteUri);

I also tried RawUrl and other options ... But I always found some problem with this.

Sometime ago I created a Github project for using AR with Localization: https://github.com/shapper/MVCLAR

Unfortunate, lately I didn't have the time to improve it and test it more.

I hope to get my hands on it again soon. You can see it there how I am doing it. Check CultureController, AttributeRouting, CultureRouteHandler and RouteTranslationProvider.

My first idea was to have something like: @Html.ActionLink("English", new { culture = "en" }); @Html.ActionLink("Portuguese", new { culture = "pt" });

These links would be placed on a master page. They would always reload the current view changing the culture value.

CultureRouteHandler would be responsible for changing the culture.

But I wasn't able to make this work ...

mdmoura avatar Mar 05 '13 23:03 mdmoura

I'm having the same problem. Fundamentally while I'm running on an English page, I need to have a link that goes to the French (for example) version of the same page. There doesn't seem to be a way to do this. If you put a {language} definition in the route you can pass it the language, but then what I get is something like /fr/EnglishController/EnglishAction (because those routes get created as well). Without the {language} parameter as mentioned in the linked issue, I don't have the extra routes but there's no way to tell it "I really want a French link".

You can kind of fake it by going back to the same page you're already on with a parameter set and then using code such that if you see that you swap the culture, then create a redirect link to exactly the same place you are. In that case you should wind up on the French page (creating the Url.Action for the redirect will now do so in French), but it's pretty ugly.

I guess what we do need is an extension method that lets you force what language you want it to use, ignoring the current culture.

Tridus avatar Mar 13 '13 18:03 Tridus

Also, I was able to work around it in the short term by using some trickery. When I want to write a URL in the other language, I change the current UICulture on the thread to the other language. Create the URL. Change it back to whatever it was originally.

It's ugly, but it does work.

Tridus avatar Mar 13 '13 19:03 Tridus

You could also create an extension to the UrlHelper that looks for the appropriate culture. All AttributeRoutes stuff a "cultureName" key/value pair (where appropriate) in the route's data tokens. It would be bit of work, but would be a nice utility for this case.

mccalltd avatar Mar 13 '13 20:03 mccalltd

Sorry for my delay in answer.

I have been moved to another project for some weeks, When I get back to the project where I need It in some weeks I will implement this.

Thanks to all for your help.

marccals avatar Mar 21 '13 07:03 marccals

Hello,

Finally I have developed my own extension to get Url's in specific culture from AttributeRouting.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using AttributeRouting.Framework;
using AttributeRouting.Web.Mvc.Framework;

namespace Va.Utils
{
    public static class URLHelperLocalization
    {
        /// <summary>
        /// Returns action/controller URL in culture specified in cultureNameParameter
        /// </summary>
        public static string LocatedAction(this UrlHelper urlHelper, string actionName, string controllerName, string cultureName)
        {
            return LocatedAction(urlHelper, actionName, controllerName, null, cultureName);
        }

        /// <summary>
        /// Returns action/controller URL in culture specified in cultureNameParameter
        /// </summary>
        public static string LocatedAction(this UrlHelper urlHelper, string actionName, string controllerName, object routeValues, string cultureName)
        {
            RouteValueDictionary routeData = new RouteValueDictionary(new { Action = actionName, Controller = controllerName });

            routeData = Concat(routeData, routeValues);

            return LocatedAction(urlHelper, routeData, cultureName);
        }

        /// <summary>
        /// Returns routeData URL in culture specified in cultureNameParameter
        /// </summary>
        public static string LocatedAction(this UrlHelper urlHelper, RouteValueDictionary routeData, string cultureName)
        {
            string localizedUrl = string.Empty;

            //Get routes in original language
            foreach (AttributeRoute routeDefaultCulture in RouteTable.Routes.OfType<AttributeRoute>().Where(r => r.Translations != null))
            {
                string routeCulture = routeDefaultCulture.CultureName;

                if (routeDefaultCulture.GetVirtualPath(urlHelper.RequestContext, routeData) != null)
                {
                    //route match RouteData
                    localizedUrl = GetLocalizedUrl(urlHelper.RequestContext, routeDefaultCulture, routeData, cultureName);
                }
            }

            return localizedUrl;
        }

        /// <summary>
        /// Get translated route, if not found for specified cultureName returns default language URL
        /// </summary>
        private static string GetLocalizedUrl(RequestContext requestContext, AttributeRoute routeDefaultCulture, RouteValueDictionary routeData, string cultureName)
        {
            AttributeRoute localizedRoute = (AttributeRoute) routeDefaultCulture.Translations.FirstOrDefault(r => r.CultureName == cultureName);

            if (localizedRoute != null)
            {
                return localizedRoute.GetVirtualPath(requestContext, routeData).VirtualPath;
            }
            else
            {
                //If invoke GetVirtualPath from routeDefaultCulture It will return the route translated in current Default culture. A copy of route is created to have a Route object with native GetVirtualPath behaviour
                Route baseRoute = new Route(routeDefaultCulture.Url, routeDefaultCulture.Defaults, null, routeDefaultCulture.RouteHandler);

                return baseRoute.GetVirtualPath(requestContext, routeData).VirtualPath;
            }
        }

        private static RouteValueDictionary Concat(RouteValueDictionary routeValueDictionary, object routeValueDictionaryAsObject)
        {
            if (routeValueDictionaryAsObject != null)
            {
                foreach (var routeValue in new RouteValueDictionary(routeValueDictionaryAsObject))
                {
                    routeValueDictionary.Add(routeValue.Key, routeValue.Value);
                }
            }

            return routeValueDictionary;
        }
    }
}

marccals avatar Jan 29 '14 20:01 marccals