지역화 리소스 dll 경로를 지정할 수 있나요?

안녕하세요, 개발 중인 프로그램에서 여러 언어를 지원할 예정입니다.
우선 resx 파일로 ko-KR, en-US 등 언어별 파일을 만들어 string들을 구분하고 UI에 표시하는 것 까지는 크게 문제없었습니다.
다만 bin 폴더에 가보니 exe가 있는 root폴더에 en-US, ko-KR 폴더 안에 각각 xxxx.resources.dll 이렇게 들어가 있더라구요.
프로그램이 대략 18개 국어를 지원해야하는데 언어 리소스 폴더가 너무 많아질 것 같아 한군데 몰아 넣고 싶습니다…
열심히 구글링 해보았으나 실패하여… 경험하신 선배님들의 조언을 듣고자 글 남깁니다.

적어도 Language 폴더 안에 en-US, ko-KR 이렇게라도 할수 있으면 좋겠네요.

뭔가 빌드 시 output 설정이나 어셈블리 참조 경로를 바꿔야하는건가 싶기도 한데 도통 모르겠네요.

읽어주셔서 감사합니다~!

3개의 좋아요

링크 한번 확인해보시겠어요?

probe 엘리먼트로 폴더를 지정할 수 있나봐요

4개의 좋아요

.NET 6 WPF 프로젝트라 그런지 App.config 파일이 없네요…
관련해서 좀 찾아보니 대부분 App.config에서 proving path 를 수정하라고 하긴 하는데
App.config 파일을 그냥 추가한다고 프로젝트와 연동되진 않네요;;

1개의 좋아요

app.config를 대체하는 설정 파일 형식이 무엇인지 찾아 볼 필요가 있겠네요.

1개의 좋아요

.Net core 시리즈부터는 App.config 파일을 지원하지 않는다고 하네요.ConfigurationBuilder를 활용해서 값을 쓰고 읽을 순 있으나 runtime 명령어가 먹히진 않는 듯 합니다.
참고로 전 .NET 6에 WPF 프로젝트입니다.

그래도 어찌어찌 방법을 찾아서 해결한 방법 공유드립니다. 저도 이번에 찾아보면서 여러가지 알게 되었습니다. 우선 빌드한 곳에 가면 AppName.deps.jsonAppName.runtimesconfig.json 2개 파일이 자동으로 생성이 되고 App에서 실행 될 때 필요한 설정이나 참조들을 해당 파일에서 읽어와 실행합니다.

결과적으로 제가 원했던거는 AppName.exe이 참조하는 resource dll 들을 특정 폴더 ex)Libs에 넣고 싶었던거고 이를 위해서는 몇가지 순서가 있습니다.

  1. dll들을 Copy할 Destination 경로를 파악하기 위해 deps.josn 파일을 열어 확인
  2. MSBuild를 사용하여 빌드 완료 후 dll들을 특정폴더에 Move or Copy하는 스크립트 작성
  3. runtimesconfig.json 파일에서 additionalProbingPaths 로 root 경로 지정

위 방법 말고 찾은 방법 몇가지도 소개해드립니다. (테스트 해보진 않음)
NetBeauty2 (종속성 dll들을 하위디렉토리에 알아서 정리해주는 tool)
AssemblyLoadContext (동적 로드를 위한 Context)

MSBuild를 이용한 자세한 방법은 예시로 설명드립니다.

먼저 deps.json 내에서 dll 경로 확인합니다.
[AppName.deps.json]

