Click here to Skip to main content
15,889,527 members
Articles / Programming Languages / XML

Localization in Gtk3 C# using WPF Dynamic Resources

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
10 Feb 2021CPOL6 min read 4K   164   1  
Gtk3 Localization in C# using WPF Dynamic Resources
In this article, you will learn how to use the WPF dynamic resources from Windows to localize a Gtk3 C# application on Linux.

Image 1

Gtk3 example

Image 2

WPF example

Introduction

Most of my Windows programs have been written using the WPF / MVVM combination. When it comes to localization, I am not a big fan of satellite assemblies, so from the beginning, I chose to use dynamic resources instead. When I moved to Linux, my goal obviously was to (re)use as much of my existing code as possible. Since my Windows applications used dynamic Resources and were written to be bilingual (English and Greek), I already had a stack of Resource files translated into Greek, so I had a strong motive to make use of them. In this article, I present a way to re-use the Windows / WPF Resource files in a Gtk3 C# application.

Background

Attached to this article, I include a small MVVM Windows WPF application which shows how dynamic resources are used by WPF and how they can also be accessed by code. Briefly, it goes like this:

Resource files contain a collection of key / value pairs. There is one Resource file for each language. The convention used for naming the Resource files is "Dictionary." + Culture-specific Language code + ".xaml". Thus the default Resource file in my applications is "Dictionary.en-US.xaml" and the Greek one is named "Dictionary.el-GR.xaml".

In the XAML files, the WPF bindings work their magic and all you have to do is bind the resource key to the property of the control, e.g., instead of having a non-translatable string literal:

XML
<Label Content="Hello World" />

you declare:

XML
<Label Content="{DynamicResource HolaMundo}" />

where in your Resource file, you have:

XML
<sys:String x:Key="HolaMundo" >Hello World</sys:String>

and WPF does the substitution for you.

To enable this, in App.xml, you declare the Resource dictionary as an application Resource:

XML
<Application.Resources>
  <ResourceDictionary>
   <ResourceDictionary.MergedDictionaries>
     <ResourceDictionary Source="\Resources\Dictionary.en-US.xaml"/>
   </ResourceDictionary.MergedDictionaries>
  </ResourceDictionary>
</Application.Resources>

In App.config, you need to declare the Language that the application will be currently using. To keep things simple, we use the Language’s locale-specific code:

XML
<appSettings>
    <add key="Language"  value="en-US" />

</appSettings>

Things get more interesting in App.xml.cs, because there we give us the ability to access the dynamic resources via code as well, which is done by this function:

C#
/// <summary>
/// This loads the Dictionary into the Application's Properties
/// IMPORTANT: Call this first, before everything else.
/// </summary>
private void InitializeLocalizedResources()
{
   String szLanguage = ConfigurationManager.AppSettings [ "Language" ];

   if ( String.IsNullOrEmpty( szLanguage ) )
      szLanguage = "en_US";

   ResourceDictionary dict = new ResourceDictionary();

   dict.Source = 
      new Uri( "\\Resources\\Dictionary." + szLanguage + ".xaml", UriKind.Relative );
   this.Resources.MergedDictionaries.Add( dict );
   // allows us to access it via code
   Properties [ szLanguage ] = dict;
}

The only constraint being that on start up, you have to call this before everything else, thus:

