Allowing Localizing after-the-fact using MUI
Here is the problem. We built an English-only C++ application. However, now some of our customers are asking how they can translate the application into other languages. We don’t have the budget to provide translated versions ourselves, and they’d be happy to do the work themselves. Is there a way they can translate our application after the fact? And without our involvement?
I thought the answer was “no,” until someone suggested MUI. My first reaction was “what’s that?” MUI, as it turns out, stands for Multilingual User Interface and has been supported in one way or another since Windows XP. I did not know this. The idea is that you remove the resources from your main binaries and instead put them into MUI files, which are just DLLs with a different extension. For example:
MyApplication.exe <— No resources
MyApplication.exe.mui <— Language-neutral (in this case English)
MyApplication.exe.mui <— German
In the picture above, the first MUI file (under the en-US directory) contains the English resources (strings, dialog boxes, etc.). This MUI file is marked as both the en-US file and the language-neutral file that will be used when Windows can’t find a better match.
The second MUI (under the de directory) contains the German resources that will be used whenever the Windows’ current language is German (weather that be in Germany, or any other country).
With very little work (less than a day), we were able to build a version of our application that our customers can translate to their heart’s content. Below I describe the steps for a developer to make their application translatable after the fact.
Before you Start
In order to create an MUI-aware application, you’ll need to install the Windows 7 SDK, which is where you’ll find MUIRCT.
Creating the MUI Files
You may not need to make many changes in your application to make all of this work if you only support Vista or later. If you require Windows XP support, you’ll need to read the section below.
The first step is to run a program called MUIRCT that will move the resources from your main executables into satellite executables. This program requires a config file that describes which resources to move over. Here is the file I used:
<?xml version="1.0" encoding="utf-8"?> <localization> <resources> <win32Resources fileType="Application"> <neutralResources> <resourceType typeNameId="#16" /> </neutralResources> <localizedResources> <resourceType typeNameId="#2" /> <resourceType typeNameId="#3" /> <resourceType typeNameId="#4" /> <resourceType typeNameId="#5" /> <resourceType typeNameId="#6" /> <resourceType typeNameId="#9" /> <resourceType typeNameId="#11" /> <resourceType typeNameId="#16" /> </localizedResources> </win32Resources> </resources> </localization>
For more details, read Preparing a Resource Configuration File on MSDN.
Next you’ll need to run MUIRCT to split your files:
muirct.exe -q muiconfig.xml -v 1 -x 0x0409 ..\MyApplication.exe MyApplication.exe en-US\MyApplication.exe.mui
The “..\MyApplication” refers to the original EXE that contains all the language-neutral resources (the 0x409 says they’re en-US resources). The second-to-last parameter, “MyApplication.exe” is the name that will be used for the new version that contains none of the resources being moved. Finally, the last parameter contains the name of the MUI file that will be created to contain all the resources that are called out in the config file as resource you want to move.
If you have no need to support Windows XP, you’re done.
Supporting Windows XP
Windows Vista and later automatically handle locating and using the correct MUI file for resources. However, if your application needs to be able to run on Windows XP as well, you’ll need to make some changes to your code. In our case that was really easy because the calls to load strings were all encapsulated in a helper class. The idea is that you’ll need to call a new API that will located and load the best-match MUI file:
std::vector<WCHAR> moduleName(MAX_PATH); GetModuleFileName(hModule, &moduleName, moduleName.size()); hInstance = ::LoadMUILibrary(moduleName.data(), MUI_LANGUAGE_NAME, 0);
The first two lines get the name of the executable that we’re running. The last line is where the magic happens. LoadMUILibrary is a function that in Windows XP will locate the appropriate satellite MUI file, load that file, and return an instance handle to that file. In Windows Vista and later, this call returns the instance handle for your main executable because the system automatically handles resource location in calls like LoadString.
Additionally, you’ll need to ensure you link to muiload.lib. I like to use a pragma in my source files, rather than setup additional libraries in the link options, using a line like this:
#pragma comment(lib, "muiload.lib")
Localizing MUI Files
I’m not an expert on localization, so I’m not sure what most companies use to localize native Windows applications. Here at Microsoft we use a tool called LocStudio, and I understand there are similar programs available outside of Microsoft.
Whichever tool you use, here’s what you have to do:
- Create a new directory for the new locale using the two-letter language identifier (like “de” for German)
- Copy the language-neutral MUI file
- Translate the strings (and possibly change control sizes and positions in dialogs)
- Copy the localized MUI file into the new directory (“de” in this example)
Just a quick note about Visual Studio. You can open native Windows executable files and edit the resources in them with Visual Studio. However, you cannot save changes you make to MUI files, so you’ll have to use another tool. Again, I don’t have knowledge of those other tools.
Working with Signed Binaries
What happens if the original binaries and MUI files are code signed? It just works. I didn’t know the answer to this so I tested it. I used MUIRCT to split the files, and then code-signed both the main executable and the MUI file. Then I made a localized copy of the MUI file for German that was not signed. I was able to run this and see the English strings when I ran with English as the default language for Windows. When I switched to German and ran the program again, it showed the German strings from the un-signed German MUI files.