I'm currently writing an ASP.NET 2.0 web application that needs to be multilingual. It has a master page that defines the look and feel of the entire web applicaition, with many content pages as you navigate around the web application.
My initial thought was that I would include a resource file for each language, and then pull the localized data from them after I has discovered what language was being requested. I noticed that you can get the requested languages, and IE is all set up for allowing users to request languages in a priority manner, and I can retrieve them in the request.UserLanguages collection. I also noticed that I could set up my webpages to automatically choose the right language/culture by adding the Culture="auto" and UICulture="auto" to the top of each page.
That all works great. Then I can add meta:Resourcekey="Button1" as an attribute to the button1 object, and if there is a resource string named "Button1.Text" in a corresponding resource file, then all is great.
Of course, I found a few problems along the way...
The meta-tag has no intellisense in the VS 2005 IDE. It's as if it doesn't even understand the attribute at all.
This technique doesn't work well in master pages (As it looks inside each page specific resource file).
If you request say culture "en-us", and there is no specific "en-us" resource, but there is a "en" resource, it pulls the data from there, that's great! However... If there is no "en" resource, but a generic resource for the page, it uses that next. Even if the user has a second langauge they would accept.
For example.. The user in IE has set up to request the cultures (In order of priority)
de
fr
en-us
This SHOULD look for a german resources, if it does not exists, then it should look for a french version. If that does not exist, then it should look for a US english version. If that does not exist, then look for a generic english version. And then if that does not exist, use the default resource. But that is not what happens... If german does not exist, it then goes directly to the default resource the site is written in, even if there was a french, english, or US english version available.
Ok, so I figured there obviously was some limitations, and I would just override the culture detection stuff in my master page, and choose what language the thread is running in so that all my resources would get picked up correctly. Opps, that's another problem. The culture information, and the meta:resource stuff is all done in InitializeCulture sub. I can override it, but it gets called before the master page is even "linked" to the page, so I'd have to copy that routine to every single page in the site instead of including it only once. Yuck.
Now, what should have been such a simple thing to do, and it appears that a lot of thought went into the whole process to make globalizing web pages a snap now has a lot of work that I would have expected to just "work".
Now I have to override each page's InitializeCulture sub separately in each page to search the header and compare it to the available resources in my site. (I guess I could create my own class that encapsulated the functionality).
meta:resourcekey= syntax doesn't work in master pages the way I want (I'm not even sure if it works at all, I haven't really tested it all that much). So now I am overriding each property individually like this:
<asp:button ID="Button1" Text="<%$ Resources:master,Button1_Text %>">
originally, I wanted to use Button1.Text, but for some reason, the IDE whines about the period in the key when it's in a global resource, but that's the exact syntax it's expecting when you use it in the local resources.
One last quirk. One of the things I'm globalizing is a menu (the menu too is in the master page). ARGH! Let me start off by saying, thanks for the menu. That said, it sucks. I guess I've gotten too spoiled with 3rd party menus as of late. I was trying to figure out how to set it up so that specific menu items were tied to the user roles. No such feature. Oh well. Instead of extending the menu class, I just decided on a quick hack, and I append ;{role},{role},{role}... to the value of the menu items, and if any roles are present, and the user belongs to none of them, then I remove the item on Page_Load. I also remove any menu items that are not-selectable and have no child nodes (So if all selectable child nodes have been removed, remove the non-selectable parent as well). This works great, too bad it wasn't built in to begin with. So anyhow, I was globalizing the menu, and changing the text property in source code to the special <%$ resources:master,"key" %> syntax. The next day I wanted to add a few more menu items, so I went into the designer, told it to edit the menu items, and the text wasn't what was in the source view. UGH! Caching the menu items...bad bad. Ok, I started to add a few more menu items... In the text field I put the <%$ resources.. %> stuff, and flipped back to source view. ARGH! It URL encoded it. So I went to the <menuitem> element, and in the properties box, it showed the URL-encoded version? So I edited in the property box, and it put a un-url-encoded version in the text property like I wanted. GREAT... So in design view, the text property gets URL encoded, but in source view, the text property is literal. Just a bit inconsistant.
Oh, and somtimes I will hit F5 to test my changes after working it the resource files, etc. VS 2005 complains there are some errors and would I like to use the last version? I say no. Sure enough it lists a bunch of weird resource related errors in the error window. Mostly they are unintelligable. Sometimes they just disappear on their own after a while, or after I click on one, and it goes nowhere near whatever error it's complaining about. In any case, now whenever that error pops up, I hit "build website", and all those weird errors just disappear, and I can F5 to start debugging my application.
My question is.. Am I doing this right? Am I missing something here, or is this really what we have to do to globalize a web app? Is there a simple meta:resourceclass="master" I can use in master pages to specify a resource in App_GlobalResources instead of in App_LocalResources, and have it magically hook all the properties found in a resource and load them all rather than having to specify each one separately? I suppose I could a routine to do that somehow myself, but since it's already there for non-master pages, I'd like to just trigger that somehow myself if possible.
Summary:
Upgrading a VERY simple (1 page!) ASP.NET application started in VS 2003 and "upgraded" to VS 2005 was extremely quirky until I completely deleted web.config, and let VS 2005 recreate it. That might be expected if I had made ANY changes to web.config, but it was the default one from VS 2003! I'm guessing it's related to the 10 or new namespaces added in the VS 2005 version.Instead of issuing a meta:ResourceKey="..." on each control, why isn't there a way to just say meta:all at the top of the page and it'll act like there is a meta:Resourcekey="ControlName" on each control?Intellisense on meta:ResourceKey is non-existant, or even listedNo "easy" way in designer to specify <%$ Resource:class,key %> for a property on a menu item. (Expression) isn't a property in the property box for menu items.
Hi, let me see if I can try to answer your concerns here:
- The meta tag in VS2005 doesn't have intellisense unfortunately, it does however, recognize that the meta prefix is valid or invalid (that is "resourcekey"). Similarly, the value assigned to the property is not checked within any local resource file. Having said that the primary generator for the attribute is the tool, through the 'Generate Local Resource' command;
- Using local resources in a master-page should work as expected. That is, if you define meta:resourekey="foo" on your controlswithin the master-page and not within any contentplaceholder then they will be picked up from the master-pages resource file. If you have overridden the contentplaceholder in the content-page, then that content comes from the content-page and notionally any resources for that overridden content comes form the content page's resource file.
- The default handling for use of culture="auto" uiculture="auto" in the page directive goes off the 0th language in the preference defined in the header. If that is not resolved, then normal ResourceManager fallback occurs, which as you've stated is the fallback on the server. If instead of this handling you want to handle the fallback defined by the user in the list of language preferences, then you will need to override the handling in the InitializeCulture, or some point prior to the ControlBuilder calls in the page's generated class.
Master-pages by their nature are user-controls and therefore do not have the InitializeCulture API. This is a point in the page lifecycle that is before the controls are even initialized. In this respect therefore, you'll need to have your override of the culture setting occur with in each consuming content-page, which is not ideal of course, or move the custom overri8de to a custom base class. You can of course move the same logic into the app events say in global.asax if so desired.
- The underscore is used in the expression's value to map to the '.' in the resource file, is by design in the case that you have shown, because these are global resources, not local resources. In the expression you define, this references a resource file too, which immediately points the expression to the resource file in the App_GlobalResources folder. Th4ese resources are also accessed through strong-typing and by this respect, the actual key name cannot be references in this manner through a period, so an underscore is used. The expression goes through the strongly-types accessor.
- I'll look into the issue of the menu designer encoding problem so more :)
- I'm not sure what the errors are you are seeing, can you report the actual error you are seeing? What version of the tool are you using, beta2, or an RC version?
- The implicit expression syntax meta:resourceKey is scoped only to the local resources I am afraid. The actual explicit expression is the way to declaratively access the global resources at the moment I am afraid. Therefore it will be more work. This is something we can consider in the future and was looked into originally. In the meantime there are several extensibility paths that you could investigate to support this.
-Editing of content pages that are build to a master-page defined in configuration is a known limitation and described in many threads in the forum. Again something that we will address in the future. There are some simple workarounds for now, such as defining an empty string for design-time until you are ready for runtime ..
- Configuring the custom loc provider is through the globalization section
<globalization resourceProviderFactory="MyResourceproviders, MyAssembly .. " ..>
- Your idea of meta:all is an interesting suggestion we can consider in the future
In addition:
- You might want to consider the fact that the resource model is fully extensible. The RESX model that you are using here is the out-of-the-box model. You can swap out the localization provider (one per app), for your custom one, and handle resources form other sources, such as a backend DB for example. The provider means that you could handle global and local resources to the same source, and the provider also gives you designer support.
- Extensibility could also mean that you build your own expression <% foo: ... %> that you could also use to map to your own resource model
We'll look for some new correct samples to help with the custom client fallback model in the meantime.
0 comments:
Post a Comment