• Post category:IOS
  • Post comments:0 Comments
  • Post author:
  • Post published:16/09/2021
  • Post last modified:16/09/2021

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 DateTimeFormatInfoNumberFormatInfoCompareInfo, 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 the CultureInfo 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 the CultureInfo 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 States
  • en-GB—English, Great Britain
  • pt-BR—Portuguese, Brazil
  • pt-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, Uzbekistan
  • uz-Latn-UZ—Uzbek language using the Latin script, Uzbekistan
  • sr-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 the CultureInfo 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 CurrentCultureproperty 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 the CultureInfo 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.

If your app content is now ready for localization, check out Phrase, a software localization platform designed to streamline app localization end to end. Phrase features a flexible API and CLI, and a beautiful web platform for your translators to work together. Check out all of Phrase’s features and try it for 14 days for free.

Leave a Reply