With version 7.3.3 of the DNN Platform a new “localization feature” has been implemented: list localization. Lists are a core platform feature whereby users can maintain lists that other bits of software can maintain. There are several lists that come out-of-the-box when you install DNN (see “Lists” table in SQL). The most visible ones are countries and regions. But there are also currencies and frequencies, plus a whole bunch of lists that tell DNN how to run things (i.e. they allow us to configure particular bits). The new feature means that what you will see in the front end of DNN can adapt to the language of the user viewing it.
In a nutshell the way the localization works is by examining the App_GlobalResources directory to see if there’s a resource file with the name “List_[ListName]”. So for the countries list we’d look for List_Country.fr-FR.resx if we’re localizing in French. “Country” is the name of the countries list. The “Value” in the Lists table is then used to look up the resource key. So, for instance, if we take Switzerland, the key “CH.Text” is looked up in this file to find “Suisse”. Naturally when you begin to pull one string, other parts move as well. So this change has prompted a couple of other changes as well to make this as smooth as possible. So for those that want to know more about this, I’ll elaborate on these changes below.
Completing Countries, Regions and Currencies
There are several good sources of geographic information on the web. And after a little research I quickly found localized lists of countries as well as complete lists of regions and currencies around the world. So one of the simpler changes that is rolled out with this version is the completion of our country, region and currency lists. The change script for DNN 7.3.3 will make a couple of additions to the country table (a couple of new countries have emerged since the last update) and the completion of the regions and currency lists. This now means that if you use region in your user’s profile, they will most likely see the region textbox as a dropdown and show the regions of the country they selected.
You may ask “Why, Peter? Why add those to each and every installation on the planet for the sake of the non-US installations?”. And you’d probably either be from the US or you might not even have noticed as you really didn’t use this feature. My reply is: out of respect for this planet’s diversity. I’ve looked at it closely and could not detect any downsides (performance wise). So it should not in any way hinder anyone else. But what we’d gain is that we’re no longer requiring those who wish to use the feature outside the US to create their own lists. Plus: if you’d have a global site (i.e. you expect to use the full features for registration from people from around the world) we’ve now saved you a gargantuan task of filling in all those regions.
User Profile Changes
There are two properties on the user profile that make use of lists: country and region. Up until now the value stored in the user’s profile was actually the “Text” value of the selected item in the list. I.e. if you selected “Switzerland” from the dropdown, the value stored would be “Switzerland”. This is of course not a good way to do this. It should have been “CH” so that someone else seeing your profile might be able to see “Suisse”. And there are about a thousand more reasons why we don’t store the text value of things in related tables. So I aimed to correct this anomaly once and for all.
In the change script for DNN 7.3.3 there is SQL code that will attempt to look up the values stored on the users’ profile and change the “Text” value to the “Value” value if it is found in the Lists table. This is done for both countries and regions. So users with “Switzerland” now get the value of “CH” stored in this field.
Naturally this means we need to do the lookup every time the profile property is requested. That change has also been made to the user profile code.
One other consequence of this change is that the list editor would need to become aware of this multi-lingual capacity. Until now the list editor would only interact with the Lists table. But now we need to come up with a strategy for editing lists that are localized. What happens if someone is editing while the page is set to Spanish? The solution we came up with is that editing in any other language than the System Locale (i.e. en-US), we will write out the text value to the resource file. But what if we first create a list entry? Well, in that case it will go to the table as well. So if you first add an entry in Spanish and later edit it in en-US, in the latter case you’d just be editing the value in the database and the original Spanish text would still remain in the resource file.
Note that to make this happen we needed to introduce two more changes. First the ListEntryInfo has a new property TextNonLocalized that stores the database value. We need this value to make sure we can still add and edit without overwriting the database value with a localized value. Secondly we keep an internal list of tables that are used just by DNN for actual logic operations (ContentTypes, Processor, etc) that will bypass this logic. These lists are actually something of an anomaly as they are not really meant for front-end consumption. So they have no business being localized.
New Localization Methods
One other gem I’d like to draw your attention to is a few extra methods to deal with resource files. In various places I’ve seen code to manipulate resource files, but what has bugged me for long is that this wasn’t done in the right place. Ideally, right next to “GetString” (which is the central retrieval method for a resource in the entire framework) you’d have something like “SaveString”. Right? I mean, that is where this should be. So we now have this method in DotNetNuke.Services.Localization. LocalizationProvider:
bool SaveString(string key, string value, string resourceFileRoot, string language, PortalSettings portalSettings, DotNetNuke.Services.Localization.LocalizationProvider.CustomizedLocale resourceType, bool addFile, bool addKey)
So basically this save a key in a resource file the same way it’d be retrieved. The last two parameters tell this method whether it should create the key and/or file if it isn’t already there.
And as I was working on the above I took the liberty of adding a few extension methods for manipulating Xml. The (somewhat outdated) XmlUtils class contains a few methods to manipulate Xml, but these methods were created with a specific scenario in mind. Creating the SaveString method mentioned above, I could not use any of those methods and still cut back on code. Microsoft’s Xml code can be a bit cumbersome at times and if a project involves a lot of XML manipulation I’ll add some methods to cut down on code. So here are a few gems for you to use in your own code. Note these methods are not just extension methods, they are largely chainable as well. Meaning you can do stuff like this:
Node.AddElement(“foo”, “bar”).AddAttribute(“fooName”, “foo1”).AddAttribute(“barName”, “bar1”)
I hope you like the additions and changes. Although it is always possible to say: this didn’t go far enough, you need to realize that a solid platform is built in baby steps. These changes are incremental. But I believe that code that was added does what it aims to do and makes a difference for those installations that are not en-US. Please let me know if you have any issues with this change.