Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Alternative way to support languages under the .NET Framework

0.00/5 (No votes)
16 Feb 2004 1  
A suggestion to an alternative way to support languages under the .NET Framework.

Sample Image - Internationalization.gif

Introduction

As a Christmas gift, I would submit an idea that I used under C++, now with C#, to support languages without the use of external files.

The support of languages was always a problem but even worse when you have lot of dialects as you have in English, German and French (and no doubt, others). In C++, you had to rewrite the Dialogs for each language / dialect which lead you to not doing it at all, not to mention the fact that the .rc often became corrupted if you played around with them (as I always did).

With C#, this was reduced to the main problem of setting the size of the Label to fit text in. This became obvious when trying to support the local use of "State/Provence/County" (in German, it is not much shorter).

Bad enough that you had to support 2 resources for all strings in external files (also, outside the code being written). If you wanted to support the few dialect differences, you get up to 6-9 resources for all strings - most of which are the same. What a waste of time and mostly not done.

The next problem is, of course (at least under C++), you had to send an extra .exe for each language. And if not, mostly the program had to be restarted before the changes took effect.

The method offered here solves these problems, and although a bit complicated, once done, the adding of an extra language is very simple.

This article will also try to show how useful Properties can be in C#. In my mind, a very useful extension even if you could claim that the same functionality could be done in extra functions in C++.

The use of a flicker-free Panel where the standard override of OnPaintBackground does not work is also used in the demo for those interested. The use of an external class to fill the Panel, StatusBar and Menus defined in the MainForm (Form1 or whatever) is also shown.

The main reason for doing this is to support both the normal Framework (PC) and the Compact Framework, without out any changes to the code. The resulting file (here Internationalization.cs) is simply copied to the directory of the other project with no changes.

With Compact Framework projects, I use a precompile directive (called COMPACT) where PC/Compact specific code is used.

The source zip has inside it, 2 zip files, one for each platform.

Beware: I used a different directory for my projects (Microsoft.Net and Microsoft.Net.Compact) - the directory inside the zip have the same name!

The Panel designing was not a major goal for this (quick and dirty, copy and paste) project. The Compact Framework version (SP1) does not support transparent Labels. The PC version supports OnSize logic.

You may not like my style of writing/formatting, but most people don't like the style of others anyway, so that's no problem for me. I use a lot of #region - #endrgion because I like the results shown in Visual Studio when I use it.

The code is highly documented (taken from a Card playing project I am working on, which I want to sell later), and an Ndoc project file and the resulting help file is included.

This effort is a thank you to the people of Code Project, and of course, microsoft.public.dotnet.framework.compact and the other microsoft.public.dotnet.framework.* newsgroups, without which I would have been lost in the last six months - please enjoy my Christmas present.

Background

When using the standard resource system, each string/control must be filled calling the resource from the .exe. So I see no real difference to what I am doing since the creation/setting of my String[] arrays takes place when the program starts.

Just as with the resources, you are using the same Fields arrays for one language at a time. We are not creating a lot of String[]s for all the languages at once. When a language changes, the old arrays will be freed and a new set created (sa_Menu_Language = new string[]{"Language","English","German","French"};).

The fact that you can define an unknown amount of Strings in an Array (sa_Menu_Language = null;) is very useful (to say the least), and the fact that with sa_Menu_Language.Length, you know automatically how many you have set is even better.

The use of Properties as a central point where everything is done appeals to me much more that the decentralized way (I at least) did before. Since language is not often changed, I even (mis)use this for setting constant String[]s like DataTable fields and the corresponding (language specific) ListView columns/labels etc. for these fields.

As always, there is a bad side effect when using this:

public void OnBuildViewListe(ref TabPage tabPageListen, 
    ref ListView listViewListen,
    ref DataView dview_Table,  ref string[] sa_listViewCols,
    int i_Hide, int i_Cols)
{
 ....
 listViewListen = new System.Windows.Forms.ListView();
 listViewListen.ColumnClick += new 
  System.Windows.Forms.ColumnClickEventHandler(this.listView_ColumnClick);
 ....
 for (int i=0;i < dview_Table.Table.Columns.Count;i++)
  listViewListen.Columns.Add(sa_listViewCols[i],
  -2,HorizontalAlignment.Center);
 //-------------------------------------------------

 for (int i=0;i < dview_Table.Count;i++)
 {
  DataRowView dset_Row = dview_Table[i];
  ListViewItem item = null;
  for (int j=0;j < dset_Row.DataView.Table.Columns.Count;j++)
  {
   if (j == 0)
    item = new ListViewItem
     (dset_Row[dset_Row.DataView.Table.Columns[j].ColumnName].ToString());
   else
    item.SubItems.Add
     (dset_Row[dset_Row.DataView.Table.Columns[j].ColumnName].ToString());
  }
  listViewListen.Items.Add(item);
 }
}

Don't ask me what sa_listViewCols[17] is! But also, don't ask what it would look like if you had an extra String for each DataTable fieldname and ListView column.

