This is an old revision of the document!


Localization Using RESX Files
WinForms Localization
  • Create WinForms Application (eg MyApp.exe).
  • Optionally, create an Resource-only Assembly (eg Acme.App.Localization.dll) where all localized resources will be placed, so that other assemblies or applications can share it.
  • Create Resource file in local application or resource-only assembly.
    • Add new Resource.resx file to the project under Properties folder.
    • Set Access Modifier to Public (top toolbar in resource document).
    • Add strings to the Resource file. Eg: rsClose = Close
  • Create other resources files for other languages. For example:
    • Resources.es.resx for Spanish (es-ES)
    • Resources.fr.resx for French Canadian (fr-CA), or potentially Resources.fr-CA.resx as well.
  • Set Culture before calling anything else in the application. Modify Program.cs to include:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Windows.Forms;
     
    namespace LocalizedAppWinForms
    {
        static class Program
        {
            public static System.Resources.ResourceManager resmgr;
     
            /// <summary>
            /// The main entry point for the application.
            /// </summary>
            [STAThread]
            static void Main()
            {
                // Sets the UI culture
                //System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("es"); // Spanish (Spain)
                //System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en"); // English (US)
                System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("fr"); // French (France)
                //System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("de"); // German (Germany)
     
                // Load localized resources
                resmgr = new System.Resources.ResourceManager(
                    "LocalizedAppWinForms.Properties.Resources",
                    System.Reflection.Assembly.GetExecutingAssembly()
                );
     
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new frmMain());
            }
        }
    }
  • Localize controls. For example, using form frmMain, modify file frmMain.cs to include:
    private void frmMain_Load(object sender, EventArgs e)
    {
        LoadResources();
    }
     
    private void LoadResources()
    {
        // Optional method to load resources local to this form
        //System.Resources.ResourceManager resmgrlocal = new System.Resources.ResourceManager(
        //    "LocalizedAppWinForms.Properties.Resources",
        //    System.Reflection.Assembly.GetExecutingAssembly()
        //);
        //lblFirstName.Text = resmgrlocal.GetString("rsFirstName");
        //lblLastName.Text  = resmgrlocal.GetString("rsLastName");
        //lblPatient.Text   = resmgrlocal.GetString("rsPatient");
        //btnClose.Text     = resmgrlocal.GetString("rsClose");
     
        // Method to load global resources (defined in Program.cs file)
        lblFirstName.Text = Program.resmgr.GetString("rsFirstName");
        lblLastName.Text  = Program.resmgr.GetString("rsLastName");
        lblPatient.Text   = Program.resmgr.GetString("rsPatient");
        btnClose.Text     = Program.resmgr.GetString("rsClose");
    }
