Resource interface generation with T4 templates (C#/Xaml)

When you globalize your Windows Store app, i.e. show the GUI in foreing languages like French or German, there’s a few things you have to do.
1. Add translated string to a resource file.
2. Add a “x:Uid” tag to the Xaml and set the value of the tag to the id of the resource string you want to show.

This is simple enough, but what if you want to display different string depending on some logic. You can’t do that in Xaml, so you must write some C# code to do it. Lets say we want to show a welcome message to the user the first time he starts the app and another message the next time he starts it.

Our Resources look like this

Name        Value
----------- -------------------
Welcome     Welcome to my app!
HelloAgain  Hello again!

A view model to acomplish this could look something like this:

public class MyViewModel
{
    public MyViewModel()
    {
        Message = FirstStart() ?
            ResourceLoader.GetForCurrentView().GetString("Welcome") :
            ResourceLoader.GetForCurrentView().GetString("HelloAgain");
    }

    public Message { get; set; }

    public bool FirstStart() { // Removed for simplicity ... }
}

This works, but it is a bit messy, brittle and hard to unit test. What we want to do, is to hide the resource loader behind a nice interface. As the resource file is xml, it is easy to read it and extract data from it. With T4 templates it is easy to generate an interface from the resource file.
Here’s a template that generates an interface.

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Xml" #>
<#@ assembly name="System.Xml.Linq" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Xml.Linq" #>
<#@ output extension=".cs" #>
<#
var resourceFile = XDocument.Load(Host.ResolvePath(@".en-USResources.resw"));
#>
namespace StringResourceDemo.Strings
{
    public interface IStringResources
    {
<#
var dataNodes = resourceFile.Elements().Elements(XName.Get("data")).ToList();
dataNodes.ForEach(node =>
{
    var key = node.Attribute(XName.Get("name")).Value;
    if (!key.Contains("."))
    {
#>
        string <# Write(key); #> { get; }
<#
    }
});
#>
    }
}

The template reads the resource file as an xml-document. Then it iterates all data nodes and writes a property for each. Note that we skip all resources with a dot in the name. If you have a control in xaml, e.g. a TextBlock, with a uid of “MyTextBlock”, then you would have a resource with the name of “MyTextBlock.Text” to set the text of the TextBlock. So the ones with a dot are resources referenced by xaml and we don’t want to access those from code.

Running the template will generate an interface that looks like this:

namespace StringResourceDemo.Strings
{
    public interface IStringResources
    {
        string HelloAgain { get; }
        string Welcome { get; }
    }
}

We also need a template to generate an implementation to the interface

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Xml" #>
<#@ assembly name="System.Xml.Linq" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Xml.Linq" #>
<#@ output extension=".cs" #>
<#
var resourceFile = XDocument.Load(Host.ResolvePath(@".en-USResources.resw"));
#>
namespace StringResourceDemo.Strings
{
    using Windows.ApplicationModel.Resources;

    public class StringResources : IStringResources
    {
<#
var dataNodes = resourceFile.Elements().Elements(XName.Get("data")).ToList();
dataNodes.ForEach(node =>
{
    var key = node.Attribute(XName.Get("name")).Value;
    if (!key.Contains("."))
        {
#>
        public string <# Write(key); #> { get { return GetString("<# Write(key); #>"); } }
<#
        }
});
#>

        private string GetString(string key)
        {
            return ResourceLoader.GetForCurrentView().GetString(key);
        }
    }
}

This will generate an implementation that looks like this:

namespace StringResourceDemo.Strings
{
    using Windows.ApplicationModel.Resources;

    public class StringResources : IStringResources
    {
        public string HelloAgain { get { return GetString("HelloAgain"); } }
        public string Welcome { get { return GetString("Welcome"); } }

        private string GetString(string key)
        {
            return ResourceLoader.GetForCurrentView().GetString(key);
        }
    }
}

Now we can modify the view model to use the interface instead:

public class MyViewModel
{
    public MyViewModel(IStringResources stringResources)
	{
	    Message = FirstStart() ?
			stringResources.Welcome :
			stringResources.HelloAgain;
	 }

	 public Message { get; set; }

	 public bool FirstStart()
	 {
		// Removed for simplicity ...
	 }
}

Now it is much easier to unit test the view model and if we change the resource ids we will get compile time errors instead of runtime errors. Much better.

A demo project with all code from this blogpost is available on GitHub: https://github.com/jayway/T4ResourceInterface

Leave a Reply

Close Menu