Setting all the Labels and TextBoxes this way is nasty, but the OnBuildViewListe method above takes care of all (3) ListViews in an another program.

Here, I also use a property int for each ListView supported. It stores by which columns the ListView is sorted, and does the following:

#region ip_MainFrame00SortCol
/// <summary>

/// Which Column is to be Sorted

/// </summary>

protected int ip_MainFrame00SortCol=0;
/// <summary>

/// Which Column is Sorted

/// </summary>

public int i_MainFrame00SortCol
{
 get
 {
  return ip_MainFrame00SortCol;
 }
 set
 {
  progessBarMainFrame.Value = 1;
  // If new Column is selected, Sort Ascending

  if (value != ip_MainFrame00SortCol)
   ip_MainFrame00SortType = 0;
  else
  {// Column was selected, Sort the other way around

   if (ip_MainFrame00SortType == 0)
    ip_MainFrame00SortType = 1;
   else
    ip_MainFrame00SortType = 0;
  }
  // Save the SQL Statement to Sort (used with creation of the DataView)

  s_MainFrame00SortType = a_SortType[ip_MainFrame00SortType]; // "ASC","DESC"

  progessBarMainFrame.Value = 2;
  // Check if the chosen Colums is valid, otherwise set to first Column

  if ((value < 0) || (value > dtable_MainFrame00.Columns.Count))
   ip_MainFrame00SortCol = 0;
  else
   ip_MainFrame00SortCol = value;
  // Save the SQL Fieldname Statement to Sort (Standard one Column)

  // (used with creation of the DataView)

  s_MainFrame00SortCol = sa_MainFrame00Rows[ip_MainFrame00SortCol];
  progessBarMainFrame.Value = 3;
  // Exceptions to the Standard (Sort with two Columns)

  if (ip_MainFrame00SortCol == 1)  // Name, Firstname

   s_MainFrame00SortCol = sa_MainFrame00Rows[1]+","+sa_MainFrame00Rows[2];
  progessBarMainFrame.Value = 4;
  // Create the sorted DataView

  DataView dview_Table = new DataView(dtable_MainFrame00,"",
           s_MainFrame00SortCol+a_SortType[ip_MainFrame00SortType],
           DataViewRowState.CurrentRows);
  progessBarMainFrame.Value = 5;
  // Create the ListView Build Function

  // - on which Control is the ListView placed (positioned)

  // - which ListView is to be used

  // - which DataView is to be used

  // - which Strings are to be used for the ListView Columns

  // - Number of first Columns that should be hidden (first=0)

  OnBuildViewListe(ref tabPage000, ref listView000, 
       ref dview_Table, ref sa_MainFrame00Cols,0,3);
  // Re-Select the last selected entry (Sort)

  if (dview_Table.Count > 0)
  {
   DataRowView dset_Row = null;
   for (int i=0;i < dview_Table.Count;i++)
   {
    dset_Row = dview_Table[i];
    if (dset_Row[sa_MainFrame00Rows[0]].ToString() == s_TableMainFrame00_ID)
    {
     listView000.Items[i].Selected = true;
     listView000.Items[i].Focused  = true;
     listView000.EnsureVisible(i);
     // Missing : Scroll to Selected Item

     break;
    }
   }
   OnSetTextDialog(ref drow_MainFrame00);
  }
  else
  {
   OnSetEmptyDialog(ref drow_MainFrame00);
   b_OnMainFrame00Browse = true; // Deactivate the MainFrame00 Text Controls

   button0010.Visible = true; // Show

  }
  progessBarMainFrame.Value = 10;
 }
} // public int i_MainFrame00SortCol

#endregion

3 tables are done this way, with between 10 and 27 fields. Note: when the column "Name" is sorted, the ListView is sorted by "Name", "FirstName"!

The following method is all I do to support all three ListViews.

#region listView_ColumnClick
  /// <summary>

  /// Find out which ListView was clicked

  /// Collect the Column Number that was selected

  /// and stores it, Build ListView

  /// </summary>

  public void listView_ColumnClick(object sender, ColumnClickEventArgs e)
  {
   System.Windows.Forms.ListView listView = 
          (System.Windows.Forms.ListView) sender;
   if (listView == listView000)
    i_MainFrame00SortCol = e.Column;
   if (listView == listView010)
    i_MainFrame01SortCol = e.Column;
   if (listView == listView020)
    i_MainFrame02SortCol = e.Column;
   progessBarMainFrame.Value = 0;
  } // private void listView000_ColumnClick(object sender,ColumnClickEventArgs e)

#endregion

BTW: this runs on the Compact Framework where ListView sorting is not supported (not to mention two column sorting).

Using the code

I had not planned the above when planning this Christmas present, but now, you have my best sample of how to use properties - it is not included in the demo!

The following code is from the documentation, and the demo code is extended to support the Menu to change to the listed languages/dialects.

