Click here to Skip to main content
15,867,453 members
Articles / Programming Languages / C#

Neo Localizer

Rate me:
Please Sign up or sign in to vote.
4.40/5 (4 votes)
13 Dec 2010GPL34 min read 27.9K   395   11   12
A possible approach to achieve software localization

Introduction

The article describes a possible approach to achieve software localization. It could be useful in saving a programmer's or translator's time, although these roles are often covered by the same person. In the past, I adopted this solution on Microsoft (Desktop, Web) and PHP projects obtaining the expected results: saving time in localization implementation and giving a more comprehensible presentation of data to the translator.

This article introduces a simple way to adopt this approach, including working samples, and does not refer to a software component that is ready to install.

The source code in the attachment is an integral part of NeoProject application (http://neoproject.biz) and is licensed as described in the License section.

How This Approach Works

The brief process of software translation implementation can be summarized in the follows steps:

Translator

Localizer Editor

Dictionaries

Localizer Engine used by Application

End User

Edits each phrase in the target language

Stores all translations in a single XML file

Simple structure of XML file

NeoLocalizer.dll

<span lang="ES" style="FONT-FAMILY: 'Times New Roman','serif'; FONT-SIZE: 12pt">WindowsForm</span>

Chooses the language once per session or "on the fly"

NeoLocalizer.dll NeoLocalizer.net.js

<span lang="ES" style="FONT-FAMILY: 'Times New Roman','serif'; FONT-SIZE: 12pt">WebForms</span>

Translator uses Localizer Editor which shows each managed language (Culture) in columns and each phrase in rows. A Translator should gain advantages from working in a familiar layout. Also, people who haven't sufficient knowledge about the target application can be helped in understanding the meaning of translations by comparing them to a known language.

NeoLocalizerEditor_a.png

Dictionaries XML file contains all translations in a very simple structure. It’s composed of a Primary key (tag name <Code>) and as many fields as there are managed languages (Cultures).

XML
<?xml version="1.0" standalone="yes"?>
<DictionaryItems>
  <DictionaryItem>
    <Code>Select language</Code>
    <en-AU>Select language</en-AU>
    <it-IT>Seleziona la lingua</it-IT>
    <es-AR>Seleccione el idioma</es-AR>
  </DictionaryItem>
  <DictionaryItem>
    <Code>Employees list</Code>
    <en-AU>Employees list</en-AU>
    <it-IT>Lista Impiegati</it-IT>
    <es-AR>Listado de Empleados</es-AR>
  </DictionaryItem>

</DictionaryItems>

This format is straightforward to import from a Microsoft Access table or Microsoft Excel worksheet. In sample projects attached, the Dictionary file is located in the Bin directory of NeoLocalizer.Editor and TestWeb applications.

Localizer Engine allows a Simple translation calling the GetTranslatedText() method. This way requires a programmer to set each text Property of each control on the form, writing code like example code below:

C#
//Simple Translation of control
this.Text = _localizer.GetTranslatedText("Sample Form");

It should make sense in forms containing a small quantity of controls, or for controls “unreachable” by programmatical translation. This example tries to reach each control (and child controls) using a nested foreach and set Text property passing text of control to the GetTranslatedText() method:

C#
//Programmatically Translation of Form controls text
foreach (System.Windows.Forms.Control control in controls)
{
  //It’s doesn’t work fine in cases of translation "on the fly"
  control.Text = _localizer.GetTranslatedText(control.Text);
}

In the case of translations made “on the fly” (where original text of a control is overridden by translated text), the use of a cached list is needed; an array of the original text of controls on the form.

C#
//Programmatically Translation of Form controls text

Hashtable _cacheDefaultTexts = new Hashtable();

protected void addCacheDefaultTexts(ControlCollection controls)
{
  foreach (System.Windows.Forms.Control control in controls)
  {
    _cacheDefaultTexts.Add(control.Name, control.Text);
    addCacheDefaultTexts(control.Controls);
  }
}

When we apply more than one translation in the same session (“on the fly”), we need to pass to the GetTranslatedText() method the original text of the Control.Text property. After the first translation, we will lose this original text. For this reason, we need to store original text into a cache and then pass it to the translation method. Finally, the End User can choose to set his own language before starting a new work session or at any time during the application’s process (on the fly). It depends on the application’s design.

Points of Interest

In the sample application attached to this article, you will find ways to make translation of controls programmatically. The WindowsForm project uses based class FormLocalizable which the FormSample2 class is inherited from. This base class also contains a function to edit at run-time a text property placed on the WindowsForm.

C#
protected void setControlsEvents(System.Windows.Forms.Form form)
  {
    form.MouseDown += new System.Windows.Forms.MouseEventHandler
			(this.Form_MouseDown_ForLocalization);
    form.KeyDown += new System.Windows.Forms.KeyEventHandler
			(this.KeyDown_ForLocalization);
    setControlsEvents(form.Controls);
  }

  protected void setControlsEvents
	(System.Windows.Forms.Control.ControlCollection controls)
  {
    foreach (System.Windows.Forms.Control control in controls)
    {
      if (control is System.Windows.Forms.StatusStrip)
      {
        setControlsEvents(((System.Windows.Forms.StatusStrip)control
		as System.Windows.Forms.StatusStrip).Items);
      }
      else if (control is System.Windows.Forms.DataGridView)
      {
        setControlsEvents((System.Windows.Forms.DataGridView)control);
      }
      else
      {
         control.MouseDown += new System.Windows.Forms.MouseEventHandler
		(this.Control_MouseDown_ForLocalization);
         control.KeyDown += new System.Windows.Forms.KeyEventHandler
		(this.KeyDown_ForLocalization);
         setControlsEvents(control.Controls);
       }
    }
  }

  protected void Form_MouseDown_ForLocalization
	(System.Object sender, System.Windows.Forms.MouseEventArgs e)
  {
    if (_keyEventArgs == null) return;
    try
    {
       System.Windows.Forms.Form form = (System.Windows.Forms.Form)sender;
    if (form.Capture && e.Button ==
	System.Windows.Forms.MouseButtons.Right && _keyEventArgs.Alt)
{
  string cachedText = (_cacheDefaultTexts.ContainsKey(form.Name)) ?
	_cacheDefaultTexts[form.Name].ToString() : form.Text;
  if (String.IsNullOrEmpty(cachedText)) return;
  editDictionaryItem(cachedText);
  _keyEventArgs = null;
}
    }

     catch { }
  }

  protected void editDictionaryItem(string cachedText)
  {
    NeoLocalizer.Editor.FormDictionaryItemNew f =
	new NeoLocalizer.Editor.FormDictionaryItemNew();
    if (cachedText.EndsWith(":"))
    {
       cachedText = cachedText.Trim(':');
    }

    f.DictionaryItemCode = cachedText;
    f.ShowDialog(this);
    if (f.DialogResult == System.Windows.Forms.DialogResult.OK)
    {
       if (DictionaryItemSaved != null) DictionaryItemSaved(this, EventArgs.Empty);
    }
  }

NeoLocalizerEditor_b.png

In the WebForm project, translation is made on the client side using jQuery for two important tasks; calling a local web service, which retrieves the Dictionary array and setting each control text property using a JavaScript class and jQuery selectors.

ASP.NET
//Default.aspx

<script type="text/javascript">
    var _defaultTexts = new Array();
    var _localizer = new Localizer();

    $(document).ready(function ()
    {
       addCacheDefaultTexts();
       $('#cboLanguages').change(changeLanguage);
    });

   function addCacheDefaultTexts()
   {
     $('label[id]').each(function (i, control)
	{ _defaultTexts[control.id] = $('#' + control.id).text(); });
     $('#grdEmployees > tbody > tr > th').each(function (i)
	{ _defaultTexts['grdEmployees_col_' + i] = $(this).text(); });
   }

   function changeLanguage()
   {
     _localizer.CurrentCultureCode = $('#cboLanguages').val();
     _localizer.Refresh();
    translateControls();
   }

   function translateControls()
   {
     $('label[id]').each(function (i, control)
     {
       var cachedText = _defaultTexts[control.id];
       $('#' + control.id).text(_localizer.GetTranslatedText(cachedText));
     });

     $('#grdEmployees > tbody > tr > th').each(function (i)
     {
        var cachedText = _defaultTexts['grdEmployees_col_' + i];
        $(this).text(_localizer.GetTranslatedText(cachedText));
     });
   }

</script>

testwebnet.png

Use Sample Code

You can open the NeoLocalizer.sln file using Microsoft Visual Studio 2008 and set as Start-up project NeoLocalizer.Editor or WebSite TestWeb.

Conclusion

I realized that the advantage of using a single dictionary for the whole application is to avoid the redundancy of words (the Unique Key is the Dictionary Item) and to save time, as we don’t have to write in each form a word which already exists in another form. A classic example could be the word “Code” or “ID”, which could exist in many different places of an application.

Possible Things To Do

Editor tool should be improved adding new functionality such as: - Importing / exporting to Microsoft Excel Worksheet and/or Microsoft Access Table for interchange reasons with translators - Creation of new dictionary files collecting controls names from a Windows/Web Form. Moreover, I will publish the PHP version of this sample code as soon as possible.

History

  • 2010.12.13 – Initial posting

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)


