Wielojęzyczne wdrożenie systemu Windows Forms w jednym zestawie (ILMerge i zestawy satelitarne / lokalizacja) - możliwe?

Mam prostą aplikację Windows Forms (C #, .NET 2.0), zbudowaną w Visual Studio 2008.

Chciałbym obsługiwać wiele języków interfejsu użytkownika, a używając właściwości „Lokalizowalny” formularza i plików .resx specyficznych dla kultury, aspekt lokalizacji działa bezproblemowo i łatwo. Visual Studio automatycznie kompiluje pliki resx specyficzne dla kultury do zestawów satelitarnych, więc w moim skompilowanym folderze aplikacji znajdują się podfoldery specyficzne dla kultury zawierające te zestawy satelitarne.

Chciałbym, aby aplikacja została wdrożona (skopiowana na miejsce) jakopojedynczy montaż, a jednak zachować zdolność do przechowywania wielu zestawów zasobów specyficznych dla kultury.

Za pomocąILMerge (lubILRepack), Mogę scalić zespoły satelitarne w głównym zespole wykonywalnym, ale standardowe mechanizmy rezerwowe .NET ResourceManager nie znajdują zasobów specyficznych dla kultury, które zostały wkompilowane w zespół główny.

Co ciekawe, jeśli wezmę mój scalony (wykonywalny) zespół i umieści go w podfolderach specyficznych dla kultury, to wszystko działa! Podobnie, kiedy używam, widzę główne i specyficzne dla kultury zasoby w scalonym zespoleReflektor (lubILSpy). Ale kopiowanie głównego zespołu do podfolderów specyficznych dla kultury i tak pokonuje cel łączenia - naprawdę potrzebuję tylko jednej kopii pojedynczego złożenia ...

zastanawiam sięczy istnieje jakikolwiek sposób na przejęcie lub wpływanie na mechanizmy rezerwowe ResourceManager w celu wyszukania zasobów specyficznych dla kultury w tym samym zespole, a nie w GAC i podfolderach nazwanych kulturą. Widzę mechanizm awaryjny opisany w następujących artykułach, ale nie mam pojęcia, w jaki sposób zostanie on zmodyfikowany:BCL Team Blog Artykuł o ResourceManager.

Czy ktokolwiek ma jakiś pomysł? Wydaje się, że jest to stosunkowo częste pytanie online (na przykład inne pytanie tutaj na temat przepełnienia stosu: „ILMerge i zlokalizowane zestawy zasobów„), ale nigdzie nie znalazłem żadnej wiarygodnej odpowiedzi.

AKTUALIZACJA 1: Podstawowe rozwiązanie

Następującyzalecenie casperOne poniżej, W końcu udało mi się wykonać tę pracę.

Umieszczam tutaj kod rozwiązania, ponieważ casperOne dostarczył jedyną odpowiedź, nie chcę dodawać własnego.

Udało mi się go uruchomić, wyciągając wnętrzności z mechanizmów rezerwowych wyszukiwania zasobów Framework zaimplementowanych w metodzie „InternalGetResourceSet” i sprawiając, że nasze samo złożenie przeszukałopierwszy zastosowany mechanizm. Jeśli zasób nie zostanie znaleziony w bieżącym zespole, wywołujemy metodę bazową, aby zainicjować domyślne mechanizmy wyszukiwania (dzięki komentarzowi @ Wouter poniżej).

Aby to zrobić, wyprowadziłem klasę „ComponentResourceManager” i nadpisałem tylko jedną metodę (i ponownie zaimplementowałem prywatną metodę ramową):

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);
            }
        }
    }
}

Aby faktycznie użyć tej klasy, musisz zastąpić System.ComponentModel.ComponentResourceManager w plikach „XXX.Designer.cs” utworzonych przez Visual Studio - i będziesz musiał to robić za każdym razem, gdy zmienisz zaprojektowaną formę - Visual Studio zastąpi to kod automatycznie. (Problem został omówiony w „Dostosuj Windows Forms Designer do korzystania z MyResourceManager„Nie znalazłem bardziej eleganckiego rozwiązania - używamfart.exe w kroku poprzedzającym budowę, aby automatycznie zastąpić.)

UPDATE 2: Kolejna praktyczna uwaga - więcej niż 2 języki

W momencie, gdy zgłosiłem powyższe rozwiązanie, w rzeczywistości obsługiwałem tylko dwa języki, a ILMerge świetnie sobie radził z łączeniem mojego zestawu satelitarnego w końcowy scalony zespół.

Ostatnio zacząłem pracować nad podobnym projektem, gdzie sąwielokrotność języki wtórne, a tym samym wiele zespołów satelitarnych, a ILMerge robił coś bardzo dziwnego: zamiast łączyć wiele złożonych przeze mnie zespołów satelitarnych, wielokrotnie łączył pierwszy zespół satelitarny!

np. wiersz polecenia:

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

W tym wierszu polecenia otrzymywałem następujące zestawy zasobów w zespolonym zespole (obserwowane z dekompilatorem ILSpy):

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

Po kilku zabawach doszedłem do wniosku, że jest to błąd w ILMerge, gdy się napotkawiele plików o tej samej nazwie w pojedynczym wywołaniu wiersza polecenia. Rozwiązaniem jest po prostu połączenie każdego zestawu satelitarnego w innym wywołaniu wiersza polecenia:

"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

Kiedy to zrobię, wynikowe zasoby w końcowym zespole są poprawne:

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

W końcu, jeśli to pomoże wyjaśnić, oto kompletny plik wsadowy po kompilacji:

"%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
AKTUALIZACJA 3: ILRepack

Kolejna szybka uwaga - Jedną z rzeczy, która przeszkadzała mi w ILMerge, było to, że jest to dodatkowe zastrzeżone narzędzie Microsoft, nie instalowane domyślnie w Visual Studio, a zatem dodatkowa zależność, która sprawia, że ​​rozpoczęcie pracy przez osobę trzecią jest nieco trudniejsze z moimi projektami open source.

Niedawno odkryłemILRepack, odpowiednik open-source (Apache 2.0), który jak dotąd działa równie dobrze dla mnie (wymiana drop-in) i może być swobodnie dystrybuowany ze źródłami projektu.

Mam nadzieję, że to pomoże komuś!

questionAnswers(5)

yourAnswerToTheQuestion