WIX 3.8 Localization

All the joy and pain found in migrating a WIX 2.x project into 3.8.

The goal:
Get a localized per-user installer from WIX that supports English and Arabic. Installer hacks should be kept to a minimum.

Chalenges found along the way:

  • The languages are in different code pages
  • They released WIX 3.x without the arabic localization found in WIX 2.x (sigh)
  • Some settings need to be modified in the project file manually (mainly transform and build related)
  • The documentation for WIX has some major gaps
  • Left to right text (arabic localization) still doesn't work correctly in WIX. This is mainly a positioning issue rather than the text itself

Steps

1. Create a typical installer

Create a new WIX project and add all necessary directories, components, component references, etc to create a working installer (non-localized). This isn't covered here, this post is more about the localization issues.

As far as localization goes, make sure that you add the localized dll files from your application to the installer.

2. Create the localization translation files

The idea here is to have one .wxs file for the installer components and features and separate files with the localized strings. Localization files end in the .wxl file extension and look like the following:

<?xml version="1.0" encoding="utf-8"?>
<WixLocalization Culture="en-us" xmlns="http://schemas.microsoft.com/wix/2006/localization">
	<String Id="ApplicationName">My Application Name</String>
	<String Id="ManufacturerName">My Manufacturer Name</String>
</WixLocalization>

These string variables can then be used in the .wxs file like so:
<product Name="!(loc.ApplicationName)">

for debugging purposes, make sure to add the cultures to build to the build tab in the project settings. You will get build errors otherwise.

3. Update the installer to use transforms

Candle, Light, and Torch

WIX builds off of three main tools; candle, light, and torch. Candle is the preprocessor for the wxs files and creates the wixobj file. Light is responsible for creating the windows installer msi from the wixobj file. Torch performs a diff to create a transform. In our case it is used to create transforms for each language such that a single msi file plus the transform can be used to run install the application.

Because not all of the languages are in the same code page, we will need to start by creating a new codepage variable in the wxs file. Let's call the variable "codepage". This variable gets set via the candle application. Update the product and package to use it.

<Product Codepage="$(var.codepage)" and <Package SummaryCodepage="$(var.codepage)"

Next we need to setup the targets for each codepage. To do this you must right click the project and select "unload". Right click it again and select "edit". Near the bottom of the file there is a section for Targets. For each code page create the following:

English (code page 1252):

<Target Name="CP1252" Outputs="$(SolutionDir)Project.Setup\obj\$(Configuration)\##ProductFull##_1252.wixobj">
<Candle ToolPath="$(WixToolPath)" Extensions="WixUIExtension;WixUtilExtension" SourceFiles="PerUser.wxs" OutputFile="$(SolutionDir)Project.Setup\obj\$(Configuration)\##ProductFull##_1252.wixobj" AdditionalOptions="-dcodepage=1252 -dProductVersion=$(Version) -d&quot;SolutionDir=$(SolutionDir)..\setup&quot; -dConfiguration=$(Configuration) -dPlatform=$(Platform)" />
</Target>

Arabic (code page 1256):

<Target Name="CP1256" Outputs="$(SolutionDir)Project.Setup\obj\$(Configuration)\##ProductFull##_1256.wixobj">
<Candle ToolPath="$(WixToolPath)" Extensions="WixUIExtension;WixUtilExtension" SourceFiles="PerUser.wxs" OutputFile="$(SolutionDir)Project.Setup\obj\$(Configuration)\##ProductFull##_1256.wixobj" AdditionalOptions="-dcodepage=1256 -dProductVersion=$(Version) -d&quot;SolutionDir=$(SolutionDir)..\setup&quot; -dConfiguration=$(Configuration) -dPlatform=$(Platform)" />
</Target>

Most of the work here is done via the candle application. If you have extensions like the WixUIExtension in your project make sure you add them here. Note that each custom parameter starts with a "-d". So our "codepage" variable becomes:

-dcodepage=1252

Next up we need to create new targets for each language. Let's start with english.

<Target Name="ENBuild" DependsOnTargets="CP1252" Inputs="$(SolutionDir)Project.Setup\obj\$(Configuration)\##ProductFull##_1252.wixobj" Outputs="$(SolutionDir)Project.Setup\bin\$(Configuration)\##Installer.MsiName##.msi">
<Light ToolPath="$(WixToolPath)" ObjectFiles="$(SolutionDir)Project.Setup\obj\$(Configuration)\##ProductFull##_1252.wixobj" Cultures="en-us" LocalizationFiles="en-us.wxl" Extensions="WixUIExtension;WixUtilExtension" OutputFile="$(SolutionDir)Project.Setup\bin\$(Configuration)\##Installer.MsiName##.msi" SuppressIces="ICE91" />
</Target>

Now let's do arabic:

<Target Name="ARBuild" DependsOnTargets="CP1256" Inputs="$(SolutionDir)Project.Setup\obj\$(Configuration)\##ProductFull##_1256.wixobj" Outputs="$(SolutionDir)Project.Setup\bin\$(Configuration)\##ProductFull##_ar-sa.msi">
<Light ToolPath="$(WixToolPath)" ObjectFiles="$(SolutionDir)Project.Setup\obj\$(Configuration)\##ProductFull##_1256.wixobj" Cultures="ar-sa" LocalizationFiles="ar-sa.wxl" Extensions="WixUIExtension;WixUtilExtension" OutputFile="$(SolutionDir)Project.Setup\bin\$(Configuration)\##ProductFull##_ar-sa.msi" SuppressIces="ICE91" />
<torch ToolPath="$(WixToolPath)" BaselineFile="$(SolutionDir)Project.Setup\bin\$(Configuration)\##Installer.MsiName##.msi" UpdateFile="$(SolutionDir)Project.Setup\bin\$(Configuration)\##ProductFull##_ar-sa.msi" AdditionalOptions="-p -t language" OutputFile="$(SolutionDir)Project.Setup\bin\$(Configuration)\1025.mst" />
</Target>

Note that the english target does not contain the torch command.

Next up we add a build target that creates everyting.

<Target Name="Build" DependsOnTargets="ENBuild;ARBuild">
</Target>

If you build your code at this point you will notice an msi file for each language. Don't worry about it. The final bootstrapper installer will just have the original base msi and all of the language transforms.

4. Add the missing installer messages

By default, WIX does not localize messages on the progress bar of the installer. That means that the status message will still say things like "installing files" or "updating the registry" in the non localized language.

In the real world this just won't cut it. We need all of the messages to be in the correct language. To do this create a UI element in the wxs file:

<UI>
	<ProgressText Action="InstallFiles" Template="!(loc.InstallFilesTemplate)">!(loc.InstallFiles)</ProgressText>
<ProgressText Action="CreateShortcuts" Template="!(loc.CreateShortcutsTemplate)">!(loc.CreateShortcuts)</ProgressText>
<ProgressText Action="WriteRegistryValues" Template="!(loc.WriteRegistryValuesTemplate)">!(loc.WriteRegistryValues)</ProgressText>
<ProgressText Action="RegisterUser" Template="!(loc.RegisterUserTemplate)">!(loc.WriteRegistryValues)</ProgressText>
<ProgressText Action="RegisterProduct" Template="!(loc.RegisterProductTemplate)">!(loc.RegisterProduct)</ProgressText>
<ProgressText Action="PublishFeatures" Template="!(loc.PublishFeaturesTemplate)">!(loc.PublishFeatures)</ProgressText>
<ProgressText Action="PublishProduct" Template="!(loc.PublishProductTemplate)">!(loc.PublishFeatures)</ProgressText>
<ProgressText Action="InstallFinalize" Template="!(loc.InstallFinalizeTemplate)">!(loc.InstallFinalize)</ProgressText>
</UI>

Next up add the localization strings to your language specific wxl files:

<String Id="InstallFiles">Installazione del archivos</String>
<String Id="InstallFilesTemplate">Archivo: [1], Tamaño de archivo: [6], Directorio: [9]</String>
<String Id="CreateShortcuts">Creacion de los atajos</String>
<String Id="CreateShortcutsTemplate">Atajo [1] creado</String>
<String Id="WriteRegistryValues">Escribir en registro</String>
<String Id="WriteRegistryValuesTemplate">Camino: [1], Nombre: [2], valor: [3]</String>
<String Id="RegisterUser">Registrar a los usuarios</String>
<String Id="RegisterUserTemplate">Usario: [1]</String>
<String Id="RegisterProduct">Registrar producto</String>
<String Id="RegisterProductTemplate">Producto: [1]</String>
<String Id="PublishFeatures">Publicar las características</String>
<String Id="PublishFeaturesTemplate">Caraterística: [1]</String>
<String Id="PublishProduct">Publicar el producto</String>
<String Id="PublishProductTemplate">Producto: [1]</String>
<String Id="InstallFinalize">Finalizar la instalación</String>
<String Id="InstallFinalizeTemplate">Finalizar [ProductName]</String>

For additional info see the stackoverflow link in the references.

Note that left to right text still doesn't work right in WIX

5. Create the bootstrapper

A bootstrapper is required to tie everything together. Basically all it needs to do is throw up a language selection dialog. It will then just call the windows installer with the language as a parameter.

I used a very simple NSIS installer for this but pretty much anything will do. Here's what it needs to include:

  • The msi file
  • All of the mst transform files (1 for each language outside of the one in the msi

In the end, the msi needs to be installed with the transform applied. It should do th equivalent of:

msiexec /I setup.msi TRANSFORMS=1025.mst

For english just run it without the transform.

Conclusion

WIX is a project that is in dire need of being re-written. As it stands now it is basically an xml wrapper for the windows installer. While I suppose that's not a bad thing in and of itself, it would be much nicer if it abstracted more of the grunt work and removed the gotchas. As it stands now, it also looks to be the best windows installer out there, including commercial packages.

Maybe someday I'll get around to writing my own windows installer that is more concerned with actual use cases :)

References

Wix Toolset

WIX: List of Tools

Joy of Setup

Wix Localization

Stackoverflow

WIX 3.8 Localization
Share this