an abstract image of a grid adhering to waves with various spotlights of teal and purple

Using Region Codes Instead of Language Codes in Sitecore URLs



Doug Yoder
Doug Yoder

Sitecore’s LinkManager class offers easy access to the language code of the content right in the URL. But what do you do when you want to use a region code instead of – or in addition to – the language code?

Usually the default behavior is acceptable for the site you are creating, however sometimes there are specific business needs that require the region to be within the URL and we want to be able to use Sitecore’s LinkManager but it is limited to using the name of the language item. Therefore we want to extend this to allow for a custom name – either a region name or a custom language name.

For example, imagine a company using the same content structure but with different language or content in each region (North America, Central America, Brazil, Europe, Asia, etc.). We could try to use Sitecore’s language feature to allow for different versions of each content item; thus preserving the content structure across all regions. For the most part this is pretty straightforward – we can use “en-US” for North America, “es-MX” for Central America, “pt-BR” for Brazil, etc.

In this example, we can set the LanguageEmbedding property of the Sitecore LinkProvider to “always” in our web.config file and Sitecore will generate URLs such as:
http://www.example.com/en-US/products
http://www.example.com/es-MX/products
http://www.example.com/pt-BR/products

However, we don’t want to use the language codes in the URL – we want the business’s region name instead.
http://www.example.com/NorthAmerica/products
http://www.example.com/CentralAmerica/products
http://www.example.com/Brazil/products

Our first thought is to just rename the “language” items in /sitecore/system/languages/ to match our region names, but it turns out Sitecore is expecting those items to be named with a valid Culture Info name. You can do this, but you have to create a new Culture on each server that will host the site. This is undesirable, as the Windows server configuration should not need to change to support the content on the hosted sites.

Our second option is to customize the Sitecore LinkProvider class and use our custom LinkProvider for Sitecore’s LinkManager. In this case, we know we will need to store the mapping between language and region somewhere – I chose to add a new field called “Region Name” to the “Language” template.

There’s one important method to override in the LinkProvider class – GetItemUrl(). In our override, we will set the parameters to always use LanguageEmbedding, then we will change the string that is generated to replace the language item’s name with our region name. I chose to use string replacement rather than dive deeper into the LinkBuilder class which is nested in the LinkProvider class.

Let’s look at some code:

public class MyLinkProvider : LinkProvider
{
    public override string GetItemUrl(Item item, UrlOptions options)
    {
        options.LanguageEmbedding = LanguageEmbedding.Always;
        options.LanguageLocation = LanguageLocation.FilePath;
        var languageId = LanguageManager.GetLanguageItemId(item.Language, item.Database);
        var languageItem = item.Database.GetItem(languageId);
        var regionName = languageItem["Region Name"] ?? "";
        var returnUrl = base.GetItemUrl(item, options).ReplaceFirst(item.Language.Name, regionName);
        return returnUrl;
    }
}

In our overridden GetItemUrl method, we set the LanguageEmbedding property of the passed in options object – this overrides any settings made in web.config. We do this since we know the sites depend on having the region code in the URL, so we want to ignore the setting from web.config. Next we use the LanguageManager class to get the Language item, this fills its “Region Name” field. Finally, we replace the first instance of the language name with the region name. The ReplaceFirst method is a simple string extension method that replaces only the first instance of the matched substring. You may wish to go one step further and make sure you’re not mangling the domain name portion of the URL.

To activate this code, simply change the type of the active provider under the <linkManager> node in your web.config file:

<linkManager defaultProvider="sitecore">
  <providers>
    <clear />
    <add name="sitecore" type="MyNamespace.MyLinkProvider, MyAssembly"

Now, whenever your components link to another Sitecore item the generated URL will use your region name.

However, this was only half the battle. Generating the link is one thing, but now we need to tell Sitecore how to handle those links. We can do this by adding our own HttpRequest pipeline processor before the ItemResolver pipeline processor.

public class MyHttpRequestProcessor : HttpRequestProcessor
{
    public override void Process(HttpRequestArgs args)
    {
        Assert.ArgumentNotNull(args, "args");
        // If Sitecore has already mapped the item, just return.
        if (Context.Item != null || Context.Database == null || args.Url.ItemPath.Length == 0) return;
        foreach (var language in LanguageManager.GetLanguages(Context.Database))
        {
            var languageId = LanguageManager.GetLanguageItemId(language, Context.Database);
            var languageItem = Context.Database.GetItem(languageId);
            var regionName = languageItem["Region Name"] ?? "";
            if (args.Url.ItemPath.StartsWith("/" + regionName))
            {
                Context.SetLanguage(language, true);
                Context.Item = ItemManager.GetItem(args.Url.ItemPath.ReplaceFirst("/" + regionName, ""), language, Version.Latest, Context.Database);
                break;
            }
        }
        // TODO: Log that we could not resolve the item based on region name (no matching language item).
    }
}

The above code is a proof of concept that could be improved if performance becomes an issue. We could read all the languages once, create a dictionary to associate the language and the region code, and store that in memory to avoid reading from the database often. However, the logic will remain the same: read the beginning of the URL, find the language with the matching region code, and set the context item and language.

The new processor can be activated by adding a line to your web.config or by using a patch file:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <httpRequestBegin>
        <processor type="MyNamespace.MyHttpRequestProcessor, MyAssembly" patch:after="processor[@type='Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel']" />
      </httpRequestBegin>
    </pipelines>
  </sitecore>
</configuration>

This method of customizing the URLs generated by Sitecore can be expanded for other purposes as well. For example, instead of using a region code in the URL, you could create custom shorter URLs for sections of your site that have deep paths in Sitecore. All you need is a custom LinkProvider to generate the links and a custom HttpRequestProcessor to interpret the URLs.

Let's Get Started

We'd love to hear from you. We probably have a lot in common. I mean, you like chatting about data-binding, UX patterns, and javascript functions, right?

X

Cookies help us improve your website experience. By using our website, you agree to our use of cookies and our privacy policy.

Accept