The .NET Framework provides reasonably good support for writing multi-lingual Windows Forms applications. Once you have set the
Localizable property of a form to
true, then the Visual Studio designer will automatically generate code in the
InitializeComponent method of the form that loads the localizable properties of the form and its controls from compiled resources. Using the Visual Studio designer, you can also create resources for other languages - simply set the
Language property of the form and modify the language specific properties of the form and controls. Visual Studio will save these changes into a separate resource file and compile them into a satellite assembly for the language when you build your application.
At runtime when your form is created (and
InitializeComponent is called), the .NET Framework selects the resources to load based on the current value of the
System.Threading.Thread.CurrentThread.CurrentUICulture property. If you change this property before your form is created, then the resources for the corresponding culture will be loaded. This means that if you want to change the user interface language once your application is running, you must close any open forms and recreate them (or restart the entire application) to force the resources to be reloaded for the new UI Culture. This is somewhat clumsy and means losing any transient application state stored in the forms. This article provides a
CultureManager component that allows the localizable resources for open forms and control to be dynamically reloaded using a new UI Culture.
The articles Instantly Changing Language in the Form and UICultureChanger Component also provide solutions to this issue. The approach taken in this article differs from these previous articles in the following ways:
- The above articles only handle a subset of standard properties, e.g.
Size, etc. By contrast, the solution presented here can load the resources for any localizable property of a component/control (localizable properties are those marked with the
CultureManager component described here provides a hook (via the
UICultureChanged event) that allows you to execute code after a forms resources have been reloaded. This can be useful, for instance, when the text displayed in a control is generated programmatically - but is still culture dependant.
CultureManager component allows you to change the UI Culture of all open forms in the application. Alternatively you can change the
UICulture for an individual form allowing you to have two (or more) open forms with different UI Cultures.
CultureManager component works with forms and controls developed using VB.NET
- The source code for this article is provided under the The Code Project Open License (CPOL). The second article above uses the GNU Lesser General Public License - this prevents inclusion of its source code directly into commercial applications. See the following CodeProject article on Licenses for more information.
How it Works
This section provides an outline of how the
CultureManager component internal implementation works. If you just want to use the component "as is", you can skip down to Using the Component.
CultureManager is implemented as a component that you place on each form in your application that you want to change culture dynamically. The
CultureManager component attaches a handler to the
static CultureManager.ApplicationUICultureChanged event so that it receives notification when the
CultureManager.ApplicationUICulture static property is changed. The component then uses .NET Reflection to locate localizable resources for the form and reloads these resources using the new UI Culture. This event architecture allows the UI Culture of all open forms in the application to be changed by simply setting the
CultureManager.ApplicationUICulture static property. Alternatively you can set the
UICulture of an individual form.
CultureManager.ApplyResources method (shown below) implements the core logic used to reload the localizable resources for a component.
protected virtual void ApplyResources(Type componentType,
if (resourceStream == null) return;
Type parentType = componentType.BaseType;
if (parentType != null)
ApplyResources(parentType, instance, culture);
= new ComponentResourceManager(componentType);
SortedList<string object,> resources = new SortedList<string object,>();
LoadResources(resourceManager, culture, resources);
Dictionary<string Component,> components = new Dictionary<string Component,>();
Dictionary<Type IExtenderProvider,> extenderProviders
= new Dictionary<Type IExtenderProvider,>();
bool isVB = IsVBAssembly(componentType.Assembly);
components["$this"] = instance;
FieldInfo fields = componentType.GetFields(BindingFlags.Instance |
foreach (FieldInfo field in fields)
string fieldName = field.Name;
fieldName = fieldName.Substring(1, fieldName.Length - 1);
string resourceName = ">>" + fieldName + ".Name";
Component childComponent = field.GetValue(instance) as Component;
if (childComponent != null)
components[fieldName] = childComponent;
ApplyResources(childComponent.GetType(), childComponent, culture);
if (childComponent is IExtenderProvider)
= childComponent as IExtenderProvider;
foreach (KeyValuePair<string object,> pair in resources)
string resourceName = pair.Key;
object resourceValue = pair.Value;
string resourceNameParts = resourceName.Split('.');
string componentName = resourceNameParts;
string propertyName = resourceNameParts;
if (componentName.StartsWith(">>")) continue;
if (IsExcluded(componentName, propertyName)) continue;
Component component = null;
if (!components.TryGetValue(componentName, out component)) continue;
Control control = component as Control;
if (control != null)
SetAutoScaleDimensions(control as ContainerControl, (SizeF)resourceValue);
resourceValue = AutoScalePadding((Padding)resourceValue);
if (control is Form && PreserveFormSize) continue;
resourceValue = AutoScaleSize((Size)resourceValue);
= TypeDescriptor.GetProperties(component).Find(propertyName, false);
if (((pd != null) && !pd.IsReadOnly) &&
((resourceValue == null) || pd.PropertyType.IsInstanceOfType(resourceValue)))
if (control != null)
control, propertyName, resourceValue);
ApplyResources method first checks if there are localizable resources associated with the component type. If there aren't, there is nothing more to do. If there are, then the method recursively calls itself to first apply any localized resources associated with its base type. The base type resources must be applied before any localized resources defined by the derived class.
Next we call
LoadResources to load the localized resources for this component type into a
SortedList. This gives us a list of all the localized resources used by the component and enables us to quickly locate resources by name. We use reflection to locate the member variables of the component that may have localized resources and build a lookup table to enable us to quickly locate a child component object given its name. Note that the naming convention used by the VB.NET designer for component member variables requires some special handling for VB.NET components.
Finally we iterate through the list of localizable resources and use reflection to set the properties of the components with some special case handling as discussed below.
Some Special Cases
Size property for a component that is anchored or docked can cause unexpected behaviour. The
SetControlLocation methods contain logic that checks the
Dock properties and only updates the components of the location or size that are not controlled by anchoring or docking. The other issue the code addresses is when the screen resolution is different to that used at design time (because the user is using DPI scaling). Normally Windows Forms automatically scales the sizes and locations of controls and forms after they are first created to account for this. Since the sizes and locations in the resources are defined in the design time resolution we need to scale them to match the current screen resolution. The
SetAutoScaleDimensions method sets the
AutoScaleFactor based the design time resolution dimensions and the
CurrentAutoScaleDimensions. Subsequent size, location and padding resources are scaled by this value
Some localized resources do not correspond to simple properties on the constituent components. Extender components (for example
ToolTips) provide extended design time properties for other controls on the form. The extender component handles code (and resource) serialization of these properties. Unfortunately there is no reflection mechanism to discover the methods on the extender component that we need to call - or the resource naming convention used for the extender properties. If there is no standard property corresponding to a localized resource, then the
ApplyExtenderResource method (see below) is called to process the resource using some special case logic.
protected virtual void ApplyExtenderResource
(Dictionary<Type, IExtenderProvider> extenderProviders,
Control control, string propertyName, object value)
IExtenderProvider extender = null;
if (propertyName == "ToolTip")
if (extenderProviders.TryGetValue(typeof(ToolTip), out extender))
(extender as ToolTip).SetToolTip(control, value as string);
We pass the
ApplyExtenderResource method a lookup table of the components that implement the
IExtenderProvider interface (indexed by type). This allows the method to locate the extender component associated with the given property and call the appropriate method to set the localized property. The base implementation of the
ApplyExtenderResource method implements logic for
HelpProvider extender components. It can be overridden to add support for other custom extender components.
Using the Component
Once you have downloaded the source code and built it, you can add the
CultureManager component to your Visual Studio toolbox. If you are using the demo solution, then the component should already appear under the "Infralution.Localization Components" tab. To use the component in another project, right click on the toolbox and select "Choose Items..", then select the "Browse" button and locate the Infralution.Localization.dll assembly that you built earlier.
Place an instance of the component on each form in your application that you want to change culture dynamically. Note that this typically does not need to be all forms in your application, only those that are opened non-modally. If a form is always opened as a modal dialog (using
ShowDialog) then there is no way for a user to change UI Culture of the application while the form is open.
Add a menu (or other mechanism) to set the
CultureManager.ApplicationUICulture of the application. This sets the
System.Threading.Thread.CurrentThread.CurrentUICulture and reloads the localizable resources for each open form that has a
The component also provides some properties and events that you can use for additional control over the UI Culture change process:
ExcludeProperties - This allows you to specify a list of properties that you don't wish to be reloaded from resources when the UI Culture is changed. For instance, if you add "
Enabled" to this list then the
Enabled property of any controls will not be reloaded from resources. Note that generally you don't need to use this mechanism because Visual Studio will only serialize resources if they have non-default values. So the
Enabled property for a control would not be stored in the form resources unless you had set it to
false in the Visual Studio designer.
PreserveFormSize - If set to
true then the forms size will not be reloaded from resources.
PreserveFormLocation - If set to
true then the forms location will not be reloaded from resources.
UICultureChanged - This event is fired when UI Culture for the form is changed (after the resources have been reloaded). This allows you to execute code to update programmatically generated text.
- 2013.02.20 – Added code to handle auto scaling of sizes and locations
- 2009.10.12 – Added
CultureManager.SetThreadUICulture method to support multiple threads
- 2008.09.12 – Fixed issue with localizing
- 2008.02.18 – Initial posting