WPF Localization
  • Create WPF Application (eg MyApp.exe).
  • Optionally, create an Resource-only Assembly (eg Acme.App.Localization.dll) where all localized resources will be placed, so that other assemblies or applications can share it.
  • Create Resource file in local application or resource-only assembly.
    • Add new Resource.resx file to the project under Properties folder.
    • Set Access Modifier to Public (top toolbar in resource document).
    • Add strings to the Resource file. Eg: rsClose = Close
  • Create other resources files for other languages. For example:
    • Resources.es.resx for Spanish (es-ES)
    • Resources.fr.resx for French Canadian (fr-CA), or potentially Resources.fr-CA.resx as well.
  • Localize controls.
    • Add reference to Window's or UserControl's namespace in XAML. For example, if using resources within the application:
      <UserControl . . .
          xmlns:properties="clr-namespace:MyApp.Properties">
        . . .
      </UserControl>  

      Or, if using a resource-only assembly instead:

      <UserControl . . .
          xmlns:localization="clr-namespace:Acme.App.Localization; assembly=Acme.App.Localization">
        . . .
      </UserControl>  
    • Add resource to a control. For example, assuming a resource string rsClose exists:
      <Button Name="btnClose" Content="{x:Static properties:Resources.rsClose}">
  • Set Culture before calling anything else in the application. Modify App.xaml.cs to include:
    public App()
    {
        MyApp.Properties.Resources.Culture = new CultureInfo("fr-CA");
    }

    However, reading the Culture setting from the Settings file is even better:

    using System;
    using System.Collections.Generic;
    using System.Configuration;
    using System.Data;
    using System.Linq;
    using System.Windows;
    using MyApp.Properties;      // for Culture
    using System.Globalization;  // for Culture
    using System.Threading;      // for Culture
    using System.Windows.Markup; // for Culture
     
    namespace MyApp
    {
        /// <summary>
        /// Interaction logic for App.xaml
        /// </summary>
        public partial class App : Application
        {
            ///----------------------------------------------------------------------------------------
            /// <summary>
            /// Constructor
            /// </summary>
            ///----------------------------------------------------------------------------------------
            public App()
            {
                InitializeCultures();
            }
     
            ///----------------------------------------------------------------------------------------
            /// <summary>
            /// Initialize cultures.
            /// </summary>
            ///----------------------------------------------------------------------------------------
            public static void InitializeCultures()
            {
                if (!string.IsNullOrEmpty(Settings.Default.Culture))
                {
                    Thread.CurrentThread.CurrentCulture = new CultureInfo(Settings.Default.Culture);
                }
                if (!string.IsNullOrEmpty(Settings.Default.UICulture))
                {
                    Thread.CurrentThread.CurrentUICulture = new CultureInfo(Settings.Default.UICulture);
                }
     
                FrameworkElement.LanguageProperty.OverrideMetadata(
                        typeof(FrameworkElement),
                        new FrameworkPropertyMetadata(XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag))
                    );
            }
        }
    }
  • Setup Settings file:
    • Create (during application installation) the default settings file (MyApp.exe.config). In the project, the file is called App.config:
      <?xml version="1.0"?>
      <configuration>
        <configSections>
          <sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
            <section name="MyApp.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false"/>
          </sectionGroup>
        </configSections>
       
        <userSettings>
          <MyApp.Properties.Settings>
            <setting name="Culture" serializeAs="String">
              <!--Pick only one value-->
              <!--<value>en-US</value>-->
              <!--<value>de-DE</value>-->
              <value/>
            </setting>
            <setting name="UICulture" serializeAs="String">
              <!--Pick only one value-->
              <!--<value>en-US</value>-->
              <!--<value>de-DE</value>-->
              <value/>
            </setting>
          </MyApp.Properties.Settings>
        </userSettings>
       
        <startup>
          <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0,Profile=Client"/>
        </startup>
      </configuration>
    • Create the user settings file. In the project, it is located in Properties/Settings.settings:
      <?xml version='1.0' encoding='utf-8'?>
      <SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="MyApp.Properties" GeneratedClassName="Settings">
        <Profiles />
        <Settings>
          <Setting Name="Culture" Type="System.String" Scope="User">
            <Value Profile="(Default)" />
          </Setting>
          <Setting Name="UICulture" Type="System.String" Scope="User">
            <Value Profile="(Default)" />
          </Setting>
        </Settings>
      </SettingsFile>
  • To change the user settings for the language, and perform this from the application itself, call a routine like this:
    ///----------------------------------------------------------------------------------------
    /// <summary>
    /// Change language user setting to the specified language code.
    /// </summary>
    /// <param name="NewLanguageCode">Language Code</param>
    ///----------------------------------------------------------------------------------------
    public void ChangeLanguageUserSetting(string NewLanguageCode = "en-US")
    {
        MyApp.Properties.Settings.Default.Culture   = NewLanguageCode;
        MyApp.Properties.Settings.Default.UICulture = NewLanguageCode;
        MyApp.Properties.Settings.Default.Save();
    }

