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:
- Possibility to specify options for language that does not have static dependencies such as ContentLanguage.PreferredCulture or the inversion of control magic (SelectPageLanguage etc)
- Make a smooth upgrade from CMS7, preferably just a recompile without changing any code
- 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.
Just to make sure: var current = contentLoader.Get(contentLink, LanguageSelector.AutoDetect()); is the equivalent of: var current = contentLoader.Get(contentLink);?
Great changes!
Frederik
current = contentLoader.Get(contentLink);
(contentLink, LanguageSelector.AutoDetect(true));
is actually equivalent to
current = contentLoader.Get
which means it has fallback to master enabled.
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?
Is there any Changes in relation with Language and Commerce Markets ?