In Part 1 & 2 we created a Windows Installer using Wix in the form of an MSI file. In the real-world installers often require localization and in this part we’ll look at a solution for localizing the installer into a second language; in this example Italian.
Strictly speaking Windows Installer doesn’t support multiple languages and can only store one set of strings at any point in time. However it does provide a mechanism which allows for localization via an implementation of transforms. A transform is a delta
MST file which can sit inside or outside of the main MSI package. By supplying the transform to Windows Installer the MSI content is replaced by matching content in the MST file at runtime before installation begins, which is ideal for localization.
Our localization solution involves the following steps:
- Make the Wix source localizable
- Generate an Italian version of the MSI file
- Create a MST transform file
- Embed the transform file into the original MSI
Wix contains all of the tools required for localization, and the WixUI library we are using already comes with stock translations for the UI dialogs provided. The localization authoring features allow us to move localizable strings into separate Wix translation files, which can then be swapped out by LIGHT when generating an MSI. This will ultimately make it easier to generate transforms later on in the process.
In the solution provided all of the custom strings have been moved into external localization files and named with the relevant culture code. These are then referenced in the main source files using the syntax
WixLocalization element in the localization file must contain correct values for
Codepage attributes for the localized version to function correctly with different character sets. We also add the language LCID as a translation string so that we can reference the language code as a variable in the main project and override the product language attribute.
The final localization file looks something like this:
<WixLocalization Culture="en-GB" xmlns="http://schemas.microsoft.com/wix/2006/localization" Codepage='1252'> <String Id='LANG' Overridable="yes">1033</String> <String Id="Windows7Required" Overridable="yes">This application only runs on Windows 7.</String> ... </WixLocalization>
The main source code references these localization strings as follows:
<![CDATA[VersionNT = 601 AND WindowsBuild > 7100]]> </Condition>
Once the strings are re-factored into the localization files, we can go ahead and get these translated into multiple languages. A set of localization files are created for each new language and the culture specific attributes set up accordingly.
Create Localized MSI
LIGHT provides some handy features that allow us to create a localized version of the MSI. Firstly the utility takes multiple translation files that can contain a mixture of cultures. The output file however will only contain a single set of translation strings. So how does this work?
The trick is the
cultures argument that takes a list of cultures in order of precedence. From left to right LIGHT will look through the supplied translations looking for the first matching culture for each string. This allows us to build fall-backs into our translation set.
Please note that WixVariables cannot reference localized translation variables, and therefore to swap out the path to the T&Cs agreement we must change our solution so that this is passed through on the command line. We can then pass in references to different agreement files for each localized installer.
So implementing this technique we can create a Italian version of the Installer as follows:
-ext WixNetFxExtension.dll -ext WixUIExtension.dll -cultures:"it-IT,en-GB" -dWixUILicenseRtf=Binary\EULA_it-IT.rtf Main.wixobj Component.wixobj -loc it-IT.wxl -loc en-GB.wxl
The example above will look for Italian translations, and for any strings not found it will fall back to English. Note, the naming of the
WXL files is irrelevant to the linker.
Creating a Transform
Now that we have an English and Italian version of the MSI we can generate a transform file. The transform will contain a set of differences between the two files. In this case it will contain just the Italian translations and a new agreement file.
The Wix TORCH tool is used to create the transform
MST file as follows:
torch.exe setup.msi it-IT\setup.msi -o it-IT.mst
it-IT.mst file is smaller than the original and contains the transforms which can be applied to the original MSI file in order to localize the installer. It could be deployed alongside the MSI file, but in our case we want to embed the transform and create a single MSI for ease of deployment.
This is where we can leverage an undocumented and unsupported feature of Windows Installer which allows us to auto-detect the language of the installer from the user’s region settings. By naming the transform with the corresponding LCID Windows Installer will automatically apply the transform at runtime.
The tool used to embed the MST with a specific name is a script taken from the Microsoft Windows Software Development Kit. This is used as follows, where
1040 is the Italian LCID:
cscript.exe WiSubStg.vbs setup.msi it-IT.mst 1040
Once embedded the transform can be applied explicitly through the command line to launch the Italian version of the installer.
msiexec /i setup.msi TRANSFORMS=":1040"
TRANSFORMS=":1040" syntax (with colon) applies embedded transforms, whereas
TRANSFORMS="it-IT.mst" (without colon) will apply an external transform file.
To test the auto-language detection functionality change the regional settings by opening control panel and navigating to regional settings. On the first dialog which defines date and time Formats, select Italy from the language selector. Apply the settings and launch the MSI with no parameters. You should then see the Italian version of the installer.
In this part of the series we talked through localizing the installer by leveraging some of the Wix tools and by taking advantage of some undocumented features of Windows Installer. Our installer is almost complete. In part 4 I’ll show how to create bootstrapper for the installer that will detect and install any prerequisites.