# Globalization Localization

> Guidance for globalization and localization in .NET. USE FOR: culture-aware formatting, request localization middleware, date/number/currency formatting across cultures, locale-sensitive string comparison. DO NOT USE FOR: simple resource file lookup (use resources-localization), ICU message formatting (use messageformat-net), general i18n architecture (use i18n).

- Skill: `tyler-r-kendrick/globalization-localization` (Agent Skill, multi-file: 16 files)
- Install (CLI): `npx skillmds@latest add tyler-r-kendrick/globalization-localization`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tyler-r-kendrick/globalization-localization/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: Tyler-R-Kendrick (https://skillmd.com/u/tyler-r-kendrick)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/tyler-r-kendrick/globalization-localization

---


# Globalization and Localization

## Overview

.NET provides a rich globalization and localization infrastructure through `System.Globalization` and the ASP.NET Core request localization middleware. Globalization is the process of designing applications that support different cultures, while localization is the process of adapting a globalized application to a particular culture. Together they enable applications to format dates, numbers, currencies, and sort strings according to the conventions of any supported locale.

The core types are `CultureInfo`, `RegionInfo`, `NumberFormatInfo`, `DateTimeFormatInfo`, and the `RequestLocalizationMiddleware` in ASP.NET Core. These provide deterministic, standards-compliant formatting for user-facing content without hand-rolled parsing logic.

## Culture-Aware Formatting

Use `CultureInfo` to control how values are formatted and parsed. Always specify a culture explicitly rather than relying on the thread's current culture, which can change unexpectedly in server environments.

```csharp
using System.Globalization;

public static class CultureFormattingExamples
{
    public static string FormatCurrency(decimal amount, string cultureName)
    {
        var culture = new CultureInfo(cultureName);
        return amount.ToString("C", culture);
        // "en-US" => "$1,234.56"
        // "de-DE" => "1.234,56 €"
        // "ja-JP" => "¥1,235"
    }

    public static string FormatDate(DateTime date, string cultureName)
    {
        var culture = new CultureInfo(cultureName);
        return date.ToString("D", culture);
        // "en-US" => "Monday, January 6, 2025"
        // "fr-FR" => "lundi 6 janvier 2025"
    }

    public static decimal ParseNumber(string input, string cultureName)
    {
        var culture = new CultureInfo(cultureName);
        return decimal.Parse(input, NumberStyles.Number, culture);
        // "1.234,56" with "de-DE" => 1234.56m
    }
}
```

## Request Localization Middleware

ASP.NET Core provides middleware that automatically sets the current culture per-request based on query strings, cookies, or the `Accept-Language` header.

```csharp
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.DependencyInjection;
using System.Globalization;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddLocalization(options =>
    options.ResourcesPath = "Resources");

builder.Services.Configure<RequestLocalizationOptions>(options =>
{
    var supportedCultures = new[]
    {
        new CultureInfo("en-US"),
        new CultureInfo("fr-FR"),
        new CultureInfo("de-DE"),
        new CultureInfo("ja-JP"),
    };

    options.DefaultRequestCulture = new RequestCulture("en-US");
    options.SupportedCultures = supportedCultures;
    options.SupportedUICultures = supportedCultures;

    // Culture resolution order:
    // 1. QueryStringRequestCultureProvider  (?culture=fr-FR)
    // 2. CookieRequestCultureProvider
    // 3. AcceptLanguageHeaderRequestCultureProvider
    options.RequestCultureProviders.Insert(0,
        new QueryStringRequestCultureProvider());
});

var app = builder.Build();
app.UseRequestLocalization();
app.Run();
```

## Custom Culture Provider

For applications that store user locale preferences in a database or JWT claim, implement a custom `RequestCultureProvider`.

```csharp
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using System.Security.Claims;
using System.Threading.Tasks;

public class UserProfileCultureProvider : RequestCultureProvider
{
    public override Task<ProviderCultureResult?> DetermineProviderCultureResult(
        HttpContext httpContext)
    {
        var cultureClaim = httpContext.User
            .FindFirst("preferred_locale")?.Value;

        if (string.IsNullOrEmpty(cultureClaim))
        {
            return NullProviderCultureResult;
        }

        var result = new ProviderCultureResult(cultureClaim);
        return Task.FromResult<ProviderCultureResult?>(result);
    }
}
```

Register the provider before the defaults:

```csharp
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.DependencyInjection;

builder.Services.Configure<RequestLocalizationOptions>(options =>
{
    options.RequestCultureProviders.Insert(0,
        new UserProfileCultureProvider());
});
```

## String Comparison and Sorting

Culture-sensitive string comparison is critical for correct sorting and searching. Use `StringComparer` or `CultureInfo.CompareInfo` for locale-aware ordering.

```csharp
using System;
using System.Collections.Generic;
using System.Globalization;

public static class CultureSortingExamples
{
    public static List<string> SortByCulture(
        List<string> items, string cultureName)
    {
        var culture = new CultureInfo(cultureName);
        items.Sort(culture.CompareInfo.Compare);
        return items;
    }

    public static bool CultureAwareContains(
        string source, string value, string cultureName)
    {
        var culture = new CultureInfo(cultureName);
        return culture.CompareInfo.IndexOf(
            source, value, CompareOptions.IgnoreCase) >= 0;
    }
}
```

## Culture Provider Comparison

| Provider | Source | Priority | Use Case |
|---|---|---|---|
| QueryStringRequestCultureProvider | `?culture=fr-FR` | Highest (default) | Testing, explicit URL switching |
| CookieRequestCultureProvider | `.AspNetCore.Culture` cookie | Medium | Persistent user preference |
| AcceptLanguageHeaderRequestCultureProvider | `Accept-Language` header | Lowest | Browser-default experience |
| Custom (e.g., UserProfileCultureProvider) | DB / JWT / session | Configurable | Enterprise apps with profiles |

## Best Practices

1. **Always pass an explicit `CultureInfo`** to `ToString`, `Parse`, and `TryParse` methods instead of relying on `CultureInfo.CurrentCulture`, which varies per-thread and per-request.
2. **Use `CultureInfo.InvariantCulture`** for machine-readable data such as log entries, config files, and inter-service communication to avoid locale-dependent parsing failures.
3. **Register `RequestLocalizationMiddleware` early** in the pipeline (before MVC/Razor), so all downstream middleware observes the correct culture.
4. **Limit supported cultures** to those you actually have translations for; do not accept arbitrary culture codes to avoid fallback surprises.
5. **Store culture-neutral data** in databases (UTC dates, invariant decimal formats) and format only at the presentation layer.
6. **Test with cultures that use different decimal separators** (e.g., `de-DE` uses `,` for decimals) to catch parsing bugs that are invisible with `en-US`.
7. **Use `StringComparison.OrdinalIgnoreCase`** for internal identifiers and keys; reserve culture-sensitive comparison for user-facing content.
8. **Set `SupportedCultures` and `SupportedUICultures` together** in `RequestLocalizationOptions` to avoid mismatches between formatting culture and resource lookup culture.
9. **Validate culture names** with `CultureInfo.GetCultureInfo` inside a try-catch before constructing `CultureInfo` objects from user input, to avoid `CultureNotFoundException`.
10. **Prefer `DateTimeOffset` over `DateTime`** when dealing with globalized applications to avoid time zone ambiguity across cultures.

