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!