Table of Contents
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.
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).
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.
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

German
- 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
Here is the TreeView
navigation which we want to translate:
First we create two language XML files, which contain the language resources for English and German.
English
="1.0"="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
="1.0"="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");
FileInfo[] files = di.GetFiles("*.xml");
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"));
}
}
...
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();
itemLang = new System.Windows.Controls.MenuItem[langFiles.Length];
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.
="1.0"="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()
{
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:
void SelLang_OnClick(object sender, RoutedEventArgs args)
{
for (int i = 0; i < itemLang.Length; i++)
itemLang[i].IsChecked = false;
System.Windows.Controls.MenuItem item = args.Source as
System.Windows.Controls.MenuItem;
item.IsChecked = true;
CrypTool.AppLogic.LanguageOptions.setLang(item.Header.ToString());
updateLang();
}
public static void setLang(String selectedLang)
{
selLang = selectedLang;
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"
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.
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".
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.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.