C# 6.0
string interpolation
localization
programming
.NET

C6.0 string interpolation localization

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

C# 6.0 string interpolation ($"Hello {name}") produces a culture-invariant string by default, which causes localization problems. Numbers, dates, and currencies get formatted using CultureInfo.CurrentCulture for the embedded expressions, but there is no built-in way to swap the interpolated string's template for a translated version. To localize properly, you need FormattableString to control culture formatting, and a separate localization system (resource files, IStringLocalizer) for the translated text itself.

String Interpolation Basics

csharp
1string name = "Alice";
2int age = 30;
3decimal price = 1234.56m;
4
5// C# 6.0+ interpolated string
6string message = $"Name: {name}, Age: {age}, Price: {price:C}";
7// "Name: Alice, Age: 30, Price: $1,234.56" (en-US)

The $"" syntax is syntactic sugar for string.Format():

csharp
// These are equivalent:
string a = $"Hello {name}, you are {age}";
string b = string.Format("Hello {0}, you are {1}", name, age);

The Localization Problem

Interpolated strings format numbers and dates using the current thread culture, but the template itself is hardcoded:

csharp
1decimal price = 1234.56m;
2DateTime date = new DateTime(2025, 3, 15);
3
4// On a German system (de-DE):
5Thread.CurrentThread.CurrentCulture = new CultureInfo("de-DE");
6string msg = $"Price: {price:C}, Date: {date:d}";
7// "Price: 1.234,56 €, Date: 15.03.2025"
8
9// On a US system (en-US):
10Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
11string msg = $"Price: {price:C}, Date: {date:d}";
12// "Price: $1,234.56, Date: 3/15/2025"

The number and date formatting respects culture, but "Price: " and "Date: " remain in English. For full localization, you need to translate the template too.

FormattableString for Culture Control

When you assign an interpolated string to FormattableString, C# preserves the format and arguments separately:

csharp
1FormattableString fs = $"Price: {price:C}, Date: {date:d}";
2
3// Format with a specific culture
4string german = fs.ToString(CultureInfo.GetCultureInfo("de-DE"));
5// "Price: 1.234,56 €, Date: 15.03.2025"
6
7string us = fs.ToString(CultureInfo.GetCultureInfo("en-US"));
8// "Price: $1,234.56, Date: 3/15/2025"
9
10// Always use invariant culture (for logging, serialization)
11string invariant = FormattableString.Invariant($"Value: {price}");
12// "Value: 1234.56" (always dot decimal separator)

FormattableString.Invariant()

For data that must never be culture-dependent (API calls, file paths, SQL):

csharp
1decimal lat = 48.8566m;
2decimal lon = 2.3522m;
3
4// WRONG — on de-DE, produces "48,8566" with comma
5string url = $"https://api.example.com/geo?lat={lat}&lon={lon}";
6
7// CORRECT — always uses dot separator
8string url = FormattableString.Invariant(
9    $"https://api.example.com/geo?lat={lat}&lon={lon}");
10// "https://api.example.com/geo?lat=48.8566&lon=2.3522"

Localization with Resource Files

For translating the text template, use .resx resource files:

xml
1<!-- Resources/Messages.resx (English, default) -->
2<data name="PriceMessage" xml:space="preserve">
3  <value>The price is {0:C} as of {1:d}</value>
4</data>
5
6<!-- Resources/Messages.de.resx (German) -->
7<data name="PriceMessage" xml:space="preserve">
8  <value>Der Preis beträgt {0:C} am {1:d}</value>
9</data>
csharp
1// Use string.Format with the localized template
2decimal price = 1234.56m;
3DateTime date = DateTime.Now;
4
5string template = Resources.Messages.PriceMessage;
6string message = string.Format(
7    CultureInfo.CurrentCulture,
8    template,
9    price,
10    date
11);
12// German: "Der Preis beträgt 1.234,56 € am 15.03.2025"
13// English: "The price is $1,234.56 as of 3/15/2025"

Note: You cannot use $"" interpolation with resource strings because the template must come from the resource file at runtime, not compile time.

ASP.NET Core IStringLocalizer

csharp
1// In ASP.NET Core
2public class HomeController : Controller
3{
4    private readonly IStringLocalizer<HomeController> _localizer;
5
6    public HomeController(IStringLocalizer<HomeController> localizer)
7    {
8        _localizer = localizer;
9    }
10
11    public IActionResult Index()
12    {
13        decimal price = 1234.56m;
14        // Template comes from resource file, arguments are formatted per culture
15        string message = _localizer["PriceMessage", price];
16        return View(model: message);
17    }
18}

Custom Extension Method

Create a helper for culture-aware interpolation:

csharp
1public static class StringExtensions
2{
3    public static string ToString(
4        this FormattableString formattable,
5        string cultureName)
6    {
7        return formattable.ToString(
8            CultureInfo.GetCultureInfo(cultureName));
9    }
10}
11
12// Usage
13FormattableString msg = $"Total: {amount:C}";
14string german = msg.ToString("de-DE");
15string french = msg.ToString("fr-FR");

C# 10+ Interpolated String Handlers

C# 10 introduced custom interpolated string handlers for performance. They also enable custom culture handling:

csharp
// The compiler converts $"..." to handler calls
// For logging frameworks, this avoids string allocation when logging is disabled
LoggerExtensions.LogInformation(logger, $"Processing order {orderId}");

Common Pitfalls

  • Assuming $"" respects thread culture for text: Only the format specifiers (:C, :d, :N) use culture. The literal text in the template is always the compile-time string.
  • Using $"" for URLs and SQL: Culture-dependent decimal separators break URLs and SQL queries. Always use FormattableString.Invariant() for machine-readable output.
  • Confusing string and FormattableString: string msg = $"..." immediately formats to string. To preserve format+arguments, use FormattableString msg = $"...".
  • Resource files with wrong format indices: If the resource string uses {0} and {1}, you must pass arguments in the same order with string.Format. There is no compile-time checking like $"" provides.
  • Mixed interpolation and concatenation: $"Hello " + $"{name}" creates two separate strings and concatenates. Use $"Hello {name}" in one expression for clarity and performance.

Summary

  • $"..." formats embedded values using CultureInfo.CurrentCulture but the template text is always hardcoded
  • Use FormattableString to format with a specific culture: fs.ToString(culture)
  • Use FormattableString.Invariant() for machine-readable output (URLs, APIs, SQL)
  • For full localization, use resource files (.resx) with string.Format() or IStringLocalizer
  • String interpolation cannot replace the template at runtime — use string.Format with localized templates instead

Course illustration
Course illustration

All Rights Reserved.