Johan Björnfot
Feb 20, 2015
  13972
(5 votes)

Changes regarding language handling during content loading in CMS8

Background

Previously there were overloads to content loading methods in IContentLoader that took an ILanguageSelector as a parameter. When working on multilingual sites these overloads could be used to load content in a specific language, and/or define if language fallback rules should apply.

The way it worked behind the scenes during load was that first the content provider could operate on the passed in language selector to decide which language to return. Then the language selector was used again to select which language version to return according to defined language fallback rules.

The main implementation of ILanguageSelector is LanguageSelector. It is a fairly complex class that previously both acted as a data carrier as well as a service. It can be hard to handle in unit tests, mainly due to a static dependency to ContentLanguage.PreferredCulture but also complexity to mock. Another feedback we heard was from implementers of content providers was that the language selector is complex to work with.

We finally ran into some issue with the language selector and the Preview functionality in the recently introduced Project feature (beta description of projects). As the language fallback functionality in the LanguageSelector was run after the project version was selected it would not take projects into consideration and the two features would not work well together.

So the current language handling using LanguageSelector which already was complex did not work very well in the project preview scenario. We did not want to add project awareness to LanguageSelector, due to the tight coupling this would mean, also considering that LanguageSelector was already complex in its own.

New loading handling

So we decided to do some improvements regarding language handling during content loading. Some of the goals we set up where:

  1. Possibility to specify options for language that does not have static dependencies such as ContentLanguage.PreferredCulture or the inversion of control magic (SelectPageLanguage etc)
  2. Make a smooth upgrade from CMS7, preferably just a recompile without changing any code
  3. Simplify language selector and how to use it

We moved the language selection from the content provider level to the loader above the providers. We have changed IContentLoader by replacing the ILanguageSelector with LoaderOptions instead. We have also added simpler overloads that just take a CultureInfo. LoaderOptions is basically just a collection of LoaderOption (currently there are LanguageLoaderOption and ProjectLoaderOption). During loading there are specific components that acts on the different loader options, affecting which language/version of a content to return.

What about LanguageSelector?

To minimize the impact on existing code, we have changed LanguageSelector to inherit from LoaderOptions. This means you can still pass in a LanguageSelector to the overloads that take a LoaderOption. We have changed the implementation of LanguageSelector so that the constructor and the static methods (like MasterLanguage) creates corresponding LoaderOptions instances. This means there is no immediate demand to replace current usage of LanguageSelector as parameter to loading methods.

Usage of IContentLoader

Below are some examples on how to load content with some different fallback strategies applied. The examples shows usage of LanguageSelector and alternatives using CultureInfo and LoaderOptions instead.

Loading specific language

Loading a page in a specific language, using LanguageSelector:

var swedishPage = contentLoader.Get<StandardPage>(contentLink, new LanguageSelector("sv"));

Using the new overload that takes a CultureInfo, the same page can be loaded as:

var swedishPage = contentLoader.Get<StandardPage>(contentLink, CultureInfo.GetCultureInfo("sv"));

This is equivalent to:

swedishPage = contentLoader.Get<StandardPage>(contentLink, 
    new LoaderOptions(){ LanguageLoaderOption.Specific(CultureInfo.GetCultureInfo("sv")) });

Loading master language

To load the master language version of a content (the master language version is the language version in which the common non-language specific properties are stored) using LanguageSelector:

var master = contentLoader.Get<StandardPage>(contentLink, LanguageSelector.MasterLanguage());

Using the new overload that takes a CultureInfo the master version can be loaded as:

master = contentLoader.Get<StandardPage>(contentLink, CultureInfo.InvariantCulture);

This is equivalent to:

master = contentLoader.Get<StandardPage>(contentLink,
    new LoaderOptions() { LanguageLoaderOption.MasterLanguage() });

Loading in current language with fallback

Using LanguageSelector to load a content instance in the same language as the current request was routed to:

var current = contentLoader.Get<StandardPage>(contentLink, LanguageSelector.AutoDetect());

LanguageSelector.AutoDetect() uses language fallback settings, which means that if the content does not exist in the requested language or if the language is not published, then another language version might be returned (according to defined fallback rules). Using LoaderOptions the call would look like:

