Implantação multilíngüe do Windows Forms de montagem única (ILMerge e assemblies / localização por satélite) - possível?

Eu tenho um aplicativo simples do Windows Forms (C #, .NET 2.0), criado com o Visual Studio 2008.

Gostaria de oferecer suporte a vários idiomas da interface do usuário e usando a propriedade "Localizable" do formulário e os arquivos .resx específicos da cultura, o aspecto de localização funciona de maneira simples e direta. O Visual Studio compila automaticamente os arquivos resx específicos da cultura em assemblies satélites, portanto, na minha pasta de aplicativos compilada, há subpastas específicas da cultura contendo esses assemblies de satélite.

Eu gostaria de ter o aplicativo ser implantado (copiado no lugar) como ummontagem únicae ainda manter a capacidade de conter vários conjuntos de recursos específicos da cultura.

UsandoILMerge (ouILRepack), Posso mesclar os assemblies de satélite no assembly executável principal, mas os mecanismos de fallback padrão do .NET ResourceManager não localizam os recursos específicos da cultura que foram compilados no assembly principal.

Curiosamente, se eu pegar meu assembly (executável) mesclado e colocar cópias dele nas subpastas específicas da cultura, então tudo funciona! Da mesma forma, posso ver os recursos principais e específicos da cultura na montagem mesclada quando usoRefletor (ouILSpy). Mas copiar o assembly principal em subpastas específicas de cultura acaba com o propósito da mesclagem - eu realmente preciso que haja apenas uma única cópia do assembly único ...

Estou me perguntandose há alguma maneira de seqüestrar ou influenciar os mecanismos de fallback do ResourceManager para procurar os recursos específicos da cultura no mesmo assembly em vez das subpastas do GAC e da cultura. Eu vejo o mecanismo de fallback descrito nos artigos a seguir, mas nenhum indício de como seria modificado:Artigo do Blog da Equipe BCL no ResourceManager.

Alguém tem alguma ideia? Esta parece ser uma questão relativamente frequente online (por exemplo, outra questão aqui no Stack Overflow: "ILMerge e assemblies de recursos localizados"), mas eu não encontrei nenhuma resposta autoritária em nenhum lugar.

ATUALIZAÇÃO 1: Solução Básica

SegueA recomendação de casperOne abaixoFinalmente consegui fazer isso funcionar.

Eu estou colocando o código da solução aqui na questão porque o casperOne forneceu a única resposta, eu não quero adicionar o meu próprio.

Consegui fazê-lo funcionar, retirando o foco dos mecanismos de fallback de descoberta de recursos do Framework implementados no método "InternalGetResourceSet" e fazendo com que a pesquisa na mesma reunião fosseprimeiro mecanismo utilizado. Se o recurso não for encontrado no conjunto atual, chamamos o método base para iniciar os mecanismos de pesquisa padrão (graças ao comentário de @ Wouter abaixo).

Para fazer isso, eu derivei a classe "ComponentResourceManager" e anulei apenas um método (e reimplantei um método de framework privado):

class SingleAssemblyComponentResourceManager : 
    System.ComponentModel.ComponentResourceManager
{
    private Type _contextTypeInfo;
    private CultureInfo _neutralResourcesCulture;

    public SingleAssemblyComponentResourceManager(Type t)
        : base(t)
    {
        _contextTypeInfo = t;
    }

    protected override ResourceSet InternalGetResourceSet(CultureInfo culture, 
        bool createIfNotExists, bool tryParents)
    {
        ResourceSet rs = (ResourceSet)this.ResourceSets[culture];
        if (rs == null)
        {
            Stream store = null;
            string resourceFileName = null;

            //lazy-load default language (without caring about duplicate assignment in race conditions, no harm done);
            if (this._neutralResourcesCulture == null)
            {
                this._neutralResourcesCulture = 
                    GetNeutralResourcesLanguage(this.MainAssembly);
            }

            // if we're asking for the default language, then ask for the
            // invariant (non-specific) resources.
            if (_neutralResourcesCulture.Equals(culture))
                culture = CultureInfo.InvariantCulture;
            resourceFileName = GetResourceFileName(culture);

            store = this.MainAssembly.GetManifestResourceStream(
                this._contextTypeInfo, resourceFileName);

            //If we found the appropriate resources in the local assembly
            if (store != null)
            {
                rs = new ResourceSet(store);
                //save for later.
                AddResourceSet(this.ResourceSets, culture, ref rs);
            }
            else
            {
                rs = base.InternalGetResourceSet(culture, createIfNotExists, tryParents);
            }
        }
        return rs;
    }

    //private method in framework, had to be re-specified here.
    private static void AddResourceSet(Hashtable localResourceSets, 
        CultureInfo culture, ref ResourceSet rs)
    {
        lock (localResourceSets)
        {
            ResourceSet objA = (ResourceSet)localResourceSets[culture];
            if (objA != null)
            {
                if (!object.Equals(objA, rs))
                {
                    rs.Dispose();
                    rs = objA;
                }
            }
            else
            {
                localResourceSets.Add(culture, rs);
            }
        }
    }
}

Para realmente usar essa classe, você precisa substituir o System.ComponentModel.ComponentResourceManager nos arquivos "XXX.Designer.cs" criados pelo Visual Studio - e você precisará fazer isso toda vez que alterar o formulário criado - o Visual Studio substitui esse código automaticamente. (O problema foi discutido em "Personalizar o Windows Forms Designer para usar MyResourceManager", Eu não encontrei uma solução mais elegante - eu usofart.exe em uma etapa de pré-compilação para substituição automática.)

ATUALIZAÇÃO 2: Outra consideração prática - mais de 2 idiomas

Na época em que relatei a solução acima, na verdade eu estava apenas suportando dois idiomas, e o ILMerge estava fazendo um ótimo trabalho de mesclar meu assembly satélite no assembly mesclado final.

Recentemente eu comecei a trabalhar em um projeto similar onde existemmúltiplo idiomas secundários e, portanto, vários assemblies de satélite e o ILMerge estavam fazendo algo muito estranho: em vez de mesclar os vários conjuntos de satélites que havia solicitado, ele estava mesclando o primeiro conjunto de satélites várias vezes!

por exemplo, linha de comando:

"c:\Program Files\Microsoft\ILMerge\ILMerge.exe" /t:exe /out:%1SomeFinalProg.exe %1InputProg.exe %1es\InputProg.resources.dll %1fr\InputProg.resources.dll

Com essa linha de comando, eu estava recebendo os seguintes conjuntos de recursos no assembly mesclado (observado com o decompiler ILSpy):

InputProg.resources
InputProg.es.resources
InputProg.es.resources <-- Duplicated!

Depois de algumas brincadeiras, acabei percebendo que isso é apenas um bug no ILMerge quando ele encontravários arquivos com o mesmo nome em uma única chamada de linha de comando. A solução é simplesmente mesclar cada assembly satélite em uma chamada de linha de comando diferente:

"c:\Program Files\Microsoft\ILMerge\ILMerge.exe" /t:exe /out:%1TempProg.exe %1InputProg.exe %1es\InputProg.resources.dll
"c:\Program Files\Microsoft\ILMerge\ILMerge.exe" /t:exe /out:%1SomeFinalProg.exe %1TempProg.exe %1fr\InputProg.resources.dll

Quando faço isso, os recursos resultantes na montagem final estão corretos:

InputProg.resources
InputProg.es.resources
InputProg.fr.resources

Então, finalmente, caso isso ajude a esclarecer, aqui está um arquivo de lote completo de pós-construção:

"%ProgramFiles%\Microsoft\ILMerge\ILMerge.exe" /t:exe /out:%1TempProg.exe %1InputProg.exe %1es\InputProg.resources.dll 
IF %ERRORLEVEL% NEQ 0 GOTO END

"%ProgramFiles%\Microsoft\ILMerge\ILMerge.exe" /t:exe /out:%1SomeFinalProg.exe %1TempProg.exe %1fr\InputProg.resources.dll 
IF %ERRORLEVEL% NEQ 0 GOTO END

del %1InputProg.exe 
del %1InputProg.pdb 
del %1TempProg.exe 
del %1TempProg.pdb 
del %1es\*.* /Q 
del %1fr\*.* /Q 
:END
ATUALIZAÇÃO 3: ILRepack

Outra nota rápida - Uma das coisas que me incomodaram com o ILMerge foi que ele é uma ferramenta proprietária adicional da Microsoft, não instalada por padrão com o Visual Studio e, portanto, uma dependência extra que torna um pouco mais difícil para um terceiro começar com meus projetos de código aberto.

Eu descobri recentementeILRepack, um equivalente de código-fonte aberto (Apache 2.0) que até agora funciona tão bem para mim (substituição imediata) e pode ser distribuído livremente com as origens do seu projeto.

Eu espero que isto ajude alguém lá fora!

questionAnswers(5)

yourAnswerToTheQuestion