Pages

Tuesday, May 24, 2016

A Simple Language Selector in Episerver

On my current Episerver project, we had a need to support a couple languages for the website, and display them via a simple nav-like language selector at the top. Being fairly new to Episerver, I started looking into how I needed to build it out and what was involved. I found a bunch of information on Localization and Globalization, and it helped point me in the right direction, but there were some minor differences I found due to the age of the articles. Episerver has been updating pretty actively since version 7, so some of the information wasn't up to date.

After putting together my solution, I wanted to share it and point out a few things I encountered along the way. This post is my attempt at adding some updated information while working with Episerver 9.x and how I built our language selector.

The Goal

The goal in this case is relatively simple: provide the user with a selection menu to change their site language to one of their preference. Since this site isn't supporting a bunch of various languages, the design is simple, like a utility navigation in a header:


How to Get There

Right now there are only 2 languages being used, but I like to have flexibility for the future, so I tend to avoid hard-coding things like this and, instead, I like to use the tools at my disposal.

Step 1 - Get the language list

Episerver has a list of languages you can work with, add to, and enable in the Admin section, and when you enable one for your site, an "Enabled" flag is set for that language. Using that, we can easily get a list of the languages that are active for the website and do something with them:
[pre class="brush:csharp"]
var activeLanguages = ServiceLocator.Current.GetInstance<ILanguageBranchRepository>().ListEnabled();
[/pre]
The Language Branch Repository is exactly what it sounds like: a repository of all language branches in the system. There are two methods for listing languages (aka Language Branches) depending on your needs:
  1. ListAll() - It returns all the languages in the system
  2. ListEnabled() - It returns only those that have the Enabled flag = true
Since we want to allow the user to select their preferred language to view the site, we probably want to only list languages that are enabled, so we use ListEnabled(). It's pretty straight forward.

Step 2 - Create the selector

Once we have the list of enabled languages, the next step is to present them to the user as links. For our design, we only want the languages that are not the currently selected one to be links. The current one should just be text, as indicated by the white text in the image above. To do this, we loop through our active languages, check them against our current language, and provide the proper markup in our view, accordingly.

It's a relatively easy operation, but I did hit one snag in my initial design. When you look at the current page object, a "Language" property is accessible, which provides the current language for the current page. The hang up is with Language Fallback: if you enable English and Japanese, create a page in English only, and then try to view the page in Japanese, you are presented with the English page. This is how Language Fallback works, and it's great. However, though you selected Japanese, the current page's current language will be English, because Japanese did not exist.

Thankfully, there is a PreferredCulture property of the ContentLanguage class that allows us to see what the preferred language - not necessarily the current - is. In my example above with English and Japanese enabled, but only English content existing, choosing Japanese has different results with the code below:
[pre class="brush:csharp"]
var preferredLang = EPiServer.Globalization.ContentLanguage.PreferredCulture.Name;    // Japanese
var currentLang = Model.CurrentPage.Language.Name;    // English
[/pre]
Therefore, when we loop to create our Language Selector, we typically want to compare to the preferredLang for our current loop item:
[pre class="brush:csharp"]
<ul class="list-inline">
    @{
        var preferredLang = EPiServer.Globalization.ContentLanguage.PreferredCulture.Name;
        foreach (var lang in activeLanguages) {
            <li>
                @if (string.Equals(preferredLang, lang.LanguageID, StringComparison.InvariantCultureIgnoreCase)) {
                    @lang.Name
                }
                else {
                    @Html.PageLink(lang.Name, Model.CurrentPage.PageLink, new { language = lang.LanguageID }, null)
                }
            </li>
        }
    }
</ul>
[/pre]
Above, there are 2 items worth pointing out. First, the If-Else condition "string.Equals" comparison uses the current "lang.LanguageID" even though the "preferredLang" variable is set to "PreferredCulture.Name". This is because the "PreferredCulture.Name" value is the same as the LanguageID.

Second, note the use of the "PageLink" extension to create the URL in the proper language. This is a newer method, I believe since EPi 7.1 or 7.5, that makes it easy to create links to pages in different languages. This also sets the language preference going forward.

End Result

For this simple case, that is all that is needed. The end result of this code is the screenshot at the top for our goal. With this Language Selector approach, providing a new language option to the user is as simple as enabling it in Episerver. Even if content isn't yet created, the functionality is going to operate as an end user would expect.

Full Code

[pre class="brush:csharp"]
<div class="lang-selector">
    <ul class="list-inline">
        @{
            var activeLanguages = EPiServer.ServiceLocation.ServiceLocator.Current.GetInstance<ILanguageBranchRepository>().ListEnabled();
            var preferredLang = EPiServer.Globalization.ContentLanguage.PreferredCulture.Name;
            foreach (var lang in activeLanguages) {
                <li>
                    @if (string.Equals(preferredLang, lang.LanguageID, StringComparison.InvariantCultureIgnoreCase)) {
                        @lang.Name
                    }
                    else {
                        @Html.PageLink(lang.Name, Model.CurrentPage.PageLink, new { language = lang.LanguageID }, null)
                    }
                </li>
            }
        }
    </ul>
</div>
[/pre]
In a follow up article, I am going to expand on this and allow finer control for our editors. Sometimes you want to enable a language and create content in that language, but you aren't quite ready to have it available for your visitors. Ektron had the option of enabling a language for just the Workarea, or for the End User as well, and I will tackle that for Episerver in the next article.

1 comment:

  1. Hi,

    Clean approach.Like it.

    Wrote similar article. Might come handy if you would like to add possibility to switch language *anywhere* on your site. http://blog.tech-fellow.net/2015/04/09/switch-content-language-everywhere-in-episerver/

    ReplyDelete