C#
private void OnAppStartup( object sender, StartupEventArgs e )
{
   try
   {
      // Initialize these here *before* everything else!!
      InitializeLocalizedResources();
      // rest of the code ..

Finally, in the .cs code files, to retrieve a string from the resources, you need something like this:

C#
String GetStringFromDynamicResources( String szLang, String szKey )
{
   if( Application.Current == null )
      return String.Empty;

   ResourceDictionary rd = ( ResourceDictionary ) Application.Current.Properties [ szLang ];

   return ( rd != null ) ? ( rd [ szKey ] as String ) : String.Empty ;
}

and that's about it, really.

Implementation

Following the GOF maxim “program to an interface, not an implementation”, we start off with an interface which best describes what we are trying to accomplish:

C#
public interface ITranslator
{
  String GetResourceString( String szKey, String szDefault );
}

The second parameter is very useful in that if an entry is not found in the dictionary, it is returned to the caller as a default. Ironically, as it turned out, it was more convenient to make the Translator classes static, so the interface seems redundant. Nevertheless, it helps us to focus on the big picture.

Windows

On Windows, we have the WPFTranslator class, where the implementation essentially encapsulates the code we described above (see the attached sample application(s)), so I’ll skip the details, since the focus of this article is the Linux version.

Linux

The Linux version is called simply Translator because it does not have any Gtk-specific code at all. Whereas the WPFTranslator can take advantage of the Application’s properties to access the dictionary, we don’t have this luxury on Linux. The “heavy lifting” here is done by the Lexicon class, which contains a Dictionary of key / value pairs. When the class is created, it reads the Resource file from disk and loads the resources it finds there into its own in-memory dictionary.

C#
protected bool LoadDictionary()
{
    bool bRetVal = false;
    XmlDocument xDoc = new XmlDocument();

    int n = 1;
    
    try
    {
        xDoc.Load( m_szFileName );

        XmlNodeList lst = xDoc.GetElementsByTagName( "sys:String" );
        if( lst != null && lst.Count > 0 )
        {
            foreach( XmlNode node in lst )
            {
                m_lstDict.Add( node.Attributes[0].Value, node.InnerText );
                n++;
            }
        }
        bRetVal = true;
    }
    catch( Exception )
    {
        bRetVal = false ;
    }
    return bRetVal;
}

It then supplies the Translator with the required entry upon request, if it is available.

C#
public String GetResourceString( String szKey, String szDefault )
{
    if( String.IsNullOrEmpty( szKey ) )
        return szDefault;

    try
    {
        String sz = m_lstDict[ szKey ];
        return ( !String.IsNullOrEmpty( sz ) ) ? sz : szDefault;
    }
    catch
    {
        return szDefault;
    }
}

The main difference between the Windows and Linux versions is that whereas the Dictionary.zzzzz.xaml files on Windows are treated as Pages and compiled into the executable along with all the other XAML files, this does not happen on Linux. The solution here is to copy the files to the output directory, where the Lexicon class can find them. One side benefit of this is that you can make changes to the Resource file, e.g., to correct a typo, without having to recompile the executable.

So, on Linux:

  1. You define the Language in the App.config file, like you did in Windows.
  2. In your Project settings, you mark the resource files to be copied to the output directory.
  3. In your code, you call Translator.GetResourceString( “key”, “Value” ).

and there you have it.

A word of caution here: File names are case-sensitive on Linux, so any “File not found” errors may be due to that.

Extensions

The attached sample programs currently cater for English and Greek only, but are able to work with any number of other languages as well.

By changing the “Language” entry in the App.config file, your application will load the appropriate resources.

And the Thanks Go To…

First of all, to the MVVM “evangelists”, Josh Smith, Sacha Barber, Jason_Dolinger et al. who opened our eyes to the huge advantages of this pattern.

The sample programs supplied here can trace their origins to Josh Smith’s sample application, as outlined in this article. Sacha Barber’s series of articles here on CodeProject on Cinch have been invaluable. Start with this article and follow the links to the other instalments because the whole series is excellent.

In fact, in the Linux sample program, you will find that I have “borrowed” his SimpleCommand class.

Special thanks are due also to the Mono and MonoDevelop teams for making it possible to run C# programs on Linux, and to the Gtk# team for their framework.

Thanks to them, I have been able to reuse about 80% of my code. :-)

A Note about the Sample Programs

The focus of this article is the Linux program, but I have included Windows WPF programs as well, so that you can compare the different implementations. You will notice of course that the resource files are identical, which is what we wanted.

The reason for having two Windows programs is basically one function call! The Visual Studio 2008 version calls ConfigurationSettings.AppSettings whereas the 2012 version calls ConfigurationManager. While preparing this article, instead of including two identical WPF programs, I decided to modify the VS 2012 version to use the ITranslator interface, just to illustrate the differences. In the 2012 version, the WPFTranslator class implements the ITranslator interface, so it is not static. Therefore, the users of this class must instantiate it. See App.xaml.cs, MainViewModel.cs and FirstViewModel.cs of the VS 2012 project. You can see from there that the static version is much more convenient to use.

I know that Visual Studio 2008 may be 12++ years old, but not everybody has the luxury of having the latest versions, and my goal is for this code, which does not use the latest features of C# anyway, to be accessible to as many people as possible.

On Linux, the supplied sample application is a MonoDevelop Solution. It requires the GtkSharp3 packages. Open it with MonoDevelop and choose Restore Packages before compiling.

References and Attributions

A full list of the locale-specific Language codes can be found here.

As mentioned previously, the SimpleCommand class used by the Gtk3 application comes from Sacha Barber’s Cinch.

The MultiTab control used by the Gtk3 application has been adapted from ApprenticeHacker’s code found here.

The icons used by the sample applications are from the iconpack Study Folders, Designer: FixIcon (Greg) found here.

History

  • 10th February, 2021: Initial version

License

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


Written By
Retired
Greece Greece
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --