none
多言語対応のWinformアプリケーションを単一のexeで作成したい RRS feed

  • 質問

  • 多言語対応したWinformを作成しています。
    ベースの言語は日本語で、他言語として英語(en-US)を利用します。
    リソースファイルは、日本語と英語の2つを用意しています。

    ●プロジェクトのファイル構成
     MyProject/
      + Resources.en-US.resx  (英語リソースファイル)
      + Resources.resx        (日本語リソースファイル)


    ビルドすると、MyProject.exeの他に、en-USフォルダに入ったdllが作成されます。

    ●ビルド後のファイル
     + bin/Debug/
       + MyProject.exe
       + en-US/
         + MyProject.resources.dll

    exeとdllを単純にILMergeでマージすると、英語表示ができません。
    うまくexeとdllをマージして、シングルのexeにしたいのです。

    WPFの例では、こちらに同様な例がありました。

    https://social.msdn.microsoft.com/Forums/ja-JP/a68caf09-4ec3-421c-86fc-1a7b4936b504/22810353283548623550245401237512383124501250312522124341123881?forum=wpfja

    残念ながら、この手法がWinformにも応用できるのかどうか、できるとしてもそのやり方がわかりません。

    どなたかアドバイスいただければ幸いです。

    よろしくお願いいたします。

    2019年8月29日 2:26

