Building Multilingual Websites with .NET Globalization

Reaching Global Audiences

Imagine launching an e-commerce site that needs to serve customers in Germany, Japan, and Brazil. Each market expects prices in local currency, dates in familiar formats, and UI text in their language. Hardcoding "Welcome" and "$99.99" won't cut it.

.NET's globalization framework handles culture-specific formatting automatically through CultureInfo. Localization features let you store translated strings in resource files and load them based on user preference. Together, these make multilingual sites manageable.

You'll learn how CultureInfo formats dates and numbers, how resource files organize translations, and how ASP.NET Core middleware detects user language. By the end, you'll build sites that adapt seamlessly to any culture.

Understanding CultureInfo

CultureInfo represents a specific culture with formatting rules for dates, numbers, and currency. Every thread has CurrentCulture for formatting and CurrentUICulture for resource loading. Setting these controls how your app displays data.

Culture names follow ISO standards: "en-US" for US English, "de-DE" for German, "ja-JP" for Japanese. Neutral cultures like "en" lack region-specific rules. Always use specific cultures when formatting to avoid ambiguity.

CultureDemo.cs
using System.Globalization;

var date = new DateTime(2025, 11, 4);
var price = 1234.56m;

// US English
CultureInfo.CurrentCulture = new CultureInfo("en-US");
Console.WriteLine($"US: {date:D} - {price:C}");

// German
CultureInfo.CurrentCulture = new CultureInfo("de-DE");
Console.WriteLine($"DE: {date:D} - {price:C}");

// Japanese
CultureInfo.CurrentCulture = new CultureInfo("ja-JP");
Console.WriteLine($"JP: {date:D} - {price:C}");

// Output:
// US: Tuesday, November 4, 2025 - $1,234.56
// DE: Dienstag, 4. November 2025 - 1.234,56 €
// JP: 2025年11月4日火曜日 - ¥1,235

The same date and decimal format differently based on culture. CultureInfo handles decimal separators, currency symbols, date ordering, and even digit grouping automatically. You write format strings once and they adapt.

Organizing Translations with Resource Files

Resource files (.resx) store translations as key-value pairs. Create a base file like Resources.resx for default language, then culture-specific files like Resources.de.resx for German. .NET selects the appropriate file based on CurrentUICulture.

The resource manager searches for the most specific culture file available, falling back to neutral cultures then the default. This lets you provide broad translations and override specific regions as needed.

Using resource files in code
// Resources.resx (default - English):
// Key: Greeting, Value: Hello
// Key: Farewell, Value: Goodbye

// Resources.de.resx (German):
// Key: Greeting, Value: Hallo
// Key: Farewell, Value: Auf Wiedersehen

// Resources.ja.resx (Japanese):
// Key: Greeting, Value: こんにちは
// Key: Farewell, Value: さようなら

using System.Resources;

var rm = new ResourceManager("MyApp.Resources", typeof(Program).Assembly);

CultureInfo.CurrentUICulture = new CultureInfo("en");
Console.WriteLine(rm.GetString("Greeting")); // Hello

CultureInfo.CurrentUICulture = new CultureInfo("de");
Console.WriteLine(rm.GetString("Greeting")); // Hallo

CultureInfo.CurrentUICulture = new CultureInfo("ja");
Console.WriteLine(rm.GetString("Greeting")); // こんにちは

Resource Manager loads the correct file automatically. If Resources.de.resx is missing a key, it falls back to Resources.resx. This graceful degradation prevents missing translations from breaking your app.

ASP.NET Core Localization Middleware

ASP.NET Core's RequestLocalizationMiddleware detects user language from headers, cookies, or query strings. It sets CultureInfo automatically before your controllers run. Configure supported cultures and default fallback in your startup code.

The middleware checks providers in order: query string, cookie, Accept-Language header. This lets users override browser defaults with a language selector that sets a preference cookie.

Program.cs - Localization setup
using Microsoft.AspNetCore.Localization;
using System.Globalization;

var builder = WebApplication.CreateBuilder(args);

// Configure supported cultures
var supportedCultures = new[]
{
    new CultureInfo("en-US"),
    new CultureInfo("de-DE"),
    new CultureInfo("ja-JP")
};

builder.Services.Configure<RequestLocalizationOptions>(options =>
{
    options.DefaultRequestCulture = new RequestCulture("en-US");
    options.SupportedCultures = supportedCultures;
    options.SupportedUICultures = supportedCultures;
});

