My good friend and colleague, George Tryfonas had developed a server control for exposing dictionary items in the page editor, for a 6.5 installation. It was a brilliant little gem, named "DictionaryLiteral" that got forgotten later on, in subsequent Sitecore projects.
Recently, we have undertaken the task of redeveloping an existing 6-year-old non-Sitecore site into a Sitecore application, and of course we went with Sitecore 8, the (currently) latest version. One of the first things we decided was to re-iterate that (now 2-year-old) implementation to include dictionary domains.
First things first, if you do not know what dictionary domains are, do read up on them. It's worth it.
So, I had the following information to go on with:
- The original implementation was a subclass of itecore.Web.UI.WebControls.Text and searched the /System/Dictionary item for a specific subpath, provided as a custom extra attribute. Once it found a match, it would then assign the match as the Sitecore.Web.UI.WebControls.Text.Item, and the "Phrase" property as the field to render. I wanted to keep that functionality as close as possible, at the very least, still subclass Sitecore.Web.UI.WebControls.Text.
- Each dictionary domain is a sitecore item within the content database, with dictionary folders and dictionary items beneath it.
- We can define a "default" dictionary domain per sitecore logical site, under /configuration/sitecore/websites/website/@dictionaryDomain attribute. The value of that attribute is the name of the dictionary domain item to use as default for that site. When the attribute is present, it should override the /Sitecore/System/Dictionary, and use that particular item as the "dictionary".
- Each dictionary domain may have a "Fallback Dictionary" [domain] property, so there is potentially an entire chain of dictionary domains one could go down into to dig up the requested resource.
- We can either ask for a dictionary item by key only, where
- We search through the hierarchy of dictionary domains and fallbacks if the site has a default dictionary domain defined or
- We search through /Sitecore/System/Dictionary if the site doesn't have a default dictionary domain or the resource was not found within the aforementioned hierarchy
- Or we can ask for a dictionary key in a specific dictionary domain, where we override the site's default dictionary domain [hierarchy] (if it exists) and go with the one provided in the override.
- The original DictionaryLiteral implementation required that the dictionary item name was identical to the dictionary item "Key" field value. That little quirk had caused a few minor problems, particularly during the first stages of entering the data within the dictionary. Since I was revisiting the idea with hindsight, I thought I should remove that requirement altogether.
So, the plan now was:
- Create a class inheriting from Sitecore.Web.UI.WebControls.Text
- Define a custom "Key" property, that will be used as our entry point, as per the original implementation.
- I had a choice of either adding a second property for domain, or go with a single property, key that would be "composite", so probably a delimited string. I opted for the latter, to avoid any race conditions where the setter for the "Key" might run before the setter for a potential "Domain" property. Therefore, providing the "Key" should be sufficient for us to derive both domain and dictionary key. The obvious choice was a delimited string. I chose the colon (":") as the separator, mainly influenced by the way ASP.NET handled inline resource file handling.
- At first, when implementing the search within a specific dictionary domain, I thought I should go with a sitecore content search to get the sitecore item for the dictionary domain. However, since this part included searching down the hierarchy, I went to see the definition of the data template for the dicitonary domain (/sitecore/templates/System/Dictionary/Dictionary Domain) to see how the dropdown with the Fallback Domain choice was populated
Which essentially means:
traverse the entire tree and find all items with template being "/sitecore/templates/System/Dictionary/Dictionary Domain" (that's the template whose ID is {0A2847E6-9885-450B-B61E-F9E6528480EF}).
Thus I chose to get the current dictionary domain (and all Fallbacks) in the good old-fashioned way of the Sitecore Fast Query
Now comes the hard part. We have an Item (either a dictionary domain or /Sitecore/System/Dictionary) and we need to search within this item's subtree for a child item that has a "Key" field, whose value should match our "SearchKey" variable's value. This sounds like a job for Sitecore Content Search, and indeed it is.
So in the above code, we get the dictionary domain item (if it exists) and search within it (that's what the extra predicate with the Path.Contains is for) for a matching item. If not found, we get the fallback domain and do it all over. If we end up having no fallbacks, we try the /Sitecore/System/Dictionary subtree as a last resort. If we started off without a domain to begin with, then we just search within /Sitecore/System/Dictionary.
There are two possible enhancements to the above code.
1. The fallback chain could lead to a circle. I'm not sure how Sitecore checks for circular references between droplinks (if at all) but we should add code for breaking the while loop if there is a circular reference
2. The actual item should be cached somehow, so that multiple calls to DictionaryLiteral do not trigger muliple searches.
3. Add a dictionary literal somewhere
4. The phrase is editable from within the page editor