すべての返信

  • ResourceManagerがリソースの管理を行っているので、独自のResourceManagerで埋め込みリソースから読み込むようにしてやればいいです。

    FormのLocalizableプロパティをtrueにしてLanguageを規定とen-USで作ってあって切り替える場合は、ComponentResourceManagerがリソースの適用を行っているので、同様に埋め込みリソースから読み込むようにしてやればいいです。

    My ProjectフォルダにあるResources.Designer.vb

    Namespace My.Resources
        Friend Module Resources
    
            Private resourceMan As Global.System.Resources.ResourceManager
    
            Private resourceCulture As Global.System.Globalization.CultureInfo
    
            <Global.System.ComponentModel.EditorBrowsableAttribute(Global.System.ComponentModel.EditorBrowsableState.Advanced)>
            Friend ReadOnly Property ResourceManager() As Global.System.Resources.ResourceManager
                Get
                    If Object.ReferenceEquals(resourceMan, Nothing) Then
                        'ここで独自のResourceManagerを使うように書き換える
                        Dim temp As Global.System.Resources.ResourceManager = New ResourceManagerEx("WindowsApp1.Resources", GetType(Resources).Assembly)
                        resourceMan = temp
                    End If
                    Return resourceMan
                End Get
            End Property
    
    '以下省略
    Class ResourceManagerEx
        Inherits System.Resources.ResourceManager
    
        Dim _baseName As String
        Dim _asm As System.Reflection.Assembly
    
        Public Sub New(baseName As String, assembly As System.Reflection.Assembly)
            MyBase.New(baseName, assembly)
    
            Me._baseName = baseName
            Me._asm = assembly
        End Sub
    
        Protected Overrides Function InternalGetResourceSet(culture As CultureInfo, createIfNotExists As Boolean, tryParents As Boolean) As ResourceSet
            '要求されるカルチャを元に、埋め込まれているリソースから探して返す
    
            Dim retval As ResourceSet = Nothing
    
    
            Dim temp As String = Me._baseName + "."
    
            For Each name As String In Me.MainAssembly.GetManifestResourceNames().Where(Function(x) x.StartsWith(temp) AndAlso x.EndsWith(".resources")).OrderBy(Function(x) x.Length)
                Dim nameafter = name.Substring(temp.Length)
                Dim parts = nameafter.Split(".")
    
                Dim key As System.Globalization.CultureInfo
                If parts.Length <> 2 Then
                    Continue For
                End If
    
                key = System.Globalization.CultureInfo.GetCultureInfo(parts(0))
    
                If key.Equals(culture) Then
                    Dim q = Me.MainAssembly.GetManifestResourceInfo(name)
                    Dim stream = Me.MainAssembly.GetManifestResourceStream(name)
    
                    retval = New ResourceSet(stream)
                    Exit For
                End If
    
                If tryParents Then
                    key = key.Parent
                    If key.Equals(culture) Then
                        Dim q = Me.MainAssembly.GetManifestResourceInfo(name)
                        Dim stream = Me.MainAssembly.GetManifestResourceStream(name)
    
                        retval = New ResourceSet(stream)
                        Exit For
                    End If
                End If
            Next
    
            If retval Is Nothing Then
                retval = MyBase.InternalGetResourceSet(culture, createIfNotExists, tryParents)
            End If
            Return retval
        End Function
    End Class

    Form.Desugber.vb

    <Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
    Partial Class Form1
        Inherits System.Windows.Forms.Form
    
        Private Sub InitializeComponent()
    
            'ここで独自のComponentResourceManagerを使うように書き換える
            Dim resources As System.ComponentModel.ComponentResourceManager = New ComponentResourceManagerEx(GetType(Form1))
    
    '省略
    Imports System
    Imports System.Globalization
    Imports System.Resources
    
    Public Class Form1
        Sub New()
            System.Diagnostics.Debugger.Launch()
    
            'System.Globalization.CultureInfo.CurrentCulture = System.Globalization.CultureInfo.GetCultureInfo("en-US")
            'System.Threading.Thread.CurrentThread.CurrentUICulture = System.Globalization.CultureInfo.GetCultureInfo("en-US")
    
            ' この呼び出しはデザイナーで必要です。
            InitializeComponent()
    
            ' InitializeComponent() 呼び出しの後で初期化を追加します。
    
            MessageBox.Show(My.Resources.TestKey)
        End Sub
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            Dim f = New Form2()
            f.ShowDialog()
        End Sub
    End Class
    
    Class ComponentResourceManagerEx
        Inherits System.ComponentModel.ComponentResourceManager
    
        Dim targetType As Type
    
        Sub New(targetFormType As Type)
            MyBase.New(targetFormType)
            If Not GetType(Form).IsAssignableFrom(targetFormType) Then
                Throw New ArgumentException(NameOf(targetFormType))
            End If
            Me.targetType = targetFormType
        End Sub
    
        Protected Overrides Function InternalGetResourceSet(culture As CultureInfo, createIfNotExists As Boolean, tryParents As Boolean) As ResourceSet
            '要求されるカルチャを元に、埋め込まれているリソースから探して返す
    
            Dim retval As ResourceSet = Nothing
    
            Dim temp As String = targetType.FullName + "."
    
            For Each name As String In Me.MainAssembly.GetManifestResourceNames().Where(Function(x) x.StartsWith(temp) AndAlso x.EndsWith(".resources")).OrderBy(Function(x) x.Length)
                Dim nameafter = name.Substring(temp.Length)
                Dim parts = nameafter.Split(".")
    
                Dim key As System.Globalization.CultureInfo
                If parts.Length <> 2 Then
                    Continue For
                End If
    
                key = System.Globalization.CultureInfo.GetCultureInfo(parts(0))
    
                If key.Equals(culture) Then
                    Dim q = Me.MainAssembly.GetManifestResourceInfo(name)
                    Dim stream = Me.MainAssembly.GetManifestResourceStream(name)
    
                    retval = New ResourceSet(stream)
                    Exit For
                End If
    
                If tryParents Then
                    key = key.Parent
                    If key.Equals(culture) Then
                        Dim q = Me.MainAssembly.GetManifestResourceInfo(name)
                        Dim stream = Me.MainAssembly.GetManifestResourceStream(name)
    
                        retval = New ResourceSet(stream)
                        Exit For
                    End If
                End If
            Next
    
            If retval Is Nothing Then
                retval = MyBase.InternalGetResourceSet(culture, createIfNotExists, tryParents)
            End If
            Return retval
        End Function
    End Class
    
    Class ResourceManagerEx
        Inherits System.Resources.ResourceManager
    
        Dim _baseName As String
        Dim _asm As System.Reflection.Assembly
    
        Public Sub New(baseName As String, assembly As System.Reflection.Assembly)
            MyBase.New(baseName, assembly)
    
            Me._baseName = baseName
            Me._asm = assembly
        End Sub
    
        Protected Overrides Function InternalGetResourceSet(culture As CultureInfo, createIfNotExists As Boolean, tryParents As Boolean) As ResourceSet
            '要求されるカルチャを元に、埋め込まれているリソースから探して返す
    
            Dim retval As ResourceSet = Nothing
    
    
            Dim temp As String = Me._baseName + "."
    
            For Each name As String In Me.MainAssembly.GetManifestResourceNames().Where(Function(x) x.StartsWith(temp) AndAlso x.EndsWith(".resources")).OrderBy(Function(x) x.Length)
                Dim nameafter = name.Substring(temp.Length)
                Dim parts = nameafter.Split(".")
    
                Dim key As System.Globalization.CultureInfo
                If parts.Length <> 2 Then
                    Continue For
                End If
    
                key = System.Globalization.CultureInfo.GetCultureInfo(parts(0))
    
                If key.Equals(culture) Then
                    Dim q = Me.MainAssembly.GetManifestResourceInfo(name)
                    Dim stream = Me.MainAssembly.GetManifestResourceStream(name)
    
                    retval = New ResourceSet(stream)
                    Exit For
                End If
    
                If tryParents Then
                    key = key.Parent
                    If key.Equals(culture) Then
                        Dim q = Me.MainAssembly.GetManifestResourceInfo(name)
                        Dim stream = Me.MainAssembly.GetManifestResourceStream(name)
    
                        retval = New ResourceSet(stream)
                        Exit For
                    End If
                End If
            Next
    
            If retval Is Nothing Then
                retval = MyBase.InternalGetResourceSet(culture, createIfNotExists, tryParents)
            End If
            Return retval
        End Function
    End Class

    おまけ:ビルド後のイベントのコマンドライン

    set ilmerge="%ProgramFiles%\Microsoft\ILMerge\ILMerge.exe" 
    
    set exe="$(TargetPath)"
    set exeTemp="$(TargetDir)_$(TargetFileName)"
    
    if not exist %ilmerge%  (
        set ilmerge="%ProgramFiles(x86)%\Microsoft\ILMerge\ILMerge.exe" 
    )
    
    cd $(TargetDir)
    
    setlocal enabledelayedexpansion
    for /d %%d in ("$(TargetDir)*.*") do call :work "%%d"
    exit  /b
    
    :work
    
      set dll="%~1\$(TargetName).resources.dll"
    
      if not exist %dll% exit /b
      if not exist $(TargetPath) exit /b
    
      move %exe% %exeTemp%
    
      %ilmerge% /ndebug /v4 /out:%exe% "%exeTemp%" %dll%
    
      del %exeTemp%
    
    exit /b


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    2019年8月29日 10:31
  • まや358さん、こんにちは。フォーラムオペレーターのHarukaです。
    MSDNフォーラムにご投稿くださいましてありがとうございます。

    ご質問いただいた件ですが、その後いかがでしょうか。
    gekkaさんから寄せられた投稿はお役に立ちましたか。

    参考になった投稿には [回答としてマーク] をお願い致します。

    設定いただくことで、
    他のユーザーもお役に立つ回答を見つけやすくなります。

    お手数ですが、ご協力の程どうかよろしくお願いいたします。


    MSDN/ TechNet Community Support Haruka

    ~参考になった投稿には「回答としてマーク」をご設定ください。なかった場合は「回答としてマークされていない」も設定できます。同じ問題で後から参照した方が、情報を見つけやすくなりますので、
    ご協力くださいますようお願いいたします。また、MSDNサポートに賛辞や苦情がある場合は、MSDNFSF@microsoft.comまでお気軽にお問い合わせください。~

    2019年9月2日 8:36
    モデレータ