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

Alternative way to support languages under the .NET Framework

By , 16 Feb 2004
Rate this:
Please Sign up or sign in to vote.

Sample Image - Internationalization.gif


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.


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 
 for (int i=0;i < dview_Table.Table.Columns.Count;i++)
 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

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
  return ip_MainFrame00SortCol;
  progessBarMainFrame.Value = 1;
  // If new Column is selected, Sort Ascending
  if (value != ip_MainFrame00SortCol)
   ip_MainFrame00SortType = 0;
  {// Column was selected, Sort the other way around
   if (ip_MainFrame00SortType == 0)
    ip_MainFrame00SortType = 1;
    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;
   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,"",
  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;
     // Missing : Scroll to Selected Item
   OnSetTextDialog(ref drow_MainFrame00);
   OnSetEmptyDialog(ref drow_MainFrame00);
   b_OnMainFrame00Browse = true; // Deactivate the MainFrame00 Text Controls
   button0010.Visible = true; // Show
  progessBarMainFrame.Value = 10;
} // public int i_MainFrame00SortCol

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)

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
  return ip_Language;
  // 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;
   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 
   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 
   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, - 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.


None as of yet.

Alternate Link

This article and file can be read and downloaded here.


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

About the Author

Mark Johnson

Germany Germany
No Biography provided

Comments and Discussions

Generalculture-specific presentation PinmemberKeith Farmer17-Feb-04 12:21 
GeneralRe: culture-specific presentation PinmemberMark Johnson17-Feb-04 17:09 
GeneralRe: culture-specific presentation PinmemberKeith Farmer18-Feb-04 15:56 
Postal code format: In the US, it's normally a 5-digit numeric ZIP code (nnnnn), with an alternative called ZIP+4, which is nnnnn-nnnn. In other countries, you'll find alphanumeric, etc.
Vehicle license plate numbers: they'll differ from place to place (US vs UK), with different vehicle classifications getting different license formats (motorcycles vs cars), and even the same type of vehicle may have a different format simply because it's for a special purpose (civilian vs military vs consular). And then of course personalized license plates, and plates with special symbols (stars, hearts, hands) exist.
There are differences in administrative regions (states, provinces, prefectures, etc) which differ from country to country, and sometimes don't exist. In some cases, it's desireable to specify sub-regions, such as US counties, which in some states are referred to as parishes but serve the same purpose. Less frequently used are townships and the like. For purposes of mailing a letter in the US, it's usually just City, State, ZIP. If you're in the military, you have a whole other set of things to deal with. University residence addresses can be complicated as well.
Phone numbers in the US and Canada are formatted as nnn-nnn-nnnn (with variations such as (nnn) nnn-nnnn, nnn.nnn.nnnn, etc), but are significantly different in Europe, and may or may not require country and city codes depending on the purpose of the form.
The way given names are presented differs between western style and Japan, China, etc. That'd be the difference between "Mark Johnson" and "JOHNSON Mark".
In all this, differences in font will change the dialog units, so labels, etc may no longer be large enough.
Date representations are probably best left to the OS to handle.
You've started tackling a rather messy problem, you see... Smile | :)
You may try investigating XForms at the w3c site.

GeneralResX Files PineditorHeath Stewart23-Dec-03 9:49 
GeneralRe: ResX Files PinmemberMark Johnson23-Dec-03 19:21 
GeneralRe: ResX Files PineditorHeath Stewart27-Dec-03 4:08 
GeneralCulture info, not language PinmemberStephane Rodriguez.23-Dec-03 5:09 
GeneralRe: Culture info, not language PinmemberMark Johnson23-Dec-03 18:44 
GeneralRe: Culture info, not language Pinmemberandy brummer17-Feb-04 5:02 
GeneralRe: Culture info, not language PinmemberMark Johnson17-Feb-04 17:21 

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
Web01 | 2.8.140421.2 | Last Updated 17 Feb 2004
Article Copyright 2003 by Mark Johnson
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid