LibrarySites.Banner

Repost: Overriding Sitecore's Logic to Determine the Context Language

This blos post describes how you can override the logic that the Sitecore ASP.NET CMS uses to determine the context language.

This is an almost direct, untested repost of http://sitecorejohn.spaces.live.com/blog/cns!960125F1D4A59952!165.entry from November of 2008. Please comment below if this solution does not work for you.

The Sitecore layout engine retrieves content from the Sitecore repository in the context language (Sitecore.Context.Language). The default logic to determine the context language is to use the first of these variables that specifies a value:

  1. The sc_lang query string parameter.
  2. The language prefix in the path in the requested URL.
  3. The language cookie associated with the context site.
  4. The default language associated with the context logical site.
  5. The DefaultLanguage setting specified in web.config.

 

In the first two cases, Sitecore generates a session cookie so that subsequent requests do not have to include the URL query string or language prefix in the URL. If the user returns to the site without specifying the URL query string parameter or the language prefix in the URL path, their language selection would be lost.

If the organization has not published a translation of the context item in the context language, Sitecore acts as if all fields in that item are empty.

Some solutions need to augment the default logic for determining the context language. For instance, the developer may want to:

  • Consider the browser's language preferences.
  • Fall back to an alternate language if the organization has not published a translation for the requested language. For example, if the browser requests Austrian German, but content does not exist in that language, but does exist in German German, the developer might want to use German German instead of the default language associated with the context site. Fall back from one language to another until content exists for a language, or use the site's default language.
  • If no content exists for the item in any of the languages investigated, but content does exist in some other language, use that language.
  • Use a persistant cookie.

 

Additionally, ASP.NET uses the System.Threading.Thread.CurrentThread.CurrentUICulture and System.Threading.Thread.CurrentThread.CurrentCulture properties for localization, such as formatting dates. Sitecore does not set these properties based on the context language, so they default to the operating system configuration. It might be helpful if the language resolution logic set these properties.

The following solution is relatively untested but could provide inspiration for a production-quality solution to meet these requirements.

Sitecore.Context.Language is a smart property, which means it follows the lazy load pattern: if code accesses this property when nothing has set it, the getter for the property contains logic to determine the context language.

Sitecore uses the Sitecore.Pipelines.HttpRequest.LanguageResolver processor in the httpRequestBegin pipeline to determine the context language. Because Sitecore.Context.Language is a smart property, this processor probably isn't necessary, as some subsequent logic in the processing of the request is almost guaranteed to access Sitecore.Context.Language. In any case, we want to override this logic. Normally when adding logic to a pipeline, we replace an existing processor with our processor, or add our processor before or after a default processor. But in this case our processor depends on Sitecore.Context.Item, and therefore must appear after the Sitecore.Pipelines.HttpRequest.ItemResolver processor which sets the Sitecore.Context.Item property. Without investigating, we don't know whether processors between Sitecore.Pipelines.HttpRequest.LanguageResolver and Sitecore.Pipelines.HttpRequest.ItemResolver use Sitecore.Context.Language, so we'll just leave that default Sitecore.Pipelines.HttpRequest.LanguageResolver alone and add our processor after Sitecore.Pipelines.HttpRequest.ItemResolver.

You can download my prototype for the httpRequestBegin pipeline processor and compile it into your Visual Studio project. This code could definitely use some refactoring.

Add the custom processor after the default Sitecore.Pipelines.HttpRequest.ItemResolver processor in web.config:

<processor type="Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel" />
<processor type="Namespace.Pipelines.HttpRequestBegin.LanguageResolver, assembly">
  <persistLanguage>true</persistLanguage>
  <setCulture>true</setCulture>
</processor>

 

If you don't want to create a persistant cookie to persist the user's language preference between browser sessions, set the value of the <persistLanguage> element to false, or simply remove this property setter.

To support fallback languages:

  1. Create a new data template called something like Custom Language.
  2. Add a section to the Custom Language template.
  3. Add a field named Fallback Language to the Custom Language template.
  4. Set the type of the Fallback Language field to Droplist.
  5. Set the Source property of the Fallback Language field to /sitecore/system/languages.
  6. Add the Custom Language template to the base templates for the System/Language data template.
  7. For each language, select a fallback language, but beware of configuring infinite recursion (for instance, don't set the fallback language for German German to Austrian German if the fallback language for Austrian German is German German).

 

Note that asNeeded is the default value for the languageEmbedding attribute of the /configuration/sitecore/linkManager/providers/add element in web.config that controls the logic for generating friendly URLs. The logic applied for this value may not be exactly what you expect, which can result in multiple URLs for a single content item in a single language. I recommend setting  languageEmbedding to always for solutions that involve multiple languages, or never otherwise.

The following properties, which default to false unless set in the processor signature in web.config, serve the purposes described:

  • FallbackDepthLimit - In case someone configures infinite recursion in fallback languages.
  • SetCulture - Whether to set System.Threading.Thread.CurrentThread.CurrentUICulture and System.Threading.Thread.CurrentThread.CurrentCulture after determining the context language.
  • PersistLanguage - Whether to create a persistent cookie, to persist the language selection between browser requests.

 

The following methods serve the purposes described:

  • Process() - The pipeline processor body.
  • LanguageSetFromFUrlPath() - Set the context language and return True if the method can determine the language from the URL path.
  • LanguageSetFromQueryString() - Set the context language and return True if the method can determine the language from the sc_lang URL query string parameter. This is the only logic in this processor that sets a cookie, so that subsequent URLs do not have to include the URL query string parameter (all of the other variables used to set the context language should appear in all HTTP requests, so there should be no need for a cookie).
  • LanguageSetFromCookie() - Set the context language and return True if the method can determine the language from the cookie.
  • LanguageSetFromBrowserPreferences() - Set the context language and return True if the method can determine the language from browser preferences.
  • LanguageSetFromContextSite() - Set the context language and return True if the method can determine the language from the context site.
  • LanguageSetFromDefaultSetting() - Set the context language and return True if the method can determine the language from the DefaultLanguage setting.
  • LanguageSetToFirstExistingLanguage() - Set the context language and return True if the method can determine a language for which content exists for the item.
  • SetLanguage() - Sets the language and returns True if the context item contains data for the specified language, or if the method can determine the language from fallback languages.
  • HasVersionInLanguage() - Returns true if the specified item contains version data for the specified language.
  • SetContextLanguage() - Sets the context language. Used by SetLanguage().

 

One thing that concerns me is how ASP.NET uses the language associated with the current thread. If we assume that the entire system processes the entire request using this thread, or passes this setting to any child threads, then I think this should be reliable. But this is obviously far beyond my knowledge of ASP.NET threading. For me, it seemed to work using the Sitecore.Web.UI.WebControls.FieldRenderer Web control, the <sc:date> XSL extension control, and a custom XSL extension method, but my test system doesn't have the kind of load that might raise threading issues.