Deployment

  • Copy application executable, and localization resource-only assembly, if using one. Eg: MyApp.exe, Acme.App.Localization.dll
  • Copy language subfolders with localized resources or resource-only assemblies. Eg:
    • ..\de\MyApp.resources.dll, or ..\de\Acme.App.Localization.resources.dll
    • ..\es\MyApp.resources.dll, or ..\es\Acme.App.Localization.resources.dll
    • ..\fr\MyApp.resources.dll, or ..\fr\Acme.App.Localization.resources.dll
    • ..\pt\MyApp.resources.dll, or ..\pt\Acme.App.Localization.resources.dll
  • Create an application settings file with default language setting. Eg: File MyApp.exe.config:
    . . .
    <userSettings>
      <MyApp.Properties.Settings>
        <setting name="Culture" serializeAs="String">
          <!--Pick only one value-->
          <value>de-DE</value>
        </setting>
        <setting name="UICulture" serializeAs="String">
          <!--Pick only one value-->
          <value>de-DE</value>
        </setting>
      </MyApp.Properties.Settings>
    </userSettings>
    . . .
Localization Sample

WPF Application Framework (WAF)

Introduction

The LocalizationSample demonstrates a way to localize WPF applications. It uses the “old” ResX files for this task because they are well supported by the .NET Framework and Visual Studio.

Remark: Microsoft encourages you to use their tool “LocBaml” for localizing WPF applications (see MSDN: How to Localize an Application). Unfortunately, this tool is not production-ready and you don’t get the same comfortable support from Visual Studio.

The LocalizationSample is part of the WPF Application Framework (WAF) download.

Run the sample

1. Open the WpfApplicationFramework solution. 2. Set the LocalizationSample project as StartUp project and start it in Debug mode. 3. The application shows a window with two text boxes and a button. The text shown in the application is in English or German depending on the Windows language settings. 4. The birthday is formatted as short date time format. This formatting is defined in the Windows Regional and Language Options. 5. Close the application. 6. Open the App.config file. In this file you can define the cultures that should be used in the application. You can use one of these settings:

  • <value/>: Use the culture defined in Windows
  • <value>en-US</value>: Define the culture English - US
  • <value>de-DE</value>: Define the culture German - Germany
  • Just uncomment the culture you like to use and comment the other two culture settings. The xml example below shows the configuration for the German - Germany culture.
<WpfLocalizationDemo.Properties.Settings>
  <setting name="Culture" serializeAs="String">
    <!--<value>en-US</value>-->
    <value>de-DE</value>
    <!--<value />-->
  </setting>
  <setting name="UICulture" serializeAs="String">
    <!--<value>en-US</value>-->
    <value>de-DE</value>
    <!--<value />-->
  </setting>
</WpfLocalizationDemo.Properties.Settings>

7. Run the application again and see how the changes affect its appearance.

CurrentCulture and CurrentUICulture

The difference between CurrentCulture and CurrentUICulture is not that obvious. These two properties represent the “Regional and Language Options” which can be found in the Windows Control Panel.

  • CurrentCulture represents the “Format” tab of the “Regional and Language Options”. This setting is responsible for formatting and parsing of values.
  • CurrentUICulture represents the second part of the “Keyboards and Languages” tab, the “Display Language”. This property controls which language of the resources is loaded and shown to the user. Remark: Not every version of Microsoft Windows supports the “Display Language” option. When you don’t see this field then your Windows doesn’t support this option.

