Share

LinkedIn

Using Region Codes Instead of Language Codes in Sitecore URLs

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.

Sitecore development, Sitecore custom code

Comments

Add a Comment

*
*

Please confirm you are human by typing the text you see in this image:

Azhar Siddiqui said: 3/21/2014 at 4:44 AM

Hi,
I have used the above mentioned method to change the language name. First of all thanks so much for explaining it so simplistically. But I am facing an issue once the page is rendered it it has the language code and then my custom language code. For example I wanted to change the ja language code to jp. So now my url formed is 'http://domain/ja/jp/...' .Could you let me know where have I gone wrong.

Thanks in advance
Azhar

Doug Yoder said: 3/25/2014 at 4:07 PM

Hi Azhar,

It sounds like the custom LinkManager isn't returning the correct URL. You could start by debugging into the override of GetItemUrl (second to last line of code in the first code example in the article). You can check what base.GetItemUrl() is returning, and see if there's another replacement you need to perform.

Hope that helps,
-Doug

Azhar Siddiqui said: 3/26/2014 at 12:01 AM

Hi,
Thanks for your reply.
My custom LinkManger is returning the correct url. In my case it returns 'http://domain/jp/...' . Only on loading, in the browser url it appends the ja. There are many links on the page they all have proper generated href links with ja changed to jp, but again on clicking those links and after the links are opened, again in the browser url the ja is appended.('http://domain/ja/jp/...' ). Another thing I have just noticed is this is happening in firefox and google chrome and is working fine in IE.

Doug Yoder said: 3/26/2014 at 2:28 PM

Hi Azhar,

So, to clarify: when you visit http://domain/jp/foo in Firefox or Chrome, it redirects you to http://domain/jp/ja/foo? Is it a redirect or something else happening (can you see how it loads in the browser F12 tools)?

-Doug