Click here to Skip to main content
15,860,859 members
Articles / Desktop Programming / WPF

LocBaml + MsBuild + ClickOnce Deployment

Rate me:
Please Sign up or sign in to vote.
4.64/5 (7 votes)
19 Mar 2010CPOL6 min read 72.6K   802   39   13
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:

    XML
    <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 1Build 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><br />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" ><br /><Culture>nl-NL</Culture><br /></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.

    Image 1

    Image 2

    Image 3

    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:

    C#
    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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


    Written By
    Software Developer Eclectic (www.eclectic.nl)
    Netherlands Netherlands
    I am software developer working with microsoft technologies for the last 20 years. Seen almost all versions of DOS and windows and programming in Assembly, C / C++ / C# and VB.NET. Currently focussing completely on everything .NET.

    Comments and Discussions

     
    Questiongreat works Pin
    Zhao Sting16-Oct-23 21:17
    Zhao Sting16-Oct-23 21:17 
    Generalincremental build Pin
    alexeyhindikaynen26-Nov-14 5:39
    alexeyhindikaynen26-Nov-14 5:39 
    QuestionEasy BAML Pin
    skiba_k18-Jun-11 10:45
    skiba_k18-Jun-11 10:45 
    Questionstep 4 typo?? Pin
    v1ncent18-Mar-10 11:19
    v1ncent18-Mar-10 11:19 
    AnswerRe: step 4 typo?? Pin
    André van heerwaarde19-Mar-10 0:52
    André van heerwaarde19-Mar-10 0:52 
    Questionaccented charcters corrupted by locbaml? Pin
    mcevoid2-Oct-09 2:37
    mcevoid2-Oct-09 2:37 
    GeneralProblems when adding CustomControlLibrary to the project Pin
    Ponzilla20-Sep-09 0:56
    Ponzilla20-Sep-09 0:56 
    GeneralRe: Problems when adding CustomControlLibrary to the project Pin
    noamrab12-Jan-12 21:44
    noamrab12-Jan-12 21:44 
    GeneralLoading from non-standard locations Pin
    laduran26-Feb-09 13:49
    laduran26-Feb-09 13:49 
    My team is in the middle of a minor civil war in deciding how to localize a WPF application. The choice is between putting all resources in XAML and rely on locbaml to process everything correctly. Or we would use the old fashined .RESX approach to localizing. It probably doesn't need to be an either/or decision but our localization tools don't seem to be able to cope with a project that uses both .resx resources and XAML resources. (don't ask) Anyway, I have two open questions:

    1) What if I wanted to load my resource DLLs from a different location other than default en-US or de-DE directories? Seems like it ought to be possible. A nice code snippet would help me.

    2) How do I handle localized bitmaps in this model? I know that I can include bitmaps in the language resource assembly and mark them translatable in the .csproj file. HOwever, How do I tell LocBAML to use a different bitmap for German than is used for English? Can LocBAML even handle that or is LocBAML limited to using strings?

    Anyway, the article was great and informed me well beyond I thought I would need to know.
    AnswerRe: Loading from non-standard locations Pin
    André van heerwaarde27-Feb-09 7:58
    André van heerwaarde27-Feb-09 7:58 
    QuestionNo files in the clickonce deployment Pin
    jifman4-Feb-09 13:34
    jifman4-Feb-09 13:34 
    AnswerRe: No files in the clickonce deployment Pin
    André van heerwaarde4-Feb-09 22:24
    André van heerwaarde4-Feb-09 22:24 
    GeneralThank you [modified] Pin
    Nader Al Khatib31-Jan-09 16:36
    Nader Al Khatib31-Jan-09 16:36 

    General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

    Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.