Развертывание многоязычных Windows Forms для одной сборки (ILMerge и сателлитные сборки / локализация) - возможно?
У меня есть простое приложение Windows Forms (C #, .NET 2.0), созданное с помощью Visual Studio 2008.
Я хотел бы поддерживать несколько языков пользовательского интерфейса, и используя свойство «Localizable» формы и специфичные для культуры файлы .resx, аспект локализации работает легко и просто. Visual Studio автоматически компилирует файлы resx для конкретной культуры в спутниковые сборки, поэтому в моей папке скомпилированных приложений есть подпапки для конкретных культур, содержащие эти спутниковые сборки.
Я хотел бы, чтобы приложение было развернуто (скопировано на место) какодиночная сборкаи все же сохранить способность содержать несколько наборов специфичных для культуры ресурсов.
С помощьюILMerge (или жеILRepack), Я могу объединить сателлитные сборки в основную исполняемую сборку, но стандартные резервные механизмы .NET ResourceManager не находят специфичные для культуры ресурсы, которые были скомпилированы в основную сборку.
Интересно, что если я возьму свою объединенную (исполняемую) сборку и поместу ее копии в подпапки для конкретной культуры, то все будет работать! Точно так же я вижу основные и специфичные для культуры ресурсы в объединенной сборке, когда используюрефлектор (или жеILSpy). Но копирование основной сборки в подпапки, зависящие от культуры, в любом случае отрицательно сказывается на цели слияния - мне действительно нужна только одна копия одной сборки ...
я задаюсь вопросомсуществует ли какой-либо способ перехвата или влияния на механизмы отката ResourceManager для поиска специфичных для культуры ресурсов в той же сборке, а не в GAC и подпапках с именами культур, Я вижу резервный механизм, описанный в следующих статьях, но не знаю, как он будет изменен:Статья блога команды BCL на ResourceManager.
У кого-нибудь есть идеи? Похоже, это довольно частый вопрос в Интернете (например, другой вопрос здесь о переполнении стека: "ILMerge и локализованные сборки ресурсов"), но я нигде не нашел никакого авторитетного ответа.
ОБНОВЛЕНИЕ 1: Основное решениеСледующийрекомендация casperOne нижеЯ наконец смог сделать эту работу.
Я поставил здесь код решения в вопросе, потому что casperOne предоставил единственный ответ, я не хочу добавлять свой собственный.
Я смог заставить его работать, вытащив кишки из механизмов отката поиска ресурсов Framework, реализованных в методе «InternalGetResourceSet», и сделав наш поиск в одной сборкепервый механизм используется. Если ресурс не найден в текущей сборке, то мы вызываем базовый метод для запуска механизмов поиска по умолчанию (благодаря комментарию @ Wouter ниже).
Для этого я получил класс «ComponentResourceManager» и переопределил только один метод (и повторно реализовал метод с частной структурой):
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);
}
}
}
}
Чтобы фактически использовать этот класс, вам необходимо заменить System.ComponentModel.ComponentResourceManager в файлах «XXX.Designer.cs», созданных Visual Studio, - и вам придется делать это каждый раз, когда вы изменяете разработанную форму - Visual Studio заменяет эту код автоматически. (Проблема обсуждалась вНастройте Windows Forms Designer для использования MyResourceManager«Я не нашел более элегантного решения - я используюfart.exe на этапе предварительной сборки для автоматической замены.)
ОБНОВЛЕНИЕ 2: еще одно практическое соображение - более 2 языковВ то время, когда я сообщал о решении выше, я фактически поддерживал только два языка, и ILMerge прекрасно справлялся со слиянием моей спутниковой сборки в окончательную объединенную сборку.
Недавно я начал работать над аналогичным проектом, где естьмножественный второстепенные языки, и, следовательно, несколько спутниковых сборок, и ILMerge делал что-то очень странное: вместо объединения нескольких спутниковых сборок, которые я запрашивал, он объединял первую спутниковую сборку несколько раз!
например, командная строка:
"c:\Program Files\Microsoft\ILMerge\ILMerge.exe" /t:exe /out:%1SomeFinalProg.exe %1InputProg.exe %1es\InputProg.resources.dll %1fr\InputProg.resources.dll
С помощью этой командной строки я получал следующие наборы ресурсов в объединенной сборке (наблюдается с помощью декомпилятора ILSpy):
InputProg.resources
InputProg.es.resources
InputProg.es.resources <-- Duplicated!
После некоторой игры я понял, что это просто ошибка в ILMerge, когда он сталкиваетсянесколько файлов с одинаковым именем в одном вызове командной строки. Решение состоит в том, чтобы просто объединить каждую спутниковую сборку в другой вызов командной строки:
"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
Когда я делаю это, получающиеся ресурсы в окончательной сборке являются правильными:
InputProg.resources
InputProg.es.resources
InputProg.fr.resources
Итак, наконец, в случае, если это поможет уточнить, вот полный пакетный файл после сборки:
"%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
ОБНОВЛЕНИЕ 3: ILRepackЕще одно быстрое замечание. Одна из вещей, которые меня беспокоили в связи с ILMerge, заключалась в том, что это дополнительный проприетарный инструмент Microsoft, не устанавливаемый по умолчанию вместе с Visual Studio, и, следовательно, дополнительная зависимость, которая затрудняет начало работы третьей стороны. с моими проектами с открытым исходным кодом.
Я недавно обнаружилILRepack, эквивалент с открытым исходным кодом (Apache 2.0), который до сих пор работает для меня так же хорошо (замена вставки), и может свободно распространяться с исходными текстами вашего проекта.
Я надеюсь, что это помогает кому-то там!