Code Walkthrough

  1. Open the WpfApplicationFramework solution.
  2. Expand the LocalizationSample project.
  3. Open the App.xaml.cs file.
    • In the constructor we call the InitializeCulture method.
    • In the InitializeCulture method we try to retrieve the Culture and UICulture property from the application settings file. When they exist then we set the Culture on the CurrentThread.
    • In the last line of the InitializeCulture method we set the WPF XmlLanguage with the CurrentCulture. This line is necessary that WPF uses the CultureInfo.CurrentCulture settings.
      FrameworkElement.LanguageProperty.OverrideMetadata( 
        typeof(FrameworkElement), 
        new FrameworkPropertyMetadata( 
               XmlLanguage.GetLanguage( CultureInfo.CurrentCulture.IetfLanguageTag )
            )
        );

      Remark: Windows Forms and Console applications use the CultureInfo.CurrentCulture setting by default. In my opinion, WPF should do the same so that we don’t have to write the previous line in our applications.

  4. Open Properties/Settings.settings file
    • Important: Change the “Access Modifier” from internal to public (see top bar in document within VS2010 IDE).
  5. Open the Properties/Resources.resx file
    • This file contains the string resources used in the user interface.
    • Important: Note that the “Access Modifier” was changed from internal to public. You find this setting in the Visual Studio Resources Designer toolbar. This is necessary so that you are able to access the resources within XAML.
    • Besides the Resources.resx file you find the Resources.de.resx file in the same folder. The second resource file contains the resources for the German language.
  6. Open the Presentation/ShellWindow.xaml file
    • The Title property of the Window uses the x:Static markup extension to access one of the Resources properties which are static.
      Title="{x:Static p:Resources.WpfLocalizationDemo}"
    • The Window defines the SizeToContent property with the “WidthAndHeight” value. This allows the Window to automatically define its Width and Height in respect to the content. By example the words in the German language are longer than in English. With the SizeToContent setting you allow WPF to create a larger Window when it has to show its resources in the German language so that none of the text gets cut.
    • The Grid in the MainWindow defines its ColumnDefinition.Width and RowDefinition.Height properties with the “Auto” value. This is done because of the same reason as the previous point defines the SizeToContent property. Remark: Try to avoid defining the Width and Height properties with concrete values. Let WPF calculate those values itself.

Code

App.config:

<?xml version="1.0"?>
<configuration>
  <configSections>
    <sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <section name="LocalizationSample.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false"/>
    </sectionGroup>
  </configSections>
 
  <userSettings>
    <LocalizationSample.Properties.Settings>
      <setting name="Culture" serializeAs="String">
        <!--Pick only one value-->
        <!--<value>en-US</value>-->
        <!--<value>de-DE</value>-->
        <value/>
      </setting>
      <setting name="UICulture" serializeAs="String">
        <!--Pick only one value-->
        <!--<value>en-US</value>-->
        <!--<value>de-DE</value>-->
        <value/>
      </setting>
    </LocalizationSample.Properties.Settings>
  </userSettings>
 
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0,Profile=Client"/>
  </startup>
</configuration>

App.xaml.cs:

using System;
using System.Globalization;
using System.Threading;
using System.Windows;
using System.Windows.Markup;
using LocalizationSample.Domain;
using LocalizationSample.Presentation;
using LocalizationSample.Properties;
 
namespace LocalizationSample
{
    public partial class App : Application
    {
        public App()
        {
            InitializeCultures();  // call here or before InitializeComponent()
        }
 
 
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
 
            Person person = new Person() 
            { 
                Name = "Luke", 
                Birthday = new DateTime(2080, 2, 6) }
            ;
 
            ShellWindow mainWindow = new ShellWindow();
            mainWindow.DataContext = person;
            mainWindow.Show();
        }
 
        private static void InitializeCultures()
        {
            if (!string.IsNullOrEmpty(Settings.Default.Culture))
            {
                Thread.CurrentThread.CurrentCulture = new CultureInfo(Settings.Default.Culture);
            }
            if (!string.IsNullOrEmpty(Settings.Default.UICulture))
            {
                Thread.CurrentThread.CurrentUICulture = new CultureInfo(Settings.Default.UICulture);
            }
 
            FrameworkElement.LanguageProperty.OverrideMetadata(typeof(FrameworkElement), new FrameworkPropertyMetadata(
                XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag)));
        }
    }
}

