У меня есть простые Windows Forms (C#.NET 2.0) приложение, созданное с Visual Studio 2008.
Я хотел бы поддерживать несколько языков UI и использования свойства "Localizable" формы и определенных для культуры .resx файлов, работы аспекта локализации беспрепятственно и легко. Visual Studio автоматически компилирует определенные для культуры resx файлы в сборки-сателлиты, таким образом, в моей скомпилированной папке приложения существуют определенные для культуры подпапки, содержащие эти сборки-сателлиты.
Я хотел бы иметь приложение быть развернутым (скопированный в место) как единственный блок и все же сохранил бы способность содержать несколько наборов определенных для культуры ресурсов.
Используя ILMerge (или ILRepack), я могу объединить сборки-сателлиты в основной исполняемый блок, но стандартная.NET, механизмы нейтрализации ResourceManager не находят определенные для культуры ресурсы, которые были скомпилированы в основной блок.
Интересно, если я беру свой объединенный (исполняемый) блок и копии места его в определенные для культуры подпапки, затем все работает! Точно так же я вижу основные и определенные для культуры ресурсы в объединенном assemby, когда я использую Отражатель (или ILSpy). Но копирование основного блока в определенные для культуры подпапки побеждает цель слияния так или иначе - я действительно должен там быть просто единственной копией единственного блока...
Я задаюсь вопросом, существует ли какой-либо способ угнать или влиять на механизмы нейтрализации ResourceManager для поиска определенных для культуры ресурсов в том же блоке, а не в GAC и названных культурой подпапках. Я вижу механизм нейтрализации, описанный в следующих статьях, но никакая подсказка относительно того, как он был бы изменен: Статья Блога Команды BCL о ResourceManager.
У кого-либо есть какая-либо идея? Это, кажется, относительно частый вопрос онлайн (например, другой вопрос здесь на Переполнении стека: "ILMerge и локализованные сборки ресурсов"), но я не нашел авторитетного ответа нигде.
Рекомендация следующего casperOne ниже, мне наконец удалось делать эту работу.
Я помещаю код решения здесь в вопрос, потому что casperOne предоставил единственный ответ, я не хочу добавлять свое собственное.
Я смог заставить это работать, вытащив кишки из механизмов нейтрализации нахождения ресурса Платформы, реализованных в методе "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);
}
}
}
}
Для фактического использования этого класса необходимо заменить Систему. ComponentModel. ComponentResourceManager в файлах "XXX.Designer.cs", созданных Visual Studio - и необходимо будет сделать этот каждый раз, когда Вы изменяете разработанную форму - замены Visual Studio тот код автоматически. (Проблема была обсуждена в, "Настраивают Разработчика Windows Forms для использования MyResourceManager", я не нашел более изящное решение - я использую fart.exe на шаге перед сборкой к автозамене.)
В то время, когда я сообщил о решении выше, я на самом деле только поддерживал два языка, и 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
Другое быстрое примечание - Одна из вещей, которые беспокоили меня ILMerge, была то, что это - дополнительный собственный инструмент Microsoft, не установленный по умолчанию с Visual Studio и поэтому дополнительной зависимостью, которая делает его что немного тяжелее для третьего лица для начала работы с моими проектами с открытым исходным кодом.
Я недавно обнаружил ILRepack, открытый исходный код (Apache 2.0), эквивалентный, который до сих пор работает точно также на меня (общедоступная замена) и может быть свободно распределен с Вашими источниками проекта.
Я надеюсь, что это помогает кому-то там!
Единственный способ увидеть, как это работает, это создать класс, который берет свое начало от ResourceManager
и затем переопределяет методы InternalGetResourceSet
и GetResourceFileName
. Оттуда вы должны иметь возможность переопределить, где получены ресурсы, приведя пример CultureInfo
.