current = contentLoader.Get<StandardPage>(contentLink, new LoaderOptions() { LanguageLoaderOption.Fallback() });

Loading specific language with fallback (including master)

Loading content in a preferred culture with fallback rules applied, including a final fallback to master (if no other fallback rules apply) can be done using LanguageSelector:

var fallback = contentLoader.Get<StandardPage>(contentLink, LanguageSelector.Fallback("sv", true));

The equivalent call using LoaderOptions is:

fallback = contentLoader.Get<StandardPage>(contentLink, new LoaderOptions(){ LanguageLoaderOption.FallbackWithMaster(CultureInfo.GetCultureInfo("sv")) });

Related changes

Changes in IContentRepository

The methods CreateLanguageBranch and GetDefault have been simplifed to take a CultureInfo as parameter, instead of an ILanguageSelector.

ILanguageSelector and content providers

The interface ILanguageSelector has been simplified to only contain a single property Language of type CultureInfo. This means content provider implementations no longer needs to call any methods on ILanguageSelector to decide which language version to return. Instead they can just return the language version specified by ILanguageSelector.Language. If CultureInfo.InvariantCulture is passed in, the provider should return content in the master language. If the content does not exist in the specified language, then the master language should be returned.

ContentReference.GetPublishedOrLatest

ContentReference has a property GetPublishedOrLatest that loads the published version or the latest version if no published version exists. When using this property with language fallback previously it only loaded published versions in the fallback chain. This has been corrected to select the published or latest version in the fallback chain as well. This property is normally not used in templates.

ILanguageSelectionSource

In previous versions of CMS, it was possible to cast the passed in LanguageSelector to ILanguageSelectionSource after content retrieval. That interface contained information about why the content was selected, for instance that the content was selected due to language fallback or replacement rules. The same information can now be retrieved after content retrieval by calling MatchLanguageSettings on IContentLanguageSettingsHandler passing in the retrieved content instance and the requested language.

Feb 20, 2015

Comments

Frederik Vig
Frederik Vig Feb 20, 2015 07:45 PM

Just to make sure: var current = contentLoader.Get(contentLink, LanguageSelector.AutoDetect()); is the equivalent of: var current = contentLoader.Get(contentLink);?

Great changes!

Frederik

Feb 21, 2015 07:14 AM

current = contentLoader.Get(contentLink);
is actually equivalent to
current = contentLoader.Get(contentLink, LanguageSelector.AutoDetect(true));
which means it has fallback to master enabled.

Johan Book
Johan Book Feb 22, 2015 01:24 AM

Seems like a fair change, makes code more predictable. However, I'm wondering if we're adviced not to use LanguageSelector anymore when starting from scratch in Episerver 8? I kind of like the simpler notation of LanguageSelector, compared to LoaderOptions. Couldn't there be default constructs for LoaderOptions that worked in the same way as LanguageSelector?

K Khan
K Khan Feb 23, 2015 01:56 PM

Is there any Changes in relation with Language and Commerce Markets ?

Please login to comment.
Latest blogs
Copy Optimizely SaaS CMS Settings to ENV Format Via Bookmarklet

Do you work with multiple Optimizely SaaS CMS instances? Use a bookmarklet to automatically copy them to your clipboard, ready to paste into your e...

Daniel Isaacs | Dec 22, 2024 | Syndicated blog

Increase timeout for long running SQL queries using SQL addon

Learn how to increase the timeout for long running SQL queries using the SQL addon.

Tomas Hensrud Gulla | Dec 20, 2024 | Syndicated blog

Overriding the help text for the Name property in Optimizely CMS

I recently received a question about how to override the Help text for the built-in Name property in Optimizely CMS, so I decided to document my...

Tomas Hensrud Gulla | Dec 20, 2024 | Syndicated blog

Resize Images on the Fly with Optimizely DXP's New CDN Feature

With the latest release, you can now resize images on demand using the Content Delivery Network (CDN). This means no more storing multiple versions...

Satata Satez | Dec 19, 2024