Showing posts with label sitecore. Show all posts
Showing posts with label sitecore. Show all posts

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.

Tuesday, May 5, 2015

Sitecore: Custom Standard Value Tokens

Overview

In Sitecore, all developers are familar with the concept of standard values.  Standard values are default values and behaviors that can be set for content items that derive from data templates.  You can define the default values for fields or you can assign behaviors such as insert options.  I will assume that the audience who reads this is already familiar with this concept and knows how to create and work with standard values.

Standard Value Token Variables

Standard values can be defined using static values such as a number or a string or they can be token variables that will be replaced by computed values at item creation time.  The default installation of Sitecore already includes a set of tokens that satisfy most applications and purposes. 
  • $name: The item name
  • $id: The item ID
  • $parentid: The item ID of the parent item
  • $parentname: The item name of the parent item
  • $date: The system date
  • $time: The system time
  • $now: The combination of system date and time
Tokens are strings that start with the "$" symbol.  When a content item is created in the content tree, the pipeline that gets invoked is:



Here, you can see that there are processors to perform basic checks and determine if token variables are detected before actually performing any substitutions.  But what if we want to define custom tokens?  Where do we inject that logic in this process?  Well, there are two possibilities.  One approach would be to inject a processor at the end of the pipeline where more custom tokens are defined and then perform additional substitutions on the new custom tokens, hence adding a "patch" to the way the pipeline works.  A better, leaner approach is to actually modify the "core" of it all to include the custom tokens and to perform substitutions as if everything came out of the box.

Taking Apart The Pipeline




First, let's use a decompiler to inspect what's happening in the expandInitialFieldValue pipeline.  In the ReplaceVariables processor, you will see that there is a call to another class that does all the work.  This class is define in the <settings> section of web.config.



Lets look at this class and see what is does and whether or not we can subclass it.  Knowing how flexible Sitecore is, the asnwer is probably yes.  Again, lets use a decompiler.  Of particular interest are the following three methods.







The execution order is Replace > ReplaceValues > ReplaceWithDefault with Replace being a virtual method while the others are not.  Fortunately for us, this means we can easily override the combined logic with a custom subclass of our own. 



In our custom class, we have to override the Replace method with the same or similar code.  Then we need two local private versions of ReplaceValues and ReplaceWithDefault.  We can use same or similar code for ReplaceWithDefault but the ReplaceValues method is where you would define your custom tokens and also tell Sitecore what to do with it.  For example, let's say you want to replace the custom "$test" token with the string "hello" this would be the resulting code.



That is all there is to define custom token variables for Sitecore standard values.  All the work is done in the ReplaceValues method. 

Custom Tokens For Date Fields

Now let's explore some of the more complex field types besides just the single-line string field.  A Sitecore date field renders as a datepicker in the content admin.  If you define the standard value for a date field,  you can pick a default date and all items will inherit this default date.  Did you know you can also replace the standard value in the date field by typing in a token name instead of using the datepicker?



If you use any of the default tokens such as "$name" or "$id" the resulting value of the field would be an empty string because the date field looks for a valid ISO-formatted datetime string such as "20150329T000000".  The only way to have a string in this format rendered is by defining a custom token with custom logic. 

Let's say the scenario is that we want the date field value to reflect the item name, we could define the "$itemnameasdate" token to be replaced like this.



Basically, if the item name is in a date format that is expected, then we convert the value to an ISO-formatted datetime string and replace the token with this string, otherwise the field will have no value.

Please keep in mind that all tokens should start with a unique string or substitutions will be invalid.  For example, if the item name is "testitem", the "$name" token will be replaced with "testitem".  If a custom token is name "$nameasdate", the substituted valued would be "testitemasdate" because a straight string.Replace method is called and your custom token replace logic will never be invoked.

Custom Tokens For Integer Fields

Integer fields are very tricky when attempting to set standard values.  You can set static integer values as the default values, no problem there.  Since token variables are string values, does this mean we are out of luck?  Kind of.  If we define a field as type Integer, we are only allowed to use integers in the standard value for that field.  This would mean we are out of luck when attempting to put down "$name" as the token to be replaced.





What can we do?  Well, behind the scenes all field values are stored as strings.  The UI is the only part that performs validations and barks at you when attempting to enter a non-integer value in the field.  So one unconventional practice is to first define the field as a string field, enter the token as the standard value, and then change the field type back to integer.  The token sticks!  Now you have a string token in an integer field as the standard value!


Next, we have to add the logic to perform the token replacement.  Let's take for example a data template that defines what a year is and we try to fill in the year integer field with the value of the item name.  If the item name can be interpreted as an integer, then it is a valid year, otherwise leave blank.  I am sure there can be more validations performed on this value to determine if the number is a year but let's keep it simple for now.  We can use this snippet of code.



There are no guarantees for this kind of workaround but at least it works as of 7.x now.  In the future if Sitecore decides to validate token names in standard values, then this code will not work but nothing will break and existing data will no be affected.  Since it is a standard value, only new items will be affected and there will be no default values set.

Final Words

Please keep in mind that if the class definition changes in future revisions of Sitecore and the Sitecore.Kernel assembly, the code in your subclass probably has to be updated to reflect the new base class code.  The code in these classes rarely change so it is not a concern but please just be aware of the possibility.