In 2014 when this project began, the expectation was to localise the application from the beginning. This was only attempted half-heartedly with a few .resx files. Since the only customer on the horizon was in the UK, it was largely abandoned. Four years later we have many UI screens, thousands of error messages and a huge amount of pharmaceutical and industrial business language to translate.

Requirements

  • It must support MVVM
  • Translation can occur in the xaml
  • Translation can be done in the view model
  • Translation can also happen anywhere else in the application, not just the UI component
  • All methods of translation should reuse the same code and the same resource
  • We need to be able to support customer specific translations. For example for some companies they want to select a "SKU" (Stock Keeping Unit) and others want to select a "production order". We need to match their domain language
  • Language switching must be dynamic at runtime

Solution

We decided to use Markup Extensions as described here http://www.wpftutorial.net/LocalizeMarkupExtension.html. The site also contains a code example which fills in some of the bits missing from the code examples on the page.

This allows us to access translation functionality from xaml like so:

xmlns:localisation="clr-namespace:UserInterface.Localisation"
...
<Label Content="{localisation:Translate LoadingScreenView.Version}"/>

We decided to name all the keys with the format ViewModel.KeyName in a hope to make it somewhat clear where keys are located.

I'm not certain why the example returns an object from the translation and not a string. This is something we'll look to change in the future because it would avoid needless .ToString() calls on the return.

Calling it from a view model looks like this:

TranslationManager.Instance.Translate($"StartupTaskModel.{Title}").ToString();

We put the TranslateExtension, TranslationData, LanguageChangedEventManager and TranslationManager in the UI component and dependency injected the ITranslationProvider into the TranslationManager. We decided to have a JSON file be our key value store instead of a resx file used in the example.

Conclusion

It's always a difficult decision on whether or not to localise from the beginning or to do it later. Here I think we made the right choice. We spent years developing unhindered by the localisation technology and only added it when we needed to. I don't think it took extra time to add it in now and we were able to make better decisions because we knew the requirements rather than making a guess. I hope I'll be saying the same thing if we ever have to do right to left support.