
-
Loading...
-
rmprimo
- Joined on 02-24-2003, 7:25 AM
- Posts 731
- Points 3,629
|
You are correct. The msdn docs make a mess of the explanation.
First of all the CultureInfo class is in System.Globalization. As such, it has NOTHING to do with threads at all and that is where the confusion comes from. It can hold info about (and BOTH) culture specific and neutral and even invariant culture and even non-existing cultures if you set up some custom ones.
On the other hand BOTH CurrentCulture and CurrentUICulture are props of the CurrentThread and that is NOT what differentiates them. They are both props of type System.Globalization.CultureInfo.
Now all this bucket of info has to be sorted out because in some situations only a subset of these data are needed. For that there is an enumeration called CultureTypes.
However let's keep it simple and look at two such subsets Neutral and Specific cultures. Without using the enumeration we could do a rule of thumb that 2-chars("en", "fr", "es") are neutral and 5-chars ("en-US","es-ES","pt-BR") are specific. There are some exceptions where there are extra chars but they are very obscure and you should look at them if they affect you.
Contrary to intuition, the name CurrentCulture has nothing to do with language, per se, but with formatting of dates and currency etc. For this reason it must be specific. A more appropriate self-descriptive name like CurrentFormattingCulture would have prevented millions of questions but they didn't let me make that decision.
On the other hand the property of the thread that has to do with language/translations etc is CurrentUICulture. This is the one the ResourceManager object takes to look up resources by culture(either neutral or specific) . Again, a wrong impression is conveyed by the UI which, at least to me, should imply exactly formatting. However the functionality is exactly the opposite. It has to do with multi-versioning of resources by culture. A more appropriate self-explanatory and less counter-intuitive name would have been CurrentResourcingCulture but, again, it wasn't up to me.
Asp.net controls with built-in localization cannot use neutral cultures because there could be a formatting difference in "pt-BR" and "pt-PT" which just "pt" couldn't account for. There may be a different religion and different calendar etc. Classic example is the 12h date-time formatting that only en-US and en-PH use. So such controls need the CurrentCulture set in the thread with 5-char SPECIFIC VALUE for culture name when you instantiate a CultureInfo object or auto detect it from the client browser.
For this reason there is a bool method IsNeutralCulture which can help you prevent exceptions(instead of primitively parsing the number of chars, which again includes most but not all scenarios, and not all constructor overloads - some take an LCID instead of culture name).
//never throws but your user may never get the UI culture changed either, and never know why. CultureInfo ci=new CultureInfo(SomeDynamicCultureNameStringOrLCID); if(!ci.IsNeutralCulture) Thread.CurrentThread.CurrentCulture=ci;
//throws but you can find out at design time, or catch and implement your own fallback. Thread.CurrentThread.CurrentCulture=CultureInfo.GetCultureInfo("fr");
//another way is to let the framework pick a specific culture for you via the static method. Thread.CurrentThread.CurrentCulture=CultureInfo.CreateSpecificCulture("fr");
You can get into a ton of political incorrectness with this method. There are many specific cultures with the same neutral parent, but the logic used to return the default specific culture is completely HAPHAZARD. Sometimes it is the most populous nation within the group. For example en returns en-US, pt returns pt-BR. This should placate any chauvinistic Brits and Potuguese.
However, es returns es-ES which is not the most populous Spanish speaking nation (it should be es-MX). Likewise, ar return ar-SA, but it should be ar-EG. The static CultureInfo.CreateSpecificCulture should really only be used when not throwing an error is more important than the other issues. Another point of interest is that the executing thread needs to be set with every request. The CultureInfo or culture name be persisted across different requests in the Session collection and retrieved in the new request (not just postbacks of the same page).
The code can be globalized in the Application_AcquireRequestState(object sender, EventArgs e) event handler in Global.asax. Then all you need do is assign a CultureInfo to the session. In the new page you do not even have to retrieve it. You can directly access the Thread.CurrentThread.CurrentUICulture and print it out or whatever and it works nice. The code generator does not even bother to stub out an Application_AcquireRequestState handler when creating a new Global.asax, but the HttpApplication.AcquireRequestState event does exist.
Actually the new static read-only properties of CultureInfo which return the executing thread's state are perfect for that. They don't even require the Threading namespace at all so it is all simplified, but only once you've suffered sorting it all out.
//identical to Thread.CurrentThread.CurrentCulture but read-only CultureInfo.CurrentCulture
//identical to Thread.CurrentThread.CurrentUICulture but read-only CultureInfo.CurrentUICulture
There are some limitations to using Session - InProc, out of proc etc. There are also other performance issues, which are beyond the scope of this topic, so in v2.0 personalization profile is now recommended as the store.
I had my own localization controls which didn't have to have a specific culture. I figured as long as the language was accounted for between "en" and "fr" it was good enough for my readers. That was a bad idea because I couldn't inherit from existing controls among many other problems.
It turns out I could get the best of both worlds, because there is a hierarchy too. "en" is a Parent of "en-US" and "en-GB" and "en-CA". The parent of "en" is the invariant culture. The parent of invariant culture is invariant culture, but it should have been null to allow testing for root level. So instead of testing for null of the parent you should always check for invariant in the self. The invariant, should not, but does have a parent: the invariant one. This kind of gets too religious for me. That alone can really bloat your code. Again the hierarchy is only for CurrentUICulture. Really the two collections are apples and oranges and are defined in separate independent RFCs. The idea of hierarchy is terrific, it should have been done at the RFC level. Microsoft did an OK job but there are still many irregularities. The good news is the irregularities are not for the popular cultures and are easily discoverable and predictable and can be accounted for in code. You better be an accomplished linguist before you set out to implement your own hierarchy, which is now possible via the provider model.
The fallback mechanism of the framework steps up this hierarchy, instead of skipping up straight to the root invariant culture as most samples show. Unfortunately I have seen no good discussion of this important feature. So out of the box you can assign en-ZW culture to the thread (both CurrentCulture and CurrentUICulture props). However you don't have to bother having a resx file for en-ZW if you don't expect many readers from Zimbabwe, and there really are not that many differences in the language. Still the Zimbabwian readers would get the data in the YourResource.en.resx for language AND still show the correct date-time formatting for Zimbabwe that is all done for you in the control. Isn't that nice!
You cannot step down the one-to-many hierarchy. Many specific en-XX have the same neutral parent en but not always vice-versa - sr is not the parent of sr-BS-Latn. So data integrity would be affected. If you need en-ZW then just say so from the start.
BTW, the CultureTypes enum is not much more helpful because it includes the invariant as neutral, which it is not, and in v2.0 there is a new undocumented CultureTypes.FrameworkCultures. CultureTypes.AllCultures includes the rest of the options. So the enum as a tool for navigating the the hierarchy is worthless. The Asp.Net team said they would fix it in Orcas but you never know. Meantime I did my own wrapper which includes one most important subset - cultures supported by my app - much smaller universe of values. The data is persisted in (what else) a resx file but it could be any data store and read into the Application object on App_Start in Global.asax
Maybe there should have been a derived class UICultureInfo: CultureInfo which would have reflected the difference, but years into the framework there would be too much legacy to contend with.
Now why couldn't they explain all that in msdn instead of going around in a vicious circle? It is a good thing msdn2 has feedback and rating so you can tell them what you think.
I am going to make a blog or an article out of this because I can't stand how many people are confused about one of the nicest features of the framework, all because of the knucklehead docs.
So any feedback corrections are appreciated.
Regards,
Rob
|
|