turquoiseowl / i18n

Smart internationalization for ASP.NET
Other
556 stars 155 forks source link

Custom Language selection menu #211

Open tomasr78 opened 9 years ago

tomasr78 commented 9 years ago

I am trying to develop custom language selection menu. Just simple menu where available languages listed with direct url to translated web pages.

var langs = LanguageHelpers.GetAppLanguages().OrderBy(x => x.Key).ToArray();

for (var index = 0; index < langs.Length; index++)
{
    var langTag=string.Empty;
    if (index!=0)
    {
        langTag = "/" + langs[index].Key;
    }        

    var url = langTag + Request.Url.AbsolutePath;

  @url

    <a href="@url">@langs[index].Value.NativeNameTitleCase</a>    
}

The problem with code above is that HREF of A element automatically pre-tagged with language tag. I do it by myself but it seems that i18n do this automatically too.

Let's say I am at page

https://localhost:44301/lt/pdf-word

The HTML code generated by code above will look like

 <a href="/lt/pdf-word">English</a>
 <a href="/lt/pdf-word">Lietuvių</a>

i18n automatically parsed HREF tag and added additional /LT/ language tag for English HREF.

Is it possible to disable HREF tagging for some A HTML elements or any other solution to solve the problem?

p.s. If I output value @url in razor view as text I get correct url

/pdf-word <-Not tagged, as needed! /lt/pdf-word

tomasr78 commented 9 years ago

Just want to add comment about your provided language selection code in Explicit User Language Selection section.

This code create duplicate URL of the same content and it is not Google friendly, should be used with care if google ranking maters.

@Html.ActionLink( linkText: nativelangname, actionName: "SetLanguage", controllerName: "Account", routeValues: new { langtag = langtag, returnUrl = Request.Url }, htmlAttributes: new { title = title } )

turquoiseowl commented 9 years ago

If a URL that is already localized is being patched on the way out, that is a bug.

Are you able to debug it? The method in question is EarlyUrlLocalizer.ProcessOutgoing. You will see that the code checks whether a URL is already localized before localizing it.

Thanks.

tomasr78 commented 9 years ago

You misunderstood me. Let's start from simple question. I have simple code below in my demo app. If we run web page with language selector, let's say http://localhost:port/LT the href="@url" will contain patched url with language selector /lt/ but @url surounded with A tag will have absolute path "/". The generated code would be

<a href="/lt">/</a>

Does it mean that i18n looks for HREF tags and patch only url inside these tags?

        var url = Request.Url.AbsolutePath;
        <a href="@url">@url</a>
turquoiseowl commented 9 years ago

Does it mean that i18n looks for HREF tags and patch only url inside these tags?

Yes.

turquoiseowl commented 9 years ago

In case it helps, you can control outgoing (late) URL localization prior to it happening for a request and subject url. E.g.

protected void Application_Start()
{
...

i18n.UrlLocalizer.OutgoingUrlFilters += delegate(string url, Uri currentRequestUrl) {
    Uri uri;
    if (Uri.TryCreate(url, UriKind.Absolute, out uri)
        || Uri.TryCreate(currentRequestUrl, url, out uri)) {
        if (uri.LocalPath.IndexOf("/Api", StringComparison.OrdinalIgnoreCase) != -1) {
            return false; }
        if (uri.LocalPath.IndexOf("xdomain", StringComparison.OrdinalIgnoreCase) != -1) {
            return false; }
    }
    return true;
};
}
turquoiseowl commented 9 years ago

As a general solution, you can add a querystring to the URLs NOT to be localized and test for that in the i18n.UrlLocalizer.OutgoingUrlFilters delegate. E.g. \fr\pdf-word?nolocal

tomasr78 commented 9 years ago

Does it mean that i18n looks for HREF tags and patch only url inside these tags? Yes.

Could you add html tag, let's say data-nolocal="true" to let exclude html href tags from modifying?

Now I face several problems without having option to control href patching.

The first one as I explained in first post to create custom language selector. Your suggested solution to use query string "\fr\pdf-word?nolocal" and then exclude url by query string is working but not perfect, it creates duplicate URL and looks more like a hack and not solution.

The second problem is with canonical html tag.

I have url http://www.domain.com/lt/terms and would like to have canonical html tag in that web page with url http://www.domain.com/terms I don't find a way to set desired url in canonical tag because i18n modify href.

It would be really useful option where I could exclude HTML tags and don't let i18n modify href.

turquoiseowl commented 9 years ago

