Click here to Skip to main content
Licence GPL3
First Posted 13 Dec 2010
Views 5,819
Downloads 156
Bookmarked 9 times

Neo Localizer

By | 13 Dec 2010 | Article
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

WindowsForm

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

NeoLocalizer.dll NeoLocalizer.net.js

WebForms

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 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:

//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:

//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.

//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.

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.

//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)

About the Author

Martin Garcia

Software Developer
NeoProject.biz
Australia Australia

Member

Senior Software Developer on Microsoft and Unix/like environments. After some time based in Italy (Bologna) and Argentina (Mendoza), settling in Australia (Brisbane).

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
GeneralIssues with this approach PinmemberGrant Frisken11:28 21 Dec '10  
GeneralRe: Issues with this approach PinmemberMartin Garcia18:34 22 Dec '10  
GeneralRe: Issues with this approach PinmemberGrant Frisken11:36 23 Dec '10  
GeneralGoogle docs Pinmemberik_never11:07 20 Dec '10  
GeneralRe: Google docs PinmemberMartin Garcia18:27 22 Dec '10  
GeneralMy vote of 4 Pinmembermarisks18:34 13 Dec '10  
GeneralMy vote of 3 PinmemberToli Cuturicu14:02 13 Dec '10  
GeneralRe: My vote of 3 Pinmembersoup6:07 14 Dec '10  
GeneralRe: My vote of 3 PinmemberMartin Garcia18:24 22 Dec '10  
GeneralRe: My vote of 3 PinmemberToli Cuturicu13:27 23 Dec '10  

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.

Permalink | Advertise | Privacy | Mobile
Web01 | 2.5.120517.1 | Last Updated 13 Dec 2010
Article Copyright 2010 by Martin Garcia
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid