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

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

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

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

2개의 좋아요

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

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

5개의 좋아요

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

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

4개의 좋아요

기본적으로 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개의 좋아요

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

3개의 좋아요

저렇게 리소스를 구성하면 키 - 값 기준으로 리소스 클래스로 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개의 좋아요

다국어를 지원하는 오픈소스 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개의 좋아요

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

3개의 좋아요

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

4개의 좋아요

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

1개의 좋아요

저는 블레이저에서 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개의 좋아요