Thursday, August 20, 2015

Sitecore: Multisite Setup with Multilingual

This is a followup blog entry to the custom language fallback strategy located here:

http://mrstevenzhao.blogspot.com/2015/08/sitecore-custom-language-fallback.html

Background

Clients usually go the route of single-Sitecore instance, multi-site setup for the purposes of cutting costs and maximizing reusability of components.  This approach usually complicates development but there are loads of advantages as well, such as writing less code and less copy-and-pasting.

Scenario

A very realistic scenario in the corporate world is a large parent company with many different brands.  All the brands will have their own individual sites.  In the Sitecore world, each of these brand websites can be an individual site in the content tree.  But what about when each of these brands have multiple international versions?  It would be easy if we could just use the "sc_lang" parameter to switch languages but what if they need to be hosted under different domain name suffixes.  For example:

www.domain.com - main site
www.domain.fr - main site with French content
www.domain.com.au - main site with Australian English content

In this scenario, we don't want to create three website nodes in the content tree.  That would defeat the purpose of language fallback and content resusability.  The only way is to ensure all three sites are reading from the same node, except that depending on the hostname suffix, we choose the corresponding language content.

Setup

To start we must ensure that there is a place to associate different languages with different domain name suffixes.  To do this, you can modify the system language template to include a new field:



Then if you guess correctly, we would eventually have to query all the system languages for the value of the Domain Name Suffix field and value.  We COULD iterate item by item in this folder but the better way would be to create a custom Lucene index that contains all these items and their fields and values.  This part is up to you to create. 

As for defining the sites, you can either do it the out-of-the-box way and add to the <site> entries in the web.config file or you can do it the way I did it with dynamic configurations without having to modify any config files.

http://mrstevenzhao.blogspot.com/2014/04/sitecore-multi-site-setup-wo-updating.html

Solution

Eventually we will need a way to route the hostname in the browser to the correct context website with the corresponding context language.  This is a two step process:

1) Find the correct "parent" site, usually the ".com" version.
2) Find the content item for the language according to the domain name suffix.

Step one can be done in the SiteResolver processor of the HtttpRequestBegin pipeline.  It is best if you create a new custom version of this class or create a a derived version of the default one.  In a nutshell, you would have to:

a) Check the URL hostname and get the value of the name without the suffix.
b) Look through all the website node names without suffixes in the content tree and try to find a match with the value from step a.
c) If a node is found, then you have found the context site.

To enhance performance you can utilize HttpContext caching and custom Lucene indexes to store all website nodes so you can just query against an index instead of iterating though items in the tree.

Step two can be done by creating a custom LanguageResolver that is derived from the default LanguageResolver.  Once you set the context language, the ItemProvider that actually gets the language version of the content items will do everything automatically.  Basically, here are the steps for the custom LanguageResolver:

a) Check the url for the language parameter "sc_lang" to see if language is already being set manually.  If so then we just let the default behavior take place.
b) If not, then we check the domain name suffix. If the domain suffix matches any of the system languages on the value of the field "Domain Name Suffix", then we have found the matching language.  Set the context language to be that language using the ISO code.

Summary

This is the high-level implementation of the strategy.  We do not want to create site nodes for every single language of a domain so we have to check that the current hostname in the browser matches the "main" site hostname in the content tree.  We then take the suffix and determine which system language is mapped to that suffix and set the context language. 


Monday, August 17, 2015

Sitecore: Custom Language Fallback Strategy

Background

Recently a client requested that a multisite solution be implemented.  At the same time, each site could have multiple international versions.  We will get into handling multiple hostnames with the same domain but different suffix in a future post.  With international sites also come with language fallback strategies because not every piece of content needs to be translated.  Some are perfectly fine left in English or whatever the default language may be.

Why is language fallback necessary?

Sometimes international sites only have a few items that are unique to their locale such as homepage and contact page. Some items like a registration component or an employee biography page do not necessarily require translation and are perfectly fine displayed in English.  If there is no language fallback in place, and we are on a Japanese site, and the Japanese site does not have its own version of the employee biographies, the user will see a blank page in the main content section with just a Japanese header menu and possibly Japanese footer.  With language fallback, even though the header and footer are in Japanese, at least the main content with the biographies will be in English, much better than a blank content area.

Solution

We know the system languages are stored in "/sitecore/system/languages". Although you can technically create a version of an item in any possible language, adding languages to the system languages folder provides a list of all languages in the drop down when selecting languages to create versions for.  This is much more convenient than going into the language picker and finding the one you need every time the first version is created for a new language.

Lets start by modifying the language template a little bit.  We can add a droplink field for the purpose of storing the fallback language.  Of course the choices allowed in this field would be another language in the same folder so just set the source to be the system languages folder.  It would look something like this:



Next, we have to make use of this new drop down field value. We have to store all these language fallbacks somewhere to be accessed later.  One way to do this is to create an index that stores all language definitions and their fields and values.  Another way is to create a Dictionary object with language name being the key and the fallback language name being the value.  If you choose to go this route, make sure you enhance the performance by caching the Dictionary object as a static item so all web requests will be pulling the fallback language from the same Dictionary object.  Achieve this by creating a static CacheManager class based on HttpContext.Current.Cache to store shared objects within the same app pool.  You can create this static Dictionary object in a custom SiteResolver.

Now how do you make use of this static Dictionary object?  We can create a custom ItemProvider, which is derived from the default ItemProvider, to look for language fallbacks.  The default GetItem() method looks like this:



It takes in the context language and then returns the resulting item.  If the resulting item does not have any versions, then we look up the fallback language for the context language via the index or static Dictionary object.  We try again to get an item in this language.



This method provides for single level language fallback and should satisfy most project requirements.  For projects that require multiple levels, you are on your own but shouldn't be more than minor adjustments.

Enhancements

Lets say that we are happy with the fallback strategy above and all seems to be working fine but then you want to suppress language fallback for certain items.  Why would you need this?  Lets say that you have a slideshow with five slides in English and only the first four slides have Japanese versions for the Japanese site.  Allowing for language fallback in the Japanese version of the site would render five slides, the first four with Japanese content and the last with English content due to fallback.  If we can guarantee that all content items have versions across all languages, it would be perfect but that is not always the case.  This is a very common scenario and it would be best to have a strategy for suppressing fallback for certain items.

You can easily achieve this by creating a sitecore template to store constant values that just have a general link field to internal items.  Create a subfolder, either in a global folder or settings folder, and use this folder to store a constant item for each template you want to bypass language fallback.  Now in the custom SiteResolver, you can create a static List object that stores all the IDs of items derived from all the templates stored in the subfolder of constants.  Tweak the ItemProvider a little to check whether the item ID matches any of the IDs in the List object.



This should return the item in the current context language.  This code must be placed before the check for fallback language.

Finally, what other enhancement can we make?  A very important one is to make sure that language fallback does not take place if we are in the content editor.  We can do this by placing this code before the check for fallback language as well.



Summary

Language fallback is very important in the real world.  Most corporate sites have international versions but the international versions have less content than the main corporate site.  If we don't enable language fallback, many pages will end up displaying a header and a footer with no main content in between.  Enabling language fallback ensures a seamless experience for the end users and removes worries from content editors knowing that if they forget to create a version of a content item in the other language, at least the end user will see something instead of nothing at all.