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.

  • "Note that asNeeded is the default value for the languageEmbedding attribute ... 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 this to always for solutions involving a single language, or never otherwise."  Shouldn't that be the other way around? Why would I want or need to embed language in the URL if I only had one language? I also need my multiple languages to ccrawled by search engines, so the URL embedding offers me quesry string and session independent paths to language versioned content.

  • @James - yes, I will correct that. Thanks!

  • Thanks for the reply, apologies for apparently typing the above with six thumbs!

  • Is it safe to use this as a replacement for the item fallback code found in the Presentation Component Cookbook section 5.2.6?

  • @Bryan: I think that falling back at the item level is too simple for most requirements - you generally want to fall back at the field level instead. I suggest that you investigate this solution instead:  trac.sitecore.net/LanguageFallbackItemProvider  Personally, I don't like complex language fallback, or mixing languages on a page.

  • In regards to the order of the logic:   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.  I am seeing that it behaves slightly different. A language cookie appears to overwrite the language prefix in the path in the request URL. Example:  My browser is set to "English (United Kingdom)"; the cookie value is "en-GB", and when I go to: dev.mysitecoresite.com/.../home.aspx I receive the "en-GB" content. I would prefer to get the "en-CA" content because it's in the URL path. Can this be achieved?  Thanks, Russ  

  • I digress. I actually had a separate issue. The original logic sequence appears to be working as you stated.  Thanks, Russ

  • Hi,  I am having an issue with the language in the url. My problem is as follows:  When loading a URL, for example, http://[domainName]/article-box/, I got an error in the CultureNotFound exception. It takes the article-box as a culture or language.   Can you please provide me some advice how to solve this issue. Sorry for this question since i'm new to sitecore.  Thanks

  • It sounds like Sitecore interprets article-box as a language, when it should not. You have a few options. If you never include a language in the URL, then I think you can simply set the languageEmbedding attribute under linkManager in web.config to false.  If you sometimes include the language in the URL (which I would not - for SEO, either the URL should always contain the language, or it should never contain the language, to avoid multiple URLs for a single page), then you may need to override the StripLanguage processor in the preprocessRequest pipeline. Actually, you may just want to upgrade, as I believe later versions may address this issue (Sitecore should only interpret URL prefixes as languages if a corresponding language exists, which is not the case for URLs such as /article-box - you might be facing a defect fixed in a later version). If your URL is actually something more like /en-US and you cannot set languageEmbedding to never, then you may need to address StripLanguage, to avoid interpreting specific URL prefixes as languages.

  • Thanks for your reply John.  Based on the information you provided in the comment, I've been able to solve the issue. That is, the /article-box is no longer being seen as a culture/language. I've used the strip language and modified the ExtractLanguage method.  However, when entering the sitecore CMS, it leads to and error 404. Can you help me on this issue or is there any patch that can fix the bug in sitecore as you said there might be a fixed for this issue in a later version.   The sitecore version being used is 7.0  Thanks again for your help

  • @Hishaam: Switching to email.