Click here to Skip to main content
Click here to Skip to main content

WPF Multi-Lingual at Runtime

, 14 Nov 2007
Rate this:
Please Sign up or sign in to vote.
WPF Multi-Lingual at Runtime

Table of Contents

Introduction

This article is about how to create a user interface in Windows Presentation Foundation (WPF) which is multilingual and where the language can be changed at runtime.

We wrote this article because there is great demand in real applications for language support and we didn't find this topic on The Code Project yet.

Background

CrypTool is an open source project developing the world's most used free educational program for cryptology. CrypTool makes it fun and easy to learn about classic and modern cryptography and cryptanalysis. It is used at universities and schools as well as in national and international companies and agencies for educational purposes.

At the moment CrypTool is being ported from C/C++ to C#.NET, replacing the MFC-based GUI with WPF, building a new layout and enhancing the functionality (e.g. more concurrent tasks, using the new graphical possibilities).

Look of the Current CrypTool Program (C/C++, MFC-based)

Screenshot - cryptool_example_de.png

CrypTool is available in three different languages: English, German and Polish. There are many requests from other countries to provide CrypTool in more languages in the future. One goal of the port to C#.NET is to make translation as easy as possible.

We decided to provide all language specific stuff and all the resources for each language in XML files. We also decided that adding new language files or updating them should be possible without recompiling the whole project. Furthermore, it is possible to change the language at runtime.

