.NET 8 の WPF アプリをビルドしたら、bin\Debug\net8.0[-windows] 配下に cs / es / tr … といった多言語フォルダーと Microsoft.CodeAnalysis.***.resources.dll がズラリ――この正体は何か、なぜ出力されるのか、どうすれば安全かつ恒久的に止められるのかを、実務の視点で整理します。設定例・副作用・検証方法までまとめて解説します。
.NET 8 WPF のビルドで多言語フォルダーが増殖する理由
ビルド直後の出力ディレクトリで次のような構造を目にすることがあります。
bin\
└─ Debug\
└─ net8.0-windows\
├─ MyApp.exe
├─ cs\
│ └─ Microsoft.CodeAnalysis.Workspaces.resources.dll
├─ es\
│ └─ Microsoft.CodeAnalysis.CSharp.resources.dll
├─ tr\
│ └─ Microsoft.CodeAnalysis.Features.resources.dll
├─ de\
│ └─ ...
└─ ほか多数...
これらの 言語フォルダーは、アセンブリのローカライズされた文字列(メッセージ、例外文言、UI テキストなど)を格納する サテライトアセンブリ(satellite assemblies)です。CLR(.NET ランタイム)は起動時やリソース取得時に ja / en-US / cs … といったカルチャ名のフォルダーを探索し、該当の *.resources.dll があれば優先的に使います。該当がなければニュートラルカルチャ(多くは英語)へフォールバックします。
WPF アプリ自身が多言語対応をしていなくても、参照している NuGet パッケージがローカライズ済みであれば、SDK の標準動作としてそれらのサテライト DLL が 自動でコピーされます。特に下記のようなパッケージを参照していると発生しやすくなります。
Microsoft.VisualStudio.Web.CodeGeneration.Design(ASP.NET のスキャフォールディング支援)- 同パッケージが内部依存する Roslyn 系コンポーネント(
Microsoft.CodeAnalysis.*)
この結果、Microsoft.CodeAnalysis.***.resources.dll が各言語フォルダーに大量出力され、ビルド出力が散らかって見える、サイズが増える、差分がノイズになる――といった困りごとに繋がります。
最短の解決策:SatelliteResourceLanguages で “残す言語” を宣言する
.NET 6 以降(.NET Core 以降)では、MSBuild プロパティ SatelliteResourceLanguages により、出力先へコピーされるサテライトアセンブリの言語を制御できます。WPF/.NET 8 でも有効です。プロパティは ビルドと発行の双方に効くため、スクリプトで削除する必要がありません。
全カルチャを抑止したい(フォルダー自体を出さない)
存在しないカルチャ名を 1 つ指定すると、すべてのサテライトアセンブリがコピー対象から外れます。XAML やアプリ本体にローカライズがなく、依存ライブラリの多言語も不要な場合に最も手早い方法です。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<!-- 存在しないカルチャを指定して全カルチャのコピーを抑止 -->
<SatelliteResourceLanguages>zz</SatelliteResourceLanguages>
</PropertyGroup>
</Project>
ここでは慣例的に zz などの架空カルチャを用いています。指定したカルチャが見つからなければ何もコピーされないという振る舞いを利用して、結果的に cs / es / tr … のフォルダーが一切作られなくなります。
特定のカルチャのみを残したい(例:日本語と英語)
必要なものだけをセミコロン区切りで列挙します。プロダクトが日本語 UI と英語 UI を提供している、といったケースに向いています。
<PropertyGroup>
<SatelliteResourceLanguages>ja;en;en-US</SatelliteResourceLanguages>
</PropertyGroup>
カルチャ名は ja-JP のような地域指定や、en のような言語単位の指定も可能です。列挙したカルチャ 以外のフォルダーは出力されません。
ポイントと注意
- このプロパティは アプリ自身のローカライズだけでなく、参照パッケージのサテライトにも適用されます。
- サテライトが見つからない場合、.NET の フォールバックによりニュートラルリソース(通常は英語)が使われます。エラーにはなりません。
- 将来的に多言語化する予定があるなら、必要カルチャを明示的に列挙しておくと運用が安定します。
もう一つの根本策:発生源のパッケージを外す/実行時資産を除外する
多言語 DLL の大半が Roslyn 由来で、実際にはアプリ実行時に不要なことが分かっている場合、パッケージ参照そのものを見直すのも効果的です。
そもそも使っていなければアンインストール
WPF プロジェクトで Microsoft.VisualStudio.Web.CodeGeneration.Design を使っていないなら、NuGet 参照を削除するだけで依存チェーンが解消され、Roslyn 系のサテライト DLL も出力されなくなります。ASP.NET Core のスキャフォールディング用であり、WPF 単体では通常不要です。
使っているが実行時には不要な場合:ExcludeAssets=runtime
設計時ツールやビルド時タスクとしてのみ必要で、アプリの実行には無関係――という場合は、PackageReference に ExcludeAssets=runtime を付与して実行時資産(およびそのサテライト)が出力に流れないようにできます。
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design"
Version="<必要なバージョン>"
ExcludeAssets="runtime" />
</ItemGroup>
この設定は そのパッケージの実行時 DLL をコピーしないという宣言です。真にランタイム不要であることを確認してから適用してください。迷う場合は、まずは SatelliteResourceLanguages での抑止を優先しましょう。
具体的な手順:最短で bin をすっきりさせる
- プロジェクトファイル(
.csproj)を開きます。 <PropertyGroup>内に<SatelliteResourceLanguages>zz</SatelliteResourceLanguages>を追加し保存します。- クリーン → リビルドします。
bin\Debug\net8.0[-windows]を確認し、言語フォルダーが出力されていないことをチェックします。
公開(publish)時にも効くの?
はい。dotnet publish でも同じプロパティが効きます。SelfContained / FrameworkDependent、PublishSingleFile の別に関わらず、指定カルチャのみが含まれます。単一ファイル配布の場合はバンドル内部の構成が最適化され、展開先に不要な言語ディレクトリが現れなくなります。
副作用と安全性:何が変わり、何が変わらないか
- 例外メッセージや内部ログの言語:多くのライブラリはニュートラルリソースとして英語を同梱しています。対象カルチャのサテライトを除外すると、英語の文言にフォールバックします。
- アプリ UI:WPF の
ResourceDictionaryや ResX に基づくローカライズを 自前で実装していない限り、UI の表示に影響はありません。多言語 UI を提供している場合は必要カルチャだけを残してください。 - パフォーマンス:起動時のディレクトリ探索やファイル I/O がわずかに減るため、起動時間やウイルススキャンの負荷が軽くなります。リビルド・発行の時間も短縮される傾向があります。
状況別のおすすめ設定(早見表)
| やりたいこと | 最適解 | 設定例 | 主なメリット | 注意点 |
|---|---|---|---|---|
| 全部いらないから出さない | サテライトを全面抑止 | <SatelliteResourceLanguages>zz</SatelliteResourceLanguages> | bin が最小化、差分も減少 | 将来の多言語化時は設定の見直しが必要 |
| 日本語と英語だけ欲しい | 必要カルチャを列挙 | <SatelliteResourceLanguages>ja;en;en-US</SatelliteResourceLanguages> | 必要最小限のフォルダーのみ出力 | カルチャ名の表記ゆれに注意 |
| 実行時に不要なパッケージが原因 | アンインストール | 該当パッケージを削除 | 依存チェーンごと縮小 | 本当に不要かを確認 |
| 設計時だけ必要 | 実行時資産を除外 | ExcludeAssets="runtime" | ランタイムへの流入を遮断 | 誤って必要 DLL を除かないよう注意 |
カルチャ名の書き方とサンプル
カルチャ名は 言語または言語-地域で指定します。よく使う例を挙げます。
| 用途 | カルチャ名 | 備考 |
|---|---|---|
| 日本語(地域指定なし) | ja | 多くのケースで十分 |
| 日本語(日本) | ja-JP | 地域で分けたい場合 |
| 英語(汎用) | en | 米/英で共通利用したいとき |
| 英語(米国) | en-US | 米国向け |
| 中国語(簡体字) | zh-Hans | または zh-CN |
| 中国語(繁体字) | zh-Hant | または zh-TW |
| トルコ語 | tr | 今回の例でよく見かける |
| チェコ語 | cs | 今回の例でよく見かける |
原因の見極め:どの参照がサテライトを持ち込んでいるか
出力されたファイル名から逆引きするのが簡単です。例:Microsoft.CodeAnalysis.CSharp.resources.dll があるなら、Microsoft.CodeAnalysis.CSharp を依存に持つパッケージが発生源です。dotnet list package --include-transitive を使うと依存チェーンを一覧できます。
dotnet list MyApp.csproj package --include-transitive
WPF アプリで Microsoft.VisualStudio.Web.CodeGeneration.Design が入っていれば、まずはこれが最有力候補です。除去できない場合でも、SatelliteResourceLanguages の 1 行で影響を封じ込められます。
「消しても大丈夫?」よくある疑問に答えます
Q. サテライトを除外したらアプリが壊れませんか?
A. 通常は問題ありません。サテライトがなくても、.NET はニュートラルのリソースへフォールバックします。UI 多言語化をしている場合や、特定言語のメッセージをユーザーに必須で見せたい場合だけ注意が必要です。
Q. 英語(en)だけにすると例外メッセージが英語になりますか?
A. はい。対象ライブラリのニュートラルが英語であれば英語の文言になります。開発・検証環境ではむしろ調査がしやすくなる利点もあります。
Q. ビルドだけでなく発行(publish)にも効きますか?
A. 効きます。CI/CD で dotnet publish を使う場合も、その成果物から不要な言語フォルダーを省けます。
Q. PrivateAssets="all" ではダメですか?
A. PrivateAssets は主に 参照の伝播を制御するもので、コピー抑止の決め手にはなりません。ランタイムへの流入を止めたいなら ExcludeAssets="runtime" を検討してください。ただし多くのケースでは SatelliteResourceLanguages の方が安全・簡潔です。
検証手順:設定の効果を確実に確認する
- 設定前の
bin\Debug\net8.0-windowsを一旦削除するか、別名フォルダーへ退避します。 SatelliteResourceLanguagesの設定を入れてクリーンビルドします。- コマンドや PowerShell で言語フォルダーの有無を確認します。
# PowerShell 例:言語フォルダーを列挙
Get-ChildItem -Directory -Name . | Where-Object { $_ -match '^[a-z]{2}(-[A-Za-z0-9]+)?$' }
何も表示されなければ抑止成功です。残す言語を設定した場合は、指定したカルチャだけが出力されているかを確認します。
運用のヒント:チーム・CI での落とし穴を避ける
- プロジェクトテンプレートの見直し:テンプレートに
SatelliteResourceLanguagesを含めておくと新規プロジェクトでも迷いません。 - 複数プロジェクト構成:ソリューション内の 起動プロジェクト(最終成果物を生成するプロジェクト)に設定すれば十分です。ライブラリ側に設定しても悪影響はありません。
- レビューの観点:
PackageReferenceの追加時に「ランタイムで本当に必要か?」をチェックする運用にしましょう。設計時限定ならExcludeAssets="runtime"の検討が定型化できます。
応用:WPF の多言語化と両立させる
アプリ自体で多言語 UI を実装している場合は、SatelliteResourceLanguages に 提供カルチャだけを列挙します。例えば日本語と英語のみ対応であれば ja;en の 2 つに限定し、他のカルチャ(cs や tr 等)は出力されません。自作のリソース辞書(Resources.ja.resx など)と、依存パッケージのサテライトの双方に一貫して適用され、成果物の予見可能性が高まります。
最終手段:どうしても残る場合の後処理(非推奨)
一部のカスタム構成や古い SDK で SatelliteResourceLanguages が効かない場合、MSBuild ターゲットで出力後に削除する方法もあります。ただし原則として 前述の方法で解決するのがベストプラクティスです。下記は参考実装であり、適用は自己責任でお願いします。
<Target Name="AfterBuild_RemoveSatellite" AfterTargets="Build">
<ItemGroup>
<CulturesToRemove Include="cs;de;es;fr;it;pl;pt;ru;tr;zh-Hans;zh-Hant"></CulturesToRemove>
</ItemGroup>
<RemoveDir Directories="@(CulturesToRemove->'$(OutDir)%(Identity)')" />
</Target>
この方式は将来のメンテナンスコストや誤削除リスクがあるため、まずは SatelliteResourceLanguages と依存見直しで対応しましょう。
効果の目安:サイズ・速度・見通し
- 出力サイズ:言語ごとに数 MB 程度の削減になることが多く、Roslyn 系を大量に含む場合は 10MB を超えて減るケースもあります。
- ビルド速度:コピー対象のファイル数が減るため、差分ビルドの所要時間が短縮されます。CI のキャッシュ効率も向上します。
- レビュー容易性:言語フォルダーがなくなることで、PR のバイナリ差分(特に発行成果物のレビュー)が読みやすくなります。
まとめ
WPF/.NET 8 のビルドで cs / es / tr などの言語フォルダーと Microsoft.CodeAnalysis.*.resources.dll が大量に出力される主因は、依存パッケージに含まれるローカライズ済みアセンブリが SDK によって自動コピーされるためです。対策はシンプルで、まずは SatelliteResourceLanguages を設定して “残す言語” を宣言すること。さらに、不要なパッケージは削除し、設計時限定のものは ExcludeAssets="runtime" を付ける――この 3 点を押さえれば、bin フォルダーはすっきりし、成果物のサイズ・速度・見通しが大きく改善します。
最小の変更で最大の効果を得るには、まず <SatelliteResourceLanguages>zz</SatelliteResourceLanguages> の 1 行から始めてください。必要になった時点でカルチャを足す――それが安全で堅牢な運用です。
付録:設定スニペット集
WPF/.NET 8(フレームワーク依存)の最小構成
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<SatelliteResourceLanguages>zz</SatelliteResourceLanguages>
</PropertyGroup>
</Project>
必要カルチャのみを許可(日本語・英語)
<PropertyGroup>
<SatelliteResourceLanguages>ja;en;en-US</SatelliteResourceLanguages>
</PropertyGroup>
設計時専用パッケージのランタイム除外(必要時のみ)
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design"
Version="<必要なバージョン>"
ExcludeAssets="runtime" />
</ItemGroup>
PowerShell で bin の言語フォルダーを一括列挙(確認用)
# カレントを bin\Debug\net8.0-windows にしてから実行
Get-ChildItem -Directory -Name . | Where-Object { $_ -match '^[a-z]{2}(-[A-Za-z0-9]+)?$' }
MSBuild タスクでの削除(非推奨・参考)
<Target Name="AfterBuild_RemoveSatellite" AfterTargets="Build">
<ItemGroup>
<CulturesToRemove Include="cs;de;es;fr;it;pl;pt;ru;tr;zh-Hans;zh-Hant"></CulturesToRemove>
</ItemGroup>
<RemoveDir Directories="@(CulturesToRemove->'$(OutDir)%(Identity)')" />
</Target>
チェックリスト
- bin に多言語フォルダーが出て困っている →
SatelliteResourceLanguagesを設定したか? - WPF プロジェクトに Web スキャフォールディングの参照が残っていないか → 使っていなければ削除。
- 設計時だけの依存はないか →
ExcludeAssets="runtime"を検討。 - 多言語 UI を提供しているか → 提供カルチャだけを列挙して、他は出力させない。
- CI の発行成果物 → publish にもプロパティが効いているか確認。

コメント