"targets": {
    ".NETCoreApp,Version=v6.0": {},
    ".NETCoreApp,Version=v6.0/win-x64": {
      "AppName/1.1.0": {
        "dependencies": {
          "CommunityToolkit.Mvvm": "8.0.0",
        },
        "runtime": {
          "AppName.dll": {}
        },
        "resources": {
          "en/AppName.resources.dll": {
            "locale": "en"
          },
          "ko-KR/AppName.resources.dll": {
            "locale": "ko-KR"
          }
        }
      },
      "CommunityToolkit.Mvvm/8.0.0": {
        "runtime": {
          "lib/net6.0/CommunityToolkit.Mvvm.dll": {
            "assemblyVersion": "8.0.0.0",
            "fileVersion": "8.0.0.1"
          }
        }
      },

runtime상 App에서 dll을 참조하는 경로를 deps.json 에서 찾기 때문인데요. 문제는 deps.json 안에있는 경로를 빌드 전 수정할수 없다는 것이고 그래서 2번에서 Copy할 때 경로를 참고해야합니다. (deps.json 내 dll 경로를 수정하는 방법도 있지만 그러면 빌드 할 때마다 바꿔줘야 합니다…)

참고할 dll 경로 (dll Name + runtime 섹션에 있는 dll경로까지가 최종 경로)
AppName/1.1.0/en/AppName.resources.dll
AppName/1.1.0/ko-KR/AppName.resources.dll
CommunityToolkit.Mvvm/8.0.0/lib/net6.0/CommunityToolkit.Mvvm.dll

그리고 이를 토대로 프로젝트 파일을 편집합니다.(.csproj)
Libs 폴더에다가 모두 Copy합니다.

<Project Sdk="Microsoft.NET.Sdk">

	<!-- 빌드 시 Copy -->
	<Target Name="CreateBuildLibs" AfterTargets="Build">
		<Message Importance="high" Text="Build directory = $(TargetDir)" />
		<Message Importance="high" Text="Publish directory = $(PublishDir)" />
		<MakeDir Directories="$(TargetDir)Libs" Condition="!Exists('$(TargetDir)Libs')" />
	</Target>

	<Target Name="MoveDlls" AfterTargets="CreateBuildLibs">
		<Copy SourceFiles="$(TargetDir)CommunityToolkit.Mvvm.dll" DestinationFolder="$(TargetDir)Libs\CommunityToolkit.Mvvm\8.0.0\lib\net6.0" SkipUnchangedFiles="true" />
	</Target>

	<Target Name="MoveResourceDlls" AfterTargets="CreateBuildLibs">
		<Copy SourceFiles="$(TargetDir)en\AppName.resources.dll" DestinationFolder="$(TargetDir)Libs\AppName\1.1.0\en" SkipUnchangedFiles="true" />
		<Copy SourceFiles="$(TargetDir)ko-KR\AppName.resources.dll" DestinationFolder="$(TargetDir)Libs\AppName\1.1.0\ko-KR" SkipUnchangedFiles="true" />
	</Target>

	<!-- 배포 시 Copy -->
	<Target Name="CreatePublishLibs" AfterTargets="Publish">
		<MakeDir Directories="$(PublishDir)Libs" Condition="!Exists('$(PublishDir)Libs')" />
	</Target>

	<Target Name="MovePublishDlls" AfterTargets="CreatePublishLibs">
		<Copy SourceFiles="$(TargetDir)CommunityToolkit.Mvvm.dll" DestinationFolder="$(PublishDir)Libs\CommunityToolkit.Mvvm\8.0.0\lib\net6.0" SkipUnchangedFiles="true" />
	</Target>

	<Target Name="MovePublishResourceDlls" AfterTargets="CreatePublishLibs">
		<Copy SourceFiles="$(TargetDir)en\AppName.resources.dll" DestinationFolder="$(PublishDir)Libs\AppName\1.1.0\en" SkipUnchangedFiles="true" />
		<Copy SourceFiles="$(TargetDir)ko-KR\AppName.resources.dll" DestinationFolder="$(PublishDir)Libs\AppName\1.1.0\ko-KR" SkipUnchangedFiles="true" />
	</Target>

여기까지 하고 빌드하면 Libs 경로에 지정한 DestinationFolder로 dll들이 모두 copy된걸 확인 할 수 있습니다.

그리고 AppName.runtimesconfig.json 을 수정해서 Libs 폴더를 root로 보게 해서 deps.json 이 dll 을 잘 찾아가도록 해야합니다.

[AppName.runtimesconfig.json]

{
  "runtimeOptions": {
    "tfm": "net6.0",
    "frameworks": [
      {
        "name": "Microsoft.NETCore.App",
        "version": "6.0.0"
      },
      {
        "name": "Microsoft.WindowsDesktop.App",
        "version": "6.0.0"
      }
    ],
    "additionalProbingPaths": [
      "Libs"
    ]
  }
}

runtimeOptions 내에 additionalProbingPaths 을 추가하여 "Libs"폴더를 보도록 지정합니다.
다만, 이렇게 되면 빌드 할때마다 runtimesconfig.json 을 열어서 수정해줘야 하니 template 파일을 만들어 option을 추가할 수 있습니다.

.csproj가 있는 동일 경로에 runtimeconfig.template.json 파일을 추가합니다.
[runtimeconfig.template.json]

{
  "additionalProbingPaths": [
    "Libs"
  ]
}

그리고 빌드하게 되면 알아서 runtimeconfig.template.json 을 읽어서 빌드된 경로에 있는 AppName.runtimesconfig.json 에 runtimeOptions 안에 반영합니다.

일단 과정이 길었는데요… 이렇게 까지 해놓고 .csproj 에서 옮길 dll들 경로만 잘 지정해주면 크게 건드리지 않아도 될 것 같습니다.
하다보니 몇가지 단점이 있긴 합니다. (버전에 따른 경로문제, 하위 디렉토리를 변경할 수 없는 문제 등)
그래도 최소한 언어 resource dll을 한곳에 몰아놓고자 하는 목적은 달성했으니 만족하겠습니다 ㅎㅎ

혹시라도 잘못된 정보를 전달하면 어쩌나 해서 다시 테스트도 해보고 출처들도 다시 찾아보고 했네요.

아래는 제가 참고한 출처 및 사이트들입니다.

주요 내용 포함된 Stackoverflow
runtimeconfig.json MS 공식
runtimeconfig.json schema
runtimeconfig.json example
정령 이게 최선인가에 대한 토론

이외에 찾다보니 AssemblySearchPaths 라는 녀석도 알게되어 소개해드립니다.
프로젝트가 참조하는 dll을 지정된 경로로 설정할 수 있는것 같습니다.
프로젝트 참조 dll 설정

누군가에겐 도움이 되었으면 하네요.

7개의 좋아요