.NET의 지역화

지역화는 애플리케이션에서 지원할 각 문화권에 맞는 지역화된 버전으로 애플리케이션 리소스를 변환하는 프로세스입니다. 지역화 가능성 검토 단계를 완료한 후에만 지역화 단계를 진행하여 전역화된 애플리케이션이 지역화 준비가 되었는지 확인해야 합니다.

지역화할 준비가 된 애플리케이션은 모든 사용자 인터페이스 요소가 포함된 블록 및 실행 코드가 포함된 블록이라는 두 가지 개념 블록으로 구분됩니다. 사용자 인터페이스 블록에는 중립적인 문화권에 적용할 수 있는 문자열, 오류 메시지, 대화 상자, 메뉴, 포함 개체 리소스 등과 같은 지역화 가능한 사용자 인터페이스 요소만 포함됩니다. 코드 블록에는 지원되는 모든 문화권에서 사용할 애플리케이션 코드만 포함됩니다. 공용 언어 런타임은 해당 리소스에서 애플리케이션의 실행 코드를 분리하는 위성 어셈블리 리소스 모델을 지원합니다. 이 모델을 구현하는 방법에 대한 자세한 내용은 .NET의 리소스를 참조하세요.

애플리케이션의 지역화된 버전 각각에 대해 대상 문화권의 적절한 언어로 번역된 지역화된 사용자 인터페이스 블록을 포함한 새 위성 어셈블리를 추가합니다. 모든 문화권의 코드 블록은 동일하게 유지해야 합니다. 지역화된 버전의 사용자 인터페이스 블록과 코드 블록을 조합하면 애플리케이션의 지역화된 버전이 생성됩니다.

이 문서에서는 IStringLocalizer<T>IStringLocalizerFactory 구현을 사용하는 방법을 알아봅니다. 이 문서의 모든 샘플 소스 코드는 Microsoft.Extensions.LocalizationMicrosoft.Extensions.Hosting NuGet 패키지를 사용합니다. 호스트에 대한 자세한 내용은 .NET 제네릭 호스트를 참조하세요.

리소스 파일

지역화 가능한 문자열을 격리하는 기본 메커니즘은 리소스 파일을 사용하는 것입니다. 리소스 파일은 파일 확장자가 .resx인 XML 파일입니다. 리소스 파일은 사용하는 애플리케이션이 실행되기 전에 번역됩니다. 즉, 미사용 번역된 콘텐츠를 나타냅니다. 리소스 파일 이름은 일반적으로 로캘 식별자를 포함하며 다음과 같은 형식을 사용합니다.

<FullTypeName><.Locale>.resx

여기서

  • <FullTypeName>은 특정 형식에 대해 지역화 가능한 리소스를 나타냅니다.
  • 선택 사항인 <.Locale>은 리소스 파일 콘텐츠의 로캘을 나타냅니다.

로캘 지정

로캘은 최소한 언어를 정의해야 하며, 문화권(지역 언어) 및 국가 또는 지역을 정의할 수도 있습니다. 이러한 세그먼트는 일반적으로 - 문자로 구분됩니다. 문화권을 지정하는 방법이 도입됨에 따라 가장 일치하는 항목이 우선시되는 경우 ‘문화권 대체’ 규칙이 적용됩니다. 로캘은 잘 알려진 언어 태그에 매핑되어야 합니다. 자세한 내용은 CultureInfo.Name를 참조하세요.

문화권 대체 시나리오

지역화된 앱이 여러 세르비아 로캘을 지원하고 MessageService에 대해 다음과 같은 리소스 파일을 포함한다고 가정하겠습니다.

파일 지역 언어 국가 코드
MessageService.sr-Cyrl-RS.resx (키릴 자모, 세르비아) RS
MessageService.sr-Cyrl.resx 키릴 자모
MessageService.sr-Latn-BA.resx (라틴 문자, 보스니아 헤르체고비나) BA
MessageService.sr-Latn-ME.resx (라틴 문자, 몬테네그로) ME
MessageService.sr-Latn-RS.resx (라틴 문자, 세르비아) RS
MessageService.sr-Latn.resx 라틴어
MessageService.sr.resx 라틴 문자
MessageService.resx

언어의 기본 지역 언어입니다.

앱의 CultureInfo.CurrentCulture"sr-Cyrl-RS"의 문화권으로 설정되어 실행되는 경우 지역화는 다음과 같은 순서로 파일의 확인을 시도합니다.

  1. MessageService.sr-Cyrl-RS.resx
  2. MessageService.sr-Cyrl.resx
  3. MessageService.sr.resx
  4. MessageService.resx

그러나 앱의 CultureInfo.CurrentCulture"sr-Latn-BA"의 문화권으로 설정되어 실행되는 경우 지역화는 다음과 같은 순서로 파일의 확인을 시도합니다.

  1. MessageService.sr-Latn-BA.resx
  2. MessageService.sr-Latn.resx
  3. MessageService.sr.resx
  4. MessageService.resx

“문화권 대체” 규칙은 일치하는 항목이 없는 경우 로캘을 무시합니다. 즉, 일치하는 항목을 찾을 수 없는 경우 리소스 파일 4번이 선택됩니다. 문화권이 "fr-FR"로 설정되었다면 지역화는 MessageService.resx 파일로 대체하게 되는데, 이는 문제가 될 수 있습니다. 자세한 내용은 리소스 대체 프로세스를 참조하세요.

리소스 조회

리소스 파일은 조회 루틴의 일부로 자동으로 확인됩니다. 프로젝트 파일 이름이 프로젝트의 루트 네임스페이스와 다른 경우 어셈블리 이름이 다를 수 있습니다. 이로 인해 리소스 조회가 성공하지 않을 수 있습니다. 이 불일치를 해결하려면 RootNamespaceAttribute를 사용하여 지역화 서비스에 힌트를 제공하세요. 제공된 힌트는 리소스 조회 중에 사용됩니다.

예제 프로젝트는 이름이 example.csproj이고 example.dllexample.exe를 만들지만, Localization.Example 네임스페이스가 사용됩니다. 이 불일치를 수정하려면 assembly 수준 특성을 적용합니다.

[assembly: RootNamespace("Localization.Example")]

지역화 서비스 등록

지역화 서비스를 등록하려면 서비스 구성 중에 AddLocalization 확장 메서드 중 하나를 호출합니다. 그러면 다음 형식의 DI(종속성 주입)를 사용할 수 있습니다.

지역화 옵션 구성

AddLocalization(IServiceCollection, Action<LocalizationOptions>) 오버로드는 Action<LocalizationOptions> 형식의 setupAction 매개 변수를 받습니다. 이 매개 변수를 사용하여 지역화 옵션을 구성할 수 있습니다.

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

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

// Omitted for brevity.

리소스 파일은 프로젝트의 어디에나 존재할 수 있지만 성공적인 것으로 입증된 일반적인 모범 사례가 있습니다. 대부분의 경우 최소 저항 경로가 사용됩니다. 위의 C# 코드에서:

  • 기본 호스트 앱 작성기를 만듭니다.
  • LocalizationOptions.ResourcesPath"Resources"로 지정하여 서비스 컬렉션에서 AddLocalization을 호출합니다.

그러면 지역화 서비스가 Resources 디렉터리에서 리소스 파일을 찾습니다.

IStringLocalizer<T>IStringLocalizerFactory 사용

지역화 서비스를 등록하고 원하는 경우 구성한 후에는 DI를 사용하여 다음과 같은 형식을 사용할 수 있습니다.

지역화된 문자열을 반환할 수 있는 메시지 서비스를 만들려면 다음 MessageService를 살펴보세요.

using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Localization;

namespace Localization.Example;

public sealed class MessageService(IStringLocalizer<MessageService> localizer)
{
    [return: NotNullIfNotNull(nameof(localizer))]
    public string? GetGreetingMessage()
    {
        LocalizedString localizedString = localizer["GreetingMessage"];

        return localizedString;
    }
}

위의 C# 코드에서:

  • IStringLocalizer<MessageService> localizer 필드가 선언되었습니다.
  • 기본 생성자는 IStringLocalizer<MessageService> 매개 변수를 정의하고 localizer 인수로 캡처합니다.
  • GetGreetingMessage 메서드가 IStringLocalizer.Item[String]을 호출하고 "GreetingMessage"를 인수로 전달합니다.

IStringLocalizer는 매개 변수화된 문자열 리소스도 지원합니다. 다음 ParameterizedMessageService를 살펴보세요.

using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Localization;

namespace Localization.Example;

public class ParameterizedMessageService(IStringLocalizerFactory factory)
{
    private readonly IStringLocalizer _localizer =
        factory.Create(typeof(ParameterizedMessageService));

    [return: NotNullIfNotNull(nameof(_localizer))]
    public string? GetFormattedMessage(DateTime dateTime, double dinnerPrice)
    {
        LocalizedString localizedString = _localizer["DinnerPriceFormat", dateTime, dinnerPrice];

        return localizedString;
    }
}

위의 C# 코드에서:

  • IStringLocalizer _localizer 필드가 선언되었습니다.
  • 기본 생성자가 IStringLocalizerFactory 매개 변수를 받아서(이 매개 변수는 ParameterizedMessageService 형식에서 IStringLocalizer를 만드는 데 사용됨) _localizer 필드에 할당합니다.
  • GetFormattedMessage 메서드가 IStringLocalizer.Item[String, Object[]]을 호출하고 "DinnerPriceFormat", dateTime 개체 및 dinnerPrice를 인수로 전달합니다.

Important

IStringLocalizerFactory는 필요하지 않습니다. 대신 사용하는 서비스가 IStringLocalizer<T>를 요구하는 것이 선호됩니다.

IStringLocalizer.Item[] 인덱서 모두 LocalizedString을 반환하는데, 이는 string?으로의 암시적 변환을 갖습니다.

모든 요소 결합

두 가지 메시지 서비스와 지역화 및 리소스 파일을 사용하는 앱의 예로 다음의 Program.cs 파일을 살펴보세요.

using System.Globalization;
using Localization.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using static System.Console;
using static System.Text.Encoding;

[assembly: RootNamespace("Localization.Example")]

OutputEncoding = Unicode;

if (args is [var cultureName])
{
    CultureInfo.CurrentCulture =
        CultureInfo.CurrentUICulture =
            CultureInfo.GetCultureInfo(cultureName);
}

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddLocalization();
builder.Services.AddTransient<MessageService>();
builder.Services.AddTransient<ParameterizedMessageService>();
builder.Logging.SetMinimumLevel(LogLevel.Warning);

using IHost host = builder.Build();

IServiceProvider services = host.Services;

ILogger logger =
    services.GetRequiredService<ILoggerFactory>()
        .CreateLogger("Localization.Example");

MessageService messageService =
    services.GetRequiredService<MessageService>();
logger.LogWarning(
    "{Msg}",
    messageService.GetGreetingMessage());

ParameterizedMessageService parameterizedMessageService =
    services.GetRequiredService<ParameterizedMessageService>();
logger.LogWarning(
    "{Msg}",
    parameterizedMessageService.GetFormattedMessage(
        DateTime.Today.AddDays(-3), 37.63));

await host.RunAsync();

위의 C# 코드에서:

  • RootNamespaceAttribute는 루트 네임스페이스로 "Localization.Example"을 설정합니다.
  • Console.OutputEncodingEncoding.Unicode에 할당되었습니다.
  • args에 단일 인수가 전달되면 CultureInfo.CurrentCultureCultureInfo.CurrentUICulturearg[0]에 대한 CultureInfo.GetCultureInfo(String)의 결과가 할당됩니다.
  • Host기본값을 사용하여 생성됩니다.
  • 지역화 서비스 MessageServiceParameterizedMessageService는 DI를 위해 IServiceCollection에 등록되었습니다.
  • 노이즈를 제거하기 위해 로깅은 경고보다 낮은 로그 수준을 무시하도록 구성됩니다.
  • MessageServiceIServiceProvider 인스턴스에서 확인되고 결과 메시지가 로깅됩니다.
  • ParameterizedMessageServiceIServiceProvider 인스턴스에서 확인되고 형식이 지정된 결과 메시지가 로깅됩니다.

