Providing fallback/replacement languages in EPiServer Commerce

D e v e l o p m e n t , E - c o m m e r c e , O p t i m i z e l y ( E p i s e r v e r )
Sven-Erik Jonsson 14.12.2015 08.41.56

Commerce has a language settings handler that comes with the product, but to quote the internal description, it leaves something to want:

"The logic is as follows: - No replacement languages - No fallback languages - All languages that are enabled for a catalog are active, the others are not."

In our case we had 58 language branches, the site uses a lot of fallbacks. We wanted to have only neutral cultures in the catalog, and language fallbacks from region specific cultures, basically to give the editors the same experience as they had when they worked with EPiServer content.


Since language settings aren't readily available from the Commerce editing interface, they had to be read from someplace. In this specific case all fallbacks where configured on the root page, so we decided to read them from there. To achive this, CatalogContentLanguageSettingsHandler (or CatalogLanguageSettingsHandler in a newer Commerce) has to be overriden. The following class does exactly that.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Web;
using EPiServer;
using EPiServer.Commerce.Catalog;
using EPiServer.Commerce.Catalog.ContentTypes;
using EPiServer.Core;
using EPiServer.DataAbstraction;
using EPiServer.ServiceLocation;
using EPiServer.Web;
using Mediachase.Commerce.Catalog;
using log4net; namespace Your.Web.Infrastructure.ContentLanguage { [ServiceConfiguration(typeof(IContentLanguageSettingsHandler))] public class RootFallbackCatalogContentLanguageSettingsHandler : CatalogContentLanguageSettingsHandler { private readonly IContentRepository _contentRepository; private readonly ILanguageBranchRepository _languageBranchRepository; public RootFallbackCatalogContentLanguageSettingsHandler(IContentRepository contentRepository, IContentEvents contentEvents, ILanguageBranchRepository languageBranchRepository, IContentProviderManager contentProviderManager, ReferenceConverter referenceConverter) : base(contentRepository, contentEvents, languageBranchRepository, contentProviderManager, referenceConverter) { _contentRepository = contentRepository; _languageBranchRepository = languageBranchRepository; } [NonSerialized] private readonly static ILog _log; static RootFallbackCatalogContentLanguageSettingsHandler() { _log = LogManager.GetLogger(typeof(RootFallbackCatalogContentLanguageSettingsHandler)); } public override ContentLanguageSetting[] Get(ContentReference contentLink) { CatalogContentBase content; if (!IsCatalogContent(contentLink)) { return base.Get(contentLink); } if (HttpContext.Current == null) { if (!TryGetContent(contentLink, out content)) { return base.Get(contentLink); } return GetLanguageSetting(content); } var contentLanguageSetting = string.Concat("EP:CatalogContentLangSetting", contentLink.ID.ToString(CultureInfo.InvariantCulture)); var item = HttpContext.Current.Items[contentLanguageSetting] as ContentLanguageSetting[]; if (item == null) { if (!TryGetContent(contentLink, out content)) { item = base.Get(contentLink); } else { var catalogLanguageSetting = string.Concat("EP:CatalogLangSetting", content.CatalogId.ToString(CultureInfo.InvariantCulture)); item = HttpContext.Current.Items[catalogLanguageSetting] as ContentLanguageSetting[]; if (item == null) { item = GetLanguageSetting(content); HttpContext.Current.Items[catalogLanguageSetting] = item; } } HttpContext.Current.Items[contentLanguageSetting] = item; } return item; } private ContentLanguageSetting[] GetLanguageSetting(CatalogContentBase catalogContent) { var rootSettings = GetRootLanguageSettings(); var languageBranches = _languageBranchRepository.ListEnabled(); var contentLanguageSettings = new List(); foreach (var languageBranch in languageBranches) { var active = languageBranch.Enabled; var contentLanguageSetting = new ContentLanguageSetting(catalogContent.ContentLink, languageBranch.LanguageID, null, new string[0], active); if (rootSettings != null && rootSettings.ContainsKey(languageBranch.LanguageID)) { var rootSetting = rootSettings[languageBranch.LanguageID]; contentLanguageSetting.LanguageBranchFallback = rootSetting.LanguageBranchFallback; contentLanguageSetting.ReplacementLanguageBranch = rootSetting.ReplacementLanguageBranch; contentLanguageSetting.IsActive = rootSetting.IsActive; } contentLanguageSettings.Add(contentLanguageSetting); } return contentLanguageSettings.ToArray(); } private IDictionary<string, ContentLanguageSetting> GetRootLanguageSettings() { if (HttpContext.Current == null) return GenerateRootLanguageDictionary(); var rootLanguageDictionary = HttpContext.Current.Items["EP:RootLangSettingsDictionary"] as IDictionary<string, ContentLanguageSetting>; if (rootLanguageDictionary == null) { rootLanguageDictionary = GenerateRootLanguageDictionary(); HttpContext.Current.Items["EP:RootLangSettingsDictionary"] = rootLanguageDictionary; } return rootLanguageDictionary; } private IDictionary<string, ContentLanguageSetting> GenerateRootLanguageDictionary() { var definition = SystemDefinition.Current; var rootPage = definition != null ? definition.RootPage : new ContentReference(1); var rootLanguageSettings = base.Get(rootPage); if (rootLanguageSettings == null) return null; return rootLanguageSettings.ToDictionary(x => x.LanguageBranch); } private static bool IsCatalogContent(ContentReference contentLink) { return contentLink.ProviderName == "CatalogContent"; } private bool TryGetContent(ContentReference contentLink, out CatalogContentBase catalogContent) { return _contentRepository.TryGet(contentLink.ToReferenceWithoutVersion(), new NullLanguageSelector(), out catalogContent); } } }

Now, there is nothing stopping you from potentially extending this concept further than we did. The settings could be stored on commerce content. An editor could be made available.