jackrabbit
Member
- Joined
- Jan 29, 2018
- Messages
- 7
- Programming Experience
- 10+
Hello,
I have been programming for a long time but I never attempted to write a multi-lingual application. I currently have a large uni-lingual application, and I need to make it multi-lingual. Of course, I want to use the easiest and safest method to handle my different languages. I googled at lot at I saw that there was many ways to go, so I wrote a small multi-lingual prototype to test everything and make sure it was robust, efficient, and easy to maintain with a large application once it will be support multiple languages. I included my fully working prototype, and I would like inputs from the experts, to validate it this is the way to go, or if I should make changes. Keep in mind that my application will be multi-lingual, but it does not need to handle regional settings, for example every values displayed will always be metric, every dates will always be displayed as YYYY-MM-DD, etc.
Here is a short description of the method I used:
- The default language is English (CultureInfo = "")
- I can either select the French language (CultureInfo = "fr-FR" or the Spanish language (CultureInfo = "es-ES")
- For global multi-lingual constants, I defined a Resource file for each language (MyRes.resx, MyRes.fr-FR.resx, MyRes.es-ES.resx)
- I will use those for global text strings used in many forms, error messages, text strings used as part of the program logic, etc
- I try to use meaningfull names for those constant, for example ErrInvalidGpsType for "This GPS type is invalid" instead of a generic constant name Err1.
- The main resource file (MyRes.resx) may also contain images which are used for buttons, etc. I tested my program and I don't need to repeat then in the French and Spanish resource file, as they are identical.
- The objects in these global resource files are accessed using the RMglobal variable, initialized at the start of the program. It does not need to be reassigned when the language is changed.
- Every time I add a new form to my project, I first draw the entire form using the default English language, then I set the form's Localizable property to True.
- I then set the Language property to French, which gives me a copy of the form where I can translate every control to french (buttons, textbox, labels, combobox, etc).
- I repeat the same operation by setting the Language property to Spanish before I translate everything.
- In my project, this creates a Form#.resx for the English form, Form#.fr.resx for the French form and Form#.es.resx for the Spanish form.
- If I later need to add a control to this form, I must always select the Default language, make changes, then go back to the French and Spanish forms to make adjustments and fix missing translations.
- When I make the language selection in Form1, it does not refresh the controls of this current form using the new language, it must be done manually with a loop thru all controls with a Text property.
- However, I do not need to do anything for the child forms that will be created later, they automatically show up with the current CultureInfo without any extra code.
- I avoid adding string constant used by the a form's logic in the form's resource files, as VisualStudio warns me that it may corrupt my project if I do so.
- So instead, I add the string constant with the form's name as a prefix directly in the global resource files, for example Form3_ErrDataInvalid if this error message is used only in Form3.
Please feel free to comment on my code, make suggestions or improvements as I want to use the best method possible to avoid making my application a nightmare to maintain once it is multi-lingual. Any input from people that produced a large multi-language application will be appreciated, so I can avoid the potential pitfalls. I want the user to manually select the language. The user may be using a English Windows, yet he may want to use the software using the French language, this is why I let the user select the language in the program's main form.
I also have a few questions:
1) Is there a way to automatically reset the form's Language property to "Default"? I find that is it really easy to accidentally add controls on a translated resource instead of always doing it on the Default resource (English) if we forget to double-check the Language property to make sure it is always set to "Default" when adding new controls?
2) Is there a way to quickly select one of the three languages from the Language ComboBox? Everytime, I need to scroll thru hundreds of languages, so that I can always select Default / French / Spanish. I find that I often accdentally select the wrong language from the list, I wish there would be a way to "delete" the unused languages from the ComboBox.
3) Is there a better, more efficient way to handle multi-lingual translations in a large application with many forms?
Thanks for all the comments, and feel free to use this template for your own multi-lingual projects.
---
Here are the main files. I also attached the complete package if someone needs to give it a try:
Form1.Designer.cs:
Form2.Designer.cs:
Resources/MyRes.es-ES.resx:
Form1.es.resx:
Form2.es.esx:
I have been programming for a long time but I never attempted to write a multi-lingual application. I currently have a large uni-lingual application, and I need to make it multi-lingual. Of course, I want to use the easiest and safest method to handle my different languages. I googled at lot at I saw that there was many ways to go, so I wrote a small multi-lingual prototype to test everything and make sure it was robust, efficient, and easy to maintain with a large application once it will be support multiple languages. I included my fully working prototype, and I would like inputs from the experts, to validate it this is the way to go, or if I should make changes. Keep in mind that my application will be multi-lingual, but it does not need to handle regional settings, for example every values displayed will always be metric, every dates will always be displayed as YYYY-MM-DD, etc.
Here is a short description of the method I used:
- The default language is English (CultureInfo = "")
- I can either select the French language (CultureInfo = "fr-FR" or the Spanish language (CultureInfo = "es-ES")
- For global multi-lingual constants, I defined a Resource file for each language (MyRes.resx, MyRes.fr-FR.resx, MyRes.es-ES.resx)
- I will use those for global text strings used in many forms, error messages, text strings used as part of the program logic, etc
- I try to use meaningfull names for those constant, for example ErrInvalidGpsType for "This GPS type is invalid" instead of a generic constant name Err1.
- The main resource file (MyRes.resx) may also contain images which are used for buttons, etc. I tested my program and I don't need to repeat then in the French and Spanish resource file, as they are identical.
- The objects in these global resource files are accessed using the RMglobal variable, initialized at the start of the program. It does not need to be reassigned when the language is changed.
- Every time I add a new form to my project, I first draw the entire form using the default English language, then I set the form's Localizable property to True.
- I then set the Language property to French, which gives me a copy of the form where I can translate every control to french (buttons, textbox, labels, combobox, etc).
- I repeat the same operation by setting the Language property to Spanish before I translate everything.
- In my project, this creates a Form#.resx for the English form, Form#.fr.resx for the French form and Form#.es.resx for the Spanish form.
- If I later need to add a control to this form, I must always select the Default language, make changes, then go back to the French and Spanish forms to make adjustments and fix missing translations.
- When I make the language selection in Form1, it does not refresh the controls of this current form using the new language, it must be done manually with a loop thru all controls with a Text property.
- However, I do not need to do anything for the child forms that will be created later, they automatically show up with the current CultureInfo without any extra code.
- I avoid adding string constant used by the a form's logic in the form's resource files, as VisualStudio warns me that it may corrupt my project if I do so.
- So instead, I add the string constant with the form's name as a prefix directly in the global resource files, for example Form3_ErrDataInvalid if this error message is used only in Form3.
Please feel free to comment on my code, make suggestions or improvements as I want to use the best method possible to avoid making my application a nightmare to maintain once it is multi-lingual. Any input from people that produced a large multi-language application will be appreciated, so I can avoid the potential pitfalls. I want the user to manually select the language. The user may be using a English Windows, yet he may want to use the software using the French language, this is why I let the user select the language in the program's main form.
I also have a few questions:
1) Is there a way to automatically reset the form's Language property to "Default"? I find that is it really easy to accidentally add controls on a translated resource instead of always doing it on the Default resource (English) if we forget to double-check the Language property to make sure it is always set to "Default" when adding new controls?
2) Is there a way to quickly select one of the three languages from the Language ComboBox? Everytime, I need to scroll thru hundreds of languages, so that I can always select Default / French / Spanish. I find that I often accdentally select the wrong language from the list, I wish there would be a way to "delete" the unused languages from the ComboBox.
3) Is there a better, more efficient way to handle multi-lingual translations in a large application with many forms?
Thanks for all the comments, and feel free to use this template for your own multi-lingual projects.
---
Here are the main files. I also attached the complete package if someone needs to give it a try:
Form1.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Globalization;
namespace MultilingualTest
{
public partial class Form1 : Form
{
public static CultureInfo EnglishCulture = new CultureInfo("");
public static CultureInfo FrenchCulture = new CultureInfo("fr-FR");
public static CultureInfo SpanishCulture = new CultureInfo("es-ES");
public static System.Resources.ResourceManager RMglobal = null;
//------------------------------------
// Initialize default language in form
//------------------------------------
public Form1()
{
System.Threading.Thread.CurrentThread.CurrentUICulture = EnglishCulture;
RMglobal = new System.Resources.ResourceManager("MultilingualTest.Resources.MyRes", typeof(Form1).Assembly);
InitializeComponent();
}
//----------------------
// Select a new language
//----------------------
private void opt_CheckedChanged(object sender, EventArgs e)
{
if (optFrench.Checked)
System.Threading.Thread.CurrentThread.CurrentUICulture = FrenchCulture;
else if (optSpanish.Checked)
System.Threading.Thread.CurrentThread.CurrentUICulture = SpanishCulture;
else
System.Threading.Thread.CurrentThread.CurrentUICulture = EnglishCulture;
System.Resources.ResourceManager RMform = new System.Resources.ResourceManager("MultilingualTest.Form1", typeof(Form1).Assembly);
// Refresh every control having the Text property with the new language: Looks like the best solution not to miss any
foreach (Control ctl in this.Controls)
{
Boolean CtlAsText = ctl.GetType().GetProperties().Any(x => x.Name == "Text");
if (CtlAsText)
ctl.Text = RMform.GetString(ctl.Name + ".Text");
}
}
//--------------------------------------------------------
// Welcome button to open a form with the current language
//--------------------------------------------------------
private void btnWelcome_Click(object sender, EventArgs e)
{
Form2 Welcome = new Form2();
Welcome.ShowDialog();
Welcome.Dispose();
}
//-------------------------------------------------------------
// Error button to display an message with the current language
//-------------------------------------------------------------
private void btnError_Click(object sender, EventArgs e)
{
txtError.Text = RMglobal.GetString("Error1");
}
}
}
Form1.Designer.cs:
Form2.Designer.cs:
Resources/MyRes.es-ES.resx:
Form1.es.resx:
Form2.es.esx: