본문 바로가기

C#

C# System.FormattableString으로 다국어 문자열 처리

보간 문자열을 그대로 문자열로 바꾸지 말고 FormattableString으로 넘기면, 문화권(Culture)에 맞게 숫자/날짜/통화 형식을 안전하게 적용하고 리소스 기반 번역도 자연스럽게 처리할 수 있습니다.

1. FormattableString이란?

$"..." 보간 문자열을 FormattableString 타입으로 받으면, 내부의 원본 서식 문자열과 인자 목록을 분리해 가지고 있습니다. 따라서 나중에 원하는 Culture로 포맷팅하거나, 리소스 템플릿에 동일한 인자를 적용할 수 있습니다.

using System;
using System.Globalization;

decimal price = 12345.67m;
DateTime date = new DateTime(2026, 5, 1);

FormattableString fs = $"총액 {price:C} - 날짜 {date:D}"; // 포맷 지시자 유지

var ko = new CultureInfo("ko-KR");
var en = new CultureInfo("en-US");

string koText = fs.ToString(ko); // "총액 ₩12,345.67 - 날짜 2026년 5월 1일 금요일"
string enText = fs.ToString(en); // "총액 $12,345.67 - 날짜 Friday, May 1, 2026"

2. CurrentCulture / Invariant 간편 사용

정밀한 숫자 로그 등은 InvariantCulture로, 사용자 노출 텍스트는 CurrentCulture로 처리하는 것이 안전합니다.

using System;
using System.Globalization;

var lat = 37.5665;
var lng = 126.9780;

// 현재 스레드 Culture로 포맷
string userMessage = FormattableString.CurrentCulture($"위도 {lat:F4}, 경도 {lng:F4}");

// 불변(파싱/로그/키) 용도
string logLine = FormattableString.Invariant($"lat={lat:F6}; lng={lng:F6}");

3. 리소스 기반 다국어: 템플릿 키 + 인자 재사용

FormattableString.Format은 "Hello {0}, total {1:C}" 형태의 템플릿을 제공합니다. 이 값을 리소스 키로 쓰고, 문화권별 템플릿을 찾아 동일한 인자에 적용하면 번역과 포맷이 동시에 처리됩니다.

using System;
using System.Collections.Generic;
using System.Globalization;

public sealed class SimpleLocalizer
{
    private readonly IDictionary<string, string> _resources;
    private readonly CultureInfo _culture;

    public SimpleLocalizer(IDictionary<string, string> resources, CultureInfo culture)
    {
        _resources = resources;
        _culture = culture;
    }

    // IStringLocalizer 패턴과 유사한 인덱서
    public string this[FormattableString fs]
    {
        get
        {
            string key = fs.Format;                 // 예: "Total {0:C} on {1:D}"
            object[] args = fs.GetArguments();      // 인자 배열
            if (!_resources.TryGetValue(key, out var template))
                template = key;                     // 리소스 없으면 원문 키 사용
            return string.Format(_culture, template, args);
        }
    }
}

// 사용 예시
var fr = new CultureInfo("fr-FR");
var resourcesFr = new Dictionary<string, string>
{
    { "Total {0:C} on {1:D}", "Total {0:C} le {1:D}" }
};

var locFr = new SimpleLocalizer(resourcesFr, fr);
var total = 1234.5m;
var when = new DateTime(2026, 5, 1);

string text = locFr[$"Total {total:C} on {when:D}"]; // "Total 1 234,50 € le vendredi 1 mai 2026"

핵심은 보간 문자열을 즉시 문자열로 만들지 않고, FormattableString 형태로 전달하는 것입니다. 이렇게 하면 템플릿 번역과 인자 포맷팅을 문화권에 맞게 한 번에 처리합니다. ASP.NET Core의 IStringLocalizer도 동일한 패턴을 지원합니다.

4. 숫자/날짜/통화 예제

using System;
using System.Globalization;

var de = new CultureInfo("de-DE");
var jp = new CultureInfo("ja-JP");

int count = 12000;
decimal money = 98765.43m;
DateTime dt = new DateTime(2026, 12, 24, 18, 30, 0);

FormattableString fs = $"개수 {count:N0}, 금액 {money:C}, 시간 {dt:F}";

Console.WriteLine(fs.ToString(de)); // "개수 12.000, 금액 98.765,43 €, 시간 Donnerstag, 24. Dezember 2026 18:30:00"
Console.WriteLine(fs.ToString(jp)); // "개수 12,000, 金額 ¥98,765, 時間 2026年12月24日 18:30:00"

5. 실전 팁/주의사항

- 인자를 미리 ToString()으로 포맷하지 마세요. 원본 타입(DateTime, decimal 등) 그대로 넘겨야 문화권별 포맷이 반영됩니다.

- 통화는 C, 숫자는 N/P, 날짜는 d/D/f/F 등 서식 지정자를 적극 활용하세요.

- 리소스 키로 영어 문장을 그대로 쓰는 방식은 간편하지만, 대형 프로젝트에서는 의미 키(예: Errors.Required)로 운영하는 것을 고려하세요. 이때도 값은 "{0} is required"처럼 자리표시자를 유지합니다.

- 로그/식별자/직렬화는 InvariantCulture, 사용자 화면은 CurrentUICulture/CurrentCulture를 사용하세요.

- ASP.NET Core에서는 IStringLocalizer[$"..."] 형태로 FormattableString을 바로 넘기면 리소스 템플릿과 인자 포맷이 자동 처리됩니다.

6. 미니 유틸리티(확장 메서드)

using System;
using System.Globalization;

public static class FormattableStringExtensions
{
    public static string Localize(this FormattableString fs, IFormatProvider provider)
        => fs.ToString(provider);

    public static string Invariant(this FormattableString fs)
        => FormattableString.Invariant(fs);
}

// 사용
var ko = new CultureInfo("ko-KR");
string s = $"가격 {1234.56m:C}".Localize(ko);
string inv = $"x={Math.PI:F6}".Invariant();

정리: 보간 문자열을 FormattableString으로 다루면, 번역(리소스 템플릿)과 지역화 포맷팅을 분리·자동화할 수 있습니다. UI는 CurrentCulture, 데이터/로그는 Invariant로 구분해 안전하게 다국어를 지원하세요.