Make your Sitecore 10 Asp.Net Core web app multilingual

Happy days! Sitecore 10 is released and guess what? It now supports Asp.Net Core. Best news ever! 😁

And as always the documentation is really good:
Developer Documentation (Sitecore 10.0)
Sitecore Headless Development
Sitecore Content Serialization
Developing with containers

Check out Nick’s great video, where he explains it all:

So much wonderful stuff!!

Today’s post will be about how to add a language selector to your Asp.Net Core website. We will use the great github repo – Helix.Examples.

First out is the Sitecore part. We will add a multi-list field containing the β€œsupported” languages for the web app:

Next step will be to add the selected languages to a “Rendering Contents Resolver”, here we use the existing – Header:

Here is the code for BasicCompany.Feature.Navigation.LayoutService.HeaderContentsResolver

using System.Diagnostics;
using System.Linq;
using Sitecore.LayoutService.Configuration;
using Sitecore.Mvc.Presentation;
using BasicCompany.Feature.Navigation.Services;
using BasicCompany.Foundation.Multisite;

namespace BasicCompany.Feature.Navigation.LayoutService
{
    public class HeaderContentsResolver : Sitecore.LayoutService.ItemRendering.ContentsResolvers.RenderingContentsResolver
    {
        protected readonly IHeaderBuilder HeaderBuilder;
        private readonly SiteSettings _siteSettings;

        public HeaderContentsResolver(INavigationRootResolver rootResolver, IHeaderBuilder headerBuilder, SiteSettings siteSettings)
        {
            Debug.Assert(rootResolver != null);
            Debug.Assert(headerBuilder != null);
            HeaderBuilder = headerBuilder;
            _siteSettings = siteSettings;
        }

        public override object ResolveContents(Rendering rendering, IRenderingConfiguration renderingConfig)
        {
            var header = HeaderBuilder.GetHeader(this.GetContextItem(rendering, renderingConfig));


            var contents = new
            {
                logoLink = this.ProcessItem(header.HomeItem, rendering, renderingConfig),
                navItems = header.NavigationItems.Select(x => new
                {
                    url = x.Url,
                    isActive = x.IsActive,
                    title = x.Item[Templates.NavigationItem.Fields.NavigationTitle]
                }),
                supportedLanguages = _siteSettings.SupportedLanguages
            };
            return contents;
        }
    }
}

Notice the line:
supportedLanguages = _siteSettings.SupportedLanguages
(The supported languages are fetched in the Foundation project – Multisite)

Here is the JSON output:

{
    "uid": "7d4f689f-208c-4ea3-88ba-0bc6e615771e",
    "componentName": "Header",
    "dataSource": "",
    "params": {},
    "fields": {
        "logoLink": {
            "NavigationTitle": {
                "value": "Home"
            },
            "FooterCopyright": {
                "value": "Copyright"
            },
            "HeaderLogo": {
                "value": {
                    "src": "https://cd.basic-company-aspnetcore.localhost/-/media/Basic-Company/helix-logo.png?h=44&iar=0&w=139&hash=0A1AD60DFDAA0C78DD1BE4928E2659D8",
                    "alt": "Sitecore Helix",
                    "width": "139",
                    "height": "44"
                }
            }
        },
        "navItems": [
            {
                "url": "/en/",
                "isActive": true,
                "title": "Home"
            },
            {
                "url": "/en/Products",
                "isActive": false,
                "title": "Products"
            },
            {
                "url": "/en/Services",
                "isActive": false,
                "title": "Services"
            }
        ],
        "supportedLanguages": [
            {
                "name": "en",
                "nativeName": "English",
                "url": "/en/",
                "twoLetterCode": "en",
                "icon": "/~/icon/Office/32x32/flag_generic.png"
            },
            {
                "name": "sv-SE",
                "nativeName": "svenska (Sverige)",
                "url": "/sv-se/",
                "twoLetterCode": "sv",
                "icon": "/~/icon/Flags/32x32/flag_sweden.png"
            },
            {
                "name": "la",
                "nativeName": "lingua latina",
                "url": "/la/",
                "twoLetterCode": "la",
                "icon": "/~/icon/Office/16x16/flag_generic.png"
            }
        ]
    }
}

