Why Globalization Matters
If you're building apps for users around the world, you'll need to handle different languages, date formats, currencies, and cultural conventions. .NET provides powerful tools for globalization and localization that make your app work seamlessly across cultures.
Globalization (often abbreviated as g11n) is the process of designing your app to support different cultures. Localization (l10n) is the actual translation and adaptation of your app for specific regions. Together, they let you create apps that feel native to users everywhere.
In this article, you'll learn how to use CultureInfo, resource files, and satellite assemblies to build multilingual .NET applications. We'll focus on practical examples you can use in your projects today.
Working with CultureInfo
The CultureInfo class is the foundation of globalization in .NET. It represents a specific culture and controls how your app formats dates, numbers, and currencies. Every thread in .NET has two culture properties: CurrentCulture (for formatting) and CurrentUICulture (for loading localized resources).
using System.Globalization;
// Get the current culture
CultureInfo currentCulture = CultureInfo.CurrentCulture;
Console.WriteLine($"Current culture: {currentCulture.Name}");
// Set a specific culture for the current thread
CultureInfo frenchCulture = new CultureInfo("fr-FR");
Thread.CurrentThread.CurrentCulture = frenchCulture;
Thread.CurrentThread.CurrentUICulture = frenchCulture;
// Format a date according to the culture
DateTime today = DateTime.Now;
Console.WriteLine(today.ToString("D")); // Outputs: mercredi 15 décembre 2024
// Format currency
decimal price = 1299.99m;
Console.WriteLine(price.ToString("C")); // Outputs: 1 299,99 €
When you set CurrentCulture, it affects how .NET formats dates, numbers, and currencies. CurrentUICulture determines which resource files your app loads for translated strings. You'll typically set both to the same culture unless you have specific requirements.
Creating Resource Files for Translations
Resource files (.resx) store your translated strings. Visual Studio makes it easy to create and manage these files. Here's how you'll structure them:
- Resources.resx - Your default (fallback) language strings
- Resources.fr-FR.resx - French translations
- Resources.es-ES.resx - Spanish translations
- Resources.de-DE.resx - German translations
When your app needs a string, .NET automatically picks the right resource file based on CurrentUICulture. If it can't find a culture-specific file, it falls back to the default.
using System.Resources;
using System.Globalization;
// Create a ResourceManager for your resource file
ResourceManager rm = new ResourceManager(
"MyApp.Resources",
typeof(Program).Assembly
);
// Set culture to French
CultureInfo french = new CultureInfo("fr-FR");
// Get localized string
string greeting = rm.GetString("Greeting", french);
Console.WriteLine(greeting); // Outputs: Bonjour
// Set culture to Spanish
CultureInfo spanish = new CultureInfo("es-ES");
greeting = rm.GetString("Greeting", spanish);
Console.WriteLine(greeting); // Outputs: Hola
// If culture not found, falls back to default
CultureInfo japanese = new CultureInfo("ja-JP");
greeting = rm.GetString("Greeting", japanese);
Console.WriteLine(greeting); // Outputs: Hello (default)
Implementing Localization in ASP.NET Core
ASP.NET Core has built-in middleware for localization that makes it easy to support multiple languages in web apps. You'll configure the supported cultures and let the framework handle culture detection based on user preferences.
using Microsoft.AspNetCore.Localization;
using System.Globalization;
var builder = WebApplication.CreateBuilder(args);
// Add localization services
builder.Services.AddLocalization(options =>
options.ResourcesPath = "Resources");
// Configure supported cultures
var supportedCultures = new[]
{
new CultureInfo("en-US"),
new CultureInfo("fr-FR"),
new CultureInfo("es-ES"),
new CultureInfo("de-DE")
};
builder.Services.Configure(options =>
{
options.DefaultRequestCulture = new RequestCulture("en-US");
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
});
builder.Services.AddControllersWithViews()
.AddViewLocalization()
.AddDataAnnotationsLocalization();
var app = builder.Build();
// Enable localization middleware
app.UseRequestLocalization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
The RequestLocalizationMiddleware automatically detects the user's preferred language from browser settings, query strings, or cookies. You can also let users manually select their language through a dropdown menu.
Injecting IStringLocalizer in Controllers
In ASP.NET Core, you'll typically use IStringLocalizer instead of ResourceManager directly. It's cleaner and works better with dependency injection.
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
public class HomeController : Controller
{
private readonly IStringLocalizer _localizer;
public HomeController(IStringLocalizer localizer)
{
_localizer = localizer;
}
public IActionResult Index()
{
// Get localized strings
ViewBag.Welcome = _localizer["Welcome"];
ViewBag.Description = _localizer["AppDescription"];
return View();
}
public IActionResult SetLanguage(string culture, string returnUrl)
{
Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(
new RequestCulture(culture)),
new CookieOptions
{
Expires = DateTimeOffset.UtcNow.AddYears(1)
}
);
return LocalRedirect(returnUrl);
}
}
Create corresponding .resx files in your Resources folder: Controllers.HomeController.en-US.resx, Controllers.HomeController.fr-FR.resx, etc. The framework automatically loads the right file based on the current culture.
Best Practices for Globalization
Follow these guidelines to build robust multilingual applications:
- Never hardcode strings: Always use resource files, even for your default language. This makes it easier to add translations later.
- Use neutral cultures for fallback: Create resources for neutral cultures (like "fr" instead of "fr-FR") so multiple regional variants can share translations.
- Test with different cultures: Regularly test your app with various cultures to catch formatting issues early.
- Handle missing translations gracefully: Always provide default fallback strings so your app doesn't break if a translation is missing.
- Consider text expansion: Translated text is often longer than English. Design your UI to handle variable text lengths.
- Externalize all culture-specific formatting: Use culture-aware formatting methods for dates, numbers, and currencies instead of custom formatting strings.
Common Pitfalls to Avoid
Here are mistakes developers often make when implementing globalization:
Assuming date formats: Never parse dates using hardcoded formats like "MM/dd/yyyy". Different cultures use different date formats. Use DateTime.Parse with the appropriate culture or TryParseExact with InvariantCulture for data storage.
String concatenation: Don't build sentences by concatenating translated fragments. Grammar rules vary between languages. Instead, create complete sentence templates in your resource files.
Forgetting CurrentUICulture: Setting only CurrentCulture affects formatting but not which resource files load. Remember to set CurrentUICulture too.
Not using satellite assemblies: For large apps, consider using satellite assemblies to keep translations separate from your main executable. This makes it easier to update translations without redeploying your entire app.