{"id":341,"date":"2011-06-14T14:38:38","date_gmt":"2011-06-14T12:38:38","guid":{"rendered":"http:\/\/hen.co.za\/blog\/?p=341"},"modified":"2011-06-14T14:38:38","modified_gmt":"2011-06-14T12:38:38","slug":"multilingual-applications","status":"publish","type":"post","link":"https:\/\/hen.co.za\/blog\/2011\/06\/multilingual-applications\/","title":{"rendered":"Multilingual applications"},"content":{"rendered":"<p>Living in the good old S of A it is natural for us to be bilingual or even multilingual. So what do we do if we have to create software that can\/must be used by people of multiple language backgrounds? Traditionally Microsoft had the concept of resource files that you can use to create multiple versions of the same application for different languages or locales. Problem with this is once the executable has been compiled it is also fixed. What happens if you want the application to be multilingual at &#8216;run-time&#8217;?<\/p>\n<p>Well, I&#8217;ve had to do something like this for myself. I first looked at resource files but it just did not solve the whole issue on its own. So I started creating something for myself that works for my application. Part of the solution still use .Net resource files for storing xml data that gets use in the language look up procedure. The idea is to load the text of controls and windows at the time the window open since control and window text get loaded at run-time anyway. The following is an explanation of the classes I created to facilitate my solution. It is a fully working solution and already in use inside a proper application. There may be other and even better or smarter solutions &#8216;out there&#8217; but this one works for me.<\/p>\n<h2>LanguageHandler<\/h2>\n<p>This is the central class that handles all the language setting\/reading etc. functionality. It encapsulate the list of &#8216;phrases&#8217; used in the application and provides methods to retrieve words or phrases depending on a look-up key. Simply translating words alone is not good enough so the focus is on whole &#8216;phrases&#8217;.<\/p>\n<p>The following is a partial view of the class without methods:<\/p>\n<blockquote><p><span style=\"color: #0000ff;\">public static class<\/span> LanguageHandler<br \/>\n{<\/p>\n<p style=\"padding-left: 30px;\"><span style=\"color: #0000ff;\">private static List<\/span>&lt;<span style=\"color: #0000ff;\">Phrase<\/span>&gt; phraseList = <span style=\"color: #0000ff;\">new List<\/span>&lt;<span style=\"color: #0000ff;\">Phrase<\/span>&gt;();<\/p>\n<p style=\"padding-left: 30px;\"><span style=\"color: #008000;\"> #region Properties<\/span><br \/>\n<span style=\"color: #0000ff;\"> private static string <\/span>languageID = &#8220;en-za&#8221;;<br \/>\n<span style=\"color: #0000ff;\"> public static Language <\/span>AppLanguage {<\/p>\n<p style=\"padding-left: 30px;\"><span style=\"color: #0000ff;\">get <\/span>{<\/p>\n<p style=\"padding-left: 60px;\"><span style=\"color: #0000ff;\">switch <\/span>(languageID) {<br \/>\n<span style=\"color: #0000ff;\">case <\/span>&#8220;af&#8221;:<br \/>\n<span style=\"color: #0000ff;\"> return Language<\/span>.Afrikaans;<br \/>\n<span style=\"color: #0000ff;\">case <\/span>&#8220;en-za&#8221;:<br \/>\n<span style=\"color: #0000ff;\"> return Language<\/span>.English;<br \/>\n<span style=\"color: #0000ff;\">default<\/span>:<br \/>\n<span style=\"color: #0000ff;\"> return Language<\/span>.Afrikaans; }<\/p>\n<p style=\"padding-left: 30px;\">}<br \/>\n<span style=\"color: #0000ff;\">set <\/span>{<\/p>\n<p style=\"padding-left: 60px;\"><span style=\"color: #0000ff;\">switch <\/span>(<span style=\"color: #0000ff;\">value<\/span>) {<br \/>\n<span style=\"color: #0000ff;\">case Language<\/span>.Afrikaans:<br \/>\nlanguageID = &#8220;af&#8221;;<br \/>\n<span style=\"color: #0000ff;\">break<\/span>;<br \/>\n<span style=\"color: #0000ff;\">case Language<\/span>.English:<br \/>\nlanguageID = &#8220;en-za&#8221;;<br \/>\n<span style=\"color: #0000ff;\">break<\/span>;<br \/>\n<span style=\"color: #0000ff;\">default<\/span>:<br \/>\nlanguageID = &#8220;en-za&#8221;;<br \/>\n<span style=\"color: #0000ff;\">break<\/span>; }<\/p>\n<p style=\"padding-left: 30px;\">}<\/p>\n<p style=\"padding-left: 30px;\"><span style=\"color: #0000ff;\">public static string <\/span>PhraseSource { set; get; }<br \/>\n<span style=\"color: #008000;\">#endregion<\/span><\/p>\n<p>}<\/p><\/blockquote>\n<h2>PhraseList.xml<\/h2>\n<p>The definition of the phrases can probably be stored in any data form but I choose a plain simple xml file to host the raw phrase data. This make it easy to maintain and port. It is also included into the application as a file resource although theoretically it could be located anywhere as long as the application can reach it.<\/p>\n<p>The structure is also very basic as it is simply a serialized version of List&lt;Phrase&gt;<\/p>\n<blockquote><p>&lt;?xml version=&#8221;1.0&#8243; encoding=&#8221;utf-16&#8243;?&gt;<br \/>\n&lt;ArrayOfPhrase xmlns:xsi=&#8221;http:\/\/www.w3.org\/2001\/XMLSchema-instance&#8221; xmlns:xsd=&#8221;http:\/\/www.w3.org\/2001\/XMLSchema&#8221;&gt;<br \/>\n&lt;phrase id=&#8221;someId&#8221; default=&#8221;Some default&#8221;&gt;<\/p>\n<p style=\"padding-left: 30px;\">&lt;phraseEntry lang=&#8221;af&#8221;&gt;Afrikaanse waarde&lt;\/phraseEntry&gt;<br \/>\n&lt;phraseEntry lang=&#8221;en-za&#8221;&gt;English value&lt;\/phraseEntry&gt;<\/p>\n<p>&lt;\/phrase&gt;<br \/>\n&lt;phrase id=&#8221;someId2&#8243; default=&#8221;Some default 2&#8243;&gt;<\/p>\n<p>&#8230;<\/p>\n<p>&lt;\/ArrayOfPhrase&gt;<\/p><\/blockquote>\n<h3>LoadPhrases method<\/h3>\n<p>Loading the data is as simple as deserializing the data. I have a helper class (not shown) to simply take the xml file and deserialize it:<\/p>\n<blockquote><p><span style=\"color: #008000;\">#region LoadPhrases<\/span><br \/>\n<span style=\"color: #0000ff;\"> public static void<\/span> LoadPhrases()<br \/>\n{<\/p>\n<p style=\"padding-left: 30px;\"><span style=\"color: #0000ff;\">if <\/span>((PhraseSource != <span style=\"color: #0000ff;\">null<\/span>) &amp;&amp; (PhraseSource.Length &gt; 0))<\/p>\n<p style=\"padding-left: 60px;\">phraseList = <span style=\"color: #0000ff;\">SerializationUtils<\/span>.DeserializeXML&lt;<span style=\"color: #0000ff;\">List<\/span>&lt;<span style=\"color: #0000ff;\">Phrase<\/span>&gt;&gt;(PhraseSource);<\/p>\n<p>}<br \/>\n<span style=\"color: #008000;\">#endregion<\/span><\/p><\/blockquote>\n<h3>GetLanguagePhrase method<\/h3>\n<p>The main (and only relevant) method for retrieving stuff from the class is GetLanguagePhrase. It simply takes one parameter &#8211; the id value of the phrase required.<\/p>\n<blockquote><p><span style=\"color: #008000;\">#region GetLanguagePhrase<\/span><br \/>\n<span style=\"color: #0000ff;\"> public static string<\/span> GetLanguagePhrase(<span style=\"color: #0000ff;\">string <\/span>languagePhraseID)<br \/>\n{<\/p>\n<p style=\"padding-left: 30px;\"><span style=\"color: #0000ff;\">if <\/span>(phraseList.Count == 0)<\/p>\n<p style=\"padding-left: 60px;\">LoadPhrases();<\/p>\n<p style=\"padding-left: 30px;\"><span style=\"color: #0000ff;\">Phrase <\/span>phrase = (<span style=\"color: #800000;\">from <\/span>p <span style=\"color: #800000;\">in <\/span>phraseList <span style=\"color: #800000;\">where <\/span>p.LanguagePhraseID == languagePhraseID <span style=\"color: #800000;\">select<\/span> p).FirstOrDefault();<br \/>\n<span style=\"color: #0000ff;\">if <\/span>(phrase != <span style=\"color: #0000ff;\">null<\/span>)<br \/>\n{<\/p>\n<p style=\"padding-left: 60px;\"><span style=\"color: #0000ff;\">PhraseEntry <\/span>phraseEntry = (<span style=\"color: #800000;\">from <\/span>pe <span style=\"color: #800000;\">in <\/span>phrase.Phrases <span style=\"color: #800000;\">where <\/span>pe.LangId == languageID <span style=\"color: #800000;\">select <\/span>pe).FirstOrDefault();<br \/>\n<span style=\"color: #0000ff;\">if<\/span> (phraseEntry != <span style=\"color: #0000ff;\">null<\/span>)<\/p>\n<p style=\"padding-left: 90px;\"><span style=\"color: #0000ff;\">return <\/span>phraseEntry.Text;<\/p>\n<p style=\"padding-left: 60px;\"><span style=\"color: #0000ff;\">else<\/span><\/p>\n<p style=\"padding-left: 90px;\"><span style=\"color: #0000ff;\">return <\/span>phrase.DefaultValue;<\/p>\n<p style=\"padding-left: 30px;\">}<br \/>\n<span style=\"color: #0000ff;\"> throw new Exception<\/span>(&#8220;Undefined language phrase ID&#8221;);<\/p>\n<p>}<br \/>\n<span style=\"color: #008000;\">#endregion<\/span><\/p><\/blockquote>\n<h3>SetControlTextAndTip methods<\/h3>\n<p>To make it a bit easier to set the text (and tooltips if available) of controls (my implementation use WinForms) I created a few helper methods to automatically set the text on the control based on a phrase id specified.<\/p>\n<blockquote><p><span style=\"color: #008000;\">#region SetControlText<\/span><br \/>\n<span style=\"color: #0000ff;\"> public static void <\/span>SetControlTextAndTip(<span style=\"color: #0000ff;\">string <\/span>languagePhraseID, <span style=\"color: #0000ff;\">params ToolStripItem<\/span>[] tsis)<br \/>\n{<\/p>\n<p style=\"padding-left: 30px;\"><span style=\"color: #0000ff;\">string <\/span>text = GetLanguagePhrase(languagePhraseID);<br \/>\n<span style=\"color: #0000ff;\">foreach <\/span>(<span style=\"color: #0000ff;\">ToolStripItem <\/span>tsi <span style=\"color: #0000ff;\">in <\/span>tsis)<br \/>\n{<\/p>\n<p style=\"padding-left: 60px;\">tsi.Text = text;<br \/>\ntsi.ToolTipText = text;<\/p>\n<p style=\"padding-left: 30px;\">}<\/p>\n<p>}<br \/>\n<span style=\"color: #0000ff;\"> public static void<\/span> SetControlTextAndTip(<span style=\"color: #0000ff;\">string <\/span>languagePhraseID, <span style=\"color: #0000ff;\">params Control<\/span>[] crtls)<br \/>\n{<\/p>\n<p style=\"padding-left: 30px;\"><span style=\"color: #0000ff;\">string <\/span>text = GetLanguagePhrase(languagePhraseID);<br \/>\n<span style=\"color: #0000ff;\">foreach <\/span>(<span style=\"color: #0000ff;\">Control <\/span>crtl <span style=\"color: #0000ff;\">in <\/span>crtls)<br \/>\n{<\/p>\n<p style=\"padding-left: 60px;\">crtl.Text = text;<\/p>\n<p style=\"padding-left: 30px;\">}<\/p>\n<p>}<br \/>\n<span style=\"color: #0000ff;\"> public static void <\/span>SetControlTextAndTip(<span style=\"color: #0000ff;\">string <\/span>languagePhraseID, <span style=\"color: #0000ff;\">params ColumnHeader<\/span>[] columnHeaders)<br \/>\n{<\/p>\n<p style=\"padding-left: 30px;\"><span style=\"color: #0000ff;\">string <\/span>text = GetLanguagePhrase(languagePhraseID);<br \/>\n<span style=\"color: #0000ff;\">foreach <\/span>(<span style=\"color: #0000ff;\">ColumnHeader <\/span>ch <span style=\"color: #0000ff;\">in <\/span>columnHeaders)<br \/>\n{<\/p>\n<p style=\"padding-left: 60px;\">ch.Text = text;<\/p>\n<p style=\"padding-left: 30px;\">}<\/p>\n<p>}<br \/>\n<span style=\"color: #008000;\"> #endregion<\/span><\/p><\/blockquote>\n<h2>The Phrase and PhraseEntry classes<\/h2>\n<p>These two classes are really simple. For the purpose of this article they could be a lot simpler but since I built a separate &#8216;editor&#8217; they have a bunch of attributes to make editing easier. This editor plus a few method inside LanguageHandler will have to wait for another article.<\/p>\n<blockquote><p>[<span style=\"color: #0000ff;\">Serializable<\/span>, <span style=\"color: #0000ff;\">XmlType<\/span>(&#8220;phrase&#8221;)]<br \/>\n<span style=\"color: #0000ff;\"> public class <\/span>Phrase : <span style=\"color: #0000ff;\">IComparable<\/span><br \/>\n{<\/p>\n<p style=\"padding-left: 30px;\">[<span style=\"color: #0000ff;\">XmlAttribute<\/span>(&#8220;id&#8221;),<br \/>\n<span style=\"color: #0000ff;\">Browsable<\/span>(<span style=\"color: #0000ff;\">true<\/span>),<br \/>\n<span style=\"color: #0000ff;\">CategoryAttribute<\/span>(&#8220;Identifier&#8221;),<br \/>\n<span style=\"color: #0000ff;\">DefaultValueAttribute<\/span>(&#8220;&#8221;),<br \/>\n<span style=\"color: #0000ff;\">DescriptionAttribute<\/span>(&#8220;Phrase ID&#8221;),<br \/>\n<span style=\"color: #0000ff;\">ReadOnly<\/span>(<span style=\"color: #0000ff;\">true<\/span>)]<br \/>\n<span style=\"color: #0000ff;\">public string<\/span> LanguagePhraseID { <span style=\"color: #0000ff;\">get<\/span>; <span style=\"color: #0000ff;\">set<\/span>; }<br \/>\n[<span style=\"color: #0000ff;\">XmlElement<\/span>(&#8220;phraseEntry&#8221;),<br \/>\n<span style=\"color: #0000ff;\">Browsable<\/span>(<span style=\"color: #0000ff;\">true<\/span>),<br \/>\n<span style=\"color: #0000ff;\">CategoryAttribute<\/span>(&#8220;Value details&#8221;),<br \/>\n<span style=\"color: #0000ff;\">DefaultValueAttribute<\/span>(&#8220;&#8221;),<br \/>\n<span style=\"color: #0000ff;\">DescriptionAttribute<\/span>(&#8220;Phrase entries&#8221;)]<br \/>\n<span style=\"color: #0000ff;\">public List<\/span>&lt;<span style=\"color: #0000ff;\">PhraseEntry<\/span>&gt; Phrases { <span style=\"color: #0000ff;\">get<\/span>; <span style=\"color: #0000ff;\">set<\/span>; }<br \/>\n[<span style=\"color: #0000ff;\">XmlAttribute<\/span>(&#8220;default&#8221;),<br \/>\n<span style=\"color: #0000ff;\">Browsable<\/span>(<span style=\"color: #0000ff;\">true<\/span>),<br \/>\n<span style=\"color: #0000ff;\">CategoryAttribute<\/span>(&#8220;Value details&#8221;),<br \/>\n<span style=\"color: #0000ff;\">DefaultValueAttribute<\/span>(&#8220;&#8221;),<br \/>\n<span style=\"color: #0000ff;\">Editor<\/span>(<span style=\"color: #0000ff;\">typeof<\/span>(MultilineStringEditor), <span style=\"color: #0000ff;\">typeof<\/span>(UITypeEditor)),<br \/>\n<span style=\"color: #0000ff;\">DescriptionAttribute<\/span>(&#8220;Default value. Use &#8216;\\\\r\\\\n&#8217; to indicate crlf.&#8221;)]<br \/>\n<span style=\"color: #0000ff;\"> public string<\/span> DefaultValue { <span style=\"color: #0000ff;\">get<\/span>; <span style=\"color: #0000ff;\">set<\/span>; }<\/p>\n<p style=\"padding-left: 30px;\"><span style=\"color: #008000;\">#region IComparable Members<\/span><br \/>\n<span style=\"color: #0000ff;\">public int<\/span> CompareTo(<span style=\"color: #0000ff;\">object <\/span>obj)<br \/>\n{<\/p>\n<p style=\"padding-left: 60px;\"><span style=\"color: #0000ff;\">Phrase <\/span>otherPhrase = (<span style=\"color: #0000ff;\">Phrase<\/span>)obj;<br \/>\n<span style=\"color: #0000ff;\">return <\/span>LanguagePhraseID.CompareTo(otherPhrase.LanguagePhraseID);<\/p>\n<p style=\"padding-left: 30px;\">}<br \/>\n<span style=\"color: #008000;\">#endregion<\/span><\/p>\n<p>}<\/p><\/blockquote>\n<blockquote><p>[<span style=\"color: #0000ff;\">Serializable<\/span>, <span style=\"color: #0000ff;\">XmlType<\/span>(&#8220;entry&#8221;), <span style=\"color: #0000ff;\">TypeConverter<\/span>(typeof(<span style=\"color: #0000ff;\">PhraseEntryConverter<\/span>))]<br \/>\n<span style=\"color: #0000ff;\"> public class<\/span> PhraseEntry<br \/>\n{<\/p>\n<p style=\"padding-left: 30px;\"><span style=\"color: #0000ff;\">public PhraseEntry<\/span>() { }<br \/>\n<span style=\"color: #0000ff;\"> public PhraseEntry<\/span>(<span style=\"color: #0000ff;\">string <\/span>phraseEntryDef)<br \/>\n{<\/p>\n<p style=\"padding-left: 60px;\"><span style=\"color: #0000ff;\">if <\/span>(phraseEntryDef.Contains(&#8220;:&#8221;))<br \/>\n{<\/p>\n<p style=\"padding-left: 90px;\">LangId = phraseEntryDef.Substring(0, phraseEntryDef.IndexOf(&#8220;:&#8221;));<br \/>\nText = phraseEntryDef.Substring(phraseEntryDef.IndexOf(&#8220;:&#8221;) + 1);<\/p>\n<p style=\"padding-left: 60px;\">}<\/p>\n<p style=\"padding-left: 30px;\">}<\/p>\n<p style=\"padding-left: 30px;\">[<span style=\"color: #0000ff;\">XmlAttribute<\/span>(&#8220;lang&#8221;),<br \/>\n<span style=\"color: #0000ff;\">Browsable<\/span>(<span style=\"color: #0000ff;\">true<\/span>),<br \/>\n<span style=\"color: #0000ff;\">CategoryAttribute<\/span>(&#8220;Phrase entry&#8221;),<br \/>\n<span style=\"color: #0000ff;\">DefaultValueAttribute<\/span>(&#8220;&#8221;),<br \/>\n<span style=\"color: #0000ff;\">DescriptionAttribute<\/span>(&#8220;Language&#8221;)]<br \/>\n<span style=\"color: #0000ff;\"> public string<\/span> LangId { <span style=\"color: #0000ff;\">get<\/span>; <span style=\"color: #0000ff;\">set<\/span>; }<br \/>\n[<span style=\"color: #0000ff;\">XmlText<\/span>(),<br \/>\n<span style=\"color: #0000ff;\">Browsable<\/span>(<span style=\"color: #0000ff;\">true<\/span>),<br \/>\n<span style=\"color: #0000ff;\">CategoryAttribute<\/span>(&#8220;Phrase entry&#8221;),<br \/>\n<span style=\"color: #0000ff;\">DefaultValueAttribute<\/span>(&#8220;&#8221;),<br \/>\n<span style=\"color: #0000ff;\">Editor<\/span>(<span style=\"color: #0000ff;\">typeof<\/span>(<span style=\"color: #0000ff;\">MultilineStringEditor<\/span>), <span style=\"color: #0000ff;\">typeof<\/span>(<span style=\"color: #0000ff;\">UITypeEditor<\/span>)),<br \/>\n<span style=\"color: #0000ff;\">DescriptionAttribute<\/span>(&#8220;Text&#8221;)]<br \/>\n<span style=\"color: #0000ff;\"> public string<\/span> Text { <span style=\"color: #0000ff;\">get<\/span>; <span style=\"color: #0000ff;\">set<\/span>; }<\/p>\n<p style=\"padding-left: 30px;\"><span style=\"color: #0000ff;\">public override string<\/span> ToString()<br \/>\n{<\/p>\n<p style=\"padding-left: 60px;\"><span style=\"color: #0000ff;\">return <\/span>LangId + &#8220;:&#8221; + Text;<\/p>\n<p style=\"padding-left: 30px;\">}<\/p>\n<p>}<\/p><\/blockquote>\n<h2>Using it all<\/h2>\n<p>Using LanguageHandler is really easy. Typically in a form&#8217;s onload event I call a private local method that sets all the texts of the controls that need to be set. There is no real performance issue to worry about since all the phrase data is stored in memory. One disadvantage is that normally the form need to be closed and reopened if the &#8216;language&#8217; has change. This is not really a problem since changing the language is something that should not happen often. Technically this private LoadLanguageResources method could be ran any number of times whenever you want. The only place where I usually call it multiple times is on the &#8216;Options&#8217; dialog where you choose the language being used.<\/p>\n<p>If you have to use the same phrase multiple times you can always store the value in a local string variable.<\/p>\n<p>The following is a short example of how to use it:<\/p>\n<blockquote><p><span style=\"color: #0000ff;\">static class<\/span> Program<br \/>\n{<\/p>\n<p>\/\/\/ &lt;summary&gt;<br \/>\n\/\/\/ The main entry point for the application.<br \/>\n\/\/\/ &lt;\/summary&gt;<br \/>\n[<span style=\"color: #0000ff;\">STAThread<\/span>]<br \/>\n<span style=\"color: #0000ff;\">static void<\/span> Main()<br \/>\n{<\/p>\n<p style=\"padding-left: 30px;\">&#8230;<br \/>\n<span style=\"color: #0000ff;\">if <\/span>(Properties.<span style=\"color: #0000ff;\">Settings<\/span>.Default.LanguageID == &#8220;English&#8221;)<\/p>\n<p style=\"padding-left: 60px;\"><span style=\"color: #0000ff;\">LanguageHandler<\/span>.AppLanguage = <span style=\"color: #0000ff;\">Language<\/span>.English;<\/p>\n<p style=\"padding-left: 30px;\"><span style=\"color: #0000ff;\">else<\/span><\/p>\n<p style=\"padding-left: 60px;\"><span style=\"color: #0000ff;\">LanguageHandler<\/span>.AppLanguage = <span style=\"color: #0000ff;\">Language<\/span>.Afrikaans;<\/p>\n<p style=\"padding-left: 30px;\"><span style=\"color: #0000ff;\">LanguageHandler<\/span>.PhraseSource = Properties.<span style=\"color: #0000ff;\">Resources<\/span>.PhraseList;<br \/>\n<span style=\"color: #0000ff;\">LanguageHandler<\/span>.LoadPhrases();<br \/>\n&#8230;<br \/>\n<span style=\"color: #0000ff;\">Form <\/span>mainForm = <span style=\"color: #0000ff;\">new MainForm<\/span>();<br \/>\n&#8230;<br \/>\n<span style=\"color: #0000ff;\">Application<\/span>.Run(mainForm);<\/p>\n<p>}<\/p>\n<p><span style=\"color: #0000ff;\">public partial class <\/span>MainForm : <span style=\"color: #0000ff;\">Form<\/span><br \/>\n{<\/p>\n<p style=\"padding-left: 30px;\">&#8230;.<\/p>\n<p style=\"padding-left: 30px;\"><span style=\"color: #0000ff;\">private void<\/span> LoadLanguageResources()<br \/>\n{<\/p>\n<p style=\"padding-left: 60px;\">Text = <span style=\"color: #0000ff;\">LanguageHandler<\/span>.GetLanguagePhrase(&#8220;AppName&#8221;);<br \/>\n<span style=\"color: #0000ff;\">LanguageHandler<\/span>.SetControlTextAndTip(&#8220;File&#8221;, fileToolStripMenuItem);<br \/>\n<span style=\"color: #0000ff;\">LanguageHandler<\/span>.SetControlTextAndTip(&#8220;NewPerson&#8221;, newPersoonToolStripMenuItem);<br \/>\n&#8230;<\/p>\n<p style=\"padding-left: 30px;\">}<\/p>\n<p style=\"padding-left: 30px;\"><span style=\"color: #0000ff;\">private void <\/span>MainForm_Load(<span style=\"color: #0000ff;\">object <\/span>sender, <span style=\"color: #0000ff;\">EventArgs <\/span>e)<br \/>\n{<\/p>\n<p style=\"padding-left: 60px;\">LoadLanguageResources();<br \/>\n&#8230;<\/p>\n<p style=\"padding-left: 30px;\">}<\/p>\n<p style=\"padding-left: 30px;\">&#8230;<\/p>\n<p>}<\/p><\/blockquote>\n<h2>Summary<\/h2>\n<p>As you can see the solution is not really complicated. I really like simple solutions that &#8216;just works&#8217;. This one has proven to be working for my needs so far. I have over 340 different phrases, some are very long themselves like message box prompts.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Living in the good old S of A it is natural for us to be bilingual or even multilingual. So what do we do if we have to create software that can\/must be used by people of multiple language backgrounds? &hellip;<\/p>\n<p class=\"read-more\"><a href=\"https:\/\/hen.co.za\/blog\/2011\/06\/multilingual-applications\/\">Read more &raquo;<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3,14],"tags":[7,8,9,35,303,10,34],"class_list":["post-341","post","type-post","status-publish","format-standard","hentry","category-development","category-technology","tag-net","tag-c","tag-code","tag-data","tag-development","tag-winforms","tag-xml"],"_links":{"self":[{"href":"https:\/\/hen.co.za\/blog\/wp-json\/wp\/v2\/posts\/341","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/hen.co.za\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/hen.co.za\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/hen.co.za\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/hen.co.za\/blog\/wp-json\/wp\/v2\/comments?post=341"}],"version-history":[{"count":16,"href":"https:\/\/hen.co.za\/blog\/wp-json\/wp\/v2\/posts\/341\/revisions"}],"predecessor-version":[{"id":357,"href":"https:\/\/hen.co.za\/blog\/wp-json\/wp\/v2\/posts\/341\/revisions\/357"}],"wp:attachment":[{"href":"https:\/\/hen.co.za\/blog\/wp-json\/wp\/v2\/media?parent=341"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/hen.co.za\/blog\/wp-json\/wp\/v2\/categories?post=341"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/hen.co.za\/blog\/wp-json\/wp\/v2\/tags?post=341"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}