+1-888-365-2779
Try Now
More in this section
Categories
Bloggers
Blogs RSS feed

Making the Sitefinity LanguageSelector Lead You to the Translated Details View Page

by Boyan Barnev
As it often happens in life we take some things for granted. And never think about them unless someone asks us the "why this stuff works like that" question.

This time the question is about our LanguageSelector widget, and in particular - "If I am viewing the details page of, let's say a News item, and change the language using the LanguageSelector widget, I expect to see the translation of the news item article in the selected language. Why am I returned to the page listing all news items for the selected language?".

The current blog post, apart from a useful how-to article demonstrating how you can achieve something new with Sitefinity, would also like to elaborate on why stuff works this way, and what were the design limitations we faced when implementing it that way.

As most of you are aware, Sitefinity ContentView widgets are capable of displaying both a list of content items, and also display in a dynamically generated details page, which shows the full item details upon clicking on a particular item. How this works actually, is that we pass the clicked item's URL as a URL parameter to the current page you're browsing (or a different page that has been specified by you where you want to have the item details open). When the page loads, the ContentView control on it says "Hey, I'll take that call", or in other words it tells Sitefinity that this is not a page in the Sitemap that should be opened, but rather a URL parameter that the ContentView widget of the particular type will handle. And it handles it with grace - you end up on a nice dynamic page where the selected item details are opened.

However, what happens when you change the language using our LanguageSelector?
To answer that question maybe it's be good to provide a brief overview of how this widget works. When you load a page that has a LanguageSelector widget placed on it, the widget will get all available translations for that page, and will resolve the absolute page URL for each of these languages. Then it will construct a list of available selections (the language choices you see) and make them lead to the corresponding absolute page URLs. So at the end when you click on a German language, for example, you get to the German translation of the page.

If you have been reading the above paragraph carefully you probably already realized what's causing the "problem" this post deals with. The URLs that you're clicking on when you change the language are the absolute URLs in the specified language version of the page you're at, they do not include the URL in the specified language of the details item you're viewing. And this is so, because unlike pages which are always viewed in the context of the Sitemap, the detail item is a dynamic URL parameter, from which we cannot easily resolve the actual item in another widget but the ContentView that handles items of this type. Some would say - why not just append the already available URL parameter to the resolved absolute URL of the page in the specified language? The answer is - because the UrlName of the detail item can be different in the different translations.

Now that we've identified what are the limitations, let's demonstrate how we can make our custom-tailored solution that would suit our specific needs.

First, we need to create a custom widget. This custom widget will inherit from the Telerik.Sitefinity.Localization.Web.UI.LanguageSelectorControl class.

Inside we can define a custom property that will determine the widget behavior - so we can easily switch between the default mode (clicking on a language opens the page translation) or our custom mode (clicking on a language opens the detail item translation). It can just be a boolean property that would automatically appear in the widget's Advanced settings, for example:
public class LanguageSelectorCustom : LanguageSelectorControl
    {
        public bool AppendDetailItemOnLanguageChange
        {
            get
            {
                return defaultValue;
            }
            set
            {
                this.defaultValue = value;
            }
        }
        private bool defaultValue = true;

Implementing it this way would result in our widget having the custom mode selected by default, and if you edit the property in the widget's Advanced options, and set it to false, it will behave just as the out of the box widget.

Our next step is to override the GetUrlForLanguage() method of the LanguageSelectorControl - this is where we resolve the absolute page URL for the available languages.

Inside we can use the System.Web.UI.ControlExtensions.GetUrlParameterString() method which will return only the URL parameter passed to the current page. This UrlParameter is actually the detail item's URL so we can then easily implement a query that would return the actual item that matches this UrlName. All in all something like this:
var url = base.GetUrlForLanguage(ci);
            if (AppendDetailItemOnLanguageChange && !this.IsDesignMode())
            {
                //get detail item URL parameter
                var paramString = this.GetUrlParameterString(false);
                if (!paramString.IsNullOrEmpty())
                {
                    //get the detail item UrlName
                    var detailItemUrl = paramString.TrimStart('/');

For the purposes of this blog post I've created a dynamic module called Projects using Sitefinity's Module Builder. So here's how you can then get the item that matches this UrlName we've obtained above:
//get the actual item that matches this UrlName
                    var providerName = String.Empty;
                    DynamicModuleManager dynamicModuleManager = DynamicModuleManager.GetManager(providerName);
                    Type projectType = TypeResolutionService.ResolveType("Telerik.Sitefinity.DynamicTypes.Model.Projects.Project");
                    var matchingItem = dynamicModuleManager.GetDataItems(projectType)
                        .Where(di => di.UrlName == detailItemUrl
                            && di.Status == Telerik.Sitefinity.GenericContent.Model.ContentLifecycleStatus.Live)
                        .SingleOrDefault();

You can change the baove implementation depending on your specific requirements - if you want the custom widget to work with other content item types, just use the corresponding manager. For example if you want it to work with News - use NewsManager, and the respective query to get an item matching this UrlName. For more information you can refer to our Modules section from the Sitefinity  Documentation's Developers Guide .

The last task we need to complete is to resolve the obtained item's UrlName in the corresponding culture. For example:
if (matchingItem != null)
                    {
                        //set culture to langName
                        var tempCI = CultureInfo.CurrentUICulture;
                        Thread.CurrentThread.CurrentUICulture = ci;
                        //get UrlName for culture
                        var urlNameForLanguage = matchingItem.UrlName[ci];
                        //set back original culture
                        Thread.CurrentThread.CurrentUICulture = tempCI;

And finally append the resolved item URL to the absolute page URL:
//Resolve the page URL and append the detail item URL to it
                        url = RouteHelper.ResolveUrl(url, UrlResolveOptions.Absolute);
                        url += "/" + urlNameForLanguage;

The complete sample can be found attached to this post:
LanguageSelectorCustom sample
Just add it to your project, compile it and register the widget in Sitefinity. I'd recommend registering it using Sitefinity Thunder as it literally takes 5 seconds to do so.

As usual here's also a demonstrative video of the end result.

I hope you enjoyed today's How-to post. Stay tuned for more, and in the meantime any comments or questions are welcome.

1 comment

Leave a comment
  1. Yitro Kurniawan Mar 02, 2014

    Hi.

     Thanks for the solution, but how if I have many dynamic modules? Iterating through all modules seems not a best practice. Any solution for this?  Thanks

    Leave a comment