Skip to content

Internationalization

Nils Kopal edited this page Feb 15, 2022 · 2 revisions

This manual shows you how to internationalize your CrypTool 2 plugin. At the moment, we have the goal to completely support two languages: german and english.

Internationalizing your plugin consists of two steps: First externalize all your strings. After that, you can start translating. Of course, the first step is the most annoying one.

In the following steps, it is assumed, that you have ReSharper installed. ReSharper is a Visual Studio plugin which gives you a lot of cool features and makes your life much easier. It includes some very useful functions for internationalizing as you will see. Besides regular developers can get a licence through the project leaders.

Table of Contents

Externalizing strings

First you have to externalize all the strings in your project that you want to translate. This means, that you want to redirect the access of these strings to a resource file. When having multiple versions of this resource file (e.g. german and english), CrypTool will automatically choose the strings in the current language.

First we should discuss, which strings you should externalize and which not. You should externalize all strings which somehow appear on the GUI, so the end user can see them. Excluded from these are most of the GuiLogMessage strings, because they provide informations to experienced users (mostly developers) and don't aim at the end user. Also, you should never externalize strings which are for technical purposes only (e.g. file names).

When starting to externalize, you first need a resource file. If you don't already have one (which is the case for most plugin projects), you can create one by going to your project properties and choose the tab "Resources" on the left side. Then click on the appearing label to generate your resource file.

In case a resource file should be used, it needs to be announced in the plugin's meta informations. This is done via the PluginInfoAttribute through the first and optional parameter resourceFile. In case of ArrayIndexer, resourceFile is "ArrayIndexer.Properties.Resources". The full path would be "Cryptool.Plugins.ArrayIndexer.Properties.Resources". But since the code runs within the "Cryptool.Plugins.ArrayIndexer" namespace, "ArrayIndexer.Properties.Resources" resolves to the same path. To give an example on how it will look like, the following code block shows ArrayIndexer's PluginInfoAttribute.

[PluginInfo("ArrayIndexer.Properties.Resources", "PluginCaption", "PluginTooltip", "ArrayIndexer/DetailedDescription/doc.xml", "ArrayIndexer/arrayindexer.png")]

For your plugin it will probably be "PROJECTNAME.Properties.Resources", where PROJECTNAME is the name you have chosen at the creation of your project. So get your path and paste it in front of the other parameters of your plugin’s PluginInfoAttribute. If this doesn't work for you a good way to get the path is described at the beginning of the section Externalizing strings from XAML.

Now you need to externalize three things:

 * Strings from all your code files (i.e. from .cs files)
 * Strings from all your XAML files
 * MetaInformation Strings.

Lets start with the strings from your code files.

Externalizing strings from code

Go through all the code files of your project and watch out for strings you want to externalize.

For instance, I want to externalize the following string from the RSA class (I know I just said, that externalizing GuiLogMessages is not needed, but this is a very good example anyway):

First, what annoys us here, is the string concatenation. We don't want to externalize this as two separate strings, especially because it makes translating these strings harder. So we make use of the string.Format feature of .Net, by changing the code to the following:

GuiLogMessage(string.Format("Finished RSA on texts in {0} seconds!", duration.TotalSeconds), NotificationLevel.Info);

As you see, we only have one string now, which will be much easier to translate, because the context won't be lost. (By the way: ReSharper helps you forming your strings to the string.Format notation, just try it!)

After that, we can externalize the string. Move your cursor over the string. Then simply press Ctrl+Shift+R. The following should happen:

Press enter (to choose the "Move to Resource..." feature). A ReSharper Dialog will open now:

You should take special care of the proposed name and the selected Resource file. The name can't contain special characters like whitespaces. It follows the same rules as variable name in .Net. The proposed name "Finished_RSA_on_texts_inseconds_" is ok, so we don't have to change it here. The selected Resource file is also the right one. But please always take a look at this, becaue sometimes ReSharper falsely chooses the resource file from the CrypPluginBase project, which should not be used! So now press "Next" (or simply hit enter) and your string will be externalized:

GuiLogMessage(string.Format(Resources.Finished_RSA_on_texts_in__0__seconds_, duration.TotalSeconds), NotificationLevel.Info);

Externalizing strings from XAML

