Microsoft & .NET.NETBuild a Localized Form that Speaks the User's Language

Build a Localized Form that Speaks the User’s Language

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Welcome to the next installment of the .NET Nuts & Bolts column. This article covers the basics of how to build a multilingual application using Microsoft .NET. It outlines the construction and then demonstrates it with examples.

Large organizations with locations across the globe have long needed to make the same application available in multiple languages. However, this problem is not unique to large organizations. In this age of outsourcing, where operations span multiple cultures, almost any organization can face the demand for a multilingual application.

The process for enabling applications to be deployed in more than one language is often referred to as localization. It refers to the application’s ability to adapt to the user’s locale-specific settings, such as language, date, and currency styles. I struggle enough with the English language, let alone attempting to master another language. I’ve often wondered why I can pick up obscure programming languages with ease and yet struggle to learn another language, but I digress.

Over the years, a number of localization approaches have emerged. This column focuses on the Microsoft .NET Framework’s solution.

Localization by Resource File

The .NET Framework provides support for localization (a.k.a. multilingual deployments) through resource files. Microsoft designed the resource file to store locale-specific information. You create a resource file for each locale you wish to support in your application. The resource files are then compiled into satellite assemblies, which are automatically loaded based on the locale settings of the computer.

For the sake of this article, I’ll pretend that I know German better than I really do and use it as my example. For those readers who are fluent in German, please don’t be offended by my bad usage.

Form Sample Code

The following sample code contains a basic application form that you soon will learn to configure into a localized form:

using System;using System.Drawing;using System.Collections;using System.ComponentModel;using System.Windows.Forms;using System.Data;namespace CodeGuru.Globalization{   /// <summary>   /// Sample application form.   /// </summary>   public class Form1 : System.Windows.Forms.Form   {      private System.Windows.Forms.Label NameLabel;      private System.Windows.Forms.TextBox NameTextBox;      private System.Windows.Forms.Label LangQuesLabel;      private System.Windows.Forms.Button GoBtn;      private System.Windows.Forms.Button HelpBtn;      /// <summary>      /// Required designer variable.      /// </summary>      private System.ComponentModel.Container components = null;      public Form1()      {         //         // Required for Windows Form Designer support         //         InitializeComponent();      }      /// <summary>      /// Clean up any resources being used.      /// </summary>      protected override void Dispose( bool disposing )      {         if( disposing )         {            if (components != null)            {               components.Dispose();            }         }         base.Dispose( disposing );      }      #region Windows Form Designer generated code      /// <summary>      /// Required method for Designer support - do not modify      /// the contents of this method with the code editor.      /// </summary>      private void InitializeComponent()      {         this.GoBtn         = new System.Windows.Forms.Button();         thisthis.HelpBtn   = new System.Windows.Forms.Button();         this.NameLabel     = new System.Windows.Forms.Label();         this.NameTextBox   = new System.Windows.Forms.TextBox();         this.LangQuesLabel = new System.Windows.Forms.Label();         this.SuspendLayout();         //         // GoBtn         //         this.GoBtn.Location = new System.Drawing.Point(72, 112);         this.GoBtn.Name     = "GoBtn";         this.GoBtn.TabIndex = 0;         this.GoBtn.Text     = "Go";         this.GoBtn.Click   += new System.EventHandler(this.GoBtn_Click);         //         // HelpBtn         //         this.HelpBtn.Location = new System.Drawing.Point(288, 112);         this.HelpBtn.Name     = "HelpBtn";         this.HelpBtn.TabIndex = 1;         this.HelpBtn.Text     = "Help";         this.HelpBtn.Click   += new System.EventHandler(this.HelpBtn_Click);         //         // NameLabel         //         this.NameLabel.Location = new System.Drawing.Point(16, 24);         this.NameLabel.Name     = "NameLabel";-         this.NameLabel.Size     = new System.Drawing.Size(72, 23);         this.NameLabel.TabIndex = 2;         this.NameLabel.Text     = "My Name is:";         //         // NameTextBox         //         this.NameTextBox.Location = new System.Drawing.Point(96, 24);         this.NameTextBox.Name     = "NameTextBox";         this.NameTextBox.Size     = new System.Drawing.Size(256, 20);         this.NameTextBox.TabIndex = 3;         this.NameTextBox.Text     = "";         //         // LangQuesLabel         //         this.LangQuesLabel.Location = new System.Drawing.Point(16, 64);         this.LangQuesLabel.Name     = "LangQuesLabel";         this.LangQuesLabel.Size     = new System.Drawing.Size(336, 23);         this.LangQuesLabel.TabIndex = 4;         this.LangQuesLabel.Text     = "Do you speak English?";         //         // Form1         //         this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);         this.ClientSize        = new System.Drawing.Size(440, 286);         this.Controls.Add(this.LangQuesLabel);         this.Controls.Add(this.NameTextBox);         this.Controls.Add(this.NameLabel);         this.Controls.Add(this.HelpBtn);         this.Controls.Add(this.GoBtn);         this.Name = "Form1";         this.Text = "English Application";         this.ResumeLayout(false);      }      #endregion      /// <summary>      /// The main entry point for the application.      /// </summary>      [STAThread]      static void Main()      {         Application.Run(new Form1());      }      private void HelpBtn_Click(object sender, System.EventArgs e)      {         MessageBox.Show("How are you?");      }      private void GoBtn_Click(object sender, System.EventArgs e)      {         MessageBox.Show("Where do you want to go today?");      }   }}

Form Sample Output

Figure 1 shows the output of the sample form. When you press either of the buttons, it displays some simple message boxes.

Figure 1: Default Locale (English)

Editing Resource Files

Resource files are simple text-based files that you can edit with a simple editor like “Visual Notepad” (as I often affectionately refer to good old Notepad). However, Visual Studio .NET makes creating resource files through the IDE easy. It also has a utility called winres.exe that allows resources to be edited in a lightweight editor that cannot change code. Because I’m all about taking the path of least resistance, I’ll create the resource files through the Visual Studio .NET IDE. Here is the basic recipe for creating a localized form using the form from above:

  1. Set the Localizable property of the form to true using the properties window.
  2. The form has a Language property as well. To enter the settings for a specific locale, you set the Language property to the desired locale. (For this example, I set the Language property to German (Germany).) Now, Visual Studio is ready for you to enter your locale-specific settings. Go through each control and change the properties as desired. Here are the changes that I made for the German (Germany) locale:
    1. Form1.Text = Deutsche Anwendung
    2. NameLabel.Text = Ich heisse:
    3. LangQuesLabel.Text = Sprechen sie Deutsche?
    4. GoBtn.Text = Gehen
    5. HelpBtn.Text = Helfen
  3. Rinse and repeat, err, I mean repeat Step 2 for every other locale you wish to set up. As you switch between the locales you have set up, you will see the form automatically change to match your configured settings, like when I changed the locale back to English (United States).
  4. Now, it’s time to compile the form. Once you have compiled, you should see an additional set of folders underneath the binDebug directory that contain locale-specific information. The form itself also will have several additional .resx files generated for it.
  5. Now, test out your application. You could go into the Control Panel and change the locale information on your machine. If you’re anything like me, however, you may not be smart enough to get it all put back. A safer way to test is setting the CurrentUICulture property of your application’s thread to specify the desired culture at the start of the Main method. For example, I added System.Threading.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("de-DE");.

Localized Form Sample Code

The following is the resulting form once I went through the sample code from the Localization by Resource File section and made all of my localized configurations:

using System;using System.Drawing;using System.Collections;using System.ComponentModel;using System.Windows.Forms;using System.Data;namespace CodeGuru.Globalization{   /// <summary>   /// Sample application form.   /// </summary>   public class Form1 : System.Windows.Forms.Form   {      private System.Windows.Forms.Label NameLabel;      private System.Windows.Forms.TextBox NameTextBox;      private System.Windows.Forms.Label LangQuesLabel;      private System.Windows.Forms.Button GoBtn;      private System.Windows.Forms.Button HelpBtn;      /// <summary>      /// Required designer variable.      /// </summary>      private System.ComponentModel.Container components = null;      public Form1()      {         //         // Required for Windows Form Designer support         //         InitializeComponent();      }      /// <summary>      /// Clean up any resources being used.      /// </summary>      protected override void Dispose( bool disposing )      {         if( disposing )         {            if (components != null)            {               components.Dispose();            }         }         base.Dispose( disposing );      }      #region Windows Form Designer generated code      /// <summary>      /// Required method for Designer support - do not modify      /// the contents of this method with the code editor.      /// </summary>      private void InitializeComponent()      {         System.Resources.ResourceManager resources = new         System.Resources.ResourceManager(typeof(Form1));         this.GoBtn         = new System.Windows.Forms.Button();         this.HelpBtn       = new System.Windows.Forms.Button();         this.NameLabel     = new System.Windows.Forms.Label();         this.NameTextBox   = new System.Windows.Forms.TextBox();         this.LangQuesLabel = new System.Windows.Forms.Label();         this.SuspendLayout();         //         // GoBtn         //         this.GoBtn.AccessibleDescription =            resources.GetString("GoBtn.AccessibleDescription");         this.GoBtn.AccessibleName =            resources.GetString("GoBtn.AccessibleName");         this.GoBtn.Anchor =            ((System.Windows.Forms.AnchorStyles)             (resources.GetObject("GoBtn.Anchor")));         this.GoBtn.BackgroundImage =            ((System.Drawing.Image)             (resources.GetObject("GoBtn.BackgroundImage")));         this.GoBtn.Dock =            ((System.Windows.Forms.DockStyle)             (resources.GetObject("GoBtn.Dock")));         this.GoBtn.Enabled =            ((bool)(resources.GetObject("GoBtn.Enabled")));         this.GoBtn.FlatStyle =            ((System.Windows.Forms.FlatStyle)             (resources.GetObject("GoBtn.FlatStyle")));         this.GoBtn.Font =            ((System.Drawing.Font)             (resources.GetObject("GoBtn.Font")));         this.GoBtn.Image =            ((System.Drawing.Image)             (resources.GetObject("GoBtn.Image")));         this.GoBtn.ImageAlign =            ((System.Drawing.ContentAlignment)             (resources.GetObject("GoBtn.ImageAlign")));         this.GoBtn.ImageIndex =            ((int)(resources.GetObject             ("GoBtn.ImageIndex")));         this.GoBtn.ImeMode = ((System.Windows.Forms.ImeMode)                               (resources.GetObject("GoBtn.ImeMode")));         this.GoBtn.Location =            ((System.Drawing.Point)             (resources.GetObject("GoBtn.Location")));         this.GoBtn.Name = "GoBtn";         this.GoBtn.RightToLeft =            ((System.Windows.Forms.RightToLeft)             (resources.GetObject("GoBtn.RightToLeft")));         this.GoBtn.Size =            ((System.Drawing.Size)             (resources.GetObject("GoBtn.Size")));         this.GoBtn.TabIndex =            ((int)(resources.GetObject             ("GoBtn.TabIndex")));         this.GoBtn.Text = resources.GetString("GoBtn.Text");         this.GoBtn.TextAlign =            ((System.Drawing.ContentAlignment)             (resources.GetObject("GoBtn.TextAlign")));         this.GoBtn.Visible = ((bool)                               (resources.GetObject("GoBtn.Visible")));         this.GoBtn.Click += new System.EventHandler(this.GoBtn_Click);         //         // HelpBtn         //         this.HelpBtn.AccessibleDescription =            resources.GetString("HelpBtn.AccessibleDescription");         this.HelpBtn.AccessibleName =            resources.GetString("HelpBtn.AccessibleName");         this.HelpBtn.Anchor =            ((System.Windows.Forms.AnchorStyles)             (resources.GetObject("HelpBtn.Anchor")));         this.HelpBtn.BackgroundImage =            ((System.Drawing.Image)             (resources.GetObject("HelpBtn.BackgroundImage")));         this.HelpBtn.Dock =            ((System.Windows.Forms.DockStyle)             (resources.GetObject("HelpBtn.Dock")));         this.HelpBtn.Enabled =            ((bool)(resources.GetObject("HelpBtn.Enabled")));         this.HelpBtn.FlatStyle =            ((System.Windows.Forms.FlatStyle)             (resources.GetObject("HelpBtn.FlatStyle")));         this.HelpBtn.Font =            ((System.Drawing.Font)             (resources.GetObject("HelpBtn.Font")));         this.HelpBtn.Image =            ((System.Drawing.Image)             (resources.GetObject("HelpBtn.Image")));         this.HelpBtn.ImageAlign =            ((System.Drawing.ContentAlignment)             (resources.GetObject("HelpBtn.ImageAlign")));         this.HelpBtn.ImageIndex =            ((int)(resources.GetObject             ("HelpBtn.ImageIndex")));         this.HelpBtn.ImeMode =            ((System.Windows.Forms.ImeMode)             (resources.GetObject("HelpBtn.ImeMode")));         this.HelpBtn.Location =            ((System.Drawing.Point)             (resources.GetObject("HelpBtn.Location")));         this.HelpBtn.Name = "HelpBtn";         this.HelpBtn.RightToLeft =            ((System.Windows.Forms.RightToLeft)             (resources.GetObject("HelpBtn.RightToLeft")));         this.HelpBtn.Size =            ((System.Drawing.Size)             (resources.GetObject("HelpBtn.Size")));         this.HelpBtn.TabIndex =            ((int)(resources.GetObject("HelpBtn.TabIndex")));         this.HelpBtn.Text = resources.GetString("HelpBtn.Text");         this.HelpBtn.TextAlign =            ((System.Drawing.ContentAlignment)             (resources.GetObject("HelpBtn.TextAlign")));         this.HelpBtn.Visible =            ((bool)(resources.GetObject("HelpBtn.Visible")));         this.HelpBtn.Click += new System.EventHandler                               (this.HelpBtn_Click);         //         // NameLabel         //         this.NameLabel.AccessibleDescription =            resources.GetString("NameLabel.AccessibleDescription");         this.NameLabel.AccessibleName =            resources.GetString("NameLabel.AccessibleName");         this.NameLabel.Anchor = ((System.Windows.Forms.AnchorStyles)                                  (resources.GetObject("NameLabel.Anchor")));         this.NameLabel.AutoSize = ((bool)(resources.GetObject                                    ("NameLabel.AutoSize")));         this.NameLabel.Dock =            ((System.Windows.Forms.DockStyle)             (resources.GetObject("NameLabel.Dock")));         this.NameLabel.Enabled =            ((bool)(resources.GetObject("NameLabel.Enabled")));         this.NameLabel.Font =            ((System.Drawing.Font)             (resources.GetObject("NameLabel.Font")));         this.NameLabel.Image =            ((System.Drawing.Image)             (resources.GetObject("NameLabel.Image")));         this.NameLabel.ImageAlign =            ((System.Drawing.ContentAlignment)             (resources.GetObject("NameLabel.ImageAlign")));         this.NameLabel.ImageIndex =            ((int)(resources.GetObject("NameLabel.ImageIndex")));         this.NameLabel.ImeMode =            ((System.Windows.Forms.ImeMode)             (resources.GetObject("NameLabel.ImeMode")));         this.NameLabel.Location =            ((System.Drawing.Point)             (resources.GetObject("NameLabel.Location")));         this.NameLabel.Name = "NameLabel";         this.NameLabel.RightToLeft =            ((System.Windows.Forms.RightToLeft)             (resources.GetObject("NameLabel.RightToLeft")));         this.NameLabel.Size =            ((System.Drawing.Size)             (resources.GetObject("NameLabel.Size")));         this.NameLabel.TabIndex =            ((int)(resources.GetObject("NameLabel.TabIndex")));         this.NameLabel.Text = resources.GetString("NameLabel.Text");         this.NameLabel.TextAlign =            ((System.Drawing.ContentAlignment)             (resources.GetObject("NameLabel.TextAlign")));         this.NameLabel.Visible =            ((bool)(resources.GetObject("NameLabel.Visible")));         //         // NameTextBox         //         this.NameTextBox.AccessibleDescription =            resources.GetString("NameTextBox.AccessibleDescription");         this.NameTextBox.AccessibleName =            resources.GetString("NameTextBox.AccessibleName");         this.NameTextBox.Anchor =            ((System.Windows.Forms.AnchorStyles)             (resources.GetObject("NameTextBox.Anchor")));         this.NameTextBox.AutoSize =            ((bool)(resources.GetObject("NameTextBox.AutoSize")));         this.NameTextBox.BackgroundImage =            ((System.Drawing.Image)             (resources.GetObject("NameTextBox.BackgroundImage")));         this.NameTextBox.Dock =            ((System.Windows.Forms.DockStyle)             (resources.GetObject("NameTextBox.Dock")));         this.NameTextBox.Enabled =            ((bool)(resources.GetObject("NameTextBox.Enabled")));         this.NameTextBox.Font =            ((System.Drawing.Font)             (resources.GetObject("NameTextBox.Font")));         this.NameTextBox.ImeMode =            ((System.Windows.Forms.ImeMode)             (resources.GetObject("NameTextBox.ImeMode")));         this.NameTextBox.Location =            ((System.Drawing.Point)             (resources.GetObject("NameTextBox.Location")));         this.NameTextBox.MaxLength =            ((int)(resources.GetObject("NameTextBox.MaxLength")));         this.NameTextBox.Multiline =            ((bool)(resources.GetObject("NameTextBox.Multiline")));         this.NameTextBox.Name = "NameTextBox";         this.NameTextBox.PasswordChar =            ((char)(resources.GetObject("NameTextBox.PasswordChar")));         this.NameTextBox.RightToLeft =            ((System.Windows.Forms.RightToLeft)             (resources.GetObject("NameTextBox.RightToLeft")));         this.NameTextBox.ScrollBars =            ((System.Windows.Forms.ScrollBars)             (resources.GetObject("NameTextBox.ScrollBars")));         this.NameTextBox.Size =            ((System.Drawing.Size)             (resources.GetObject("NameTextBox.Size")));         this.NameTextBox.TabIndex =            ((int)(resources.GetObject("NameTextBox.TabIndex")));         this.NameTextBox.Text =            resources.GetString("NameTextBox.Text");         this.NameTextBox.TextAlign =            ((System.Windows.Forms.HorizontalAlignment)             (resources.GetObject("NameTextBox.TextAlign")));         this.NameTextBox.Visible =            ((bool)(resources.GetObject("NameTextBox.Visible")));         this.NameTextBox.WordWrap =            ((bool)(resources.GetObject("NameTextBox.WordWrap")));         //         // LangQuesLabel         //         this.LangQuesLabel.AccessibleDescription =            resources.GetString("LangQuesLabel.AccessibleDescription");         this.LangQuesLabel.AccessibleName =            resources.GetString("LangQuesLabel.AccessibleName");         this.LangQuesLabel.Anchor =            ((System.Windows.Forms.AnchorStyles)             (resources.GetObject("LangQuesLabel.Anchor")));         this.LangQuesLabel.AutoSize =            ((bool)(resources.GetObject("LangQuesLabel.AutoSize")));         this.LangQuesLabel.Dock =            ((System.Windows.Forms.DockStyle)             (resources.GetObject("LangQuesLabel.Dock")));         this.LangQuesLabel.Enabled =            ((bool)(resources.GetObject("LangQuesLabel.Enabled")));         this.LangQuesLabel.Font =            ((System.Drawing.Font)             (resources.GetObject("LangQuesLabel.Font")));         this.LangQuesLabel.Image =            ((System.Drawing.Image)             (resources.GetObject("LangQuesLabel.Image")));         this.LangQuesLabel.ImageAlign =            ((System.Drawing.ContentAlignment)             (resources.GetObject("LangQuesLabel.ImageAlign")));         this.LangQuesLabel.ImageIndex =            ((int)(resources.GetObject("LangQuesLabel.ImageIndex")));         this.LangQuesLabel.ImeMode =            ((System.Windows.Forms.ImeMode)             (resources.GetObject("LangQuesLabel.ImeMode")));         this.LangQuesLabel.Location =            ((System.Drawing.Point)             (resources.GetObject("LangQuesLabel.Location")));         this.LangQuesLabel.Name = "LangQuesLabel";         this.LangQuesLabel.RightToLeft =            ((System.Windows.Forms.RightToLeft)             (resources.GetObject("LangQuesLabel.RightToLeft")));         this.LangQuesLabel.Size =            ((System.Drawing.Size)             (resources.GetObject("LangQuesLabel.Size")));         this.LangQuesLabel.TabIndex =            ((int)(resources.GetObject("LangQuesLabel.TabIndex")));         this.LangQuesLabel.Text =            resources.GetString("LangQuesLabel.Text");         this.LangQuesLabel.TextAlign =            ((System.Drawing.ContentAlignment)             (resources.GetObject("LangQuesLabel.TextAlign")));         this.LangQuesLabel.Visible =            ((bool)(resources.GetObject("LangQuesLabel.Visible")));         //         // Form1         //         this.AccessibleDescription =            resources.GetString("$this.AccessibleDescription");         this.AccessibleName =            resources.GetString("$this.AccessibleName");         this.AutoScaleBaseSize =            ((System.Drawing.Size)             (resources.GetObject("$this.AutoScaleBaseSize")));         this.AutoScroll =            ((bool)(resources.GetObject("$this.AutoScroll")));         this.AutoScrollMargin =            ((System.Drawing.Size)             (resources.GetObject("$this.AutoScrollMargin")));         this.AutoScrollMinSize =            ((System.Drawing.Size)             (resources.GetObject("$this.AutoScrollMinSize")));         this.BackgroundImage =            ((System.Drawing.Image)             (resources.GetObject("$this.BackgroundImage")));         this.ClientSize =            ((System.Drawing.Size)             (resources.GetObject("$this.ClientSize")));         this.Controls.Add(this.LangQuesLabel);         this.Controls.Add(this.NameTextBox);         this.Controls.Add(this.NameLabel);         this.Controls.Add(this.HelpBtn);         this.Controls.Add(this.GoBtn);         this.Enabled = ((bool)(resources.GetObject("$this.Enabled")));         this.Font =            ((System.Drawing.Font)(resources.GetObject("$this.Font")));         this.Icon =            ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));         this.ImeMode =            ((System.Windows.Forms.ImeMode)             (resources.GetObject("$this.ImeMode")));         this.Location =            ((System.Drawing.Point)             (resources.GetObject("$this.Location")));         this.MaximumSize =            ((System.Drawing.Size)             (resources.GetObject("$this.MaximumSize")));         this.MinimumSize =            ((System.Drawing.Size)             (resources.GetObject("$this.MinimumSize")));         this.Name = "Form1";         this.RightToLeft =            ((System.Windows.Forms.RightToLeft)             (resources.GetObject("$this.RightToLeft")));         this.StartPosition =            ((System.Windows.Forms.FormStartPosition)             (resources.GetObject("$this.StartPosition")));         this.Text = resources.GetString("$this.Text");         this.ResumeLayout(false);      }      #endregion      /// <summary>      /// The main entry point for the application.      /// </summary>      [STAThread]      static void Main()      {         System.Threading.Thread.CurrentThread.CurrentUICulture = new         System.Globalization.CultureInfo("de-DE");         Application.Run(new Form1());      }      private void HelpBtn_Click(object sender, System.EventArgs e)      {         MessageBox.Show("How are you?");      }      private void GoBtn_Click(object sender, System.EventArgs e)      {         MessageBox.Show("Where do you want to go today?");      }   }}

Localized Form Screen Output

Figure 2 shows the application form that displays when you run the above sample with the CurrentUICulture explicitly set to de-GR. Simply commenting out the line of code that sets the CurrentUICulture will cause the application to display the English-based form again the next time it runs.

Figure 2: de-GR Locale (German)

Possible Enhancements

This column demonstrated on some of the basics of creating a multilingual, or localized, application. It only touched upon setting the display value for labels and buttons. Here are a couple of additional items to ponder:

  • You set display values for simple controls such as labels and buttons. How would you handle a case like the click events of one of the buttons where a message box is displayed? How do you make the message locale-specific as well?
  • What if you utilize a database to drive the contents of dropdown lists and similar controls? What would be the best way to allow the contents of the dropdown list to respond appropriately?

Future Columns

The topic of the next column is yet to be determined. If you have something in particular that you would like to see explained here, you can reach me at mstrawmyer@crowechizek.com.

About the Author

Mark Strawmyer, MCSD, MCSE, MCDBA is a Senior Architect of .NET applications for large and mid-size organizations. Mark is a technology leader with Crowe Chizek in Indianapolis, Indiana. He specializes in architecture, design, and development of Microsoft-based solutions. Mark was honored to be named a Microsoft MVP for application development with C#. You can reach Mark at mstrawmyer@crowechizek.com.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories