CultureInfo and RegionInfo are great classes, providing a real lot of useful information about languages, countries, currencies and much more. But I found them to be quite complicated, partially hard to use and confusing.
I'm rather thinking in countries, languages and currencies. Maybe someone else has already solved this, but I did not find a suitable solution. So I wrote three classes CountryInfo, LanguageInfo and CurrencyInfo.
They are reasonably linked among each other and offer additional information, not provided by or not easily accessible using the respective .NET classes. For example:
The languages spoken in a country (see languages in Canada):
The default language for a country (e.g. English not Spanish for the US):
The countries where a language is spoken (see countries for French):
The origin of a language:
The countries where a currency is used (see countries for US Dollar):
Additionally, I wrote some extensions to CultureInfo to get the corresponding CountryInfo, LanguageInfo and CurrencyInfo from a CultureInfo at hand.
Together with the linking between entities mentioned above, you can even expose a default country and currency for a neutral (not country-related) culture:
This may not necessarily always make sense, though:
But for specific cultures, all is ok:
Using the Code
What struck me, too, is that CultureInfo as well as RegionInfo are identified and created by either a
string, e.g. "
en-US" or an integer (LCID). When you pass a non-existing
string or integer to the constructor, you get an exception.
I would have wished to have an
enum, e.g. "
CultureCode", where each
enum value represents a culture. This would be much easier and compile-safer to deal with.
Thus, for my classes, I introduced three
public enum CountryCode
public enum LanguageCode
public enum CurrencyCode
CurrencyInfo instances can easily be got from a
static method of the respective class:
CountryInfo countryInfo = CountryInfo.GetCountry(CountryCode.Us);
LanguageInfo languageInfo = LanguageInfo.GetLanguage(LanguageCode.Es);
CurrencyInfo currencyInfo = CurrencyInfo.GetCurrency(CurrencyCode.Eur);
enum approach provides nice intellisense!
CurrencyInfo instances are created in
static constructors of the respective classes and are kept in an internal dictionary. It can be accessed over the
static All property, each of the classes provides, for example:
public static SortedDictionary<CountryCode, CountryInfo> All
return new SortedDictionary<CountryCode, CountryInfo>(_all);
The instance constructors are made
private, so you always get the same, e.g.,
CountryInfo instance when calling
public static CountryInfo GetCountry(CountryCode countryCode)
As all collections (e.g. all countries where a language is spoken) in instances are always returned as a copy, the whole system is immutable.
Of course, particular attention has been given to thread-safety, therefore it should be guaranteed throughout. In the demo project, where the above screenshots are taken from, some unit tests are included, which should cover all functionality provided. Thus, I expect the classes to be very robust.
Points of Interest
There were some edge-cases which made it partially hard to maintain consistency. For example, region "
Caribbean", which does not represent a single country, but (really) a region. It has
029", which cannot be converted to an
enum. Also the invariant culture was an issue. Explore the source code how I solved both cases.
Some extra information my classes provide are subject to research and/or a matter of taste. For example, I've added a property
ShortEnglishName for countries to say e.g. just Monaco instead of Principality of Monaco as it comes from MS. Same applies for default languages and default countries. In some cases, I may have been incorrect. As you have the source code, feel free to change it, if you do not agree.
- 19 June 2014 - First published, version 1.0
- 23 June 2014 - Added