It’s been a busy month and I’ve not had much of a chance to work on my app for a few weeks. So, the official ‘Week 1’ series of posts starts now with a bit of work I did on porting a T4 template from C# to F#.
This post is part of a series on my journey rebuilding my app and learning a lot of new stuff. Other posts in the series:
Series: All About Money - A journey to build a mobile app
- The state of the app
- The goal: first x-platform release
- Thinking functional and DDD - in the personal finance tracking context
- The new Project structure
- Design of the existing app
- UI and wireframes
… more to come …
- The final push to release
T4 is awesome! In the C# version of my app - I use it to generate some C# to access string labels from resource files at runtime. Now - this is a problem that has been solved before many times and well.
My requirement for text localisation is simple. AS discussed in my project structure post, I’ve got a portable class library (PCL) -
Money.Common.UI - that has all the view models which I plan to share across the Windows / Android phone / tablet versions. Since the UI is going to be very similar on different platforms - my labels / text etc. on the UI is likely to be similar too. Also, I have longer term plans to translate the app for other languages (i.e non-English). There are some parts of the UI where the labels are only used/declared in the UI project(s) - like labels for buttons etc. But some others, like page headings, error/information/warning messages and similar ones need to be driven from code - from the view models.
Given this, I need a solution that:
- Let’s me create a simple (ideally text-based) file which holds all UI the strings
- The resource file should be easy to edit and share with people who are language translators
- The resource file should have a structure, so I can use it to generate strongly-typed code that references the labels
- The file should be transformable to other text / other formats used by each UI platform to represent string resources in the native tools for each platform (eg.
.reswfiles for Windows,
.xmlfiles for Android etc), so that I can make the best use of the localisation support built into each platform, and not have to hack it into the UI myself with some custom libraries.
These requirements are not new for the re-write of this app. I’ve had these for a while, and I already came up with a simple working solution using
.resx files + T4 to generate some C# code (that doesn’t use System.Resources.ResourceManager APIs - because these APIs don’t seem to work well for WinRT apps).
The new/tricky part for me is - to get it to generate F# code, given T4 tools are not natively supported in Visual Studio when using F# projects.
The existing C# solution
With the old C# solution, the tooling is great. T4 is natively supported and generates files when using ‘Run Custom Tool’ (or automatically if needed during the build process) - the files are added to the project automatically and I can even use something like the free version of the Tangible T4 editor to get basic intellisense when working with the
I could generate a static class in C# that would delegate the reading of the resource files to a platform specific API via a common interface - so the PCL doesn’t need to know how to read platform-specific version of the resource files. So, I abstracted that into the following interface:
In the PCL project, there is a
Strings.tt file which looks like the below:
.tt file reads a
.resx that is named after it:
The build action for the
Strings.resx is set to ‘None’ - since I didn’t really want the default code with the
ResourceManager APIs generated for a
.resx file - my
.tt file handles it with my customisations. The code generated from the
.tt file looks like:
Now, the usage of the labels in the view model code becomes simple, easy, and strongly-typed:
In the Windows UI project, another
.tt file would take the
.resx file and convert it to a
.resw file which is what WinRT localisation APIs natively use.
Finally, the Windows UI project also has a
IResourceLoader implementation as follows:
Now, it seems like having all these parts makes it unnecessarily complex. Especially, given MVVMCross has some localisation plugins. But, the
.tt files were already written: since my original C# project was written in 2012, started with just a Windows store app and I didn’t start with MVVMCross. In my case, it felt like just reusing most of the existing work I’d already done seemed sensible. If starting a new project, I might consider one of the JSON/other localisation plugins with MVVMCross - but I’m not sure how well they work with the native localisation APIs in each platform.
(The full gist for all the files above is at: https://gist.github.com/krishna-nadiminti/844fe77c9722374a2fb5)
The F# version
Since my view models project is now a F# PCL - I need the strongly-typed code to also be in F#.
First off, my plan was to just re-use the existing T4 template (written in C#) - to generate F#. But as soon as I found that T4 isn’t well supported in FSharp projects, and that you’d have to resort to adding pre-build steps on the project file to run the templating engine on the command-line, I started looking for nicer / cleaner options…
Next, I considered using the ResX type provider - but it was tied to the
ResourceManager API too. That wouldn’t work in this case. I need my UI platform to provide the implementation of a
IResourceLoader, while the PCL can just read the resources and use them without worrying about how the implementation looks like on each platform.
After looking around a bit more, @pezi_pink’s Mixin type provider seemed promising - but too complex for my case. After all - all I needed was to generate a bunch of constants in a language, reading strings/keys from a text file. After a quick twitter conversation, he suggested I look at Templatus - which uses a T4-like templating language and a command-line tool to generate text, with the advantage of being able to write the template in F# + text. I tried playing with it for a little bit, but couldn’t get some of the basic command-line parameter passing options to work. Again, if I didn’t already have a working T4 template, I’d probably had dug deeper to get it working.
In the end, I decided to just stick with T4, and add a pre-build event to run T4 on the command-line for the F# project:
"C:\Program Files (x86)\Common Files\Microsoft Shared\TextTemplating\14.0\TextTransform.exe" $(ProjectDir)Strings.tt
I had to manually add the
Strings.generated.fs project the first time and order it correctly so that the view models can access it - but after that it was easy.
The advantage with this approach is that I didn’t need to modify my 958 line
Strings.resx file from the C# version. And I only had to spend less than 10 minutes porting the C# version of the
Strings.tt to the F# version. (The F# version is still written in C#, but generates F#!).
In fact, I spent way more time researching the options and writing up this blog post, than actually porting the
Strings.tt file and getting it to work with the F# PCL.
The interesting part is how simple the F# version looks and reads:
Everyone knows F# is terse, but the readability is significantly improved here. And one quick glance tells you - it is just a bunch of constants.
122 lines in C# vs 26 lines in F#. (excluding comments)
So - this has been my journey so far with localisation.
Till next time…