*MessageService 클래스는 각각 단일 항목을 갖는 .resx 파일 집합을 정의합니다. 다음은 MessageService 리소스 파일의 콘텐츠 예입니다. MessageService.resx를 시작으로 살펴보세요.

<?xml version="1.0" encoding="utf-8"?>
<root>
  <data name="GreetingMessage" xml:space="preserve">
    <value>Hi friends, the ".NET" developer community is excited to see you here!</value>
  </data>
</root>

MessageService.sr-Cyrl-RS.resx:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <data name="GreetingMessage" xml:space="preserve">
    <value>Здраво пријатељи, ".NЕТ" девелопер заједница је узбуђена што вас види овде!</value>
  </data>
</root>

MessageService.sr-Latn.resx:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <data name="GreetingMessage" xml:space="preserve">
    <value>Zdravo prijatelji, ".NET" developer zajednica je uzbuđena što vas vidi ovde!</value>
  </data>
</root>

다음은 ParameterizedMessageService 리소스 파일의 콘텐츠 예입니다. ParameterizedMessageService.resx를 시작으로 살펴보세요.

<?xml version="1.0" encoding="utf-8"?>
<root>
  <data name="DinnerPriceFormat" xml:space="preserve">
    <value>On {0:D} my dinner cost {1:C}.</value>
  </data>
</root>

ParameterizedMessageService.sr-Cyrl-RS.resx:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <data name="DinnerPriceFormat" xml:space="preserve">
    <value>У {0:D} моја вечера је коштала {1:C}.</value>
  </data>
</root>

ParameterizedMessageService.sr-Latn.resx:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <data name="DinnerPriceFormat" xml:space="preserve">
    <value>U {0:D} moja večera je koštala {1:C}.</value>
  </data>
</root>

모든 리소스 파일 XML 주석, 스키마 및 <resheader> 요소는 간결성을 위해 의도적으로 생략되었습니다.

예제 실행

다음 예제 실행에서는 지정된 대상 지정 로캘에 대한 다양한 지역화된 출력을 보여 줍니다.

"sr-Latn"의 경우를 살펴보겠습니다.

dotnet run --project .\example\example.csproj sr-Latn

warn: Localization.Example[0]
      Zdravo prijatelji, ".NET" developer zajednica je uzbuđena što vas vidi ovde!
warn: Localization.Example[0]
      U utorak, 03. avgust 2021. moja večera je koštala 37,63 ¤.

프로젝트 실행을 위해 .NET CLI에 대한 인수를 생략하자 기본 시스템 문화권이 사용되었습니다. 이 경우 "en-US":

dotnet run --project .\example\example.csproj

warn: Localization.Example[0]
      Hi friends, the ".NET" developer community is excited to see you here!
warn: Localization.Example[0]
      On Tuesday, August 3, 2021 my dinner cost $37.63.

"sr-Cryl-RS"를 전달하자 올바른 리소스 파일이 발견되고 지역화가 적용되었습니다.

dotnet run --project .\example\example.csproj sr-Cryl-RS

warn: Localization.Example[0]
      Здраво пријатељи, ".NЕТ" девелопер заједница је узбуђена што вас види овде!
warn: Localization.Example[0]
      У уторак, 03. август 2021. моја вечера је коштала 38 RSD.

샘플 애플리케이션에서는 "fr-CA"에 대한 리소스 파일을 제공하지 않지만, 해당 문화권으로 호출될 경우 지역화되지 않은 리소스 파일이 사용됩니다.

Warning

이 문화권은 있지만 올바른 리소스 파일은 없기 때문에 형식을 적용할 경우 부분적인 지역화만 볼 수 있게 됩니다.

dotnet run --project .\example\example.csproj fr-CA

warn: Localization.Example[0]
     Hi friends, the ".NET" developer community is excited to see you here!
warn: Localization.Example[0]
     On mardi 3 août 2021 my dinner cost 37,63 $.

참고 항목