Jeremy Davis
Jeremy Davis
Sitecore, C# and web development
Article printed from: https://blog.jermdavis.dev/posts/2015/patterns-for-navigation-controls-part-5

Patterns for navigation controls - Language trees

Published 09 February 2015
Updated 26 June 2018
This is post 5 of 5 in a series titled Patterns for navigation controls

After looking at the navigation for sites with a single content tree in many language versions last week, this week we'll look at the other simple pattern for language navigation: When you have multiple trees of content with one language each and you need navigation links to let people swap between these content trees. This approach works best for websites where the structure of the content you need in each language differs significantly.

To set up this "microsite" pattern we need a content tree where we have a series of site roots, where each one has a tree of content in one language:

Language Content Tree

The "EnglishHome" tree contains content only in the English language. Ditto for Japanese, French and German trees.

Commonly when implementing the microsite patterns, you have separate domain names for each of the sites. For development purposes you can set these up in your hosts file. For example, the Sitecore installer added a host name for "TEST" when my test site was set up. So we can add language specific host names for our four microsites:

127.0.0.1	TEST	test.en	test.fr	test.ja	test.de

					

These names then need adding to your site bindings in IIS:

Bindings

And then you need to tell Sitecore to map these domain names to the language root items. This can be done through the <sites/> configuration in your Sitecore configuration. The best way to set this up is with a configuration patch file:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <sites>
      <site patch:before="*[@name='website']" name="nav-English" hostName="test.en" virtualFolder="/" physicalFolder="/"
            rootPath="/sitecore/content" startItem="/EnglishHome" language="en"
            database="web" content="web" domain="extranet" allowDebug="false" cacheHtml="true" htmlCacheSize="10MB" registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="5MB"
            filteredItemsCacheSize="2MB" enablePreview="false" enableWebEdit="false" enableDebugger="false" disableClientData="false" />

      <site patch:after="*[@name='nav-English']" name="nav-Japanese" hostName="test.ja" virtualFolder="/" physicalFolder="/"
            rootPath="/sitecore/content" startItem="/JapaneseHome" language="ja-JP"
            database="web" content="web" domain="extranet" allowDebug="false" cacheHtml="true" htmlCacheSize="10MB" registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="5MB"
            filteredItemsCacheSize="2MB" enablePreview="false" enableWebEdit="false" enableDebugger="false" disableClientData="false" />

      <site patch:after="*[@name='nav-Japanese']" name="nav-French" hostName="test.fr" virtualFolder="/" physicalFolder="/"
            rootPath="/sitecore/content" startItem="/FrenchHome" language="fr-FR"
            database="web" content="web" domain="extranet" allowDebug="false" cacheHtml="true" htmlCacheSize="10MB" registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="5MB"
            filteredItemsCacheSize="2MB" enablePreview="false" enableWebEdit="false" enableDebugger="false" disableClientData="false" />

      <site patch:after="*[@name='nav-French']" name="nav-German" hostName="test.de" virtualFolder="/" physicalFolder="/"
            rootPath="/sitecore/content" startItem="/GermanHome" language="de-DE"
            database="web" content="web" domain="extranet" allowDebug="false" cacheHtml="true" htmlCacheSize="10MB" registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="5MB"
            filteredItemsCacheSize="2MB" enablePreview="false" enableWebEdit="false" enableDebugger="false" disableClientData="false" />
    </sites>
  </sitecore>
</configuration>

					

The order of <site/> elements is important. So this patch makes sure that the domain-specific sites are inserted ahead of the default site. This means that Sitecore will match the language microsite domain names to their appropriate content before falling back to the "standard" site for other domains. The first patch item configures the English site, and inserts it ahead of the "website" (default) site. The subsequent items add the Japanese, French and German sites after the English one. Note how the name elements are all prefixed with "nav-". This will enable code to spot the important site bindings when it comes to generating the navigation. The hostName, language and startItem attributes are set correctly for each of the sites to map the host names to our root items. The remaining attributes can be set according to your needs for the sites.

Once the config is all set up, it's common to set the DisplayName property on the language site root items so that they are in the appropriate language:

Languages

With the configuration in place, you can put some code in place to display the microsite navigation links. This can start with some simple mark-up:

<asp:Repeater runat="server" ID="siteRepeater">
    <HeaderTemplate>
        <ul>
    </HeaderTemplate>
    <ItemTemplate>
        <li>
            <asp:HyperLink runat="server" ID="siteLink" />
        </li>
    </ItemTemplate>
    <FooterTemplate>
        </ul>
    </FooterTemplate>
</asp:Repeater>

					

and add some code:

private Sitecore.Links.UrlOptions _options = new Sitecore.Links.UrlOptions { LanguageEmbedding = Sitecore.Links.LanguageEmbedding.Never, SiteResolving = true, ShortenUrls = true };

protected void Page_Load(object sender, EventArgs e)
{
    var siteList = Sitecore.Configuration.Factory.GetSiteNames()
        .Where(n => n.StartsWith("nav-"))
        .Select(n => Sitecore.Configuration.Factory.GetSiteInfo(n));

    siteRepeater.DataSource = siteList;
    siteRepeater.ItemDataBound += siteRepeater_ItemDataBound;
    siteRepeater.DataBind();
}

private void siteRepeater_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
    if (e.Item.ItemType == ListItemType.AlternatingItem || e.Item.ItemType == ListItemType.Item)
    {
        var siteLink = e.Item.FindControl("siteLink") as HyperLink;
        var siteInfo = e.Item.DataItem as Sitecore.Web.SiteInfo;

        var siteLanguage = Sitecore.Context.Database.GetLanguages()
            .Where(l => l.Name == siteInfo.Language)
            .FirstOrDefault();

        var rootItemPath = siteInfo.RootPath + siteInfo.StartItem;
        var siteItem = Sitecore.Context.Database.GetItem(rootItemPath, siteLanguage);

        siteLink.Text = siteItem.DisplayName;
        siteLink.NavigateUrl = Sitecore.Links.LinkManager.GetItemUrl(siteItem, _options);
    }
}

					

The _options variable contains the settings we want to use for generating our links via the LinkManager. This can be configured in the Sitecore config if you prefer. But I've found in the past that the overall site's link configuration tends to be different to that needed for language navigation – hence I tend to configure it separately.

When the page loads, we need to bind our site data to the repeater. This means finding a list of the configured sites which we want to show. We can ask Sitecore to provide a list of the configured <site> element names. These can then be filtered to find the ones we named with the "nav-" prefix and then these can be projected into SiteInfo objects for processing.

When each item is bound to the repeater we need to grab the right Language object for the current site element's configuration, and then load this site's root item using this language. We get the path data and the language data from the SiteInfo object - which was populated from the XML configuration data above.

Finally, the link and display name data can be bound to a hyperlink on screen.

With this code in place we can browse to test.en and see:

English Nav

If you then click the Japanese link, you get:

Japanese Nav

The domain name is correct, and the language has changed to match. Success!

As usual, apologies for the Google Translated foreign language content - it's probably wrong, but you get the idea, I hope...

↑ Back to top