If I understand you correctly, you suggest that the UrlLocalizer code that parses the response in order to extract URLs be modified to test for the presence of URLs in HTML tags that include an attribute along the lines of data-i18n-url-nolocal="true", and to ignore such URLs in that case.

That would be a nice feature. It should be possible to include that logic in the regex that currently parses for URLs. Indeed that regex could be exposed publicly to allow for complete customization. Would you care to have a stab at it yourself? I'm a bit snowed under myself at the moment. Accompanying unit tests for it would be nice too.

tomasr78 commented 9 years ago

Everything is correct that you wrote. I will try to develop this feature and post a code!

tomasr78 commented 9 years ago

Sorry for delay but I was busy with other projects. Today I have implemented the feature, will post to repository and let you know.

tomasr78 commented 9 years ago

I have made changes to repository and updated with new feature, html tags with data-i18n-url-nolocal data tag attribute from now will be ignored. tested on Dev and Prod servers for one week, working as expected.

tomasr78 commented 9 years ago

The examples "The custom language selector". Produce clear direct language selection menu without query tags.

    var langs = LanguageHelpers.GetAppLanguages().OrderBy(x => x.Key).ToArray();

    for (var index = 0; index < langs.Length; index++)
    {
        string url;
        if (index == 0)
        {
            url = Request.Url.AbsolutePath;
        }
        else
        {
            var langTag = "/" + langs[index].Key;
            url = langTag + Request.Url.AbsolutePath.TrimEnd(Convert.ToChar("/"));
        }

        <a data-i18n-url-nolocal href="@url">@langs[index].Value.NativeNameTitleCase</a>
    }

}
tomasr78 commented 9 years ago

Canonical link example, create simple canonical links for web pages using these rules: removes query strings, removes trailing slashes, removes EN language tag from URL, removes any language selector from URL.

Razor

  @{
        if (ViewData["CanonicalUrl"] != null)
        {
            <link data-i18n-url-nolocal rel="canonical" href="@ViewData["CanonicalUrl"]" />
        }
    }

Action Filter

  public class CanonicalUrlAttribute : ActionFilterAttribute
    {
        public CanonicalUrlAttribute()
        {
            IncludeLangTag = true;
        }

        public bool IncludeLangTag { get; set; }

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            if (filterContext.HttpContext.Request.Url != null)
            {

                var absolutePath = filterContext.HttpContext.Request.Url.ToString();

                if (IncludeLangTag)
                {
                    var langTag = filterContext.HttpContext.GetPrincipalAppLanguageForRequest();
                    var host = filterContext.HttpContext.Request.Url.Authority;
                    var newHost = host + "/" + langTag;
                    absolutePath = absolutePath.Replace(host, newHost);
                }

                //Removing query string
                absolutePath = absolutePath.RemoveRight("?");

                //Removing trailing slash
                absolutePath = absolutePath.TrimEnd(Convert.ToChar("/"));

                //Removing EN language tag from URL
                absolutePath = absolutePath.Replace("/en", "");

                filterContext.Controller.ViewData["CanonicalUrl"] = absolutePath;

            }
            base.OnActionExecuting(filterContext);
        }
    }

Controller

//These rules will be applied to all actions: removes query strings, removes trailing slashes, removes EN language tag from URL
[CanonicalUrl]
public class HomeController : Controller
{

}
//These rules will be applied to all actions: removes query strings, removes trailing slashes, removes EN language tag from URL
//Additional rules will be applied: removes any language selector from URL
//It means that all /terms web pages will have canonical tags without language tag. Useful if no translation is available for a web page to let search engine know that it is the same page with different urls.
[CanonicalUrl(IncludeLangTag=false)]
public ActionResult Terms()
{
   return View("Terms");
}
tomasr78 commented 8 years ago

I have committed data-i18n-url-nolocal feature implementation half year ago but it seems it is missing in new release. Have you implemented your own solution to ignore HREF tags localization?

turquoiseowl commented 8 years ago

I can't remember either adding or removing it. Was there a Pull Request? Was it just a change to UrlsToExcludeFromProcessing?

tomasr78 commented 8 years ago

I have submitted pull request and it is missing in latest releases. I also submitted tests and tested my implementation in production and it works perfectly.

And no, it is not the same as UrlsToExcludeFromProcessing. The UrlsToExcludeFromProcessing is global exclusion and my implementation of data-i18n-url-nolocal data html tag can be used on certain html tags to exclude localization.