Written By
Software Developer (Senior) BrisConnections Operations Pty Ltd
Australia Australia
Senior Software Developer on Microsoft and Unix-like environments. Senior Data Analyst and Database Developer in particular on the MSSQL and MySQL platforms

Comments and Discussions

 
QuestionExtra Localization Pin
Z@clarco22-Feb-17 0:03
Z@clarco22-Feb-17 0:03 
PraiseExactly what I was looking for Pin
dragonfly.672-Feb-17 2:18
dragonfly.672-Feb-17 2:18 
GeneralIssues with this approach Pin
Grant Frisken21-Dec-10 11:28
Grant Frisken21-Dec-10 11:28 
.NET has a very good localization mechanism built into it in the form of .resx files. You simply set the "Localizable" property for a form or control and the Visual Studio designer will automatically create the resx file and put all localizable properties in it. The standard approach allows you to localize more then just the text. For many languages it is necessary to alter the layout of forms or increase the size to allow for longer text. The problem with using only the standard .NET tools is, as you point out, that it is difficult to manage (between the developer and the translator) the multiple resources files - many of them containing the duplicate entries.

There are quite a few articles on Code Project that take a similar approach to what you have here. However you don't need to throw out all the advantages of using the standard .NET localization approach to overcome its difficulties. The solution is to instead use a localization tool that manages scans your project for localized resources and combines them into a single file that the developer can provide to the translator. Typically these tools manage the interaction between the developer and the translator and once the translator has completed the translations allow the individual localized resource files to be built from single file.

