Introduction
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 enum
s:
CountryCode
public enum CountryCode
{
Ae,
Af,
Al,
LanguageCode
public enum LanguageCode
{
Af,
Am,
Ar,
Arn,
CurrencyCode
public enum CurrencyCode
{
Aed,
Afn,
All,
Amd,
The respective CountryInfo
, LanguageInfo
and 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);
Additionally, the enum
approach provides nice intellisense!
All CountryInfo
, LanguageInfo
and 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
{
get
{
return new SortedDictionary<CountryCode, CountryInfo>(_all);
}
}
The instance constructors are made private
, so you always get the same, e.g., CountryInfo
instance when calling CountryInfo.GetCountry(CountryCode)
:
public static CountryInfo GetCountry(CountryCode countryCode)
{
return _all[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 TwoLetterISORegionName
"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.
History
- 19 June 2014 - First published, version 1.0
- 23 June 2014 - Added
VehicleRegistrationCode
and GermanName
to CountryInfo