.net 7 WPF에서 다국어 처리는 어떤식으로 하시나요?

.net 7 WPF 프로젝트 개발 중입니다.

지금은 모든 UI가 영어로 되어 있는데 한글화 작업을 진행 하려고 합니다.

혹시 관련해서 참고 할만한 예제가 있을까요?

2 Likes

프로젝트 구조를 이해하시긴 어려우시겠지만

간단히 설명을 드리자면
string key 방식으로 언어별로 xaml로 만들어주신다음에 전환하는 방식이 있습니다!

5 Likes

@이광석 님과 비슷한 방법일것 같은데 Andorid 앱 만들때 그쪽 SDK 에서 제공하는 String.xml 리소스 와 동일하게 구현 하여 WPF 에 적용 한 기억이 있습니다.

동일하게 구현하는것이 그리 어렵지 않을겁니다.

4 Likes

기본적으로 Localization 리소스를 제공 합니다.
리소스 파일 네이밍에
xxx…{국가-언어코드}.resx 형식으로 네이밍을 지으면 자동으로 다국어 리소스로 활용 됩니다.
예]
Resource.MyProject.resx 영문 (Default)
Resource.MyProject.ko-KR.resx 한국어
Resource.MyProject.zh-CN.resx 중국어

[한글]

[영문]

다국어 리소스의 Key 값으로 구분을 지을 수 있으며,

Thread.CurrentThread.CurrentUICulture

으로 다국어 문화를 변경 및 설정 할 수 있습니다.

이런 구성으로 빌드 하시면 다국어 리소스 관련 하여

ko-KR / zh-CN 등 다국어 리소스(영문이 기본) dll이 추가로 생성 됩니다.

5 Likes

@aroooong 저렇게 설정 된 키값을 실제 컨트롤과 바인딩은 어떻게 되나요?

3 Likes

저렇게 리소스를 구성하면 키 - 값 기준으로 리소스 클래스로 static 속성이 자동 생성 됩니다.

뷰 xaml에서는
해당 리소스의 위치 네임스페이스 정의 해주시고

대략 다음과 같이 사용 합니다.

xmlns:lang="clr-namespace:MyProject.Localization"

<TextBlock Text="{x:Static lang:Resource_MyProject.키값}" />

코드비하인드 cs에서는 직접 리소스 클래스에 접근해서 속성을 다음과 같이 사용 합니다.
static 속성이기에 바로 접근 가능합니다.

Localization.Resource_MyProject.키값

참고로 이러한 다국어 처리는 WPF에 종속 적인 것이 아닌

닷넷에서 정식(?) 으로 지원하는 다국어 처리 방법으로 윈폼, ASP.NET Core 환경 모두 위와 같이 할 수 있습니다.
(웹 프론트앤드는 사실 위 방법 말고 다른 방법으로도 많이 처리 하긴 합니다.)

4 Likes

다국어를 지원하는 오픈소스 WPF 프로젝트 중에 Playnite가 있는데요. 코드를 참고하시면 도움이 될듯 합니다.

언어별로 문자열 리소스를 관리하기 위한 XAML 파일을 만들어 프로그램 내에서 사용되는 모든 문자열에 Key를 부여해서 정의합니다.

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:sys="clr-namespace:System;assembly=mscorlib">
    <sys:String x:Key="LOCAboutAuthor">Made by Josef Němec</sys:String>
    ...

Playnite/source/Playnite/Localization/LocSource.xaml at master · JosefNemec/Playnite (github.com)

설정된 언어에 따라 ResourceDictionary를 동적으로 로딩하는 Localization과 같은 클래스를 생성합니다.

public static void SetLanguage(string language)
{
    var dictionaries = Application.Current.Resources.MergedDictionaries;
    var langFile = Path.Combine(PlaynitePaths.LocalizationsPath, language + ".xaml");
    if (File.Exists(langFile) && language != SourceLanguageId)
    {
        var res = Xaml.FromFile<ResourceDictionary>(langFile);
        res.Source = new Uri(langFile, UriKind.Absolute);
        dictionaries.Add(res);
    }
...
}    

뷰에서는 DynamicResource 또는 StaticResource를 사용하여 정의된 Key를 통해 리소스를 참조합니다. (관리 측면에서는 StaticResource 사용이 유리)

<DockPanel LastChildFill="False" DockPanel.Dock="Bottom">
    <TextBlock Text="{DynamicResource LOCAboutAuthor}" Foreground="LightGray"/>
    ...

Playnite/source/Playnite.DesktopApp/Windows/AboutWindow.xaml at master · JosefNemec/Playnite (github.com)

코드 비하인드에서는 역시 Application.Current.Resources[key] as string 등의 방법으로 정의된 리소스를 참조합니다.

