Getting Sitecore Dictionary in JavaScript

It’s very easy to get translations in back-end code — either Razor views or .cs files — by simply calling to

Sitecore.Globalization.Translate.Text()

But what about JavaScript widgets? There we have several options:

  • Item Web API results in overhead and is hard to use without additional implementation.
  • StringDictionary embeds values right in HTML and needs to be configured accordingly.
  • Injecting translated text into .js components via HTML tags has the same drawbacks as StringDictionary.

All these options are cumbersome and inconvenient.

I will show you a convenient way to have Translate.Text() right in JavaScript! The idea is to use a JavaScript dictionary object in our .js widgets/components. To achieve the goal we need the following:

  • Serialize dictionary values into .json files
  • Implement Dictionary.js

Let’s start coding!

Serializing the dictionary into .json files

Serializing the whole dictionary creates one .json file per language in a temp folder. The file’s name looks like “dictionary.{language}.json”, e.g. “dictionary.fr-CA.json”. The dictionary is serialized each time we perform a site publish or when we publish a Dictionary root item or its descendants.

using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Sitecore;
using Sitecore.Data;
using Sitecore.Data.Events;
using Sitecore.Data.Items;
using Sitecore.Data.Managers;
using Sitecore.Diagnostics;
using Sitecore.Events;
using Sitecore.Globalization;
using Sitecore.IO;
using Sitecore.Publishing;
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;

namespace AndreyVinda.JsDictionary.EventHandlers
{
    ///
<summary>
    /// Creates dictionary .json files on publish events
    /// </summary>

    public class SerializeDictionaryToJson
    {
        private static ID DictionaryFolderTemplateID = new ID("{267D9AC7-5D85-4E9D-AF89-99AB296CC218}");

        public void OnPublishEnd(object sender, EventArgs args)
        {
            var sitecoreArgs = args as SitecoreEventArgs;
            Assert.IsNotNull(sitecoreArgs, "The parameter 'sitecoreArgs' is null");

            var publisher = sitecoreArgs.Parameters[0] as Publisher;
            Assert.IsNotNull(publisher, "The parameter 'publisher' is null");

            var rootItem = publisher.Options.RootItem;

            if (ShouldSearializeDictionary(rootItem))
            {
                SerializeDictionaryToJsonFiles();
            }
        }

        public void OnPublishEndRemote(object sender, EventArgs args)
        {
            var remoteEventArgs = args as PublishEndRemoteEventArgs;
            Assert.IsNotNull(remoteEventArgs, "The parameter 'remoteEventArgs' is null");

            Item rootItem = null;
            var rootItemID = remoteEventArgs.RootItemId;
            if (rootItemID != default(Guid))
            {
                var db = Database.GetDatabase("web");
                rootItem = db.GetItem(ID.Parse(rootItemID));
            }

            if (ShouldSearializeDictionary(rootItem))
            {
                SerializeDictionaryToJsonFiles();
            }
        }

        private bool ShouldSearializeDictionary(Item rootItem)
        {
            return rootItem == null ||
                rootItem.TemplateID == DictionaryFolderTemplateID ||
                rootItem.TemplateID == TemplateIDs.DictionaryEntry ||
                rootItem.ID == ItemIDs.Dictionary;
        }

        public void SerializeDictionaryToJsonFiles()
        {
            var db = Database.GetDatabase("web");
            var languages = LanguageManager.GetLanguages(db);

            foreach (var language in languages)
            {
                var values = GetDictionaryValues(db, language);
                CreateDictionaryJsonFile(values, language);
            }

            Log.Info("Dictionary has been serialized to json files successfully.", this);
        }

        public void CreateDictionaryJsonFile(IDictionary<string, object> values, Language language)
        {
            var json = JsonConvert.SerializeObject(values, new KeyValuePairConverter());
            var filePath = $"{TempFolder.Folder}/dictionary.{language.Name}.json";
            FileUtil.WriteToFile(filePath, json);
        }

        public IDictionary<string, object> GetDictionaryValues(Database db, Language language)
        {
            IDictionary<string, object> dictionary = new ExpandoObject();

            using (new LanguageSwitcher(language))
            {
                var root = db.GetItem("/sitecore/system/Dictionary");

                var items = root.Axes.GetDescendants()
                   .Where(i => i.TemplateID == TemplateIDs.DictionaryEntry);

                foreach (var item in items)
                {
                    var key = item[FieldIDs.DictionaryKey];
                    dictionary[key] = item[FieldIDs.DictionaryPhrase];
                }
            }

            return dictionary;
        }
    }
}

The last thing is to plug SerializeDictionaryToJson.cs to publish:end and publish:end:remote events by using patch config.

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <events>
      <event name="publish:end">
        <handler type="AndreyVinda.JsDictionary.EventHandlers.SerializeDictionaryToJson, AndreyVinda.JsDictionary" method="OnPublishEnd"/>
      </event>
      <event name="publish:end:remote">
        <handler type="AndreyVinda.JsDictionary.EventHandlers.SerializeDictionaryToJson, AndreyVinda.JsDictionary" method="OnPublishEndRemote"/>
      </event>
    </events>
  </sitecore>
</configuration>

Implement Dictionary.js

I suppose you are using a module loader (in my example it’s require.js). Dictionary.js loads the proper .json file only once per page based on the current context language and provides translation.

define(["jquery"], function ($) {

    Dictionary._instance = null;

    function Dictionary() {
        this.values = {};
    }

    Dictionary.prototype.translate = function (key) {
        return this.values[key] || key;
    }

    Dictionary.prototype.getContextLanguage = function () {
        return $('meta[http-equiv="content-language"]').attr("content");
    }

    Dictionary.prototype.loadValues = function () {
        var language = this.getContextLanguage();
        var valuesUrl = "/temp/dictionary." + language + ".json";

        // We disable browser's cache to ensure translations are up to date
        $.ajax({ cache: false, async: false, url: valuesUrl })
            .done(function (data) {
                this.values = Object.freeze(data);
            }.bind(this))
            .fail(function () {
                console.error("Couldn't load dictionary values");
            });
    }

    Dictionary.getInstance = function () {
        if (Dictionary._instance == null) {
            var dictionary = new Dictionary();
            dictionary.loadValues();
            Dictionary._instance = dictionary;
        }
        return Dictionary._instance;
    }

    return Dictionary.getInstance();
});

How to use Dictionary.js

In this sample, our SearchBox widget utilizes translations based on the current page language.

define(["Dictionary"], function (dictionary) {

 function SearchBox(options) {
 this.noResultsFoundText = dictionary.translate("NoResultsFound");

 //...
 }

 //...

 return SearchBox;
});

dictionary.en.json snippet

{ ... "NoResultsFound": "Sorry, we have no content matching your criteria." ... }

dictionary.fr-CA.json snippet

{ ... "NoResultsFound": "Désolé, nous avons aucun contenu ne correspond à vos critères." ... }

Dictionary.js features

  • Doesn’t inject values into HTML.
  • Doesn’t impact SEO.
  • Lazy loading.
  • No need to configure.
  • Small overhead: serialized .json file is 13kB for 300 items in real-world application.
  • Can be loaded from browser’s cache.

Enjoy!

Advertisements

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s