That was the Sitecore part… And now it's time for some wonderful Asp.Net Core stuff. Let's go to the Renderinghost πŸ™‚

The idea is to present the language selector in the Header view. We can do this in a number of ways, but I wanted to try out the View components in ASP.NET Core which are "supported" in the Sitecore.AspNet.RenderingEngine. (I really like the view components, they remind me of blazor components)
Let's create the "codebehind" for the view component – LanguageSelectorViewComponent

using BasicCompany.Feature.Navigation.Models;
using Microsoft.AspNetCore.Mvc;
using Sitecore.AspNet.RenderingEngine.Binding;
using Sitecore.AspNet.RenderingEngine.Mvc;
using System.Threading.Tasks;

namespace BasicCompany.Feature.Navigation.Components
{
    public class LanguageSelectorViewComponent : BindingViewComponent
    {
        public LanguageSelectorViewComponent(IViewModelBinder binder) : base(binder)
        {
        }

        public Task InvokeAsync()
        {
            return BindView<LanguageSelector>();
        }

    }
}      

Next is the model, which is used in the view component – LanguageSelector

using Sitecore.AspNet.RenderingEngine.Binding.Attributes;

namespace BasicCompany.Feature.Navigation.Models
{
    public class LanguageSelector
    {
        [SitecoreComponentField]
        public SupportedLanguage[] SupportedLanguages { get; set; }
    }
}    

Notice the SitecoreComponentField attribute, it will bind/map the JSON field to the SupportedLanguages property.

Here is the markup for the component:

@model LanguageSelector

<div class="navbar-item has-dropdown is-hoverable">
    <a class="navbar-link">
        @Model.SupportedLanguages.FirstOrDefault(l => l.Name == this.SitecoreContext().Language).NativeName
    </a>
    <div class="navbar-dropdown is-boxed">

        @foreach (var language in @Model.SupportedLanguages.Where(l => l.Name != this.SitecoreContext().Language))
        {
            <a class="navbar-item" href="@language.Url">
                @language.NativeName
            </a>
        }

    </div>
</div>

The LanguageSelector model has an array of languages – SupportedLanguages. The SitecoreContext().Language will give us the current language.

In order to make the component discoverable for the header view and give us some nice IntelliSense. We need to update the _ViewImports.cshtml, by adding a tag helper for the BasicCompany.Feature.Navigation namespace(that is where the component resides):

@using Sitecore.LayoutService.Client.Response.Model
@using Sitecore.LayoutService.Client.Response.Model.Fields
@using Sitecore.AspNet.RenderingEngine.Extensions
@using BasicCompany.Feature.Navigation.Models

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Sitecore.AspNet.RenderingEngine
@addTagHelper *, BasicCompany.Feature.Navigation

Finally, in the Header view, we can now add the view component. Thanks to the tag helper we can easily find the vc:language-selector component

We have one last thing to do, we need to set all the supported languages in Startup.cs. This part allows the website to change the language context when a language is "selected".

// Enable ASP.NET Core Localization, which is required for Sitecore content localization.
app.UseRequestLocalization(options =>
{

    // If you add languages in Sitecore which this site / Rendering Host should support, add them here.
    var supportedCultures = new List { new CultureInfo(_defaultLanguage), new CultureInfo("sv-se"), new CultureInfo("la") };

    options.DefaultRequestCulture = new RequestCulture(_defaultLanguage, _defaultLanguage);
    options.SupportedCultures = supportedCultures;
    options.SupportedUICultures = supportedCultures;

    // Allow culture to be resolved via standard Sitecore URL prefix and query string (sc_lang).
    options.UseSitecoreRequestLocalization();
});

Here is the end result:

No Javascript was harmed during the making of this post because Javascript was never needed πŸ˜‚

That’s all for now folks πŸ™‚


One thought on “Make your Sitecore 10 Asp.Net Core web app multilingual

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.