A Note About the Demonstration Application (C#, .NET)

This demonstration application shows how these multi-language requirements of CrypTool are implemented in the new C#.NET port.

By default, the Windows Forms solve the localization issue by using the Windows Forms Designer. In the form properties, you have to change the desired language and enter the new text in the new language. When you build a project, the resource files which contain the language labels are compiled from the Visual Studio XML format (*.resx) to a binary format (*.resources), which are then embedded in assemblies. By choosing WPF, we couldn't find anything similar to the Windows Forms Designer for WPF. So we had to find our own solution. The second reason for choosing our own solution to manage the languages was that Visual Studio automatically compiles the XML language files to a binary format. This means that you have to recompile the whole project by editing one of the language files or by adding a new one. Our solution manages the language files in plain text and gives the user a simple way to extend the application with new languages.

The demonstration application here took small parts out of the complete C# code from the CrypTool subversion repository (https://file.sec-tud.de/svn/CrypTool/CrypTool2.0 with user "anonymous" and password "anonymous") to make this specific task clear. The demonstration application only consists of a main window with a navigation tree (left) and a welcome window (right). Different from the current C/C++ version of CrypTool which has an MDI interface, we decided to make it more dialog-based where the dialogs can dock each other.

English

Screenshot - CrypToolWholeEN.png

German

Screenshot - CrypToolWholeGER.png

Prerequisites for Running the Demonstration Application

  • To run the demo application on Windows XP, you will need to install the .NET Framework 3.0 which is available for download here. If you have Windows Vista, the .NET Framework 3.0 is installed by default.

Prerequisites for Compiling the Source Code of the Demonstration Application

How the Demonstration Application Works

Here is the TreeView navigation which we want to translate:

Screenshot - navien.png Screenshot - naviger.png

First we create two language XML files, which contain the language resources for English and German.

English

<?xml version="1.0" encoding="utf-8" ?>
<CrypTool>
  ...
  <MenuItemFile Header="File"/>
  ...
  <MenuItemEdit Header="Edit"/>
  <MenuItemUndo Header="Undo"/>
  <MenuItemRedo Header="Redo"/>
  <MenuItemCut Header="Cut"/>
  <MenuItemCopy Header="Copy"/>
  <MenuItemPaste Header="Paste"/>
  <MenuItemDelete Header="Delete"/>
  <MenuItemFindReplace Header="Find/Replace..."/>
  <MenuItemFindNext Header="Find Next"/>
  <MenuItemSelAll Header="SelectAll"/>
  <MenuItemShowKey Header="Show Key..."/>
  <MenuItemParentWindow Header="Parent Window"/>
  <MenuItemView Header="View"/>
  ...
  <MenuItemEncryption Header="Crypt/Decrypt"/>
  ...
  <MenuItemDigSignature_PKI Header="Digital Signature/PKI"/>
  ...
  <MenuItemIndivProcedures Header="Indiv. Procedures"/>
  ...
  <MenuItemAnalysis Header="Analysis"/>
  ...
  <MenuItemOptions Header="Options"/>
  <MenuItemPlotOptions Header="Plot Options..."/>
  <MenuItemAnalysisOptions Header="Analysis Options..."/>
  <MenuItemTextOptions Header="Text Options"/>
  <MenuItemStartingOptions Header="Starting Options..."/>
  <MenuItemFurtherOptions Header="Further Options..."/>
  <MenuItemLanguage Header="Language"/>
  <MenuItemWindow Header="Window"/>
  ...
  <MenuItemHelp Header="Help"/>
  ...
  <ButtonNew Header="Create new editor dialog"/>
  <ButtonQuit Header="QuitCrypTool"/>
</CrypTool>

German

<?xml version="1.0" encoding="utf-8" ?>
<CrypTool>
   ...
   <MenuItemFile Header="Datei"/>
   ... 
   <MenuItemEdit Header="Bearbeiten"/>
   <MenuItemUndo Header="Rückgängig"/>
   <MenuItemRedo Header="Wiederherstellen"/>
   <MenuItemCut Header="Ausschneiden"/>
   <MenuItemCopy Header="Kopieren"/>
   <MenuItemPaste Header="Einfügen"/>
   <MenuItemDelete Header="Löschen"/>
   <MenuItemFindReplace Header="Suchen/Ersetzen..."/>
   <MenuItemFindNext Header="Suche nächstes"/>
   <MenuItemSelAll Header="Alles markieren"/>
   <MenuItemShowKey Header="Schlüssel anzeigen"/>
   <MenuItemParentWindow Header="ÜbergeordnetesFenster"/>
   <MenuItemView Header="Ansicht"/>
   ... 
   <MenuItemEncryption Header="Ver-/Entschlüsseln"/>
   ... 
   <MenuItemDigSignature_PKI Header="Digitale Signaturen/PKI"/>
   ... 
   <MenuItemIndivProcedures Header="Einzelverfahren"/>
   ... 
   <MenuItemAnalysis Header="Analyse"/>
   ... 
   <MenuItemOptions Header="Optionen"/>
   <MenuItemPlotOptions Header="Grafikoptionen..."/>
   <MenuItemAnalysisOptions Header="Analyseoptionen..."/>
   <MenuItemTextOptions Header="Textoptionen"/>
   <MenuItemStartingOptions Header="Startoptionen..."/> 
   <MenuItemFurtherOptions Header="Weitere Optionen..."/>
   <MenuItemLanguage Header="Sprache"/>
   <MenuItemWindow Header="Fenster"/>
   ... 
   <MenuItemHelp Header="Hilfe"/>
   ... 
   <ButtonNew Header="Neues Editorfenster erzeugen"/>
   <ButtonQuit Header="CrypTool Beenden"/>
</CrypTool> 

To compile the following code correctly, you will need to include System.IO and System.Xml.

To fill the TreeViewItem with available language files, we have to explore the language folder where we placed all the language files.

...
private static String[]
langFiles;
...
public static void
readLangFiles()
{
   DirectoryInfo di = new DirectoryInfo("lng");
   //read files from folder "lng"
   FileInfo[] files = di.GetFiles("*.xml"); //get only xml files 
   //save all available files without suffix
   //because in tree view item we want to see only the name
   langFiles = new String[files.Length]; 
   for (int i = 0; i < files.Length; i++)
   {
      langFiles[i] = files[i].Name;
      langFiles[i] = langFiles[i].Substring(0,
      langFiles[i].IndexOf(".xml"));
   }       
} 
...
//return the available languages
public static String[] getLangFiles()
{
   return langFiles;
}

So, as seen before, CrypTool will see all new language files in the folder lng without recompiling the whole project.

Now add the available languages to the TreeViewItem:

private void getLangItems()
{
   String langName;
   String[] langFiles = CrypTool.AppLogic.LanguageOptions.getLangFiles(); 
   //get the tree view item control from XAML
   itemLang = new System.Windows.Controls.MenuItem[langFiles.Length]; 
   //add new items in tree view
   for(int i=0;i<langFiles.Length;i++)
   {
      langName = langFiles[i];
      itemLang[i] = new System.Windows.Controls.MenuItem();
      itemLang[i].Header = langName;
      itemLang[i].Click += SelLang_OnClick;
      MenuItemLanguage.Items.Add(itemLang[i]);
   }
}

To permanently save the selected language, we decided to store this information in a global XML file, called CrypTool.xml.

<?xml version="1.0" encoding="utf-8" ?>
<CrypTool> 
   <Language>german</Language>
   ...
</CrypTool>

If we start CrypTool next time, the last selected language will be loaded automatically. To load the stored selected language, we use the following code when the MainWindow is loaded:

public static void readSelLang()
{ 
   XmlDocument doc = new XmlDocument();
   doc.Load("CrypTool.xml"); 
   XmlElement root = doc.DocumentElement; 
   selLang = root.SelectSingleNode("./Language").InnerText;
}
public static String getSelLang()
{
   return selLang;
}  

Now we have to mark the selected language TreeViewItem. This function is only necessary at the first start of CrypTool because changing the language at runtime automatically does the selection by calling the SelLang_OnClick() function:

void MarkSelectedLang()
{ 
   //Only on Load
   String strLang = CrypTool.AppLogic.LanguageOptions.getSelLang();
   for (int i = 0; i < itemLang.Length; i++)
      if (itemLang[i].Header.ToString() == strLang)
         itemLang[i].IsChecked = true;
}

If we want to change the selected language by clicking one of the wanted language items, we have to mark the selected language and store the selection in the CrypTool.xml file:

// on click event
void SelLang_OnClick(object sender, RoutedEventArgs args)
{ 
   //first uncheck all MenuItems
   for (int i = 0; i < itemLang.Length; i++)
      itemLang[i].IsChecked = false; 
      //check now the selected MenuItem            
      System.Windows.Controls.MenuItem item = args.Source as
         System.Windows.Controls.MenuItem;
      item.IsChecked = true;
      CrypTool.AppLogic.LanguageOptions.setLang(item.Header.ToString());
      
      updateLang();
}  
  
//store selection in CrypTool.xml
public static void setLang(String selectedLang)
{ 
   selLang = selectedLang; 
   //save to CrypTool.xml
   XmlDocument doc = new XmlDocument();
   doc.Load("CrypTool.xml"); 
   XmlElement root = doc.DocumentElement; 
   XmlNode node = root.SelectSingleNode("./Language");                 
   node.InnerText = selLang;
  
   doc.Save("CrypTool.xml");
} 

To change the labels of the TreeView at runtime you will need to execute the following code. It is necessary to include System.Data.XmlDataProvider before.

public void updateLang()
{ 
   String selLangFullPath = 
      CrypTool.AppLogic.LanguageOptions.getSelLangFullPath();
   XmlDataProvider xmlData = (XmlDataProvider)(this.FindResource("Lang"));
     
   xmlData.Source = new Uri(selLangFullPath, UriKind.Relative);
}

Now follows the crucial step: the connection between language and GUI in the XML files on the one hand and the shown components in the program during runtime on the other hand. So the last step is to bind the XAML code with our language resource files. With data binding in XAML, you are able to load stored data from data sources like XML files directly from the XAML code without any C# code (bind the labels for the components to the content of the XML language files).

You have to define a resource in your XAML file and bind all components with the resource sources which you want to translate.

<Window x:Class="CrypTool.DlgMain"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   Title="CrypTool v.2 Alpha" Height="480" Width="240"
   WindowStyle="SingleBorderWindow" 
   WindowStartupLocation="CenterScreen" Topmost="True">
   <Window.Resources>
     <XmlDataProvider x:Key="Lang" Source="/lng/german.xml" XPath="CrypTool"/>
     </Window.Resources>
     <Window.Background>
     ... 
     <TreeView Margin="10" FontSize="12" Name="Menu" 
        VerticalAlignment="Top" Grid.Column="0" Grid.Row="0" 
        Background="{x:Null}">
     <TreeViewItem FontWeight="Bold" Name="MenuItemFile" Header="{Binding
        Source={StaticResource Lang}, XPath=MenuItemFile/@Header}">
     <TreeViewItem Name="MenuItemNew" Header="{Binding
        Source={StaticResource Lang}, XPath=MenuItemNew/@Header}"  
        Selected="MenuItemNew_OnClick"/>
     <TreeViewItem Name="MenuItemOpen" Header="{Binding Source={StaticResource
        Lang}, XPath=MenuItemOpen/@Header}" Selected="MenuItemOpen_OnClick"/>
     <TreeViewItem Name="MenuItemClose" Header="{Binding Source=
        {StaticResource Lang}, XPath=MenuItemClose/@Header}" 
        Selected="MenuItemClose_OnClick"/>
     <TreeViewItem Name="MenuItemCloseAll" Header="{Binding 
        Source={StaticResource Lang}, XPath=MenuItemCloseAll/@Header}" 
        Selected="MenuItemCloseAll_OnClick"/>
     <TreeViewItem Name="MenuItemSave" Header="{Binding Source={StaticResource
        Lang}, XPath=MenuItemSave/@Header}" Selected="MenuItemSave_OnClick"/>
     <TreeViewItem Name="MenuItemSaveAs" Header="{Binding Source=
        {StaticResource Lang}, XPath=MenuItemSaveAs/@Header}" 
        Selected="MenuItemSaveAs_OnClick"/>
     <TreeViewItem Name="MenuItemDocProperties" Header="{Binding Source=
        {StaticResource Lang}, XPath=MenuItemDocProperties/@Header}" 
        Selected="ShowDlgDocPrefs"/>
     <TreeViewItem Name="MenuItemDocSetup" Header="{Binding Source=
        {StaticResource Lang}, XPath=MenuItemDocSetup/@Header}" 
        Selected="DocSetup"/>
     <TreeViewItem Name="MenuItemPrint" Header="{Binding Source=
        {StaticResource Lang}, XPath=MenuItemPrint/@Header}" 
        Selected="PrintDialog"/>
     <TreeViewItem Name="MenuItemPrintPreview" Header="{Binding Source=
        {StaticResource Lang}, XPath=MenuItemPrintPreview/@Header}" 
        Selected="PrintDialogPreview"/>
     <TreeViewItem Name="MenuItemOpenFileHistory" Header="{Binding Source=
        {StaticResource Lang}, XPath=MenuItemOpenFileHistory/@Header}">
   <StackPanel Margin="10" Grid.Column="0" Grid.Row="1">
     <Button Margin="1,1,1,1" Background="{x:Null}" 
       Click="MenuItemNew_OnClick" Content="{Binding Source={StaticResource
       Lang}, XPath=ButtonNew/@Header}"/>
     <Button Margin="1,1,1,1" Background="{x:Null}" 
       Click="CloseDlgMain" Content="{Binding Source={StaticResource Lang},
       XPath=ButtonQuit/@Header}"/>
   </StackPanel>
 </Grid>
