C# CultureInfo
last modified October 18, 2026
In this article we show how to globalize applications in C# with
CultureInfo.
Globalization is the process of designing the application in such a way that it can be used by users from across the globe (for multiple cultures). Localization is the process of customizing the application to a specific culture: translating the texts, and showing numbers, dates, and currencies the way the users expect.
Types of cultures
A neutral culture is a culture that is associated with a language but not
with a country or region. A specific culture is a culture that is
associated with both a language and a country or region. For example,
fr is the name for the neutral French culture, while
fr-FR is the name for the French culture in France. Formatting
numbers, currencies, and dates needs a specific culture, since they differ even among
countries with the same language (en-US and en-GB write dates in a different order).
The invariant culture is culture-insensitive. It is associated with the
English language (for historical reasons) but not with any country or region. It
has the empty string as its name. CultureInfo.InvariantCulture retrieves
it. The invariant culture is used for storing and exchanging data in a way that is
not tied to any language, and does not change with the machine's settings or the
version of the operating system.
C# CultureInfo
CultureInfo provides information about a specific culture. The
information includes the names of the culture, the writing system, the calendar
used, the sort order of strings, and formatting for dates and numbers.
using System.Globalization;
The CultureInfo is part of the System.Globalization
namespace.
Two cultures are associated with each thread:
CultureInfo.CurrentCulture- the culture for formatting and parsing numbers, dates, currencies, and for sorting and comparing stringsCultureInfo.CurrentUICulture- the culture for looking up translated resources, such as the texts in resource files
By default, both come from the settings of the operating system (on Linux and macOS,
from the LANG and LC_* environment variables). A server
application must not rely on them, as the settings of the server are probably not
the culture of the user. In ASP.NET Core, the localization middleware sets the
culture of each request.
N format or the names of
months, can differ between platforms and library versions. The examples were run on
Linux with ICU. A project can switch the globalization off with
<InvariantGlobalization>true</InvariantGlobalization>,
for instance in small containers; then all cultures behave like the invariant
culture, and creating a culture other than the invariant one fails or returns data
of the invariant culture.
C# current culture
The CultureInfo.CurrentCulture property gets or sets the culture of the
current thread. CultureInfo.DefaultThreadCurrentCulture gets or sets
the default culture for the threads of the application that have not set their own.
using System.Globalization;
double val = 1235.56;
Console.WriteLine($"Current culture: {CultureInfo.CurrentCulture.Name}");
Console.WriteLine($"Current UI culture: {CultureInfo.CurrentUICulture.Name}");
Console.WriteLine(val);
// the default for all threads that have not set their own culture
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("fr-FR");
Console.WriteLine($"After the default was set: {CultureInfo.CurrentCulture.Name}, {val}");
var t = new Thread(() => Console.WriteLine($"thread: {CultureInfo.CurrentCulture.Name}"));
t.Start();
t.Join();
// the culture of the current thread
CultureInfo.CurrentCulture = new CultureInfo("de-DE");
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("ja-JP");
Console.WriteLine($"Current culture: {CultureInfo.CurrentCulture.Name}, {val}");
Console.WriteLine(val.ToString(CultureInfo.InvariantCulture));
// the explicitly set culture flows to the threads and tasks we start
var t2 = new Thread(() => Console.WriteLine($"thread 2: {CultureInfo.CurrentCulture.Name}"));
t2.Start();
t2.Join();
await Task.Run(() => Console.WriteLine($"task: {CultureInfo.CurrentCulture.Name}"));
// unless the execution context does not flow
var t3 = new Thread(() => Console.WriteLine($"thread 3: {CultureInfo.CurrentCulture.Name}"));
t3.UnsafeStart();
t3.Join();
The example prints a value in several cultures and shows how the culture is passed to other threads.
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("fr-FR");
We set the default culture. The main thread has not set its own culture yet, so it uses the default, and so does the new thread.
CultureInfo.CurrentCulture = new CultureInfo("de-DE");
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("ja-JP");
Now the main thread has an explicit culture, de-DE. We change the default as well.
The explicit culture of a thread wins over the default, and it flows with
the execution context to the threads, tasks, and asynchronous continuations that the
thread starts. The default is used only by threads that have no culture of their own,
as the one started with UnsafeStart, which does not flow the context.
$ dotnet run Current culture: en-US Current UI culture: en-US 1235.56 After the default was set: fr-FR, 1235,56 thread: fr-FR Current culture: de-DE, 1235,56 1235.56 thread 2: de-DE task: de-DE thread 3: ja-JP
The best practice is to set the default culture at the start of the program, before any threads are started, or to pass a culture explicitly to the methods that format and parse. The explicit argument is always the safest: the result does not depend on a global setting that other code can change.
C# list cultures
The CultureInfo.GetCultures method gets the list of supported cultures
filtered by the specified culture types. AllCultures includes all, the
SpecificCultures filter returns the specific ones and
NeutralCultures the neutral ones.
using System.Globalization;
Console.OutputEncoding = System.Text.Encoding.UTF8;
CultureInfo[] all = CultureInfo.GetCultures(CultureTypes.AllCultures);
Console.WriteLine($"{all.Length} cultures");
Console.WriteLine($"{CultureInfo.GetCultures(CultureTypes.SpecificCultures).Length} specific cultures");
Console.WriteLine($"{CultureInfo.GetCultures(CultureTypes.NeutralCultures).Length} neutral cultures");
Console.WriteLine();
Console.WriteLine("{0,-10}{1,-5}{2,-35}{3,-25}", "Name", "ISO", "Display name", "Native name");
foreach (CultureInfo ci in CultureInfo.GetCultures(CultureTypes.SpecificCultures).Where(c => c.TwoLetterISOLanguageName is "sk" or "cs" or "de" or "ja" or "ar").Take(12))
{
Console.WriteLine("{0,-10}{1,-5}{2,-35}{3,-25}", ci.Name, ci.TwoLetterISOLanguageName, ci.DisplayName, ci.NativeName);
}
// neutral and specific cultures
var fr = new CultureInfo("fr");
var frFr = new CultureInfo("fr-FR");
Console.WriteLine($"{fr.Name}: neutral={fr.IsNeutralCulture}, parent of fr-FR: {frFr.Parent.Name}");
Console.WriteLine(CultureInfo.InvariantCulture.Name == "");
Console.WriteLine(CultureInfo.CreateSpecificCulture("fr").Name);
The example counts the cultures and lists those of a few languages.
Console.OutputEncoding = System.Text.Encoding.UTF8;
To show the native names of the cultures, we set the output encoding to UTF-8. In addition, the terminal must have a font that can display all these scripts.
var fr = new CultureInfo("fr");
var frFr = new CultureInfo("fr-FR");
Console.WriteLine($"{fr.Name}: neutral={fr.IsNeutralCulture}, parent of fr-FR: {frFr.Parent.Name}");
Console.WriteLine(CultureInfo.CreateSpecificCulture("fr").Name);
Every specific culture has a neutral parent. The CreateSpecificCulture
method turns a neutral culture into a specific one, here fr-FR.
$ dotnet run 787 cultures 547 specific cultures 240 neutral cultures Name ISO Display name Native name ar-001 ar Arabic (world) العربية (العالم) ar-AE ar Arabic (United Arab Emirates) العربية (الإمارات العربية المتحدة) ar-BH ar Arabic (Bahrain) العربية (البحرين) ... fr: neutral=True, parent of fr-FR: fr True fr-FR
The number of cultures depends on the version of .NET and the ICU data.
C# invariant culture
The invariant culture is independent of the machine and of the user. We use it when we persist or transmit data: files, network protocols, databases, configuration. Saving dates and numbers in a culture-independent way allows us to read them later on any machine.
using System.Globalization;
// the invariant culture: stable, independent of the machine; use it for data
CultureInfo.CurrentCulture = new CultureInfo("sk-SK");
DateTime[] dates = [new(2019, 10, 9), new(2020, 1, 2)];
double price = 1234.5;
string line = string.Format(CultureInfo.InvariantCulture, "{0:d}|{1:d}|{2}", dates[0], dates[1], price);
File.WriteAllText("data.txt", line);
Console.WriteLine(line); // what is stored
Console.WriteLine($"{dates[0]:d} {price}"); // what the user in Slovakia sees
// reading it back
string[] parts = File.ReadAllText("data.txt").Split('|');
DateTime d = DateTime.Parse(parts[0], CultureInfo.InvariantCulture);
double p = double.Parse(parts[2], CultureInfo.InvariantCulture);
Console.WriteLine($"{d:D}");
Console.WriteLine(p + 1);
// parsing with the wrong culture fails or gives a wrong result
Console.WriteLine(double.TryParse(parts[2], out double wrong) ? wrong : "cannot parse with sk-SK");
Console.WriteLine(DateTime.TryParse("10/09/2019", out var wrongDate) ? wrongDate.ToString("d MMMM yyyy") : "invalid");
Console.WriteLine(DateTime.Parse("10/09/2019", new CultureInfo("en-US")).ToString("d MMMM yyyy"));
The example writes data with the invariant culture while the current culture is Slovak, and reads it back.
string line = string.Format(CultureInfo.InvariantCulture, "{0:d}|{1:d}|{2}", dates[0], dates[1], price);
We pass CultureInfo.InvariantCulture to string.Format. The
stored line has the date as month/day/year and the decimal point.
DateTime d = DateTime.Parse(parts[0], CultureInfo.InvariantCulture); double p = double.Parse(parts[2], CultureInfo.InvariantCulture);
We pass the invariant culture to parse as well, so the data is understood independently of the culture of the reader.
Console.WriteLine(double.TryParse(parts[2], out double wrong) ? wrong : "cannot parse with sk-SK");
Console.WriteLine(DateTime.TryParse("10/09/2019", out var wrongDate) ? wrongDate.ToString("d MMMM yyyy") : "invalid");
Parsing with the wrong culture fails or, worse, gives a wrong result. "1234.5" is not a number in Slovakia, where the decimal separator is a comma. And "10/09/2019" is read as the 10th of September, not as the 9th of October, which the same text means to a user in the United States. Such bugs often stay hidden for a long time, because they appear only on machines with a different configuration.
$ dotnet run 10/09/2019|01/02/2020|1234.5 9. 10. 2019 1234,5 streda 9. októbra 2019 1235,5 cannot parse with sk-SK 10 septembra 2019 9 októbra 2019
Note that the output of p + 1, 1235,5, is a number printed with the
Slovak culture, while the number itself stays the same.
C# CultureInfo sorting
Sorting strings is culture specific. Every language orders its letters in its own way. A culture-aware comparer puts the accented letters and the digraphs in the right places. An ordinal comparison, in contrast, compares the numeric values of the characters.
using System.Globalization;
Console.OutputEncoding = System.Text.Encoding.UTF8;
string[] words = ["čaj", "auto", "drevo", "cibuľa", "čučoriedka", "banán", "čerešňa", "červený", "čierny", "cesnak", "Cukor", "chlieb", "hrach"];
var sk = new CultureInfo("sk-SK");
var inv = CultureInfo.InvariantCulture;
var sorted = words.ToList();
sorted.Sort(StringComparer.Create(sk, ignoreCase: false));
Console.WriteLine("sk-SK: " + string.Join(", ", sorted));
sorted.Sort(StringComparer.Create(inv, ignoreCase: false));
Console.WriteLine("invariant: " + string.Join(", ", sorted));
sorted.Sort(StringComparer.Ordinal);
Console.WriteLine("ordinal: " + string.Join(", ", sorted));
// string comparison and CompareInfo
Console.WriteLine(string.Compare("cesnak", "čaj", sk, CompareOptions.None));
Console.WriteLine(string.Compare("cesnak", "čaj", sk, CompareOptions.IgnoreNonSpace));
Console.WriteLine(string.Equals("čaj", "caj", StringComparison.CurrentCultureIgnoreCase));
CultureInfo.CurrentCulture = sk;
Console.WriteLine(string.Equals("čaj", "caj", StringComparison.CurrentCultureIgnoreCase));
Console.WriteLine(sk.CompareInfo.Compare("čaj", "CAJ", CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace));
Console.WriteLine(sk.CompareInfo.IndexOf("Kôň a čaj", "CAJ", CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace));
The example sorts a list of Slovak words with three comparers and compares strings.
sorted.Sort(StringComparer.Create(sk, ignoreCase: false));
The comparer created for the Slovak culture follows the Slovak alphabet, in which the letter "č" comes right after "c", and "ch" is a separate letter that follows "h". Hence "chlieb" is the last word. Also, the capital letter "Cukor" is sorted among the words by its letters, not before all lower-case words.
sorted.Sort(StringComparer.Ordinal);
The ordinal comparer orders by the code of the characters: all upper-case letters first and all accented letters at the end, because "č" has a higher code than "z". This is fast and deterministic, and the right choice for identifiers and keys, but not for text shown to users.
Console.WriteLine(string.Compare("cesnak", "čaj", sk, CompareOptions.IgnoreNonSpace));
sk.CompareInfo.IndexOf("Kôň a čaj", "CAJ", CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace)
The CompareOptions loosen the comparison: ignore the case, accents, or
the width of characters. The CompareInfo object of a culture has
comparison and search methods which take these options.
$ dotnet run sk-SK: auto, banán, cesnak, cibuľa, Cukor, čaj, čerešňa, červený, čierny, čučoriedka, drevo, hrach, chlieb invariant: auto, banán, čaj, čerešňa, červený, cesnak, chlieb, cibuľa, čierny, čučoriedka, Cukor, drevo, hrach ordinal: Cukor, auto, banán, cesnak, chlieb, cibuľa, drevo, hrach, čaj, čerešňa, červený, čierny, čučoriedka -1 -1 False False 1 -1
The invariant culture sorts "č" as a variant of "c", so "čaj" comes before "cesnak"; only the Slovak culture knows the rule of the alphabet. In the last lines, the accent-insensitive search gives -1: the text "Kôň a čaj" does not contain "caj" with the same letter in Slovak, because in the Slovak alphabet "č" is a different letter and not an accented "c". This is the difference between linguistic and simple comparison.
NumberFormatInfo
The NumberFormatInfo object provides culture-specific information for
formatting and parsing numeric values. It is available from the
NumberFormat property of a culture.
using System.Globalization;
Console.OutputEncoding = System.Text.Encoding.UTF8;
foreach (var name in new[] { "en-US", "sk-SK", "de-CH", "fr-FR", "hi-IN", "fa-IR", "ja-JP" })
{
var nfi = new CultureInfo(name).NumberFormat;
Console.WriteLine($"{name}: decimal='{nfi.NumberDecimalSeparator}' group='{nfi.NumberGroupSeparator}' " +
$"currency='{nfi.CurrencySymbol}' percent='{nfi.PercentSymbol}' negative='{nfi.NegativeSign}'");
}
Console.WriteLine(new CultureInfo("fr-FR").NumberFormat.NumberGroupSizes.Length);
Console.WriteLine(string.Join(",", new CultureInfo("hi-IN").NumberFormat.NumberGroupSizes));
The example shows the decimal and group separators, the currency symbol, the percent symbol, and the negative sign for several cultures.
var nfi = new CultureInfo(name).NumberFormat;
The NumberFormatInfo properties include NumberDecimalSeparator,
NumberGroupSeparator, NumberGroupSizes,
CurrencySymbol, CurrencyDecimalDigits,
PercentSymbol, and NegativeSign.
$ dotnet run en-US: decimal='.' group=',' currency='$' percent='%' negative='-' sk-SK: decimal=',' group=' ' currency='€' percent='%' negative='-' de-CH: decimal='.' group='’' currency='CHF' percent='%' negative='-' fr-FR: decimal=',' group=' ' currency='€' percent='%' negative='-' hi-IN: decimal='.' group=',' currency='₹' percent='%' negative='-' fa-IR: decimal='٫' group='٬' currency='ریال' percent='٪' negative='−' ja-JP: decimal='.' group=',' currency='¥' percent='%' negative='-' 1 3,2
The last two lines show the group sizes. Most cultures group the digits by three, but the Indian culture uses a group of three followed by groups of two (1,21,56,320). The group separator of the French and Slovak cultures is a no-break space, so a number is not split across lines. The Persian culture uses its own decimal and group characters, a different percent sign and a real minus sign.
C# CultureInfo numbers, currency and percent
Cultures differ in the decimal separator (a point in the United Kingdom, Japan, and Israel, a comma in Slovakia, France, and Germany), in the thousands separator (a space, a period, an apostrophe, a comma), in the grouping, in the position and spacing of the currency symbol, and of the percent sign.
using System.Globalization;
Console.OutputEncoding = System.Text.Encoding.UTF8;
decimal val = 12_156_320.54m;
int whole = 12_156_320;
double pct = 0.68;
foreach (var name in new[] { "en-US", "sk-SK", "de-CH", "hi-IN", "es-ES", "ja-JP", "fa-IR" })
{
var ci = new CultureInfo(name);
Console.WriteLine($"{name,-6} | {whole.ToString("N0", ci),-14} | {val.ToString("N2", ci),-16} | {val.ToString("C2", ci),-18} | {pct.ToString("P0", ci)}");
}
The example prints the same three values in seven cultures: an integer with the
N0 format, a decimal with N2 and C2, and a
double as a percentage with P0. We specify the number of decimals
explicitly, because the default differs between platforms.
$ dotnet run en-US | 12,156,320 | 12,156,320.54 | $12,156,320.54 | 68% sk-SK | 12 156 320 | 12 156 320,54 | 12 156 320,54 € | 68 % de-CH | 12’156’320 | 12’156’320.54 | CHF 12’156’320.54 | 68% hi-IN | 1,21,56,320 | 1,21,56,320.54 | ₹1,21,56,320.54 | 68% es-ES | 12.156.320 | 12.156.320,54 | 12.156.320,54 € | 68 % ja-JP | 12,156,320 | 12,156,320.54 | ¥12,156,320.54 | 68% fa-IR | 12٬156٬320 | 12٬156٬320٫54 | ریال12٬156٬320٫54 | 68٪
The currency symbol comes from the culture, but the amount is just a number.
A culture does not convert 12 million euros to dollars; showing "$" in front of a
value in euros is a bug. When the currency is known, build the text from the
currency code and the amount, with the culture used only for the number format, or
take the symbol from the RegionInfo.
C# CultureInfo dates, week and names
The DateTimeFormat property (a DateTimeFormatInfo) has the
patterns for dates and times, the names of days and months, and the rules for the
week: the first day of the week and the way to find the first week of the year.
using System.Globalization;
Console.OutputEncoding = System.Text.Encoding.UTF8;
var now = new DateTime(2026, 10, 18, 22, 31, 7);
foreach (var name in new[] { "en-US", "en-GB", "sk-SK", "de-DE", "ja-JP", "ar-SA" })
{
var ci = new CultureInfo(name);
var dtf = ci.DateTimeFormat;
Console.WriteLine($"{name}: first day={dtf.FirstDayOfWeek}, week rule={dtf.CalendarWeekRule}, short date='{dtf.ShortDatePattern}', long time='{dtf.LongTimePattern}'");
}
var sk = new CultureInfo("sk-SK");
Console.WriteLine(now.ToString(sk.DateTimeFormat.FullDateTimePattern, sk));
Console.WriteLine(now.ToString(sk.DateTimeFormat.LongDatePattern, sk));
Console.WriteLine(now.ToString(sk.DateTimeFormat.ShortTimePattern, sk));
Console.WriteLine(string.Join(", ", sk.DateTimeFormat.DayNames));
Console.WriteLine(string.Join(", ", sk.DateTimeFormat.AbbreviatedDayNames));
var hu = new CultureInfo("hu-HU");
Console.WriteLine(string.Join(", ", hu.DateTimeFormat.MonthNames.Take(12)));
Console.WriteLine(string.Join(" ", hu.DateTimeFormat.AbbreviatedMonthNames.Take(12)));
Console.WriteLine(sk.DateTimeFormat.GetMonthName(10));
Console.WriteLine(sk.DateTimeFormat.GetAbbreviatedDayName(DayOfWeek.Friday));
The example prints the week rules and the date patterns of several cultures, and then the names of days and months in Slovak and Hungarian.
Console.WriteLine($"{name}: first day={dtf.FirstDayOfWeek}, week rule={dtf.CalendarWeekRule}, ...");
In the United States, the week starts on Sunday, and the first week of the year is the one that contains January 1. In most European countries, the week starts on Monday, and the first week is the first one that has at least four days in the new year (the ISO 8601 rule). Saturday is the first day of the week in Iran (fa-IR), and in several Arabic countries, such as Egypt and the United Arab Emirates.
Console.WriteLine(now.ToString(sk.DateTimeFormat.FullDateTimePattern, sk));
The patterns, such as FullDateTimePattern, LongDatePattern,
and ShortTimePattern, are format strings that can be passed to
ToString. The standard format specifiers like "F", "D", and "t" select
them automatically.
Console.WriteLine(string.Join(", ", sk.DateTimeFormat.DayNames));
Console.WriteLine(string.Join(" ", hu.DateTimeFormat.AbbreviatedMonthNames.Take(12)));
The DayNames and MonthNames properties return the full
names, and AbbreviatedDayNames and AbbreviatedMonthNames
the short names. The day names start with Sunday, whatever the first day of the week
is, so they are indexed with (int)DayOfWeek. The month array has
thirteen entries (the last one is empty), which is why we take twelve.
$ dotnet run en-US: first day=Sunday, week rule=FirstDay, short date='M/d/yyyy', long time='h:mm:ss tt' en-GB: first day=Monday, week rule=FirstFourDayWeek, short date='dd/MM/yyyy', long time='HH:mm:ss' sk-SK: first day=Monday, week rule=FirstFourDayWeek, short date='d. M. yyyy', long time='H:mm:ss' de-DE: first day=Monday, week rule=FirstFourDayWeek, short date='dd.MM.yyyy', long time='HH:mm:ss' ja-JP: first day=Sunday, week rule=FirstDay, short date='yyyy/MM/dd', long time='H:mm:ss' ar-SA: first day=Sunday, week rule=FirstDay, short date='d/M/yyyy g', long time='h:mm:ss tt' nedeľa 18. októbra 2026 22:31:07 nedeľa 18. októbra 2026 22:31 nedeľa, pondelok, utorok, streda, štvrtok, piatok, sobota ne, po, ut, st, št, pi, so január, február, március, április, május, június, július, augusztus, szeptember, október, november, december jan. febr. márc. ápr. máj. jún. júl. aug. szept. okt. nov. dec. október pi
CultureInfo Calendar
A calendar is a system of organizing datetime units. Cultures use different
calendars, and some cultures can use multiple calendars. The Calendar
property is the default calendar of the culture, and OptionalCalendars
lists the others.
using System.Globalization;
Console.OutputEncoding = System.Text.Encoding.UTF8;
var now = new DateTime(2026, 10, 18, 13, 35, 18);
var perCi = new CultureInfo("fa-IR");
Console.WriteLine($"Default calendar: {perCi.Calendar}");
foreach (var oc in perCi.OptionalCalendars)
{
perCi.DateTimeFormat.Calendar = oc;
Console.WriteLine(now.ToString("F", perCi));
}
// the calendar of a culture is a property of the culture, the DateTime stays Gregorian
Console.WriteLine(new CultureInfo("th-TH").Calendar.GetYear(now));
Console.WriteLine(new CultureInfo("ja-JP").DateTimeFormat.Calendar);
Console.WriteLine(now.ToString("D", new CultureInfo("th-TH")));
The Persian culture uses the Persian calendar as the default one, and the Gregorian and Hijri calendars as optional calendars. We display one datetime in all supported Persian calendars, and then use the Thai and Japanese cultures.
foreach (var oc in perCi.OptionalCalendars)
{
perCi.DateTimeFormat.Calendar = oc;
Console.WriteLine(now.ToString("F", perCi));
}
We go through the array of supported calendars, set the
DateTimeFormat.Calendar to the selected one, and print the datetime. A
DateTime value is always Gregorian internally; the calendar of the
culture changes only how it is shown and the calendar-specific methods.
$ dotnet run Default calendar: System.Globalization.PersianCalendar 1405 مهر 26, یکشنبه 13:35:18 2026 اکتبر 18, یکشنبه 13:35:18 1448 جمادیالاول 7, یکشنبه 13:35:18 2569 System.Globalization.GregorianCalendar วันอาทิตย์ที่ 18 ตุลาคม พ.ศ. 2569
The Thai culture shows the Buddhist era year 2569, which is 543 years ahead of the Gregorian year. See the date and time tutorial for more about calendars.
C# RegionInfo and TextInfo
The RegionInfo class describes a country or region: its names, the ISO
codes, the currency, and the use of the metric system. The TextInfo
class of a culture knows the casing rules and the direction of the writing.
using System.Globalization;
Console.OutputEncoding = System.Text.Encoding.UTF8;
// RegionInfo describes a country
foreach (var name in new[] { "sk-SK", "en-US", "ja-JP", "de-CH" })
{
var ri = new RegionInfo(name);
Console.WriteLine($"{ri.Name}: {ri.EnglishName}, {ri.NativeName}, currency {ri.ISOCurrencySymbol} ({ri.CurrencySymbol}), metric: {ri.IsMetric}");
}
// the region of the current culture
Console.WriteLine(new RegionInfo(CultureInfo.CurrentCulture.Name).TwoLetterISORegionName);
// text info: casing and the writing direction
var ar = new CultureInfo("ar-SA");
var en = new CultureInfo("en-US");
Console.WriteLine($"{ar.TextInfo.IsRightToLeft} {en.TextInfo.IsRightToLeft}");
Console.WriteLine(en.TextInfo.ToTitleCase("an old falcon"));
Console.WriteLine(new CultureInfo("tr-TR").TextInfo.ToUpper("istanbul"));
Console.WriteLine(en.TextInfo.ToUpper("istanbul"));
Console.WriteLine(en.TextInfo.ListSeparator + new CultureInfo("sk-SK").TextInfo.ListSeparator);
The example prints region information and shows text properties.
Console.WriteLine(new CultureInfo("tr-TR").TextInfo.ToUpper("istanbul"));
The casing depends on the culture. The Turkish culture uppercases "i" to the dotted
"İ", while the English one gives "I". Use ToUpperInvariant for data,
such as keys and protocol keywords. The IsRightToLeft property tells
whether the writing goes from right to left, which the user interface must respect.
$ dotnet run SK: Slovakia, Slovensko, currency EUR (€), metric: True US: United States, United States, currency USD ($), metric: False JP: Japan, 日本, currency JPY (¥), metric: True CH: Switzerland, Schweiz, currency CHF (CHF), metric: True US True False An Old Falcon İSTANBUL ISTANBUL ,;
The last line shows the list separators of the United States (comma) and Slovakia (semicolon), which matters for CSV files created by spreadsheet programs: in a Slovak, German, or French installation of a spreadsheet, a "CSV" file uses semicolons, since the comma is the decimal separator there.
C# CultureInfo errors and customization
An invalid culture name throws a CultureNotFoundException. The objects
returned by CultureInfo.GetCultureInfo are cached and read-only. To change
a setting, we clone a culture.
using System.Globalization;
// an unknown culture name
try
{
var ci = new CultureInfo("xx-YY");
Console.WriteLine($"{ci.Name} {ci.DisplayName}");
}
catch (CultureNotFoundException e)
{
Console.WriteLine(e.Message);
}
try
{
new CultureInfo("not a culture");
}
catch (CultureNotFoundException e)
{
Console.WriteLine(e.InvalidCultureName);
}
// CultureInfo.GetCultureInfo caches and returns read-only objects
var cached = CultureInfo.GetCultureInfo("sk-SK");
Console.WriteLine(cached.IsReadOnly);
try
{
cached.NumberFormat.NumberDecimalSeparator = ";";
}
catch (InvalidOperationException e)
{
Console.WriteLine("read-only: " + e.Message);
}
// an own copy can be customized
var custom = (CultureInfo)new CultureInfo("en-US").Clone();
custom.NumberFormat.CurrencySymbol = "EUR ";
custom.NumberFormat.CurrencyDecimalDigits = 0;
Console.WriteLine(1234.5.ToString("C", custom));
// culture name comparisons are case-insensitive, and codes use a hyphen
Console.WriteLine(new CultureInfo("SK-sk").Name);
Console.WriteLine(CultureInfo.GetCultureInfo("sk_SK", predefinedOnly: false).Name);
The program tries invalid names, a read-only culture, and a customized copy.
var ci = new CultureInfo("xx-YY");
Not every unknown name throws an exception: a well-formed name, such as "xx-YY", is
accepted by the ICU-based implementation, and creates a culture without any data,
which formats like the invariant culture. Names that are not well formed, like "not
a culture", throw the exception, and the InvalidCultureName property
tells which name was wrong. Don't depend on the exception to validate culture names;
compare with the list of GetCultures.
var custom = (CultureInfo)new CultureInfo("en-US").Clone();
custom.NumberFormat.CurrencySymbol = "EUR ";
custom.NumberFormat.CurrencyDecimalDigits = 0;
A cloned culture is writable. We change the currency symbol and the number of
decimals of the currency format. Customized cultures are useful for exotic formats,
but a simple NumberFormatInfo object is often enough.
$ dotnet run xx-YY xx (YY) not a culture True read-only: Instance is read-only. EUR 1,234 sk-SK sk_sk
Culture names are case-insensitive, and the names are normalized: "SK-sk" becomes "sk-SK". The last line shows that with the ICU implementation an underscore is accepted but not normalized, so the hyphen should be used.
Source
In this article we have worked with C# CultureInfo, which is used to
globalize C# applications.
Author
List all C# tutorials.