English (en/default) resources. File Properties/Resources.resx:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <!-- 
    Microsoft ResX Schema 
 
    Version 2.0
 
    The primary goals of this format is to allow a simple XML format 
    that is mostly human readable. The generation and parsing of the 
    various data types are done through the TypeConverter classes 
    associated with the data types.
 
    Example:
 
    ... ado.net/XML headers & schema ...
    <resheader name="resmimetype">text/microsoft-resx</resheader>
    <resheader name="version">2.0</resheader>
    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
        <value>[base64 mime encoded serialized .NET Framework object]</value>
    </data>
    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
        <comment>This is a comment</comment>
    </data>
 
    There are any number of "resheader" rows that contain simple 
    name/value pairs.
 
    Each data row contains a name, and value. The row also contains a 
    type or mimetype. Type corresponds to a .NET class that support 
    text/value conversion through the TypeConverter architecture. 
    Classes that don't support this are serialized and stored with the 
    mimetype set.
 
    The mimetype is used for serialized objects, and tells the 
    ResXResourceReader how to depersist the object. This is currently not 
    extensible. For a given mimetype the value must be set accordingly:
 
    Note - application/x-microsoft.net.object.binary.base64 is the format 
    that the ResXResourceWriter will generate, however the reader can 
    read any of the formats listed below.
 
    mimetype: application/x-microsoft.net.object.binary.base64
    value   : The object must be serialized with 
            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
            : and then encoded with base64 encoding.
 
    mimetype: application/x-microsoft.net.object.soap.base64
    value   : The object must be serialized with 
            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
            : and then encoded with base64 encoding.
 
    mimetype: application/x-microsoft.net.object.bytearray.base64
    value   : The object must be serialized into a byte array 
            : using a System.ComponentModel.TypeConverter
            : and then encoded with base64 encoding.
    -->
  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
    <xsd:element name="root" msdata:IsDataSet="true">
      <xsd:complexType>
        <xsd:choice maxOccurs="unbounded">
          <xsd:element name="metadata">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" />
              </xsd:sequence>
              <xsd:attribute name="name" use="required" type="xsd:string" />
              <xsd:attribute name="type" type="xsd:string" />
              <xsd:attribute name="mimetype" type="xsd:string" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="assembly">
            <xsd:complexType>
              <xsd:attribute name="alias" type="xsd:string" />
              <xsd:attribute name="name" type="xsd:string" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="data">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="resheader">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" />
            </xsd:complexType>
          </xsd:element>
        </xsd:choice>
      </xsd:complexType>
    </xsd:element>
  </xsd:schema>
  <resheader name="resmimetype">
    <value>text/microsoft-resx</value>
  </resheader>
  <resheader name="version">
    <value>2.0</value>
  </resheader>
  <resheader name="reader">
    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <resheader name="writer">
    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <data name="Birthday" xml:space="preserve">
    <value>Birthday</value>
  </data>
  <data name="Close" xml:space="preserve">
    <value>Close</value>
  </data>
  <data name="Name" xml:space="preserve">
    <value>Name</value>
  </data>
  <data name="WpfLocalizationDemo" xml:space="preserve">
    <value>WPF Localization Demo</value>
  </data>
</root>