Externalizing strings from XAML is much harder, especially because ReSharper can't help you very much here (mostly because it seems to have a lot of bugs in its XAML externalizing functionality).

First you have to find out, in which namespace your resource class lies. Normally, the namespace of the resource file is chosen by VisualStudio as the standard namespace of the project with the subnamespace "Properties". To make sure, that you grasp the right namespace, you can open the Resources.Designer.cs class. This is the automatically generated source file for accessing the resources (i.e. do not try to alter it). It should look like this:



The red arrow indicates the location where you find the namespace you are searching for. So in the example, we know now, that our resource class (which is named "Resources") has the following path:

Cryptool.Plugins.Cryptography.Encryption.Properties.Resources
This is important now. Open the .xaml.cs file of the XAML file you want to externalize. Immediately before the class declaration, set the attribute:
[Cryptool.PluginBase.Attributes.Localization("RESOURCECLASSPATH")]
Here, you should replace RESOURCECLASSPATH by the above obtained resource class path:

Now your xaml file is ready to be externalized. Open the the .xaml file and watch out for all strings you want to externalize (Of course, you DON'T want to externalize things Attributes like "Name" or "Visiblity").

Lets assume you want to externalize the following text attribute:

Simply replace this by:

<TextBlock Width="117.812" Height="29.237" Canvas.Left="112.482" Text="{Loc Encryption}" TextWrapping="Wrap" FontSize="24"/>
Now instead of setting the string "Encryption" directly, XAML gets the string from your resource file under the key "Encryption". So you have to create a new entry in your resource file with the key "Encryption" and the value "Encryption" (of course again you must take care that your key does not contain special characters. So when externalizing the string "Hello World", the key can be named something like "Hello_World") You can do this by opening your project settings and clicking the "Resources" tab. It shows you all entries. Simply add your new one:

Externalizing metainformation strings

By metainformations, I mean those strings, that are passed to CrypWin passively by attributes (e.g. the name and description of the plugin).

First you have to change your PluginInfoAttribute so that it contains your resource class path. For instance, when using the RSA class as an example, you must change

[PluginInfo("RSA", "RSA En/Decryption", "RSA/DetailedDescription/Description.xaml", "RSA/iconrsa.png", "RSA/Images/encrypt.png", "RSA/Images/decrypt.png")]
to this:
[PluginInfo("RSA.Properties.Resources", "RSAcaption", "RSAtooltip", "RSA/DetailedDescription/Description.xaml", "RSA/iconrsa.png", "RSA/Images/encrypt.png", "RSA/Images/decrypt.png")]
What happened here? We inserted a new parameter in the begining, which describes the path to the resource class (Check out the XAML section to learn how to find this path out). Furthermore, we replaced the caption and tooltip parameter by keys in the resource file.

Consequently, you have to add two new entries into your resource file:

Now you can replace all other meta information strings with resource keys. For instance, if we want to externalize the name and description of the InputText plugin input:

Change the attribute to:

[PropertyInfo(Direction.InputData, "inputTextName", "inputTextDescription", "")]
and create two new corresponding entries in the resource file.

Translating the resource file

When you have externalized all strings, you can start translating them. Let's assume that your resources file is located in Properties -> Resources.resx:

Copy the Resources.resx file and paste it into the properties "directory" again (You have to use CTRL+C and CTRL+V for this). Now rename the copy to "Resources.de.resx" (do not use de-DE). This will be the resourcefile for the german translation:

Open this new file. But not by simply clicking on it, but by pressing F7. This opens the resource file in a code view. Scroll down and you will see all strings to translate. Those strings that aren't translated yet are shown gray by ReSharper.

Now simply translate all the strings.

Adding new strings afterwards

When you already translated your plugin project, but added new strings to it and externalize them, you will have the problem, that entries for them only exist in the Resources.resx, but not yet in the Resources.de.resx.

Of course, you can simply copy them over by hand. But ReSharper gives you a much more convenient possibility: Open Resources.resx in code mode (i.e. by pressing F7) and scroll to the new externalized string.

As you can see, ReSharper underlines all entries that are not overriden in the german resource file.

Now press ALT+Return, which will open the ReSharper menu. Choose "Override X in German"

Now you have the corresponding entry in the german resource file.