Creating strongly typed WinRT resources

The string resources most commonly used for localisation were before WinRT stored in .resx files. These had a handy feature that they also generated a strongly typed accessor class for when you needed to refer to resources in code. In WinRT however we use .resw files and for whatever reason there isn’t a associated code generator. So how do we get strongly typed access to our string resources?

Thankfully Visual Studio always has a built in generator in the form of T4 Templates. To use create a new “Text Template” under Add New Item in Visual Studio. I typically name it Strings.tt. You can now paste the following:

This template looks for a file named “en\Resources.resw” below the .tt file. You can change this by modifying the template on line 14. It doesn’t matter what language you generate the file form as long as the resw for each language as the same keys.

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Xml" #>
<#@ assembly name="System.Xml.Linq" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#@ import namespace="System.Xml.Linq" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="Microsoft.CSharp" #>
<#@ output extension=".cs" #>
<#
   
    var inputFilePath = @"en\Resources.resw";
    var provider = new CSharpCodeProvider();
    string className = CreateClassName(provider);

    SetCurrentDirectory();

    if (File.Exists(inputFilePath)) 
    {
#>
//------------------------------------------------------------------------------
// 
// This code was generated by a resource generator.
// 
//------------------------------------------------------------------------------
using System;
using Windows.ApplicationModel.Resources;

namespace <#= GetNamespace() #> 
{
    public static class <#= className #> 
    {
        public static ResourceLoader ResourceLoader 
        { 
            get; private set; 
        }

        static <#= className #>() 
        {
            ResourceLoader = new ResourceLoader();
        }

        public static string Get(string resource)
        {
            return ResourceLoader.GetString(resource); 
        }

        public static string Format(string resource, params object[] args)
        {
            return String.Format(ResourceLoader.GetString(resource), args);
        }

<#
        foreach (string name in GetResourceKeys(inputFilePath)) {
#>
        public static string <#= BuildPropertyName(provider, name)  #> 
        {
            get 
            { 
                return ResourceLoader.GetString("<#= BuildResourceString(name) #>"); 
            }
        }

<#
        }
#>
    }
}
<#
    } 
    else 
    {
        throw new FileNotFoundException(String.Format("Unable to find Resource file: {0}", inputFilePath)); 
    } 
#>
<#+
    private string BuildPropertyName(CSharpCodeProvider provider, string name)
    {
        return provider.CreateEscapedIdentifier(Regex.Replace(name, @"\[.+\]|\.", String.Empty));
    }

    private string BuildResourceString(string name)
    {
        return name.Replace(".", "/");
    }

    private void SetCurrentDirectory() 
    {
        Directory.SetCurrentDirectory(Host.ResolvePath(""));
    }

    private string CreateClassName(CSharpCodeProvider provider) 
    {
        return provider.CreateEscapedIdentifier(Path.GetFileNameWithoutExtension(Host.TemplateFile));
    }

    private string GetNamespace() 
    {
        return Host.ResolveParameterValue("directiveId", "namespaceDirectiveProcessor", "namespaceHint");
    }

    private static IEnumerable GetResourceKeys(string filePath)
    {
        var doc = XDocument.Load(filePath);

        return doc.Root.Elements("data").Select(e => e.Attribute("name").Value);
    }
#>

One caveat is that the template isn’t automatically regenerated when the target file is changed, so whenever you want to update your strongly typed class simply right click on the .tt file in the Solution Explorer and select “Run Custom Tool”.