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ązanieNastę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ęzykiW 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: ILRepackKolejna 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ś!