</Window> 

In the following part, you have to define the standard path and so the default language. We temporarily choose the English language.

<XmlDataProvider x:Key="Lang" Source="/lng/english.xml"

Summary

To have multi-language support which is both easy to enhance to new languages and also easy to handle for the developer. We first described how to manage the language files and to store the language data in the language files. Then we described how to dynamically load the available language files without knowing the content of the language folder. After that, we showed how to update the TreeViewItem with the available language files and mark the selected language preferences. Finally we described how to change a new language at runtime and store the new language preferences permanently.

Developers WANTED

The WPF version of CrypTool is still in alpha stage but we work hard to release the first beta soon. To accelerate this project we would appreciate every help. We are looking for further WPF-, .NET and cryptography developers to contribute to this open source project.

If you want to see the complete source code of the current C#.NET port (not only the code of this demonstration application), there is read-only access for everyone via HTTPS to the subversion repository of WPF CrypTool via SVN co https://file.sec-tud.de/svn/CrypTool/CrypTool2.0 with user "anonymous" and password "anonymous".

Related Articles

About Me

I'm a professional developer for C++, C# and Java and developing productive software since 10 years. Since the first beta of Windows Presentation Foundation, I am intensely developing .NET 3.0 applications to follow this technique. Three years ago, when I wrote my diploma thesis I began to work actively in the CrypTool project. Since 1.5 years, I'm the lead developer of the WPF version of CrypTool.

I would like to thank Bernhard for reviewing the draft of this article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Sebastian Przybylski
Web Developer
Germany Germany
No Biography provided

Comments and Discussions

 
GeneralUnfeasible approach PinmemberNachbarsLumpi12-Jan-10 4:35 
The more complex a project gets, the less you want to write a user interface n times (where n is the count of numbers).
 
Actually I would recommend either the locbaml approach or this one:
Real-Time Multilingual WPF Demo[^]
GeneralMy favorite approach PinmembereisernWolf7-Jul-08 19:47 
GeneralInteresting approach PinmvpJosh Smith21-Aug-07 2:41 
GeneralRe: Interesting approach PinmemberSebastian Przybylski27-Aug-07 2:50 
GeneralRe: Interesting approach PinmemberEd.Poore29-Aug-07 12:30 
GeneralRe: Interesting approach PinmemberAgaveAnejo15-Nov-07 8:54 

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

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

| Advertise | Privacy | Mobile
Web03 | 2.8.140709.1 | Last Updated 14 Nov 2007
Article Copyright 2007 by Sebastian Przybylski
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid