Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

LocBaml + MsBuild + ClickOnce Deployment

0.00/5 (No votes)
19 Mar 2010 1  
How to integrate LOCBAML with MSBuild to create a WPF ClickOnce deployment package

Introduction

This article gives a possible solution on how to integrate LocBaml and MSBuild and ClickOnce deployment. My solution is tested with VS2008 SP1, but it should also work with older versions of Visual Studio.

Microsoft was kind enough to give us LocBaml.exe, but the application is just a sample application. This means the application has some quirky behavior, but nobody is building a full commercial application to handle localization, until that time we are forced to use this tool. If you want to use it in something other than a toy app, we need to be able to generate a complete application with all the localized satellite assemblies with each build of the project.

Obstacles

Merging

This brings us to the first hurdle, we need to merge the results of the last translation back into the build. LocBaml basically converts the binary XAML data into a CSV file. This means every change in an XAML file gives a different CSV and it’s a bit cumbersome to translate the CSV after each build. I solved this by writing an application (MergeLocBamlCsv) that merges the newly generated CSV with a translated CSV of the last build. Both application EXE and source are included in the zip file.

The merge process is simple:

  • Parse the original translated file
  • Split each line at the comma into 7 parts
  • Remember the 7th part for each line with a translated text part. This means for a string resource or a where the 3rd column is not ’None’ and the text doesn’t start with a ‘#’ (this is a link). We use the first 2 parts as key to compare with the new CSV file.
  • We overwrite the translated file with the new CSV file.
  • We split each line of the new CSV file and compare the first 2 parts. If they match with a pair of the translated CSV we replace the 7th part and join the parts with a , to a complete new line.

Stripping

The second hurdle is all the stuff in the CSV file we don’t need for translation. In a big program, the CSV can become cluttered with details and ‘false positives (links)’. To simplify the translation process, I have written a second program (StripLocBamlCsv) to strip away all the lines unnecessary for translation. This leaves us with a CSV file where every last column can be translated, instead of 1 every 5 -10 lines. Both application EXE and source are included in the zip file.

The strip process is even simpler:

  • Parse the original translated file
  • Split each line at the comma into 7 parts
  • Check if the line has a (translated) text part. This means for a string resource or a where the 3rd column is not ’None’ and the text doesn’t start with a ‘#’ (this is a link). These lines we write back, all the others are skipped.

Visual Studio/ MSBuild

Visual Studio has no out-of-the-box support for building satellite assemblies for a WPF app with ClickOnce deployment. To fix this, I made a new project target special for LocBaml. My solution expects to use only XAML files, no custom resx files. This corresponds to approach 2 in the CodeProject article Localizing WPF Applications using Locbaml by brunzefb. I suggest to read this article because I don’t want to repeat his excellent explanation of the approach.

The Solution

My solution is based on 3 applications and a separate msbuild target file:

  • LocBaml.exe
  • MergeLocBamlCsv.exe
  • StripLocBamlCsv.exe
  • LocBaml.Target.xml

They are included in the sample application that accompanies this article. They are only needed in the second stage, if we have an application in our original culture.

The extra msbuild rules has two parts:

  • Add an extra build action ‘LocBamlCsv’, so we can place all code in msbuild list of files.
  • Overrule the ‘CreateSatelliteAssemblies’ target. This target will be called after the EXE is built, but before the application manifest is created. We can create our satellite assemblies here and add them to the list ‘IntermediateSatelliteAssembliesWithTargetPath’. After this is done, all the DLLs are automatically added to the application manifest in deployment files. So when we publish the application, they are automatically added without any additional work by us.
  • The build rules looks like this:

    <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <!-- Adds the build action 'LocBamlCsv' -->
    <ItemGroup>
    <AvailableItemName Include="LocBamlCsv" />
    </ItemGroup>
    
    <Target Name="CreateSatelliteAssemblies" DependsOnTargets=
    	"$(CreateSatelliteAssembliesDependsOn)">
    <!-- Locbaml needs the runtime assemblies in the intermediate dir -->
    <Copy SourceFiles="$(ProjectDir)Translation\locBaml.exe" 
    	DestinationFolder="$(IntermediateOutputPath)" />
    <Copy SourceFiles="@(ReferenceCopyLocalPaths)" 
    	DestinationFiles="@(ReferenceCopyLocalPaths->'
    	$(IntermediateOutputPath)%(DestinationSubDirectory)%
    	(Filename)%(Extension)')" SkipUnchangedFiles="true" />
    
    <!-- Locbaml, do it in 4 steps.
    1) parse us culture and create csv.
    2) merge new csv with last known translated csv
    3) generate a new assembly from the merged csv
    4) strip translation CSV -->
    <MakeDir Directories="$(IntermediateOutputPath)%(LocBamlCsv.Culture)" />
    <Exec Command="LocBaml /parse $(UICulture)\$(TargetName).resources.dll 
    	/out:$(ProjectDir)Translation\translate.csv"
    WorkingDirectory="$(IntermediateOutputPath)" />
    <Exec Command="$(ProjectDir)\Translation\MergeLocBamlCsv 
    	%(LocBamlCsv.FullPath) $(ProjectDir)Translation\translate.csv" />
    <Exec Command="LocBaml /generate $(UICulture)\$(TargetName).resources.dll 
        /trans:%(LocBamlCsv.FullPath) /out:%(LocBamlCsv.Culture) /cul:%(LocBamlCsv.Culture)"
    WorkingDirectory="$(IntermediateOutputPath)" 
      Outputs="$(IntermediateOutputPath)%(LocBamlCsv.Culture)\$(TargetName).resources.dll" />
    <Exec Command="$(ProjectDir)\Translation\StripLocBamlCsv %(LocBamlCsv.FullPath)"/>
    <Delete Files="$(IntermediateOutputPath)\locbaml.exe" />
    
    <!-- Add the new satellite DLLs to the list, so they are added to the manifest.-->
    <ItemGroup>
    <IntermediateSatelliteAssembliesWithTargetPath Include=
    	"$(IntermediateOutputPath)%(LocBamlCsv.Culture)\$(TargetName).resources.dll">
    <Culture>%(LocBamlCsv.Culture)</Culture>
    <TargetPath>%(LocBamlCsv.Culture)\$(TargetName).resources.dll</TargetPath>
    </IntermediateSatelliteAssembliesWithTargetPath>
    </ItemGroup>
    
    </Target>
    </Project> 

    Here is a step by step list on how to get it all to work.

    Step 1 Build a WPF application
    Step 2 Add tag <UICulture>en-US</UICulture> to the project file.
    Step3 Uncomment line with NeutralResourcesLanguage in AssemblyInfo.cs
    Step 4 Add the UID’s to each XAML file
    Msbuild /t:updateuid <project file>
    Msbuild /t:checkuid <project file>
    Step 5 Copy the applications to the (new) subfolder Translation in the project directory.
    Step 6 Place the XML file in the root of the project directory and import in the project file. A nice place is at the end of the file after the other import tag.
    <Import Project="$(ProjectDir)LocBamlCsv.Target.xml" />
    Step 7 Place an empty CSV file in the directory and set the build action to ‘LocBamlCsv
    Step 8 For some reason, the culture can't be changed from Visual Studio. Add the correct Culture manually to item in the project file so we get an entry like: <LocBamlCsv Include="Translation\Translate.nl-NL.csv" >
    <Culture>nl-NL</Culture>
    </LocBamlCsv>
    Step 9 Build the application
    Step 10 Translate the CSV files from step 7, they are now filled.
    Step 11 Build again and you have a localized application.
    Step 12 Don’t forget to repeat step 4 before you begin a new translation. Otherwise the build and publish can be repeated without any worries.

    Sample Application

    I have included an example application to have a functional starting point. It consists of a main application window with a close button and a login window. The login window has some flags on the bottom of the window to switch cultures during runtime. The application currently has 3 languages: English, German and Dutch. Each window has some static and some dynamic text parts.

    Points of Interest

    The code to switch the language needs to reload the XAML and the resource file, otherwise you keep the same language, even after the UI culture is changed. So the window is closed and must be redisplayed. Secondly the merged dictionary is cleared and reloaded, this reloads our string table.

    The code to do this looks like:

    private void btGb_MouseDown( object sender, MouseButtonEventArgs e )
    {
    Image img = sender as Image;
    if (( img != null ) && ( img.Tag != null))
    {
    Thread.CurrentThread.CurrentUICulture = new CultureInfo( (string)img.Tag );
    Thread.CurrentThread.CurrentCulture = new CultureInfo( (string)img.Tag );
    // Reload all the merged dictionaries to reset the resources.
    List<Uri> dictionaryList = new List<Uri>();
    foreach (ResourceDictionary dictionary in 
    	Application.Current.Resources.MergedDictionaries)
    {
    dictionaryList.Add(dictionary.Source);
    }
    Application.Current.Resources.MergedDictionaries.Clear();
    foreach (Uri uri in dictionaryList)
    {
    ResourceDictionary resourceDictionary1 = new ResourceDictionary();
    resourceDictionary1.Source = uri;
    Application.Current.Resources.MergedDictionaries.Add( resourceDictionary1 );
    }
    
    IsLanguageChange = true;
    DialogResult = false;
    Close();
    }
    } 

    History

    • 1.00
      • The original
    • 1.01
      • Changed the example to include image names
      • Updated MergeLocbamlCsv and StripLocbamlCsv enhanced method which determines if a line is translatable
      • The image source is now a reason to include a line for translation (for laduran)
    • 1.02
      • Removed a typo from step 4

    License

    This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

    A list of licenses authors might use can be found here