There are quite a few good tools on the market that will do this (including of course the one that my company Infralution sells - Globalizer.NET)
Infralution - we provide .NET solutions:

Globalizer.NET - .NET Localization made easy
Infralution Licensing System - simple, secure and affordable licensing
Virtual Tree - superfast, flexible, databound tree/list view

GeneralRe: Issues with this approach Pin
Martin Garcia22-Dec-10 18:34
Martin Garcia22-Dec-10 18:34 
GeneralRe: Issues with this approach Pin
Grant Frisken23-Dec-10 11:36
Grant Frisken23-Dec-10 11:36 
GeneralGoogle docs Pin
ik_never20-Dec-10 11:07
ik_never20-Dec-10 11:07 
GeneralRe: Google docs Pin
Martin Garcia22-Dec-10 18:27
Martin Garcia22-Dec-10 18:27 
GeneralMy vote of 4 Pin
marisks13-Dec-10 18:34
marisks13-Dec-10 18:34 
GeneralMy vote of 3 Pin
Toli Cuturicu13-Dec-10 14:02
Toli Cuturicu13-Dec-10 14:02 
GeneralRe: My vote of 3 Pin
soup14-Dec-10 6:07
soup14-Dec-10 6:07 
GeneralRe: My vote of 3 Pin
Martin Garcia22-Dec-10 18:24
Martin Garcia22-Dec-10 18:24 
GeneralRe: My vote of 3 Pin
Toli Cuturicu23-Dec-10 13:27
Toli Cuturicu23-Dec-10 13:27 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.