private void StartScan()
{
    foreach (var config in ScannerConfigs)
    {
        if (config.Save && config.Name.IsNullOrWhiteSpace())
        {
            Dialogs.ShowErrorMessage(resources.GetString(LOC.ScanConfigError) + "\n" +
                                     resources.GetString(LOC.ScanConfigNameError), "");

문자열 리소스와 Key를 관리하기 하는 시트와 코드 생성기를 제작하여 Key를 const string으로 참조해서 사용하면 KeyNotFoundException 등을 피할 수 았어 편리합니다.

6 Likes

모두 감사 드립니다.
모든분을 해결책으로 선택 하고 싶은데 안되네요 ㅎㅎ

3 Likes

WPF에서 다국어 처리는 아래 패키지 하나면 끝입니다.
사용하기도 쉽고 리소스 어셈블리 나누기도 쉽고 Blend의 디자인 타임에서 언어 전환도 미리 해 볼 수 있습니다. 저는 10년 넘게 사용하고 있네요.

4 Likes

@Ivory 감사합니다. 한번 써보겠습니다.

1 Like

저는 블레이저에서 IStringLocalizer 사용 중입니다.
이 인터페이스와 그에 대한 구현은 아래의 패키지에서 제공합니다.

NuGet Gallery | Microsoft.Extensions.Localization 8.0.0-rc.1.23421.29

이 구현은 서비스 컨테이너에 등록되어 사용됩니다.

블레이저(Asp.Net Core)는 기본적으로 Microsoft.Extensions.DipendencyInjection 패키지를 포함하고 있기 때문에 서비스 컨테이너 설정이 필요 없지만, WPF 의 경우 아래의 글을 참고하세요.

How to implement Dependency Injection in WPF | ExecuteCommands

링크의 예제 코드에서 IStringLocalization<T> 서비스를 추가하기 위해, 아래 부분을 추가합니다.

// ...
    private void ConfigureServices(ServiceCollection services)
    {
        // ...
       services.AddLocalization();

        // 또는 .resx 파일들을 별도의 폴더에 모아 두고 싶다면(아래에서 다시 설명)
       services.AddLocalization(options =>
       {
           options.ResourcesPath = "{폴더명}";
       });
    }

IStringLocalizer<T> 를 링크된 글의 예제의 MainWindow 에 적용해 보면,

 <Grid>
           <DataGrid Name="EmployeeDG" AutoGenerateColumns="False">
            <DataGrid.Columns>
<!--                
                <DataGridTextColumn Header="Id" Binding="{Binding Id}"/>
                <DataGridTextColumn Header="First Name" Binding="{Binding FirstName}"/>
                <DataGridTextColumn Header="Last Name" Binding="{Binding LastName}"/>
-->
                <DataGridTextColumn x:Name="idColumn" Binding="{Binding Id}"/>
                <DataGridTextColumn x:Name="firstNameColumn" Binding="{Binding FirstName}"/>
                <DataGridTextColumn x:Name="lastNameColumn" Binding="{Binding LastName}"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
public partial class MainWindow : Window
{
    EmployeeDbContext dbContext;

    public MainWindow(EmployeeDbContext dbContext, IStringLocalizer<MainWindow> localizer)
    {
        // ...
        idColumn.Header = localizer["아이디"];
        firstNameColumn.Header = localizer["이름"];
        lastNameColumn.Header = localizer["성"];
    //...

IStringLocalizer<T> 의 가장 중요한 특징은 T.{컬쳐}.resource 파일이나, 그 파일에 키가 없는 경우, 키를 반환한다는 점으로, 동일한 경우 에러를 발생시키는 ResourceManager 와 가장 큰 차이점입니다.
(내부적으로는 ResourceManager에 기반합니다)

예를 들어,

IStringLocalizer<MainWindow> localizer

idColumn.Header = localizer[“아이디”];

localizer는 MainWindow.resource 혹은 MainWindow.kr.resource 파일이 없거나, 파일은 존재하지만, 그 안에 “아이디” 키가 없으면 키인 “아이디” 를 반환하는 것이죠.

즉, 형식 매개 변수 T는 T.({컬쳐}.)resource 파일을 찾는 힌트를 제공하는 것에 지나지 않습니다.

위의 코드에서는 프로젝트에 존재하는 MainWindow 클래스의 이름을 형식 매게 변수로 사용했지만, 아래와 같이 placeholder 클래스를 사용해도 됩니다.

... IStringLocalizer<UIString> localizer...

class UIString {}

이 경우, 자원 파일은 UIString.en.resx가 되겠지요.

보시다시피, IStringLocalizer<T> 를 사용하면, T.resx 혹은 T.kr.resx 파일 - 다시 말하면, 폴백 자원이 프로젝트에 추가된 것과 동일한 효과가 있습니다.

이로 인해, 자원 파일을 신경 쓰지 않고, 메인 언어(한글) 버전을 쉽고 빠르게 완성하고, 완성된 한글 버전을 기준으로 다국어 파일을 만들 수 있게 되죠. 예를 들어, 메인 윈도우에 영어를 추가하는 시점에, 아래의 파일을 프로젝트에 추가하면 됩니다.

MainWindow.en.resx

물론, 이 자원 파일은 IStringLocalization<T> 에서 T 의 종류만큼 추가되어야 합니다.
이 파일들을 한 곳에서 모아 관리하기 위해서는 서비스 등록할 때, 아래의 코드를 쓰는 것입니다.

       services.AddLocalization(options =>
       {
           options.ResourcesPath = "{폴더명}";
       });

저는 보통, IStringLocalization<App> 을 UI 전반에 사용되는 공통 문자열, 예를 들면, 페이지 타이틀 같은 것들을 저장하고, 페이지 별 전용 문자열을 위해 각 페이지 클래스를 형식 매개변수로 사용합니다.

추가된 .resx 파일들을 .resource 로 빌드 - 폴백 자원 바이너리 통합, 위성 어셈블리 생성-하고 배포하는 것은 Localization 시스템이 담당하기에, 개발자는 신경 쓰지 않아도 됩니다.

주의: 예제 코드는 이 닷넷데브의 편집창에서 작성된 것으로 테스트되지 않은 코드입니다.

4 Likes