|

Introduction
Over the years, I have done mobility applications on Windows Mobile 2003 & 5.0, and I have done globalized/localized applications in ASP, ASP.NET, and Smart Clients. Recently, I came across my first requirement to bring these things together and write a localized, Smart Client application targeted at the Windows Mobile 5.0 platform (utilizing the Microsoft Compact Framework version 2.0). Like most things new on the Compact Framework, it starts out exciting and quickly turns frustrating, as you discover all new areas of the "full" .NET Framework that were sacrificed in order to reduce the footprint of the "compact" version. This article is about the challenges I faced and the solution I used.
Problem Space
In the full .NET Framework, you have the System.Resources.ResourceManager class, which provides all the built-in functionality you need to easily load resources from external assemblies based on the System.Threading.Thread.CurrentUICulture. The ResourceManager is smart enough to find the right assembly, look for the resource and, if it doesn't find the resource, it will "auto-magically" look for your resource in the parent culture, until it either finds a match, or ends up back at the InvariantCulture (which is basically the unknown or generic culture). Changing the resources used by your user interface is as easy as:
Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US");
The first limitation with the Compact Framework is that the CurrentThread.CurrentUICulture is not supported. This means that your application will use the culture of the OS (see this article on MSDN for more information). But what if you have a situation where all mobile devices are deployed set for US English (en-US), but some operators may want to use a particular application in Mexican Spanish (es-MX) or French Canadian (fr-CA)? You can't do it with the built-in ResourceManager unless you use the OS' Control Panel to change the culture settings for the entire device.
Problem #1 is that we need a way to change the CultureInfo of just our application and have that be used by the ResourceManager.
Next, if you have done any globalization/localization work within Visual Studio 2005, you may have found that there is a built-in mechanism for making your forms "Localizable". I'm not going to try and get into a full dissertation on how this works. If you are interested, I would recommend a walk down "Google Lane". In short, you can use the IDE's form properties to view your form in different locales and change certain property values (i.e., Control.Text) on a per-locale basis. "Under the covers," the IDE creates localized resource assemblies for each locale you support, and will "auto-magically" select the right set of resources at run-time (a-la the ResourceManager described above).
The second limitation of the Compact Framework is that the mechanism which updates the UI is all tied to the CurrentUICulture of the current thread, which as we saw above, is never going to change (unless it is done at the device level).
Problem #2 is that we need the ability to update our UI on-the-fly whenever a change is detected in the culture settings.
Design Goals
In addition to solving the above problem, there were certain design goals which I had for the final solution.
- It needed to work (obviously), but in doing so, I didn't want to reinvent the wheel. It needed to use as much of the existing infrastructure as possible. In particular, this meant the logic behind finding/selecting the resource files as well as the fall-back mechanism for missing assemblies or resources.
- It needed to be as "self-contained" and easy to use as possible. I didn't want the developers to get tied up in implementing the mechanism.
- It needed to support the addition of new resource assemblies in the future without any change to the source code or binaries.
The Solution
The solution that I deployed for this comes in two parts. The first is a custom ResourceManager (implemented as a Singleton), which gives applications the ability to specify the CultureInfo to be used when looking up resources. The second part is a LocalizedForm (derived from System.Windows.Forms.Form) which is designed to be used as the base class for any form that requires localization support. Please refer to the included source code and example project for full details on how this is implemented.
CompactFramework.Utilities.Localization.ResourceManager
The custom ResourceManager gives your application a way to specify the Assembly and CultureInfo of the resources to use. Whenever the CultureInfo is changed, an event is raised, letting subscribers to the event know that there has been a change. The ResourceManager also exposes some overloaded helper methods which allow the LocalizedForm to more easily retrieve resources.
CompactFramework.Utilities.Localization.LocalizedForm
The LocalizedForm provides a common base-class that can be used to provide the functionality of listening for and responding to the ResourceManager.CultureChanged event. It will loop through all of its child controls and menus and update their appropriate properties (i.e., Control.Text, MenuItem.Text, PictureBox.Image, etc.) with the appropriate, localized resource.
Menus Suck!
Another of those areas where the Compact Framework is just plain annoying is in working with menu controls. The root of the problem is that the MenuItem doesn't actually derive from the Control base-class and, as a result, doesn't actually have a Name property that you can read at run-time. I played briefly with a Reflection-based solution to this problem, but having recently worked through a Reflection-based hardware abstraction layer for a similar application, I knew that I was going to run into limitations of the Compact Framework as well as performance penalties.
Instead, I placed a little of the implementation burden on the developer in order to improve performance. As a result, the LocalizedForm base class maintains a Dictionary<MenuItem, string> which the developer fills as part of the constructor on each form, like so:
this.AddMenuToDictionary(this.menuItem1, "MainMenuLeft");
this.AddMenuToDictionary(this.menuItem2, "MainMenuRight");
The LocalizedForm class uses these strings in lieu of the Control.Name property when looking for the Text value in the resource assembly.
Two Final "Gotchas"
There are two aspects of using localization that you must be careful to do correctly, or it will simply not work (and not throw any errors). If you don't locate and name your resource files correctly, it won't find them. If you don't name your resources correctly, it won't find them. In either case, the ResourceManager will simply return its best match or (worst case) will not make any change to the UI.
Location and Name of Your Resource Files
There is one last thing that you have to be sure to do in order for the "built-in" functionality of the underlying ResourceManager to function properly. When you add these resource files to your project, they must be located inside of a project directory named "Resources" and they must be named Resources.<culture-code>.resx (see the image at the top of this article for an example). If you do not do these two things, the ResourceManager will not be able to locate your resource files and you will not get any localized updates.
Naming Your Resources
As you can see in the image at the top of the article, this infrastructure utilizes the name of the control, combined with the name of the parent form, to uniquely identify a resource. If you do not follow this naming convention in your resource files, a match will not be found and no change will be made to the UI (and no error thrown).
What's Remaining?
Right now, I handle the basic controls (TextBox, Label, Button, CheckBox, TabPage, Form, RadioButton, and PictureBox). Additional controls can certainly be added by updating the LocalizedForm.UpdateControls() function.
Summary
I hope that this was interesting and/or useful to you. I enjoy doing them, I appreciate feedback (both positive and constructive criticism), and I like seeing what people do with these in their own solutions. If this was useful or interesting to you, please take a moment to rate the article. If you have questions or suggestions, feel free to post them below.
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 74 (Total in Forum: 74) (Refresh) | FirstPrevNext |
|
 |
|
|
Move strings from code to resx and translate their automatically. Try RGreatEx[^] free.
--- Best regards, Alexander Nesterenko Safe Develop Team www.safedevelop.com
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Do you know if any change is needed to make it work on VB?
Are assemblies different in any way in VB? I linked the CompactFramework.Utilities.Localization to my VB application, but even setting the ExecuteAssembly as first thing, certifying to have the resources files in the Resources folders and everything.. I still got a MissingManifestResourceException.
Maybe VB handles the namespaces differently, or maybe the My namespace is the gotcha...
Any ideas? Thanks in advance 
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi there,
I use your example and tutorial source to localize my app. Thanks for that, it works pretty good and gave me a solution after hours of walking through the net..  I would be pleased if you could answer me (i. e. have time for) one question:
All strings I use are saved in a Resource-Folder and have the names "Resources.en-US.resx" for example. On the compact framework everything works well, and I have the locale folders in the folder of my executable (i. e. "en-US") with a dll called "App.resources.dll" (where App is my App name and "default namespace"). I load the resources by generating a ResourceManager with the BaseName "App.Resources.Resources" and the executing assembly. If I want to have a localized string I ask the ResourceManager with with GetString(name, culture) for the value of "key" name in the given culture. This works pretty good in compact framework, but if I execute the application on the desktop (with the full framework) I can not load the resources, they are not found. The application tells me this (that no fitting culture or invariant culture had been dfound) by throwing an exception if I try to use GetString(..., ...).
The same directory structure and so on, but it does not work. In gernal I only use the parts of the compact framework that are also available on the desktop.
How can this be? Is there a difference in resource lookup between full and compact framework?
Thanks in advance, Tom
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Well, the short answer is that I don't know why. I have never tried it except on the CF platform, but I agree with you that, in theory, it should work on the full framework.
I think you need to step through the ResourceManager singleton wrapper and see what it's doing. It sounds like the assemblies or namespaces aren't right, though, again, why it would work on the CF and not the full Framework, I have no idea.
Sorry I wasn;t more help. :(
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Well, thanks anyway for the quick answer. I will step through it, maybe I find something (if so, I will post it).
Ragards, Tom
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Interstingly it works if I put the the localized resources into the Properties folder (and thus namespace) of the project (and intitalize the ResourceManager with "App.Properties.Resources"). Strange...
One more thing: On The Desktop-Framework the ToString()-Method returns different values than on the compact framwork, at least for some controls (", Text: ..." is appendended to the name of the control) - so it is better/more consistent in this way (In my case sometimes there is no TopLevelControl, so I added some lines):
if (c.TopLevelControl != null) return GetResourceName(c.TopLevelControl.Name.ToString(), c.Name); else if (c.Parent != null) return GetResourceName(c.Parent.Name.ToString(), c.Name); else return GetResourceName("Freestanding", c.Name);
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
That's because resource files have a namespace that is inferred by the directory structure within which the resource files reside in the project. That's why in my example, I have a resource namespace that includes "Resource.Resource" - the first one is because the resource files are all in a directory named "Resource" (for neatness) and the second is because that's the start of the name of the resource file itself.
If you want your resource files to be somewhere else, you need to change the line of code in the Resource Manager that builds the assembly name containing the resources.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Sure, that was clear - but thanks anyways. Strange is (or maybe I do not get it ) that it does not work (at least not on the Desktop, on the PDA (Compact Framework) it does) if I put the resource files into the "Resource" folder like you suggested (which is fine and good) and one passes the "App.Resource.Resource" String to the ResourceManager constructer. But if one puts the resource files into the "Properties" folder and one passes the "App.Properties.Resource" argument it works on both platforms. Essentially both ways should no be different, shoulnd't they?
Regards, Tom
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi,
great assembly and I like to use it in my own application. Your sample works fine so I add the Utilities.Localization Project into my one. Also I added two files Resources.en.resx and Resources.de.resx into the folder Resources.
But I get ever a MissingManifestResourceException when I use the GetString function.
Here's my source: ResourceManager.Instance.CallingAssembly = Assembly.GetExecutingAssembly(); ResourceManager.Instance.Culture = new System.Globalization.CultureInfo("de"); MessageBox.Show(ResourceManager.Instance.GetString(@"Rooms")); <- Error
It doesn't matter what ci I use - de and en reports the same error.
Maybe that happens because of I call it fron a dll and not from an exe? When I look at my application folder there're to folders (de and en) are created and inside there's a dll called myapp.resources.dll
Maybe somebody can help me please ...
Thanks
Andre
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
You probably have a namespace issue. Place a breakpoint in the ResourceManager class near line 74:
_rm = new System.Resources.ResourceManager(string.Format("{0}.{1}.{1}", _assy.GetName().Name, @"Resources"), _assy);
This is probably the line throwing the exception. See what value you get for:
string.Format("{0}.{1}.{1}", _assy.GetName().Name, @"Resources")
If it doesn't match your application, that's your problem.
Tony
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi Tony,
it seems to be passed and the return value for the string is 'MyApp.Resources.Resources'. Like I see the moduledir is null - is this important?
I hope that helps to solf the problem ...
thanks,
Andre
modified on Tuesday, October 21, 2008 4:51 PM
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
When you look at your compiled resource assemblies with Reflector, do they contain resources that match that name space? When that line of code executes, do you get a resource manager reference or a null reference? Are you setting the assembly property first thing after your application starts up?
This article was never meant to provide a "drop-in" tool for any consumer. It was an article about the challenges involved and how I overcame those challenges, with a simple application to demonstrate the solution so that others with similar problems could start off a "leg up." The problems and questions that people have here are always around "how do I get this to work in my application?" That's fine and I am happy to offer help and ideas, but you have to understand the basic concepts of assemblies, namespaces, resource files, etc. and you have to do the work yourself. Many people have used this article and it's code very successfully with no problems because they understood the concepts here and could make changes where their application required it.
I can't offer intelligent, detailed answers to your questions without having a copy of your source code and time to review it. Since that isn't an option, you need to compare in detail how my working example project functions and compare that to how your integrated application does not and figure out what you need to change in order to close the gap. Those might be changes to your code or to my example code or both - it will depend on what you find.
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
So I am a newbe to Windows Form development how would I impliment this in an application that has nearly 10 different forms? When I select the culture I want it to change the whole application not just the one form.
By the way, this is just what I was looking for and once I can impliment it across the whole application my applicaion will be finished! Thanks for the great work!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
You can have as many forms in your project as you want respond to the localization changed event by having those forms derive from CompactFramework.Utilities.Localization.LocalizedForm. All of the LocalizedForm classes will use the same ResourceManager instance, because it is a Singleton.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Tony - again new to windows forms development, how do I derive my forms from this CompactFramework.Utilities.Localization.LocalizedForm? Also do you have a VB example? My whole project is done in VB and I have found some converters although some work with CF code and some not so well.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
"Is there a VB.NET version?"
No, the version and code here is all there is and I don't really have the time or interest to do a port to VB.NET. I would recommend that if you really want to understand what it is doing, that you port it by hand and not try to use a converter. There isn't that much actual code when all is said and done and I am not doing anything weird or tricky with the C# syntax.
If you really don't want to port the code, just compile the CompactFramework.Utilities.Localization project as a C# project and you can use it from your VB.NET project just fine.
"How do I derive my forms from LocalizedForm?"
In VB.NET parlance, you would use the "Implements" key word. Add a new form to your project and change the base class of your form from System.Windows.Forms.Form to CompactFramework.Utilities.Localization.LocalizedForm. If you need additional help beyond that, I would recommend picking up a good book or reading some of the online documentation at MSDN on VB.NET syntax.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Thanks for the help! One other question, your program has a file (Resources) which holds all the .resx files for all the different languages. Is this as easy as merging all the current .resx files I have for each form into one main one? What I want is one main .resx file I send to the translators and add them later and build the application output. Right now I have 6 languages and about 10 forms so roughly 60 .resx files. I would like 6.
Also, with .NET CF 3.5 is there any major changes I should be concerned about?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I'm not sure what you are seeing/asking. The application uses a folder "Resources" that contains a single resource file for each region and locale. This is how the .NET resource management works. So you would have one *.resx for the entire application for each combination of region and locale.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Your response answers my question. I am using VS2008 .NETCF 3.5 and when you want to localize a form you set the Localizable property of the form to True and add the languages by selecting them from the dropdown list.
I have come across something which stumps me. I have changed one of my existing forms to Inherit CompactFramework.Utilities.Localization.LocalizedForm vs. Inheriting System.Windows.Forms.Form and I get errors and warnings. Warning is stating the class is not CLS compliant and the error I get is "Object reference not set to an instance of an object". Why would I get this? Right now I have added the CompactFramework.Utilities.Localization CS project to my solution and added the reference but have not yet done anything else (except as mentioned) The call stack is as follows:
at Microsoft.CompactFramework.Design.DeviceTypeResolutionService.AssemblyEntry.get_Assembly() at Microsoft.CompactFramework.Design.DeviceTypeResolutionService.AssemblyEntry.GetAssembly(Boolean reflectionBased) at Microsoft.CompactFramework.Design.DeviceTypeResolutionService.AssemblyEntry.Search(String fullName, String typeName, Boolean ignoreTypeCase, Boolean reflectionBased, Assembly& assembly, String description) at Microsoft.CompactFramework.Design.DeviceTypeResolutionService.SearchNormalEntries(AssemblyName assemblyName, String typeName, Boolean ignoreTypeCase, Boolean reflectionBased, Assembly& assembly, Boolean fastSearch) at Microsoft.CompactFramework.Design.DeviceTypeResolutionService.SearchEntries(AssemblyName assemblyName, String typeName, Boolean ignoreCase, Boolean reflectionBased, Assembly& assembly, ReferenceType refType) at Microsoft.CompactFramework.Design.DeviceTypeResolutionService.GetType(String typeName, Boolean throwOnError, Boolean ignoreCase, ReferenceType refType) at Microsoft.VisualStudio.Design.Serialization.CodeDom.AggregateTypeResolutionService.GetType(String name, Boolean throwOnError, Boolean ignoreCase) at Microsoft.VisualStudio.Design.Serialization.CodeDom.AggregateTypeResolutionService.GetType(String name) at System.ComponentModel.Design.DesignerHost.System.ComponentModel.Design.IDesignerHost.GetType(String typeName) at System.ComponentModel.Design.Serialization.CodeDomDesignerLoader.EnsureDocument(IDesignerSerializationManager manager) at System.ComponentModel.Design.Serialization.CodeDomDesignerLoader.PerformLoad(IDesignerSerializationManager manager) at Microsoft.VisualStudio.Design.Serialization.CodeDom.VSCodeDomDesignerLoader.PerformLoad(IDesignerSerializationManager serializationManager) at System.ComponentModel.Design.Serialization.BasicDesignerLoader.BeginLoad(IDesignerLoaderHost host)
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
If you are going to use this code library, you do *not* want to set the form as Localizable and use the IDE to set the languages, resource strings etc. This entire article is about the limitations of that mechanism and why I did not use it. If you need your application to run off a different localization than the mobile OS is set to use, then re-read this article and review the example project on how to accomplish this.
This code was only ever written, compiled and deployed to Windows Mobile 5.0 with the Compact .NET Framework 2.0 with Visual Studio 2005. Anything beyond that I haven't tried and don't have the environment to reproduce right now. You are pretty much on your own. 
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Forgot to mention, I did set all my forms to not localizable.
The question I have is the assembly info (which is what the call stack error is looking at), do I have to do anything special to my exisiting project i.e.: modify namespace info, change assembly names or root names? Any of these changes should be the same in .NET 2.0 or .NET 3.5.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
The attached solution works fine in a VS2005/CF2.0 environment. You don't say, but the stack trace you provide would seem to indicate that you are getting these errors when the VS designer tries to render the form for graphical editing. My guess is that the namespace and/or base class(es) being used by VS2008/CF3.5 are different and that your current solution is complaining about the old solution's code and references as a result. Again, without having the environment myself, I can't be more specific, sorry. :(
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
You are correct, the attached does work in VS2005/CF2.0. I have converted it (via VS2008 converter) to VS2008/CF3.5 and it still works fine as stand alone but when I try to include the mentioned project and convert the exisiting forms I have I get the error. Yes it is in the graphical editor when I get this error. Remove the knowns (I'm using a different version of software and different platform), how would one include this into their existing project and convert their existing forms and/or project to make it work?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Is your problem related to the namespace issue described in the message thread here:
Include Solution into my Project?[^]
There was a design limitation in the original version where the assembly name and namespace had to match in order for it to work correctly. Beyond this, I never saw the problem you are describing while using the library and others had no issues as well. I am sure that it is a simple, but probably obscure thing. I would be more than happy to figure it out, except that I don't have a Windows Mobile "environment" setup any more. It's been more than 2 years since I had such a project or owned a WM phone.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
One question: when you add a new form to your CF project, what is the fully-qualified namespace and class name of the class from which your form inherits? Is it the same as the namespace and class from which the LocalizedForm inherits?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
General News Question Answer Joke Rant Admin
|