var app = builder.Build();

app.UseRequestLocalization();

app.MapGet("/", () =>
{
    var culture = CultureInfo.CurrentCulture;
    var price = 99.99m;
    return $"Culture: {culture.Name}, Price: {price:C}";
});

app.Run();

Users get content in their browser's language automatically. Add a language switcher that sets a cookie, and they can override the default. The middleware handles detection complexity so your controllers just read CultureInfo.

Culture-Aware Formatting Patterns

Different cultures format numbers, dates, and times differently. The invariant culture uses consistent formatting suitable for data storage. Specific cultures format for display. Always use invariant culture when serializing data to avoid parsing issues.

For user-facing output, use CurrentCulture. For machine-readable formats like JSON or database values, use CultureInfo.InvariantCulture. This separation prevents bugs when users in different regions share data.

FormattingPatterns.cs
using System.Globalization;

var date = new DateTime(2025, 11, 4, 14, 30, 0);
var number = 1234567.89;

// Display to user (culture-aware)
CultureInfo.CurrentCulture = new CultureInfo("de-DE");
Console.WriteLine($"Display: {date:f}"); // 04. November 2025 14:30
Console.WriteLine($"Number: {number:N2}"); // 1.234.567,89

// Store in database (invariant)
var dateStr = date.ToString("o", CultureInfo.InvariantCulture);
var numStr = number.ToString(CultureInfo.InvariantCulture);
Console.WriteLine($"Store: {dateStr}"); // 2025-11-04T14:30:00.0000000
Console.WriteLine($"Store: {numStr}"); // 1234567.89

// Parse from database (invariant)
var parsedDate = DateTime.Parse(dateStr, CultureInfo.InvariantCulture);
var parsedNum = double.Parse(numStr, CultureInfo.InvariantCulture);

Invariant culture uses ISO formats that parse unambiguously anywhere. Culture-specific formats make displays friendly but can fail when parsing if culture changes. Keep storage and display concerns separate.

Try It Yourself

Build a simple culture switcher that demonstrates formatting changes. You'll see dates, numbers, and currency adapt as you change cultures.

Steps

  1. Create: dotnet new console -n CultureSwitch
  2. Navigate: cd CultureSwitch
  3. Replace Program.cs with the code below
  4. Run: dotnet run
Program.cs
using System.Globalization;

var cultures = new[] { "en-US", "de-DE", "ja-JP", "fr-FR" };
var date = DateTime.Now;
var price = 1499.99m;

foreach (var cultureName in cultures)
{
    var culture = new CultureInfo(cultureName);
    CultureInfo.CurrentCulture = culture;

    Console.WriteLine($"\n{culture.DisplayName}:");
    Console.WriteLine($"  Date: {date:D}");
    Console.WriteLine($"  Price: {price:C}");
    Console.WriteLine($"  Number: {123456.78:N2}");
}
CultureSwitch.csproj
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>
</Project>

What you'll see


English (United States):
  Date: Tuesday, November 4, 2025
  Price: $1,499.99
  Number: 123,456.78

German (Germany):
  Date: Dienstag, 4. November 2025
  Price: 1.499,99 €
  Number: 123.456,78

Japanese (Japan):
  Date: 2025年11月4日火曜日
  Price: ¥1,500
  Number: 123,456.78

French (France):
  Date: mardi 4 novembre 2025
  Price: 1 499,99 €
  Number: 123 456,78

Notice how decimal separators, thousand separators, currency symbols, and date formats all change automatically. CultureInfo handles these details so you don't hardcode formatting logic.

FAQ

What's the difference between globalization and localization?

Globalization (g11n) makes your app culture-aware—date formats, number formats, text direction. Localization (l10n) provides translated content for specific cultures. You globalize once to support the framework, then localize for each target language.

How do I detect user's preferred language?

In ASP.NET Core, use RequestLocalizationMiddleware which checks Accept-Language headers, cookies, or query strings. It sets CultureInfo.CurrentCulture and CurrentUICulture automatically. Users can override via language switcher that sets a preference cookie.

Should I store translations in database or resource files?

Use .resx resource files for static UI text compiled into assemblies. Use database for user-generated content or frequently changing translations. Resource files perform better but require recompilation. Databases offer flexibility but add query overhead.

Back to Articles