German (de) resources. File Properties/Resources.de.resx:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <!-- 
    Microsoft ResX Schema 
 
    Version 2.0
 
    The primary goals of this format is to allow a simple XML format 
    that is mostly human readable. The generation and parsing of the 
    various data types are done through the TypeConverter classes 
    associated with the data types.
 
    Example:
 
    ... ado.net/XML headers & schema ...
    <resheader name="resmimetype">text/microsoft-resx</resheader>
    <resheader name="version">2.0</resheader>
    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
        <value>[base64 mime encoded serialized .NET Framework object]</value>
    </data>
    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
        <comment>This is a comment</comment>
    </data>
 
    There are any number of "resheader" rows that contain simple 
    name/value pairs.
 
    Each data row contains a name, and value. The row also contains a 
    type or mimetype. Type corresponds to a .NET class that support 
    text/value conversion through the TypeConverter architecture. 
    Classes that don't support this are serialized and stored with the 
    mimetype set.
 
    The mimetype is used for serialized objects, and tells the 
    ResXResourceReader how to depersist the object. This is currently not 
    extensible. For a given mimetype the value must be set accordingly:
 
    Note - application/x-microsoft.net.object.binary.base64 is the format 
    that the ResXResourceWriter will generate, however the reader can 
    read any of the formats listed below.
 
    mimetype: application/x-microsoft.net.object.binary.base64
    value   : The object must be serialized with 
            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
            : and then encoded with base64 encoding.
 
    mimetype: application/x-microsoft.net.object.soap.base64
    value   : The object must be serialized with 
            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
            : and then encoded with base64 encoding.
 
    mimetype: application/x-microsoft.net.object.bytearray.base64
    value   : The object must be serialized into a byte array 
            : using a System.ComponentModel.TypeConverter
            : and then encoded with base64 encoding.
    -->
  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
    <xsd:element name="root" msdata:IsDataSet="true">
      <xsd:complexType>
        <xsd:choice maxOccurs="unbounded">
          <xsd:element name="metadata">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" />
              </xsd:sequence>
              <xsd:attribute name="name" use="required" type="xsd:string" />
              <xsd:attribute name="type" type="xsd:string" />
              <xsd:attribute name="mimetype" type="xsd:string" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="assembly">
            <xsd:complexType>
              <xsd:attribute name="alias" type="xsd:string" />
              <xsd:attribute name="name" type="xsd:string" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="data">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="resheader">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" />
            </xsd:complexType>
          </xsd:element>
        </xsd:choice>
      </xsd:complexType>
    </xsd:element>
  </xsd:schema>
  <resheader name="resmimetype">
    <value>text/microsoft-resx</value>
  </resheader>
  <resheader name="version">
    <value>2.0</value>
  </resheader>
  <resheader name="reader">
    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <resheader name="writer">
    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <data name="Birthday" xml:space="preserve">
    <value>Geburtstag</value>
  </data>
  <data name="Close" xml:space="preserve">
    <value>Schließen</value>
  </data>
  <data name="Name" xml:space="preserve">
    <value>Name</value>
  </data>
  <data name="WpfLocalizationDemo" xml:space="preserve">
    <value>WPF Lokalisierungs Demo</value>
  </data>
</root>

User Settings. File Properties/Settings.settings:

<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="WpfLocalizationDemo.Properties" GeneratedClassName="Settings">
  <Profiles />
  <Settings>
    <Setting Name="Culture" Type="System.String" Scope="User">
      <Value Profile="(Default)" />
    </Setting>
    <Setting Name="UICulture" Type="System.String" Scope="User">
      <Value Profile="(Default)" />
    </Setting>
  </Settings>
</SettingsFile>

Main Window: Presentation/ShellWindow.xaml.cs:

using System.Windows;
 
namespace LocalizationSample.Presentation
{
    public partial class ShellWindow : Window
    {
        public ShellWindow()
        {
            InitializeComponent();
        }
    }
}

Main Window: Presentation/ShellWindow.xaml

<Window x:Class="LocalizationSample.Presentation.ShellWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:p="clr-namespace:LocalizationSample.Properties"
    Title="{x:Static p:Resources.WpfLocalizationDemo}" SizeToContent="WidthAndHeight" ResizeMode="CanMinimize">
 
    <Grid Margin="0,0,11,11">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
 
        <Label Content="{x:Static p:Resources.Name}" Grid.Column="0" Grid.Row="0" Margin="11,10,0,0"/>
        <TextBox Text="{Binding Name}" Grid.Column="1" Grid.Row="0" Width="100" Margin="11,11,0,0"/>
 
        <Label Content="{x:Static p:Resources.Birthday}" Grid.Column="0" Grid.Row="1" Margin="11,6,0,0"/>
        <TextBox Text="{Binding Birthday, StringFormat=d}" Grid.Column="1" Grid.Row="1" Width="100" Margin="11,7,0,0"/>
 
        <Button Content="{x:Static p:Resources.Close}" Grid.Column="1" Grid.Row="2" Width="73" HorizontalAlignment="Right" Margin="11,22,0,0"/>
    </Grid>
</Window>

Data Model: Domain/Person.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace LocalizationSample.Domain
{
    internal class Person
    {
        public string Name { get; set; }
 
        public DateTime Birthday { get; set; }
    }
}
Reference Material