I don’t have very fond memories of using .Net resource files (.resx) to handle the translation of static web page elements such as form labels.
The Visual Studio resx editor was slow and using it to change resources that were more than a single line of text was a pain. I still have nightmares where I’m trying to edit complete email templates on a single field of a resx file…
The biggest problem with resource files, however, is that they are included in the project. This creates a problem for both the developers and the content managers, since the content managers are not able to change resources themselves, and the developers need to be changing resources files when they could be doing something productive.
The following will show a way to handle resource items in a MVC website project, where the resources can be stored wherever they are more easily handled by content managers: a SharePoint list, a database table, an excel file, or something completely different.
The process is really simple:
- Select every resource item key needed to render the current page
- Fetch the resources associated with the keys provided at the end of the current action
- Populate a ViewModel property with the resource items
- Use the resource items in their respective places in the markup
1 – Compile a list of resource keys
Typically in a website, there are certain areas of the layout that are common across all pages, the header and the footer being the most prominent candidates. There are, however, other areas that can be reused across a smaller subset of pages such as a common banner in the FAQ pages. Taking this into account, we split the resource keys by groups, where each group (a list of strings) contains every resource key needed for that area and load them on each action, like so:
model.ContentResourceKeys = new List<string>(); model.ContentResourceKeys.AddRange(ResourceHelper.SOCIALNETWORK_RESOURCE_KEYS); model.ContentResourceKeys.AddRange(ResourceHelper.HOTEL_BANNER_RESOURCE_KEYS); model.ContentResourceKeys.AddRange(ResourceHelper.DOCUMENTS_RESOURCE_KEYS); model.ContentResourceKeys.AddRange(ResourceHelper.GENERAL_FOOTER_BANNERS_RESOURCE_KEYS); model.ContentResourceKeys.AddRange(ResourceHelper.LANDING_PAGE_SEARCH_AREA_RESOURCE_KEYS); model.ContentResourceKeys.AddRange(ResourceHelper.LANDING_PAGE_MODULES_RESOURCE_KEYS);
The ContentResourceKeys property is defined in a MasterModel that is extended by every View Model used across the site, as can be seen here: http://blogit.create.pt/andresantos/2014/11/07/pre-set-common-viewmodel-preperties-before-action-result/. That post also describes the Action Filter that is executed after every action on the site and it’s the place where we append the footer and header resources to the ContentResourceKeys list:
List<string> resourceKeys = new List<string>(); resourceKeys.AddRange(ResourceHelper.HEADER_RESOURCE_KEYS.ToList()); resourceKeys.AddRange(ResourceHelper.LOGIN_RESOURCE_KEYS.ToList()); resourceKeys.AddRange(ResourceHelper.FOOTER_RESOURCE_KEYS.ToList()); resourceKeys.AddRange(ResourceHelper.LOADING_RESOURCE_KEYS.ToList()); resourceKeys.AddRange(ResourceHelper.HEADER_COOKIES_RESOURCE_KEYS.ToList()); if (model.ContentResourceKeys != null && model.ContentResourceKeys.Count > 0) { resourceKeys.AddRange(model.ContentResourceKeys); }
2 – Fetch the resources
After compiling the resource keys needed, we go on and fetch the associated resource items. The project where this was implemented used a WebAPI to serve all the page content and the resource items were no exception. So, we send a POST request to the resource WebAPI method and fetch the needed resources, for the user language, from the available backoffice (we used a SharePoint list).
model.Resources.AddRange(ResourceFacade.ListResources(model.CurrentLanguageCode, resourceKeys));
3 – Add them to the View Model
The Resources property available in the View Model where we added the result of the call to the resources WebAPI method is a string Dictionary. Since C# Dictionaries don’t provide an AddRange method, we create an extension method that enables us to append resources to a dictionary that’s not empty. Moreover, with this extension method we are able to scrap duplicate keys:
public static class IDictionaryExtensions { public static void AddRange<T, S>(this IDictionary<T, S> source, IDictionary<T, S> collection) { if (collection == null) { throw new ArgumentNullException("Collection is null"); } foreach (var item in collection) { if (!source.ContainsKey(item.Key)) { source.Add(item.Key, item.Value); } } } }
4 – Use the resources in the views
Since every View Model extends the MasterModel, we are able to use the same property everywhere:
<footer id="footer"> <div class="container"> <div class="newsletter"> <h2>@Model.Resources["Footer.Newsletter.Title"]</h2>
This way, we are able to manage the resource keys in a SharePoint list, or any other backoffice solution.
The localization platform https://poeditor.com is a good online alternative to translate .resx files. I recommend you check it out, it makes collaborative localization projects a lot more efficient.
Cheers