Software localization
All You Need to Know About CultureInfo in .NET Applications
CultureInfo, a class provided by the .NET framework, provides information about the locale of an application. In this guide, we will provide an overview of CultureInfo's core features and how to use them for internationalization. We will get to know different categories of culture provided by .NET and ways to determine the culture of an application thread. Last but not least, we will look into some practical examples of using the CultureInfo class, including fetching the localized day of the week as per the culture and doing smart substring checks irrespective of the letter case of culture.
What is the CultureInfo class?
The CultureInfo
class provides information about the locale of an application. With the help of CultureInfo
, we can fetch culture-specific information (e.g. language, sublanguage, country/region, calendar, etc.) and access the culture-specific instance of the DateTimeFormatInfo
, NumberFormatInfo
, CompareInfo
, and TextInfo
classes, which hold the information required for culture-specific operations, such as casing, formatting dates and numbers, and comparing strings.
Constructors of the CultureInfo class
The CultureInfo
class has 4 constructors defined; let’s take a look at them:
public CultureInfo(int culture)
—used to initialize a new instance of theCultureInfo
class based on the culture specified by the culture identifier.public CultureInfo(int culture, bool useUserOverride)
—takes an additional Boolean that specifies whether to use the user-selected culture settings from the system; if you pass true, the user-selected culture settings will be used; if you pass false, the default culture settings will be used.public CultureInfo(string name)
—used to initialize a new instance of theCultureInfo
class, based on the culture specified by name.public CultureInfo(string name, bool useUserOverride)
—takes an additional Boolean that specifies whether to use the user-selected culture settings from the system; if you pass true, the user-selected culture settings will be used; if you pass false, the default culture settings will be used.
Culture names and identifiers
The CultureInfo
class specifies a unique name for each culture, based on RFC 4646. The culture name is a combination of a two-letter lowercase language code and a two-letter uppercase country/region code, separated by a hyphen (-). For example:
en-US
—English, United Statesen-GB
—English, Great Britainpt-BR—
Portuguese, Brazilpt-PT
—Portuguese, Portugal
There are a few cultures that have a script associated with them. The name for a culture with an associated script can be represented using the language-script-country/region
patter. For example:
uz-Cyrl-UZ
—Uzbek language using the Cyrillic script, Uzbekistanuz-Latn-UZ
—Uzbek language using the Latin script, Uzbekistansr-Cyrl-CS
—Serbian language using the Cyrillic script, Serbia
Retrieving all cultures on the current system
We can use the GetCultures
method of the CultureInfo
class to fetch a list of all available cultures on your local machine. Refer to the code snippet below:
public void GetAllCulture() { CultureInfo[] availableCultures = CultureInfo.GetCultures(CultureTypes.SpecificCultures); foreach (CultureInfo cultureInfo in availableCultures) { Console.WriteLine(cultureInfo.DisplayName); } }
The cultures are broadly categorized into the following three groups we would like to explore in detail:
- Invariant cultures
- Neutral cultures
- Specific cultures
Invariant culture
The invariant culture is culture-independent. It is associated with the English language but not with any region. We can use an empty string (“”) to specify the invariant culture by name. We use the static CultureInfo.InvariantCulture
property to access the invariant culture. It is associated with the English language but not with any region.
The invariant culture is useful when we need culture-independent results since it will not change over time, from one user to another, or from one run instance to another.
For example, the English language uses commas for formatting thousands (e.g. 100,000). However, the German language uses periods for the same purpose (e.g. 100.000). If we try to parse the "100.000" value in the English culture, parsing will fail. However, we can use the invariant culture to convert a number to a string and later parse it back from any computer with any culture set. Take a look at the code snippet below:
decimal price = 123.45m; string priceToString = price.ToString(CultureInfo.InvariantCulture); var parsedPrice = decimal.Parse(priceToString, CultureInfo.InvariantCulture); // => 123.45
Neutral culture
A neutral culture is used to specify a culture that has only a language associated with it without any country/region. For example, en
is the neutral name for English culture. We can use en
without worrying about whether we're serving our content to the US, UK, or any other English-speaking country.
Specific culture
A specific culture specifies both the language and the country/region associated with it. For example, fr-FR
specifies the French culture that is specific to France.
The culture categories follow a hierarchy. The neutral culture is the parent of the specific culture and the invariant culture is the parent of the neutral culture.
Current culture vs current UI culture
Somewhat confusingly, there are two separate CultureInfo
objects that represent the active locale in .NET.
CultureInfo.CurrentCulture
—the current culture of a thread in a .NET application is used to represent the default user locale of the system (we'll discuss threading and culture resolution in a moment); the current culture is used to determine casing conventions, string comparison conventions, and date, time, number, and currency value formatting conventions.
CultureInfo.CurrentUICulture
—the current UI culture (note the UI part) of a thread in a .NET application is used by the Resource Manager to look up culture-specific resources at run time.
When a new application thread is started, its current culture and current UI culture are defined by the current system culture, and not by the current thread culture. Refer to the code snippet below:
using System; using System.Globalization; using System.Threading; namespace ConsoleApp { public class Program { static Random random = new Random(); public static void Main() { Console.OutputEncoding = System.Text.Encoding.Unicode; if (Thread .CurrentThread .CurrentCulture .Name != "hi-IN" ) { Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("hi-IN"); Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture("hi-IN"); } else { Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US"); Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture("en-US"); } ThreadProcess(); Thread workerThread = new Thread(ThreadProcess); workerThread.Name = "WorkerThread"; workerThread.Start(); } private static void DisplayThreadDetails() { Console.WriteLine($"\nCurrent Thread Name: " + $"'{Thread.CurrentThread.Name}'"); Console.WriteLine($"Current Thread Culture/UI Culture:" + $"{Thread.CurrentThread.CurrentCulture.Name}/" + $"{Thread.CurrentThread.CurrentUICulture.Name}"); } private static void DisplayValues() { Console.WriteLine("Some currency values:"); for (int i = 0; i < 3; i++) { Console.WriteLine("\t{0:C2}", random.NextDouble() * 10); } } private static void ThreadProcess() { DisplayThreadDetails(); DisplayValues(); } } }
We will set the current culture and current UI culture of the application thread to the hi-IN culture. We will display 3 random currency values and start a new thread, which in turn will display three new random currency values. As you can see in the output below, the new thread does not reflect the formatting conventions of the hi-IN
culture. Instead, it shows the en-US
culture, which is different from the output of the main application thread.
How is the current culture determined?
The current culture is a per-thread property, which means each thread has its own current culture.
The current culture of a thread is determined using the following mechanism:
- The thread retrieves the value from the
DefaultThreadCurrentCulture
property; the latter is defined in theCultureInfo
class and is used to set the default culture for threads in the current application domain; the thread culture is set using this property only if the value is not zero. - If the thread is from a thread pool that is executing a task-based async operation, then its culture is determined by the culture of the calling thread.
- By calling the
GetUserDefaultLocaleName
function on Windows or the uloc_getDefault function from ICU, which currently calls the POSIX setlocale function with category LC_MESSAGES, on Unix-like systems.
What will happen if the application starts multiple threads and we set a specific culture different from the system-installed culture or the user's preferred culture? Well, the current culture for those threads will be set to the culture that is returned by the GetUserDefaultLocaleName
function—unless we assign a culture to the DefaultThreadCurrentCulture
property in the application domain in which the thread is executing.
The CultureInfo
class defines the static CurrentCulture
property that can be used to get and set the current culture of a thread:
public void GetSetCurrentCulture() { // Get the current culture CultureInfo culture = CultureInfo.CurrentCulture; Console.WriteLine($"The current culture is {culture.Name}"); // Set the current culture CultureInfo.CurrentCulture = new CultureInfo("fr-FR"); }
How is the current UI culture determined?
Similar to the current culture, the current UI culture is also a per-thread property.
The current UI culture of a thread is determined using the following mechanism:
- The thread retrieves the value from the
DefaultThreadCurrentUICulture
property; this property is defined in theCultureInfo
class and is used to set the default UI culture for threads in the current application domain; the thread culture is set using this property only if the value is not zero. - If the thread is from a thread pool that is executing a task based async operation, then its UI culture is determined by the UI culture of the calling thread.
- The thread will call the Windows
GetUserDefaultUILanguage
function, which will return the language identifier for the user UI language for the current user; If the current user has not set any language, then this method will return the preferred language set for the system.
The CultureInfo
class defines the static CurrentUICulture
property that can be used to get and set the current UI culture of a thread:
public void GetSetCurrentUICulture() { // Get the current UI culture CultureInfo uiCulture = CultureInfo.CurrentUICulture; Console.WriteLine($"The current UI culture is {uiCulture.Name}"); // Set the current UI culture CultureInfo.CurrentUICulture = new CultureInfo("fr-FR"); }
How does CultureInfo work with resource files?
A resource file is used to make resources like strings, images, or object data available to the application. There are multiple ways of creating a resource file for a .NET application such as .txt
, .restext
, .resx
, or .resources
, but the discussion about it is beyond the scope of this article.
The most commonly used format for resource files in a .NET app is .resx
. It is an XML file that can store strings, binary data such as images, icons, and audio clips, and programmatic objects. The value of the current UI culture is used by the Resource Manager to look up culture-specific resources at run time. During the compilation, the default .resx
file gets compiled into the assembly it is part of. The localized resource file (e.g. app.fr.resx) gets compiled to its directory, based on the culture identifier, which is "fr" in this case, into an assembly called <AssemblyName>.resources.dll
.
How to get a localized weekday based on culture?
Let us assume we want to fetch the current weekday based on the culture of the application. If the culture is set to English, the weekday should be displayed in the English language; if the culture is set to French, the weekday should be displayed in the French language, etc.:
public void getLocalDayOfWeek() { CultureInfo frenchCulture = new CultureInfo("fr-FR"); string dayOfWeekFrench = frenchCulture.DateTimeFormat .GetDayName(DateTime.Today.DayOfWeek); Console.WriteLine($"The day of the week is" + $" {dayOfWeekFrench}"); CultureInfo englishCulture = new CultureInfo("en-US"); string dayOfWeekEnglish = englishCulture.DateTimeFormat .GetDayName(DateTime.Today.DayOfWeek); Console.WriteLine($"The day of the week is" + $" {dayOfWeekEnglish}"); }
Output:
- The weekday is "mercredi"
- The weekday is "Wednesday"
Smarter substring checking
The CultureInfo
class can also help us find the occurrence of a string within another string of the same language but with a different letter case. Refer to the code below:
public void CheckSubString() { string mainString = "WELCOME TO PHRASE"; string subString = "phrase"; CultureInfo englishCulture = new CultureInfo("en-US"); bool isStringfound = englishCulture.CompareInfo .IndexOf( mainString, subString, CompareOptions.IgnoreCase) >= 0; Console.WriteLine(isStringfound); }
Output:
- true
Case insensitivity is a language-dependent feature. For example, the English language uses the characters "I" and "i" for the upper and lower case versions of the ninth letter of the alphabet, whereas the Turkish language uses these characters for the eleventh and twelfth letters of its alphabet. The Turkish upper-case version of "i" is the unfamiliar character "İ". Thus, the strings "tin" and "TIN" are the same words in English but different words in Turkish.
Therefore, to correctly compare two strings of different letter cases, we should know the language of the text.
Wrapping things up
In this guide, we provided an overview of the CultureInfo
class and its application to internationalizing .NET applications. We learned about the current culture and current UI culture of an app as well as how they are determined during thread execution. We also had a look at how CultureInfo
works with the resources file of an app. Last but not least, we explored the practical use of CultureInfo
in fetching a localized weekday, based on culture, and doing a smart substring check.
🔗 Resource » Check out our tutorial on how to format a TimeSpan in a culture-aware manner for more details on internationalizing .NET apps.
Once your app is ready for localization, a localization solution, such as Phrase, can help you manage your localization projects easily and in the most efficient way possible.
The Phrase Localization Platform features a flexible API and CLI, and a beautiful web platform for your translators. With GitHub, GitLab, and Bitbucket sync, Phrase does the heavy lifting in your localization pipeline, so you can stay focused on the code you love.
Check out all Phrase features for developers and see for yourself how it can help you take your apps global.