수정 이력(7건, 최종 수정 2026년 09월 15일)
이 글에 적용한 변경 사항의 기록입니다. 보관해 둔 수정 전 버전은 DOI가 부여된 고정 URL에서 읽을 수 있습니다.
- front matter의 locale을 ja에서 ko로 바로잡아 이 글이 일본어 블로그 목록이 아니라 한국어 블로그 목록에 실리도록 했습니다. 본문은 바꾸지 않았습니다.
- Codex 리뷰에 따라 상담·문의 링크에 /ko/ 로케일 접두를 붙였습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
- permalink·저자 표기·지식 맵 래퍼·깨진 내부 링크 등 CI가 지적한 표시용 수정을 반영했습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
- 글 맨 앞에 「이 글의 지식 맵」 절을 추가했습니다. 본문에서 다루는 개념과 그 관계를 요약·그림·상세 페이지 링크로 정리한 것입니다. 본문의 주장은 바꾸지 않았습니다.
- 외부 리뷰(1283건)에 대응해 본문을 갱신했습니다. 개별 변경 내용은 아래 이력을 참조하십시오.
- 기본 언어를 선언하는 작성법(AssemblyInfo와 SDK 스타일 모두)과, 실행 중에 언어를 전환하는 경우의 구현 예를 WinForms·WPF 각각에 추가했습니다. 더불어 BAML이나 LocBaml 등 용어 설명, 디자이너 방식의 조작 절차도 추가했습니다.
- 본문에 영어 단어가 섞여 있던 오기를 「화면 구성」으로 고쳤습니다.
- 최초 공개
이 글을 인용하기(DOI(등록된 아카이브): 10.5281/zenodo.21635408)
아래 DOI는 이전에 등록된 아카이브를 가리키며 현재 본문과 다를 수 있습니다. 현재 본문을 참조할 때는 이 페이지의 URL을 사용하세요.
Go Komura (2026). 「WinForms/WPF 앱의 다국어화 ── resx·satellite assembly·culture 전환의 실무」. 합동회사 코무라소프트. https://comcomponent.com/ko/blog/winforms-wpf-localization-guide/
- DOI(등록된 아카이브)
- 10.5281/zenodo.21635408
- DOI(마지막 등록 버전)
- 10.5281/zenodo.21635409
「해외 거점에서도 같은 앱을 쓰고 싶다」「공장 현장에 외국어 화자 직원이 늘어서 화면도 영어로 하고 싶다」──Windows 데스크톱 앱의 다국어화는 요청 자체는 단순하지만, 막상 착수하면 「resx를 어떻게 쓰는가」「WPF는 방식이 여러 가지라 무엇이 정답인지 모르겠다」「실행 중에 언어를 전환할 수 있는가」처럼 판단이 막히는 지점이 이어집니다.
이 글에서는 WinForms/WPF 업무 앱을 전제로, .NET 로컬라이즈의 기반(culture와 리소스 fallback), resx와 satellite assembly의 구조, 프레임워크별 현실적인 방식, 런타임 전환, 그리고 문자열 이외(서식·레이아웃·RTL)의 쟁점까지 실무에서 판단이 막히는 순서로 정리합니다.
1. 먼저 결론(판단표)
다국어화 방식은 프레임워크와 요구사항의 조합으로 정해집니다.
| 상황 | 권장 방식 | 이유 |
|---|---|---|
| WinForms·화면의 레이블/버튼이 중심 | 폼의 Localizable = true + 언어별 resx(디자이너 방식) |
컨트롤의 문자열과 크기·위치를 언어별로 조정할 수 있고, Visual Studio만으로 끝낼 수 있습니다 |
| WinForms/WPF 공통·메시지나 공유 문구가 많음 | 공유 resx(Resources.resx + Resources.ja.resx 등)+ 생성되는 강력한 형식의 클래스 |
문자열을 코드에서 형식 안전하게 참조할 수 있습니다. 화면과 로직에서 같은 문구를 공유할 수 있습니다 |
| WPF | 공유 resx + x:Static 바인딩 |
LocBaml 방식은 .NET(Core)의 WPF에서는 쓸 수 없습니다1. resx 방식이면 WinForms와 지식을 공유할 수 있습니다 |
| 실행 중 언어 전환이 필수 | resx + 변경 알림 구조(또는 화면 재생성) | 방식 선정보다 먼저 「정말로 재시작 없이 전환이 필요한지」를 요구사항으로 확인합니다(제6장) |
| 번역을 외부에 위탁함 | resx 기반으로 설계하고, 키와 번역문의 대응표로 주고받음 | resx는 XML이라 그대로도 넘길 수 있지만, 문맥 정보(어느 화면의 어느 버튼인지)를 붙이는 운용이 품질을 좌우합니다 |
결론을 먼저 적습니다.
CurrentCulture(서식)와CurrentUICulture(리소스 언어)는 별개입니다. 이 구분이 다국어화 설계 전체의 기반이 됩니다(제2장).- 리소스의 실체는 「중립 언어 리소스 + 언어별 satellite assembly」이며, fallback은 자동입니다.
ja-JP→ja→ 중립 순으로 탐색되므로, 번역이 빠져 있어도 앱은 죽지 않고 기본 언어로 표시됩니다2. - WinForms는 디자이너 방식(
Localizable = true)과 공유 resx 방식의 병용이 실무적인 해법입니다. 화면은 디자이너 방식, 메시지 박스나 공통 문구는 공유 resx로 모읍니다(제4장). - WPF에서 공식 Walkthrough에 있는 LocBaml 방식은 .NET(Core)에서는 쓸 수 없습니다1. 현재 실무는 resx+
x:Static(또는 바인딩) 방식이 1순위 후보입니다(제5장). - 런타임 언어 전환은 「다음 시작 시에 반영」을 1순위 후보로 둡니다. 표시 중인 화면의 리소스는 자동으로 바뀌지 않기 때문입니다(제6장).
- 문자열 번역은 다국어화의 절반에 불과합니다. 번역 후 문자열 길이에 맞춘 레이아웃 추종, 날짜·숫자의 서식, 글꼴, 오른쪽에서 왼쪽으로 쓰는 언어(RTL) 대응이 나머지 절반입니다(제7장).
그림의 실선은 항상 성립하는 관계, 점선은 조건이 붙는 관계입니다(성립 조건은 상세 페이지의 관계별 설명에 적혀 있습니다). 관계 전체 목록(총 23건, 근거와 확신도 포함)과 주요 개념의 정의는 지식 맵 상세 페이지에 정리되어 있습니다(일본어). 데이터: JSON-LD / Turtle
2. 기반 ── CurrentCulture와 CurrentUICulture의 차이
.NET의 culture(문화권 설정)에는 역할이 다른 두 속성이 있습니다3.
| 속성 | 정하는 것 | 예 |
|---|---|---|
CultureInfo.CurrentCulture |
날짜·숫자·통화의 서식 | 2026/07/07로 표시할지 7/7/2026로 표시할지 |
CultureInfo.CurrentUICulture |
어느 언어의 리소스를 읽을지 | 버튼에 「저장」을 낼지 「Save」를 낼지 |
이 둘은 독립적으로 설정할 수 있습니다. 예를 들어 「화면은 영어, 다만 날짜나 통화는 일본 서식」이라는 조합(일본 국내의 외국어 화자용 업무 앱에서 흔한 요구사항)은 CurrentUICulture = en 그리고 CurrentCulture = ja-JP로 자연스럽게 표현할 수 있습니다.
기본값은 둘 다 OS 설정에서 오므로, 「아무것도 하지 않으면 OS를 따른다」가 출발점입니다. 앱 전체에서 언어를 명시적으로 전환하는 경우는 스레드마다 설정하지 않고, 기본값을 바꿉니다.
using System.Globalization;
// 이후에 만들어지는 모든 스레드의 기본 culture를 바꿉니다
// (UI 스레드 생성 전=Application.Run 전에 실행합니다)
CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("en"); // 리소스 언어
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("ja-JP"); // 서식
Thread.CurrentThread.CurrentUICulture에 대한 대입은 그 스레드에만 적용됩니다. async/await나 백그라운드 처리로 스레드를 넘나드는 업무 앱에서는 DefaultThreadCurrentUICulture / DefaultThreadCurrentCulture로 기본값째 바꾸는 편이 사고가 없습니다3.
참고로 로그·파일 출력·시스템 간 연동의 서식은 사용자의 culture를 따르게 하지 않고 CultureInfo.InvariantCulture에 고정합니다. 날짜 서식 설계 전반은 「업무 앱의 일시·타임존 설계」를 참조하십시오.
3. 리소스의 구조 ── resx·satellite assembly·fallback
.NET 로컬라이즈는 「허브 앤 스포크」 모델입니다. 기본 언어(중립) 리소스를 본체 assembly에 임베드하고, 언어별 리소스는 satellite assembly라는 별도 DLL로 분리합니다4.
빌드하면 언어별 resx는 다음과 같은 출력 구성이 됩니다.
MyApp.exe ← 본체(중립 리소스를 포함)
MyApp.dll
ja/MyApp.resources.dll ← 일본어 satellite assembly
en/MyApp.resources.dll ← 영어 satellite assembly
실행 시 ResourceManager는 CurrentUICulture를 기점으로 특정 culture(ja-JP)→중립 culture(ja)→기본(본체) 순으로 리소스를 찾습니다2. 이 자동 fallback 덕분에 번역이 1건 빠져 있어도 앱은 죽지 않고, 기본 언어 문자열이 표시됩니다. 반대로 말하면 「영어 화면이어야 하는데 일부만 일본어가 섞인다」는 번역 누락의 신호이며, 테스트 관점으로 쓸 수 있습니다(제8장).
실무에서는 다음 두 가지를 짚어 둡니다.
- 기본 언어가 어느 culture인지
NeutralResourcesLanguage특성으로 선언해 두면, 기본 culture일 때 satellite를 찾으러 가는 낭비가 없어지고, fallback의 종점이 명확해집니다4. - satellite assembly는 「본체와 함께 배포해야 비로소 동작하므로」, 설치 프로그램이나 복사 배포 설계에 언어 폴더를 포함해야 합니다(제8장).
NeutralResourcesLanguage 선언 방법은 프로젝트 형식에 따라 두 가지입니다. 먼저 AssemblyInfo.cs를 직접 갖는 구형식(.NET Framework의 기본)인 경우입니다.
// Properties/AssemblyInfo.cs
using System.Resources;
// 본체 assembly에 임베드한 리소스가 일본어임을 선언합니다
[assembly: NeutralResourcesLanguage("ja-JP")]
SDK 스타일 프로젝트(.NET 5 이후의 기본)에서는 AssemblyInfo.cs가 빌드 시 자동 생성되므로, 프로젝트 파일 쪽 속성으로 지정합니다.
<!-- MyApp.csproj -->
<PropertyGroup>
<NeutralLanguage>ja-JP</NeutralLanguage>
</PropertyGroup>
Visual Studio에서는 프로젝트 속성의 [패키지] > [일반]에 있는 「어셈블리의 중립 언어」로 같은 설정을 할 수 있습니다. 참고로 SDK 스타일 프로젝트에서 둘 다 쓰면 특성이 이중 정의되어 빌드 오류(CS0579)가 됩니다. 어느 한쪽만 하십시오.
4. WinForms의 실무 ── 디자이너 방식과 공유 resx 방식
WinForms에는 두 방식이 있으며, 실무에서는 병용합니다.
4.1 화면은 디자이너 방식(Localizable = true)
폼의 속성에서 Localizable을 true로 하고, Language 속성을 대상 언어(예: 영어)로 전환한 뒤 레이블이나 버튼의 Text를 편집하면, Visual Studio가 Form1.en.resx 같은 언어별 resx를 자동 생성합니다5. 절차를 조작 순으로 적으면 다음과 같습니다.
- 디자이너에서 폼을 열고, 컨트롤이 아니라 폼 자체(배경 부분)를 클릭해 선택합니다
- 속성 창(F4)에서
Localizable을True로 합니다. 이 시점에 컨트롤의 문자열·크기·위치가 기본 언어용Form1.resx에 기록됩니다 - 같은 속성 창의
Language를 「(기본값)」에서 대상 언어(예: English)로 바꿉니다 - 그 상태에서 레이블이나 버튼의
Text·크기·위치를 편집합니다. 편집 내용은Form1.en.resx에 저장되고, 기본 언어 resx는 바뀌지 않습니다 - 작업이 끝나면
Language를 「(기본값)」으로 되돌립니다
5를 잊은 채 일반적인 레이아웃 변경을 이어가면, 그 변경이 기본 언어가 아니라 편집 중인 언어의 resx에만 들어가 「일본어일 때만 버튼 위치가 이상하다」는 형태로 나중에 드러납니다. 디자이너 방식을 쓰는 팀에서는 Language를 되돌리는 일을 먼저 공유해 두십시오.
이 방식의 강점은 문자열뿐 아니라 컨트롤의 크기·위치도 언어별로 저장된다는 점입니다. 일본어 「등록」이 영어 「Register」가 되어 버튼에서 넘치는 문제를 디자이너에서 언어마다 조정할 수 있습니다. 한편 언어 수×폼 수만큼 resx가 늘고, 폼 레이아웃을 바꿀 때마다 모든 언어를 확인해야 하는 보수 비용도 생깁니다. 언어 수가 늘 전망이 있다면, 다음 레이아웃 쪽 대책(제7장)으로 「조정하지 않아도 깨지지 않는」 화면 구성을 우선하십시오.
4.2 메시지·공통 문구는 공유 resx 방식
메시지 박스 문구, 확인 대화상자, 상태 표시줄 표시처럼 코드에서 쓰는 문자열은 프로젝트 공통 Resources.resx에 모으고, 언어별로 Resources.en.resx 등을 추가합니다. resx에서는 강력한 형식의 클래스가 자동 생성되므로, 코드에서는 키를 오타 없이 참조할 수 있습니다.
// 생성된 강력한 형식의 클래스를 거쳐 참조합니다.
// 반환 문자열은 CurrentUICulture에 따라 자동으로 바뀝니다
MessageBox.Show(
Properties.Resources.ConfirmDeleteMessage,
Properties.Resources.ConfirmTitle,
MessageBoxButtons.YesNo);
여기서 가장 중요한 규칙은 사용자에게 보이는 문자열을 코드에 직접 쓰지 않는 것입니다. 다국어화 공수는 구조 도입이 아니라 「흩어진 직접 작성 문자열을 거둬들이는 일」에 쓰입니다. 1개 언어로 운영하는 단계에서도 resx를 거치게 해 두는 가치가 여기에 있습니다.
5. WPF의 실무 ── LocBaml이 아니라 resx를 1순위 후보로
이 장에 나오는 용어를 먼저 정리합니다.
| 용어 | 읽기·정식 명칭 | 의미 |
|---|---|---|
| BAML | Binary Application Markup Language | XAML을 빌드 시 컴파일한 이진 형식. WPF 앱에서는 XAML 자체가 아니라 이 BAML이 assembly에 임베드되어 런타임에 읽힙니다 |
| LocBaml | ── | BAML에서 번역 대상 문자열을 뽑고, 번역문을 끼워 넣어 언어별 BAML을 다시 만드는 .NET Framework 시절의 샘플 도구1 |
x:Static |
── | XAML에서 C# 쪽 정적 속성·필드·상수의 값 자체를 읽기 위한 표기. 읽기는 화면이 구축되는 순간의 1회뿐이며, 나중에 값이 바뀌어도 화면에 반영되지 않습니다 |
| satellite assembly | ── | 언어별 리소스만 담은 언어코드/MyApp.resources.dll(제3장) |
WPF 다국어화는 정보가 엇갈리기 쉬운 영역입니다. 공식 문서의 Walkthrough에는 UICulture를 프로젝트 파일에 설정해 BAML 리소스를 만들고 LocBaml 도구로 번역을 끼워 넣는 방식이 실려 있지만, LocBaml은 .NET Framework의 WPF 전용 샘플 도구이며 .NET(Core)의 WPF에서는 동작하지 않습니다1. 지금 새로 고르는 방식은 아니라고 보십시오.
실무의 1순위 후보는 WinForms와 같은 공유 resx 방식을 XAML에서 참조하는 형태입니다.
<!-- resx의 액세스 한정자를 Public으로 한 뒤 x:Static으로 참조합니다 -->
<Window xmlns:res="clr-namespace:MyApp.Properties"
Title="{x:Static res:Resources.MainWindowTitle}">
<Button Content="{x:Static res:Resources.SaveButton}" />
</Window>
이 방식의 이점은 리소스 구조(satellite assembly·fallback)가 WinForms와 완전히 공통이고, 혼재 솔루션에서도 문구를 한곳에서 관리할 수 있다는 점입니다. x:Static은 시작 시 해석되는 정적 참조이므로, 런타임 전환이 필요하면 바인딩+변경 알림으로 발전시킵니다(제6장).
XAML 쪽에서는 더불어 루트 요소의 xml:lang을 설정해 두면, hyphenation이나 글꼴 fallback, 숫자 표시 등 WPF의 언어 의존 기능이 올바르게 동작합니다1.
WinForms/WPF 중 어느 쪽으로 만들지 판단 자체는 「WinForms / WPF / WinUI 구분 판단표」를 참조하십시오.
6. 런타임 언어 전환 ── 「다음 시작 시에 반영」을 1순위 후보로
설정 화면에서 언어를 고르게 하는 요구사항에서는, 반영 시점을 두 가지 안 중에서 고릅니다.
- 안 A: 다음 시작 시에 반영(권장). 선택한 언어를 설정 파일에 저장하고, 시작 시
Application.Run전(WPF라면Application시작 전)에DefaultThreadCurrentUICulture를 설정합니다. 구현이 단순하고, 화면 재구축에 따른 장애(입력 중 데이터 손실, 이벤트 핸들러 이중 등록 등)가 구조적으로 일어나지 않습니다. - 안 B: 즉시 반영.
CurrentUICulture를 바꿔도 이미 표시된 화면의 문자열은 자동으로 바뀌지 않습니다. WinForms 디자이너 방식이라면ComponentResourceManager.ApplyResources로 열린 폼에 다시 적용하고, WPF라면 문자열을 바인딩+변경 알림으로 공급하는 식으로, 어느 쪽이든 「모든 화면이 리소스 재적용에 대응하고 있다」는 전제의 구현이 됩니다.
업무 앱에서는 언어를 바꾸는 빈도(보통 도입 시 1회)에 비해 안 B의 구현·테스트 비용이 맞지 않는 경우가 많아, 안 A를 기본으로 하고 안 B는 요구사항으로 정말 필요할 때만 하는 것을 권합니다. 언어 설정의 저장 위치는 사용자별 설정이므로 %LOCALAPPDATA% 아래가 적절합니다. 설정 위치 판단은 「Windows 업무 앱의 구성 관리 실무」의 판단표를 참조하십시오.
6.1 언어 설정의 저장과 읽기(안 A의 구현)
안 A는 다음 정도의 코드면 충분합니다. 저장 위치는 Environment.SpecialFolder.LocalApplicationData(= %LOCALAPPDATA%) 아래에 자체 폴더를 만듭니다.
using System;
using System.Globalization;
using System.IO;
internal static class LanguageSetting
{
private static readonly string Dir = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"MyCompany", "MyApp");
private static readonly string FilePath = Path.Combine(Dir, "language.txt");
// 설정 화면에서 고른 언어를 저장합니다(예: "en", "ja-JP")
public static void Save(string cultureName)
{
Directory.CreateDirectory(Dir);
File.WriteAllText(FilePath, cultureName);
}
// 시작 시 Application.Run(WPF라면 Application 시작)보다 앞에서 호출합니다.
// 저장이 없거나 깨진 경우는 아무것도 하지 않습니다 = OS 표시 언어를 따릅니다
public static void ApplyOnStartup()
{
if (!File.Exists(FilePath)) return;
var name = File.ReadAllText(FilePath).Trim();
if (name.Length == 0) return;
try
{
CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo(name);
}
catch (CultureNotFoundException)
{
// 알 수 없는 culture 이름입니다. 설정을 무시하고 OS 표시 언어 그대로 둡니다
}
}
}
핵심은 설정 파일이 없는 상태를 「OS를 따른다」는 의미로 둬 두는 것입니다. 최초 시작 시나 파일이 깨졌을 때 예외로 멈추지 않고, CurrentUICulture의 기본값(제2장)이 그대로 적용됩니다.
6.2 안 B(즉시 반영)를 구현하는 경우의 최소 형태
요구사항상 어떻게든 즉시 반영이 필요한 경우에 대비해, 구현의 골격을 보입니다. 어느 쪽이든 「모든 화면이 이 구조 위에 올라타 있다」는 전제라는 점은 같습니다.
WinForms에서 디자이너 방식(Localizable = true)을 쓰는 경우는 ComponentResourceManager로 언어별 resx를 열린 폼에 다시 적용합니다.
using System.ComponentModel;
using System.Globalization;
using System.Windows.Forms;
public partial class MainForm : Form
{
// 언어 전환 후, 열린 각 폼에서 호출합니다
public void ApplyCurrentLanguage(CultureInfo culture)
{
CultureInfo.DefaultThreadCurrentUICulture = culture;
CultureInfo.CurrentUICulture = culture; // 실행 중인 UI 스레드에도 적용합니다
var resources = new ComponentResourceManager(GetType());
// 폼 자신(제목·크기)은 "$this"라는 키로 저장되어 있습니다
resources.ApplyResources(this, "$this");
ApplyRecursive(Controls, resources);
}
private static void ApplyRecursive(
Control.ControlCollection controls, ComponentResourceManager resources)
{
foreach (Control control in controls)
{
resources.ApplyResources(control, control.Name);
ApplyRecursive(control.Controls, resources); // 중첩된 패널도 따라갑니다
}
}
}
이 재귀는 Control만 따라가므로, 메뉴나 도구 모음 항목(ToolStripItem은 Control이 아닙니다)은 별도로 ToolStrip.Items를 따라가 같은 처리를 걸어야 합니다. 「메뉴만 언어가 바뀌지 않는다」는 이것이 원인입니다.
WPF에서는 x:Static(값을 1회만 읽는 표기)에서 바인딩으로 바꾸고, 언어 전환 시 변경 알림을 던집니다.
using System.ComponentModel;
using System.Globalization;
using System.Windows.Data;
// 문자열 공급원. XAML에서는 이 인스턴스에 바인딩합니다
public sealed class Loc : INotifyPropertyChanged
{
public static Loc Instance { get; } = new Loc();
private Loc() { }
// 인덱서를 거쳐 resx 키를 꺼냅니다. 없으면 키 이름을 그대로 반환합니다
public string this[string key] =>
Properties.Resources.ResourceManager.GetString(key, CultureInfo.CurrentUICulture) ?? key;
public event PropertyChangedEventHandler PropertyChanged;
public void ChangeLanguage(CultureInfo culture)
{
CultureInfo.DefaultThreadCurrentUICulture = culture;
CultureInfo.CurrentUICulture = culture;
// 인덱서 전체의 변경 알림. Binding.IndexerName의 내용은 "Item[]"
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(Binding.IndexerName));
}
}
<!-- resx의 액세스 한정자는 Internal 또는 Public으로 둡니다 -->
<Window xmlns:loc="clr-namespace:MyApp">
<Button Content="{Binding Path=[SaveButton], Source={x:Static loc:Loc.Instance}}" />
</Window>
제5장의 x:Static 직접 참조와의 차이는 바인딩이므로 변경 알림을 받을 수 있다는 점뿐입니다. 뒤쪽의 resx와 satellite assembly 구조는 같으므로, 나중에 안 A에서 안 B로 옮기는 경우에도 리소스를 다시 만들 필요는 없고, XAML의 참조 방법을 바꾸는 작업이 됩니다.
7. 문자열 이외의 다국어화 ── 레이아웃·서식·글꼴·RTL
번역이 끝나도 다국어화는 아직 절반이 남습니다.
- 레이아웃의 신축. 같은 뜻의 문구라도 언어에 따라 길이가 크게 달라집니다(일본어 「저장」→독일어 「Speichern」). 고정 좌표·고정 크기 레이아웃은 번역할 때마다 깨지므로, WinForms에서는
TableLayoutPanel과AutoSize를 조합해 문자열 길이를 따라가는 레이아웃으로 만드는 것이 공식에서 안내하는 방법입니다6. WPF는 레이아웃 시스템이 원래 가변이므로, 너비를 고정하지 않기만 해도 대부분은 대응할 수 있습니다. 더불어 고 DPI 환경에서의 깨짐 대책(「WinForms 고 DPI 대응 가이드」「WPF 고 DPI 대응 가이드」)과 함께 설계하면 재작업이 없습니다. - 날짜·숫자·통화의 서식. 화면 표시는
CurrentCulture에 맡기고, 독자적인ToString("yyyy/MM/dd")고정을 화면 코드에서 줄입니다. 반대로 파일·로그·연동 데이터는InvariantCulture에 고정합니다(제2장). - 글꼴. 대상 언어의 문자가 글꼴에 포함되는지 확인합니다. 중국어(간체/번체)나 한국어를 추가하는 경우, 일본어용 글꼴 지정 그대로면 자형이 부자연스러워질 수 있습니다(이른바 CJK 자형 문제). UI 글꼴 지정을 한곳에 모아 두면, 언어 추가 시 변경이 수월해집니다.
- 오른쪽에서 왼쪽으로 쓰는 언어(RTL). 아랍어·히브리어 대응에서는 문자열 번역에 더해 화면의 좌우 반전이 필요합니다. WinForms는
RightToLeft/RightToLeftLayout속성, WPF는FlowDirection속성으로 제어합니다. RTL 대응은 「나중에 붙이면」 모든 화면을 다시 보게 되므로, 대상 언어에 들어갈 가능성이 있다면 처음부터 요구사항에 넣으십시오. - 이미지·아이콘 안의 글자. 이미지에 구운 문자열은 리소스 구조로는 바뀌지 않습니다. 글자가 들어간 이미지를 피하고, 이미지+텍스트 레이블 구성으로 두는 것이 안전합니다.
UI 설계 전반의 판단은 「Windows 앱의 UX 설계 판단표」도 참조하십시오.
8. 운용 ── 번역 주고받기·누락 검출·배포
마지막으로, 구조가 만들어진 뒤의 운용 면입니다.
- 번역 주고받기에는 문맥을 붙입니다. resx의 키와 원문만 넘기면 「OK」「열기」처럼 짧은 문구의 번역 품질이 안정되지 않습니다. 키·원문·번역문에 「표시되는 화면과 용도」를 붙인 대응표(스프레드시트 등)로 주고받고, 수령 후 resx에 반영하는 운용이 현실적입니다.
- 번역 누락은 fallback으로 찾습니다. 리소스 fallback에 의해 번역 누락은 「그 언어 화면에 기본 언어가 섞인다」는 형태로 나타납니다2. 언어별 resx의 키 목록을 중립 resx와 맞춰 보는 간단한 검사(스크립트나 테스트 코드)를 CI에 넣어 두면, 릴리스 전에 기계적으로 검출할 수 있습니다.
- 배포에 satellite assembly를 포함합니다.
ja/en/등 언어 폴더째 배포하지 않으면, 그 언어만 fallback되어 「번역했는데 반영되지 않는다」는 상태가 됩니다. 수동 복사에서 언어 폴더가 빠지는 것은 단골 사고이므로, 설치 프로그램(MSI/MSIX 등)에 디렉터리 구조째 넣습니다. ClickOnce 배포에서는 기본으로 모든 언어의 satellite assembly가 배포에 포함됩니다7. 배포 방식 전체의 판단은 「Windows 앱의 배포 방법 고르기」를 참조하십시오.
정리
데스크톱 앱의 다국어화는 resx와 satellite assembly라는 오래 검증된 구조 위에 서 있으며, 구조 자체는 어렵지 않습니다. 실무의 성패를 가르는 것은 CurrentCulture와 CurrentUICulture를 구분한 설계, 사용자에게 보이는 문자열을 직접 쓰지 않는 규율, 번역 후 문자열 길이를 견디는 레이아웃, 그리고 「즉시 전환이 정말 필요한가」라는 요구사항의 가늠입니다. WPF에서는 오래된 LocBaml 방식 정보에 끌려가지 말고, resx 기반으로 짜는 것이 현재의 실무적인 해법입니다.
기존 앱의 다국어화 방식 선정과 영향 범위 산정, 해외 거점 전개를 위한 화면·장표·연동 데이터의 다국어 대응은 코드베이스 현황을 보면서 판단해야 하는 경우가 많으므로, 막히면 상담해 주십시오.
관련 글
- WinForms / WPF / WinUI 구분 판단표
- Windows 앱의 UX 설계 판단표
- 업무 앱의 일시·타임존 설계
- WinForms 고 DPI 대응 가이드
- WPF 고 DPI 대응 가이드
- appsettings.json만이 아니다 ── Windows 업무 앱의 구성 관리 실무
- Windows 앱의 배포 방법 고르기(MSI / MSIX / ClickOnce / xcopy / 자체 업데이터)
- Windows 사용자 프로필의 구조
관련 상담 영역
合同会社小村ソフト에서는 WinForms/WPF 앱의 다국어 대응 설계·구현, 기존 앱을 나중에 다국어화할 때의 방식 선정과 영향 범위 정리에 관한 기술 상담을 다룹니다.
참고 링크
-
Microsoft Learn, WPF Globalization and Localization Overview. 프로젝트 파일의
UICulture설정에 의한 satellite assembly 생성, 루트 요소의xml:lang의 역할, 그리고 LocBaml이 .NET Framework의 WPF 전용 샘플 도구이며 .NET의 WPF에서는 동작하지 않는다는 점에 대해. ↩ ↩2 ↩3 ↩4 ↩5 -
Microsoft Learn, Retrieve resources in .NET apps. 리소스 fallback 프로세스(특정 culture→중립 culture→메인 assembly의 기본 리소스 순으로 탐색된다는 점)에 대해. ↩ ↩2 ↩3
-
Microsoft Learn, CultureInfo.CurrentUICulture Property.
CurrentUICulture가 ResourceManager의 culture 고유 리소스 탐색에 쓰인다는 점, 기본값이 OS의 사용자 인터페이스 언어에서 온다는 점,DefaultThreadCurrentUICulture로 모든 스레드 기본값을 설정하는 점에 대해. ↩ ↩2 -
Microsoft Learn, Resources in .NET apps. resx 리소스의 작성·패키징, 메인 assembly와 satellite assembly에 의한 허브 앤 스포크 모델,
NeutralResourcesLanguage특성에 대해. ↩ ↩2 -
Microsoft Learn, Walkthrough: Localizing a Hybrid Application. Windows Forms 디자이너에서
Localizable속성을true로 하고Language속성을 전환해 언어별 resx(예: Form1.es-ES.resx)를 생성하는 절차,InitializeComponent호출 전에CurrentUICulture를 설정하는 예에 대해. ↩ -
Microsoft Learn, How to: Design a Windows Forms Layout that Responds Well to Localization.
TableLayoutPanel을 써서, 로컬라이즈에 따른Text속성의 문자열 길이 변화에 컨트롤 크기가 따라가는 레이아웃을 만드는 방법에 대해. ↩ -
Microsoft Learn, Localize ClickOnce applications. 모든 satellite assembly를 하나의 배포에 넣는 방식이 Visual Studio의 기본이라는 점, 런타임에 OS culture에 맞는 satellite assembly가 쓰인다는 점에 대해. ↩
관련 기사
같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.
Windows 프린터 드라이버 제공 종료 ── 업무 앱의 장표·라벨 인쇄는 어떻게 대비할 것인가
Microsoft는 v3/v4 프린터 드라이버의 제공 종료를 단계적으로 진행하고 있으며, 2026년 7월부터는 IPP 클래스 드라이버가 우선됩니다. Windows protected print mode에서 무엇이 사라지는지, 업무 앱의 장표·라벨 ...
Windows 업무 앱의 인쇄와 PDF 출력 ── System.Drawing.Printing / WPF / 장표 라이브러리 구분
PrintDocument로 하는 WinForms 인쇄, WPF의 FlowDocument/FixedDocument 인쇄, PDF 출력 선택지를 요건별 판단표로 정리합니다. 페이지 나눔 제어, DPI 어긋남, Windows 서비스에서의 인쇄 위험까지...
Windows 앱의 작업 트레이 상주와 토스트 알림 ── NotifyIcon의 함정과 AppNotification 고르는 법
업무용 Windows 앱의 작업 트레이 상주와 토스트 알림 구현을 정리합니다. NotifyIcon의 올바른 사용법, Explorer 재시작 시 재등록, 토스트 API 3종의 선정 판단표, 알림이 도착하지 않는 경우까지 설명합니다.
WinForms / WPF 앱의 CI/CD 실전 ── GitHub Actions로 빌드부터 서명·배포까지 자동화하기
WinForms / WPF 앱의 CI/CD를 GitHub Actions로 구성하는 실무 가이드입니다. windows-latest에서 빌드+테스트를 돌리는 최소 YAML, 태그 기반 버전 부여, signtool 서명 연동, MSI/MSIX/Clic...
CSV는 「그냥 텍스트」가 아닙니다 ── C# 업무 앱의 CSV 실무(문자 코드·Excel 호환·인젝션 대책)
업무 앱 CSV 입출력의 전형적인 사고──Split(',') 자체 파싱, BOM 없는 UTF-8의 문자 깨짐, 앞자리 0 소실──을 정리하고, RFC 4180 규칙, Shift_JIS 취급, TextFieldParser를 이용한 안전한 읽기까지 ...
관련 토픽
이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.
Windows 기술 토픽
Windows 개발, 장애 조사, 기존 자산 활용에 관한 KomuraSoft LLC 기사를 모은 토픽 허브입니다.
UI 스레드 & 타이머
WPF / WinForms UI 스레드, async 흐름, Dispatcher 사용, 타이머 판단을 정리한 토픽 페이지입니다.
이 주제와 연결되는 서비스
이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.
Windows 앱 개발
데스크톱 앱의 다국어 대응 설계와 구현은 Windows 앱 개발의 실무 상담 범위이기 때문입니다.
기술 상담 & 설계 리뷰
기존 앱을 나중에 다국어화할 때의 방식 선정과 영향 범위 산정은 설계 리뷰를 동반하는 기술 상담에 해당하기 때문입니다.
자주 묻는 질문
이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.
- 일본어만으로 만든 기존 앱을 나중에 다국어화할 수 있습니까?
- 가능합니다만, 작업의 대부분은 「리소스 구조를 도입하는 일」이 아니라 「코드에 직접 쓴 문자열을 찾아내 교체하는 일」입니다. 화면 문자열뿐 아니라 메시지 박스, 로그, 예외 메시지, 장표, DB에 저장한 표시용 문자열까지 점검해야 하고, 규모는 화면 수보다 문자열이 얼마나 흩어져 있는지에 따라 정해집니다. 신규 개발 단계부터 사용자에게 보이는 문자열만이라도 resx를 거치도록 두면, 이후 다국어화 비용이 자릿수가 달라질 정도로 줄어듭니다.
- 번역은 기계 번역으로 끝내도 됩니까?
- 사내 도구나 임시 대응이라면 기계 번역부터 시작하는 것은 현실적입니다. 다만 업무 앱은 버튼이나 메뉴의 짧은 단어(예: 「등록」「취소」)가 많고, 문맥이 없으면 기계 번역의 오역률이 높아지는 영역입니다. 최소한 해당 언어를 실제로 쓰는 사람이 화면을 만져 보는 리뷰를 한 바퀴 넣는 것을 권합니다. 또한 번역 품질보다 먼저 「번역 후 긴 문자열에도 레이아웃이 깨지지 않는지」를 TableLayoutPanel이나 AutoSize로 미리 확보해 두는 편이 더 중요합니다.
- 로그나 오류 메시지도 번역해야 합니까?
- 사용자 화면에 내는 메시지는 번역하고, 로그 파일이나 이벤트 로그에 쓰는 내용은 번역하지 않는(언어를 고정하는) 것이 실무의 정석입니다. 로그는 사용자가 아니라 개발자·운영자가 읽는 것이며, 다국어 로그는 조사 시 검색성을 해칩니다. 서식(날짜·숫자)도 로그는 CultureInfo.InvariantCulture에 고정하십시오. CurrentCulture와 CurrentUICulture를 나눠 다루는 설계로 두면, 이 구분이 자연스럽게 이루어집니다.
- OS 표시 언어와 별도로, 앱만 언어를 전환할 수 있게 해야 합니까?
- 해외 거점 전개나, 일본 국내에서 외국어 화자 직원이 쓰는 업무 앱에서는 앱 내 언어 설정을 두는 가치가 큽니다. 기본은 OS 표시 언어를 따르고(CurrentUICulture의 기본값), 설정 화면에서 명시적으로 고른 경우에만 덮어쓰는 2단 구성이 무난합니다. 전환 반영은 「다음 시작 시」를 사양으로 하는 것이 가장 안전하고, 구현과 운영 설명도 단순해집니다.