The Panel not only shows the Street/Stra�e/Gasse/rue that I live in, but also shows the local changes that can take effect in a simple address program.

public string[] sa_Menu_Language = null;
public string[] sa_AdressTitels  = null; 
protected int ip_Language = 1; 
public int i_Language
{
 get
 {
  return ip_Language;
 }
 set
 {
  // Check for valid Language, set Default if an unknown value has been set !

  if ((value == 1)  || (value == 1202) || 
      (value == 1613) || (value == 44)   || // English

      (value == 33) || (value == 3281) || 
      (value == 4122) || (value == 1418) || // French

      (value == 37) || (value == 3287) || 
      (value == 411)  || (value == 43)   || // German

      (value == 49))                        // German

   ip_Language = value;
  else
   ip_Language = 1;               // English when not supported

  // Set Language independent Values (like Table and Columns names)

  // Set Language dependent Values (like Buttons, Lables, Messages etc.)

  if ((ip_Language == 1) || (ip_Language == 1202) || 
    (ip_Language == 1613) || (ip_Language == 44))  // English

  {
   sa_Menu_Language = new string[]{"Language","English","German","French"};
   sa_AdressTitels  = new 
     string[]{"State","City","Street","Zip-Code"}; // US-English

   if ((ip_Language == 1613) || (ip_Language == 44))  // Canada and UK

    sa_AdressTitels[3] = "Postal-Code";
   if (ip_Language == 1613) // Canada - English

    sa_AdressTitels[0] = "Provence";
   if (ip_Language == 44)   // United Kindom

    sa_AdressTitels[0] = "County";
  }  // English

  if ((ip_Language == 33)   || 
      (ip_Language == 3281) ||  // France, Belgien,

      (ip_Language == 4122) || 
      (ip_Language == 1418))    // Switzerland, Canada - Quebec

  {
   sa_Menu_Language = new 
     string[]{"Langue","anglais","allemand","fran�aise"};
   sa_AdressTitels  = new 
     string[]{"Departement","ville","rue","code de poste"}; // French

   if (ip_Language == 4122)   // Switzerland

    sa_AdressTitels[0] = "Canton";
   if ((ip_Language == 3281) || (ip_Language == 1418)) // Belgien and Canada

    sa_AdressTitels[0] = "Province";
  }  // French

  if ((ip_Language == 37) || (ip_Language == 43) || 
    (ip_Language == 49) || // DDR - Austra - Germany

    (ip_Language == 3287) || (ip_Language == 411)) // Belgien - Switzerland

  {
   sa_Menu_Language = new 
     string[]{"Sprache","englisch","deutsch","franz�sisch"};
   sa_AdressTitels  = new 
     string[]{"Bundesland","Stadt","Stra�e","Postleitzahl"}; // German

   if (ip_Language == 37)   // DDR

    sa_AdressTitels[0] = "Bezirk";
   if (ip_Language == 3287) // Belgien

    sa_AdressTitels[0] = "Provinz";
   if (ip_Language == 411)   // Switzerland

    sa_AdressTitels[0] = "Kanton";
   if (ip_Language == 43)   // Austria

    sa_AdressTitels[2] = "Gasse";
  }  // German

  // Set the Controls with the Language dependent Values

  // (Valid Language and setting values must be done by now)

  label_State.Text = sa_AdressTitels[0];
  if (mainMenuMain != null)
  {  // Menu may not exist

   menuItemLanguage.Text      = sa_Menu_Language[0];
  }
 } // Set

}  // i_Language

For the rest, please look at the supplied code. It compiles on the normal .NET Framework and Compact Framework with no warnings at Warning set to 0. (Exception: my messages on which platform it is being compiled on and DEBUG/RELEASE information).

About me, and others I would like to thank (I know, but it is Christmas)

I have spent the last 6 months (rather long unemployment) learning C# after being encouraged by Harald B�hr, Berlin, and I'm glad I did - despite my misgiving's about this "new .NET thing".

Also, I would like to thank Stan Persky, Vancouver, Canada, for feeding me again this summer with among other things: Banana Splits.

Also, my thanks to Allen Kempe, from the Blue Grass of Kentucky, for the meals and Italian wine, and for getting me eMbedded Visual Tools to debug on my PDA.

Otherwise, if you're looking for someone in Berlin, Germany for an Internet employment, to quote Dickings: I'm Willing!

As to any French readers, it has been a very long time since I was in France and I've never wrote it very well. As to Spanish, it's even worse, so I didn't try. Italian, well I like eating your food and drinking your wine and listening to your songs, but I speak it worse than I write French.

My apologies, and Merry Christmas to All!

Mark Johnson, Berlin Germany, mj10777@mj10777.de - 23.12.2003.

Points of Interest

I would be very interested in hearing any reaction to this article. If an E-mail does not get lost in the Junk Box, I will also reply.

History

None as of yet.

Alternate Link

This article and file can be read and downloaded here.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here