Internationalization in C# — Complete Guide
In this tutorial, you will learn about Internationalization in C#. We cover key concepts, practical examples, and best practices to help you master this topic.
Hook
The world speaks many languages, and your applications should too. C# and .NET provide comprehensive support for building globally-aware applications through the System.Globalization namespace, resource files, and culture-aware formatting. Internationalization (i18n) lets you reach users worldwide.
Learning Path
graph LR A[Internationalization] --> B[Cultures] A --> C[Resource Files] B --> D[Formatting] B --> E[String Comparison] C --> F[Localization] style A fill:#4a90d9,color:#fff style B fill:#4a90d9,color:#fff style C fill:#4a90d9,color:#fff style D fill:#4a90d9,color:#fff style E fill:#4a90d9,color:#fff style F fill:#4a90d9,color:#fff
Understanding Cultures
A culture defines language, number formats, dates, and sorting rules.
using System;
using System.Globalization;
public static class CultureDemo
{
public static void ShowCultures()
{
// Get current culture
CultureInfo current = CultureInfo.CurrentCulture;
Console.WriteLine($"Current culture: {current.DisplayName}");
// List all cultures
CultureInfo[] cultures = CultureInfo.GetCultures(CultureTypes.AllCultures);
Console.WriteLine($"Total cultures: {cultures.Length}");
// Specific cultures
var us = new CultureInfo("en-US");
var de = new CultureInfo("de-DE");
var ja = new CultureInfo("ja-JP");
var ar = new CultureInfo("ar-SA");
Console.WriteLine($"\nUS: {us.DisplayName}");
Console.WriteLine($"Germany: {de.DisplayName}");
Console.WriteLine($"Japan: {ja.DisplayName}");
Console.WriteLine($"Saudi Arabia: {ar.DisplayName}");
}
}
Culture-Aware Formatting
Numbers, dates, and currencies should be formatted per culture.
public static class FormattingDemo
{
public static void ShowFormatting()
{
double amount = 1234.56;
DateTime date = new(2026, 6, 28, 14, 30, 0);
var cultures = new[] {
new CultureInfo("en-US"),
new CultureInfo("de-DE"),
new CultureInfo("fr-FR"),
new CultureInfo("ja-JP"),
new CultureInfo("ar-SA")
};
foreach (var culture in cultures)
{
Console.WriteLine($"\n{culture.DisplayName}:");
Console.WriteLine($" Number: {amount.ToString("N2", culture)}");
Console.WriteLine($" Currency: {amount.ToString("C2", culture)}");
Console.WriteLine($" Date: {date.ToString("D", culture)}");
Console.WriteLine($" Short date: {date.ToString("d", culture)}");
Console.WriteLine($" Percent: {(0.25).ToString("P1", culture)}");
}
}
}
Output:
English (United States):
Number: 1,234.56
Currency: $1,234.56
Date: Sunday, June 28, 2026
German (Germany):
Number: 1.234,56
Currency: 1.234,56 €
Date: Sonntag, 28. Juni 2026
Resource Files
Resource files (.resx) store locale-specific strings.
<!-- Resources.resx (default/English) -->
<data name="WelcomeMessage" xml:space="preserve">
<value>Welcome to our application!</value>
</data>
<data name="GoodbyeMessage" xml:space="preserve">
<value>Thank you for using our app.</value>
</data>
<!-- Resources.de.resx (German) -->
<data name="WelcomeMessage" xml:space="preserve">
<value>Willkommen in unserer Anwendung!</value>
</data>
// Strongly-typed resources (auto-generated)
Console.WriteLine(Resources.WelcomeMessage);
Console.WriteLine(Resources.GoodbyeMessage);
// Manual resource lookup
ResourceManager rm = new ResourceManager("MyApp.Resources", Assembly.GetExecutingAssembly());
string welcome = rm.GetString("WelcomeMessage", new CultureInfo("de-DE"))!;
Localizing ASP.NET Core
Use the localization middleware in web applications.
// Program.cs
builder.Services.AddLocalization(options =>
{
options.ResourcesPath = "Resources";
});
builder.Services.AddControllersWithViews()
.AddViewLocalization()
.AddDataAnnotationsLocalization();
var supportedCultures = new[]
{
new CultureInfo("en"),
new CultureInfo("de"),
new CultureInfo("fr"),
new CultureInfo("ja")
};
builder.Services.Configure<RequestLocalizationOptions>(options =>
{
options.DefaultRequestCulture = new RequestCulture("en");
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
options.RequestCultureProviders.Insert(0,
new QueryStringRequestCultureProvider());
});
var app = builder.Build();
app.UseRequestLocalization();
app.Run();
// In a controller
public class HomeController : Controller
{
private readonly IStringLocalizer<HomeController> _localizer;
public HomeController(IStringLocalizer<HomeController> localizer)
{
_localizer = localizer;
}
public IActionResult Index()
{
ViewBag.Message = _localizer["WelcomeMessage"];
return View();
}
}
String Comparison Across Cultures
String comparison varies by culture.
public static class StringComparisonDemo
{
public static void Compare()
{
string a = "Straße";
string b = "STRASSE";
// Invariant culture
bool equal1 = string.Equals(a, b, StringComparison.InvariantCultureIgnoreCase);
Console.WriteLine($"Invariant ignore case: {equal1}");
// Ordinal (byte-by-byte)
bool equal2 = string.Equals(a, b, StringComparison.OrdinalIgnoreCase);
Console.WriteLine($"Ordinal ignore case: {equal2}");
// German culture
var de = new CultureInfo("de-DE");
int result = string.Compare(a, b, de, CompareOptions.IgnoreCase);
Console.WriteLine($"German ignore case: {result == 0}");
// Sort order differs by culture
string[] words = { "apple", "Apfel", "uber", "über" };
Array.Sort(words, StringComparer.CurrentCulture);
Console.WriteLine($"Sorted (current culture): {string.Join(", ", words)}");
Array.Sort(words, StringComparer.Ordinal);
Console.WriteLine($"Sorted (ordinal): {string.Join(", ", words)}");
}
}
Pluralization and Gender
Handle plural forms and gender-specific text.
public static string GetItemsMessage(int count, IStringLocalizer localizer)
{
// Simple approach for English
if (count == 0)
return localizer["NoItems"];
if (count == 1)
return localizer["OneItem"];
return localizer["MultipleItems", count];
}
// For complex pluralization, use libraries like PluralizationService
// or ICU MessageFormat via NuGet packages
Common Mistakes
Ignoring culture in string formatting: Use
CultureInfo.InvariantCulturefor machine-readable output (Serialization, logs) and current culture for user-facing text.Hardcoding strings: Never hardcode user-facing strings. Always use resource files or localization services.
Assuming date/time formats: Different cultures use different date separators, order (MM/dd vs dd/MM), and calendars.
Not handling right-to-left languages: Arabic and Hebrew text require special layout considerations in UI.
Using string concatenation for localized messages: Word order changes across languages. Use format strings with placeholders instead.
Practice Questions
Create resource files for an application in English, French, and Japanese, and display localized messages based on the current thread culture.
Write a custom
IFormatProviderthat displays numbers in a specific format (e.g., engineering notation).Implement a date picker component that respects different calendar systems (Gregorian, Hijri, Japanese).
Challenge: Build a localization system that loads translations from a JSON file instead of .resx resources.
FAQ
Mini Project: Multi-Language Greeting App
Build a console application that greets the user in their preferred language.
using System;
using System.Globalization;
using System.Resources;
public static class GreetingApp
{
// Simulated resource strings (in production, use .resx files)
private static readonly Dictionary<string, Dictionary<string, string>> Translations = new()
{
["en"] = new()
{
["Greeting"] = "Hello!",
["TimeMessage"] = "Current time: {0}",
["DayMessage"] = "Today is {0}",
["Goodbye"] = "Goodbye!"
},
["de"] = new()
{
["Greeting"] = "Hallo!",
["TimeMessage"] = "Aktuelle Zeit: {0}",
["DayMessage"] = "Heute ist {0}",
["Goodbye"] = "Auf Wiedersehen!"
},
["fr"] = new()
{
["Greeting"] = "Bonjour!",
["TimeMessage"] = "Heure actuelle: {0}",
["DayMessage"] = "Aujourd'hui c'est {0}",
["Goodbye"] = "Au revoir!"
},
["ja"] = new()
{
["Greeting"] = "Konnichiwa!",
["TimeMessage"] = "Genzai no jikan: {0}",
["DayMessage"] = "Kyo wa {0}",
["Goodbye"] = "Sayonara!"
}
};
public static void Run(string languageCode)
{
if (!Translations.TryGetValue(languageCode, out var strings))
{
languageCode = "en";
strings = Translations["en"];
}
var culture = new CultureInfo(languageCode);
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
var now = DateTime.Now;
Console.WriteLine(strings["Greeting"]);
Console.WriteLine(strings["TimeMessage"], now.ToString("T", culture));
Console.WriteLine(strings["DayMessage"], now.ToString("dddd", culture));
Console.WriteLine();
Console.WriteLine(strings["Goodbye"]);
}
}
// Usage
Console.WriteLine("=== English ===");
GreetingApp.Run("en");
Console.WriteLine("\n=== German ===");
GreetingApp.Run("de");
Console.WriteLine("\n=== Japanese ===");
GreetingApp.Run("ja");
Output:
=== English ===
Hello!
Current time: 2:30:00 PM
Today is Sunday
Goodbye!
=== German ===
Hallo!
Aktuelle Zeit: 14:30:00
Heute ist Sonntag
Auf Wiedersehen!
=== Japanese ===
Konnichiwa!
Genzai no jikan: 14:30:00
Kyo wa Sunday
Sayonara!
Internationalization makes your C# applications accessible to a global audience. The .NET platform's culture-aware APIs handle formatting, sorting, and resource management, letting you focus on creating great user experiences in any language.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro